mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-04 02:09:19 +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
|
* 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
|
* 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.R
|
||||||
import io.timelimit.android.databinding.SpecialModeDialogBinding
|
import io.timelimit.android.databinding.SpecialModeDialogBinding
|
||||||
import io.timelimit.android.extensions.showSafe
|
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.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.getActivityViewModel
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
|
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
|
||||||
import org.threeten.bp.Instant
|
import org.threeten.bp.Instant
|
||||||
import org.threeten.bp.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import org.threeten.bp.LocalTime
|
||||||
import org.threeten.bp.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
|
|
||||||
class SetCategorySpecialModeFragment: DialogFragment() {
|
class SetCategorySpecialModeFragment: DialogFragment() {
|
||||||
|
@ -48,8 +50,8 @@ class SetCategorySpecialModeFragment: DialogFragment() {
|
||||||
|
|
||||||
private const val PAGE_TYPE = 0
|
private const val PAGE_TYPE = 0
|
||||||
private const val PAGE_SUGGESTION = 1
|
private const val PAGE_SUGGESTION = 1
|
||||||
private const val PAGE_CLOCK = 2
|
private const val PAGE_CALENDAR = 2
|
||||||
private const val PAGE_CALENDAR = 3
|
private const val PAGE_CLOCK = 3
|
||||||
|
|
||||||
fun newInstance(childId: String, categoryId: String, mode: SpecialModeDialogMode) = SetCategorySpecialModeFragment().apply {
|
fun newInstance(childId: String, categoryId: String, mode: SpecialModeDialogMode) = SetCategorySpecialModeFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
|
@ -166,24 +168,22 @@ class SetCategorySpecialModeFragment: DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
run {
|
run {
|
||||||
fun readClockTime(timeZone: String, now: Long) = binding.timePicker.let {
|
fun readClockTime(timeZone: String, localDate: LocalDate?, now: Long) = binding.timePicker.let {
|
||||||
LocalDateTime.ofInstant(
|
val zoneId = ZoneId.of(timeZone)
|
||||||
Instant.ofEpochMilli(now),
|
|
||||||
ZoneId.of(timeZone)
|
LocalDateTime.of(
|
||||||
)
|
localDate ?: LocalDateTime.ofInstant(Instant.ofEpochMilli(now), zoneId).toLocalDate(),
|
||||||
.toLocalDate()
|
LocalTime.of(it.currentHour, it.currentMinute)
|
||||||
.atStartOfDay(ZoneId.of(timeZone))
|
).toInstant(zoneId).toEpochMilli()
|
||||||
.plusHours(it.currentHour.toLong())
|
|
||||||
.plusMinutes(it.currentMinute.toLong())
|
|
||||||
.toEpochSecond() * 1000
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update() {
|
fun update() {
|
||||||
val content = model.content.value
|
val content = model.content.value
|
||||||
|
val screen = content?.screen
|
||||||
val minTime = model.minTimestamp.value
|
val minTime = model.minTimestamp.value
|
||||||
|
|
||||||
if (content?.screen is SetCategorySpecialModeModel.Screen.WithType.ClockScreen && minTime != null) {
|
if (screen is SetCategorySpecialModeModel.Screen.WithType.ClockScreen && minTime != null) {
|
||||||
val currentSelectedTime = readClockTime(content.childTimezone, model.now())
|
val currentSelectedTime = readClockTime(content.childTimezone, screen.date, model.now())
|
||||||
val isEnabled = currentSelectedTime > minTime
|
val isEnabled = currentSelectedTime > minTime
|
||||||
|
|
||||||
binding.confirmTimePickerButton.isEnabled = isEnabled
|
binding.confirmTimePickerButton.isEnabled = isEnabled
|
||||||
|
@ -203,10 +203,8 @@ class SetCategorySpecialModeFragment: DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
run {
|
run {
|
||||||
fun readCalendarTime(timeZone: String) = binding.datePicker.let {
|
fun readCalendarDate() = binding.datePicker.let {
|
||||||
LocalDate.of(it.year, it.month + 1, it.dayOfMonth)
|
LocalDate.of(it.year, it.month + 1, it.dayOfMonth)
|
||||||
.atStartOfDay(ZoneId.of(timeZone))
|
|
||||||
.toEpochSecond() * 1000
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update() {
|
fun update() {
|
||||||
|
@ -214,19 +212,23 @@ class SetCategorySpecialModeFragment: DialogFragment() {
|
||||||
val minTime = model.minTimestamp.value
|
val minTime = model.minTimestamp.value
|
||||||
|
|
||||||
if (content?.screen is SetCategorySpecialModeModel.Screen.WithType.CalendarScreen && minTime != null) {
|
if (content?.screen is SetCategorySpecialModeModel.Screen.WithType.CalendarScreen && minTime != null) {
|
||||||
val currentSelectedTime = readCalendarTime(content.childTimezone)
|
val zoneId = ZoneId.of(content.childTimezone)
|
||||||
val isEnabled = currentSelectedTime > minTime
|
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.isEnabled = isConfirmEnabled
|
||||||
binding.confirmDatePickerButton.setOnClickListener {
|
binding.timeOfDayDatePickerButton.isEnabled = isClockEnabled
|
||||||
val currentSelectedTimeNew = readCalendarTime(content.childTimezone)
|
|
||||||
|
|
||||||
if (currentSelectedTimeNew > minTime) {
|
if (isConfirmEnabled) binding.confirmDatePickerButton.setOnClickListener {
|
||||||
model.applySelection(timeInMillis = currentSelectedTimeNew, auth = auth)
|
model.applySelection(timeInMillis = currentStartOfDayTime, auth = auth)
|
||||||
} else update()
|
}
|
||||||
}
|
|
||||||
|
if (isClockEnabled) binding.timeOfDayDatePickerButton.setOnClickListener {
|
||||||
|
model.openClockScreen(currentSelectedDate)
|
||||||
}
|
}
|
||||||
} else binding.confirmDatePickerButton.isEnabled = false
|
} 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.UpdateCategoryDisableLimitsAction
|
||||||
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
|
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
|
import org.threeten.bp.LocalDate
|
||||||
|
|
||||||
class SetCategorySpecialModeModel(application: Application): AndroidViewModel(application) {
|
class SetCategorySpecialModeModel(application: Application): AndroidViewModel(application) {
|
||||||
private val logic = DefaultAppLogic.with(application)
|
private val logic = DefaultAppLogic.with(application)
|
||||||
|
@ -113,7 +114,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
|
||||||
options = options
|
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)
|
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 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 openCalendarScreen() { durationSelectionLive.value = DurationSelection.Calendar }
|
||||||
|
|
||||||
fun goBack(): Boolean = if (durationSelectionLive.value != DurationSelection.SuggestionList) {
|
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
|
true
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -240,10 +245,10 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
|
||||||
DisableLimits
|
DisableLimits
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum class DurationSelection {
|
internal sealed class DurationSelection {
|
||||||
SuggestionList,
|
object SuggestionList: DurationSelection()
|
||||||
Clock,
|
data class Clock(val date: LocalDate?): DurationSelection()
|
||||||
Calendar
|
object Calendar: DurationSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Content(
|
data class Content(
|
||||||
|
@ -259,8 +264,8 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
|
||||||
sealed class WithType: Screen() {
|
sealed class WithType: Screen() {
|
||||||
abstract val type: Type
|
abstract val type: Type
|
||||||
|
|
||||||
data class ClockScreen(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 CalendarScreen(override val type: Type): WithType()
|
||||||
|
|
||||||
data class SuggestionList(
|
data class SuggestionList(
|
||||||
override val type: Type,
|
override val type: Type,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -99,6 +99,46 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
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
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -119,27 +159,6 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</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>
|
</io.timelimit.android.ui.view.SafeViewFlipper>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</layout>
|
</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_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_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_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_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>
|
<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_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_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_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_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>
|
<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