Add DueDatePicker preview and fix layout issues

pull/3441/head
Alex Baker 9 months ago
parent 7ee578ba64
commit 41f2f51c37

@ -5,6 +5,7 @@ import android.os.Bundle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -41,8 +42,11 @@ class DateAndTimePickerActivity : AppCompatActivity() {
var showTimePicker by rememberSaveable { mutableStateOf(false) } var showTimePicker by rememberSaveable { mutableStateOf(false) }
if (showTimePicker) { if (showTimePicker) {
TimePickerDialog( TimePickerDialog(
millisOfDay = 0, state = rememberTimePickerState(
is24Hour = is24HourFormat, initialHour = 0,
initialMinute = 0,
is24Hour = is24HourFormat
),
initialDisplayMode = remember { preferences.timeDisplayMode }, initialDisplayMode = remember { preferences.timeDisplayMode },
setDisplayMode = { preferences.timeDisplayMode = it }, setDisplayMode = { preferences.timeDisplayMode = it },
selected = { selected = {

@ -25,6 +25,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -39,6 +40,7 @@ import org.tasks.R
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DatePickerBottomSheet( fun DatePickerBottomSheet(
sheetState: SheetState,
showButtons: Boolean, showButtons: Boolean,
dismiss: () -> Unit, dismiss: () -> Unit,
accept: () -> Unit, accept: () -> Unit,
@ -49,9 +51,7 @@ fun DatePickerBottomSheet(
) { ) {
ModalBottomSheet( ModalBottomSheet(
modifier = Modifier.statusBarsPadding(), modifier = Modifier.statusBarsPadding(),
sheetState = rememberModalBottomSheetState( sheetState = sheetState,
skipPartiallyExpanded = true,
),
onDismissRequest = { dismiss() }, onDismissRequest = { dismiss() },
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
) { ) {

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.NextWeek import androidx.compose.material.icons.automirrored.outlined.NextWeek
import androidx.compose.material.icons.outlined.AccessTime import androidx.compose.material.icons.outlined.AccessTime
@ -34,8 +35,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tasks.R import org.tasks.R
@ -77,6 +80,7 @@ fun DatePickerShortcuts(
) { ) {
Column( Column(
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
modifier = Modifier.widthIn(max = LocalConfiguration.current.screenWidthDp.dp / 2)
) { ) {
dateShortcuts() dateShortcuts()
} }
@ -344,6 +348,8 @@ fun ShortcutButton(
) )
Text( Text(
text = text, text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
} }
} }

@ -24,7 +24,7 @@ import androidx.compose.material3.TimeInput
import androidx.compose.material3.TimePicker import androidx.compose.material3.TimePicker
import androidx.compose.material3.TimePickerDefaults import androidx.compose.material3.TimePickerDefaults
import androidx.compose.material3.TimePickerLayoutType import androidx.compose.material3.TimePickerLayoutType
import androidx.compose.material3.rememberTimePickerState import androidx.compose.material3.TimePickerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -41,18 +41,12 @@ import org.tasks.R
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun TimePickerDialog( fun TimePickerDialog(
millisOfDay: Int, state: TimePickerState,
is24Hour: Boolean,
initialDisplayMode: DisplayMode, initialDisplayMode: DisplayMode,
setDisplayMode: (DisplayMode) -> Unit, setDisplayMode: (DisplayMode) -> Unit,
selected: (Int) -> Unit, selected: (Int) -> Unit,
dismiss: () -> Unit, dismiss: () -> Unit,
) { ) {
val state = rememberTimePickerState(
initialHour = millisOfDay / (60 * 60_000),
initialMinute = (millisOfDay / (60_000)) % 60,
is24Hour = is24Hour
)
var displayMode by remember { mutableStateOf(initialDisplayMode) } var displayMode by remember { mutableStateOf(initialDisplayMode) }
val layoutType = with(LocalConfiguration.current) { val layoutType = with(LocalConfiguration.current) {
if (screenHeightDp < screenWidthDp) { if (screenHeightDp < screenWidthDp) {

@ -6,8 +6,14 @@ 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.material3.DatePickerState
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberDatePickerState 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.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -16,6 +22,10 @@ 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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewFontScale
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
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
@ -23,6 +33,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 kotlinx.coroutines.runBlocking
import org.tasks.compose.pickers.DatePickerBottomSheet 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
@ -31,12 +42,19 @@ import org.tasks.data.createDueDate
import org.tasks.data.entity.Task import org.tasks.data.entity.Task
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.dialogs.DateTimePicker.Companion.MULTIPLE_TIMES
import org.tasks.dialogs.DateTimePicker.Companion.NO_TIME
import org.tasks.extensions.Context.is24HourFormat import org.tasks.extensions.Context.is24HourFormat
import org.tasks.notifications.NotificationManager import org.tasks.notifications.NotificationManager
import org.tasks.themes.TasksTheme import org.tasks.themes.TasksTheme
import org.tasks.time.DateTime import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.millisOfDay import org.tasks.time.millisOfDay
import org.tasks.time.noon
import org.tasks.time.plusDays
import org.tasks.time.startOfDay import org.tasks.time.startOfDay
import org.tasks.time.withMillisOfDay
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -116,83 +134,49 @@ class DateTimePicker : BaseDateTimePicker() {
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
) = content { ) = content {
TasksTheme(theme = theme.themeBase.index) { TasksTheme(theme = theme.themeBase.index) {
val state = rememberDatePickerState( val datePickerState = rememberDatePickerState(
initialDisplayMode = remember { preferences.calendarDisplayMode }, initialDisplayMode = remember { preferences.calendarDisplayMode },
) )
DatePickerBottomSheet( DueDatePicker(
state = state, sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
datePickerState = datePickerState,
initialTimeDisplayMode = remember { preferences.timeDisplayMode },
selectedDay = selectedDay,
selectedTime = selectedTime,
showButtons = !autoclose, showButtons = !autoclose,
setDisplayMode = { preferences.calendarDisplayMode = it },
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 { showNoDate = remember {
!requireArguments().getBoolean( !requireArguments().getBoolean(
EXTRA_HIDE_NO_DATE, EXTRA_HIDE_NO_DATE,
false false
) )
}, },
selectedDay = { returnDate(it.startOfDay()) }, setDateDisplayMode = { preferences.calendarDisplayMode = it },
clearDate = { returnDate(day = 0, time = 0) }, setTimeDisplayMode = { preferences.timeDisplayMode = it },
) dismiss = { onDismissHandler?.onDismiss() ?: dismiss() },
}, accept = { sendSelected() },
timeShortcuts = { setDateTime = { day, time -> returnDate(day, time) },
var showTimePicker by rememberSaveable { setTime = { returnSelectedTime(it) },
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 }, is24Hour = remember { requireContext().is24HourFormat },
initialDisplayMode = remember { preferences.timeDisplayMode }, today = today.millis,
setDisplayMode = { preferences.timeDisplayMode = it },
selected = { returnSelectedTime(it + 1000) },
dismiss = { showTimePicker = false },
)
}
TimeShortcuts(
day = 0,
selected = selectedTime,
morning = remember { preferences.dateShortcutMorning + 1000 }, morning = remember { preferences.dateShortcutMorning + 1000 },
afternoon = remember { preferences.dateShortcutAfternoon + 1000 }, afternoon = remember { preferences.dateShortcutAfternoon + 1000 },
evening = remember { preferences.dateShortcutEvening + 1000 }, evening = remember { preferences.dateShortcutEvening + 1000 },
night = remember { preferences.dateShortcutNight + 1000 }, night = remember { preferences.dateShortcutNight + 1000 },
selectedMillisOfDay = { returnSelectedTime(it) },
pickTime = { showTimePicker = true },
clearTime = { returnDate(time = 0) },
)
}
) )
LaunchedEffect(selectedDay) { LaunchedEffect(selectedDay) {
if (selectedDay > 0) { if (selectedDay > 0) {
state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset) datePickerState.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset)
} else { } else {
state.selectedDateMillis = null datePickerState.selectedDateMillis = null
} }
} }
LaunchedEffect(state.selectedDateMillis) { LaunchedEffect(datePickerState.selectedDateMillis) {
if (state.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) { if (datePickerState.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) {
return@LaunchedEffect return@LaunchedEffect
} }
state.selectedDateMillis?.let { datePickerState.selectedDateMillis?.let {
returnDate(day = it - DateTime(it).offset) returnDate(day = it - DateTime(it).offset)
} }
} }
@ -201,6 +185,7 @@ class DateTimePicker : BaseDateTimePicker() {
private fun returnSelectedTime(millisOfDay: Int) { private fun returnSelectedTime(millisOfDay: Int) {
val day = when { val day = when {
millisOfDay == NO_TIME -> selectedDay
selectedDay == MULTIPLE_DAYS -> MULTIPLE_DAYS selectedDay == MULTIPLE_DAYS -> MULTIPLE_DAYS
selectedDay > 0 -> selectedDay selectedDay > 0 -> selectedDay
today.withMillisOfDay(millisOfDay).isAfterNow -> today.millis today.withMillisOfDay(millisOfDay).isAfterNow -> today.millis
@ -278,3 +263,140 @@ class DateTimePicker : BaseDateTimePicker() {
outState.putInt(EXTRA_TIME, selectedTime) outState.putInt(EXTRA_TIME, selectedTime)
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DueDatePicker(
sheetState: SheetState,
datePickerState: DatePickerState,
initialTimeDisplayMode: DisplayMode,
selectedDay: Long,
selectedTime: Int,
today: Long,
morning: Int,
afternoon: Int,
evening: Int,
night: Int,
is24Hour: Boolean,
showButtons: Boolean,
showNoDate: Boolean,
setDateDisplayMode: (DisplayMode) -> Unit,
setTimeDisplayMode: (DisplayMode) -> Unit,
dismiss: () -> Unit,
accept: () -> Unit,
setDateTime: (Long, Int) -> Unit,
setTime: (Int) -> Unit,
) {
DatePickerBottomSheet(
sheetState = sheetState,
state = datePickerState,
showButtons = showButtons,
setDisplayMode = setDateDisplayMode,
dismiss = dismiss,
accept = accept,
dateShortcuts = {
DueDateShortcuts(
today = today,
tomorrow = remember { today.plusDays(1) },
nextWeek = remember { today.plusDays(7) },
selected = selectedDay,
showNoDate = showNoDate,
selectedDay = { setDateTime(it.startOfDay(), selectedTime) },
clearDate = { setDateTime(0, 0) },
)
},
timeShortcuts = {
var showTimePicker by rememberSaveable {
mutableStateOf(
false
)
}
if (showTimePicker) {
val time = if (selectedTime == MULTIPLE_TIMES
|| !Task.hasDueTime(
today.withMillisOfDay(
selectedTime
)
)
) {
today.noon().millisOfDay
} else {
selectedTime
}
TimePickerDialog(
state = rememberTimePickerState(
initialHour = time / (60 * 60_000),
initialMinute = (time / (60_000)) % 60,
is24Hour = is24Hour,
),
initialDisplayMode = initialTimeDisplayMode,
setDisplayMode = setTimeDisplayMode,
selected = { setTime(it + 1000) },
dismiss = { showTimePicker = false },
)
}
TimeShortcuts(
day = 0,
selected = selectedTime,
morning = morning,
afternoon = afternoon,
evening = evening,
night = night,
selectedMillisOfDay = { setTime(it) },
pickTime = { showTimePicker = true },
clearTime = { setTime(NO_TIME) },
)
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@PreviewLightDark
@PreviewFontScale
@PreviewScreenSizes
@Preview(
locale = "es",
fontScale = 2f
)
@Preview(
locale = "es",
)
@Preview(
locale = "de",
fontScale = 2f
)
@Preview(
locale = "de",
)
@Composable
fun DueDatePickerPreview() {
TasksTheme {
val today = currentTimeMillis().startOfDay()
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
).apply {
runBlocking { show() }
}
DueDatePicker(
sheetState = sheetState,
datePickerState = rememberDatePickerState(),
initialTimeDisplayMode = DisplayMode.Input,
selectedDay = 0,
selectedTime = 0,
today = today,
morning = TimeUnit.HOURS.toMillis(9).toInt(),
afternoon = TimeUnit.HOURS.toMillis(13).toInt(),
evening = TimeUnit.HOURS.toMillis(17).toInt(),
night = TimeUnit.HOURS.toMillis(20).toInt(),
is24Hour = true,
showButtons = true,
showNoDate = true,
setDateDisplayMode = {},
setTimeDisplayMode = {},
dismiss = {},
accept = {},
setDateTime = { _, _ -> },
setTime = {},
)
}
}

@ -6,6 +6,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -39,8 +40,11 @@ class MyTimePickerDialog : DialogFragment() {
primary = theme.themeColor.primaryColor, primary = theme.themeColor.primaryColor,
) { ) {
TimePickerDialog( TimePickerDialog(
millisOfDay = remember { initial.millisOfDay }, state = rememberTimePickerState(
is24Hour = remember { requireContext().is24HourFormat }, initialHour = initial.millisOfDay / (60 * 60_000),
initialMinute = (initial.millisOfDay / (60_000)) % 60,
is24Hour = requireContext().is24HourFormat
),
initialDisplayMode = remember { preferences.timeDisplayMode }, initialDisplayMode = remember { preferences.timeDisplayMode },
setDisplayMode = { preferences.timeDisplayMode = it }, setDisplayMode = { preferences.timeDisplayMode = it },
selected = { selected = {

@ -8,6 +8,8 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.rememberTimePickerState
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
@ -87,6 +89,9 @@ class StartDatePicker : BaseDateTimePicker() {
initialDisplayMode = remember { preferences.calendarDisplayMode }, initialDisplayMode = remember { preferences.calendarDisplayMode },
) )
DatePickerBottomSheet( DatePickerBottomSheet(
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
state = state, state = state,
showButtons = !autoclose, showButtons = !autoclose,
setDisplayMode = { preferences.calendarDisplayMode = it }, setDisplayMode = { preferences.calendarDisplayMode = it },
@ -112,8 +117,11 @@ class StartDatePicker : BaseDateTimePicker() {
selectedTime selectedTime
} }
TimePickerDialog( TimePickerDialog(
millisOfDay = time, state = rememberTimePickerState(
is24Hour = remember { requireContext().is24HourFormat }, initialHour = time / (60 * 60_000),
initialMinute = (time / (60_000)) % 60,
is24Hour = requireContext().is24HourFormat
),
initialDisplayMode = remember { preferences.timeDisplayMode }, initialDisplayMode = remember { preferences.timeDisplayMode },
setDisplayMode = { preferences.timeDisplayMode = it }, setDisplayMode = { preferences.timeDisplayMode = it },
selected = { returnSelectedTime(it + 1000) }, selected = { returnSelectedTime(it + 1000) },

Loading…
Cancel
Save