mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Add option to restrict viewing child users
This commit is contained in:
parent
7139c87fec
commit
a0a1c20e10
15 changed files with 1274 additions and 13 deletions
1012
app/schemas/io.timelimit.android.data.RoomDatabase/30.json
Normal file
1012
app/schemas/io.timelimit.android.data.RoomDatabase/30.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -218,4 +218,10 @@ object DatabaseMigrations {
|
|||
database.execSQL("CREATE INDEX IF NOT EXISTS `session_duration_index_category_id` ON `session_duration` (`category_id`)")
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATE_TO_V30 = object: Migration(29, 30) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE `user` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import io.timelimit.android.data.model.*
|
|||
AllowedContact::class,
|
||||
UserKey::class,
|
||||
SessionDuration::class
|
||||
], version = 29)
|
||||
], version = 30)
|
||||
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
|
||||
companion object {
|
||||
private val lock = Object()
|
||||
|
@ -100,7 +100,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database
|
|||
DatabaseMigrations.MIGRATE_TO_V26,
|
||||
DatabaseMigrations.MIGRATE_TO_V27,
|
||||
DatabaseMigrations.MIGRATE_TO_V28,
|
||||
DatabaseMigrations.MIGRATE_TO_V29
|
||||
DatabaseMigrations.MIGRATE_TO_V29,
|
||||
DatabaseMigrations.MIGRATE_TO_V30
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* 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
|
||||
|
@ -62,7 +62,9 @@ data class User(
|
|||
@ColumnInfo(name = "mail_notification_flags")
|
||||
val mailNotificationFlags: Int,
|
||||
@ColumnInfo(name = "blocked_times")
|
||||
val blockedTimes: ImmutableBitmask
|
||||
val blockedTimes: ImmutableBitmask,
|
||||
@ColumnInfo(name = "flags")
|
||||
val flags: Long
|
||||
): JsonSerializable {
|
||||
companion object {
|
||||
private const val ID = "id"
|
||||
|
@ -78,6 +80,7 @@ data class User(
|
|||
private const val RELAX_PRIMARY_DEVICE = "relaxPrimaryDevice"
|
||||
private const val MAIL_NOTIFICATION_FLAGS = "mailNotificationFlags"
|
||||
private const val BLOCKED_TIMES = "blockedTimes"
|
||||
private const val FLAGS = "flags"
|
||||
|
||||
fun parse(reader: JsonReader): User {
|
||||
var id: String? = null
|
||||
|
@ -93,6 +96,7 @@ data class User(
|
|||
var relaxPrimaryDevice = false
|
||||
var mailNotificationFlags = 0
|
||||
var blockedTimes = ImmutableBitmask(BitSet())
|
||||
var flags = 0L
|
||||
|
||||
reader.beginObject()
|
||||
while (reader.hasNext()) {
|
||||
|
@ -110,6 +114,7 @@ data class User(
|
|||
RELAX_PRIMARY_DEVICE -> relaxPrimaryDevice = reader.nextBoolean()
|
||||
MAIL_NOTIFICATION_FLAGS -> mailNotificationFlags = reader.nextInt()
|
||||
BLOCKED_TIMES -> blockedTimes = ImmutableBitmaskJson.parse(reader.nextString(), Category.BLOCKED_MINUTES_IN_WEEK_LENGTH)
|
||||
FLAGS -> flags = reader.nextLong()
|
||||
else -> reader.skipValue()
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +133,8 @@ data class User(
|
|||
categoryForNotAssignedApps = categoryForNotAssignedApps,
|
||||
relaxPrimaryDevice = relaxPrimaryDevice,
|
||||
mailNotificationFlags = mailNotificationFlags,
|
||||
blockedTimes = blockedTimes
|
||||
blockedTimes = blockedTimes,
|
||||
flags = flags
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -159,6 +165,9 @@ data class User(
|
|||
}
|
||||
}
|
||||
|
||||
val restrictViewingToParents: Boolean
|
||||
get() = flags and UserFlags.RESTRICT_VIEWING_TO_PARENTS == UserFlags.RESTRICT_VIEWING_TO_PARENTS
|
||||
|
||||
override fun serialize(writer: JsonWriter) {
|
||||
writer.beginObject()
|
||||
|
||||
|
@ -175,6 +184,7 @@ data class User(
|
|||
writer.name(RELAX_PRIMARY_DEVICE).value(relaxPrimaryDevice)
|
||||
writer.name(MAIL_NOTIFICATION_FLAGS).value(mailNotificationFlags)
|
||||
writer.name(BLOCKED_TIMES).value(ImmutableBitmaskJson.serialize(blockedTimes))
|
||||
writer.name(FLAGS).value(flags)
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
|
@ -207,3 +217,8 @@ class UserTypeConverter {
|
|||
@TypeConverter
|
||||
fun toString(value: UserType) = UserTypeJson.serialize(value)
|
||||
}
|
||||
|
||||
object UserFlags {
|
||||
const val RESTRICT_VIEWING_TO_PARENTS = 1L
|
||||
const val ALL_FLAGS = RESTRICT_VIEWING_TO_PARENTS
|
||||
}
|
|
@ -128,7 +128,8 @@ class AppSetupLogic(private val appLogic: AppLogic) {
|
|||
categoryForNotAssignedApps = "",
|
||||
relaxPrimaryDevice = false,
|
||||
mailNotificationFlags = 0,
|
||||
blockedTimes = ImmutableBitmask(BitSet())
|
||||
blockedTimes = ImmutableBitmask(BitSet()),
|
||||
flags = 0
|
||||
)
|
||||
|
||||
appLogic.database.user().addUserSync(child)
|
||||
|
@ -150,7 +151,8 @@ class AppSetupLogic(private val appLogic: AppLogic) {
|
|||
categoryForNotAssignedApps = "",
|
||||
relaxPrimaryDevice = false,
|
||||
mailNotificationFlags = 0,
|
||||
blockedTimes = ImmutableBitmask(BitSet())
|
||||
blockedTimes = ImmutableBitmask(BitSet()),
|
||||
flags = 0
|
||||
)
|
||||
|
||||
appLogic.database.user().addUserSync(parent)
|
||||
|
|
|
@ -1826,6 +1826,34 @@ data class ResetParentBlockedTimesAction(val parentId: String): ParentAction() {
|
|||
}
|
||||
}
|
||||
|
||||
data class UpdateUserFlagsAction(val userId: String, val modifiedBits: Long, val newValues: Long): ParentAction() {
|
||||
companion object {
|
||||
private const val TYPE_VALUE = "UPDATE_USER_FLAGS"
|
||||
private const val USER_ID = "userId"
|
||||
private const val MODIFIED_BITS = "modified"
|
||||
private const val NEW_VALUES = "values"
|
||||
}
|
||||
|
||||
init {
|
||||
IdGenerator.assertIdValid(userId)
|
||||
|
||||
if (modifiedBits or UserFlags.ALL_FLAGS != UserFlags.ALL_FLAGS || modifiedBits or newValues != modifiedBits) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(writer: JsonWriter) {
|
||||
writer.beginObject()
|
||||
|
||||
writer.name(TYPE).value(TYPE_VALUE)
|
||||
writer.name(USER_ID).value(userId)
|
||||
writer.name(MODIFIED_BITS).value(modifiedBits)
|
||||
writer.name(NEW_VALUES).value(newValues)
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
// child actions
|
||||
object ChildSignInAction: ChildAction() {
|
||||
private const val TYPE_VALUE = "CHILD_SIGN_IN"
|
||||
|
|
|
@ -68,6 +68,7 @@ object ActionParser {
|
|||
// UpdateCategoryTimeWarningsAction
|
||||
// UpdateCategoryBatteryLimit
|
||||
// UpdateCategorySorting
|
||||
// UpdateUserFlagsAction
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,7 +176,8 @@ object LocalDatabaseParentActionDispatcher {
|
|||
categoryForNotAssignedApps = "",
|
||||
relaxPrimaryDevice = false,
|
||||
mailNotificationFlags = 0,
|
||||
blockedTimes = ImmutableBitmask(BitSet())
|
||||
blockedTimes = ImmutableBitmask(BitSet()),
|
||||
flags = 0
|
||||
))
|
||||
}
|
||||
is UpdateCategoryBlockedTimesAction -> {
|
||||
|
@ -586,6 +587,15 @@ object LocalDatabaseParentActionDispatcher {
|
|||
database.category().updateCategorySorting(categoryId, index)
|
||||
}
|
||||
}
|
||||
is UpdateUserFlagsAction -> {
|
||||
val user = database.user().getUserByIdSync(action.userId)!!
|
||||
|
||||
val updatedUser = user.copy(
|
||||
flags = (user.flags and action.modifiedBits.inv()) or action.newValues
|
||||
)
|
||||
|
||||
database.user().updateUserSync(updatedUser)
|
||||
}
|
||||
}.let { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,11 +34,13 @@ import io.timelimit.android.ui.help.HelpDialogFragment
|
|||
import io.timelimit.android.ui.main.ActivityViewModel
|
||||
import io.timelimit.android.ui.main.getActivityViewModel
|
||||
import io.timelimit.android.ui.manage.child.ManageChildFragmentArgs
|
||||
import io.timelimit.android.ui.manage.child.advanced.limituserviewing.LimitUserViewingView
|
||||
import io.timelimit.android.ui.manage.child.advanced.manageblocktemporarily.ManageBlockTemporarilyView
|
||||
import io.timelimit.android.ui.manage.child.advanced.managedisabletimelimits.ManageDisableTimelimitsViewHelper
|
||||
import io.timelimit.android.ui.manage.child.advanced.password.ManageChildPassword
|
||||
import io.timelimit.android.ui.manage.child.advanced.timezone.UserTimezoneView
|
||||
import io.timelimit.android.ui.manage.child.primarydevice.PrimaryDeviceView
|
||||
import kotlin.concurrent.fixedRateTimer
|
||||
|
||||
class ManageChildAdvancedFragment : Fragment() {
|
||||
companion object {
|
||||
|
@ -149,6 +151,15 @@ class ManageChildAdvancedFragment : Fragment() {
|
|||
fragmentManager = fragmentManager!!
|
||||
)
|
||||
|
||||
LimitUserViewingView.bind(
|
||||
view = binding.limitViewing,
|
||||
auth = auth,
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
fragmentManager = parentFragmentManager,
|
||||
userEntry = childEntry,
|
||||
userId = params.childId
|
||||
)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.child.advanced.limituserviewing
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import io.timelimit.android.R
|
||||
import io.timelimit.android.data.model.User
|
||||
import io.timelimit.android.data.model.UserFlags
|
||||
import io.timelimit.android.databinding.LimitUserViewingViewBinding
|
||||
import io.timelimit.android.livedata.ignoreUnchanged
|
||||
import io.timelimit.android.livedata.map
|
||||
import io.timelimit.android.sync.actions.UpdateUserFlagsAction
|
||||
import io.timelimit.android.ui.help.HelpDialogFragment
|
||||
import io.timelimit.android.ui.main.ActivityViewModel
|
||||
|
||||
object LimitUserViewingView {
|
||||
fun bind(
|
||||
view: LimitUserViewingViewBinding,
|
||||
auth: ActivityViewModel,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
fragmentManager: FragmentManager,
|
||||
userEntry: LiveData<User?>,
|
||||
userId: String
|
||||
) {
|
||||
view.titleView.setOnClickListener {
|
||||
HelpDialogFragment.newInstance(
|
||||
title = R.string.limit_user_viewing_title,
|
||||
text = R.string.limit_user_viewing_help
|
||||
).show(fragmentManager)
|
||||
}
|
||||
|
||||
userEntry.map { it?.restrictViewingToParents ?: false }.ignoreUnchanged().observe(lifecycleOwner, Observer { checked ->
|
||||
view.enableSwitch.setOnCheckedChangeListener { buttonView, isChecked -> /* ignore */ }
|
||||
view.enableSwitch.isChecked = checked
|
||||
view.enableSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
if (isChecked != checked) {
|
||||
if (
|
||||
!auth.tryDispatchParentAction(
|
||||
UpdateUserFlagsAction(
|
||||
userId = userId,
|
||||
modifiedBits = UserFlags.RESTRICT_VIEWING_TO_PARENTS,
|
||||
newValues = if (isChecked) UserFlags.RESTRICT_VIEWING_TO_PARENTS else 0
|
||||
)
|
||||
)
|
||||
) {
|
||||
view.enableSwitch.isChecked = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* 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
|
||||
|
@ -69,11 +69,19 @@ class OverviewFragment : CoroutineFragment(), CanNotAddDevicesInLocalModeDialogF
|
|||
}
|
||||
|
||||
override fun onUserClicked(user: User) {
|
||||
if (
|
||||
user.restrictViewingToParents &&
|
||||
logic.deviceUserId.value != user.id &&
|
||||
!auth.requestAuthenticationOrReturnTrue()
|
||||
) {
|
||||
// do "nothing"/ request authentication
|
||||
} else {
|
||||
when (user.type) {
|
||||
UserType.Child -> handlers.openManageChildScreen(childId = user.id)
|
||||
UserType.Parent -> handlers.openManageParentScreen(parentId = user.id)
|
||||
}.let { }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAddDeviceClicked() {
|
||||
launch {
|
||||
|
|
|
@ -98,6 +98,9 @@
|
|||
<include android:id="@+id/password"
|
||||
layout="@layout/manage_child_password" />
|
||||
|
||||
<include android:id="@+id/limit_viewing"
|
||||
layout="@layout/limit_user_viewing_view" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
app:cardUseCompatPadding="true"
|
||||
android:layout_width="match_parent"
|
||||
|
|
48
app/src/main/res/layout/limit_user_viewing_view.xml
Normal file
48
app/src/main/res/layout/limit_user_viewing_view.xml
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?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/>.
|
||||
-->
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardUseCompatPadding="true">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:drawableTint="?colorOnSurface"
|
||||
android:id="@+id/title_view"
|
||||
android:background="?selectableItemBackground"
|
||||
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:text="@string/limit_user_viewing_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/enable_switch"
|
||||
android:text="@string/limit_user_viewing_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</layout>
|
24
app/src/main/res/values-de/strings-limit-user-viewing.xml
Normal file
24
app/src/main/res/values-de/strings-limit-user-viewing.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?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="limit_user_viewing_title">Nutzersichtbarkeit einschränken</string>
|
||||
<string name="limit_user_viewing_checkbox">Nur dem Benutzer selbst und den Eltern erlauben, die Benutzerdetails anzuzeigen</string>
|
||||
<string name="limit_user_viewing_help">Damit kann verhindert werden, dass andere Kinder der Familie
|
||||
die Einstellungen und Nutzungsdauern dieses Kindes sehen können. Das ist nicht als
|
||||
Sicherheitsfunktion gedacht und leicht umgehbar, z.B. mit der Nutzung einer älteren TimeLimit-
|
||||
Version.
|
||||
</string>
|
||||
</resources>
|
23
app/src/main/res/values/strings-limit-user-viewing.xml
Normal file
23
app/src/main/res/values/strings-limit-user-viewing.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?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="limit_user_viewing_title">Restrict viewing the user</string>
|
||||
<string name="limit_user_viewing_checkbox">Only allow parents and the user itself to view its details</string>
|
||||
<string name="limit_user_viewing_help">This allows preventing other child users to
|
||||
view the configuration and used times of this child user. This is not intended as security feature
|
||||
and it is not one - using an older version is enough to circumvent this.
|
||||
</string>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue