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 var onDismissHandler: OnDismissHandler? = null
protected val autoclose by lazy {
arguments?.getBoolean(EXTRA_AUTO_CLOSE) ?: false
}
override fun onAttach(activity: Activity) { override fun onAttach(activity: Activity) {
super.onAttach(activity) super.onAttach(activity)
@ -33,8 +36,6 @@ abstract class BaseDateTimePicker : DialogFragment() {
override fun onCancel(dialog: DialogInterface) = sendSelected() override fun onCancel(dialog: DialogInterface) = sendSelected()
protected fun closeAutomatically(): Boolean = arguments?.getBoolean(EXTRA_AUTO_CLOSE) ?: false
protected abstract fun sendSelected() protected abstract fun sendSelected()
companion object { companion object {

@ -6,16 +6,8 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup 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.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -24,7 +16,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.compose.content import androidx.fragment.compose.content
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -32,7 +23,7 @@ import com.todoroo.astrid.dao.TaskDao
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch 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.DueDateShortcuts
import org.tasks.compose.pickers.TimePickerDialog import org.tasks.compose.pickers.TimePickerDialog
import org.tasks.compose.pickers.TimeShortcuts import org.tasks.compose.pickers.TimeShortcuts
@ -126,93 +117,80 @@ class DateTimePicker : BaseDateTimePicker() {
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
) = content { ) = content {
TasksTheme(theme = theme.themeBase.index) { TasksTheme(theme = theme.themeBase.index) {
val sheetState = rememberModalBottomSheetState( val state = rememberDatePickerState()
skipPartiallyExpanded = true, DatePickerBottomSheet(
) state = state,
ModalBottomSheet( showButtons = !autoclose,
sheetState = sheetState, dismiss = { onDismissHandler?.onDismiss() ?: dismiss() },
onDismissRequest = { accept = { sendSelected() },
onDismissHandler?.onDismiss() ?: dismiss() dateShortcuts = {
}, DueDateShortcuts(
containerColor = MaterialTheme.colorScheme.surface, today = today.millis,
) { tomorrow = remember { today.plusDays(1).millis },
val state = rememberDatePickerState() nextWeek = remember { today.plusDays(7).millis },
Column( selected = selectedDay,
modifier = Modifier.verticalScroll(rememberScrollState()) showNoDate = remember {
) { !requireArguments().getBoolean(
DatePicker( EXTRA_HIDE_NO_DATE,
state = state, false
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) },
)
},
) )
}, },
colors = DatePickerDefaults.colors( selectedDay = { returnDate(it.startOfDay()) },
containerColor = MaterialTheme.colorScheme.surface, clearDate = { returnDate(day = 0, time = 0) },
),
) )
} },
LaunchedEffect(selectedDay) { timeShortcuts = {
if (selectedDay > 0) { var showTimePicker by rememberSaveable {
state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset) mutableStateOf(
} else { false
state.selectedDateMillis = null )
}
}
LaunchedEffect(state.selectedDateMillis) {
if (state.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) {
return@LaunchedEffect
} }
state.selectedDateMillis?.let { if (showTimePicker) {
returnDate(day = it - DateTime(it).offset) 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) { private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) {
selectedDay = day selectedDay = day
selectedTime = time selectedTime = time
if (closeAutomatically()) { if (autoclose) {
sendSelected() sendSelected()
} }
} }

@ -6,16 +6,8 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup 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.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -24,12 +16,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.compose.content import androidx.fragment.compose.content
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import dagger.hilt.android.AndroidEntryPoint 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.StartDateShortcuts
import org.tasks.compose.pickers.TimePickerDialog import org.tasks.compose.pickers.TimePickerDialog
import org.tasks.compose.pickers.TimeShortcuts import org.tasks.compose.pickers.TimeShortcuts
@ -93,95 +84,73 @@ class StartDatePicker : BaseDateTimePicker() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
) = content { ) = content {
TasksTheme(theme = theme.themeBase.index) { TasksTheme(theme = theme.themeBase.index) {
val sheetState = rememberModalBottomSheetState( val state = rememberDatePickerState()
skipPartiallyExpanded = true, DatePickerBottomSheet(
) state = state,
ModalBottomSheet( showButtons = !autoclose,
sheetState = sheetState, dismiss = { onDismissHandler?.onDismiss() ?: dismiss() },
onDismissRequest = { accept = { sendSelected() },
onDismissHandler?.onDismiss() ?: dismiss() dateShortcuts = {
StartDateShortcuts(
selected = selectedDay,
selectedDay = { returnDate(it) },
selectedDayTime = { day, time -> returnDate(day, time) },
clearDate = { returnDate(day = 0, time = 0) },
)
}, },
containerColor = MaterialTheme.colorScheme.surface, timeShortcuts = {
) { var showTimePicker by rememberSaveable { mutableStateOf(false) }
val state = rememberDatePickerState() if (showTimePicker) {
Column( val time = if (selectedTime < 0 || !Task.hasDueTime(
modifier = Modifier.verticalScroll(rememberScrollState()), today.withMillisOfDay(selectedTime).millis
) {
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) },
)
},
) )
}, ) {
colors = DatePickerDefaults.colors( today.noon().millisOfDay
containerColor = MaterialTheme.colorScheme.surface,
),
)
LaunchedEffect(selectedDay) {
if (selectedDay > 0) {
state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset)
} else { } else {
state.selectedDateMillis = null selectedTime
}
}
LaunchedEffect(state.selectedDateMillis) {
if (state.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) {
return@LaunchedEffect
}
state.selectedDateMillis?.let {
returnDate(day = it - DateTime(it).offset)
} }
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) { private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) {
selectedDay = day selectedDay = day
selectedTime = time selectedTime = time
if (closeAutomatically()) { if (autoclose) {
sendSelected() sendSelected()
} }
} }

Loading…
Cancel
Save