feat(mail): «Создать задачу» и «Создать событие» из письма
Паритет живого веба: пункты в меню действий письма (иконки темы create-task-black/calendar-black) → модалки в стиле темы (заголовок 18/600, плашки-выбор списка/календаря #F5F5F5 r10, дата через DatePicker, «Весь день», время ЧЧ:ММ). Префилл: название = тема письма, описание = «Из письма от <отправитель>». Сохранение через существующие TasksRepository.createTask / CalendarRepository.saveEvent (CalDAV) — feature:mail теперь зависит от feature:tasks и feature:calendar (цикла нет). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -982,6 +982,8 @@ fun MailMessageSenderCard(
|
||||
onMove: () -> Unit,
|
||||
onSnooze: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onCreateTask: (() -> Unit)? = null,
|
||||
onCreateEvent: (() -> Unit)? = null,
|
||||
) {
|
||||
val base = serverUrl.trimEnd('/')
|
||||
var headersExpanded by remember { mutableStateOf(false) }
|
||||
@@ -1126,6 +1128,8 @@ fun MailMessageSenderCard(
|
||||
onEditTags = onEditTags,
|
||||
onMove = onMove,
|
||||
onSnooze = onSnooze,
|
||||
onCreateTask = onCreateTask,
|
||||
onCreateEvent = onCreateEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1185,6 +1189,8 @@ fun MailMessageActionsMenu(
|
||||
onEditTags: () -> Unit,
|
||||
onMove: () -> Unit,
|
||||
onSnooze: () -> Unit,
|
||||
onCreateTask: (() -> Unit)? = null,
|
||||
onCreateEvent: (() -> Unit)? = null,
|
||||
) {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
@@ -1238,6 +1244,18 @@ fun MailMessageActionsMenu(
|
||||
onDismiss()
|
||||
onEditTags()
|
||||
}
|
||||
onCreateTask?.let { create ->
|
||||
MailMessageIconMenuItem(serverUrl, "create-task-black.svg", "Создать задачу") {
|
||||
onDismiss()
|
||||
create()
|
||||
}
|
||||
}
|
||||
onCreateEvent?.let { create ->
|
||||
MailMessageIconMenuItem(serverUrl, "calendar-black.svg", "Создать событие") {
|
||||
onDismiss()
|
||||
create()
|
||||
}
|
||||
}
|
||||
MailMessageIconMenuItem(serverUrl, "folder-black.svg", "Переместить сообщение") {
|
||||
onDismiss()
|
||||
onMove()
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
package ru.forbion.f7cloud.feature.mail
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import ru.forbion.f7cloud.core.designsystem.F7Colors
|
||||
import ru.forbion.f7cloud.core.designsystem.F7OutlinedField
|
||||
import ru.forbion.f7cloud.core.designsystem.F7PrimaryButton
|
||||
import ru.forbion.f7cloud.core.designsystem.F7SecondaryButton
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/** Выбор из вариантов «плашками» — как строки в модалках живой темы (#F5F5F5, r10, 36dp). */
|
||||
@Composable
|
||||
private fun <T> PickerRows(
|
||||
options: List<T>,
|
||||
selected: T?,
|
||||
label: (T) -> String,
|
||||
onSelect: (T) -> Unit,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
options.forEach { option ->
|
||||
val isSelected = option == selected
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 36.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(if (isSelected) F7Colors.PrimaryLight else F7Colors.SurfaceMuted)
|
||||
.then(
|
||||
if (isSelected) {
|
||||
Modifier.border(
|
||||
1.dp,
|
||||
F7Colors.Primary.copy(alpha = 0.25f),
|
||||
RoundedCornerShape(10.dp),
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.clickable { onSelect(option) }
|
||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
label(option),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = F7Colors.TextPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateSheetScaffold(
|
||||
title: String,
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.94f)
|
||||
.heightIn(max = 620.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = Color.White,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
// Живая тема: заголовки модалок 18/600
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = F7Colors.TextPrimary,
|
||||
)
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun DueDateRow(dueDate: LocalDate?, onChange: (LocalDate?) -> Unit) {
|
||||
var pickerOpen by remember { mutableStateOf(false) }
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 36.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(F7Colors.SurfaceMuted)
|
||||
.clickable { pickerOpen = true }
|
||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
dueDate?.format(DateTimeFormatter.ofPattern("d MMMM yyyy"))
|
||||
?: "Срок не задан (нажмите, чтобы выбрать)",
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (dueDate != null) F7Colors.TextPrimary else F7Colors.TextSecondary,
|
||||
)
|
||||
if (dueDate != null) {
|
||||
Text(
|
||||
"Сбросить",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = F7Colors.Primary,
|
||||
modifier = Modifier.clickable { onChange(null) },
|
||||
)
|
||||
}
|
||||
}
|
||||
if (pickerOpen) {
|
||||
val state = rememberDatePickerState(
|
||||
initialSelectedDateMillis = (dueDate ?: LocalDate.now())
|
||||
.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(),
|
||||
)
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { pickerOpen = false },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
state.selectedDateMillis?.let { millis ->
|
||||
onChange(
|
||||
Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault()).toLocalDate(),
|
||||
)
|
||||
}
|
||||
pickerOpen = false
|
||||
}) { Text("ОК", color = F7Colors.Primary) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { pickerOpen = false }) {
|
||||
Text("Отмена", color = F7Colors.TextSecondary)
|
||||
}
|
||||
},
|
||||
) {
|
||||
DatePicker(state = state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Модалка «Создать задачу» из письма (паритет живого веба). */
|
||||
@Composable
|
||||
fun MailCreateTaskSheet(
|
||||
visible: Boolean,
|
||||
taskLists: List<MailTargetList>,
|
||||
defaultSummary: String,
|
||||
defaultDescription: String,
|
||||
creating: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onCreate: (listHref: String, summary: String, description: String, due: LocalDate?) -> Unit,
|
||||
) {
|
||||
if (!visible) return
|
||||
var summary by rememberSaveable(defaultSummary) { mutableStateOf(defaultSummary) }
|
||||
var description by rememberSaveable(defaultDescription) { mutableStateOf(defaultDescription) }
|
||||
var dueDate by remember { mutableStateOf<LocalDate?>(null) }
|
||||
var selectedList by remember(taskLists) { mutableStateOf(taskLists.firstOrNull()) }
|
||||
|
||||
CreateSheetScaffold(title = "Создать задачу", onDismiss = onDismiss) {
|
||||
F7OutlinedField(value = summary, onValueChange = { summary = it }, label = "Название")
|
||||
F7OutlinedField(
|
||||
value = description,
|
||||
onValueChange = { description = it },
|
||||
label = "Описание",
|
||||
minLines = 3,
|
||||
)
|
||||
if (taskLists.isNotEmpty()) {
|
||||
Text("Список задач", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
||||
PickerRows(
|
||||
options = taskLists,
|
||||
selected = selectedList,
|
||||
label = { it.name },
|
||||
onSelect = { selectedList = it },
|
||||
)
|
||||
}
|
||||
Text("Срок", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
||||
DueDateRow(dueDate) { dueDate = it }
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
F7SecondaryButton("Отмена", onClick = onDismiss, modifier = Modifier.weight(1f))
|
||||
F7PrimaryButton(
|
||||
text = if (creating) "Создание…" else "Создать",
|
||||
onClick = {
|
||||
selectedList?.let { onCreate(it.href, summary.trim(), description.trim(), dueDate) }
|
||||
},
|
||||
enabled = !creating && summary.isNotBlank() && selectedList != null,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Модалка «Создать событие» из письма (паритет живого веба). */
|
||||
@Composable
|
||||
fun MailCreateEventSheet(
|
||||
visible: Boolean,
|
||||
calendars: List<MailTargetList>,
|
||||
defaultTitle: String,
|
||||
defaultDescription: String,
|
||||
creating: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onCreate: (
|
||||
calendarHref: String,
|
||||
title: String,
|
||||
description: String,
|
||||
date: LocalDate,
|
||||
startTime: String,
|
||||
endTime: String,
|
||||
allDay: Boolean,
|
||||
) -> Unit,
|
||||
) {
|
||||
if (!visible) return
|
||||
var title by rememberSaveable(defaultTitle) { mutableStateOf(defaultTitle) }
|
||||
var description by rememberSaveable(defaultDescription) { mutableStateOf(defaultDescription) }
|
||||
var date by remember { mutableStateOf(LocalDate.now()) }
|
||||
var startTime by rememberSaveable { mutableStateOf("10:00") }
|
||||
var endTime by rememberSaveable { mutableStateOf("11:00") }
|
||||
var allDay by rememberSaveable { mutableStateOf(false) }
|
||||
var selectedCalendar by remember(calendars) { mutableStateOf(calendars.firstOrNull()) }
|
||||
|
||||
CreateSheetScaffold(title = "Создать событие", onDismiss = onDismiss) {
|
||||
F7OutlinedField(value = title, onValueChange = { title = it }, label = "Название")
|
||||
Text("Дата", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
||||
DueDateRow(date) { picked -> picked?.let { date = it } }
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Checkbox(
|
||||
checked = allDay,
|
||||
onCheckedChange = { allDay = it },
|
||||
colors = CheckboxDefaults.colors(checkedColor = F7Colors.Primary),
|
||||
)
|
||||
Text("Весь день", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextPrimary)
|
||||
}
|
||||
if (!allDay) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
F7OutlinedField(value = startTime, onValueChange = { startTime = it }, label = "Начало (ЧЧ:ММ)")
|
||||
}
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
F7OutlinedField(value = endTime, onValueChange = { endTime = it }, label = "Конец (ЧЧ:ММ)")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (calendars.isNotEmpty()) {
|
||||
Text("Календарь", style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
||||
PickerRows(
|
||||
options = calendars,
|
||||
selected = selectedCalendar,
|
||||
label = { it.name },
|
||||
onSelect = { selectedCalendar = it },
|
||||
)
|
||||
}
|
||||
F7OutlinedField(
|
||||
value = description,
|
||||
onValueChange = { description = it },
|
||||
label = "Описание",
|
||||
minLines = 3,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
F7SecondaryButton("Отмена", onClick = onDismiss, modifier = Modifier.weight(1f))
|
||||
F7PrimaryButton(
|
||||
text = if (creating) "Создание…" else "Создать",
|
||||
onClick = {
|
||||
selectedCalendar?.let {
|
||||
onCreate(it.href, title.trim(), description.trim(), date, startTime, endTime, allDay)
|
||||
}
|
||||
},
|
||||
enabled = !creating && title.isNotBlank() && selectedCalendar != null,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Цель создания (список задач или календарь): href + имя. */
|
||||
data class MailTargetList(val href: String, val name: String)
|
||||
@@ -465,6 +465,34 @@ fun MailScreen(
|
||||
openScheduledReplyForMessage(detail, option)
|
||||
},
|
||||
)
|
||||
// «Создать задачу/событие из письма» — паритет живого веба:
|
||||
// префилл темой письма и отправителем.
|
||||
val createDefaults = state.messageDetail?.let { detail ->
|
||||
detail.subject.ifBlank { "(без темы)" } to
|
||||
"Из письма от ${detail.from.ifBlank { detail.fromEmail }}"
|
||||
}
|
||||
MailCreateTaskSheet(
|
||||
visible = state.createTaskSheetOpen,
|
||||
taskLists = state.createTargets,
|
||||
defaultSummary = createDefaults?.first.orEmpty(),
|
||||
defaultDescription = createDefaults?.second.orEmpty(),
|
||||
creating = state.creatingFromMessage,
|
||||
onDismiss = vm::dismissCreateSheets,
|
||||
onCreate = { listHref, summary, description, due ->
|
||||
vm.createTaskFromMessage(session, listHref, summary, description, due)
|
||||
},
|
||||
)
|
||||
MailCreateEventSheet(
|
||||
visible = state.createEventSheetOpen,
|
||||
calendars = state.createTargets,
|
||||
defaultTitle = createDefaults?.first.orEmpty(),
|
||||
defaultDescription = createDefaults?.second.orEmpty(),
|
||||
creating = state.creatingFromMessage,
|
||||
onDismiss = vm::dismissCreateSheets,
|
||||
onCreate = { calHref, title, description, date, start, end, allDay ->
|
||||
vm.createEventFromMessage(session, calHref, title, description, date, start, end, allDay)
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
MailInboxScreen(
|
||||
@@ -783,6 +811,8 @@ private fun MailMessageDetailHeader(
|
||||
onEditTags = onTagsSheetOpen,
|
||||
onMove = onMoveSheetOpen,
|
||||
onSnooze = onSnoozeSheetOpen,
|
||||
onCreateTask = { vm.openCreateTaskSheet(session) },
|
||||
onCreateEvent = { vm.openCreateEventSheet(session) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.forbion.f7cloud.core.auth.AuthSession
|
||||
@@ -44,6 +45,11 @@ data class MailUiState(
|
||||
val appSettingsSaving: Boolean = false,
|
||||
val error: String? = null,
|
||||
val unauthorized: Boolean = false,
|
||||
// «Создать задачу/событие из письма» (паритет живого веба)
|
||||
val createTaskSheetOpen: Boolean = false,
|
||||
val createEventSheetOpen: Boolean = false,
|
||||
val createTargets: List<MailTargetList> = emptyList(),
|
||||
val creatingFromMessage: Boolean = false,
|
||||
)
|
||||
|
||||
class MailViewModel(
|
||||
@@ -995,6 +1001,104 @@ class MailViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
// --- «Создать задачу/событие из письма» (паритет живого веба) ---
|
||||
|
||||
fun openCreateTaskSheet(session: AuthSession) {
|
||||
_state.update { it.copy(createTaskSheetOpen = true, createTargets = emptyList()) }
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
ru.forbion.f7cloud.feature.tasks.TasksRepository()
|
||||
.listTaskLists(session)
|
||||
.map { MailTargetList(it.href, it.displayName) }
|
||||
}.onSuccess { lists ->
|
||||
_state.update { it.copy(createTargets = lists) }
|
||||
}.onFailure { t ->
|
||||
_state.update { it.copy(createTaskSheetOpen = false, error = t.message) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openCreateEventSheet(session: AuthSession) {
|
||||
_state.update { it.copy(createEventSheetOpen = true, createTargets = emptyList()) }
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
ru.forbion.f7cloud.feature.calendar.CalendarRepository()
|
||||
.listCalendars(session)
|
||||
.map { MailTargetList(it.href, it.displayName) }
|
||||
}.onSuccess { calendars ->
|
||||
_state.update { it.copy(createTargets = calendars) }
|
||||
}.onFailure { t ->
|
||||
_state.update { it.copy(createEventSheetOpen = false, error = t.message) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissCreateSheets() {
|
||||
_state.update { it.copy(createTaskSheetOpen = false, createEventSheetOpen = false) }
|
||||
}
|
||||
|
||||
fun createTaskFromMessage(
|
||||
session: AuthSession,
|
||||
listHref: String,
|
||||
summary: String,
|
||||
description: String,
|
||||
due: java.time.LocalDate?,
|
||||
) {
|
||||
_state.update { it.copy(creatingFromMessage = true) }
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
ru.forbion.f7cloud.feature.tasks.TasksRepository().createTask(
|
||||
session = session,
|
||||
listHref = listHref,
|
||||
summary = summary,
|
||||
dueDate = due,
|
||||
description = description,
|
||||
)
|
||||
}.onSuccess {
|
||||
_state.update {
|
||||
it.copy(creatingFromMessage = false, createTaskSheetOpen = false, error = null)
|
||||
}
|
||||
}.onFailure { t ->
|
||||
_state.update { it.copy(creatingFromMessage = false, error = t.message) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createEventFromMessage(
|
||||
session: AuthSession,
|
||||
calendarHref: String,
|
||||
title: String,
|
||||
description: String,
|
||||
date: java.time.LocalDate,
|
||||
startTime: String,
|
||||
endTime: String,
|
||||
allDay: Boolean,
|
||||
) {
|
||||
_state.update { it.copy(creatingFromMessage = true) }
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
ru.forbion.f7cloud.feature.calendar.CalendarRepository().saveEvent(
|
||||
session,
|
||||
ru.forbion.f7cloud.feature.calendar.CalendarEventDraft(
|
||||
title = title,
|
||||
date = date,
|
||||
startTime = startTime,
|
||||
endTime = endTime,
|
||||
allDay = allDay,
|
||||
description = description,
|
||||
calendarHref = calendarHref,
|
||||
),
|
||||
)
|
||||
}.onSuccess {
|
||||
_state.update {
|
||||
it.copy(creatingFromMessage = false, createEventSheetOpen = false, error = null)
|
||||
}
|
||||
}.onFailure { t ->
|
||||
_state.update { it.copy(creatingFromMessage = false, error = t.message) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MARK_READ_DELAY_MS = 2000L
|
||||
private const val FOLDER_CACHE_TTL_MS = 15 * 60 * 1000L
|
||||
|
||||
Reference in New Issue
Block a user