diff --git a/app/src/main/java/org/tasks/compose/pickers/DatePickerBottomSheet.kt b/app/src/main/java/org/tasks/compose/pickers/DatePickerBottomSheet.kt new file mode 100644 index 000000000..9f5ba874c --- /dev/null +++ b/app/src/main/java/org/tasks/compose/pickers/DatePickerBottomSheet.kt @@ -0,0 +1,107 @@ +package org.tasks.compose.pickers + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import org.tasks.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DatePickerBottomSheet( + showButtons: Boolean, + dismiss: () -> Unit, + accept: () -> Unit, + dateShortcuts: @Composable ColumnScope.() -> Unit, + timeShortcuts: @Composable ColumnScope.() -> Unit, + state: DatePickerState, +) { + ModalBottomSheet( + modifier = Modifier.statusBarsPadding(), + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true, + ), + onDismissRequest = { dismiss() }, + containerColor = MaterialTheme.colorScheme.surface, + ) { + Box( + modifier = Modifier.fillMaxWidth(), + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()), + ) { + DatePicker( + state = state, + showModeToggle = false, + title = {}, + colors = DatePickerDefaults.colors( + containerColor = MaterialTheme.colorScheme.surface, + ), + headline = { + DatePickerShortcuts( + dateShortcuts = dateShortcuts, + timeShortcuts = timeShortcuts, + ) + }, + ) + if (showButtons) { + Spacer(modifier = Modifier.height(56.dp)) + } + } + if (showButtons) { + Surface( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(), + shadowElevation = 8.dp, + color = MaterialTheme.colorScheme.surface, + ) { + Row( + modifier = Modifier + .height(56.dp) + .padding(horizontal = 24.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + TextButton( + onClick = { dismiss() } + ) { + Text(stringResource(R.string.cancel)) + } + Spacer(modifier = Modifier.width(8.dp)) + TextButton( + onClick = { accept() } + ) { + Text(stringResource(R.string.ok)) + } + } + } + } + } + } +} diff --git a/app/src/main/java/org/tasks/dialogs/BaseDateTimePicker.kt b/app/src/main/java/org/tasks/dialogs/BaseDateTimePicker.kt index babc287cc..4acd1aeae 100644 --- a/app/src/main/java/org/tasks/dialogs/BaseDateTimePicker.kt +++ b/app/src/main/java/org/tasks/dialogs/BaseDateTimePicker.kt @@ -16,6 +16,9 @@ abstract class BaseDateTimePicker : DialogFragment() { } protected var onDismissHandler: OnDismissHandler? = null + protected val autoclose by lazy { + arguments?.getBoolean(EXTRA_AUTO_CLOSE) ?: false + } override fun onAttach(activity: Activity) { super.onAttach(activity) @@ -33,8 +36,6 @@ abstract class BaseDateTimePicker : DialogFragment() { override fun onCancel(dialog: DialogInterface) = sendSelected() - protected fun closeAutomatically(): Boolean = arguments?.getBoolean(EXTRA_AUTO_CLOSE) ?: false - protected abstract fun sendSelected() companion object { diff --git a/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt b/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt index 203b6b44a..b6e517f8f 100644 --- a/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt +++ b/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt @@ -6,16 +6,8 @@ import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.DatePicker -import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberDatePickerState -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -24,7 +16,6 @@ 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.Modifier import androidx.fragment.app.Fragment import androidx.fragment.compose.content import androidx.lifecycle.lifecycleScope @@ -32,7 +23,7 @@ import com.todoroo.astrid.dao.TaskDao import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch -import org.tasks.compose.pickers.DatePickerShortcuts +import org.tasks.compose.pickers.DatePickerBottomSheet import org.tasks.compose.pickers.DueDateShortcuts import org.tasks.compose.pickers.TimePickerDialog import org.tasks.compose.pickers.TimeShortcuts @@ -126,93 +117,80 @@ class DateTimePicker : BaseDateTimePicker() { savedInstanceState: Bundle?, ) = content { TasksTheme(theme = theme.themeBase.index) { - val sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true, - ) - ModalBottomSheet( - sheetState = sheetState, - onDismissRequest = { - onDismissHandler?.onDismiss() ?: dismiss() - }, - containerColor = MaterialTheme.colorScheme.surface, - ) { - val state = rememberDatePickerState() - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { - DatePicker( - state = state, - showModeToggle = false, - title = {}, - headline = { - DatePickerShortcuts( - timeShortcuts = { - var showTimePicker by rememberSaveable { mutableStateOf(false) } - if (showTimePicker) { - val time = if (selectedTime == MULTIPLE_TIMES - || !Task.hasDueTime(today.withMillisOfDay(selectedTime).millis) - ) { - today.noon().millisOfDay - } else { - selectedTime - } - TimePickerDialog( - millisOfDay = time, - is24Hour = remember { requireContext().is24HourFormat }, - textInput = remember { preferences.timeInputMode == 1 }, - selected = { returnSelectedTime(it + 1000) }, - dismiss = { showTimePicker = false }, - ) - } - TimeShortcuts( - day = 0, - 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(time = 0) }, - ) - }, - dateShortcuts = { - DueDateShortcuts( - today = today.millis, - tomorrow = remember { today.plusDays(1).millis }, - nextWeek = remember { today.plusDays(7).millis }, - selected = selectedDay, - showNoDate = remember { - !requireArguments().getBoolean( - EXTRA_HIDE_NO_DATE, - false - ) - }, - selectedDay = { returnDate(it.startOfDay()) }, - clearDate = { returnDate(day = 0, time = 0) }, - ) - }, + val state = rememberDatePickerState() + DatePickerBottomSheet( + state = state, + showButtons = !autoclose, + dismiss = { onDismissHandler?.onDismiss() ?: dismiss() }, + accept = { sendSelected() }, + dateShortcuts = { + DueDateShortcuts( + today = today.millis, + tomorrow = remember { today.plusDays(1).millis }, + nextWeek = remember { today.plusDays(7).millis }, + selected = selectedDay, + showNoDate = remember { + !requireArguments().getBoolean( + EXTRA_HIDE_NO_DATE, + false ) }, - colors = DatePickerDefaults.colors( - containerColor = MaterialTheme.colorScheme.surface, - ), + selectedDay = { returnDate(it.startOfDay()) }, + clearDate = { returnDate(day = 0, 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 + }, + timeShortcuts = { + var showTimePicker by rememberSaveable { + mutableStateOf( + false + ) } - state.selectedDateMillis?.let { - returnDate(day = it - DateTime(it).offset) + if (showTimePicker) { + val time = if (selectedTime == MULTIPLE_TIMES + || !Task.hasDueTime( + today.withMillisOfDay( + selectedTime + ).millis + ) + ) { + today.noon().millisOfDay + } else { + selectedTime + } + TimePickerDialog( + millisOfDay = time, + is24Hour = remember { requireContext().is24HourFormat }, + textInput = remember { preferences.timeInputMode == 1 }, + selected = { returnSelectedTime(it + 1000) }, + dismiss = { showTimePicker = false }, + ) } + TimeShortcuts( + day = 0, + 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(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) } } } @@ -231,7 +209,7 @@ class DateTimePicker : BaseDateTimePicker() { private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) { selectedDay = day selectedTime = time - if (closeAutomatically()) { + if (autoclose) { sendSelected() } } diff --git a/app/src/main/java/org/tasks/dialogs/StartDatePicker.kt b/app/src/main/java/org/tasks/dialogs/StartDatePicker.kt index a6c74704e..2be16d895 100644 --- a/app/src/main/java/org/tasks/dialogs/StartDatePicker.kt +++ b/app/src/main/java/org/tasks/dialogs/StartDatePicker.kt @@ -6,16 +6,8 @@ import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.DatePicker -import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberDatePickerState -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -24,12 +16,11 @@ 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.Modifier import androidx.fragment.app.Fragment import androidx.fragment.compose.content import com.todoroo.astrid.dao.TaskDao import dagger.hilt.android.AndroidEntryPoint -import org.tasks.compose.pickers.DatePickerShortcuts +import org.tasks.compose.pickers.DatePickerBottomSheet import org.tasks.compose.pickers.StartDateShortcuts import org.tasks.compose.pickers.TimePickerDialog import org.tasks.compose.pickers.TimeShortcuts @@ -93,95 +84,73 @@ class StartDatePicker : BaseDateTimePicker() { savedInstanceState: Bundle? ) = content { TasksTheme(theme = theme.themeBase.index) { - val sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true, - ) - ModalBottomSheet( - sheetState = sheetState, - onDismissRequest = { - onDismissHandler?.onDismiss() ?: dismiss() + val state = rememberDatePickerState() + DatePickerBottomSheet( + state = state, + showButtons = !autoclose, + dismiss = { onDismissHandler?.onDismiss() ?: dismiss() }, + accept = { sendSelected() }, + dateShortcuts = { + StartDateShortcuts( + selected = selectedDay, + selectedDay = { returnDate(it) }, + selectedDayTime = { day, time -> returnDate(day, time) }, + clearDate = { returnDate(day = 0, time = 0) }, + ) }, - containerColor = MaterialTheme.colorScheme.surface, - ) { - val state = rememberDatePickerState() - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - ) { - DatePicker( - state = state, - showModeToggle = false, - title = {}, - headline = { - DatePickerShortcuts( - 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( - millisOfDay = time, - is24Hour = remember { requireContext().is24HourFormat }, - textInput = remember { preferences.timeInputMode == 1 }, - 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 - ) - }, - ) - }, - dateShortcuts = { - StartDateShortcuts( - selected = selectedDay, - selectedDay = { returnDate(it) }, - selectedDayTime = { day, time -> returnDate(day, time) }, - 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 ) - }, - colors = DatePickerDefaults.colors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) - - LaunchedEffect(selectedDay) { - if (selectedDay > 0) { - state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset) + ) { + today.noon().millisOfDay } 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) + selectedTime } + TimePickerDialog( + millisOfDay = time, + is24Hour = remember { requireContext().is24HourFormat }, + textInput = remember { preferences.timeInputMode == 1 }, + 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) } } } @@ -200,7 +169,7 @@ class StartDatePicker : BaseDateTimePicker() { private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) { selectedDay = day selectedTime = time - if (closeAutomatically()) { + if (autoclose) { sendSelected() } }