Correct the calculations for choosing the earliest day to show in the calendar / selected day (#3923)

To determine the earliest day to show in the calendar, take the current
date/time, add the minimum scheduled seconds buffer (which may roll the
date/time over to the next day), and then clamp to the start of that
day. So it's either today (if the current time + minimum scheduled
seconds is less than midnight) or it's tomorrow.

When displaying the calendar work around a misfeature in Material Date
Picker. It accepts UTC seconds-since-epoch, but does not convert it to
the local time for display.

While I'm here, show the selected day in the time picker's title.

Fixes https://github.com/tuskyapp/Tusky/issues/3916
This commit is contained in:
Nik Clayton 2023-08-07 19:30:08 +02:00 committed by GitHub
parent 846289b8cc
commit 5d4c14aed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 36 additions and 17 deletions

View File

@ -57,7 +57,9 @@ class ComposeScheduleView
).apply { ).apply {
timeZone = TimeZone.getTimeZone("UTC") timeZone = TimeZone.getTimeZone("UTC")
} }
private var scheduleDateTime: Calendar? = null
/** The date/time the user has chosen to schedule the status, in UTC */
private var scheduleDateTimeUtc: Calendar? = null
init { init {
binding.scheduledDateTime.setOnClickListener { openPickDateDialog() } binding.scheduledDateTime.setOnClickListener { openPickDateDialog() }
@ -71,13 +73,13 @@ class ComposeScheduleView
} }
private fun updateScheduleUi() { private fun updateScheduleUi() {
if (scheduleDateTime == null) { if (scheduleDateTimeUtc == null) {
binding.scheduledDateTime.text = "" binding.scheduledDateTime.text = ""
binding.invalidScheduleWarning.visibility = GONE binding.invalidScheduleWarning.visibility = GONE
return return
} }
val scheduled = scheduleDateTime!!.time val scheduled = scheduleDateTimeUtc!!.time
binding.scheduledDateTime.text = String.format( binding.scheduledDateTime.text = String.format(
"%s %s", "%s %s",
dateFormat.format(scheduled), dateFormat.format(scheduled),
@ -98,21 +100,37 @@ class ComposeScheduleView
} }
fun resetSchedule() { fun resetSchedule() {
scheduleDateTime = null scheduleDateTimeUtc = null
updateScheduleUi() updateScheduleUi()
} }
fun openPickDateDialog() { fun openPickDateDialog() {
val yesterday = Calendar.getInstance().timeInMillis - 24 * 60 * 60 * 1000 // The earliest point in time the calendar should display. Start with current date/time
val earliest = calendar().apply {
// Add the minimum scheduling interval. This may roll the calendar over to the
// next day (e.g. if the current time is 23:57).
add(Calendar.SECOND, MINIMUM_SCHEDULED_SECONDS)
// Clear out the time components, so it's midnight
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val calendarConstraints = CalendarConstraints.Builder() val calendarConstraints = CalendarConstraints.Builder()
.setValidator( .setValidator(DateValidatorPointForward.from(earliest.timeInMillis))
DateValidatorPointForward.from(yesterday)
)
.build() .build()
initializeSuggestedTime() initializeSuggestedTime()
// Work around a misfeature in MaterialDatePicker. The `selection` is treated as
// millis-from-epoch, in UTC, which is good. However, it is also *displayed* in UTC
// instead of converting to the user's local timezone.
//
// So we have to add the TZ offset before setting it in the picker
val tzOffset = TimeZone.getDefault().getOffset(scheduleDateTimeUtc!!.timeInMillis)
val picker = MaterialDatePicker.Builder val picker = MaterialDatePicker.Builder
.datePicker() .datePicker()
.setSelection(scheduleDateTime!!.timeInMillis) .setSelection(scheduleDateTimeUtc!!.timeInMillis + tzOffset)
.setCalendarConstraints(calendarConstraints) .setCalendarConstraints(calendarConstraints)
.build() .build()
picker.addOnPositiveButtonClickListener { selection: Long -> onDateSet(selection) } picker.addOnPositiveButtonClickListener { selection: Long -> onDateSet(selection) }
@ -129,11 +147,12 @@ class ComposeScheduleView
private fun openPickTimeDialog() { private fun openPickTimeDialog() {
val pickerBuilder = MaterialTimePicker.Builder() val pickerBuilder = MaterialTimePicker.Builder()
scheduleDateTime?.let { scheduleDateTimeUtc?.let {
pickerBuilder.setHour(it[Calendar.HOUR_OF_DAY]) pickerBuilder.setHour(it[Calendar.HOUR_OF_DAY])
.setMinute(it[Calendar.MINUTE]) .setMinute(it[Calendar.MINUTE])
} }
pickerBuilder.setTitleText(dateFormat.format(scheduleDateTimeUtc!!.timeInMillis))
pickerBuilder.setTimeFormat(getTimeFormat(context)) pickerBuilder.setTimeFormat(getTimeFormat(context))
val picker = pickerBuilder.build() val picker = pickerBuilder.build()
@ -154,7 +173,7 @@ class ComposeScheduleView
fun setDateTime(scheduledAt: String?) { fun setDateTime(scheduledAt: String?) {
val date = getDateTime(scheduledAt) ?: return val date = getDateTime(scheduledAt) ?: return
initializeSuggestedTime() initializeSuggestedTime()
scheduleDateTime!!.time = date scheduleDateTimeUtc!!.time = date
updateScheduleUi() updateScheduleUi()
} }
@ -180,24 +199,24 @@ class ComposeScheduleView
// see https://github.com/material-components/material-components-android/issues/882 // see https://github.com/material-components/material-components-android/issues/882
newDate.timeZone = TimeZone.getTimeZone("UTC") newDate.timeZone = TimeZone.getTimeZone("UTC")
newDate.timeInMillis = selection newDate.timeInMillis = selection
scheduleDateTime!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE] scheduleDateTimeUtc!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE]
openPickTimeDialog() openPickTimeDialog()
} }
private fun onTimeSet(hourOfDay: Int, minute: Int) { private fun onTimeSet(hourOfDay: Int, minute: Int) {
initializeSuggestedTime() initializeSuggestedTime()
scheduleDateTime?.set(Calendar.HOUR_OF_DAY, hourOfDay) scheduleDateTimeUtc?.set(Calendar.HOUR_OF_DAY, hourOfDay)
scheduleDateTime?.set(Calendar.MINUTE, minute) scheduleDateTimeUtc?.set(Calendar.MINUTE, minute)
updateScheduleUi() updateScheduleUi()
listener?.onTimeSet(time) listener?.onTimeSet(time)
} }
val time: String? val time: String?
get() = scheduleDateTime?.time?.let { iso8601.format(it) } get() = scheduleDateTimeUtc?.time?.let { iso8601.format(it) }
private fun initializeSuggestedTime() { private fun initializeSuggestedTime() {
if (scheduleDateTime == null) { if (scheduleDateTimeUtc == null) {
scheduleDateTime = calendar().apply { scheduleDateTimeUtc = calendar().apply {
add(Calendar.MINUTE, 15) add(Calendar.MINUTE, 15)
} }
} }