Allow selecting date and time for special mode end times

This commit is contained in:
Jonas Lochmann 2023-07-31 02:00:00 +02:00
parent a367aeec27
commit 85b83d4fb9
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
6 changed files with 108 additions and 59 deletions

View file

@ -0,0 +1,21 @@
/*
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.extensions
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZoneId
fun LocalDateTime.toInstant(zone: ZoneId) = toInstant(zone.rules.getOffset(this))

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -30,12 +30,14 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import io.timelimit.android.R
import io.timelimit.android.databinding.SpecialModeDialogBinding
import io.timelimit.android.extensions.showSafe
import io.timelimit.android.extensions.toInstant
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.getActivityViewModel
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
import org.threeten.bp.Instant
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.ZoneId
class SetCategorySpecialModeFragment: DialogFragment() {
@ -48,8 +50,8 @@ class SetCategorySpecialModeFragment: DialogFragment() {
private const val PAGE_TYPE = 0
private const val PAGE_SUGGESTION = 1
private const val PAGE_CLOCK = 2
private const val PAGE_CALENDAR = 3
private const val PAGE_CALENDAR = 2
private const val PAGE_CLOCK = 3
fun newInstance(childId: String, categoryId: String, mode: SpecialModeDialogMode) = SetCategorySpecialModeFragment().apply {
arguments = Bundle().apply {
@ -166,24 +168,22 @@ class SetCategorySpecialModeFragment: DialogFragment() {
}
run {
fun readClockTime(timeZone: String, now: Long) = binding.timePicker.let {
LocalDateTime.ofInstant(
Instant.ofEpochMilli(now),
ZoneId.of(timeZone)
)
.toLocalDate()
.atStartOfDay(ZoneId.of(timeZone))
.plusHours(it.currentHour.toLong())
.plusMinutes(it.currentMinute.toLong())
.toEpochSecond() * 1000
fun readClockTime(timeZone: String, localDate: LocalDate?, now: Long) = binding.timePicker.let {
val zoneId = ZoneId.of(timeZone)
LocalDateTime.of(
localDate ?: LocalDateTime.ofInstant(Instant.ofEpochMilli(now), zoneId).toLocalDate(),
LocalTime.of(it.currentHour, it.currentMinute)
).toInstant(zoneId).toEpochMilli()
}
fun update() {
val content = model.content.value
val screen = content?.screen
val minTime = model.minTimestamp.value
if (content?.screen is SetCategorySpecialModeModel.Screen.WithType.ClockScreen && minTime != null) {
val currentSelectedTime = readClockTime(content.childTimezone, model.now())
if (screen is SetCategorySpecialModeModel.Screen.WithType.ClockScreen && minTime != null) {
val currentSelectedTime = readClockTime(content.childTimezone, screen.date, model.now())
val isEnabled = currentSelectedTime > minTime
binding.confirmTimePickerButton.isEnabled = isEnabled
@ -203,10 +203,8 @@ class SetCategorySpecialModeFragment: DialogFragment() {
}
run {
fun readCalendarTime(timeZone: String) = binding.datePicker.let {
fun readCalendarDate() = binding.datePicker.let {
LocalDate.of(it.year, it.month + 1, it.dayOfMonth)
.atStartOfDay(ZoneId.of(timeZone))
.toEpochSecond() * 1000
}
fun update() {
@ -214,19 +212,23 @@ class SetCategorySpecialModeFragment: DialogFragment() {
val minTime = model.minTimestamp.value
if (content?.screen is SetCategorySpecialModeModel.Screen.WithType.CalendarScreen && minTime != null) {
val currentSelectedTime = readCalendarTime(content.childTimezone)
val isEnabled = currentSelectedTime > minTime
val zoneId = ZoneId.of(content.childTimezone)
val currentSelectedDate = readCalendarDate()
val currentStartOfDayTime = LocalDateTime.of(currentSelectedDate, LocalTime.MIN).toInstant(zoneId).toEpochMilli()
val currentEndOfDayTime = LocalDateTime.of(currentSelectedDate, LocalTime.of(23, 59)).toInstant(zoneId).toEpochMilli()
binding.confirmDatePickerButton.isEnabled = isEnabled
val isConfirmEnabled = currentStartOfDayTime > minTime
val isClockEnabled = currentEndOfDayTime > minTime
if (isEnabled) {
binding.confirmDatePickerButton.setOnClickListener {
val currentSelectedTimeNew = readCalendarTime(content.childTimezone)
binding.confirmDatePickerButton.isEnabled = isConfirmEnabled
binding.timeOfDayDatePickerButton.isEnabled = isClockEnabled
if (currentSelectedTimeNew > minTime) {
model.applySelection(timeInMillis = currentSelectedTimeNew, auth = auth)
} else update()
}
if (isConfirmEnabled) binding.confirmDatePickerButton.setOnClickListener {
model.applySelection(timeInMillis = currentStartOfDayTime, auth = auth)
}
if (isClockEnabled) binding.timeOfDayDatePickerButton.setOnClickListener {
model.openClockScreen(currentSelectedDate)
}
} else binding.confirmDatePickerButton.isEnabled = false
}

View file

@ -26,6 +26,7 @@ import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.sync.actions.UpdateCategoryDisableLimitsAction
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
import io.timelimit.android.ui.main.ActivityViewModel
import org.threeten.bp.LocalDate
class SetCategorySpecialModeModel(application: Application): AndroidViewModel(application) {
private val logic = DefaultAppLogic.with(application)
@ -113,7 +114,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
options = options
)
}
DurationSelection.Clock -> Screen.WithType.ClockScreen(type = type)
is DurationSelection.Clock -> Screen.WithType.ClockScreen(type = type, date = durationSelection.date)
DurationSelection.Calendar -> Screen.WithType.CalendarScreen(type = type)
}
@ -127,11 +128,15 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
}
fun selectType(type: Type) { typeLive.value = type }
fun openClockScreen() { durationSelectionLive.value = DurationSelection.Clock }
fun openClockScreen(date: LocalDate? = null) { durationSelectionLive.value = DurationSelection.Clock(date) }
fun openCalendarScreen() { durationSelectionLive.value = DurationSelection.Calendar }
fun goBack(): Boolean = if (durationSelectionLive.value != DurationSelection.SuggestionList) {
durationSelectionLive.value = DurationSelection.SuggestionList
val v = durationSelectionLive.value
durationSelectionLive.value =
if (v is DurationSelection.Clock && v.date != null) DurationSelection.Calendar
else DurationSelection.SuggestionList
true
} else if (
@ -240,10 +245,10 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
DisableLimits
}
internal enum class DurationSelection {
SuggestionList,
Clock,
Calendar
internal sealed class DurationSelection {
object SuggestionList: DurationSelection()
data class Clock(val date: LocalDate?): DurationSelection()
object Calendar: DurationSelection()
}
data class Content(
@ -259,8 +264,8 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
sealed class WithType: Screen() {
abstract val type: Type
data class ClockScreen(override val type: Type, ): WithType()
data class CalendarScreen(override val type: Type, ): WithType()
data class ClockScreen(override val type: Type, val date: LocalDate?): WithType()
data class CalendarScreen(override val type: Type): WithType()
data class SuggestionList(
override val type: Type,

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
@ -99,6 +99,46 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<DatePicker
android:layout_gravity="center_horizontal"
android:id="@+id/date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
style="?borderlessButtonStyle"
android:layout_margin="8dp"
android:text="@string/manage_child_special_mode_wizard_time_of_day"
android:id="@+id/time_of_day_date_picker_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="1dp" />
<Button
android:layout_margin="8dp"
android:text="@string/generic_ok"
android:id="@+id/confirm_date_picker_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -119,27 +159,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<DatePicker
android:layout_gravity="center_horizontal"
android:id="@+id/date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_margin="8dp"
android:layout_gravity="end"
android:text="@string/generic_ok"
android:id="@+id/confirm_date_picker_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</io.timelimit.android.ui.view.SafeViewFlipper>
</LinearLayout>
</layout>

View file

@ -829,6 +829,7 @@
<string name="manage_child_special_mode_wizard_block_option">vorübergehend sperren</string>
<string name="manage_child_special_mode_wizard_disable_limits_title">Begrenzungen von %s deaktivieren</string>
<string name="manage_child_special_mode_wizard_disable_limits_option">Begrenzungen vorübergehend deaktivieren</string>
<string name="manage_child_special_mode_wizard_time_of_day">Uhrzeit wählen</string>
<string name="manage_child_confirm_enable_limits_again_title">Begrenzungen aktivieren</string>
<string name="manage_child_confirm_enable_limits_again_text">Sollen die Begrenzungen für die Kategorie %s wirklich wieder aktiviert werden?</string>

View file

@ -883,6 +883,7 @@
<string name="manage_child_special_mode_wizard_block_option">block temporarily</string>
<string name="manage_child_special_mode_wizard_disable_limits_title">Disable limits for %s</string>
<string name="manage_child_special_mode_wizard_disable_limits_option">disable limits temporarily</string>
<string name="manage_child_special_mode_wizard_time_of_day">Select time</string>
<string name="manage_child_confirm_enable_limits_again_title">Enable limitations</string>
<string name="manage_child_confirm_enable_limits_again_text">Would you like to enable the limitations for the category %s?</string>