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
* 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.getActivityViewModel
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.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.payment.RequiresPurchaseDialogFragment
import io.timelimit.android.ui.view.SelectTimeSpanViewListener
@ -115,6 +116,18 @@ class LockActionFragment : Fragment() {
override fun requestLocationPermission() {
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,
timeZone = content.userRelatedData.timeZone
)
binding.manageDisableTimeLimits.handlers = ManageDisableTimelimitsViewHelper.createHandlers(
childId = content.userId,
childTimezone = content.timeZone,
activity = requireActivity(),
hasFullVersion = content.hasFullVersion
)
}
is LockscreenContent.Blocked.BlockDueToNoCategory -> {
binding.appCategoryTitle = null
@ -298,4 +305,5 @@ interface Handlers {
fun disableTemporarilyLockForAllCategories()
fun setThisDeviceAsCurrentDevice()
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
* 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.category.create.CreateCategoryDialogFragment
import io.timelimit.android.ui.manage.child.category.specialmode.SetCategorySpecialModeFragment
import io.timelimit.android.ui.manage.child.category.specialmode.SpecialModeDialogMode
class ManageChildCategoriesFragment : Fragment() {
companion object {
@ -118,7 +119,7 @@ class ManageChildCategoriesFragment : Fragment() {
SetCategorySpecialModeFragment.newInstance(
childId = params.childId,
categoryId = category.category.id,
selfLimitMode = true
mode = SpecialModeDialogMode.Regular
).show(parentFragmentManager)
false
@ -128,7 +129,7 @@ class ManageChildCategoriesFragment : Fragment() {
SetCategorySpecialModeFragment.newInstance(
childId = params.childId,
categoryId = category.category.id,
selfLimitMode = !auth.isParentAuthenticated()
mode = if (auth.isParentAuthenticated()) SpecialModeDialogMode.Regular else SpecialModeDialogMode.SelfLimitAdd
).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
* 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 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_SUGGESTION = 1
private const val PAGE_CLOCK = 2
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 {
putString(CHILD_ID, childId)
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? {
val childId = requireArguments().getString(CHILD_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() }
} else {
auth.authenticatedUser.observe(viewLifecycleOwner) { if (it == null) dismissAllowingStateLoss() }
@ -134,7 +134,8 @@ class SetCategorySpecialModeFragment: DialogFragment() {
when (screen) {
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
}
@ -148,7 +149,7 @@ class SetCategorySpecialModeFragment: DialogFragment() {
binding.blockTemporarilyOption.setOnClickListener { model.selectType(SetCategorySpecialModeModel.Type.BlockTemporarily) }
binding.disableLimitsOption.setOnClickListener { model.selectType(SetCategorySpecialModeModel.Type.DisableLimits) }
binding.isAddLimitMode = selfLimitMode
binding.isAddLimitMode = mode == SpecialModeDialogMode.SelfLimitAdd
binding.suggestionList.also {
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
* 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 var didInit = false
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()
@ -68,8 +68,8 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
}
}
val minTimestamp = selfLimitAddModeLive.switchMap { selfLimitAddMode ->
if (selfLimitAddMode) selfLimitAddModeMinTimestamp else nowLive
val minTimestamp = specialMode.switchMap { mode ->
if (mode == SpecialModeDialogMode.SelfLimitAdd) selfLimitAddModeMinTimestamp else nowLive
}
val content: LiveData<Content?> = object: MediatorLiveData<Content>() {
@ -80,7 +80,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
addSource(durationSelectionLive) { update() }
addSource(typeLive) { update() }
addSource(userRelatedData) { didLoadUserRelatedData = true; update() }
addSource(selfLimitAddModeLive) { update() }
addSource(specialMode) { update() }
}
fun update() {
@ -93,19 +93,22 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
val durationSelection = durationSelectionLive.value!!
val type = typeLive.value
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 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
}
val screen = if (type == null) Screen.SelectType else when (durationSelection) {
DurationSelection.SuggestionList -> when (type) {
Type.BlockTemporarily -> {
if (selfLimitAddMode) SpecialModeDuration.items
if (specialMode == SpecialModeDialogMode.SelfLimitAdd) SpecialModeDuration.items
else listOf(SpecialModeOption.NoEndTimeOption) + SpecialModeDuration.items
}
Type.DisableLimits -> SpecialModeDuration.items
@ -136,7 +139,10 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
durationSelectionLive.value = DurationSelection.SuggestionList
true
} else if (typeLive.value != null) {
} else if (
typeLive.value != null &&
specialMode.value != SpecialModeDialogMode.DisableLimitsOnly
) {
typeLive.value = null
true
@ -147,7 +153,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
fun applySelection(selection: SpecialModeOption, auth: ActivityViewModel) {
val content = content.value
val screen = content?.screen
val selfLimitAddMode = selfLimitAddModeLive.value ?: return
val specialMode = specialMode.value ?: return
if (screen is Screen.WithType) {
when (selection) {
@ -160,7 +166,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
timezone = content.childTimezone
)
if (selfLimitAddMode) {
if (specialMode == SpecialModeDialogMode.SelfLimitAdd) {
val minTime = minTimestamp.value ?: return
if (endTime < minTime) {
@ -176,7 +182,7 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
endTime = endTime,
blocked = true
),
allowAsChild = selfLimitAddMode
allowAsChild = specialMode == SpecialModeDialogMode.SelfLimitAdd
)
requestClose.value = true
@ -221,12 +227,16 @@ class SetCategorySpecialModeModel(application: Application): AndroidViewModel(ap
auth = auth
)
fun init(childId: String, categoryId: String, selfLimitAddMode: Boolean) {
fun init(childId: String, categoryId: String, mode: SpecialModeDialogMode) {
if (!didInit) {
didInit = true
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"?>
<!--
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
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 3 of the License.
@ -282,11 +282,32 @@
</LinearLayout>
</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:id="@+id/manage_disable_time_limits"
android:foreground="?selectableItemBackground"
android:onClick="@{() -> handlers.disableLimitsTemporarily()}"
app:cardUseCompatPadding="true"
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
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_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">
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>

View file

@ -671,6 +671,8 @@
<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_disable_limits_temporarily_text">Temporarily disable limits for the category to allow using this application</string>
<string name="lock_reason_no_category">
This App was not assigned to any category.
Due to that, there are no restriction settings.