Compose date time pickers

pull/3428/head
Alex Baker 9 months ago
parent 2f42d0ad28
commit 33a7a0a53f

@ -0,0 +1,350 @@
package org.tasks.compose.pickers
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
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.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.NextWeek
import androidx.compose.material.icons.outlined.AccessTime
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CalendarViewWeek
import androidx.compose.material.icons.outlined.Coffee
import androidx.compose.material.icons.outlined.NightsStay
import androidx.compose.material.icons.outlined.Schedule
import androidx.compose.material.icons.outlined.Today
import androidx.compose.material.icons.outlined.WbSunny
import androidx.compose.material.icons.outlined.WbTwilight
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.DateTimePicker.Companion.MULTIPLE_DAYS
import org.tasks.dialogs.DateTimePicker.Companion.MULTIPLE_TIMES
import org.tasks.dialogs.DateTimePicker.Companion.NO_DAY
import org.tasks.dialogs.DateTimePicker.Companion.NO_TIME
import org.tasks.dialogs.StartDatePicker.Companion.DAY_BEFORE_DUE
import org.tasks.dialogs.StartDatePicker.Companion.DUE_DATE
import org.tasks.dialogs.StartDatePicker.Companion.DUE_TIME
import org.tasks.dialogs.StartDatePicker.Companion.WEEK_BEFORE_DUE
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.DateStyle
import org.tasks.kmp.org.tasks.time.getFullDate
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.minusDays
import org.tasks.time.startOfDay
import org.tasks.time.withMillisOfDay
import java.util.Calendar.FRIDAY
import java.util.Calendar.MONDAY
import java.util.Calendar.SATURDAY
import java.util.Calendar.SUNDAY
import java.util.Calendar.THURSDAY
import java.util.Calendar.TUESDAY
import java.util.Calendar.WEDNESDAY
@Composable
fun DatePickerShortcuts(
dateShortcuts: @Composable ColumnScope.() -> Unit,
timeShortcuts: @Composable ColumnScope.() -> Unit,
) {
Row(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
) {
Column(
horizontalAlignment = Alignment.Start,
) {
dateShortcuts()
}
Spacer(modifier = Modifier.weight(1f))
Column {
timeShortcuts()
}
}
}
@Composable
fun StartDateShortcuts(
selected: Long,
selectedDay: (Long) -> Unit,
selectedDayTime: (Long, Int) -> Unit,
clearDate: () -> Unit,
) {
var custom by remember { mutableLongStateOf(0) }
LaunchedEffect(selected) {
custom = if (selected !in listOf(DUE_DATE, DUE_TIME, DAY_BEFORE_DUE, WEEK_BEFORE_DUE, NO_DAY)) {
selected
} else {
custom
}
}
if (custom > 0 || custom == MULTIPLE_DAYS) {
ShortcutButton(
icon = Icons.Outlined.Today,
text = if (custom == MULTIPLE_DAYS) {
stringResource(R.string.date_picker_multiple)
} else {
remember(custom) {
runBlocking {
if (custom < currentTimeMillis().startOfDay().minusDays(1)) {
getFullDate(custom, style = DateStyle.LONG)
} else {
getRelativeDay(custom, style = DateStyle.LONG)
}
}
}
},
selected = selected == custom,
onClick = { selectedDay(custom) },
)
}
ShortcutButton(
icon = Icons.Outlined.Today,
text = stringResource(R.string.due_date),
selected = selected == DUE_DATE,
onClick = { selectedDay(DUE_DATE) },
)
ShortcutButton(
icon = Icons.Outlined.Schedule,
text = stringResource(R.string.due_time),
selected = selected == DUE_TIME,
onClick = { selectedDayTime(DUE_TIME, NO_TIME) },
)
ShortcutButton(
icon = Icons.Outlined.WbSunny,
text = stringResource(R.string.day_before_due),
selected = selected == DAY_BEFORE_DUE,
onClick = { selectedDay(DAY_BEFORE_DUE) },
)
ShortcutButton(
icon = Icons.Outlined.CalendarViewWeek,
text = stringResource(R.string.week_before_due),
selected = selected == WEEK_BEFORE_DUE,
onClick = { selectedDay(WEEK_BEFORE_DUE) },
)
ShortcutButton(
icon = Icons.Outlined.Block,
text = stringResource(R.string.no_date),
selected = selected == NO_DAY,
onClick = { clearDate() },
)
}
@Composable
fun DueDateShortcuts(
today: Long,
tomorrow: Long,
nextWeek: Long,
selected: Long,
showNoDate: Boolean,
selectedDay: (Long) -> Unit,
clearDate: () -> Unit,
) {
var custom by remember { mutableLongStateOf(0) }
LaunchedEffect(selected) {
custom = if (selected == MULTIPLE_DAYS || selected !in listOf(today, tomorrow, nextWeek, NO_DAY)) {
selected
} else {
custom
}
}
if (custom > 0 || custom == MULTIPLE_DAYS) {
ShortcutButton(
icon = Icons.Outlined.Today,
text = if (custom == MULTIPLE_DAYS) {
stringResource(R.string.date_picker_multiple)
} else {
remember(custom) {
runBlocking {
if (custom < today.minusDays(1)) {
getFullDate(custom, style = DateStyle.LONG)
} else {
getRelativeDay(custom, style = DateStyle.LONG)
}
}
}
},
selected = selected == custom,
onClick = { selectedDay(custom) },
)
}
ShortcutButton(
icon = Icons.Outlined.Today,
text = stringResource(R.string.today),
selected = selected == today,
onClick = { selectedDay(today) },
)
ShortcutButton(
icon = Icons.Outlined.WbSunny,
text = stringResource(R.string.tomorrow),
selected = selected == tomorrow,
onClick = { selectedDay(tomorrow) },
)
ShortcutButton(
icon = Icons.AutoMirrored.Outlined.NextWeek,
text = stringResource(
remember {
when (newDateTime(nextWeek).dayOfWeek) {
SUNDAY -> R.string.next_sunday
MONDAY -> R.string.next_monday
TUESDAY -> R.string.next_tuesday
WEDNESDAY -> R.string.next_wednesday
THURSDAY -> R.string.next_thursday
FRIDAY -> R.string.next_friday
SATURDAY -> R.string.next_saturday
else -> throw IllegalArgumentException()
}
}
),
selected = selected == nextWeek,
onClick = { selectedDay(nextWeek) },
)
if (showNoDate) {
ShortcutButton(
icon = Icons.Outlined.Block,
text = stringResource(R.string.no_date),
selected = selected == NO_DAY,
onClick = { clearDate() },
)
}
}
@Composable
fun TimeShortcuts(
day: Long,
selected: Int,
morning: Int,
afternoon: Int,
evening: Int,
night: Int,
selectedMillisOfDay: (Int) -> Unit,
pickTime: () -> Unit,
clearTime: () -> Unit,
) {
var custom by remember { mutableIntStateOf(0) }
LaunchedEffect(selected) {
custom = if (selected == MULTIPLE_TIMES || selected !in listOf(morning, afternoon, evening, night, NO_TIME)) {
selected
} else {
custom
}
}
val is24HourFormat = LocalContext.current.is24HourFormat
val now = remember { currentTimeMillis() }
if (custom > 0 || custom == MULTIPLE_TIMES) {
ShortcutButton(
icon = Icons.Outlined.AccessTime,
text = if (custom == MULTIPLE_TIMES) {
stringResource(R.string.date_picker_multiple)
} else {
remember(custom) {
getTimeString(now.withMillisOfDay(custom), is24HourFormat)
}
},
selected = selected == custom,
onClick = { selectedMillisOfDay(custom) },
)
}
ShortcutButton(
icon = Icons.Outlined.Coffee,
text = remember {
getTimeString(now.withMillisOfDay(morning), is24HourFormat)
},
selected = selected == morning,
onClick = { selectedMillisOfDay(morning) },
)
ShortcutButton(
icon = Icons.Outlined.WbSunny,
text = remember {
getTimeString(now.withMillisOfDay(afternoon), is24HourFormat)
},
selected = selected == afternoon,
onClick = { selectedMillisOfDay(afternoon) },
)
ShortcutButton(
icon = Icons.Outlined.WbTwilight,
text = remember {
getTimeString(now.withMillisOfDay(evening), is24HourFormat)
},
selected = selected == evening,
onClick = { selectedMillisOfDay(evening) },
)
ShortcutButton(
icon = Icons.Outlined.NightsStay,
text = remember {
getTimeString(now.withMillisOfDay(night), is24HourFormat)
},
selected = selected == night,
onClick = { selectedMillisOfDay(night) },
)
ShortcutButton(
icon = Icons.Outlined.AccessTime,
text = stringResource(R.string.shortcut_pick_time),
selected = false,
onClick = { pickTime() },
)
ShortcutButton(
icon = Icons.Outlined.Block,
text = stringResource(R.string.no_time),
selected = day != DUE_TIME && selected == NO_TIME,
onClick = { clearTime() },
)
}
@Composable
fun ShortcutButton(
icon: ImageVector,
text: String,
selected: Boolean,
onClick: () -> Unit,
) {
val color =
if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
TextButton(
onClick = { onClick() },
colors = ButtonDefaults.textButtonColors(contentColor = color)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
imageVector = icon,
contentDescription = null,
colorFilter = ColorFilter.tint(color)
)
Text(
text = text,
)
}
}
}

@ -0,0 +1,139 @@
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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Keyboard
import androidx.compose.material.icons.outlined.Schedule
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TimeInput
import androidx.compose.material3.TimePicker
import androidx.compose.material3.TimePickerDefaults
import androidx.compose.material3.TimePickerLayoutType
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import org.tasks.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TimePickerDialog(
millisOfDay: Int,
is24Hour: Boolean,
textInput: Boolean,
selected: (Int) -> Unit,
dismiss: () -> Unit,
) {
val state = rememberTimePickerState(
initialHour = millisOfDay / (60 * 60_000),
initialMinute = (millisOfDay / (60_000)) % 60,
is24Hour = is24Hour
)
var showingTextInput by remember { mutableStateOf(textInput) }
val layoutType = with(LocalConfiguration.current) {
if (screenHeightDp < screenWidthDp) {
TimePickerLayoutType.Horizontal
} else {
TimePickerLayoutType.Vertical
}
}
BasicAlertDialog(
onDismissRequest = { dismiss() },
properties = DialogProperties(usePlatformDefaultWidth = layoutType == TimePickerLayoutType.Vertical)
) {
Surface(
shape = RoundedCornerShape(28.0.dp),
color = MaterialTheme.colorScheme.surface,
) {
Column(verticalArrangement = Arrangement.SpaceBetween) {
// Wrap the content with a Box and Modifier.weight(1f) to ensure that any "confirm"
// and "dismiss" buttons are not pushed out of view when running on small screens,
// or when nesting a DateRangePicker.
// Fill is false to support collapsing the dialog's height when switching to input
// mode.
Box(
Modifier
.fillMaxWidth()
.padding(top = 32.dp)
.weight(1f, fill = false),
contentAlignment = Alignment.Center,
) {
if (showingTextInput) {
TimeInput(
state = state,
colors = TimePickerDefaults.colors(
timeSelectorSelectedContainerColor = MaterialTheme.colorScheme.primary,
timeSelectorSelectedContentColor = MaterialTheme.colorScheme.onPrimary,
),
)
} else {
TimePicker(
state = state,
layoutType = layoutType,
colors = TimePickerDefaults.colors(
timeSelectorSelectedContainerColor = MaterialTheme.colorScheme.primary,
timeSelectorSelectedContentColor = MaterialTheme.colorScheme.onPrimary,
),
)
}
}
// Buttons
Box(
modifier = Modifier
.padding(start = 6.dp, bottom = 8.dp, end = 6.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(
onClick = { showingTextInput = !showingTextInput },
) {
Icon(
imageVector = if (showingTextInput) {
Icons.Outlined.Schedule
} else {
Icons.Outlined.Keyboard
},
contentDescription = null
)
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = dismiss) {
Text(text = stringResource(id = R.string.cancel))
}
TextButton(
onClick = {
selected(state.hour * 60 * 60_000 + state.minute * 60_000)
dismiss()
}
) {
Text(text = stringResource(id = R.string.ok))
}
}
}
}
}
}
}

