mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 17:59:51 +02:00
Remove parent blocked time areas feature
This was replaced by the limit login category
This commit is contained in:
parent
206260d068
commit
e672dd9eb1
20 changed files with 52 additions and 575 deletions
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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" />
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
Loading…
Add table
Add a link
Reference in a new issue