Add buttons to date picker bottom sheet

pull/3432/head
Alex Baker 9 months ago
parent de42915ced
commit c78ec022a5

@ -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))
}
}
}
}
}
}
}

@ -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 {

@ -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()
}
}

@ -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()
}
}

Loading…
Cancel
Save