Allow disabling limits for single category from the lockscreen

This commit is contained in:
Jonas Lochmann 2022-05-09 02:00:00 +02:00
parent aa06c8aaed
commit 2f68475fa3
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
8 changed files with 105 additions and 38 deletions

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 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
@ -45,8 +45,9 @@ import io.timelimit.android.ui.help.HelpDialogFragment
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.manage.category.settings.networks.RequestWifiPermission import io.timelimit.android.ui.manage.category.settings.networks.RequestWifiPermission
import io.timelimit.android.ui.manage.child.advanced.managedisabletimelimits.ManageDisableTimelimitsViewHelper
import io.timelimit.android.ui.manage.child.category.create.CreateCategoryDialogFragment import io.timelimit.android.ui.manage.child.category.create.CreateCategoryDialogFragment
import io.timelimit.android.ui.manage.child.category.specialmode.SetCategorySpecialModeFragment
import io.timelimit.android.ui.manage.child.category.specialmode.SpecialModeDialogMode
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
import io.timelimit.android.ui.view.SelectTimeSpanViewListener import io.timelimit.android.ui.view.SelectTimeSpanViewListener
@ -115,6 +116,18 @@ class LockActionFragment : Fragment() {
override fun requestLocationPermission() { override fun requestLocationPermission() {
RequestWifiPermission.doRequest(this@LockActionFragment, LOCATION_REQUEST_CODE) RequestWifiPermission.doRequest(this@LockActionFragment, LOCATION_REQUEST_CODE)
} }
override fun disableLimitsTemporarily() {
if (auth.requestAuthenticationOrReturnTrue()) {
if (blockedCategoryId != null) {
SetCategorySpecialModeFragment.newInstance(
childId = userRelatedData.user.id,
categoryId = blockedCategoryId,
mode = SpecialModeDialogMode.DisableLimitsOnly
).show(parentFragmentManager)
}
}
}
} }
} }
@ -253,12 +266,6 @@ class LockActionFragment : Fragment() {
categoryId = content.blockedCategoryId, categoryId = content.blockedCategoryId,
timeZone = content.userRelatedData.timeZone timeZone = content.userRelatedData.timeZone
) )
binding.manageDisableTimeLimits.handlers = ManageDisableTimelimitsViewHelper.createHandlers(
childId = content.userId,
childTimezone = content.timeZone,
activity = requireActivity(),
hasFullVersion = content.hasFullVersion
)
} }
is LockscreenContent.Blocked.BlockDueToNoCategory -> { is LockscreenContent.Blocked.BlockDueToNoCategory -> {
binding.appCategoryTitle = null binding.appCategoryTitle = null
@ -298,4 +305,5 @@ interface Handlers {
fun disableTemporarilyLockForAllCategories() fun disableTemporarilyLockForAllCategories()
fun setThisDeviceAsCurrentDevice() fun setThisDeviceAsCurrentDevice()
fun requestLocationPermission() fun requestLocationPermission()
fun disableLimitsTemporarily()
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 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
@ -43,6 +43,7 @@ import io.timelimit.android.ui.manage.child.ManageChildFragmentArgs
import io.timelimit.android.ui.manage.child.ManageChildFragmentDirections import io.timelimit.android.ui.manage.child.ManageChildFragmentDirections
import io.timelimit.android.ui.manage.child.category.create.CreateCategoryDialogFragment import io.timelimit.android.ui.manage.child.category.create.CreateCategoryDialogFragment
import io.timelimit.android.ui.manage.child.category.specialmode.SetCategorySpecialModeFragment import io.timelimit.android.ui.manage.child.category.specialmode.SetCategorySpecialModeFragment
import io.timelimit.android.ui.manage.child.category.specialmode.SpecialModeDialogMode
class ManageChildCategoriesFragment : Fragment() { class ManageChildCategoriesFragment : Fragment() {
companion object { companion object {
@ -118,7 +119,7 @@ class ManageChildCategoriesFragment : Fragment() {
SetCategorySpecialModeFragment.newInstance( SetCategorySpecialModeFragment.newInstance(
childId = params.childId, childId = params.childId,
categoryId = category.category.id, categoryId = category.category.id,
selfLimitMode = true mode = SpecialModeDialogMode.Regular
).show(parentFragmentManager) ).show(parentFragmentManager)
false false
@ -128,7 +129,7 @@ class ManageChildCategoriesFragment : Fragment() {
SetCategorySpecialModeFragment.newInstance( SetCategorySpecialModeFragment.newInstance(
childId = params.childId, childId = params.childId,
categoryId = category.category.id, categoryId = category.category.id,
selfLimitMode = !auth.isParentAuthenticated() mode = if (auth.isParentAuthenticated()) SpecialModeDialogMode.Regular else SpecialModeDialogMode.SelfLimitAdd
).show(parentFragmentManager) ).show(parentFragmentManager)
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 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
@ -43,18 +43,18 @@ class SetCategorySpecialModeFragment: DialogFragment() {
private const val CHILD_ID = "childId" private const val CHILD_ID = "childId"
private const val CATEGORY_ID = "categoryId" private const val CATEGORY_ID = "categoryId"
private const val SELF_LIMIT_MODE = "selfLimitMode" private const val MODE = "mode"
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_CLOCK = 2
private const val PAGE_CALENDAR = 3 private const val PAGE_CALENDAR = 3
fun newInstance(childId: String, categoryId: String, selfLimitMode: Boolean) = SetCategorySpecialModeFragment().apply { fun newInstance(childId: String, categoryId: String, mode: SpecialModeDialogMode) = SetCategorySpecialModeFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putString(CHILD_ID, childId) putString(CHILD_ID, childId)
putString(CATEGORY_ID, categoryId) putString(CATEGORY_ID, categoryId)
putBoolean(SELF_LIMIT_MODE, selfLimitMode) putSerializable(MODE, mode)
} }
} }
} }
@ -73,11 +73,11 @@ class SetCategorySpecialModeFragment: DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val childId = requireArguments().getString(CHILD_ID)!! val childId = requireArguments().getString(CHILD_ID)!!
val categoryId = requireArguments().getString(CATEGORY_ID)!! val categoryId = requireArguments().getString(CATEGORY_ID)!!
val selfLimitMode = requireArguments().getBoolean(SELF_LIMIT_MODE) val mode = requireArguments().getSerializable(MODE)!! as SpecialModeDialogMode
model.init(childId = childId, categoryId = categoryId, selfLimitAddMode = selfLimitMode) model.init(childId = childId, categoryId = categoryId, mode = mode)
if (selfLimitMode) { if (mode == SpecialModeDialogMode.SelfLimitAdd) {
auth.authenticatedUserOrChild.observe(viewLifecycleOwner) { if (it == null) dismissAllowingStateLoss() } auth.authenticatedUserOrChild.observe(viewLifecycleOwner) { if (it == null) dismissAllowingStateLoss() }
} else { } else {
auth.authenticatedUser.observe(viewLifecycleOwner) { if (it == null) dismissAllowingStateLoss() } auth.authenticatedUser.observe(viewLifecycleOwner) { if (it == null) dismissAllowingStateLoss() }
@ -134,7 +134,8 @@ class SetCategorySpecialModeFragment: DialogFragment() {
when (screen) { when (screen) {
is SetCategorySpecialModeModel.Screen.WithType.SuggestionList -> { is SetCategorySpecialModeModel.Screen.WithType.SuggestionList -> {
setPage(PAGE_SUGGESTION) if (flipper.displayedChild == 0 && mode == SpecialModeDialogMode.DisableLimitsOnly) flipper.displayedChild = 1
else setPage(PAGE_SUGGESTION)
specialModeOptionAdapter.items = screen.options specialModeOptionAdapter.items = screen.options
} }
@ -148,7 +149,7 @@ class SetCategorySpecialModeFragment: DialogFragment() {
binding.blockTemporarilyOption.setOnClickListener { model.selectType(SetCategorySpecialModeModel.Type.BlockTemporarily) } binding.blockTemporarilyOption.setOnClickListener { model.selectType(SetCategorySpecialModeModel.Type.BlockTemporarily) }
binding.disableLimitsOption.setOnClickListener { model.selectType(SetCategorySpecialModeModel.Type.DisableLimits) } binding.disableLimitsOption.setOnClickListener { model.selectType(SetCategorySpecialModeModel.Type.DisableLimits) }
binding.isAddLimitMode = selfLimitMode binding.isAddLimitMode = mode == SpecialModeDialogMode.SelfLimitAdd
binding.suggestionList.also { binding.suggestionList.also {
it.adapter = specialModeOptionAdapter it.adapter = specialModeOptionAdapter

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 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
@ -39,7 +39,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
private val typeLive = MutableLiveData<Type?>().apply { value = null } private val typeLive = MutableLiveData<Type?>().apply { value = null }
private var didInit = false private var didInit = false
private val childAndCategoryId = MutableLiveData<Pair<String, String>>() private val childAndCategoryId = MutableLiveData<Pair<String, String>>()
private val selfLimitAddModeLive = MutableLiveData<Boolean>().apply { value = false } private val specialMode = MutableLiveData<SpecialModeDialogMode>()
fun now() = logic.realTimeLogic.getCurrentTimeInMillis() fun now() = logic.realTimeLogic.getCurrentTimeInMillis()
@ -68,8 +68,8 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
} }
} }
val minTimestamp = selfLimitAddModeLive.switchMap { selfLimitAddMode -> val minTimestamp = specialMode.switchMap { mode ->
if (selfLimitAddMode) selfLimitAddModeMinTimestamp else nowLive if (mode == SpecialModeDialogMode.SelfLimitAdd) selfLimitAddModeMinTimestamp else nowLive
} }
val content: LiveData<Content?> = object: MediatorLiveData<Content>() { val content: LiveData<Content?> = object: MediatorLiveData<Content>() {
@ -80,7 +80,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
addSource(durationSelectionLive) { update() } addSource(durationSelectionLive) { update() }
addSource(typeLive) { update() } addSource(typeLive) { update() }
addSource(userRelatedData) { didLoadUserRelatedData = true; update() } addSource(userRelatedData) { didLoadUserRelatedData = true; update() }
addSource(selfLimitAddModeLive) { update() } addSource(specialMode) { update() }
} }
fun update() { fun update() {
@ -93,19 +93,22 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
val durationSelection = durationSelectionLive.value!! val durationSelection = durationSelectionLive.value!!
val type = typeLive.value val type = typeLive.value
val (userRelatedData, categoryId) = userRelatedData.value ?: run { value = null; return } val (userRelatedData, categoryId) = userRelatedData.value ?: run { value = null; return }
val selfLimitAddMode = selfLimitAddModeLive.value ?: return val specialMode = specialMode.value ?: return
val targetCategory = userRelatedData.categoryById[categoryId] ?: run { value = null; return } val targetCategory = userRelatedData.categoryById[categoryId] ?: run { value = null; return }
val categoryTitle = targetCategory.category.title val categoryTitle = targetCategory.category.title
if (targetCategory.category.temporarilyBlocked && targetCategory.category.temporarilyBlockedEndTime == 0L && selfLimitAddMode) { if (
targetCategory.category.temporarilyBlocked && targetCategory.category.temporarilyBlockedEndTime == 0L &&
specialMode == SpecialModeDialogMode.SelfLimitAdd
) {
value = null; return value = null; return
} }
val screen = if (type == null) Screen.SelectType else when (durationSelection) { val screen = if (type == null) Screen.SelectType else when (durationSelection) {
DurationSelection.SuggestionList -> when (type) { DurationSelection.SuggestionList -> when (type) {
Type.BlockTemporarily -> { Type.BlockTemporarily -> {
if (selfLimitAddMode) SpecialModeDuration.items if (specialMode == SpecialModeDialogMode.SelfLimitAdd) SpecialModeDuration.items
else listOf(SpecialModeOption.NoEndTimeOption) + SpecialModeDuration.items else listOf(SpecialModeOption.NoEndTimeOption) + SpecialModeDuration.items
} }
Type.DisableLimits -> SpecialModeDuration.items Type.DisableLimits -> SpecialModeDuration.items
@ -136,7 +139,10 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
durationSelectionLive.value = DurationSelection.SuggestionList durationSelectionLive.value = DurationSelection.SuggestionList
true true
} else if (typeLive.value != null) { } else if (
typeLive.value != null &&
specialMode.value != SpecialModeDialogMode.DisableLimitsOnly
) {
typeLive.value = null typeLive.value = null
true true
@ -147,7 +153,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
fun applySelection(selection: SpecialModeOption, auth: ActivityViewModel) { fun applySelection(selection: SpecialModeOption, auth: ActivityViewModel) {
val content = content.value val content = content.value
val screen = content?.screen val screen = content?.screen
val selfLimitAddMode = selfLimitAddModeLive.value ?: return val specialMode = specialMode.value ?: return
if (screen is Screen.WithType) { if (screen is Screen.WithType) {
when (selection) { when (selection) {
@ -160,7 +166,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
timezone = content.childTimezone timezone = content.childTimezone
) )
if (selfLimitAddMode) { if (specialMode == SpecialModeDialogMode.SelfLimitAdd) {
val minTime = minTimestamp.value ?: return val minTime = minTimestamp.value ?: return
if (endTime < minTime) { if (endTime < minTime) {
@ -176,7 +182,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
endTime = endTime, endTime = endTime,
blocked = true blocked = true
), ),
allowAsChild = selfLimitAddMode allowAsChild = specialMode == SpecialModeDialogMode.SelfLimitAdd
) )
requestClose.value = true requestClose.value = true
@ -221,12 +227,16 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
auth = auth auth = auth
) )
fun init(childId: String, categoryId: String, selfLimitAddMode: Boolean) { fun init(childId: String, categoryId: String, mode: SpecialModeDialogMode) {
if (!didInit) { if (!didInit) {
didInit = true didInit = true
childAndCategoryId.value = childId to categoryId childAndCategoryId.value = childId to categoryId
selfLimitAddModeLive.value = selfLimitAddMode specialMode.value = mode
if (mode == SpecialModeDialogMode.DisableLimitsOnly) {
selectType(Type.DisableLimits)
}
} }
} }

View file

@ -0,0 +1,22 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 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.ui.manage.child.category.specialmode
enum class SpecialModeDialogMode {
Regular,
DisableLimitsOnly,
SelfLimitAdd
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann TimeLimit Copyright <C> 2019 - 2022 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.
@ -282,11 +282,32 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<io.timelimit.android.ui.view.ManageDisableTimelimitsView <androidx.cardview.widget.CardView
android:visibility="@{reason == BlockingReason.BlockedAtThisTime || reason == BlockingReason.TimeOver || reason == BlockingReason.TimeOverExtraTimeCanBeUsedLater || reason == BlockingReason.SessionDurationLimit || reason == BlockingReason.MissingRequiredNetwork ? View.VISIBLE : View.GONE}" android:visibility="@{reason == BlockingReason.BlockedAtThisTime || reason == BlockingReason.TimeOver || reason == BlockingReason.TimeOverExtraTimeCanBeUsedLater || reason == BlockingReason.SessionDurationLimit || reason == BlockingReason.MissingRequiredNetwork ? View.VISIBLE : View.GONE}"
android:id="@+id/manage_disable_time_limits" android:foreground="?selectableItemBackground"
android:onClick="@{() -> handlers.disableLimitsTemporarily()}"
app:cardUseCompatPadding="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
tools:text="@string/manage_child_special_mode_wizard_disable_limits_title"
android:text="@{@string/manage_child_special_mode_wizard_disable_limits_title(appCategoryTitle)}"
android:textAppearance="?android:textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:text="@string/lock_disable_limits_temporarily_text"
android:textAppearance="?android:textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:visibility="@{reason == BlockingReason.RequiresCurrentDevice ? View.VISIBLE : View.GONE}" android:visibility="@{reason == BlockingReason.RequiresCurrentDevice ? View.VISIBLE : View.GONE}"

View file

@ -620,6 +620,8 @@
<string name="lock_grant_permission_title">Berechtigung erteilen</string> <string name="lock_grant_permission_title">Berechtigung erteilen</string>
<string name="lock_grant_permission_text">TimeLimit ermöglichen, das verbundene Netzwerk zu erkennen</string> <string name="lock_grant_permission_text">TimeLimit ermöglichen, das verbundene Netzwerk zu erkennen</string>
<string name="lock_disable_limits_temporarily_text">Begrenzungen für die Kategorie vorübergehnd deaktivieren, um diese Anwendung freizugeben</string>
<string name="lock_reason_no_category"> <string name="lock_reason_no_category">
Die App wurde zu keiner Kategorie zugeordnet. Das bedeutet, dass es keine Einschränkungs-Einstellungen gibt. Und da ist es die einfachste Lösung, die App zu sperren. Die App wurde zu keiner Kategorie zugeordnet. Das bedeutet, dass es keine Einschränkungs-Einstellungen gibt. Und da ist es die einfachste Lösung, die App zu sperren.
</string> </string>

View file

@ -671,6 +671,8 @@
<string name="lock_grant_permission_title">Grant permission</string> <string name="lock_grant_permission_title">Grant permission</string>
<string name="lock_grant_permission_text">Allow TimeLimit to see the connected network</string> <string name="lock_grant_permission_text">Allow TimeLimit to see the connected network</string>
<string name="lock_disable_limits_temporarily_text">Temporarily disable limits for the category to allow using this application</string>
<string name="lock_reason_no_category"> <string name="lock_reason_no_category">
This App was not assigned to any category. This App was not assigned to any category.
Due to that, there are no restriction settings. Due to that, there are no restriction settings.