diff --git a/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt b/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt index 3bf40a858..c8bc3e8c1 100644 --- a/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt +++ b/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt @@ -7,17 +7,134 @@ import androidx.compose.material.ContentAlpha import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.todoroo.astrid.ui.StartDateControlSet.Companion.getRelativeDateString +import kotlinx.coroutines.runBlocking import org.tasks.R import org.tasks.compose.TaskEditRow +import org.tasks.compose.pickers.StartDateTimePicker +import org.tasks.data.entity.Task +import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.dialogs.StartDatePicker +import org.tasks.extensions.Context.is24HourFormat +import org.tasks.kmp.org.tasks.time.DateStyle +import org.tasks.kmp.org.tasks.time.getRelativeDateTime +import org.tasks.preferences.Preferences import org.tasks.themes.TasksTheme import org.tasks.time.DateTimeUtils2.currentTimeMillis +import org.tasks.time.millisOfDay +import org.tasks.time.startOfDay + +@Composable +fun StartDateRow( + current: Long, + setCurrent: (Long) -> Unit, + dueDate: Long, + isNew: Boolean, + hasStartAlarm: Boolean, + showDueDate: Boolean +) { + val context = LocalContext.current + val preferences = remember { Preferences(context) } + + val day = + if (current <= 0L) { + if (isNew) when (preferences.getIntegerFromString(R.string.p_default_hideUntil_key, Task.HIDE_UNTIL_NONE)) { + Task.HIDE_UNTIL_DUE -> StartDatePicker.DUE_DATE + Task.HIDE_UNTIL_DUE_TIME -> StartDatePicker.DUE_TIME + Task.HIDE_UNTIL_DAY_BEFORE -> StartDatePicker.DAY_BEFORE_DUE + Task.HIDE_UNTIL_WEEK_BEFORE -> StartDatePicker.WEEK_BEFORE_DUE + else -> 0L + } + else current + } else { + val dueDay = dueDate.startOfDay() + val dueTime = dueDate.millisOfDay + val hideUntil = current.toDateTime() + when (current) { + dueDay -> if (hideUntil.millisOfDay == dueTime) { + StartDatePicker.DUE_TIME + } else { + StartDatePicker.DUE_DATE + } + dueDay.toDateTime().minusDays(1).millis -> + StartDatePicker.DAY_BEFORE_DUE + dueDay.toDateTime().minusDays(7).millis -> + StartDatePicker.WEEK_BEFORE_DUE + else -> current + } + } + + val time = + if (current > 0L && + day == dueDate.startOfDay() && + current.millisOfDay == dueDate.millisOfDay) + StartDatePicker.NO_TIME + else + current.millisOfDay + + val selectedDay = rememberSaveable { mutableLongStateOf(day) } + val selectedTime = rememberSaveable { mutableIntStateOf(time) } + + fun getSelectedValue(dueDate: Long): Long { + val due = dueDate.takeIf { it > 0 }?.toDateTime() + return when (selectedDay.longValue) { + StartDatePicker.DUE_DATE -> due?.withMillisOfDay(selectedTime.intValue)?.millis ?: 0 + StartDatePicker.DUE_TIME -> due?.millis ?: 0 + StartDatePicker.DAY_BEFORE_DUE -> due?.minusDays(1)?.withMillisOfDay(selectedTime.intValue)?.millis ?: 0 + StartDatePicker.WEEK_BEFORE_DUE -> due?.minusDays(7)?.withMillisOfDay(selectedTime.intValue)?.millis ?: 0 + else -> selectedDay.longValue + selectedTime.intValue + } + } + + val showPicker = remember { mutableStateOf(false) } + + StartDateRow( + startDate = current, + selectedDay = selectedDay.longValue, + selectedTime = selectedTime.intValue, + hasStartAlarm = hasStartAlarm, + hasDueDate = dueDate > 0L, + printDate = { + runBlocking { + getRelativeDateTime( + selectedDay.longValue + selectedTime.intValue, + context.is24HourFormat, + DateStyle.FULL, + alwaysDisplayFullDate = preferences.alwaysDisplayFullDate + ) + } + }, + onClick = { showPicker.value = true} + ) + + LaunchedEffect(dueDate) { setCurrent(getSelectedValue(dueDate)) } + + if (showPicker.value) { + StartDateTimePicker( + selectedDay = selectedDay.longValue, + selectedTime = selectedTime.intValue, + updateValues = { day, time -> selectedDay.longValue = day; selectedTime.intValue = time }, + accept = { setCurrent(getSelectedValue(dueDate)); showPicker.value = false }, + dismiss = { showPicker.value = false }, + autoclose = preferences.getBoolean( + R.string.p_auto_dismiss_datetime_edit_screen, + false + ), + showDueDate = showDueDate + ) + } +} @Composable fun StartDateRow( diff --git a/app/src/main/java/org/tasks/compose/edit/TaskEditScreen.kt b/app/src/main/java/org/tasks/compose/edit/TaskEditScreen.kt index d00c15887..0980499ba 100644 --- a/app/src/main/java/org/tasks/compose/edit/TaskEditScreen.kt +++ b/app/src/main/java/org/tasks/compose/edit/TaskEditScreen.kt @@ -270,7 +270,19 @@ fun TaskEditScreen( ) CalendarControlSet.TAG -> AndroidFragment() - StartDateControlSet.TAG -> AndroidFragment() + StartDateControlSet.TAG -> { + //AndroidFragment() + StartDateRow( + current = editViewModel.startDate.collectAsStateWithLifecycle().value, + setCurrent = { editViewModel.setStartDate(it) }, + dueDate = editViewModel.dueDate.collectAsStateWithLifecycle().value, + isNew = viewState.isNew, + hasStartAlarm = remember (viewState.alarms) { + viewState.alarms.any { it.type == Alarm.TYPE_REL_START } + }, + showDueDate = !viewState.list.account.isOpenTasks + ) + } ReminderControlSet.TAG -> AndroidFragment() LocationControlSet.TAG -> AndroidFragment() FilesControlSet.TAG -> AndroidFragment() diff --git a/app/src/main/java/org/tasks/compose/pickers/StartDateTimePicker.kt b/app/src/main/java/org/tasks/compose/pickers/StartDateTimePicker.kt new file mode 100644 index 000000000..1ce0da824 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/pickers/StartDateTimePicker.kt @@ -0,0 +1,138 @@ +package org.tasks.compose.pickers + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberTimePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.platform.LocalContext +import org.tasks.data.entity.Task +import org.tasks.date.DateTimeUtils.newDateTime +import org.tasks.dialogs.BaseDateTimePicker +import org.tasks.dialogs.StartDatePicker.Companion.DUE_DATE +import org.tasks.dialogs.StartDatePicker.Companion.DUE_TIME +import org.tasks.extensions.Context.is24HourFormat +import org.tasks.preferences.Preferences +import org.tasks.time.DateTime + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun StartDateTimePicker ( + selectedDay: Long, + selectedTime: Int, + updateValues: (Long, Int) -> Unit, + accept: () -> Unit, + dismiss: () -> Unit, + autoclose: Boolean, + showDueDate: Boolean, + onDismissHandler: BaseDateTimePicker.OnDismissHandler? = null +) { + val context = LocalContext.current + val preferences = remember { Preferences(context) } + val state = rememberDatePickerState( + initialDisplayMode = remember { preferences.calendarDisplayMode }, + ) + + val today = remember { newDateTime().startOfDay() } + + fun returnDate(day: Long = selectedDay, time: Int = selectedTime) { + if (day != selectedDay || time != selectedTime) { + updateValues(day, time) + } + if (autoclose) accept() + } + + fun returnSelectedTime(millisOfDay: Int) { + val day = when { + selectedDay == DUE_TIME -> DUE_DATE + selectedDay != 0L -> selectedDay + today.withMillisOfDay(millisOfDay).isAfterNow -> today.millis + else -> today.plusDays(1).millis + } + returnDate(day = day, time = millisOfDay) + } + + DatePickerBottomSheet( + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ), + state = state, + showButtons = !autoclose, + setDisplayMode = { preferences.calendarDisplayMode = it }, + cancel = { dismiss(); onDismissHandler?.onDismiss() }, + accept = accept, + dateShortcuts = { + StartDateShortcuts( + selected = selectedDay, + selectedDay = { returnDate(it) }, + selectedDayTime = { day, time -> returnDate(day, time) }, + showDueDate = showDueDate, + clearDate = { returnDate(day = 0, time = 0) }, + ) + }, + timeShortcuts = { + var showTimePicker by rememberSaveable { mutableStateOf(false) } + if (showTimePicker) { + val time = if (selectedTime < 0 || !Task.hasDueTime( + today.withMillisOfDay(selectedTime).millis + ) + ) { + today.noon().millisOfDay + } else { + selectedTime + } + TimePickerDialog( + state = rememberTimePickerState( + initialHour = time / (60 * 60_000), + initialMinute = (time / (60_000)) % 60, + is24Hour = LocalContext.current.is24HourFormat + ), + initialDisplayMode = remember { preferences.timeDisplayMode }, + setDisplayMode = { preferences.timeDisplayMode = it }, + selected = { returnSelectedTime(it + 1000) }, + dismiss = { showTimePicker = false } + ) + } + TimeShortcuts( + day = selectedDay, + selected = selectedTime, + morning = remember { preferences.dateShortcutMorning + 1000 }, + afternoon = remember { preferences.dateShortcutAfternoon + 1000 }, + evening = remember { preferences.dateShortcutEvening + 1000 }, + night = remember { preferences.dateShortcutNight + 1000 }, + selectedMillisOfDay = { returnSelectedTime(it) }, + pickTime = { showTimePicker = true }, + clearTime = { + returnDate( + day = when (selectedDay) { + DUE_TIME -> DUE_DATE + else -> selectedDay + }, + time = 0 + ) + } + ) + } + ) + LaunchedEffect(selectedDay) { + if (selectedDay > 0) { + state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset) + } else { + state.selectedDateMillis = null + } + } + LaunchedEffect(state.selectedDateMillis) { + if (state.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) { + return@LaunchedEffect + } + state.selectedDateMillis?.let { + returnDate(day = it - DateTime(it).offset) + } + } +} \ No newline at end of file