Add new time picker mode

This commit is contained in:
Jonas L 2019-07-01 00:00:00 +00:00
parent dc711f4c60
commit 5e694f0c1c
13 changed files with 1074 additions and 45 deletions

View file

@ -0,0 +1,759 @@
{
"formatVersion": 1,
"database": {
"version": 19,
"identityHash": "a7aedc16546b129da089b18988dce47f",
"entities": [
{
"tableName": "user",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `password` TEXT NOT NULL, `second_password_salt` TEXT NOT NULL, `type` TEXT NOT NULL, `timezone` TEXT NOT NULL, `disable_limits_until` INTEGER NOT NULL, `mail` TEXT NOT NULL, `current_device` TEXT NOT NULL, `category_for_not_assigned_apps` TEXT NOT NULL, `relax_primary_device` INTEGER NOT NULL, `mail_notification_flags` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "secondPasswordSalt",
"columnName": "second_password_salt",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timeZone",
"columnName": "timezone",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "disableLimitsUntil",
"columnName": "disable_limits_until",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mail",
"columnName": "mail",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentDevice",
"columnName": "current_device",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "categoryForNotAssignedApps",
"columnName": "category_for_not_assigned_apps",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "relaxPrimaryDevice",
"columnName": "relax_primary_device",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mailNotificationFlags",
"columnName": "mail_notification_flags",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "device",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `model` TEXT NOT NULL, `added_at` INTEGER NOT NULL, `current_user_id` TEXT NOT NULL, `apps_version` TEXT NOT NULL, `network_time` TEXT NOT NULL, `current_protection_level` TEXT NOT NULL, `highest_permission_level` TEXT NOT NULL, `current_usage_stats_permission` TEXT NOT NULL, `highest_usage_stats_permission` TEXT NOT NULL, `current_notification_access_permission` TEXT NOT NULL, `highest_notification_access_permission` TEXT NOT NULL, `current_app_version` INTEGER NOT NULL, `highest_app_version` INTEGER NOT NULL, `tried_disabling_device_admin` INTEGER NOT NULL, `did_reboot` INTEGER NOT NULL, `had_manipulation` INTEGER NOT NULL, `did_report_uninstall` INTEGER NOT NULL, `is_user_kept_signed_in` INTEGER NOT NULL, `show_device_connected` INTEGER NOT NULL, `default_user` TEXT NOT NULL, `default_user_timeout` INTEGER NOT NULL, `consider_reboot_manipulation` INTEGER NOT NULL, `current_overlay_permission` TEXT NOT NULL, `highest_overlay_permission` TEXT NOT NULL, `current_accessibility_service_permission` INTEGER NOT NULL, `was_accessibility_service_permission` INTEGER NOT NULL, `enable_activity_level_blocking` INTEGER NOT NULL, `q_or_later` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "model",
"columnName": "model",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "addedAt",
"columnName": "added_at",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "currentUserId",
"columnName": "current_user_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "installedAppsVersion",
"columnName": "apps_version",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "networkTime",
"columnName": "network_time",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentProtectionLevel",
"columnName": "current_protection_level",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "highestProtectionLevel",
"columnName": "highest_permission_level",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentUsageStatsPermission",
"columnName": "current_usage_stats_permission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "highestUsageStatsPermission",
"columnName": "highest_usage_stats_permission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentNotificationAccessPermission",
"columnName": "current_notification_access_permission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "highestNotificationAccessPermission",
"columnName": "highest_notification_access_permission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentAppVersion",
"columnName": "current_app_version",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "highestAppVersion",
"columnName": "highest_app_version",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "manipulationTriedDisablingDeviceAdmin",
"columnName": "tried_disabling_device_admin",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "manipulationDidReboot",
"columnName": "did_reboot",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hadManipulation",
"columnName": "had_manipulation",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "didReportUninstall",
"columnName": "did_report_uninstall",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isUserKeptSignedIn",
"columnName": "is_user_kept_signed_in",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showDeviceConnected",
"columnName": "show_device_connected",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "defaultUser",
"columnName": "default_user",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "defaultUserTimeout",
"columnName": "default_user_timeout",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "considerRebootManipulation",
"columnName": "consider_reboot_manipulation",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "currentOverlayPermission",
"columnName": "current_overlay_permission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "highestOverlayPermission",
"columnName": "highest_overlay_permission",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "accessibilityServiceEnabled",
"columnName": "current_accessibility_service_permission",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "wasAccessibilityServiceEnabled",
"columnName": "was_accessibility_service_permission",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enableActivityLevelBlocking",
"columnName": "enable_activity_level_blocking",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "qOrLater",
"columnName": "q_or_later",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "app",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, `title` TEXT NOT NULL, `launchable` INTEGER NOT NULL, `recommendation` TEXT NOT NULL, PRIMARY KEY(`device_id`, `package_name`))",
"fields": [
{
"fieldPath": "deviceId",
"columnName": "device_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isLaunchable",
"columnName": "launchable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "recommendation",
"columnName": "recommendation",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"device_id",
"package_name"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_app_device_id",
"unique": false,
"columnNames": [
"device_id"
],
"createSql": "CREATE INDEX `index_app_device_id` ON `${TABLE_NAME}` (`device_id`)"
},
{
"name": "index_app_package_name",
"unique": false,
"columnNames": [
"package_name"
],
"createSql": "CREATE INDEX `index_app_package_name` ON `${TABLE_NAME}` (`package_name`)"
}
],
"foreignKeys": []
},
{
"tableName": "category_app",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`category_id`, `package_name`))",
"fields": [
{
"fieldPath": "categoryId",
"columnName": "category_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"category_id",
"package_name"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_category_app_category_id",
"unique": false,
"columnNames": [
"category_id"
],
"createSql": "CREATE INDEX `index_category_app_category_id` ON `${TABLE_NAME}` (`category_id`)"
},
{
"name": "index_category_app_package_name",
"unique": false,
"columnNames": [
"package_name"
],
"createSql": "CREATE INDEX `index_category_app_package_name` ON `${TABLE_NAME}` (`package_name`)"
}
],
"foreignKeys": []
},
{
"tableName": "category",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `child_id` TEXT NOT NULL, `title` TEXT NOT NULL, `blocked_times` TEXT NOT NULL, `extra_time` INTEGER NOT NULL, `temporarily_blocked` INTEGER NOT NULL, `base_version` TEXT NOT NULL, `apps_version` TEXT NOT NULL, `rules_version` TEXT NOT NULL, `usedtimes_version` TEXT NOT NULL, `parent_category_id` TEXT NOT NULL, `block_all_notifications` INTEGER NOT NULL, `time_warnings` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "childId",
"columnName": "child_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "blockedMinutesInWeek",
"columnName": "blocked_times",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "extraTimeInMillis",
"columnName": "extra_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "temporarilyBlocked",
"columnName": "temporarily_blocked",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "baseVersion",
"columnName": "base_version",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "assignedAppsVersion",
"columnName": "apps_version",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timeLimitRulesVersion",
"columnName": "rules_version",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "usedTimesVersion",
"columnName": "usedtimes_version",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "parentCategoryId",
"columnName": "parent_category_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "blockAllNotifications",
"columnName": "block_all_notifications",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timeWarnings",
"columnName": "time_warnings",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "used_time",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`day_of_epoch` INTEGER NOT NULL, `used_time` INTEGER NOT NULL, `category_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `day_of_epoch`))",
"fields": [
{
"fieldPath": "dayOfEpoch",
"columnName": "day_of_epoch",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "usedMillis",
"columnName": "used_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryId",
"columnName": "category_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"category_id",
"day_of_epoch"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "time_limit_rule",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `apply_to_extra_time_usage` INTEGER NOT NULL, `day_mask` INTEGER NOT NULL, `max_time` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "categoryId",
"columnName": "category_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "applyToExtraTimeUsage",
"columnName": "apply_to_extra_time_usage",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dayMask",
"columnName": "day_mask",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "maximumTimeInMillis",
"columnName": "max_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "config",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "key",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "temporarily_allowed_app",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`device_id`, `package_name`))",
"fields": [
{
"fieldPath": "deviceId",
"columnName": "device_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"device_id",
"package_name"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "pending_sync_action",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sequence_number` INTEGER NOT NULL, `action` TEXT NOT NULL, `integrity` TEXT NOT NULL, `scheduled_for_upload` INTEGER NOT NULL, `type` TEXT NOT NULL, `user_id` TEXT NOT NULL, PRIMARY KEY(`sequence_number`))",
"fields": [
{
"fieldPath": "sequenceNumber",
"columnName": "sequence_number",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "encodedAction",
"columnName": "action",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "integrity",
"columnName": "integrity",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "scheduledForUpload",
"columnName": "scheduled_for_upload",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userId",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"sequence_number"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_pending_sync_action_scheduled_for_upload",
"unique": false,
"columnNames": [
"scheduled_for_upload"
],
"createSql": "CREATE INDEX `index_pending_sync_action_scheduled_for_upload` ON `${TABLE_NAME}` (`scheduled_for_upload`)"
}
],
"foreignKeys": []
},
{
"tableName": "app_activity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `app_package_name` TEXT NOT NULL, `activity_class_name` TEXT NOT NULL, `activity_title` TEXT NOT NULL, PRIMARY KEY(`device_id`, `app_package_name`, `activity_class_name`))",
"fields": [
{
"fieldPath": "deviceId",
"columnName": "device_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "appPackageName",
"columnName": "app_package_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "activityClassName",
"columnName": "activity_class_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "activity_title",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"device_id",
"app_package_name",
"activity_class_name"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL, `id` TEXT NOT NULL, `first_notify_time` INTEGER NOT NULL, `dismissed` INTEGER NOT NULL, PRIMARY KEY(`type`, `id`))",
"fields": [
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "firstNotifyTime",
"columnName": "first_notify_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isDismissed",
"columnName": "dismissed",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"type",
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"a7aedc16546b129da089b18988dce47f\")"
]
}
}

View file

@ -133,4 +133,12 @@ object DatabaseMigrations {
database.execSQL("ALTER TABLE `category` ADD COLUMN `time_warnings` INTEGER NOT NULL DEFAULT 0")
}
}
val MIGRATE_TO_V19 = object: Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
// this is empty
//
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
}
}
}

View file

@ -34,7 +34,7 @@ import io.timelimit.android.data.model.*
PendingSyncAction::class,
AppActivity::class,
Notification::class
], version = 18)
], version = 19)
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
companion object {
private val lock = Object()
@ -86,7 +86,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database
DatabaseMigrations.MIGRATE_TO_V15,
DatabaseMigrations.MIGRATE_TO_V16,
DatabaseMigrations.MIGRATE_TO_V17,
DatabaseMigrations.MIGRATE_TO_V18
DatabaseMigrations.MIGRATE_TO_V18,
DatabaseMigrations.MIGRATE_TO_V19
)
.build()
}

View file

@ -221,4 +221,7 @@ abstract class ConfigDao {
fun getEnableBackgroundSyncAsync(): LiveData<Boolean> = getValueOfKeyAsync(ConfigurationItemType.EnableBackgroundSync).map { (it ?: "0") != "0" }
fun setEnableBackgroundSync(enable: Boolean) = updateValueSync(ConfigurationItemType.EnableBackgroundSync, if (enable) "1" else "0")
fun getEnableAlternativeDurationSelectionAsync() = getValueOfKeyAsync(ConfigurationItemType.EnableAlternativeDurationSelection).map { it == "1" }
fun setEnableAlternativeDurationSelectionSync(enable: Boolean) = updateValueSync(ConfigurationItemType.EnableAlternativeDurationSelection, if (enable) "1" else "0")
}

View file

@ -90,7 +90,8 @@ enum class ConfigurationItemType {
ServerMessage,
CustomServerUrl,
ForegroundAppQueryRange,
EnableBackgroundSync
EnableBackgroundSync,
EnableAlternativeDurationSelection
}
object ConfigurationItemTypeUtil {
@ -108,6 +109,7 @@ object ConfigurationItemTypeUtil {
private const val CUSTOM_SERVER_URL = 13
private const val FOREGROUND_APP_QUERY_RANGE = 14
private const val ENABLE_BACKGROUND_SYNC = 15
private const val ENABLE_ALTERNATIVE_DURATION_SELECTION = 16
val TYPES = listOf(
ConfigurationItemType.OwnDeviceId,
@ -123,7 +125,8 @@ object ConfigurationItemTypeUtil {
ConfigurationItemType.ServerMessage,
ConfigurationItemType.CustomServerUrl,
ConfigurationItemType.ForegroundAppQueryRange,
ConfigurationItemType.EnableBackgroundSync
ConfigurationItemType.EnableBackgroundSync,
ConfigurationItemType.EnableAlternativeDurationSelection
)
fun serialize(value: ConfigurationItemType) = when(value) {
@ -141,6 +144,7 @@ object ConfigurationItemTypeUtil {
ConfigurationItemType.CustomServerUrl -> CUSTOM_SERVER_URL
ConfigurationItemType.ForegroundAppQueryRange -> FOREGROUND_APP_QUERY_RANGE
ConfigurationItemType.EnableBackgroundSync -> ENABLE_BACKGROUND_SYNC
ConfigurationItemType.EnableAlternativeDurationSelection -> ENABLE_ALTERNATIVE_DURATION_SELECTION
}
fun parse(value: Int) = when(value) {
@ -158,6 +162,7 @@ object ConfigurationItemTypeUtil {
CUSTOM_SERVER_URL -> ConfigurationItemType.CustomServerUrl
FOREGROUND_APP_QUERY_RANGE -> ConfigurationItemType.ForegroundAppQueryRange
ENABLE_BACKGROUND_SYNC -> ConfigurationItemType.EnableBackgroundSync
ENABLE_ALTERNATIVE_DURATION_SELECTION -> ConfigurationItemType.EnableAlternativeDurationSelection
else -> throw IllegalArgumentException()
}
}

View file

@ -50,6 +50,7 @@ import io.timelimit.android.ui.manage.child.advanced.managedisabletimelimits.Man
import io.timelimit.android.ui.manage.child.category.create.CreateCategoryDialogFragment
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
import io.timelimit.android.ui.view.SelectTimeSpanViewListener
class LockFragment : Fragment() {
companion object {
@ -224,6 +225,8 @@ class LockFragment : Fragment() {
// bind adding extra time controls
logic.fullVersion.shouldProvideFullVersionFunctions.observe(this, Observer { hasFullVersion ->
binding.extraTimeBtnOk.setOnClickListener {
binding.extraTimeSelection.clearNumberPickerFocus()
if (hasFullVersion) {
if (auth.isParentAuthenticated()) {
runAsync {
@ -255,6 +258,22 @@ class LockFragment : Fragment() {
}
})
logic.database.config().getEnableAlternativeDurationSelectionAsync().observe(this, Observer {
binding.extraTimeSelection.enablePickerMode(it)
})
binding.extraTimeSelection.listener = object: SelectTimeSpanViewListener {
override fun onTimeSpanChanged(newTimeInMillis: Long) {
// ignore
}
override fun setEnablePickerMode(enable: Boolean) {
Threads.database.execute {
logic.database.config().setEnableAlternativeDurationSelectionSync(enable)
}
}
}
// bind disable time limits
mergeLiveData(logic.deviceUserEntry, logic.fullVersion.shouldProvideFullVersionFunctions).observe(this, Observer {
(child, hasFullVersion) ->

View file

@ -23,6 +23,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import com.google.android.material.snackbar.Snackbar
import io.timelimit.android.R
import io.timelimit.android.async.Threads
import io.timelimit.android.databinding.FragmentCategorySettingsBinding
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic
@ -31,6 +32,7 @@ import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.getActivityViewModel
import io.timelimit.android.ui.manage.category.ManageCategoryFragmentArgs
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
import io.timelimit.android.ui.view.SelectTimeSpanViewListener
class CategorySettingsFragment : Fragment() {
companion object {
@ -99,6 +101,8 @@ class CategorySettingsFragment : Fragment() {
appLogic.fullVersion.shouldProvideFullVersionFunctions.observe(this, Observer { hasFullVersion ->
binding.extraTimeBtnOk.setOnClickListener {
binding.extraTimeSelection.clearNumberPickerFocus()
if (hasFullVersion) {
val newExtraTime = binding.extraTimeSelection.timeInMillis
@ -118,6 +122,22 @@ class CategorySettingsFragment : Fragment() {
}
})
appLogic.database.config().getEnableAlternativeDurationSelectionAsync().observe(this, Observer {
binding.extraTimeSelection.enablePickerMode(it)
})
binding.extraTimeSelection.listener = object: SelectTimeSpanViewListener {
override fun onTimeSpanChanged(newTimeInMillis: Long) {
// ignore
}
override fun setEnablePickerMode(enable: Boolean) {
Threads.database.execute {
appLogic.database.config().setEnableAlternativeDurationSelectionSync(enable)
}
}
}
return binding.root
}

View file

@ -26,6 +26,7 @@ import androidx.lifecycle.Observer
import com.google.android.material.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.timelimit.android.async.Threads
import io.timelimit.android.data.IdGenerator
import io.timelimit.android.data.model.TimeLimitRule
import io.timelimit.android.data.model.UserType
@ -92,6 +93,7 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment() {
val view = FragmentEditTimeLimitRuleDialogBinding.inflate(layoutInflater, container, false)
val listener = targetFragment as EditTimeLimitRuleDialogFragmentListener
var newRule: TimeLimitRule
val database = DefaultAppLogic.with(context!!).database
auth.authenticatedUser.observe(this, Observer {
if (it == null || it.second.type != UserType.Parent) {
@ -135,7 +137,7 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment() {
view.timeSpan.timeInMillis = newRule.maximumTimeInMillis.toLong()
val affectedDays = Math.max(0, (0..6).map { (newRule.dayMask.toInt() shr it) and 1 }.sum())
view.timeSpan.maxDays = affectedDays - 1
view.timeSpan.maxDays = Math.max(0, affectedDays - 1) // max prevents crash
view.affectsMultipleDays = affectedDays >= 2
}
@ -160,6 +162,8 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment() {
}
override fun onSaveRule() {
view.timeSpan.clearNumberPickerFocus()
if (existingRule != null) {
if (existingRule != newRule) {
if (!auth.tryDispatchParentAction(
@ -213,10 +217,20 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment() {
bindRule()
}
}
override fun setEnablePickerMode(enable: Boolean) {
Threads.database.execute {
database.config().setEnableAlternativeDurationSelectionSync(enable)
}
}
}
database.config().getEnableAlternativeDurationSelectionAsync().observe(this, Observer {
view.timeSpan.enablePickerMode(it)
})
if (existingRule != null) {
DefaultAppLogic.with(context!!).database.timeLimitRules()
database.timeLimitRules()
.getTimeLimitRuleByIdLive(existingRule!!.id).observe(this, Observer {
if (it == null) {
// rule was deleted

View file

@ -18,6 +18,7 @@ package io.timelimit.android.ui.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.SeekBar
import io.timelimit.android.R
@ -34,14 +35,16 @@ class SelectTimeSpanView(context: Context, attributeSet: AttributeSet): FrameLay
var listener: SelectTimeSpanViewListener? = null
var timeInMillis: Long by Delegates.observable(0L) {
_, _, _ ->
var timeInMillis: Long by Delegates.observable(0L) { _, _, _ ->
bindTime()
listener?.onTimeSpanChanged(timeInMillis)
}
var maxDays: Int by Delegates.observable(0) {
_, _, _ -> binding.maxDays = maxDays
var maxDays: Int by Delegates.observable(0) { _, _, _ ->
binding.maxDays = maxDays
binding.dayPicker.maxValue = maxDays
binding.dayPickerContainer.visibility = if (maxDays > 0) View.VISIBLE else View.GONE
}
init {
@ -69,6 +72,10 @@ class SelectTimeSpanView(context: Context, attributeSet: AttributeSet): FrameLay
binding.daysText = TimeTextUtil.days(totalDays, context!!)
binding.minutesText = TimeTextUtil.minutes(minutes, context!!)
binding.hoursText = TimeTextUtil.hours(hours, context!!)
binding.minutePicker.value = binding.minutes ?: 0
binding.hourPicker.value = binding.hours ?: 0
binding.dayPicker.value = binding.days ?: 0
}
private fun readStatusFromBinding() {
@ -79,7 +86,43 @@ class SelectTimeSpanView(context: Context, attributeSet: AttributeSet): FrameLay
timeInMillis = (((days * 24) + hours) * 60 + minutes) * 1000 * 60
}
fun clearNumberPickerFocus() {
binding.minutePicker.clearFocus()
binding.hourPicker.clearFocus()
binding.dayPicker.clearFocus()
}
fun enablePickerMode(enable: Boolean) {
binding.seekbarContainer.visibility = if (enable) View.GONE else View.VISIBLE
binding.pickerContainer.visibility = if (enable) View.VISIBLE else View.GONE
}
init {
binding.minutePicker.minValue = 0
binding.minutePicker.maxValue = 59
binding.hourPicker.minValue = 0
binding.hourPicker.maxValue = 23
binding.dayPicker.minValue = 0
binding.dayPicker.maxValue = 1
binding.dayPickerContainer.visibility = View.GONE
binding.minutePicker.setOnValueChangedListener { _, _, newValue ->
binding.minutes = newValue
readStatusFromBinding()
}
binding.hourPicker.setOnValueChangedListener { _, _, newValue ->
binding.hours = newValue
readStatusFromBinding()
}
binding.dayPicker.setOnValueChangedListener { _, _, newValue ->
binding.days = newValue
readStatusFromBinding()
}
binding.daysSeek.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
binding.days = progress
@ -124,9 +167,15 @@ class SelectTimeSpanView(context: Context, attributeSet: AttributeSet): FrameLay
// ignore
}
})
binding.pickerContainer.visibility = GONE
binding.switchToPickerButton.setOnClickListener { listener?.setEnablePickerMode(true) }
binding.switchToSeekbarButton.setOnClickListener { listener?.setEnablePickerMode(false) }
}
}
interface SelectTimeSpanViewListener {
fun onTimeSpanChanged(newTimeInMillis: Long)
fun setEnablePickerMode(enable: Boolean)
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/>
</vector>

View file

@ -53,6 +53,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/seekbar_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_weight="1"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<SeekBar
android:id="@+id/minutes_seek"
android:progress="@{safeUnbox(minutes)}"
@ -96,4 +108,94 @@
</LinearLayout>
<ImageButton
android:id="@+id/switch_to_picker_button"
android:src="@drawable/ic_unfold_more_black_24dp"
android:background="?selectableItemBackground"
android:layout_gravity="center_vertical"
android:layout_width="32dp"
android:layout_height="32dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/picker_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/day_picker_container"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<NumberPicker
android:id="@+id/day_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center_horizontal"
android:text="@string/select_time_span_view_days"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<NumberPicker
android:id="@+id/hour_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center_horizontal"
android:text="@string/select_time_span_view_hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<NumberPicker
android:id="@+id/minute_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center_horizontal"
android:text="@string/select_time_span_view_minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="0dp" />
<ImageButton
android:id="@+id/switch_to_seekbar_button"
android:rotation="90"
android:layout_margin="16dp"
android:src="@drawable/ic_unfold_more_black_24dp"
android:background="?selectableItemBackground"
android:layout_gravity="center_vertical"
android:layout_width="32dp"
android:layout_height="32dp" />
</LinearLayout>
</LinearLayout>
</layout>

View file

@ -0,0 +1,20 @@
<?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/>.
-->
<resources>
<string name="select_time_span_view_minutes">Minuten</string>
<string name="select_time_span_view_hours">Stunden</string>
<string name="select_time_span_view_days">Tage</string>
</resources>

View file

@ -0,0 +1,20 @@
<?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/>.
-->
<resources>
<string name="select_time_span_view_minutes">Minutes</string>
<string name="select_time_span_view_hours">Hours</string>
<string name="select_time_span_view_days">Days</string>
</resources>