@ -1,47 +1,21 @@
package org.tasks.dialogs
import android.app.Activity
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.CalendarView
import android.widget.FrameLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getTimeString
import androidx.fragment.app.DialogFragment
import org.tasks.preferences.Preferences
import org.tasks.themes.Theme
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.withMillisOfDay
import javax.inject.Inject
abstract class BaseDateTimePicker : BottomSheetDialogFragment() {
abstract class BaseDateTimePicker : DialogFragment() {
@Inject lateinit var theme: Theme
@Inject lateinit var preferences: Preferences
protected var morning = 32401000
protected var afternoon = 46801000
protected var evening = 61201000
protected var night = 72001000
interface OnDismissHandler {
fun onDismiss()
}
private var onDismissHandler: OnDismissHandler? = null
protected var onDismissHandler: OnDismissHandler? = null
override fun onAttach(activity: Activity) {
super.onAttach(activity)
@ -57,99 +31,12 @@ abstract class BaseDateTimePicker : BottomSheetDialogFragment() {
onDismissHandler?.onDismiss()
}
override fun onResume() {
super.onResume()
refreshButtons()
}
override fun onCancel(dialog: DialogInterface) = sendSelected()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.setOnShowListener {
dialog
.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
?.let { bottomSheet ->
val behavior = BottomSheetBehavior.from(bottomSheet)
behavior.skipCollapsed = true
val insets = ViewCompat.getRootWindowInsets(requireActivity().window.decorView)
if (insets?.isVisible(WindowInsetsCompat.Type.ime()) == true) {
lifecycleScope.launch {
delay(100)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
} else {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
if (!closeAutomatically()) {
addButtons(dialog)
}
}
return dialog
}
private fun addButtons(dialog: BottomSheetDialog) {
val coordinator = dialog
.findViewById<CoordinatorLayout>(com.google.android.material.R.id.coordinator)
val containerLayout =
dialog.findViewById<FrameLayout>(com.google.android.material.R.id.container)
val buttons = theme.getLayoutInflater(requireContext())
.inflate(R.layout.dialog_date_time_picker_buttons, null)
buttons.findViewById<View>(R.id.cancel_button).setOnClickListener { dismiss() }
buttons.findViewById<View>(R.id.ok_button).setOnClickListener { sendSelected() }
buttons.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM
).apply {
gravity = Gravity.BOTTOM
}
containerLayout!!.addView(buttons)
buttons.post {
(coordinator!!.layoutParams as ViewGroup.MarginLayoutParams).apply {
buttons.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
this.bottomMargin = buttons.measuredHeight
containerLayout.requestLayout()
}
}
}
protected fun closeAutomatically(): Boolean = arguments?.getBoolean(EXTRA_AUTO_CLOSE) ?: false
protected fun setupShortcutsAndCalendar() {
morning = preferences.dateShortcutMorning + 1000
afternoon = preferences.dateShortcutAfternoon + 1000
evening = preferences.dateShortcutEvening + 1000
night = preferences.dateShortcutNight + 1000
val is24HourFormat = requireContext().is24HourFormat
val now = currentTimeMillis()
morningButton.text = getTimeString(now.withMillisOfDay(morning), is24HourFormat)
afternoonButton.text = getTimeString(now.withMillisOfDay(afternoon), is24HourFormat)
eveningButton.text = getTimeString(now.withMillisOfDay(evening), is24HourFormat)
nightButton.text = getTimeString(now.withMillisOfDay(night), is24HourFormat)
}
protected abstract val calendarView: CalendarView
protected abstract val morningButton: MaterialButton
protected abstract val afternoonButton: MaterialButton
protected abstract val eveningButton: MaterialButton
protected abstract val nightButton: MaterialButton
protected abstract fun sendSelected()
protected abstract fun refreshButtons()
companion object {
const val EXTRA_AUTO_CLOSE = "extra_auto_close"
}

@ -5,38 +5,48 @@ import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
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
import androidx.compose.runtime.mutableLongStateOf
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
import com.todoroo.astrid.dao.TaskDao
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.compose.pickers.DatePickerShortcuts
import org.tasks.compose.pickers.DueDateShortcuts
import org.tasks.compose.pickers.TimePickerDialog
import org.tasks.compose.pickers.TimeShortcuts
import org.tasks.data.createDueDate
import org.tasks.data.entity.Task
import org.tasks.databinding.DialogDateTimePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker
import org.tasks.dialogs.MyTimePickerDialog.Companion.timeInputMode
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.notifications.NotificationManager
import org.tasks.themes.TasksTheme
import org.tasks.time.DateTime
import org.tasks.time.millisOfDay
import org.tasks.time.startOfDay
import org.tasks.time.withMillisOfDay
import java.util.Calendar.FRIDAY
import java.util.Calendar.MONDAY
import java.util.Calendar.SATURDAY
import java.util.Calendar.SUNDAY
import java.util.Calendar.THURSDAY
import java.util.Calendar.TUESDAY
import java.util.Calendar.WEDNESDAY
import javax.inject.Inject
@AndroidEntryPoint
@ -46,19 +56,9 @@ class DateTimePicker : BaseDateTimePicker() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var notificationManager: NotificationManager
lateinit var binding: DialogDateTimePickerBinding
private var customDate = NO_DAY
private var customTime = NO_TIME
private var selectedDay = NO_DAY
private var selectedTime = NO_TIME
private var selectedDay by mutableLongStateOf(NO_DAY)
private var selectedTime by mutableIntStateOf(NO_TIME)
private val today = newDateTime().startOfDay()
private val tomorrow = today.plusDays(1)
private val nextWeek = today.plusDays(7)
override val calendarView get() = binding.calendarView
override val morningButton get() = binding.shortcuts.morningButton
override val afternoonButton get() = binding.shortcuts.afternoonButton
override val eveningButton get() = binding.shortcuts.eveningButton
override val nightButton get() = binding.shortcuts.nightButton
companion object {
const val EXTRA_DAY = "extra_day"
@ -66,16 +66,14 @@ class DateTimePicker : BaseDateTimePicker() {
const val EXTRA_TASKS = "extra_tasks"
const val EXTRA_TIMESTAMP = "extra_timestamp"
const val EXTRA_HIDE_NO_DATE = "extra_hide_no_date"
private const val REQUEST_TIME = 10101
private const val FRAG_TAG_TIME_PICKER = "frag_tag_time_picker"
private const val NO_DAY = 0L
private const val NO_TIME = 0
private const val MULTIPLE_DAYS = -1L
private const val MULTIPLE_TIMES = -1
const val NO_DAY = 0L
const val NO_TIME = 0
const val MULTIPLE_DAYS = -1L
const val MULTIPLE_TIMES = -1
fun newDateTimePicker(
autoClose: Boolean,
vararg tasks: Task
vararg tasks: Task,
): DateTimePicker {
val fragment = DateTimePicker()
val dueDates = tasks.map { it.dueDate.startOfDay() }.toSet()
@ -109,124 +107,115 @@ class DateTimePicker : BaseDateTimePicker() {
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DialogDateTimePickerBinding.inflate(theme.getLayoutInflater(requireContext()))
setupShortcutsAndCalendar()
with (binding.shortcuts) {
nextWeekButton.text =
getString(
when (newDateTime().plusWeeks(1).dayOfWeek) {
SUNDAY -> R.string.next_sunday
MONDAY -> R.string.next_monday
TUESDAY -> R.string.next_tuesday
WEDNESDAY -> R.string.next_wednesday
THURSDAY -> R.string.next_thursday
FRIDAY -> R.string.next_friday
SATURDAY -> R.string.next_saturday
else -> throw IllegalArgumentException()
}
)
noDateButton.isGone = requireArguments().getBoolean(EXTRA_HIDE_NO_DATE, false)
noDateButton.setOnClickListener { clearDate() }
noTime.setOnClickListener { clearTime() }
todayButton.setOnClickListener { setToday() }
tomorrowButton.setOnClickListener { setTomorrow() }
nextWeekButton.setOnClickListener { setNextWeek() }
morningButton.setOnClickListener { setMorning() }
afternoonButton.setOnClickListener { setAfternoon() }
eveningButton.setOnClickListener { setEvening() }
nightButton.setOnClickListener { setNight() }
currentDateSelection.setOnClickListener { currentDate() }
currentTimeSelection.setOnClickListener { currentTime() }
pickTimeButton.setOnClickListener { pickTime() }
}
binding.calendarView.setOnDateChangeListener { _, y, m, d ->
returnDate(day = DateTime(y, m + 1, d).millis)
refreshButtons()
}
selectedDay = savedInstanceState?.getLong(EXTRA_DAY) ?: requireArguments().getLong(EXTRA_DAY)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
selectedDay =
savedInstanceState?.getLong(EXTRA_DAY) ?: requireArguments().getLong(EXTRA_DAY)
selectedTime =
savedInstanceState?.getInt(EXTRA_TIME)
?: requireArguments().getInt(EXTRA_TIME)
.takeIf { it == MULTIPLE_TIMES || Task.hasDueTime(it.toLong()) }
?: NO_TIME
return binding.root
}
override fun refreshButtons() {
when (selectedDay) {
0L -> binding.shortcuts.dateGroup.check(R.id.no_date_button)
today.millis -> binding.shortcuts.dateGroup.check(R.id.today_button)
tomorrow.millis -> binding.shortcuts.dateGroup.check(R.id.tomorrow_button)
nextWeek.millis -> binding.shortcuts.dateGroup.check(R.id.next_week_button)
else -> {
customDate = selectedDay
binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) {
requireContext().getString(R.string.date_picker_multiple)
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
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 {
runBlocking {
getRelativeDay(selectedDay)
}
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(
containerColor = MaterialTheme.colorScheme.surface,
),
)
}
if (selectedTime == MULTIPLE_TIMES || Task.hasDueTime(selectedTime.toLong())) {
when (selectedTime) {
morning -> binding.shortcuts.timeGroup.check(R.id.morning_button)
afternoon -> binding.shortcuts.timeGroup.check(R.id.afternoon_button)
evening -> binding.shortcuts.timeGroup.check(R.id.evening_button)
night -> binding.shortcuts.timeGroup.check(R.id.night_button)
else -> {
customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE
binding.shortcuts.currentTimeSelection.text =
if (customTime == MULTIPLE_TIMES) {
requireContext().getString(R.string.date_picker_multiple)
LaunchedEffect(selectedDay) {
if (selectedDay > 0) {
state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset)
} else {
getTimeString(
today.millis.withMillisOfDay(selectedTime),
requireContext().is24HourFormat
)
state.selectedDateMillis = null
}
}
LaunchedEffect(state.selectedDateMillis) {
if (state.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) {
return@LaunchedEffect
}
} else {
binding.shortcuts.timeGroup.check(R.id.no_time)
state.selectedDateMillis?.let {
returnDate(day = it - DateTime(it).offset)
}
if (selectedDay > 0) {
binding.calendarView.setDate(selectedDay, true, true)
}
}
private fun clearDate() = returnDate(day = 0, time = 0)
private fun clearTime() = returnDate(time = 0)
private fun setToday() = returnDate(day = today.startOfDay().millis)
private fun setTomorrow() = returnDate(day = tomorrow.startOfDay().millis)
private fun setNextWeek() = returnDate(day = nextWeek.startOfDay().millis)
private fun setMorning() = returnSelectedTime(morning)
private fun setAfternoon() = returnSelectedTime(afternoon)
private fun setEvening() = returnSelectedTime(evening)
private fun setNight() = returnSelectedTime(night)
private fun currentDate() = returnDate(day = customDate)
private fun currentTime() = returnSelectedTime(customTime)
private fun pickTime() {
val time = if (selectedTime == MULTIPLE_TIMES
|| !Task.hasDueTime(today.withMillisOfDay(selectedTime).millis)) {
today.noon().millisOfDay
} else {
selectedTime
}
newTimePicker(this, REQUEST_TIME, today.withMillisOfDay(time).millis)
.show(parentFragmentManager, FRAG_TAG_TIME_PICKER)
}
private fun returnSelectedTime(millisOfDay: Int) {
@ -244,8 +233,6 @@ class DateTimePicker : BaseDateTimePicker() {
selectedTime = time
if (closeAutomatically()) {
sendSelected()
} else {
refreshButtons()
}
}
@ -254,14 +241,17 @@ class DateTimePicker : BaseDateTimePicker() {
override fun sendSelected() {
if (selectedDay != arguments?.getLong(EXTRA_DAY)
|| selectedTime != arguments?.getInt(EXTRA_TIME)) {
|| selectedTime != arguments?.getInt(EXTRA_TIME)
) {
if (taskIds.isEmpty()) {
val intent = Intent()
intent.putExtra(EXTRA_TIMESTAMP, when {
intent.putExtra(
EXTRA_TIMESTAMP, when {
selectedDay == NO_DAY -> 0
selectedTime == NO_TIME -> selectedDay
else -> selectedDay.toDateTime().withMillisOfDay(selectedTime).millis
})
}
)
targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, intent)
} else {
lifecycleScope.launch(NonCancellable) {
@ -278,17 +268,20 @@ class DateTimePicker : BaseDateTimePicker() {
} else {
selectedTime
}
it.setDueDateAdjustingHideUntil(when {
it.setDueDateAdjustingHideUntil(
when {
day == NO_DAY -> 0L
time == NO_TIME -> createDueDate(
Task.URGENCY_SPECIFIC_DAY,
day
)
else -> createDueDate(
Task.URGENCY_SPECIFIC_DAY_TIME,
day.toDateTime().withMillisOfDay(time).millis
)
})
}
)
taskDao.save(it)
}
}
@ -303,17 +296,4 @@ class DateTimePicker : BaseDateTimePicker() {
outState.putLong(EXTRA_DAY, selectedDay)
outState.putInt(EXTRA_TIME, selectedTime)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_TIME) {
if (resultCode == RESULT_OK) {
val timestamp = data!!.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, today.millis)
returnSelectedTime(newDateTime(timestamp).millisOfDay + 1000)
} else {
refreshButtons()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}

@ -15,7 +15,6 @@ import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.compose.edit.Priority
import org.tasks.data.entity.Task
import org.tasks.databinding.DialogDateTimePickerBinding
import org.tasks.databinding.DialogPriorityPickerBinding
import javax.inject.Inject
@ -39,7 +38,6 @@ class PriorityPicker : DialogFragment() {
}
}
lateinit var binding: DialogDateTimePickerBinding
private val priorityPickerViewModel: PriorityPickerViewModel by viewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

@ -5,23 +5,41 @@ import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
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
import androidx.compose.runtime.mutableLongStateOf
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 kotlinx.coroutines.runBlocking
import org.tasks.R
import org.tasks.compose.pickers.DatePickerShortcuts
import org.tasks.compose.pickers.StartDateShortcuts
import org.tasks.compose.pickers.TimePickerDialog
import org.tasks.compose.pickers.TimeShortcuts
import org.tasks.data.entity.Task
import org.tasks.databinding.DialogStartDatePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker
import org.tasks.dialogs.MyTimePickerDialog.Companion.timeInputMode
import org.tasks.extensions.Context.is24HourFormat
import org.tasks.kmp.org.tasks.time.getRelativeDay
import org.tasks.kmp.org.tasks.time.getTimeString
import org.tasks.notifications.NotificationManager
import org.tasks.themes.TasksTheme
import org.tasks.time.DateTime
import org.tasks.time.withMillisOfDay
import javax.inject.Inject
@AndroidEntryPoint
@ -31,21 +49,11 @@ class StartDatePicker : BaseDateTimePicker() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var notificationManager: NotificationManager
lateinit var binding: DialogStartDatePickerBinding
private var customDate = NO_DAY
private var customTime = NO_TIME
private var selectedDay = NO_DAY
private var selectedTime = NO_TIME
private var selectedDay by mutableLongStateOf(NO_DAY)
private var selectedTime by mutableIntStateOf(NO_TIME)
private val today = newDateTime().startOfDay()
override val calendarView get() = binding.calendarView
override val morningButton get() = binding.shortcuts.morningButton
override val afternoonButton get() = binding.shortcuts.afternoonButton
override val eveningButton get() = binding.shortcuts.eveningButton
override val nightButton get() = binding.shortcuts.nightButton
companion object {
private const val REQUEST_TIME = 10101
private const val FRAG_TAG_TIME_PICKER = "frag_tag_time_picker"
const val EXTRA_DAY = "extra_day"
const val EXTRA_TIME = "extra_time"
const val NO_DAY = 0L
@ -67,110 +75,116 @@ class StartDatePicker : BaseDateTimePicker() {
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DialogStartDatePickerBinding.inflate(theme.getLayoutInflater(requireContext()))
setupShortcutsAndCalendar()
binding.calendarView.setOnDateChangeListener { _, y, m, d ->
returnDate(day = DateTime(y, m + 1, d).millis)
refreshButtons()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
selectedDay = savedInstanceState?.getLong(EXTRA_DAY) ?: requireArguments().getLong(EXTRA_DAY)
selectedTime =
savedInstanceState?.getInt(EXTRA_TIME)
?: requireArguments().getInt(EXTRA_TIME)
.takeIf { Task.hasDueTime(it.toLong()) }
?: NO_TIME
with(binding.shortcuts) {
noDateButton.setOnClickListener { clearDate() }
noTime.setOnClickListener { clearTime() }
dueDateButton.setOnClickListener { setToday() }
dayBeforeDueButton.setOnClickListener { setTomorrow() }
weekBeforeDueButton.setOnClickListener { setNextWeek() }
morningButton.setOnClickListener { setMorning() }
afternoonButton.setOnClickListener { setAfternoon() }
eveningButton.setOnClickListener { setEvening() }
nightButton.setOnClickListener { setNight() }
dueTimeButton.setOnClickListener { setDueTime() }
currentDateSelection.setOnClickListener { currentDate() }
currentTimeSelection.setOnClickListener { currentTime() }
pickTimeButton.setOnClickListener { pickTime() }
}
return binding.root
}
override fun refreshButtons() {
when (selectedDay) {
0L -> binding.shortcuts.dateGroup.check(R.id.no_date_button)
DUE_DATE -> binding.shortcuts.dateGroup.check(R.id.due_date_button)
DUE_TIME -> {
binding.shortcuts.dateGroup.check(R.id.due_time_button)
binding.shortcuts.timeGroup.clearChecked()
}
DAY_BEFORE_DUE -> binding.shortcuts.dateGroup.check(R.id.day_before_due_button)
WEEK_BEFORE_DUE -> binding.shortcuts.dateGroup.check(R.id.week_before_due_button)
else -> {
customDate = selectedDay
binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text =
runBlocking {
getRelativeDay(selectedDay)
}
}
}
if (Task.hasDueTime(selectedTime.toLong())) {
when (selectedTime) {
morning -> binding.shortcuts.timeGroup.check(R.id.morning_button)
afternoon -> binding.shortcuts.timeGroup.check(R.id.afternoon_button)
evening -> binding.shortcuts.timeGroup.check(R.id.evening_button)
night -> binding.shortcuts.timeGroup.check(R.id.night_button)
else -> {
customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE
binding.shortcuts.currentTimeSelection.text = getTimeString(
today.millis.withMillisOfDay(selectedTime),
requireContext().is24HourFormat
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
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 < 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 }
)
}
if (selectedDay == DUE_TIME) {
selectedDay = DUE_DATE
}
} else if (selectedDay != DUE_TIME) {
binding.shortcuts.timeGroup.check(R.id.no_time)
}
if (selectedDay > 0) {
binding.calendarView.setDate(selectedDay, true, true)
}
}
private fun clearDate() = returnDate(day = 0, time = 0)
private fun clearTime() = returnDate(
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
)
private fun setToday() = returnDate(day = DUE_DATE)
private fun setTomorrow() = returnDate(day = DAY_BEFORE_DUE)
private fun setNextWeek() = returnDate(day = WEEK_BEFORE_DUE)
private fun setMorning() = returnSelectedTime(morning)
private fun setAfternoon() = returnSelectedTime(afternoon)
private fun setEvening() = returnSelectedTime(evening)
private fun setNight() = returnSelectedTime(night)
private fun setDueTime() = returnDate(day = DUE_TIME, time = NO_TIME)
private fun currentDate() = returnDate(day = customDate)
private fun currentTime() = returnSelectedTime(customTime)
private fun pickTime() {
val time = if (selectedTime < 0 || !Task.hasDueTime(today.withMillisOfDay(selectedTime).millis)) {
today.noon().millisOfDay
},
)
},
dateShortcuts = {
StartDateShortcuts(
selected = selectedDay,
selectedDay = { returnDate(it) },
selectedDayTime = { day, time -> returnDate(day, time) },
clearDate = { returnDate(day = 0, time = 0) },
)
},
)
},
colors = DatePickerDefaults.colors(
containerColor = MaterialTheme.colorScheme.surface,
),
)
LaunchedEffect(selectedDay) {
if (selectedDay > 0) {
state.selectedDateMillis = selectedDay + (DateTime(selectedDay).offset)
} else {
selectedTime
state.selectedDateMillis = null
}
}
LaunchedEffect(state.selectedDateMillis) {
if (state.selectedDateMillis == selectedDay + (DateTime(selectedDay).offset)) {
return@LaunchedEffect
}
state.selectedDateMillis?.let {
returnDate(day = it - DateTime(it).offset)
}
}
}
}
}
newTimePicker(this, REQUEST_TIME, today.withMillisOfDay(time).millis)
.show(parentFragmentManager, FRAG_TAG_TIME_PICKER)
}
private fun returnSelectedTime(millisOfDay: Int) {
@ -188,8 +202,6 @@ class StartDatePicker : BaseDateTimePicker() {
selectedTime = time
if (closeAutomatically()) {
sendSelected()
} else {
refreshButtons()
}
}
@ -211,17 +223,4 @@ class StartDatePicker : BaseDateTimePicker() {
outState.putLong(EXTRA_DAY, selectedDay)
outState.putInt(EXTRA_TIME, selectedTime)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_TIME) {
if (resultCode == RESULT_OK) {
val timestamp = data!!.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, today.millis)
returnSelectedTime(newDateTime(timestamp).millisOfDay + 1000)
} else {
refreshButtons()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="1.00" android:color="?attr/colorAccent" android:state_checkable="true" android:state_checked="true" android:state_enabled="true"/>
<item android:alpha="0.60" android:color="?attr/colorOnSurface" android:state_checkable="true" android:state_checked="false" android:state_enabled="true"/>
<item android:alpha="1.00" android:color="?attr/colorAccent" android:state_enabled="true"/>
<item android:alpha="0.38" android:color="?attr/colorOnSurface"/>
</selector>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnSecondary" android:state_selected="true" />
<item android:color="?attr/colorOnSurface" />
</selector>

@ -1,148 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/date_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/half_keyline_first"
android:layout_marginBottom="@dimen/half_keyline_first"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toLeftOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="wrap"
app:layout_constraintWidth_percent="0.5"
app:singleSelection="true"
android:layout_marginStart="16dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/current_date_selection"
style="@style/DateTimeShortcuts"
tools:text="Nov 24, 2021"
android:visibility="gone"
app:icon="@drawable/ic_outline_today_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/today_button"
style="@style/DateTimeShortcuts"
android:text="@string/today"
app:icon="@drawable/ic_calendar_today_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/tomorrow_button"
style="@style/DateTimeShortcuts"
android:text="@string/tomorrow"
app:icon="@drawable/ic_outline_wb_sunny_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/today_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/next_week_button"
style="@style/DateTimeShortcuts"
tools:text="Next Thurs"
app:icon="@drawable/ic_next_week_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tomorrow_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/no_date_button"
style="@style/DateTimeShortcuts"
android:text="@string/no_date"
app:icon="@drawable/ic_outline_not_interested_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/next_week_button" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/time_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/half_keyline_first"
android:layout_marginBottom="@dimen/half_keyline_first"
android:orientation="vertical"
app:layout_constraintLeft_toRightOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="wrap"
app:layout_constraintWidth_percent="0.5"
app:selectionRequired="true"
app:singleSelection="true"
android:layout_marginEnd="16dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/current_time_selection"
style="@style/DateTimeShortcuts"
app:icon="@drawable/ic_outline_schedule_24px"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="08:15" />
<com.google.android.material.button.MaterialButton
android:id="@+id/morning_button"
style="@style/DateTimeShortcuts"
tools:text="9 AM"
app:icon="@drawable/ic_local_cafe_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/afternoon_button"
style="@style/DateTimeShortcuts"
android:layout_marginTop="1dp"
tools:text="1 PM"
app:icon="@drawable/ic_outline_wb_sunny_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/morning_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/evening_button"
style="@style/DateTimeShortcuts"
tools:text="5 PM"
app:icon="@drawable/ic_weather_sunset"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/afternoon_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/night_button"
style="@style/DateTimeShortcuts"
tools:text="8 PM"
app:icon="@drawable/ic_nights_stay_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/evening_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/pick_time_button"
style="@style/DateTimeShortcuts"
android:text="@string/shortcut_pick_time"
app:icon="@drawable/ic_outline_schedule_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/evening_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/no_time"
style="@style/DateTimeShortcuts"
android:text="@string/no_time"
app:icon="@drawable/ic_outline_not_interested_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/night_button" />
</com.google.android.material.button.MaterialButtonToggleGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dialog_background"
android:paddingBottom="@dimen/keyline_first">
<include
layout="@layout/date_time_picker_shortcuts"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/shortcuts" />
<CalendarView
android:id="@+id/calendar_view"
android:layout_below="@id/shortcuts"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
</androidx.core.widget.NestedScrollView>

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dialog_background">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end">
<View
style="@style/horizontal_divider"
android:id="@+id/divider"
android:background="@color/divider"
android:layout_alignParentTop="true" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:textColor="?attr/colorAccent"
android:id="@+id/ok_button"
android:text="@string/ok"
android:layout_below="@id/divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:textColor="?attr/colorAccent"
android:layout_below="@id/divider"
android:id="@+id/cancel_button"
android:text="@string/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:layout_toStartOf="@id/ok_button" />
</RelativeLayout>
</FrameLayout>

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/keyline_first">
<include
layout="@layout/start_date_picker_shortcuts"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/shortcuts" />
<CalendarView
android:id="@+id/calendar_view"
android:layout_below="@id/shortcuts"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
</androidx.core.widget.NestedScrollView>

@ -1,149 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/date_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/half_keyline_first"
android:layout_marginBottom="@dimen/half_keyline_first"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toLeftOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="wrap"
app:layout_constraintWidth_percent="0.5"
app:singleSelection="true"
android:layout_marginStart="16dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/current_date_selection"
style="@style/DateTimeShortcuts"
tools:text="Nov 24, 2021"
android:visibility="gone"
app:icon="@drawable/ic_outline_today_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/due_date_button"
style="@style/DateTimeShortcuts"
android:text="@string/due_date"
app:icon="@drawable/ic_calendar_today_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/due_time_button"
style="@style/DateTimeShortcuts"
app:icon="@drawable/ic_outline_schedule_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="@string/due_time" />
<com.google.android.material.button.MaterialButton
android:id="@+id/day_before_due_button"
style="@style/DateTimeShortcuts"
android:text="@string/day_before_due"
app:icon="@drawable/ic_outline_wb_sunny_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/due_date_button" />
<com.google.android.material.button.MaterialButton
android:id="@+id/week_before_due_button"
style="@style/DateTimeShortcuts"
app:icon="@drawable/ic_date_range_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/day_before_due_button"
android:text="@string/week_before_due" />
<com.google.android.material.button.MaterialButton
android:id="@+id/no_date_button"
style="@style/DateTimeShortcuts"
android:text="@string/no_date"
app:icon="@drawable/ic_outline_not_interested_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/week_before_due_button" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/time_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/half_keyline_first"
android:layout_marginBottom="@dimen/half_keyline_first"
android:orientation="vertical"
app:layout_constraintLeft_toRightOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="wrap"
app:layout_constraintWidth_percent="0.5"
app:selectionRequired="true"
app:singleSelection="true"
android:layout_marginEnd="16dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/current_time_selection"
style="@style/DateTimeShortcuts"
app:icon="@drawable/ic_outline_schedule_24px"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible"
tools:text="08:15" />
<com.google.android.material.button.MaterialButton
android:id="@+id/morning_button"
style="@style/DateTimeShortcuts"
tools:text="9 AM"
app:icon="@drawable/ic_local_cafe_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/afternoon_button"
style="@style/DateTimeShortcuts"
android:layout_marginTop="1dp"
tools:text="1 PM"
app:icon="@drawable/ic_outline_wb_sunny_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/evening_button"
style="@style/DateTimeShortcuts"
tools:text="5 PM"
app:icon="@drawable/ic_weather_sunset"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/night_button"
style="@style/DateTimeShortcuts"
tools:text="8 PM"
app:icon="@drawable/ic_nights_stay_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/pick_time_button"
style="@style/DateTimeShortcuts"
android:text="@string/shortcut_pick_time"
app:icon="@drawable/ic_outline_schedule_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/no_time"
style="@style/DateTimeShortcuts"
android:text="@string/no_time"
app:icon="@drawable/ic_outline_not_interested_24px"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</com.google.android.material.button.MaterialButtonToggleGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -98,20 +98,6 @@
<item name="android:textColor">@color/text_primary</item>
</style>
<style name="DateTimeShortcuts" parent="Widget.Material3.Button.TextButton">
<item name="android:textColor">@color/button_accent_text</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item>
<item name="android:paddingTop">@dimen/quarter_keyline_first</item>
<item name="android:paddingBottom">@dimen/quarter_keyline_first</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="iconPadding">@dimen/keyline_first</item>
<item name="iconTint">@color/button_accent_text</item>
<item name="android:gravity">start|center_vertical</item>
<item name="backgroundTint">@android:color/transparent</item>
<item name="rippleColor">?attr/colorSecondary</item>
</style>
<!--=============================================== MainActivity == -->
<style name="BaseHorizontalDivider">

Loading…
Cancel
Save