mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 17:59:51 +02:00
Allow selecting date and time for special mode end times
This commit is contained in:
parent
a367aeec27
commit
85b83d4fb9
6 changed files with 108 additions and 59 deletions
|
@ -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))
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue