From a7e2131638d3e98bf52a9b9f8c61f42c797b3caf Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 12 Sep 2022 02:00:00 +0200 Subject: [PATCH] Add optional translucency to the Widget --- .../46.json | 1760 +++++++++++++++++ .../io/timelimit/android/data/Database.kt | 1 + .../io/timelimit/android/data/Migrations.kt | 9 +- .../io/timelimit/android/data/RoomDatabase.kt | 5 +- .../android/data/dao/WidgetConfigDao.kt | 40 + .../android/data/model/WidgetConfig.kt | 30 + .../android/ui/widget/TimesWidgetProvider.kt | 58 +- .../android/ui/widget/TimesWidgetService.kt | 18 +- .../ui/widget/config/WidgetConfigActivity.kt | 5 + .../ui/widget/config/WidgetConfigModel.kt | 53 +- .../config/WidgetConfigOtherDialogFragment.kt | 79 + .../res/layout/widget_times_category_item.xml | 2 +- ...widget_times_category_item_translucent.xml | 60 + .../res/layout/widget_times_translucent.xml | 36 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 1 + 17 files changed, 2133 insertions(+), 27 deletions(-) create mode 100644 app/schemas/io.timelimit.android.data.RoomDatabase/46.json create mode 100644 app/src/main/java/io/timelimit/android/data/dao/WidgetConfigDao.kt create mode 100644 app/src/main/java/io/timelimit/android/data/model/WidgetConfig.kt create mode 100644 app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigOtherDialogFragment.kt create mode 100644 app/src/main/res/layout/widget_times_category_item_translucent.xml create mode 100644 app/src/main/res/layout/widget_times_translucent.xml diff --git a/app/schemas/io.timelimit.android.data.RoomDatabase/46.json b/app/schemas/io.timelimit.android.data.RoomDatabase/46.json new file mode 100644 index 0000000..4f38f8c --- /dev/null +++ b/app/schemas/io.timelimit.android.data.RoomDatabase/46.json @@ -0,0 +1,1760 @@ +{ + "formatVersion": 1, + "database": { + "version": 46, + "identityHash": "067b5609922124e1ce7a1b842d8d53f4", + "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, `blocked_times` TEXT NOT NULL, `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 + }, + { + "fieldPath": "obsoleteBlockedTimes", + "columnName": "blocked_times", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "flags", + "columnName": "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, `had_manipulation_flags` 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, `manipulation_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": "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": "hadManipulationFlags", + "columnName": "had_manipulation_flags", + "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 + }, + { + "fieldPath": "manipulationFlags", + "columnName": "manipulation_flags", + "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" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_app_device_id` ON `${TABLE_NAME}` (`device_id`)" + }, + { + "name": "index_app_package_name", + "unique": false, + "columnNames": [ + "package_name" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `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": "appSpecifierString", + "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" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_category_app_category_id` ON `${TABLE_NAME}` (`category_id`)" + }, + { + "name": "index_category_app_package_name", + "unique": false, + "columnNames": [ + "package_name" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `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, `extra_time_day` INTEGER NOT NULL, `temporarily_blocked` INTEGER NOT NULL, `temporarily_blocked_end_time` INTEGER NOT NULL, `base_version` TEXT NOT NULL, `apps_version` TEXT NOT NULL, `rules_version` TEXT NOT NULL, `usedtimes_version` TEXT NOT NULL, `tasks_version` TEXT NOT NULL DEFAULT '', `parent_category_id` TEXT NOT NULL, `block_all_notifications` INTEGER NOT NULL, `time_warnings` INTEGER NOT NULL, `min_battery_charging` INTEGER NOT NULL, `min_battery_mobile` INTEGER NOT NULL, `sort` INTEGER NOT NULL, `disable_limits_until` INTEGER NOT NULL, `flags` INTEGER NOT NULL DEFAULT 0, `block_notification_delay` INTEGER NOT NULL DEFAULT 0, 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": "extraTimeDay", + "columnName": "extra_time_day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "temporarilyBlocked", + "columnName": "temporarily_blocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "temporarilyBlockedEndTime", + "columnName": "temporarily_blocked_end_time", + "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": "tasksVersion", + "columnName": "tasks_version", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "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 + }, + { + "fieldPath": "minBatteryLevelWhileCharging", + "columnName": "min_battery_charging", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minBatteryLevelMobile", + "columnName": "min_battery_mobile", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sort", + "columnName": "sort", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "disableLimitsUntil", + "columnName": "disable_limits_until", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "flags", + "columnName": "flags", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "blockNotificationDelay", + "columnName": "block_notification_delay", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "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, `start_time_of_day` INTEGER NOT NULL, `end_time_of_day` INTEGER NOT NULL, PRIMARY KEY(`category_id`, `day_of_epoch`, `start_time_of_day`, `end_time_of_day`))", + "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 + }, + { + "fieldPath": "startTimeOfDay", + "columnName": "start_time_of_day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endTimeOfDay", + "columnName": "end_time_of_day", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "day_of_epoch", + "start_time_of_day", + "end_time_of_day" + ], + "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, `start_minute_of_day` INTEGER NOT NULL, `end_minute_of_day` INTEGER NOT NULL, `session_duration_milliseconds` INTEGER NOT NULL, `session_pause_milliseconds` INTEGER NOT NULL, `per_day` 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 + }, + { + "fieldPath": "startMinuteOfDay", + "columnName": "start_minute_of_day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endMinuteOfDay", + "columnName": "end_minute_of_day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sessionDurationMilliseconds", + "columnName": "session_duration_milliseconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sessionPauseMilliseconds", + "columnName": "session_pause_milliseconds", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "perDay", + "columnName": "per_day", + "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" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `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": [] + }, + { + "tableName": "allowed_contact", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `phone` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phone", + "columnName": "phone", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "user_key", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `key` BLOB NOT NULL, `last_use` INTEGER NOT NULL, PRIMARY KEY(`user_id`), FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "key", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "lastUse", + "columnName": "last_use", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "user_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_user_key_key", + "unique": true, + "columnNames": [ + "key" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_user_key_key` ON `${TABLE_NAME}` (`key`)" + } + ], + "foreignKeys": [ + { + "table": "user", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "user_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "session_duration", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` TEXT NOT NULL, `max_session_duration` INTEGER NOT NULL, `session_pause_duration` INTEGER NOT NULL, `start_minute_of_day` INTEGER NOT NULL, `end_minute_of_day` INTEGER NOT NULL, `last_usage` INTEGER NOT NULL, `last_session_duration` INTEGER NOT NULL, PRIMARY KEY(`category_id`, `max_session_duration`, `session_pause_duration`, `start_minute_of_day`, `end_minute_of_day`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "maxSessionDuration", + "columnName": "max_session_duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sessionPauseDuration", + "columnName": "session_pause_duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startMinuteOfDay", + "columnName": "start_minute_of_day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endMinuteOfDay", + "columnName": "end_minute_of_day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUsage", + "columnName": "last_usage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastSessionDuration", + "columnName": "last_session_duration", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "max_session_duration", + "session_pause_duration", + "start_minute_of_day", + "end_minute_of_day" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "session_duration_index_category_id", + "unique": false, + "columnNames": [ + "category_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `session_duration_index_category_id` ON `${TABLE_NAME}` (`category_id`)" + } + ], + "foreignKeys": [ + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "user_limit_login_category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `pre_block_duration` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`user_id`), FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "preBlockDuration", + "columnName": "pre_block_duration", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "user_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "user_limit_login_category_index_category_id", + "unique": false, + "columnNames": [ + "category_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `user_limit_login_category_index_category_id` ON `${TABLE_NAME}` (`category_id`)" + } + ], + "foreignKeys": [ + { + "table": "user", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "user_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "category_network_id", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` TEXT NOT NULL, `network_item_id` TEXT NOT NULL, `hashed_network_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `network_item_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "networkItemId", + "columnName": "network_item_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hashedNetworkId", + "columnName": "hashed_network_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "network_item_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "child_task", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`task_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, `task_title` TEXT NOT NULL, `extra_time_duration` INTEGER NOT NULL, `pending_request` INTEGER NOT NULL, `last_grant_timestamp` INTEGER NOT NULL, PRIMARY KEY(`task_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "taskId", + "columnName": "task_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "taskTitle", + "columnName": "task_title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extraTimeDuration", + "columnName": "extra_time_duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pendingRequest", + "columnName": "pending_request", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastGrantTimestamp", + "columnName": "last_grant_timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "task_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "category_time_warning", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` TEXT NOT NULL, `minutes` INTEGER NOT NULL, PRIMARY KEY(`category_id`, `minutes`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "minutes", + "columnName": "minutes", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "minutes" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "crypt_container_metadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`crypt_container_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `device_id` TEXT, `category_id` TEXT, `type` INTEGER NOT NULL, `server_version` TEXT NOT NULL, `current_generation` INTEGER NOT NULL, `current_generation_first_timestamp` INTEGER NOT NULL, `next_counter` INTEGER NOT NULL, `current_generation_key` BLOB, `status` INTEGER NOT NULL, FOREIGN KEY(`device_id`) REFERENCES `device`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "cryptContainerId", + "columnName": "crypt_container_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serverVersion", + "columnName": "server_version", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentGeneration", + "columnName": "current_generation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentGenerationFirstTimestamp", + "columnName": "current_generation_first_timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nextCounter", + "columnName": "next_counter", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentGenerationKey", + "columnName": "current_generation_key", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "crypt_container_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_crypt_container_metadata_device_id", + "unique": false, + "columnNames": [ + "device_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_crypt_container_metadata_device_id` ON `${TABLE_NAME}` (`device_id`)" + }, + { + "name": "index_crypt_container_metadata_category_id", + "unique": false, + "columnNames": [ + "category_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_crypt_container_metadata_category_id` ON `${TABLE_NAME}` (`category_id`)" + } + ], + "foreignKeys": [ + { + "table": "device", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "device_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "crypt_container_data", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`crypt_container_id` INTEGER NOT NULL, `encrypted_data` BLOB NOT NULL, PRIMARY KEY(`crypt_container_id`), FOREIGN KEY(`crypt_container_id`) REFERENCES `crypt_container_metadata`(`crypt_container_id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "cryptContainerId", + "columnName": "crypt_container_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "encryptedData", + "columnName": "encrypted_data", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "crypt_container_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "crypt_container_metadata", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "crypt_container_id" + ], + "referencedColumns": [ + "crypt_container_id" + ] + } + ] + }, + { + "tableName": "crypt_container_pending_key_request", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`crypt_container_id` INTEGER NOT NULL, `request_time_crypt_container_generation` INTEGER NOT NULL, `request_sequence_id` INTEGER NOT NULL, `request_key` BLOB NOT NULL, PRIMARY KEY(`crypt_container_id`), FOREIGN KEY(`crypt_container_id`) REFERENCES `crypt_container_metadata`(`crypt_container_id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "cryptContainerId", + "columnName": "crypt_container_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requestTimeCryptContainerGeneration", + "columnName": "request_time_crypt_container_generation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requestSequenceId", + "columnName": "request_sequence_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "requestKey", + "columnName": "request_key", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "crypt_container_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_crypt_container_pending_key_request_request_sequence_id", + "unique": true, + "columnNames": [ + "request_sequence_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_crypt_container_pending_key_request_request_sequence_id` ON `${TABLE_NAME}` (`request_sequence_id`)" + } + ], + "foreignKeys": [ + { + "table": "crypt_container_metadata", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "crypt_container_id" + ], + "referencedColumns": [ + "crypt_container_id" + ] + } + ] + }, + { + "tableName": "crypt_container_key_result", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`request_sequence_id` INTEGER NOT NULL, `device_id` TEXT NOT NULL, `status` INTEGER NOT NULL, PRIMARY KEY(`request_sequence_id`, `device_id`), FOREIGN KEY(`request_sequence_id`) REFERENCES `crypt_container_pending_key_request`(`request_sequence_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`device_id`) REFERENCES `device`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "requestSequenceId", + "columnName": "request_sequence_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "request_sequence_id", + "device_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_crypt_container_key_result_request_sequence_id", + "unique": false, + "columnNames": [ + "request_sequence_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_crypt_container_key_result_request_sequence_id` ON `${TABLE_NAME}` (`request_sequence_id`)" + }, + { + "name": "index_crypt_container_key_result_device_id", + "unique": false, + "columnNames": [ + "device_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_crypt_container_key_result_device_id` ON `${TABLE_NAME}` (`device_id`)" + } + ], + "foreignKeys": [ + { + "table": "crypt_container_pending_key_request", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "request_sequence_id" + ], + "referencedColumns": [ + "request_sequence_id" + ] + }, + { + "table": "device", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "device_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "device_public_key", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`device_id` TEXT NOT NULL, `public_key` BLOB NOT NULL, `next_sequence_number` INTEGER NOT NULL, PRIMARY KEY(`device_id`), FOREIGN KEY(`device_id`) REFERENCES `device`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "public_key", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "nextSequenceNumber", + "columnName": "next_sequence_number", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "device_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "device", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "device_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "user_u2f_key", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `added_at` INTEGER NOT NULL, `key_handle` BLOB NOT NULL, `public_key` BLOB NOT NULL, `next_counter` INTEGER NOT NULL, FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "keyId", + "columnName": "key_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "addedAt", + "columnName": "added_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keyHandle", + "columnName": "key_handle", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "publicKey", + "columnName": "public_key", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "nextCounter", + "columnName": "next_counter", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "key_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_user_u2f_key_key_handle_public_key", + "unique": true, + "columnNames": [ + "key_handle", + "public_key" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_user_u2f_key_key_handle_public_key` ON `${TABLE_NAME}` (`key_handle`, `public_key`)" + }, + { + "name": "index_user_u2f_key_user_id", + "unique": false, + "columnNames": [ + "user_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_user_u2f_key_user_id` ON `${TABLE_NAME}` (`user_id`)" + } + ], + "foreignKeys": [ + { + "table": "user", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "user_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "widget_category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`widget_id` INTEGER NOT NULL, `category_id` TEXT NOT NULL, PRIMARY KEY(`widget_id`, `category_id`), FOREIGN KEY(`category_id`) REFERENCES `category`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "widgetId", + "columnName": "widget_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "widget_id", + "category_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_widget_category_category_id", + "unique": false, + "columnNames": [ + "category_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_widget_category_category_id` ON `${TABLE_NAME}` (`category_id`)" + } + ], + "foreignKeys": [ + { + "table": "category", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "widget_config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`widget_id` INTEGER NOT NULL, `translucent` INTEGER NOT NULL, PRIMARY KEY(`widget_id`))", + "fields": [ + { + "fieldPath": "widgetId", + "columnName": "widget_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "translucent", + "columnName": "translucent", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "widget_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, '067b5609922124e1ce7a1b842d8d53f4')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/data/Database.kt b/app/src/main/java/io/timelimit/android/data/Database.kt index c786f51..9d9d2fc 100644 --- a/app/src/main/java/io/timelimit/android/data/Database.kt +++ b/app/src/main/java/io/timelimit/android/data/Database.kt @@ -47,6 +47,7 @@ interface Database { fun deviceKey(): DeviceKeyDao fun u2f(): U2FDao fun widgetCategory(): WidgetCategoryDao + fun widgetConfig(): WidgetConfigDao fun runInTransaction(block: () -> T): T fun runInUnobservedTransaction(block: () -> T): T diff --git a/app/src/main/java/io/timelimit/android/data/Migrations.kt b/app/src/main/java/io/timelimit/android/data/Migrations.kt index 4419806..d6438cc 100644 --- a/app/src/main/java/io/timelimit/android/data/Migrations.kt +++ b/app/src/main/java/io/timelimit/android/data/Migrations.kt @@ -333,6 +333,12 @@ object DatabaseMigrations { } } + val MIGRATE_TO_V46 = object: Migration(45, 46) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `widget_config` (`widget_id` INTEGER NOT NULL, `translucent` INTEGER NOT NULL, PRIMARY KEY(`widget_id`))") + } + } + val ALL = arrayOf( MIGRATE_TO_V2, MIGRATE_TO_V3, @@ -377,6 +383,7 @@ object DatabaseMigrations { MIGRATE_TO_V42, MIGRATE_TP_V43, MIGRATE_TO_V44, - MIGRATE_TO_V45 + MIGRATE_TO_V45, + MIGRATE_TO_V46 ) } diff --git a/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt b/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt index 0e24449..36ad5ea 100644 --- a/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt +++ b/app/src/main/java/io/timelimit/android/data/RoomDatabase.kt @@ -59,8 +59,9 @@ import java.util.concurrent.TimeUnit CryptContainerKeyResult::class, DevicePublicKey::class, UserU2FKey::class, - WidgetCategory::class -], version = 45) + WidgetCategory::class, + WidgetConfig::class +], version = 46) abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database { companion object { private val lock = Object() diff --git a/app/src/main/java/io/timelimit/android/data/dao/WidgetConfigDao.kt b/app/src/main/java/io/timelimit/android/data/dao/WidgetConfigDao.kt new file mode 100644 index 0000000..117628b --- /dev/null +++ b/app/src/main/java/io/timelimit/android/data/dao/WidgetConfigDao.kt @@ -0,0 +1,40 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.data.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.timelimit.android.data.model.WidgetConfig + +@Dao +interface WidgetConfigDao { + @Query("SELECT * FROM widget_config") + fun queryAll(): List + + @Query("SELECT * FROM widget_config WHERE widget_id = :widgetId") + fun queryByWidgetId(widgetId: Int): WidgetConfig? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun upsert(config: WidgetConfig) + + @Query("DELETE FROM widget_config WHERE widget_id IN (:widgetIds)") + fun deleteByWidgetIds(widgetIds: IntArray) + + @Query("DELETE FROM widget_config") + fun deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/data/model/WidgetConfig.kt b/app/src/main/java/io/timelimit/android/data/model/WidgetConfig.kt new file mode 100644 index 0000000..e1a9955 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/data/model/WidgetConfig.kt @@ -0,0 +1,30 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.data.model + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity( + tableName = "widget_config" +) +data class WidgetConfig ( + @ColumnInfo(name = "widget_id") + @PrimaryKey + val widgetId: Int, + val translucent: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetProvider.kt b/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetProvider.kt index a1656a6..288dc33 100644 --- a/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetProvider.kt +++ b/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetProvider.kt @@ -26,6 +26,8 @@ import androidx.core.content.getSystemService import io.timelimit.android.BuildConfig import io.timelimit.android.R import io.timelimit.android.async.Threads +import io.timelimit.android.coroutines.executeAndWait +import io.timelimit.android.coroutines.runAsync import io.timelimit.android.integration.platform.android.BackgroundActionService import io.timelimit.android.logic.DefaultAppLogic @@ -34,24 +36,48 @@ class TimesWidgetProvider: AppWidgetProvider() { private const val LOG_TAG = "TimesWidgetProvider" private fun handleUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { - for (appWidgetId in appWidgetIds) { - val views = RemoteViews(context.packageName, R.layout.widget_times) + runAsync { + val configs = Threads.database.executeAndWait { + try { + DefaultAppLogic.with(context).database.widgetConfig() + .queryAll() + .associateBy { it.widgetId } + } catch (ex: Exception) { + if (BuildConfig.DEBUG) { + Log.d(LOG_TAG, "could not query database", ex) + } - views.setRemoteAdapter(android.R.id.list, TimesWidgetService.intent(context, appWidgetId)) - views.setPendingIntentTemplate(android.R.id.list, BackgroundActionService.getSwitchToDefaultUserIntent(context)) - views.setEmptyView(android.R.id.list, android.R.id.empty) + emptyMap() + } + } - appWidgetManager.updateAppWidget(appWidgetId, views) + for (appWidgetId in appWidgetIds) { + val config = configs[appWidgetId] + val translucent = config?.translucent ?: false + + val views = RemoteViews( + context.packageName, + if (translucent) R.layout.widget_times_translucent + else R.layout.widget_times + ) + + views.setRemoteAdapter(android.R.id.list, TimesWidgetService.intent(context, appWidgetId, translucent)) + views.setPendingIntentTemplate(android.R.id.list, BackgroundActionService.getSwitchToDefaultUserIntent(context)) + views.setEmptyView(android.R.id.list, android.R.id.empty) + + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + TimesWidgetService.notifyContentChanges(context) } - - TimesWidgetService.notifyContentChanges(context) } - fun triggerUpdates(context: Context) { + fun triggerUpdates(context: Context, appWidgetIds: IntArray? = null) { context.getSystemService()?.also { appWidgetManager -> - val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, TimesWidgetProvider::class.java)) + val usedAppWidgetIds = appWidgetIds + ?: appWidgetManager.getAppWidgetIds(ComponentName(context, TimesWidgetProvider::class.java)) - handleUpdate(context, appWidgetManager, appWidgetIds) + handleUpdate(context, appWidgetManager, usedAppWidgetIds) } } } @@ -76,7 +102,10 @@ class TimesWidgetProvider: AppWidgetProvider() { Threads.database.execute { try { - database.widgetCategory().deleteByWidgetIds(appWidgetIds) + database.runInTransaction { + database.widgetCategory().deleteByWidgetIds(appWidgetIds) + database.widgetConfig().deleteByWidgetIds(appWidgetIds) + } } catch (ex: Exception) { if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "onDisabled", ex) @@ -92,7 +121,10 @@ class TimesWidgetProvider: AppWidgetProvider() { Threads.database.execute { try { - database.widgetCategory().deleteAll() + database.runInTransaction { + database.widgetCategory().deleteAll() + database.widgetConfig().deleteAll() + } } catch (ex: Exception) { if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "onDisabled", ex) diff --git a/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetService.kt b/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetService.kt index 1d255b4..cb289c7 100644 --- a/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetService.kt +++ b/app/src/main/java/io/timelimit/android/ui/widget/TimesWidgetService.kt @@ -36,10 +36,12 @@ import io.timelimit.android.ui.manage.child.category.CategoryItemLeftPadding class TimesWidgetService: RemoteViewsService() { companion object { private const val EXTRA_APP_WIDGET_ID = "appWidgetId" + private const val EXTRA_TRANSLUCENT = "translucent" - fun intent(context: Context, appWidgetId: Int) = Intent(context, TimesWidgetService::class.java) - .setData(Uri.parse("widget:$appWidgetId")) + fun intent(context: Context, appWidgetId: Int, translucent: Boolean) = Intent(context, TimesWidgetService::class.java) + .setData(Uri.parse("widget:$appWidgetId:$translucent")) .putExtra(EXTRA_APP_WIDGET_ID, appWidgetId) + .putExtra(EXTRA_TRANSLUCENT, translucent) fun notifyContentChanges(context: Context) { context.getSystemService()?.also { appWidgetManager -> @@ -68,7 +70,7 @@ class TimesWidgetService: RemoteViewsService() { notifyContentChanges(this) } - private fun createFactory(appWidgetId: Int) = object : RemoteViewsFactory { + private fun createFactory(appWidgetId: Int, translucent: Boolean) = object : RemoteViewsFactory { private var currentItems: List = emptyList() init { onDataSetChanged() } @@ -98,11 +100,14 @@ class TimesWidgetService: RemoteViewsService() { override fun getCount(): Int = currentItems.size override fun getViewAt(position: Int): RemoteViews { + val categoryItemView = if (translucent) R.layout.widget_times_category_item_translucent + else R.layout.widget_times_category_item + if (position >= currentItems.size) { - return RemoteViews(packageName, R.layout.widget_times_category_item) + return RemoteViews(packageName, categoryItemView) } - fun createCategoryItem(title: String?, subtitle: String, paddingLeft: Int) = RemoteViews(packageName, R.layout.widget_times_category_item).also { result -> + fun createCategoryItem(title: String?, subtitle: String, paddingLeft: Int) = RemoteViews(packageName, categoryItemView).also { result -> result.setTextViewText(R.id.title, title ?: "") result.setTextViewText(R.id.subtitle, subtitle) @@ -161,6 +166,7 @@ class TimesWidgetService: RemoteViewsService() { } override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = createFactory( - intent.getIntExtra(EXTRA_APP_WIDGET_ID, 0) + intent.getIntExtra(EXTRA_APP_WIDGET_ID, 0), + intent.getBooleanExtra(EXTRA_TRANSLUCENT, false) ) } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt index b1c09e3..cfa4da5 100644 --- a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt @@ -56,6 +56,11 @@ class WidgetConfigActivity: FragmentActivity() { WidgetConfigFilterDialogFragment().showSafe(supportFragmentManager, WidgetConfigFilterDialogFragment.DIALOG_TAG) } } + is WidgetConfigModel.State.ShowOtherOptions -> { + if (supportFragmentManager.findFragmentByTag(WidgetConfigOtherDialogFragment.DIALOG_TAG) == null) { + WidgetConfigOtherDialogFragment().showSafe(supportFragmentManager, WidgetConfigOtherDialogFragment.DIALOG_TAG) + } + } is WidgetConfigModel.State.Done -> { setResult( RESULT_OK, diff --git a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigModel.kt b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigModel.kt index 98d5858..48147de 100644 --- a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigModel.kt +++ b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigModel.kt @@ -27,8 +27,10 @@ import io.timelimit.android.data.extensions.sortedCategories import io.timelimit.android.data.model.Category import io.timelimit.android.data.model.UserType import io.timelimit.android.data.model.WidgetCategory +import io.timelimit.android.data.model.WidgetConfig import io.timelimit.android.livedata.castDown import io.timelimit.android.logic.DefaultAppLogic +import io.timelimit.android.ui.widget.TimesWidgetProvider import kotlinx.coroutines.launch class WidgetConfigModel(application: Application): AndroidViewModel(application) { @@ -83,11 +85,16 @@ class WidgetConfigModel(application: Application): AndroidViewModel(application) viewModelScope.launch { try { - Threads.database.executeAndWait { + val currentConfig = Threads.database.executeAndWait { database.widgetCategory().deleteByWidgetId(oldState.appWidgetId) + + database.widgetConfig().queryByWidgetId(oldState.appWidgetId) } - stateInternal.value = State.Done(appWidgetId = oldState.appWidgetId) + stateInternal.value = State.ShowOtherOptions( + appWidgetId = oldState.appWidgetId, + translucent = currentConfig?.translucent ?: false + ) } catch (ex: Exception) { if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "selectModeAll", ex) @@ -117,7 +124,7 @@ class WidgetConfigModel(application: Application): AndroidViewModel(application) viewModelScope.launch { try { - Threads.database.executeAndWait { + val currentConfig = Threads.database.executeAndWait { val userAndDeviceRelatedData = database.derivedDataDao().getUserAndDeviceRelatedDataSync() val currentCategoryIds = userAndDeviceRelatedData!!.userRelatedData!!.categoryById.keys @@ -135,9 +142,14 @@ class WidgetConfigModel(application: Application): AndroidViewModel(application) categoriesToAdd.toList().map { WidgetCategory(oldState.appWidgetId, it) } ) } + + database.widgetConfig().queryByWidgetId(oldState.appWidgetId) } - stateInternal.value = State.Done(appWidgetId = oldState.appWidgetId) + stateInternal.value = State.ShowOtherOptions( + appWidgetId = oldState.appWidgetId, + translucent = currentConfig?.translucent ?: false + ) } catch (ex: Exception) { if (BuildConfig.DEBUG) { Log.d(LOG_TAG, "selectModeAll", ex) @@ -148,6 +160,38 @@ class WidgetConfigModel(application: Application): AndroidViewModel(application) } } + fun selectOtherOptions(translucent: Boolean) { + val oldState = state.value + if (!(oldState is State.ShowOtherOptions)) return + stateInternal.value = State.Working + + viewModelScope.launch { + try { + Threads.database.executeAndWait { + database.widgetConfig().upsert( + WidgetConfig( + widgetId = oldState.appWidgetId, + translucent = translucent + ) + ) + } + + TimesWidgetProvider.triggerUpdates( + context = getApplication(), + appWidgetIds = intArrayOf(oldState.appWidgetId) + ) + + stateInternal.value = State.Done(oldState.appWidgetId) + } catch (ex: Exception) { + if (BuildConfig.DEBUG) { + Log.d(LOG_TAG, "selectOtherOptions", ex) + } + + stateInternal.value = State.ErrorCancel + } + } + } + fun userCancel() { stateInternal.value = State.UserCancel } @@ -157,6 +201,7 @@ class WidgetConfigModel(application: Application): AndroidViewModel(application) object Working: State() data class ShowModeSelection(val appWidgetId: Int, val selectedFilterCategories: Set, val categories: List): State() data class ShowCategorySelection(val appWidgetId: Int, val selectedFilterCategories: Set, val categories: List): State() + data class ShowOtherOptions(val appWidgetId: Int, val translucent: Boolean): State() data class Done(val appWidgetId: Int): State() object Unconfigured: State() object UserCancel: State() diff --git a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigOtherDialogFragment.kt b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigOtherDialogFragment.kt new file mode 100644 index 0000000..97fe79a --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigOtherDialogFragment.kt @@ -0,0 +1,79 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.timelimit.android.ui.widget.config + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import io.timelimit.android.R + +class WidgetConfigOtherDialogFragment: DialogFragment() { + companion object { + private const val STATE_TRANSLUCENT = "translucent" + + const val DIALOG_TAG = "WidgetConfigOtherDialogFragment" + } + + private val model: WidgetConfigModel by activityViewModels() + private var translucent = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + model.state.value?.also { + if (it is WidgetConfigModel.State.ShowOtherOptions) { + translucent = it.translucent + } + } + + savedInstanceState?.also { translucent = it.getBoolean(STATE_TRANSLUCENT) } + + model.state.observe(this) { + if (!(it is WidgetConfigModel.State.ShowOtherOptions)) dismissAllowingStateLoss() + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + outState.putBoolean(STATE_TRANSLUCENT, translucent) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(requireContext(), theme) + .setMultiChoiceItems( + arrayOf( + getString(R.string.widget_config_other_translucent) + ), + booleanArrayOf( + translucent + ) + ) { _, _, checked -> + translucent = checked + } + .setPositiveButton(R.string.wiazrd_next) { _, _ -> + model.selectOtherOptions(translucent) + } + .create() + + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + + model.userCancel() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/widget_times_category_item.xml b/app/src/main/res/layout/widget_times_category_item.xml index a44894e..97203e7 100644 --- a/app/src/main/res/layout/widget_times_category_item.xml +++ b/app/src/main/res/layout/widget_times_category_item.xml @@ -16,7 +16,7 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_times_translucent.xml b/app/src/main/res/layout/widget_times_translucent.xml new file mode 100644 index 0000000..a6d94f4 --- /dev/null +++ b/app/src/main/res/layout/widget_times_translucent.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index bb2e830..68159ea 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1618,6 +1618,7 @@ alle Kategorien anzeigen sichtbare Kategorien filtern Sie müssen mindestens eine Kategorie auswählen + Transparenz aktivieren Weiter diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 29a0185..f4d07c0 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -18,7 +18,9 @@ #00796B #1976d2 #ffffff + #33000000 #000000 + #ffffff #00000000 #9E9E9E diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ca925a8..a2ad61c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1666,6 +1666,7 @@ Show all Categories Filter visible Categories You must select at least one category + Enable translucency Next