Remove parent blocked time areas feature

This was replaced by the limit login category
This commit is contained in:
Jonas Lochmann 2020-09-07 02:00:00 +02:00
parent 206260d068
commit e672dd9eb1
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
20 changed files with 52 additions and 575 deletions

View file

@ -81,7 +81,7 @@
"notNull": true "notNull": true
}, },
{ {
"fieldPath": "blockedTimes", "fieldPath": "obsoleteBlockedTimes",
"columnName": "blocked_times", "columnName": "blocked_times",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true "notNull": true

View file

@ -20,11 +20,8 @@ import android.util.JsonWriter
import androidx.room.* import androidx.room.*
import io.timelimit.android.data.IdGenerator import io.timelimit.android.data.IdGenerator
import io.timelimit.android.data.JsonSerializable import io.timelimit.android.data.JsonSerializable
import io.timelimit.android.data.customtypes.ImmutableBitmask
import io.timelimit.android.data.customtypes.ImmutableBitmaskAdapter import io.timelimit.android.data.customtypes.ImmutableBitmaskAdapter
import io.timelimit.android.data.customtypes.ImmutableBitmaskJson
import io.timelimit.android.util.parseJsonArray import io.timelimit.android.util.parseJsonArray
import java.util.*
@Entity(tableName = "user") @Entity(tableName = "user")
@TypeConverters( @TypeConverters(
@ -62,7 +59,8 @@ data class User(
@ColumnInfo(name = "mail_notification_flags") @ColumnInfo(name = "mail_notification_flags")
val mailNotificationFlags: Int, val mailNotificationFlags: Int,
@ColumnInfo(name = "blocked_times") @ColumnInfo(name = "blocked_times")
val blockedTimes: ImmutableBitmask, @Deprecated(message = "this feature was removed; the limit login category is a replacement")
val obsoleteBlockedTimes: String = "",
@ColumnInfo(name = "flags") @ColumnInfo(name = "flags")
val flags: Long val flags: Long
): JsonSerializable { ): JsonSerializable {
@ -79,7 +77,7 @@ data class User(
private const val CATEGORY_FOR_NOT_ASSIGNED_APPS = "categoryForNotAssignedApps" private const val CATEGORY_FOR_NOT_ASSIGNED_APPS = "categoryForNotAssignedApps"
private const val RELAX_PRIMARY_DEVICE = "relaxPrimaryDevice" private const val RELAX_PRIMARY_DEVICE = "relaxPrimaryDevice"
private const val MAIL_NOTIFICATION_FLAGS = "mailNotificationFlags" private const val MAIL_NOTIFICATION_FLAGS = "mailNotificationFlags"
private const val BLOCKED_TIMES = "blockedTimes" private const val OBSOLETE_BLOCKED_TIMES = "blockedTimes"
private const val FLAGS = "flags" private const val FLAGS = "flags"
fun parse(reader: JsonReader): User { fun parse(reader: JsonReader): User {
@ -95,7 +93,6 @@ data class User(
var categoryForNotAssignedApps = "" var categoryForNotAssignedApps = ""
var relaxPrimaryDevice = false var relaxPrimaryDevice = false
var mailNotificationFlags = 0 var mailNotificationFlags = 0
var blockedTimes = ImmutableBitmask(BitSet())
var flags = 0L var flags = 0L
reader.beginObject() reader.beginObject()
@ -113,7 +110,6 @@ data class User(
CATEGORY_FOR_NOT_ASSIGNED_APPS -> categoryForNotAssignedApps = reader.nextString() CATEGORY_FOR_NOT_ASSIGNED_APPS -> categoryForNotAssignedApps = reader.nextString()
RELAX_PRIMARY_DEVICE -> relaxPrimaryDevice = reader.nextBoolean() RELAX_PRIMARY_DEVICE -> relaxPrimaryDevice = reader.nextBoolean()
MAIL_NOTIFICATION_FLAGS -> mailNotificationFlags = reader.nextInt() MAIL_NOTIFICATION_FLAGS -> mailNotificationFlags = reader.nextInt()
BLOCKED_TIMES -> blockedTimes = ImmutableBitmaskJson.parse(reader.nextString(), Category.BLOCKED_MINUTES_IN_WEEK_LENGTH)
FLAGS -> flags = reader.nextLong() FLAGS -> flags = reader.nextLong()
else -> reader.skipValue() else -> reader.skipValue()
} }
@ -133,7 +129,6 @@ data class User(
categoryForNotAssignedApps = categoryForNotAssignedApps, categoryForNotAssignedApps = categoryForNotAssignedApps,
relaxPrimaryDevice = relaxPrimaryDevice, relaxPrimaryDevice = relaxPrimaryDevice,
mailNotificationFlags = mailNotificationFlags, mailNotificationFlags = mailNotificationFlags,
blockedTimes = blockedTimes,
flags = flags flags = flags
) )
} }
@ -186,7 +181,7 @@ data class User(
writer.name(CATEGORY_FOR_NOT_ASSIGNED_APPS).value(categoryForNotAssignedApps) writer.name(CATEGORY_FOR_NOT_ASSIGNED_APPS).value(categoryForNotAssignedApps)
writer.name(RELAX_PRIMARY_DEVICE).value(relaxPrimaryDevice) writer.name(RELAX_PRIMARY_DEVICE).value(relaxPrimaryDevice)
writer.name(MAIL_NOTIFICATION_FLAGS).value(mailNotificationFlags) writer.name(MAIL_NOTIFICATION_FLAGS).value(mailNotificationFlags)
writer.name(BLOCKED_TIMES).value(ImmutableBitmaskJson.serialize(blockedTimes)) writer.name(OBSOLETE_BLOCKED_TIMES).value("")
writer.name(FLAGS).value(flags) writer.name(FLAGS).value(flags)
writer.endObject() writer.endObject()

View file

@ -128,7 +128,6 @@ class AppSetupLogic(private val appLogic: AppLogic) {
categoryForNotAssignedApps = "", categoryForNotAssignedApps = "",
relaxPrimaryDevice = false, relaxPrimaryDevice = false,
mailNotificationFlags = 0, mailNotificationFlags = 0,
blockedTimes = ImmutableBitmask(BitSet()),
flags = 0 flags = 0
) )
@ -151,7 +150,6 @@ class AppSetupLogic(private val appLogic: AppLogic) {
categoryForNotAssignedApps = "", categoryForNotAssignedApps = "",
relaxPrimaryDevice = false, relaxPrimaryDevice = false,
mailNotificationFlags = 0, mailNotificationFlags = 0,
blockedTimes = ImmutableBitmask(BitSet()),
flags = 0 flags = 0
) )

View file

@ -66,7 +66,6 @@ object ApplyServerDataStatus {
categoryForNotAssignedApps = newEntry.categoryForNotAssignedApps, categoryForNotAssignedApps = newEntry.categoryForNotAssignedApps,
relaxPrimaryDevice = newEntry.relaxPrimaryDevice, relaxPrimaryDevice = newEntry.relaxPrimaryDevice,
mailNotificationFlags = newEntry.mailNotificationFlags, mailNotificationFlags = newEntry.mailNotificationFlags,
blockedTimes = newEntry.blockedTimes,
flags = newEntry.flags flags = newEntry.flags
) )

View file

@ -1832,48 +1832,6 @@ data class RenameChildAction(val childId: String, val newName: String): ParentAc
} }
} }
data class UpdateParentBlockedTimesAction(val parentId: String, val blockedTimes: ImmutableBitmask): ParentAction() {
companion object {
const val TYPE_VALUE = "UPDATE_PARENT_BLOCKED_TIMES"
private const val PARENT_ID = "parentId"
private const val BLOCKED_TIMES = "times"
}
init {
IdGenerator.assertIdValid(parentId)
}
override fun serialize(writer: JsonWriter) {
writer.beginObject()
writer.name(TYPE).value(TYPE_VALUE)
writer.name(PARENT_ID).value(parentId)
writer.name(BLOCKED_TIMES).value(ImmutableBitmaskJson.serialize(blockedTimes))
writer.endObject()
}
}
data class ResetParentBlockedTimesAction(val parentId: String): ParentAction() {
companion object {
const val TYPE_VALUE = "RESET_PARENT_BLOCKED_TIMES"
private const val PARENT_ID = "parentId"
}
init {
IdGenerator.assertIdValid(parentId)
}
override fun serialize(writer: JsonWriter) {
writer.beginObject()
writer.name(TYPE).value(TYPE_VALUE)
writer.name(PARENT_ID).value(parentId)
writer.endObject()
}
}
data class UpdateUserFlagsAction(val userId: String, val modifiedBits: Long, val newValues: Long): ParentAction() { data class UpdateUserFlagsAction(val userId: String, val modifiedBits: Long, val newValues: Long): ParentAction() {
companion object { companion object {
private const val TYPE_VALUE = "UPDATE_USER_FLAGS" private const val TYPE_VALUE = "UPDATE_USER_FLAGS"

View file

@ -252,7 +252,6 @@ object LocalDatabaseParentActionDispatcher {
categoryForNotAssignedApps = "", categoryForNotAssignedApps = "",
relaxPrimaryDevice = false, relaxPrimaryDevice = false,
mailNotificationFlags = 0, mailNotificationFlags = 0,
blockedTimes = ImmutableBitmask(BitSet()),
flags = 0 flags = 0
)) ))
} }
@ -674,32 +673,6 @@ object LocalDatabaseParentActionDispatcher {
null null
} }
is UpdateParentBlockedTimesAction -> {
val userEntry = database.user().getUserByIdSync(action.parentId)
if (userEntry?.type != UserType.Parent) {
throw IllegalArgumentException("no valid parent id")
}
database.user().updateUserSync(
userEntry.copy(
blockedTimes = action.blockedTimes
)
)
}
is ResetParentBlockedTimesAction -> {
val userEntry = database.user().getUserByIdSync(action.parentId)
if (userEntry?.type != UserType.Parent) {
throw IllegalArgumentException("no valid parent id")
}
database.user().updateUserSync(
userEntry.copy(
blockedTimes = ImmutableBitmask(BitSet())
)
)
}
is UpdateCategoryBatteryLimit -> { is UpdateCategoryBatteryLimit -> {
val categoryEntry = database.category().getCategoryByIdSync(action.categoryId) val categoryEntry = database.category().getCategoryByIdSync(action.categoryId)
?: throw IllegalArgumentException("can not update battery limit for a category which does not exist") ?: throw IllegalArgumentException("can not update battery limit for a category which does not exist")

View file

@ -174,7 +174,6 @@ data class ServerUserData(
val categoryForNotAssignedApps: String, val categoryForNotAssignedApps: String,
val relaxPrimaryDevice: Boolean, val relaxPrimaryDevice: Boolean,
val mailNotificationFlags: Int, val mailNotificationFlags: Int,
val blockedTimes: ImmutableBitmask,
val flags: Long, val flags: Long,
val limitLoginCategory: String? val limitLoginCategory: String?
) { ) {
@ -191,7 +190,6 @@ data class ServerUserData(
private const val CATEGORY_FOR_NOT_ASSIGNED_APPS = "categoryForNotAssignedApps" private const val CATEGORY_FOR_NOT_ASSIGNED_APPS = "categoryForNotAssignedApps"
private const val RELAX_PRIMARY_DEVICE = "relaxPrimaryDevice" private const val RELAX_PRIMARY_DEVICE = "relaxPrimaryDevice"
private const val MAIL_NOTIFICATION_FLAGS = "mailNotificationFlags" private const val MAIL_NOTIFICATION_FLAGS = "mailNotificationFlags"
private const val BLOCKED_TIMES = "blockedTimes"
private const val FLAGS = "flags" private const val FLAGS = "flags"
private const val USER_LIMIT_LOGIN_CATEGORY = "llc" private const val USER_LIMIT_LOGIN_CATEGORY = "llc"
@ -227,7 +225,6 @@ data class ServerUserData(
CATEGORY_FOR_NOT_ASSIGNED_APPS -> categoryForNotAssignedApps = reader.nextString() CATEGORY_FOR_NOT_ASSIGNED_APPS -> categoryForNotAssignedApps = reader.nextString()
RELAX_PRIMARY_DEVICE -> relaxPrimaryDevice = reader.nextBoolean() RELAX_PRIMARY_DEVICE -> relaxPrimaryDevice = reader.nextBoolean()
MAIL_NOTIFICATION_FLAGS -> mailNotificationFlags = reader.nextInt() MAIL_NOTIFICATION_FLAGS -> mailNotificationFlags = reader.nextInt()
BLOCKED_TIMES -> blockedTimes = ImmutableBitmaskJson.parse(reader.nextString(), Category.BLOCKED_MINUTES_IN_WEEK_LENGTH)
FLAGS -> flags = reader.nextLong() FLAGS -> flags = reader.nextLong()
USER_LIMIT_LOGIN_CATEGORY -> if (reader.peek() == JsonToken.NULL) reader.nextNull() else limitLoginCategory = reader.nextString() USER_LIMIT_LOGIN_CATEGORY -> if (reader.peek() == JsonToken.NULL) reader.nextNull() else limitLoginCategory = reader.nextString()
else -> reader.skipValue() else -> reader.skipValue()
@ -248,7 +245,6 @@ data class ServerUserData(
categoryForNotAssignedApps = categoryForNotAssignedApps, categoryForNotAssignedApps = categoryForNotAssignedApps,
relaxPrimaryDevice = relaxPrimaryDevice, relaxPrimaryDevice = relaxPrimaryDevice,
mailNotificationFlags = mailNotificationFlags, mailNotificationFlags = mailNotificationFlags,
blockedTimes = blockedTimes,
flags = flags, flags = flags,
limitLoginCategory = limitLoginCategory limitLoginCategory = limitLoginCategory
) )

View file

@ -33,7 +33,6 @@ import java.util.concurrent.CountDownLatch
sealed class AllowUserLoginStatus { sealed class AllowUserLoginStatus {
data class Allow(val maxTime: Long): AllowUserLoginStatus() data class Allow(val maxTime: Long): AllowUserLoginStatus()
data class ForbidByCurrentTime(val missingNetworkTime: Boolean, val maxTime: Long): AllowUserLoginStatus()
data class ForbidByCategory(val categoryTitle: String, val blockingReason: BlockingReason, val maxTime: Long): AllowUserLoginStatus() data class ForbidByCategory(val categoryTitle: String, val blockingReason: BlockingReason, val maxTime: Long): AllowUserLoginStatus()
object ForbidByMissingSync: AllowUserLoginStatus() object ForbidByMissingSync: AllowUserLoginStatus()
object ForbidUserNotFound: AllowUserLoginStatus() object ForbidUserNotFound: AllowUserLoginStatus()
@ -47,23 +46,6 @@ object AllowUserLoginStatusUtil {
return AllowUserLoginStatus.Allow(maxTime = Long.MAX_VALUE) return AllowUserLoginStatus.Allow(maxTime = Long.MAX_VALUE)
} }
if (!data.loginRelatedData.user.blockedTimes.dataNotToModify.isEmpty) {
if (!time.shouldTrustTimePermanently) {
return AllowUserLoginStatus.ForbidByCurrentTime(missingNetworkTime = true, maxTime = Long.MAX_VALUE)
} else {
val minuteOfWeek = getMinuteOfWeek(time.timeInMillis, TimeZone.getTimeZone(data.loginRelatedData.user.timeZone))
if (data.loginRelatedData.user.blockedTimes.dataNotToModify[minuteOfWeek]) {
val nextAllowedSlot = data.loginRelatedData.user.blockedTimes.dataNotToModify.nextClearBit(minuteOfWeek)
val minutesToWait: Long = (nextAllowedSlot - minuteOfWeek).toLong()
// not very nice but it works
val msToWait = if (minutesToWait <= 1) 5000 else (minutesToWait - 1) * 1000 * 60
return AllowUserLoginStatus.ForbidByCurrentTime(missingNetworkTime = false, maxTime = time.timeInMillis + msToWait)
}
}
}
return if (data.limitLoginCategoryUserRelatedData != null && data.loginRelatedData.limitLoginCategory != null) { return if (data.limitLoginCategoryUserRelatedData != null && data.loginRelatedData.limitLoginCategory != null) {
if (missingSyncForLimitLoginUser) { if (missingSyncForLimitLoginUser) {
AllowUserLoginStatus.ForbidByMissingSync AllowUserLoginStatus.ForbidByMissingSync
@ -87,33 +69,20 @@ object AllowUserLoginStatusUtil {
AllowUserLoginStatus.ForbidByCategory( AllowUserLoginStatus.ForbidByCategory(
categoryTitle = blockingHandling.createdWithCategoryRelatedData.category.title, categoryTitle = blockingHandling.createdWithCategoryRelatedData.category.title,
blockingReason = blockingHandling.systemLevelBlockingReason, blockingReason = blockingHandling.systemLevelBlockingReason,
maxTime = blockingHandling.dependsOnMaxTime.coerceAtMost( maxTime = blockingHandling.dependsOnMaxTime
if (data.loginRelatedData.user.blockedTimes.dataNotToModify.isEmpty)
Long.MAX_VALUE
else
time.timeInMillis + 1000 * 5
)
) )
} else { } else {
val maxTimeByCategories = handlings.minBy { it.dependsOnMaxTime }?.dependsOnMaxTime val maxTimeByCategories = handlings.minBy { it.dependsOnMaxTime }?.dependsOnMaxTime
?: Long.MAX_VALUE ?: Long.MAX_VALUE
AllowUserLoginStatus.Allow( AllowUserLoginStatus.Allow(
maxTime = maxTimeByCategories.coerceAtMost( maxTime = maxTimeByCategories
if (data.loginRelatedData.user.blockedTimes.dataNotToModify.isEmpty)
Long.MAX_VALUE
else
time.timeInMillis + 1000 * 5
)
) )
} }
} }
} else { } else {
AllowUserLoginStatus.Allow( AllowUserLoginStatus.Allow(
maxTime = if (data.loginRelatedData.user.blockedTimes.dataNotToModify.isEmpty) maxTime = Long.MAX_VALUE
Long.MAX_VALUE
else
time.timeInMillis + 1000 * 5
) )
} }
} }
@ -211,7 +180,6 @@ object AllowUserLoginStatusUtil {
val scheduledTime: Long = when (result) { val scheduledTime: Long = when (result) {
AllowUserLoginStatus.ForbidUserNotFound -> Long.MAX_VALUE AllowUserLoginStatus.ForbidUserNotFound -> Long.MAX_VALUE
AllowUserLoginStatus.ForbidByMissingSync -> Long.MAX_VALUE AllowUserLoginStatus.ForbidByMissingSync -> Long.MAX_VALUE
is AllowUserLoginStatus.ForbidByCurrentTime -> result.maxTime
is AllowUserLoginStatus.Allow -> result.maxTime is AllowUserLoginStatus.Allow -> result.maxTime
is AllowUserLoginStatus.ForbidByCategory -> result.maxTime is AllowUserLoginStatus.ForbidByCategory -> result.maxTime
} }

View file

@ -16,6 +16,7 @@
package io.timelimit.android.ui.login package io.timelimit.android.ui.login
import android.app.Application import android.app.Application
import android.content.Context
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -43,6 +44,34 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
class LoginDialogFragmentModel(application: Application): AndroidViewModel(application) { class LoginDialogFragmentModel(application: Application): AndroidViewModel(application) {
companion object {
private fun formatAllowLoginStatusError(status: AllowUserLoginStatus, context: Context): String = when (status) {
is AllowUserLoginStatus.Allow -> context.getString(R.string.error_general)
is AllowUserLoginStatus.ForbidUserNotFound -> context.getString(R.string.error_general)
is AllowUserLoginStatus.ForbidByCategory -> context.getString(
R.string.login_category_blocked,
status.categoryTitle,
formatBlockingReasonForLimitLoginCategory(status.blockingReason, context)
)
is AllowUserLoginStatus.ForbidByMissingSync -> context.getString(R.string.login_missing_sync)
}
fun formatBlockingReasonForLimitLoginCategory(reason: BlockingReason, context: Context) = when (reason) {
BlockingReason.TemporarilyBlocked -> context.getString(R.string.lock_reason_short_temporarily_blocked)
BlockingReason.TimeOver -> context.getString(R.string.lock_reason_short_time_over)
BlockingReason.TimeOverExtraTimeCanBeUsedLater -> context.getString(R.string.lock_reason_short_time_over)
BlockingReason.BlockedAtThisTime -> context.getString(R.string.lock_reason_short_blocked_time_area)
BlockingReason.MissingNetworkTime -> context.getString(R.string.lock_reason_short_missing_network_time)
BlockingReason.RequiresCurrentDevice -> context.getString(R.string.lock_reason_short_requires_current_device)
BlockingReason.NotificationsAreBlocked -> context.getString(R.string.lock_reason_short_notification_blocking)
BlockingReason.BatteryLimit -> context.getString(R.string.lock_reason_short_battery_limit)
BlockingReason.SessionDurationLimit -> context.getString(R.string.lock_reason_short_session_duration)
BlockingReason.MissingRequiredNetwork -> context.getString(R.string.lock_reason_short_missing_required_network)
BlockingReason.NotPartOfAnCategory -> "???"
BlockingReason.None -> "???"
}
}
val selectedUserId = MutableLiveData<String?>().apply { value = null } val selectedUserId = MutableLiveData<String?>().apply { value = null }
private val logic = DefaultAppLogic.with(application) private val logic = DefaultAppLogic.with(application)
private val users = logic.database.user().getAllUsersLive() private val users = logic.database.user().getAllUsersLive()
@ -85,12 +114,9 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
if (status is AllowUserLoginStatus.Allow) { if (status is AllowUserLoginStatus.Allow) {
loginScreen loginScreen
} else if ( } else if (
(status is AllowUserLoginStatus.ForbidByCurrentTime && status.missingNetworkTime) ||
(status is AllowUserLoginStatus.ForbidByCategory && status.blockingReason == BlockingReason.MissingNetworkTime) (status is AllowUserLoginStatus.ForbidByCategory && status.blockingReason == BlockingReason.MissingNetworkTime)
) { ) {
liveDataFromValue(ParentUserLoginMissingTrustedTime as LoginDialogStatus) liveDataFromValue(ParentUserLoginMissingTrustedTime as LoginDialogStatus)
} else if (status is AllowUserLoginStatus.ForbidByCurrentTime) {
liveDataFromValue(ParentUserLoginBlockedTime as LoginDialogStatus)
} else if (status is AllowUserLoginStatus.ForbidByCategory) { } else if (status is AllowUserLoginStatus.ForbidByCategory) {
liveDataFromValue( liveDataFromValue(
ParentUserLoginBlockedByCategory( ParentUserLoginBlockedByCategory(
@ -167,7 +193,6 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
allUsers.singleOrNull { it.type == UserType.Parent }?.let { user -> allUsers.singleOrNull { it.type == UserType.Parent }?.let { user ->
val emptyPasswordValid = Threads.crypto.executeAndWait { PasswordHashing.validateSync("", user.password) } val emptyPasswordValid = Threads.crypto.executeAndWait { PasswordHashing.validateSync("", user.password) }
val hasBlockedTimes = !user.blockedTimes.dataNotToModify.isEmpty
val shouldSignIn = if (emptyPasswordValid) { val shouldSignIn = if (emptyPasswordValid) {
Threads.database.executeAndWait { Threads.database.executeAndWait {
@ -188,10 +213,6 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
secondPasswordHash = Threads.crypto.executeAndWait { PasswordHashing.hashSyncWithSalt("", user.secondPasswordSalt) } secondPasswordHash = Threads.crypto.executeAndWait { PasswordHashing.hashSyncWithSalt("", user.secondPasswordSalt) }
)) ))
if (hasBlockedTimes) {
Toast.makeText(getApplication(), R.string.manage_parent_blocked_times_toast, Toast.LENGTH_LONG).show()
}
isLoginDone.value = true isLoginDone.value = true
} }
} }
@ -241,16 +262,16 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
} }
if (user != null && user.type == UserType.Parent) { if (user != null && user.type == UserType.Parent) {
val hasBlockedTimes = !user.blockedTimes.dataNotToModify.isEmpty val allowLoginStatus = Threads.database.executeAndWait {
val shouldSignIn = Threads.database.executeAndWait {
AllowUserLoginStatusUtil.calculateSync( AllowUserLoginStatusUtil.calculateSync(
logic = logic, logic = logic,
userId = user.id, userId = user.id,
didSync = didSync.value ?: false didSync = didSync.value ?: false
) is AllowUserLoginStatus.Allow )
} }
val shouldSignIn = allowLoginStatus is AllowUserLoginStatus.Allow
if (shouldSignIn) { if (shouldSignIn) {
// this feature is limited to the local mode // this feature is limited to the local mode
model.setAuthenticatedUser(AuthenticatedUser( model.setAuthenticatedUser(AuthenticatedUser(
@ -259,13 +280,9 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
secondPasswordHash = "device" secondPasswordHash = "device"
)) ))
if (hasBlockedTimes) {
Toast.makeText(getApplication(), R.string.manage_parent_blocked_times_toast, Toast.LENGTH_LONG).show()
}
isLoginDone.value = true isLoginDone.value = true
} else { } else {
Toast.makeText(getApplication(), R.string.login_blocked_time, Toast.LENGTH_SHORT).show() Toast.makeText(getApplication(), formatAllowLoginStatusError(allowLoginStatus, getApplication()), Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -310,27 +327,24 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
secondPasswordHash = secondPasswordHash secondPasswordHash = secondPasswordHash
) )
val hasBlockedTimes = !userEntry.blockedTimes.dataNotToModify.isEmpty val allowLoginStatus = Threads.database.executeAndWait {
val shouldSignIn = Threads.database.executeAndWait {
AllowUserLoginStatusUtil.calculateSync( AllowUserLoginStatusUtil.calculateSync(
logic = logic, logic = logic,
userId = userEntry.id, userId = userEntry.id,
didSync = didSync.value ?: false didSync = didSync.value ?: false
) is AllowUserLoginStatus.Allow )
} }
val shouldSignIn = allowLoginStatus is AllowUserLoginStatus.Allow
if (!shouldSignIn) { if (!shouldSignIn) {
Toast.makeText(getApplication(), R.string.login_blocked_time, Toast.LENGTH_SHORT).show() Toast.makeText(getApplication(), formatAllowLoginStatusError(allowLoginStatus, getApplication()), Toast.LENGTH_SHORT).show()
return@runAsync return@runAsync
} }
model.setAuthenticatedUser(authenticatedUser) model.setAuthenticatedUser(authenticatedUser)
if (hasBlockedTimes) {
Toast.makeText(getApplication(), R.string.manage_parent_blocked_times_toast, Toast.LENGTH_LONG).show()
}
if (setAsDeviceUser) { if (setAsDeviceUser) {
if (userEntryInfo.deviceRelatedData.deviceEntry.currentUserId != userEntry.id) { if (userEntryInfo.deviceRelatedData.deviceEntry.currentUserId != userEntry.id) {
ActivityViewModel.dispatchWithoutCheckOrCatching( ActivityViewModel.dispatchWithoutCheckOrCatching(
@ -435,7 +449,6 @@ class LoginDialogFragmentModel(application: Application): AndroidViewModel(appli
sealed class LoginDialogStatus sealed class LoginDialogStatus
data class UserListLoginDialogStatus(val usersToShow: List<User>, val isLocalMode: Boolean): LoginDialogStatus() data class UserListLoginDialogStatus(val usersToShow: List<User>, val isLocalMode: Boolean): LoginDialogStatus()
object ParentUserLoginMissingTrustedTime: LoginDialogStatus() object ParentUserLoginMissingTrustedTime: LoginDialogStatus()
object ParentUserLoginBlockedTime: LoginDialogStatus()
object ParentUserLoginWaitingForSync: LoginDialogStatus() object ParentUserLoginWaitingForSync: LoginDialogStatus()
data class ParentUserLoginBlockedByCategory(val categoryTitle: String, val reason: BlockingReason): LoginDialogStatus() data class ParentUserLoginBlockedByCategory(val categoryTitle: String, val reason: BlockingReason): LoginDialogStatus()
data class ParentUserLogin( data class ParentUserLogin(

View file

@ -34,7 +34,6 @@ import io.timelimit.android.async.Threads
import io.timelimit.android.data.model.User import io.timelimit.android.data.model.User
import io.timelimit.android.databinding.NewLoginFragmentBinding import io.timelimit.android.databinding.NewLoginFragmentBinding
import io.timelimit.android.extensions.setOnEnterListenr import io.timelimit.android.extensions.setOnEnterListenr
import io.timelimit.android.logic.BlockingReason
import io.timelimit.android.ui.main.getActivityViewModel import io.timelimit.android.ui.main.getActivityViewModel
import io.timelimit.android.ui.manage.parent.key.ScannedKey import io.timelimit.android.ui.manage.parent.key.ScannedKey
import io.timelimit.android.ui.view.KeyboardViewListener import io.timelimit.android.ui.view.KeyboardViewListener
@ -50,10 +49,9 @@ class NewLoginFragment: DialogFragment() {
private const val CHILD_ALREADY_CURRENT_USER = 3 private const val CHILD_ALREADY_CURRENT_USER = 3
private const val CHILD_AUTH = 4 private const val CHILD_AUTH = 4
private const val CHILD_LOGIN_REQUIRES_PREMIUM = 5 private const val CHILD_LOGIN_REQUIRES_PREMIUM = 5
private const val BLOCKED_LOGIN_TIME = 6 private const val UNVERIFIED_TIME = 6
private const val UNVERIFIED_TIME = 7 private const val PARENT_LOGIN_BLOCKED = 7
private const val PARENT_LOGIN_BLOCKED = 8 private const val WAITING_FOR_SYNC = 8
private const val WAITING_FOR_SYNC = 9
} }
private val model: LoginDialogFragmentModel by lazy { private val model: LoginDialogFragmentModel by lazy {
@ -252,15 +250,6 @@ class NewLoginFragment: DialogFragment() {
null null
} }
ParentUserLoginBlockedTime -> {
if (binding.switcher.displayedChild != BLOCKED_LOGIN_TIME) {
binding.switcher.setInAnimation(context!!, R.anim.wizard_open_step_in)
binding.switcher.setOutAnimation(context!!, R.anim.wizard_open_step_out)
binding.switcher.displayedChild = BLOCKED_LOGIN_TIME
}
null
}
is CanNotSignInChildHasNoPassword -> { is CanNotSignInChildHasNoPassword -> {
if (binding.switcher.displayedChild != CHILD_MISSING_PASSWORD) { if (binding.switcher.displayedChild != CHILD_MISSING_PASSWORD) {
binding.switcher.setInAnimation(context!!, R.anim.wizard_open_step_in) binding.switcher.setInAnimation(context!!, R.anim.wizard_open_step_in)
@ -319,20 +308,7 @@ class NewLoginFragment: DialogFragment() {
} }
binding.parentLoginBlocked.categoryTitle = status.categoryTitle binding.parentLoginBlocked.categoryTitle = status.categoryTitle
binding.parentLoginBlocked.reason = when (status.reason) { binding.parentLoginBlocked.reason = LoginDialogFragmentModel.formatBlockingReasonForLimitLoginCategory(status.reason, context!!)
BlockingReason.TemporarilyBlocked -> getString(R.string.lock_reason_short_temporarily_blocked)
BlockingReason.TimeOver -> getString(R.string.lock_reason_short_time_over)
BlockingReason.TimeOverExtraTimeCanBeUsedLater -> getString(R.string.lock_reason_short_time_over)
BlockingReason.BlockedAtThisTime -> getString(R.string.lock_reason_short_blocked_time_area)
BlockingReason.MissingNetworkTime -> getString(R.string.lock_reason_short_missing_network_time)
BlockingReason.RequiresCurrentDevice -> getString(R.string.lock_reason_short_requires_current_device)
BlockingReason.NotificationsAreBlocked -> getString(R.string.lock_reason_short_notification_blocking)
BlockingReason.BatteryLimit -> getString(R.string.lock_reason_short_battery_limit)
BlockingReason.SessionDurationLimit -> getString(R.string.lock_reason_short_session_duration)
BlockingReason.MissingRequiredNetwork -> getString(R.string.lock_reason_short_missing_required_network)
BlockingReason.NotPartOfAnCategory -> "???"
BlockingReason.None -> "???"
}
null null
} }

View file

@ -24,34 +24,18 @@ import androidx.fragment.app.FragmentManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.timelimit.android.R import io.timelimit.android.R
import io.timelimit.android.extensions.showSafe import io.timelimit.android.extensions.showSafe
import kotlinx.android.synthetic.main.fragment_blocked_time_areas_help_dialog.*
class BlockedTimeAreasHelpDialog : BottomSheetDialogFragment() { class BlockedTimeAreasHelpDialog : BottomSheetDialogFragment() {
companion object { companion object {
private const val DIALOG_TAG = "r" private const val DIALOG_TAG = "r"
private const val FOR_USER = "forUser"
fun newInstance(forUser: Boolean) = BlockedTimeAreasHelpDialog().apply { fun newInstance() = BlockedTimeAreasHelpDialog()
arguments = Bundle().apply {
putBoolean(FOR_USER, forUser)
}
}
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_blocked_time_areas_help_dialog, container, false) return inflater.inflate(R.layout.fragment_blocked_time_areas_help_dialog, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val forUser = arguments?.getBoolean(FOR_USER, false)
if (forUser == true) {
text1.setText(R.string.manage_parent_blocked_times_description)
}
}
fun show(manager: FragmentManager) { fun show(manager: FragmentManager) {
showSafe(manager, DIALOG_TAG) showSafe(manager, DIALOG_TAG)
} }

View file

@ -1,180 +0,0 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 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.parent.blockedtimes
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.LiveData
import com.google.android.material.snackbar.Snackbar
import io.timelimit.android.R
import io.timelimit.android.data.customtypes.ImmutableBitmask
import io.timelimit.android.data.model.User
import io.timelimit.android.data.model.withConfigCopiedToOtherDates
import io.timelimit.android.databinding.ManageParentBlockedTimesFragmentBinding
import io.timelimit.android.livedata.liveDataFromValue
import io.timelimit.android.livedata.map
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.sync.actions.UpdateParentBlockedTimesAction
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.ActivityViewModelHolder
import io.timelimit.android.ui.main.AuthenticationFab
import io.timelimit.android.ui.main.FragmentWithCustomTitle
import io.timelimit.android.ui.manage.category.blocked_times.*
import kotlinx.android.synthetic.main.fragment_blocked_time_areas.*
import java.util.*
class ManageParentBlockedTimesFragment : Fragment(), FragmentWithCustomTitle, CopyBlockedTimeAreasDialogFragmentListener {
companion object {
private const val MINUTES_PER_DAY = 60 * 24
private const val MAX_BLOCKED_MINUTES_PER_DAY = 60 * 18 + 1
}
private val params: ManageParentBlockedTimesFragmentArgs by lazy {
ManageParentBlockedTimesFragmentArgs.fromBundle(arguments!!)
}
private val authActivity: ActivityViewModelHolder by lazy {
activity!! as ActivityViewModelHolder
}
private val auth: ActivityViewModel by lazy {
authActivity.getActivityViewModel()
}
private val parent: LiveData<User?> by lazy {
DefaultAppLogic.with(context!!).database.user().getParentUserByIdLive(params.parentUserId)
}
override fun getCustomTitle() = parent.map { "${getString(R.string.manage_parent_blocked_times_title)} < ${it?.name} < ${getString(R.string.main_tab_overview)}" as String? }
override fun onCopyBlockedTimeAreasConfirmed(sourceDay: Int, targetDays: Set<Int>) {
parent.value?.blockedTimes?.let { current ->
updateBlockedTimes(current, current.withConfigCopiedToOtherDates(sourceDay, targetDays))
}
}
private fun validateBlockedTimeAreas(newMask: BitSet): Boolean {
for (day in 0 until 7) {
var blocked = 0
for (minute in 0 until MINUTES_PER_DAY) {
if (newMask[day * MINUTES_PER_DAY + minute]) {
blocked++
}
}
if (blocked >= MAX_BLOCKED_MINUTES_PER_DAY) {
return false
}
}
return true
}
private fun updateBlockedTimes(oldMask: ImmutableBitmask, newMask: ImmutableBitmask) {
if (!validateBlockedTimeAreas(newMask.dataNotToModify)) {
Snackbar.make(coordinator, R.string.manage_parent_lockout_hour_rule, Snackbar.LENGTH_LONG).show()
return
}
if (
auth.tryDispatchParentAction(
UpdateParentBlockedTimesAction(
parentId = params.parentUserId,
blockedTimes = newMask
)
)
) {
Snackbar.make(coordinator, R.string.blocked_time_areas_snackbar_modified, Snackbar.LENGTH_SHORT)
.setAction(R.string.generic_undo) {
auth.tryDispatchParentAction(
UpdateParentBlockedTimesAction(
parentId = params.parentUserId,
blockedTimes = oldMask
)
)
}
.show()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = ManageParentBlockedTimesFragmentBinding.inflate(inflater, container, false)
// auth button
AuthenticationFab.manageAuthenticationFab(
fab = binding.fab,
fragment = this,
shouldHighlight = auth.shouldHighlightAuthenticationButton,
authenticatedUser = auth.authenticatedUser,
doesSupportAuth = liveDataFromValue(true)
)
binding.fab.setOnClickListener { authActivity.showAuthenticationScreen() }
// dispatching
fun requestAuthenticationOrReturnTrue(): Boolean {
if (!auth.requestAuthenticationOrReturnTrue()) {
return false
}
val authenticatedUser = auth.authenticatedUser.value?.second?.id ?: return false
val targetUser = params.parentUserId
if (authenticatedUser == targetUser) {
return true
} else {
TryResetParentBlockedTimesDialogFragment.newInstance(parentUserId = params.parentUserId).show(fragmentManager!!)
return false
}
}
// UI
binding.btnHelp.setOnClickListener {
BlockedTimeAreasHelpDialog.newInstance(forUser = true).show(fragmentManager!!)
}
binding.btnCopyToOtherDays.setOnClickListener {
if (requestAuthenticationOrReturnTrue()) {
CopyBlockedTimeAreasDialogFragment.newInstance(this@ManageParentBlockedTimesFragment).show(fragmentManager!!)
}
}
BlockedTimeAreasLogic.init(
recycler = binding.recycler,
daySpinner = binding.spinnerDay,
detailedModeCheckbox = binding.detailedMode,
checkAuthentication = {
if (requestAuthenticationOrReturnTrue()) {
BlockedTimeAreasLogic.Authentication.FullyAvailable
} else {
// empty hook ... not clean, but it works good enough (and this component is old)
BlockedTimeAreasLogic.Authentication.Missing({})
}
},
updateBlockedTimes = { a, b -> updateBlockedTimes(a, b) },
currentData = parent.map { it?.blockedTimes },
lifecycleOwner = this
)
return binding.root
}
}

View file

@ -1,66 +0,0 @@
/*
* TimeLimit Copyright <C> 2019 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.parent.blockedtimes
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import io.timelimit.android.R
import io.timelimit.android.data.model.UserType
import io.timelimit.android.extensions.showSafe
import io.timelimit.android.sync.actions.ResetParentBlockedTimesAction
import io.timelimit.android.ui.main.ActivityViewModelHolder
class TryResetParentBlockedTimesDialogFragment: DialogFragment() {
companion object {
private const val DIALOG_TAG = "TryResetParentBlockedTimesDialogFragment"
private const val PARENT_USER_ID = "parentUserId"
fun newInstance(parentUserId: String) = TryResetParentBlockedTimesDialogFragment().apply {
arguments = Bundle().apply {
putString(PARENT_USER_ID, parentUserId)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val parentUserId = arguments!!.getString(PARENT_USER_ID)!!
val auth = (activity!! as ActivityViewModelHolder).getActivityViewModel()
auth.authenticatedUser.observe(this, Observer {
if (it?.second?.type != UserType.Parent) {
dismissAllowingStateLoss()
}
})
return AlertDialog.Builder(context!!, theme)
.setMessage(R.string.manage_parent_blocked_times_info)
.setPositiveButton(R.string.manage_parent_blocked_action_reset) { _, _ ->
auth.tryDispatchParentAction(
ResetParentBlockedTimesAction(
parentId = parentUserId
)
)
}
.setNegativeButton(R.string.generic_cancel, null)
.create()
}
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
}

View file

@ -166,39 +166,6 @@
<include android:id="@+id/parent_limit_login" <include android:id="@+id/parent_limit_login"
layout="@layout/parent_limit_login_view" /> layout="@layout/parent_limit_login_view" />
<androidx.cardview.widget.CardView
app:cardUseCompatPadding="true"
android:onClick="@{() -> handlers.onManageBlockedTimesClicked()}"
android:foreground="?selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="@string/manage_parent_blocked_times_title"
android:textAppearance="?android:textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:text="@string/manage_parent_blocked_times_description"
android:textAppearance="?android:textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:textAppearance="?android:textAppearanceSmall"
android:text="@string/purchase_required_info_local_mode_free"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<include android:id="@+id/delete_parent" <include android:id="@+id/delete_parent"
layout="@layout/delete_parent_view" /> layout="@layout/delete_parent_view" />

View file

@ -38,8 +38,6 @@
<include android:id="@+id/child_without_premium" <include android:id="@+id/child_without_premium"
layout="@layout/new_login_fragment_child_without_premium" /> layout="@layout/new_login_fragment_child_without_premium" />
<include layout="@layout/new_login_fragment_blocked_time" />
<include layout="@layout/new_login_fragment_missing_trusted_time" /> <include layout="@layout/new_login_fragment_missing_trusted_time" />
<include layout="@layout/new_login_fragment_parent_login_blocked" <include layout="@layout/new_login_fragment_parent_login_blocked"

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 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/>.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:gravity="center_horizontal"
android:textAppearance="?android:textAppearanceMedium"
android:padding="8dp"
android:text="@string/login_blocked_time"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</layout>

View file

@ -28,7 +28,6 @@
<string name="login_child_info">Dadurch wirst du als aktueller Nutzer dieses Gerätes festgelegt</string> <string name="login_child_info">Dadurch wirst du als aktueller Nutzer dieses Gerätes festgelegt</string>
<string name="login_child_done_toast">Der Benutzer dieses Gerätes wurde geändert</string> <string name="login_child_done_toast">Der Benutzer dieses Gerätes wurde geändert</string>
<string name="login_missing_trusted_time">Zum Anmelden mit diesem Benutzer wird die Uhrzeit benötigt</string> <string name="login_missing_trusted_time">Zum Anmelden mit diesem Benutzer wird die Uhrzeit benötigt</string>
<string name="login_blocked_time">Das Anmelden mit diesem Benutzer zu dieser Zeit wurde gesperrt</string>
<string name="login_missing_sync">Warte auf erfolgreiche Synchronisation</string> <string name="login_missing_sync">Warte auf erfolgreiche Synchronisation</string>
<string name="login_scan_code">Code scannen</string> <string name="login_scan_code">Code scannen</string>
<string name="login_scan_code_err_expired">Dieser Code ist alt</string> <string name="login_scan_code_err_expired">Dieser Code ist alt</string>

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 - 2020 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/>.
-->
<resources>
<string name="manage_parent_blocked_times_title">Zeitschloss (veraltet)</string>
<string name="manage_parent_blocked_times_description">
Sie können anstelle dieser Funktion eine Anmeldeverhinderungskategorie verwenden.
Hiermit können Zeiten festgelegt werden, an denen sich ein Elternteil nicht anmelden kann.
Das ist bei der Selbstbeschränkung relevant.
</string>
<string name="manage_parent_blocked_times_info">
Ein Elternteil kann nur für sich selbst Anmeldebeschränkungen festlegen.
Andere Elternteile können diese nur zurücksetzen.
</string>
<string name="manage_parent_blocked_action_reset">Einstellungen zurücksetzen</string>
<string name="manage_parent_lockout_hour_rule">Um ein Aussperren zu verhindern können maximal 18 Stunden je Tag blockiert werden</string>
<string name="manage_parent_blocked_times_toast">
Sie verwenden das veraltete Zeitschloss für Ihren Elternbenutzer.
Sie sollten stattdessen die Anmeldeverhinderungskategorie verwenden.
</string>
</resources>

View file

@ -28,7 +28,6 @@
<string name="login_child_info">This will set you as the current user of this device</string> <string name="login_child_info">This will set you as the current user of this device</string>
<string name="login_child_done_toast">The user of this device was changed</string> <string name="login_child_done_toast">The user of this device was changed</string>
<string name="login_missing_trusted_time">The current time is required to sign in with this user</string> <string name="login_missing_trusted_time">The current time is required to sign in with this user</string>
<string name="login_blocked_time">Signing in as this user at this time was blocked</string>
<string name="login_missing_sync">Waiting for a successful sync</string> <string name="login_missing_sync">Waiting for a successful sync</string>
<string name="login_scan_code">Scan code</string> <string name="login_scan_code">Scan code</string>
<string name="login_scan_code_err_expired">This code is old</string> <string name="login_scan_code_err_expired">This code is old</string>

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TimeLimit Copyright <C> 2019 - 2020 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/>.
-->
<resources>
<string name="manage_parent_blocked_times_title">Time lock (deprecated)</string>
<string name="manage_parent_blocked_times_description">
You can use a limit login category instead.
This allows to set times at which the parent can not sign in.
This is relevant for limiting ones own usage.
</string>
<string name="manage_parent_blocked_times_info">
A parent can only add limits for itself.
Other parents can only reset the limits.
</string>
<string name="manage_parent_blocked_action_reset">Reset limits</string>
<string name="manage_parent_lockout_hour_rule">To prevent losing access, you can only block 18 hours per day</string>
<string name="manage_parent_blocked_times_toast">
You are using the deprecated time lock for your parent user.
You should use a limit login category instead.
</string>
</resources>