mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 17:59:51 +02:00
Compare commits
No commits in common. "master" and "release-6.20.0" have entirely different histories.
master
...
release-6.
81 changed files with 612 additions and 2968 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,4 +10,3 @@
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.idea
|
.idea
|
||||||
.kotlin
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,18 +21,17 @@ plugins {
|
||||||
id "androidx.navigation.safeargs.kotlin"
|
id "androidx.navigation.safeargs.kotlin"
|
||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
id 'com.squareup.wire'
|
id 'com.squareup.wire'
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.1.10"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'io.timelimit.android'
|
namespace 'io.timelimit.android'
|
||||||
compileSdk 36
|
compileSdkVersion 34
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.timelimit.android"
|
applicationId "io.timelimit.android"
|
||||||
minSdkVersion 26
|
minSdkVersion 21
|
||||||
targetSdkVersion 36
|
targetSdkVersion 34
|
||||||
versionCode 224
|
versionCode 214
|
||||||
versionName "7.3.0"
|
versionName "6.20.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
kapt {
|
kapt {
|
||||||
arguments {
|
arguments {
|
||||||
|
@ -51,7 +50,6 @@ android {
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose true
|
compose true
|
||||||
viewBinding true
|
viewBinding true
|
||||||
buildConfig true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions 'api', 'channel', 'server'
|
flavorDimensions 'api', 'channel', 'server'
|
||||||
|
@ -147,8 +145,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_21
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_21
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -157,7 +155,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.5.7"
|
kotlinCompilerExtensionVersion = "1.5.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,24 +165,23 @@ wire {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def nav_version = "2.5.3"
|
def nav_version = "2.5.3"
|
||||||
def room_version = "2.7.2"
|
def room_version = "2.6.1"
|
||||||
def work_version = '2.10.2'
|
def work_version = '2.9.0'
|
||||||
def paging_version = "3.3.6"
|
def paging_version = "3.2.1"
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.10"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20"
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.core:core:1.16.0'
|
implementation 'androidx.core:core:1.12.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.1.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation "com.google.android.material:material:1.12.0"
|
implementation "com.google.android.material:material:1.11.0"
|
||||||
implementation 'androidx.compose.material:material:1.8.3'
|
implementation 'androidx.compose.material:material:1.6.3'
|
||||||
implementation 'androidx.activity:activity-compose:1.10.1'
|
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||||
implementation "com.google.accompanist:accompanist-flowlayout:0.30.0"
|
implementation "com.google.accompanist:accompanist-flowlayout:0.30.0"
|
||||||
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
|
implementation 'androidx.compose.material:material-icons-extended:1.6.3'
|
||||||
debugImplementation "androidx.compose.ui:ui-tooling:1.8.3"
|
debugImplementation "androidx.compose.ui:ui-tooling:1.6.3"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.8.8'
|
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||||
implementation 'androidx.fragment:fragment-compose:1.8.8'
|
|
||||||
|
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||||
|
@ -201,12 +198,14 @@ dependencies {
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
// androidTestImplementation "android.arch.work:work-testing:$work_version"
|
// androidTestImplementation "android.arch.work:work-testing:$work_version"
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.6.2'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
|
|
||||||
|
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.0'
|
||||||
|
|
||||||
implementation 'org.mindrot:jbcrypt:0.4'
|
implementation 'org.mindrot:jbcrypt:0.4'
|
||||||
|
|
||||||
|
@ -214,11 +213,11 @@ dependencies {
|
||||||
|
|
||||||
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3'
|
implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
|
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
|
||||||
|
|
||||||
googleApiImplementation "com.android.billingclient:billing-ktx:8.0.0"
|
googleApiImplementation "com.android.billingclient:billing-ktx:6.2.0"
|
||||||
|
|
||||||
implementation('io.socket:socket.io-client:2.0.0') {
|
implementation('io.socket:socket.io-client:2.0.0') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
|
@ -230,5 +229,5 @@ dependencies {
|
||||||
|
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
|
|
||||||
api "com.squareup.wire:wire-runtime:5.3.5"
|
api "com.squareup.wire:wire-runtime:4.4.3"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -55,9 +55,7 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme">
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
tools:targetApi="tiramisu">
|
|
||||||
|
|
||||||
<!-- UI -->
|
<!-- UI -->
|
||||||
|
|
||||||
|
@ -126,14 +124,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.diagnose.exception.DiagnoseExceptionActivity"
|
|
||||||
android:theme="@style/AppTheme.Translucent"
|
|
||||||
android:exported="false"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:taskAffinity=":exception"
|
|
||||||
android:launchMode="singleTop" />
|
|
||||||
|
|
||||||
<!-- system integration -->
|
<!-- system integration -->
|
||||||
|
|
||||||
<receiver android:name=".integration.platform.android.receiver.BootReceiver" android:exported="false">
|
<receiver android:name=".integration.platform.android.receiver.BootReceiver" android:exported="false">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,9 +17,16 @@ package io.timelimit.android
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||||
|
|
||||||
class Application : Application() {
|
class Application : Application() {
|
||||||
// two legacy screens use small id numbers as they want; by running generateViewId() often enough,
|
// two legacy screens use small id numbers as they want; by running generateViewId() often enough,
|
||||||
// all ids that are harcoded this way are not returned from generateViewId
|
// all ids that are harcoded this way are not returned from generateViewId
|
||||||
init { (0..1024).forEach { _ -> View.generateViewId() } }
|
init { (0..1024).forEach { _ -> View.generateViewId() } }
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
AndroidThreeTen.init(this)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,7 +18,6 @@ package io.timelimit.android.coroutines
|
||||||
import io.timelimit.android.async.Threads
|
import io.timelimit.android.async.Threads
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
fun <T> runAsync(block: suspend CoroutineScope.() -> T) {
|
fun <T> runAsync(block: suspend CoroutineScope.() -> T) {
|
||||||
GlobalScope.launch (Dispatchers.Main) {
|
GlobalScope.launch (Dispatchers.Main) {
|
||||||
block()
|
block()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -50,7 +50,7 @@ interface Database {
|
||||||
fun widgetCategory(): WidgetCategoryDao
|
fun widgetCategory(): WidgetCategoryDao
|
||||||
fun widgetConfig(): WidgetConfigDao
|
fun widgetConfig(): WidgetConfigDao
|
||||||
|
|
||||||
fun <T> runInTransaction(body: Callable<T>): T
|
fun <T> runInTransaction(block: Callable<T>): T
|
||||||
fun <T> runInUnobservedTransaction(block: () -> T): T
|
fun <T> runInUnobservedTransaction(block: () -> T): T
|
||||||
fun registerWeakObserver(tables: Array<Table>, observer: WeakReference<Observer>)
|
fun registerWeakObserver(tables: Array<Table>, observer: WeakReference<Observer>)
|
||||||
fun registerTransactionCommitListener(listener: () -> Unit)
|
fun registerTransactionCommitListener(listener: () -> Unit)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -22,45 +22,45 @@ import io.timelimit.android.extensions.MinuteOfDay
|
||||||
|
|
||||||
object DatabaseMigrations {
|
object DatabaseMigrations {
|
||||||
private val MIGRATE_TO_V2 = object: Migration(1, 2) {
|
private val MIGRATE_TO_V2 = object: Migration(1, 2) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE device ADD COLUMN did_report_uninstall INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE device ADD COLUMN did_report_uninstall INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V3 = object: Migration(2, 3) {
|
private val MIGRATE_TO_V3 = object: Migration(2, 3) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE device ADD COLUMN is_user_kept_signed_in INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE device ADD COLUMN is_user_kept_signed_in INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V4 = object: Migration(3, 4) {
|
private val MIGRATE_TO_V4 = object: Migration(3, 4) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `user` ADD COLUMN `category_for_not_assigned_apps` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `category_for_not_assigned_apps` TEXT NOT NULL DEFAULT \"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V5 = object: Migration(4, 5) {
|
private val MIGRATE_TO_V5 = object: Migration(4, 5) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `parent_category_id` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `parent_category_id` TEXT NOT NULL DEFAULT \"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V6 = object: Migration(5, 6) {
|
private val MIGRATE_TO_V6 = object: Migration(5, 6) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `show_device_connected` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `show_device_connected` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V7 = object: Migration(6, 7) {
|
private val MIGRATE_TO_V7 = object: Migration(6, 7) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `default_user` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `default_user` TEXT NOT NULL DEFAULT \"\"")
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `default_user_timeout` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `default_user_timeout` INTEGER NOT NULL DEFAULT 0")
|
||||||
db.execSQL("ALTER TABLE `user` ADD COLUMN `relax_primary_device` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `relax_primary_device` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V8 = object: Migration(7, 8) {
|
private val MIGRATE_TO_V8 = object: Migration(7, 8) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
||||||
|
@ -68,14 +68,14 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V9 = object: Migration(8, 9) {
|
private val MIGRATE_TO_V9 = object: Migration(8, 9) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `did_reboot` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `did_reboot` INTEGER NOT NULL DEFAULT 0")
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `consider_reboot_manipulation` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `consider_reboot_manipulation` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V10 = object: Migration(9, 10) {
|
private val MIGRATE_TO_V10 = object: Migration(9, 10) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
||||||
|
@ -83,40 +83,40 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V11 = object: Migration(10, 11) {
|
private val MIGRATE_TO_V11 = object: Migration(10, 11) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `user` ADD COLUMN `mail_notification_flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `mail_notification_flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V12 = object: Migration(11, 12) {
|
private val MIGRATE_TO_V12 = object: Migration(11, 12) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `block_all_notifications` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `block_all_notifications` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V13 = object: Migration(12, 13) {
|
private val MIGRATE_TO_V13 = object: Migration(12, 13) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `current_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `current_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"")
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `highest_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `highest_overlay_permission` TEXT NOT NULL DEFAULT \"not granted\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V14 = object: Migration(13, 14) {
|
private val MIGRATE_TO_V14 = object: Migration(13, 14) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `current_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `current_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `was_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `was_accessibility_service_permission` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V15 = object: Migration(14, 15) {
|
private val MIGRATE_TO_V15 = object: Migration(14, 15) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE `app_activity` (`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`))")
|
database.execSQL("CREATE TABLE `app_activity` (`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`))")
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `enable_activity_level_blocking` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `enable_activity_level_blocking` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V16 = object: Migration(15, 16) {
|
private val MIGRATE_TO_V16 = object: Migration(15, 16) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
||||||
|
@ -124,20 +124,20 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V17 = object: Migration(16, 17) {
|
private val MIGRATE_TO_V17 = object: Migration(16, 17) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `notification` (`type` INTEGER NOT NULL, `id` TEXT NOT NULL, `first_notify_time` INTEGER NOT NULL, `dismissed` INTEGER NOT NULL, PRIMARY KEY(`type`, `id`))")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `notification` (`type` INTEGER NOT NULL, `id` TEXT NOT NULL, `first_notify_time` INTEGER NOT NULL, `dismissed` INTEGER NOT NULL, PRIMARY KEY(`type`, `id`))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V18 = object: Migration(17, 18) {
|
private val MIGRATE_TO_V18 = object: Migration(17, 18) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `q_or_later` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `q_or_later` INTEGER NOT NULL DEFAULT 0")
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `time_warnings` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `time_warnings` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V19 = object: Migration(18, 19) {
|
private val MIGRATE_TO_V19 = object: Migration(18, 19) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
||||||
|
@ -145,44 +145,44 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V20 = object: Migration(19, 20) {
|
private val MIGRATE_TO_V20 = object: Migration(19, 20) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `allowed_contact` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `phone` TEXT NOT NULL)")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `allowed_contact` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `phone` TEXT NOT NULL)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V21 = object: Migration(20, 21) {
|
private val MIGRATE_TO_V21 = object: Migration(20, 21) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `device` ADD COLUMN `had_manipulation_flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `device` ADD COLUMN `had_manipulation_flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V22 = object: Migration(21, 22) {
|
private val MIGRATE_TO_V22 = object: Migration(21, 22) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `user` ADD COLUMN `blocked_times` TEXT NOT NULL DEFAULT \"\"")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `blocked_times` TEXT NOT NULL DEFAULT \"\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V23 = object: Migration(22, 23) {
|
private val MIGRATE_TO_V23 = object: Migration(22, 23) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_charging` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_charging` INTEGER NOT NULL DEFAULT 0")
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_mobile` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `min_battery_mobile` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V24 = object: Migration(23, 24) {
|
private val MIGRATE_TO_V24 = object: Migration(23, 24) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `temporarily_blocked_end_time` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `temporarily_blocked_end_time` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V25 = object: Migration(24, 25) {
|
private val MIGRATE_TO_V25 = object: Migration(24, 25) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `sort` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `sort` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V26 = object: Migration(25, 26) {
|
private val MIGRATE_TO_V26 = object: Migration(25, 26) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// this is empty
|
// this is empty
|
||||||
//
|
//
|
||||||
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
// a new possible enum value was added, the version upgrade enables the downgrade mechanism
|
||||||
|
@ -190,152 +190,152 @@ object DatabaseMigrations {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V27 = object: Migration(26, 27) {
|
private val MIGRATE_TO_V27 = object: Migration(26, 27) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `extra_time_day` INTEGER NOT NULL DEFAULT -1")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `extra_time_day` INTEGER NOT NULL DEFAULT -1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V28 = object: Migration(27, 28) {
|
private val MIGRATE_TO_V28 = object: Migration(27, 28) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `user_key` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `user_key` (`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 )")
|
||||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_user_key_key` ON `user_key` (`key`)")
|
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_user_key_key` ON `user_key` (`key`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V29 = object: Migration(28, 29) {
|
private val MIGRATE_TO_V29 = object: Migration(28, 29) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `start_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MIN_START_MINUTE}")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `start_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MIN_START_MINUTE}")
|
||||||
db.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `end_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MAX_END_MINUTE}")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `end_minute_of_day` INTEGER NOT NULL DEFAULT ${TimeLimitRule.MAX_END_MINUTE}")
|
||||||
db.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `session_duration_milliseconds` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `session_duration_milliseconds` INTEGER NOT NULL DEFAULT 0")
|
||||||
db.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `session_pause_milliseconds` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `session_pause_milliseconds` INTEGER NOT NULL DEFAULT 0")
|
||||||
|
|
||||||
db.execSQL("ALTER TABLE `used_time` RENAME TO `used_time_old`")
|
database.execSQL("ALTER TABLE `used_time` RENAME TO `used_time_old`")
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `used_time` (`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`))")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `used_time` (`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`))")
|
||||||
db.execSQL("INSERT INTO `used_time` SELECT `day_of_epoch`, `used_time`, `category_id`, ${MinuteOfDay.MIN} AS `start_time_of_day`, ${MinuteOfDay.MAX} AS `end_time_of_day` FROM `used_time_old`")
|
database.execSQL("INSERT INTO `used_time` SELECT `day_of_epoch`, `used_time`, `category_id`, ${MinuteOfDay.MIN} AS `start_time_of_day`, ${MinuteOfDay.MAX} AS `end_time_of_day` FROM `used_time_old`")
|
||||||
db.execSQL("DROP TABLE `used_time_old`")
|
database.execSQL("DROP TABLE `used_time_old`")
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `session_duration` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `session_duration` (`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 )")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `session_duration_index_category_id` ON `session_duration` (`category_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `session_duration_index_category_id` ON `session_duration` (`category_id`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V30 = object: Migration(29, 30) {
|
private val MIGRATE_TO_V30 = object: Migration(29, 30) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `user` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V31 = object: Migration(30, 31) {
|
private val MIGRATE_TO_V31 = object: Migration(30, 31) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `user_limit_login_category` (`user_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, 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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `user_limit_login_category` (`user_id` TEXT NOT NULL, `category_id` TEXT NOT NULL, 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 )")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `user_limit_login_category_index_category_id` ON `user_limit_login_category` (`category_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `user_limit_login_category_index_category_id` ON `user_limit_login_category` (`category_id`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V32 = object: Migration(31, 32) {
|
private val MIGRATE_TO_V32 = object: Migration(31, 32) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `category_network_id` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `category_network_id` (`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 )")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V33 = object: Migration(32, 33) {
|
private val MIGRATE_TO_V33 = object: Migration(32, 33) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `disable_limits_until` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `disable_limits_until` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V34 = object: Migration(33, 34) {
|
private val MIGRATE_TO_V34 = object: Migration(33, 34) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `child_task` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `child_task` (`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 )")
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `tasks_version` TEXT NOT NULL DEFAULT ''")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `tasks_version` TEXT NOT NULL DEFAULT ''")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V35 = object: Migration(34, 35) {
|
private val MIGRATE_TO_V35 = object: Migration(34, 35) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `per_day` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `per_day` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V36 = object: Migration(35, 36) {
|
private val MIGRATE_TO_V36 = object: Migration(35, 36) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `user_limit_login_category` ADD COLUMN pre_block_duration INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `user_limit_login_category` ADD COLUMN pre_block_duration INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V37 = object: Migration(36, 37) {
|
private val MIGRATE_TO_V37 = object: Migration(36, 37) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE `category` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE `category` ADD COLUMN `flags` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V38 = object: Migration(37, 38) {
|
private val MIGRATE_TO_V38 = object: Migration(37, 38) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE category ADD COLUMN block_notification_delay INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE category ADD COLUMN block_notification_delay INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V39 = object: Migration(38, 39) {
|
private val MIGRATE_TO_V39 = object: Migration(38, 39) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// nothing to do, there was just a new config item type added
|
// nothing to do, there was just a new config item type added
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V40 = object: Migration(39, 40) {
|
private val MIGRATE_TO_V40 = object: Migration(39, 40) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `category_time_warning` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `category_time_warning` (`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 )")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V41 = object: Migration(40, 41) {
|
private val MIGRATE_TO_V41 = object: Migration(40, 41) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
// nothing to do, there was just a new config item type added
|
// nothing to do, there was just a new config item type added
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V42 = object: Migration(41, 42) {
|
private val MIGRATE_TO_V42 = object: Migration(41, 42) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("ALTER TABLE device ADD COLUMN manipulation_flags INTEGER NOT NULL DEFAULT 0")
|
database.execSQL("ALTER TABLE device ADD COLUMN manipulation_flags INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TP_V43 = object: Migration(42, 43) {
|
private val MIGRATE_TP_V43 = object: Migration(42, 43) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_metadata` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_metadata` (`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 )")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_metadata_device_id` ON `crypt_container_metadata` (`device_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_metadata_device_id` ON `crypt_container_metadata` (`device_id`)")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_metadata_category_id` ON `crypt_container_metadata` (`category_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_metadata_category_id` ON `crypt_container_metadata` (`category_id`)")
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_data` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_data` (`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 )")
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_pending_key_request` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_pending_key_request` (`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 )")
|
||||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_crypt_container_pending_key_request_request_sequence_id` ON `crypt_container_pending_key_request` (`request_sequence_id`)")
|
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_crypt_container_pending_key_request_request_sequence_id` ON `crypt_container_pending_key_request` (`request_sequence_id`)")
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_key_result` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `crypt_container_key_result` (`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 )")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_key_result_request_sequence_id` ON `crypt_container_key_result` (`request_sequence_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_key_result_request_sequence_id` ON `crypt_container_key_result` (`request_sequence_id`)")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_key_result_device_id` ON `crypt_container_key_result` (`device_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_crypt_container_key_result_device_id` ON `crypt_container_key_result` (`device_id`)")
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `device_public_key` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `device_public_key` (`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 )")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val MIGRATE_TO_V44 = object: Migration(43, 44) {
|
private val MIGRATE_TO_V44 = object: Migration(43, 44) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `user_u2f_key` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `user_u2f_key` (`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 )")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_user_u2f_key_user_id` ON `user_u2f_key` (`user_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_user_u2f_key_user_id` ON `user_u2f_key` (`user_id`)")
|
||||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_user_u2f_key_key_handle_public_key` ON `user_u2f_key` (`key_handle`, `public_key`)")
|
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_user_u2f_key_key_handle_public_key` ON `user_u2f_key` (`key_handle`, `public_key`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V45 = object: Migration(44, 45) {
|
val MIGRATE_TO_V45 = object: Migration(44, 45) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `widget_category` (`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 )")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `widget_category` (`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 )")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_widget_category_category_id` ON `widget_category` (`category_id`)")
|
database.execSQL("CREATE INDEX IF NOT EXISTS `index_widget_category_category_id` ON `widget_category` (`category_id`)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATE_TO_V46 = object: Migration(45, 46) {
|
val MIGRATE_TO_V46 = object: Migration(45, 46) {
|
||||||
override fun migrate(db: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE IF NOT EXISTS `widget_config` (`widget_id` INTEGER NOT NULL, `translucent` INTEGER NOT NULL, PRIMARY KEY(`widget_id`))")
|
database.execSQL("CREATE TABLE IF NOT EXISTS `widget_config` (`widget_id` INTEGER NOT NULL, `translucent` INTEGER NOT NULL, PRIMARY KEY(`widget_id`))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,9 @@ import java.util.concurrent.TimeUnit
|
||||||
UserU2FKey::class,
|
UserU2FKey::class,
|
||||||
WidgetCategory::class,
|
WidgetCategory::class,
|
||||||
WidgetConfig::class
|
WidgetConfig::class
|
||||||
], version = 49, autoMigrations = [
|
], version = 48, autoMigrations = [
|
||||||
AutoMigration(from = 46, to = 47),
|
AutoMigration(from = 46, to = 47),
|
||||||
AutoMigration(from = 47, to = 48),
|
AutoMigration(from = 47, to = 48)
|
||||||
AutoMigration(from = 48, to = 49)
|
|
||||||
])
|
])
|
||||||
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
|
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -140,10 +139,6 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
|
||||||
"endTransaction() is deprecated",
|
|
||||||
replaceWith = ReplaceWith("runInTransaction(Runnable)")
|
|
||||||
)
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
override fun endTransaction() {
|
override fun endTransaction() {
|
||||||
openHelper.writableDatabase.endTransaction()
|
openHelper.writableDatabase.endTransaction()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,7 +19,6 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.RoomWarnings
|
|
||||||
import io.timelimit.android.data.model.UsedTimeItem
|
import io.timelimit.android.data.model.UsedTimeItem
|
||||||
import io.timelimit.android.data.model.UsedTimeListItem
|
import io.timelimit.android.data.model.UsedTimeListItem
|
||||||
import io.timelimit.android.livedata.ignoreUnchanged
|
import io.timelimit.android.livedata.ignoreUnchanged
|
||||||
|
@ -68,14 +67,10 @@ abstract class UsedTimeDao {
|
||||||
abstract fun getAllUsedTimeItemsSync(): List<UsedTimeItem>
|
abstract fun getAllUsedTimeItemsSync(): List<UsedTimeItem>
|
||||||
|
|
||||||
// breaking it into multiple lines causes issues during compilation ...
|
// breaking it into multiple lines causes issues during compilation ...
|
||||||
// this warns about an unused column, but this column is used in the ORDER BY
|
|
||||||
@SuppressWarnings(RoomWarnings.QUERY_MISMATCH)
|
|
||||||
@Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.id = :categoryId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.id = :categoryId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId")
|
@Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.id = :categoryId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.id = :categoryId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId")
|
||||||
abstract fun getUsedTimeListItemsByCategoryId(categoryId: String): Flow<List<UsedTimeListItem>>
|
abstract fun getUsedTimeListItemsByCategoryId(categoryId: String): Flow<List<UsedTimeListItem>>
|
||||||
|
|
||||||
// breaking it into multiple lines causes issues during compilation ...
|
// breaking it into multiple lines causes issues during compilation ...
|
||||||
// this warns about an unused column, but this column is used in the ORDER BY
|
|
||||||
@SuppressWarnings(RoomWarnings.QUERY_MISMATCH)
|
|
||||||
@Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.child_id = :userId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.child_id = :userId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId")
|
@Query("SELECT 2 AS type, start_time_of_day AS startMinuteOfDay, end_time_of_day AS endMinuteOfDay, used_time AS duration, day_of_epoch AS day, NULL AS lastUsage, NULL AS maxSessionDuration, NULL AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM used_time JOIN category ON (used_time.category_id = category.id) WHERE category.child_id = :userId UNION ALL SELECT 1 AS type, start_minute_of_day AS startMinuteOfDay, end_minute_of_day AS endMinuteOfDay, last_session_duration AS duration, NULL AS day, last_usage AS lastUsage, max_session_duration AS maxSessionDuration, session_pause_duration AS pauseDuration, category.id AS categoryId, category.title AS categoryTitle FROM session_duration JOIN category ON (session_duration.category_id = category.id) WHERE category.child_id = :userId ORDER BY type, day DESC, lastUsage DESC, startMinuteOfDay, endMinuteOfDay, categoryId")
|
||||||
abstract fun getUsedTimeListItemsByUserId(userId: String): Flow<List<UsedTimeListItem>>
|
abstract fun getUsedTimeListItemsByUserId(userId: String): Flow<List<UsedTimeListItem>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -41,7 +41,7 @@ data class ChildTask(
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@ColumnInfo(name = "task_id")
|
@ColumnInfo(name = "task_id")
|
||||||
val taskId: String,
|
val taskId: String,
|
||||||
@ColumnInfo(name = "category_id", index = true)
|
@ColumnInfo(name = "category_id")
|
||||||
val categoryId: String,
|
val categoryId: String,
|
||||||
@ColumnInfo(name = "task_title")
|
@ColumnInfo(name = "task_title")
|
||||||
val taskTitle: String,
|
val taskTitle: String,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -295,7 +295,4 @@ object ExperimentalFlags {
|
||||||
|
|
||||||
object ConsentFlags {
|
object ConsentFlags {
|
||||||
const val APP_LIST_SYNC = 1L
|
const val APP_LIST_SYNC = 1L
|
||||||
|
|
||||||
// this is used internally
|
|
||||||
const val BLOCK_USER_SWITCH_BY_DEFAULT = 2L
|
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -73,5 +73,4 @@ data class DeviceRelatedData (
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isExperimentalFlagSetSync(flags: Long) = (experimentalFlags and flags) == flags
|
fun isExperimentalFlagSetSync(flags: Long) = (experimentalFlags and flags) == flags
|
||||||
fun isConsentFlagSet(flags: Long) = (consentFlags and flags) == flags
|
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.date
|
package io.timelimit.android.date
|
||||||
|
|
||||||
import java.time.DayOfWeek
|
import org.threeten.bp.DayOfWeek
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class DateInTimezone(val dayOfWeek: Int, val dayOfEpoch: Int, val localDate: LocalDate) {
|
data class DateInTimezone(val dayOfWeek: Int, val dayOfEpoch: Int, val localDate: LocalDate) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.extensions
|
package io.timelimit.android.extensions
|
||||||
|
|
||||||
import java.time.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
|
|
||||||
fun LocalDateTime.toInstant(zone: ZoneId) = toInstant(zone.rules.getOffset(this))
|
fun LocalDateTime.toInstant(zone: ZoneId) = toInstant(zone.rules.getOffset(this))
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -25,7 +25,4 @@ interface DeviceOwnerApi {
|
||||||
fun setOrganizationName(name: String)
|
fun setOrganizationName(name: String)
|
||||||
|
|
||||||
fun transferOwnership(packageName: String, dryRun: Boolean = false)
|
fun transferOwnership(packageName: String, dryRun: Boolean = false)
|
||||||
|
|
||||||
// returns true on success; never throws
|
|
||||||
fun grantLocationAccess(): Boolean
|
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -56,7 +56,6 @@ abstract class PlatformIntegration(
|
||||||
abstract fun setShowNotificationToRevokeTemporarilyAllowedApps(show: Boolean)
|
abstract fun setShowNotificationToRevokeTemporarilyAllowedApps(show: Boolean)
|
||||||
abstract fun showRemoteResetNotification()
|
abstract fun showRemoteResetNotification()
|
||||||
abstract fun showTimeWarningNotification(title: String, text: String)
|
abstract fun showTimeWarningNotification(title: String, text: String)
|
||||||
abstract fun showExtraTimeStartedNotification(categoryId: String, categoryTitle: String)
|
|
||||||
// returns package names for which it was set
|
// returns package names for which it was set
|
||||||
abstract fun setSuspendedApps(packageNames: List<String>, suspend: Boolean): List<String>
|
abstract fun setSuspendedApps(packageNames: List<String>, suspend: Boolean): List<String>
|
||||||
abstract fun stopSuspendingForAllApps()
|
abstract fun stopSuspendingForAllApps()
|
||||||
|
@ -219,8 +218,7 @@ data class AppStatusMessage(
|
||||||
val title: String,
|
val title: String,
|
||||||
val text: String,
|
val text: String,
|
||||||
val subtext: String? = null,
|
val subtext: String? = null,
|
||||||
val showSwitchToDefaultUserOption: Boolean = false,
|
val showSwitchToDefaultUserOption: Boolean = false
|
||||||
val showErrorMessage: Boolean = false
|
|
||||||
): Parcelable
|
): Parcelable
|
||||||
|
|
||||||
data class BatteryStatus(
|
data class BatteryStatus(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.integration.platform.android
|
package io.timelimit.android.integration.platform.android
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.admin.DeviceAdminReceiver
|
import android.app.admin.DeviceAdminReceiver
|
||||||
import android.app.admin.DevicePolicyManager
|
import android.app.admin.DevicePolicyManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
@ -71,7 +70,6 @@ class AndroidDeviceOwnerApi(
|
||||||
override val delegations: List<DeviceOwnerApi.DelegationScope> = delegationList.map { it.second }
|
override val delegations: List<DeviceOwnerApi.DelegationScope> = delegationList.map { it.second }
|
||||||
|
|
||||||
override fun setDelegations(packageName: String, scopes: List<DeviceOwnerApi.DelegationScope>) {
|
override fun setDelegations(packageName: String, scopes: List<DeviceOwnerApi.DelegationScope>) {
|
||||||
if (BuildConfig.storeCompilant) throw IllegalStateException()
|
|
||||||
if (VERSION.SDK_INT <= VERSION_CODES.O) throw IllegalStateException()
|
if (VERSION.SDK_INT <= VERSION_CODES.O) throw IllegalStateException()
|
||||||
|
|
||||||
val resolvedScopes = scopes.map { scope ->
|
val resolvedScopes = scopes.map { scope ->
|
||||||
|
@ -90,7 +88,6 @@ class AndroidDeviceOwnerApi(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDelegations(): Map<String, List<DeviceOwnerApi.DelegationScope>> {
|
override fun getDelegations(): Map<String, List<DeviceOwnerApi.DelegationScope>> {
|
||||||
if (BuildConfig.storeCompilant) return emptyMap()
|
|
||||||
if (VERSION.SDK_INT <= VERSION_CODES.O) throw IllegalStateException()
|
if (VERSION.SDK_INT <= VERSION_CODES.O) throw IllegalStateException()
|
||||||
|
|
||||||
return delegationList.map { (scope, delegation) ->
|
return delegationList.map { (scope, delegation) ->
|
||||||
|
@ -111,7 +108,6 @@ class AndroidDeviceOwnerApi(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transferOwnership(packageName: String, dryRun: Boolean) {
|
override fun transferOwnership(packageName: String, dryRun: Boolean) {
|
||||||
if (BuildConfig.storeCompilant) throw IllegalStateException()
|
|
||||||
if (VERSION.SDK_INT < VERSION_CODES.P) throw IllegalStateException()
|
if (VERSION.SDK_INT < VERSION_CODES.P) throw IllegalStateException()
|
||||||
if (!devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) throw SecurityException()
|
if (!devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) throw SecurityException()
|
||||||
|
|
||||||
|
@ -128,25 +124,4 @@ class AndroidDeviceOwnerApi(
|
||||||
devicePolicyManager.setDelegatedScopes(componentName, packageName, emptyList())
|
devicePolicyManager.setDelegatedScopes(componentName, packageName, emptyList())
|
||||||
devicePolicyManager.transferOwnership(componentName, targetComponentName, null)
|
devicePolicyManager.transferOwnership(componentName, targetComponentName, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun grantLocationAccess(): Boolean {
|
|
||||||
if (BuildConfig.storeCompilant) return false
|
|
||||||
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) return false
|
|
||||||
if (!devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) return false
|
|
||||||
|
|
||||||
try {
|
|
||||||
return devicePolicyManager.setPermissionGrantState(
|
|
||||||
componentName, componentName.packageName, Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
|
||||||
)
|
|
||||||
} catch (ex: SecurityException) {
|
|
||||||
// set to default so that granting this manually is possible
|
|
||||||
devicePolicyManager.setPermissionGrantState(
|
|
||||||
componentName, componentName.packageName, Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -27,10 +27,6 @@ import io.timelimit.android.integration.platform.PlatformFeature
|
||||||
object AndroidFeatures {
|
object AndroidFeatures {
|
||||||
private const val FEATURE_ADB = "adb"
|
private const val FEATURE_ADB = "adb"
|
||||||
private const val FEATURE_CONFIG_PRIVATE_DNS = "dns"
|
private const val FEATURE_CONFIG_PRIVATE_DNS = "dns"
|
||||||
const val FEATURE_ADD_USER = "add_user"
|
|
||||||
const val FEATURE_USER_SWITCH = "user_switch"
|
|
||||||
private const val FEATURE_VPN = "vpn"
|
|
||||||
private const val FEATURE_UNKNOWN_SOURCES = "unknown_sources"
|
|
||||||
|
|
||||||
fun applyBlockedFeatures(features: Set<String>, policyManager: DevicePolicyManager, admin: ComponentName): Boolean {
|
fun applyBlockedFeatures(features: Set<String>, policyManager: DevicePolicyManager, admin: ComponentName): Boolean {
|
||||||
fun apply(feature: String, restriction: String) {
|
fun apply(feature: String, restriction: String) {
|
||||||
|
@ -44,18 +40,6 @@ object AndroidFeatures {
|
||||||
apply(FEATURE_CONFIG_PRIVATE_DNS, UserManager.DISALLOW_CONFIG_PRIVATE_DNS)
|
apply(FEATURE_CONFIG_PRIVATE_DNS, UserManager.DISALLOW_CONFIG_PRIVATE_DNS)
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(FEATURE_ADD_USER, UserManager.DISALLOW_ADD_USER)
|
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.P) {
|
|
||||||
apply(FEATURE_USER_SWITCH, UserManager.DISALLOW_USER_SWITCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(FEATURE_VPN, UserManager.DISALLOW_CONFIG_VPN)
|
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
|
|
||||||
apply(FEATURE_UNKNOWN_SOURCES, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,30 +60,6 @@ object AndroidFeatures {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.add(PlatformFeature(
|
|
||||||
id = FEATURE_ADD_USER,
|
|
||||||
title = context.getString(R.string.dummy_app_feature_add_user)
|
|
||||||
))
|
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.P) {
|
|
||||||
result.add(PlatformFeature(
|
|
||||||
id = FEATURE_USER_SWITCH,
|
|
||||||
title = context.getString(R.string.dummy_app_feature_switch_user)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
result.add(PlatformFeature(
|
|
||||||
id = FEATURE_VPN,
|
|
||||||
title = context.getString(R.string.dummy_app_feature_vpn)
|
|
||||||
))
|
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
|
|
||||||
result.add(PlatformFeature(
|
|
||||||
id = FEATURE_UNKNOWN_SOURCES,
|
|
||||||
title = context.getString(R.string.dummy_app_feature_unknown_sources)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.integration.platform.android
|
package io.timelimit.android.integration.platform.android
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
@ -41,7 +40,6 @@ import android.widget.Toast
|
||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import io.timelimit.android.BuildConfig
|
import io.timelimit.android.BuildConfig
|
||||||
|
@ -443,26 +441,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExtraTimeStartedNotification(categoryId: String, categoryTitle: String) {
|
|
||||||
NotificationChannels.createNotificationChannels(notificationManager, context)
|
|
||||||
|
|
||||||
notificationManager.notify(
|
|
||||||
categoryId,
|
|
||||||
NotificationIds.EXTRA_TIME_STARTED,
|
|
||||||
NotificationCompat.Builder(context, NotificationChannels.EXTRA_TIME_STARTED)
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_timelapse)
|
|
||||||
.setContentTitle(context.getString(R.string.notification_extra_time_started))
|
|
||||||
.setContentText(categoryTitle)
|
|
||||||
.setWhen(System.currentTimeMillis())
|
|
||||||
.setShowWhen(true)
|
|
||||||
.setLocalOnly(true)
|
|
||||||
.setAutoCancel(false)
|
|
||||||
.setOngoing(false)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun disableDeviceAdmin() {
|
override fun disableDeviceAdmin() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
if (policyManager.isDeviceOwnerApp(context.packageName)) {
|
if (policyManager.isDeviceOwnerApp(context.packageName)) {
|
||||||
|
@ -507,84 +485,21 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
||||||
|
|
||||||
if (enableLockdown) {
|
if (enableLockdown) {
|
||||||
// disable problematic features
|
// disable problematic features
|
||||||
|
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_ADD_USER)
|
||||||
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
|
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_SAFE_BOOT)
|
policyManager.addUserRestriction(deviceAdmin, UserManager.DISALLOW_SAFE_BOOT)
|
||||||
}
|
}
|
||||||
|
|
||||||
policyManager.getPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
).let {
|
|
||||||
try {
|
|
||||||
if (it == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
|
|
||||||
policyManager.setPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
if (ContextCompat.checkSelfPermission(
|
|
||||||
context,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
)
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
|
||||||
else
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (ex: SecurityException) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
policyManager.setPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.CALL_PHONE,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
|
||||||
)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
policyManager.setPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.POST_NOTIFICATIONS,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else /* disable lockdown */ {
|
} else /* disable lockdown */ {
|
||||||
// enable problematic features
|
// enable problematic features
|
||||||
|
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_ADD_USER)
|
||||||
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
|
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_FACTORY_RESET)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_SAFE_BOOT)
|
policyManager.clearUserRestriction(deviceAdmin, UserManager.DISALLOW_SAFE_BOOT)
|
||||||
}
|
}
|
||||||
|
|
||||||
policyManager.setPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
policyManager.setPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.CALL_PHONE,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
policyManager.setPermissionGrantState(
|
|
||||||
deviceAdmin,
|
|
||||||
context.packageName,
|
|
||||||
Manifest.permission.POST_NOTIFICATIONS,
|
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
enableSystemApps()
|
enableSystemApps()
|
||||||
stopSuspendingForAllApps()
|
stopSuspendingForAllApps()
|
||||||
setBlockedFeatures(emptySet())
|
setBlockedFeatures(emptySet())
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,7 +29,6 @@ import io.timelimit.android.logic.DefaultAppLogic
|
||||||
import io.timelimit.android.sync.actions.SignOutAtDeviceAction
|
import io.timelimit.android.sync.actions.SignOutAtDeviceAction
|
||||||
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
||||||
import io.timelimit.android.ui.MainActivity
|
import io.timelimit.android.ui.MainActivity
|
||||||
import io.timelimit.android.ui.diagnose.exception.DiagnoseExceptionActivity
|
|
||||||
import io.timelimit.android.ui.notification.NotificationAreaSync
|
import io.timelimit.android.ui.notification.NotificationAreaSync
|
||||||
|
|
||||||
class BackgroundActionService: Service() {
|
class BackgroundActionService: Service() {
|
||||||
|
@ -45,7 +44,7 @@ class BackgroundActionService: Service() {
|
||||||
fun prepareRevokeTemporarilyAllowed(context: Context) = Intent(context, BackgroundActionService::class.java)
|
fun prepareRevokeTemporarilyAllowed(context: Context) = Intent(context, BackgroundActionService::class.java)
|
||||||
.putExtra(ACTION, ACTION_REVOKE_TEMPORARILY_ALLOWED_APPS)
|
.putExtra(ACTION, ACTION_REVOKE_TEMPORARILY_ALLOWED_APPS)
|
||||||
|
|
||||||
fun getSwitchToDefaultUserIntent(context: Context): PendingIntent = PendingIntent.getService(
|
fun getSwitchToDefaultUserIntent(context: Context) = PendingIntent.getService(
|
||||||
context,
|
context,
|
||||||
PendingIntentIds.SWITCH_TO_DEFAULT_USER,
|
PendingIntentIds.SWITCH_TO_DEFAULT_USER,
|
||||||
Intent(context, BackgroundActionService::class.java)
|
Intent(context, BackgroundActionService::class.java)
|
||||||
|
@ -58,24 +57,14 @@ class BackgroundActionService: Service() {
|
||||||
.putExtra(EXTRA_NOTIFICATION_TYPE, type)
|
.putExtra(EXTRA_NOTIFICATION_TYPE, type)
|
||||||
.putExtra(EXTRA_NOTIFICATION_ID, id)
|
.putExtra(EXTRA_NOTIFICATION_ID, id)
|
||||||
|
|
||||||
fun getOpenAppIntent(context: Context): PendingIntent = PendingIntent.getActivity(
|
fun getOpenAppIntent(context: Context) = PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
PendingIntentIds.OPEN_MAIN_APP,
|
PendingIntentIds.OPEN_MAIN_APP,
|
||||||
Intent(context, MainActivity::class.java),
|
Intent(context, MainActivity::class.java),
|
||||||
PendingIntentIds.PENDING_INTENT_FLAGS
|
PendingIntentIds.PENDING_INTENT_FLAGS
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getOpenAppWithErrorIntent(context: Context): PendingIntent = PendingIntent.getActivities(
|
fun getSyncNotificationsPendingIntent(context: Context) = PendingIntent.getService(
|
||||||
context,
|
|
||||||
PendingIntentIds.OPEN_MAIN_APP_WITH_ERROR,
|
|
||||||
arrayOf(
|
|
||||||
Intent(context, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
||||||
Intent(context, DiagnoseExceptionActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
),
|
|
||||||
PendingIntentIds.PENDING_INTENT_FLAGS
|
|
||||||
)
|
|
||||||
|
|
||||||
fun getSyncNotificationsPendingIntent(context: Context): PendingIntent = PendingIntent.getService(
|
|
||||||
context,
|
context,
|
||||||
PendingIntentIds.SYNC_NOTIFICATIONS,
|
PendingIntentIds.SYNC_NOTIFICATIONS,
|
||||||
Intent(context, BackgroundActionService::class.java)
|
Intent(context, BackgroundActionService::class.java)
|
||||||
|
|
|
@ -86,12 +86,7 @@ class BackgroundService: Service() {
|
||||||
.setContentTitle(appStatusMessage.title)
|
.setContentTitle(appStatusMessage.title)
|
||||||
.setContentText(appStatusMessage.text)
|
.setContentText(appStatusMessage.text)
|
||||||
.setSubText(appStatusMessage.subtext)
|
.setSubText(appStatusMessage.subtext)
|
||||||
.setContentIntent(
|
.setContentIntent(BackgroundActionService.getOpenAppIntent(context))
|
||||||
if (appStatusMessage.showErrorMessage)
|
|
||||||
BackgroundActionService.getOpenAppWithErrorIntent(context)
|
|
||||||
else
|
|
||||||
BackgroundActionService.getOpenAppIntent(context)
|
|
||||||
)
|
|
||||||
.setWhen(0)
|
.setWhen(0)
|
||||||
.setShowWhen(false)
|
.setShowWhen(false)
|
||||||
.setSound(null)
|
.setSound(null)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -34,7 +34,6 @@ object NotificationIds {
|
||||||
const val WORKER_REPORT_UNINSTALL = 8
|
const val WORKER_REPORT_UNINSTALL = 8
|
||||||
const val WORKER_SYNC_BACKGROUND = 9
|
const val WORKER_SYNC_BACKGROUND = 9
|
||||||
const val NEW_DEVICE = 10
|
const val NEW_DEVICE = 10
|
||||||
const val EXTRA_TIME_STARTED = 11
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object NotificationChannels {
|
object NotificationChannels {
|
||||||
|
@ -48,7 +47,6 @@ object NotificationChannels {
|
||||||
const val TEMP_ALLOWED_APP = "temporarily allowed App"
|
const val TEMP_ALLOWED_APP = "temporarily allowed App"
|
||||||
const val APP_RESET = "app reset"
|
const val APP_RESET = "app reset"
|
||||||
const val NEW_DEVICE = "new device"
|
const val NEW_DEVICE = "new device"
|
||||||
const val EXTRA_TIME_STARTED = "extra time started"
|
|
||||||
|
|
||||||
private fun createAppStatusChannel(notificationManager: NotificationManager, context: Context) {
|
private fun createAppStatusChannel(notificationManager: NotificationManager, context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
@ -216,20 +214,6 @@ object NotificationChannels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createExtraTimeStartedNotificationChannel(notificationManager: NotificationManager, context: Context) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
notificationManager.createNotificationChannel(
|
|
||||||
NotificationChannel(
|
|
||||||
EXTRA_TIME_STARTED,
|
|
||||||
context.getString(R.string.notification_channel_extra_time_started_title),
|
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
|
||||||
).apply {
|
|
||||||
description = context.getString(R.string.notification_channel_extra_time_started_description)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createNotificationChannels(notificationManager: NotificationManager, context: Context) {
|
fun createNotificationChannels(notificationManager: NotificationManager, context: Context) {
|
||||||
createAppStatusChannel(notificationManager, context)
|
createAppStatusChannel(notificationManager, context)
|
||||||
createBlockedNotificationChannel(notificationManager, context)
|
createBlockedNotificationChannel(notificationManager, context)
|
||||||
|
@ -241,7 +225,6 @@ object NotificationChannels {
|
||||||
createTempAllowedAppChannel(notificationManager, context)
|
createTempAllowedAppChannel(notificationManager, context)
|
||||||
createAppResetChannel(notificationManager, context)
|
createAppResetChannel(notificationManager, context)
|
||||||
createNewDeviceChannel(notificationManager, context)
|
createNewDeviceChannel(notificationManager, context)
|
||||||
createExtraTimeStartedNotificationChannel(notificationManager, context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +237,6 @@ object PendingIntentIds {
|
||||||
const val OPEN_UPDATER = 6
|
const val OPEN_UPDATER = 6
|
||||||
const val U2F_NFC_DISCOVERY = 7
|
const val U2F_NFC_DISCOVERY = 7
|
||||||
const val U2F_USB_RESPONSE = 8
|
const val U2F_USB_RESPONSE = 8
|
||||||
const val OPEN_MAIN_APP_WITH_ERROR = 9
|
|
||||||
val DYNAMIC_NOTIFICATION_RANGE = 100..10000
|
val DYNAMIC_NOTIFICATION_RANGE = 100..10000
|
||||||
|
|
||||||
val PENDING_INTENT_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val PENDING_INTENT_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -184,7 +184,7 @@ class LollipopForegroundAppHelper(context: Context) : UsageStatsForegroundAppHel
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doesActivityExistAsAlias(app: ForegroundApp) = try {
|
private fun doesActivityExistAsAlias(app: ForegroundApp) = try {
|
||||||
packageManager.getPackageInfo(app.packageName, PackageManager.GET_ACTIVITIES).activities?.find {
|
packageManager.getPackageInfo(app.packageName, PackageManager.GET_ACTIVITIES).activities.find {
|
||||||
it.enabled && it.targetActivity == app.activityName
|
it.enabled && it.targetActivity == app.activityName
|
||||||
} != null
|
} != null
|
||||||
} catch (ex: PackageManager.NameNotFoundException) {
|
} catch (ex: PackageManager.NameNotFoundException) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -153,10 +153,6 @@ class DummyIntegration(
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showExtraTimeStartedNotification(categoryId: String, categoryTitle: String) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun disableDeviceAdmin() {
|
override fun disableDeviceAdmin() {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
@ -207,7 +203,5 @@ class DummyIntegration(
|
||||||
override fun setOrganizationName(name: String) = throw SecurityException()
|
override fun setOrganizationName(name: String) = throw SecurityException()
|
||||||
|
|
||||||
override fun transferOwnership(packageName: String, dryRun: Boolean) = throw IllegalStateException("unsupported operation")
|
override fun transferOwnership(packageName: String, dryRun: Boolean) = throw IllegalStateException("unsupported operation")
|
||||||
|
|
||||||
override fun grantLocationAccess(): Boolean = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -65,11 +65,6 @@ class AppSetupLogic(private val appLogic: AppLogic) {
|
||||||
appLogic.database.deleteAllData()
|
appLogic.database.deleteAllData()
|
||||||
|
|
||||||
appLogic.database.config().setCustomServerUrlSync(customServerUrl)
|
appLogic.database.config().setCustomServerUrlSync(customServerUrl)
|
||||||
|
|
||||||
appLogic.database.config().setConsentFlagSync(
|
|
||||||
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run {
|
run {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -436,9 +436,6 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
||||||
val oldRemainingTime = nowRemaining.includingExtraTime - timeToSubtractForCategory
|
val oldRemainingTime = nowRemaining.includingExtraTime - timeToSubtractForCategory
|
||||||
val newRemainingTime = oldRemainingTime - timeToSubtract
|
val newRemainingTime = oldRemainingTime - timeToSubtract
|
||||||
|
|
||||||
val oldRemainingNonExtraTime = nowRemaining.default - timeToSubtractForCategory
|
|
||||||
val newRemainingNonExtraTime = oldRemainingNonExtraTime - timeToSubtract
|
|
||||||
|
|
||||||
val commitedSessionDuration = handling.remainingSessionDuration
|
val commitedSessionDuration = handling.remainingSessionDuration
|
||||||
val oldSessionDuration = handling.remainingSessionDuration?.let { it - timeToSubtractForCategory }
|
val oldSessionDuration = handling.remainingSessionDuration?.let { it - timeToSubtractForCategory }
|
||||||
|
|
||||||
|
@ -479,10 +476,6 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldRemainingNonExtraTime > 0 && newRemainingNonExtraTime <= 0) {
|
|
||||||
appLogic.platformIntegration.showExtraTimeStartedNotification(categoryId, category.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldSessionDuration != null) {
|
if (oldSessionDuration != null) {
|
||||||
val newSessionDuration = oldSessionDuration - timeToSubtract
|
val newSessionDuration = oldSessionDuration - timeToSubtract
|
||||||
|
|
||||||
|
@ -904,8 +897,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
||||||
appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage(
|
appLogic.platformIntegration.setAppStatusMessage(AppStatusMessage(
|
||||||
appLogic.context.getString(R.string.background_logic_error),
|
appLogic.context.getString(R.string.background_logic_error),
|
||||||
appLogic.context.getString(R.string.background_logic_error_internal),
|
appLogic.context.getString(R.string.background_logic_error_internal),
|
||||||
showSwitchToDefaultUserOption = deviceRelatedData.canSwitchToDefaultUser,
|
showSwitchToDefaultUserOption = deviceRelatedData.canSwitchToDefaultUser
|
||||||
showErrorMessage = true
|
|
||||||
))
|
))
|
||||||
appLogic.platformIntegration.setShowBlockingOverlay(false)
|
appLogic.platformIntegration.setShowBlockingOverlay(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,12 +19,10 @@ import io.timelimit.android.async.Threads
|
||||||
import io.timelimit.android.data.invalidation.Observer
|
import io.timelimit.android.data.invalidation.Observer
|
||||||
import io.timelimit.android.data.invalidation.Table
|
import io.timelimit.android.data.invalidation.Table
|
||||||
import io.timelimit.android.data.model.CategoryApp
|
import io.timelimit.android.data.model.CategoryApp
|
||||||
import io.timelimit.android.data.model.ConsentFlags
|
|
||||||
import io.timelimit.android.data.model.ExperimentalFlags
|
import io.timelimit.android.data.model.ExperimentalFlags
|
||||||
import io.timelimit.android.data.model.UserType
|
import io.timelimit.android.data.model.UserType
|
||||||
import io.timelimit.android.data.model.derived.UserRelatedData
|
import io.timelimit.android.data.model.derived.UserRelatedData
|
||||||
import io.timelimit.android.integration.platform.ProtectionLevel
|
import io.timelimit.android.integration.platform.ProtectionLevel
|
||||||
import io.timelimit.android.integration.platform.android.AndroidFeatures
|
|
||||||
import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
|
import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
|
||||||
import io.timelimit.android.logic.blockingreason.CategoryHandlingCache
|
import io.timelimit.android.logic.blockingreason.CategoryHandlingCache
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
@ -109,23 +107,12 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
|
||||||
val hasManagedFeatures = featureCategoryApps.isNotEmpty()
|
val hasManagedFeatures = featureCategoryApps.isNotEmpty()
|
||||||
val enableBlocking = isRestrictedUser && (enableBlockingAtSystemLevel || hasManagedFeatures)
|
val enableBlocking = isRestrictedUser && (enableBlockingAtSystemLevel || hasManagedFeatures)
|
||||||
|
|
||||||
val blockUserSwitchByDefault =
|
|
||||||
userAndDeviceRelatedData?.deviceRelatedData?.isConsentFlagSet(ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT) == true
|
|
||||||
&& userAndDeviceRelatedData.userRelatedData?.user?.type == UserType.Child
|
|
||||||
|
|
||||||
val featureToAllowDefaults = mapOf(
|
|
||||||
AndroidFeatures.FEATURE_ADD_USER to false,
|
|
||||||
AndroidFeatures.FEATURE_USER_SWITCH to !blockUserSwitchByDefault
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!enableBlocking) {
|
if (!enableBlocking) {
|
||||||
lastDefaultCategory = null
|
lastDefaultCategory = null
|
||||||
lastAllowedCategoryList = emptySet()
|
lastAllowedCategoryList = emptySet()
|
||||||
lastCategoryApps = emptyList()
|
lastCategoryApps = emptyList()
|
||||||
applySuspendedApps(emptyList())
|
applySuspendedApps(emptyList())
|
||||||
applyBlockedFeatures(
|
applyBlockedFeatures(emptySet())
|
||||||
featureToAllowDefaults.filter { !it.value }.map { it.key }.toSet()
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -204,15 +191,9 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
|
||||||
val deviceSpecificFeatureIdentifiers = deviceSpecificFeatures.map { it.appSpecifierString }.toSet()
|
val deviceSpecificFeatureIdentifiers = deviceSpecificFeatures.map { it.appSpecifierString }.toSet()
|
||||||
val globalFeatures = featureCategoryApps.filter { !deviceSpecificFeatureIdentifiers.contains(it.appSpecifierString) }
|
val globalFeatures = featureCategoryApps.filter { !deviceSpecificFeatureIdentifiers.contains(it.appSpecifierString) }
|
||||||
val effectiveFeatures = deviceSpecificFeatures + globalFeatures
|
val effectiveFeatures = deviceSpecificFeatures + globalFeatures
|
||||||
|
val featuresToBlock = effectiveFeatures.filter { !categoryIdsToAllow.contains(it.categoryId) }
|
||||||
val featuresToAllow = featureToAllowDefaults + effectiveFeatures.associate {
|
.map { it.appSpecifierString.substring(DummyApps.FEATURE_APP_PREFIX.length) }
|
||||||
Pair(
|
.toSet()
|
||||||
it.appSpecifierString.substring(DummyApps.FEATURE_APP_PREFIX.length),
|
|
||||||
categoryIdsToAllow.contains(it.categoryId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val featuresToBlock = featuresToAllow.filter { !it.value }.map { it.key }.toSet()
|
|
||||||
|
|
||||||
applySuspendedApps(appsToBlock)
|
applySuspendedApps(appsToBlock)
|
||||||
applyBlockedFeatures(featuresToBlock)
|
applyBlockedFeatures(featuresToBlock)
|
||||||
|
|
|
@ -29,7 +29,7 @@ import io.timelimit.android.logic.RemainingSessionDuration
|
||||||
import io.timelimit.android.logic.RemainingTime
|
import io.timelimit.android.logic.RemainingTime
|
||||||
import io.timelimit.android.sync.actions.AddUsedTimeActionItemAdditionalCountingSlot
|
import io.timelimit.android.sync.actions.AddUsedTimeActionItemAdditionalCountingSlot
|
||||||
import io.timelimit.android.sync.actions.AddUsedTimeActionItemSessionDurationLimitSlot
|
import io.timelimit.android.sync.actions.AddUsedTimeActionItemSessionDurationLimitSlot
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class CategoryItselfHandling (
|
data class CategoryItselfHandling (
|
||||||
|
|
|
@ -43,7 +43,6 @@ import java.io.OutputStreamWriter
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.KeyStore.PrivateKeyEntry
|
import java.security.KeyStore.PrivateKeyEntry
|
||||||
import java.security.ProviderException
|
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -146,11 +145,7 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi {
|
||||||
private suspend fun sendMailLoginCode(mail: String, locale: String, deviceAuthToken: String?, skipDeviceVerification: Boolean): String = withDeviceVerification (enable = !skipDeviceVerification) { client ->
|
private suspend fun sendMailLoginCode(mail: String, locale: String, deviceAuthToken: String?, skipDeviceVerification: Boolean): String = withDeviceVerification (enable = !skipDeviceVerification) { client ->
|
||||||
postJsonRequest(
|
postJsonRequest(
|
||||||
"auth/send-mail-login-code-v2",
|
"auth/send-mail-login-code-v2",
|
||||||
client = client,
|
client = client
|
||||||
transformRequest = { it
|
|
||||||
.header("X-Client-Package", BuildConfig.APPLICATION_ID)
|
|
||||||
.header("X-Client-Version", BuildConfig.VERSION_NAME)
|
|
||||||
}
|
|
||||||
) { writer ->
|
) { writer ->
|
||||||
writer.beginObject()
|
writer.beginObject()
|
||||||
writer.name(MAIL).value(mail)
|
writer.name(MAIL).value(mail)
|
||||||
|
@ -663,17 +658,10 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi {
|
||||||
private suspend fun postJsonRequest(
|
private suspend fun postJsonRequest(
|
||||||
path: String,
|
path: String,
|
||||||
client: OkHttpClient = httpClient,
|
client: OkHttpClient = httpClient,
|
||||||
transformRequest: (Request.Builder) -> Request.Builder = { it },
|
requestBody: (writer: JsonWriter) -> Unit
|
||||||
requestBody: (writer: JsonWriter) -> Unit,
|
|
||||||
): Response {
|
): Response {
|
||||||
if (!sendContentLength) {
|
if (!sendContentLength) {
|
||||||
val response = postJsonRequest(
|
val response = postJsonRequest(path, requestBody, transmitContentLength = false, client = client)
|
||||||
path,
|
|
||||||
requestBody,
|
|
||||||
transmitContentLength = false,
|
|
||||||
client = client,
|
|
||||||
transformRequest = transformRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
if (response.code != 411) return response
|
if (response.code != 411) return response
|
||||||
|
|
||||||
|
@ -682,21 +670,14 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi {
|
||||||
sendContentLength = true
|
sendContentLength = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return postJsonRequest(
|
return postJsonRequest(path, requestBody, transmitContentLength = true, client = client)
|
||||||
path,
|
|
||||||
requestBody,
|
|
||||||
transmitContentLength = true,
|
|
||||||
client = client,
|
|
||||||
transformRequest = transformRequest
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun postJsonRequest(
|
private suspend fun postJsonRequest(
|
||||||
path: String,
|
path: String,
|
||||||
requestBody: (writer: JsonWriter) -> Unit,
|
requestBody: (writer: JsonWriter) -> Unit,
|
||||||
transmitContentLength: Boolean,
|
transmitContentLength: Boolean,
|
||||||
client: OkHttpClient = httpClient,
|
client: OkHttpClient = httpClient
|
||||||
transformRequest: (Request.Builder) -> Request.Builder = { it }
|
|
||||||
): Response {
|
): Response {
|
||||||
val body = createJsonRequestBody(requestBody, transmitContentLength)
|
val body = createJsonRequestBody(requestBody, transmitContentLength)
|
||||||
|
|
||||||
|
@ -705,7 +686,6 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi {
|
||||||
.url("$endpointWithoutSlashAtEnd/$path")
|
.url("$endpointWithoutSlashAtEnd/$path")
|
||||||
.post(body)
|
.post(body)
|
||||||
.header("Content-Encoding", "gzip")
|
.header("Content-Encoding", "gzip")
|
||||||
.let { transformRequest(it) }
|
|
||||||
.build()
|
.build()
|
||||||
).waitForResponse()
|
).waitForResponse()
|
||||||
}
|
}
|
||||||
|
@ -717,34 +697,28 @@ class HttpServerApi(private val endpointWithoutSlashAtEnd: String): ServerApi {
|
||||||
val keyId = "temp-" + UUID.randomUUID().toString()
|
val keyId = "temp-" + UUID.randomUUID().toString()
|
||||||
val now = getTimeInMillis()
|
val now = getTimeInMillis()
|
||||||
|
|
||||||
try {
|
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, keyStoreName)
|
||||||
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, keyStoreName)
|
.also {
|
||||||
.also {
|
it.initialize(
|
||||||
it.initialize(
|
KeyGenParameterSpec.Builder(
|
||||||
KeyGenParameterSpec.Builder(
|
keyId,
|
||||||
keyId,
|
KeyProperties.PURPOSE_SIGN
|
||||||
KeyProperties.PURPOSE_SIGN
|
|
||||||
)
|
|
||||||
.setAlgorithmParameterSpec(
|
|
||||||
ECGenParameterSpec("prime256v1")
|
|
||||||
)
|
|
||||||
.setDigests(
|
|
||||||
KeyProperties.DIGEST_NONE,
|
|
||||||
KeyProperties.DIGEST_SHA256,
|
|
||||||
KeyProperties.DIGEST_SHA384,
|
|
||||||
KeyProperties.DIGEST_SHA512
|
|
||||||
)
|
|
||||||
.setCertificateNotBefore(Date(now - 1000 * 60))
|
|
||||||
.setCertificateNotAfter(Date(now + 1000 * 60))
|
|
||||||
.setAttestationChallenge(byteArrayOf())
|
|
||||||
.build()
|
|
||||||
)
|
)
|
||||||
}.genKeyPair()
|
.setAlgorithmParameterSpec(
|
||||||
} catch (ex: ProviderException) {
|
ECGenParameterSpec("prime256v1")
|
||||||
// java.security.ProviderException: Failed to generate attestation certificate chain
|
)
|
||||||
|
.setDigests(
|
||||||
return block(httpClient)
|
KeyProperties.DIGEST_NONE,
|
||||||
}
|
KeyProperties.DIGEST_SHA256,
|
||||||
|
KeyProperties.DIGEST_SHA384,
|
||||||
|
KeyProperties.DIGEST_SHA512
|
||||||
|
)
|
||||||
|
.setCertificateNotBefore(Date(now - 1000 * 60))
|
||||||
|
.setCertificateNotAfter(Date(now + 1000 * 60))
|
||||||
|
.setAttestationChallenge(byteArrayOf())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}.genKeyPair()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val key = keyStore.getEntry(keyId, null) as PrivateKeyEntry
|
val key = keyStore.getEntry(keyId, null) as PrivateKeyEntry
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -64,10 +64,10 @@ object U2FResponse {
|
||||||
|
|
||||||
val flags = rawResponse.payload[0]
|
val flags = rawResponse.payload[0]
|
||||||
|
|
||||||
val counter = rawResponse.payload[4].toUByte().toUInt() or
|
val counter = rawResponse.payload[4].toUInt() or
|
||||||
rawResponse.payload[3].toUByte().toUInt().shl(8) or
|
rawResponse.payload[3].toUInt().shl(8) or
|
||||||
rawResponse.payload[2].toUByte().toUInt().shl(16) or
|
rawResponse.payload[2].toUInt().shl(16) or
|
||||||
rawResponse.payload[1].toUByte().toUInt().shl(24)
|
rawResponse.payload[1].toUInt().shl(24)
|
||||||
|
|
||||||
val signature = rawResponse.payload.sliceArray(5 until rawResponse.payload.size)
|
val signature = rawResponse.payload.sliceArray(5 until rawResponse.payload.size)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,24 +19,23 @@ import android.Manifest
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.SystemBarStyle
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -130,19 +129,11 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
if (granted) mainModel.reportPermissionsChanged()
|
if (granted) mainModel.reportPermissionsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isNightMode =
|
supportActionBar!!.hide()
|
||||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
|
||||||
Configuration.UI_MODE_NIGHT_YES
|
|
||||||
|
|
||||||
enableEdgeToEdge(
|
|
||||||
statusBarStyle = SystemBarStyle.dark(
|
|
||||||
if (isNightMode) android.graphics.Color.TRANSPARENT
|
|
||||||
else resources.getColor(R.color.colorPrimaryDark)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
U2fManager.setupActivity(this)
|
||||||
|
|
||||||
|
@ -323,8 +314,9 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
screen = screen,
|
screen = screen,
|
||||||
fragmentManager = supportFragmentManager,
|
fragmentManager = supportFragmentManager,
|
||||||
fragmentIds = mainModel.fragmentIds,
|
fragmentIds = mainModel.fragmentIds,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
paddingValues = paddingValues
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
showAuthenticationDialog = showAuthenticationDialog,
|
showAuthenticationDialog = showAuthenticationDialog,
|
||||||
|
@ -391,10 +383,10 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
if (intent.flags and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT == Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) {
|
if ((intent?.flags ?: 0) and Intent.FLAG_ACTIVITY_REORDER_TO_FRONT == Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.ui
|
package io.timelimit.android.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
|
@ -44,28 +42,27 @@ fun ScreenMultiplexer(
|
||||||
screen: Screen?,
|
screen: Screen?,
|
||||||
fragmentManager: FragmentManager,
|
fragmentManager: FragmentManager,
|
||||||
fragmentIds: MutableSet<Int>,
|
fragmentIds: MutableSet<Int>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier
|
||||||
paddingValues: PaddingValues
|
|
||||||
) {
|
) {
|
||||||
when (screen) {
|
when (screen) {
|
||||||
null -> {/* nothing to do */ }
|
null -> {/* nothing to do */ }
|
||||||
is Screen.FragmentScreen -> FragmentScreen(screen, fragmentManager, fragmentIds, modifier = modifier.padding(paddingValues))
|
is Screen.FragmentScreen -> FragmentScreen(screen, fragmentManager, fragmentIds, modifier = modifier)
|
||||||
is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier, paddingValues = paddingValues)
|
is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier)
|
||||||
is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier.padding(paddingValues))
|
is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier)
|
||||||
is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier.padding(paddingValues))
|
is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier)
|
||||||
is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen, modifier.padding(paddingValues))
|
is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen, modifier)
|
||||||
is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier.padding(paddingValues))
|
is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier)
|
||||||
is Screen.SetupConnectModePrivacyScreen -> SetupConnectedModePrivacyScreen(screen.customServerDomain, screen.accept, modifier.padding(paddingValues))
|
is Screen.SetupConnectModePrivacyScreen -> SetupConnectedModePrivacyScreen(screen.customServerDomain, screen.accept, modifier)
|
||||||
is Screen.SetupSelectConnectedModeScreen -> SelectConnectedModeScreen(mailLogin = screen.mailLogin, codeLogin = screen.codeLogin, modifier = modifier.padding(paddingValues))
|
is Screen.SetupSelectConnectedModeScreen -> SelectConnectedModeScreen(mailLogin = screen.mailLogin, codeLogin = screen.codeLogin, modifier = modifier)
|
||||||
is Screen.SetupSelectModeScreen -> SelectModeScreen(selectLocal = screen.selectLocal, selectConnected = screen.selectConnected, selectUninstall = screen.selectUninstall, modifier = modifier.padding(paddingValues))
|
is Screen.SetupSelectModeScreen -> SelectModeScreen(selectLocal = screen.selectLocal, selectConnected = screen.selectConnected, selectUninstall = screen.selectUninstall, modifier = modifier)
|
||||||
is Screen.DeleteRegistration -> DeleteRegistrationScreen(screen.content, modifier.padding(paddingValues))
|
is Screen.DeleteRegistration -> DeleteRegistrationScreen(screen.content, modifier)
|
||||||
is Screen.ManageBlockedTimes -> BlockedTimesScreen(screen.content, screen.intro, modifier.padding(paddingValues))
|
is Screen.ManageBlockedTimes -> BlockedTimesScreen(screen.content, screen.intro, modifier)
|
||||||
is Screen.ChildUsageHistory -> UsageHistoryScreen(screen.content, modifier.padding(paddingValues))
|
is Screen.ChildUsageHistory -> UsageHistoryScreen(screen.content, modifier)
|
||||||
is Screen.SetupParentMailAuthentication -> AuthenticateByMailScreen(screen.content, modifier.padding(paddingValues))
|
is Screen.SetupParentMailAuthentication -> AuthenticateByMailScreen(screen.content, modifier)
|
||||||
is Screen.SignupBlocked -> SignupBlockedScreen(modifier.padding(paddingValues))
|
is Screen.SignupBlocked -> SignupBlockedScreen(modifier)
|
||||||
is Screen.SignInWrongMailAddress -> SignInWrongMailAddress(modifier.padding(paddingValues))
|
is Screen.SignInWrongMailAddress -> SignInWrongMailAddress(modifier)
|
||||||
is Screen.ConfirmNewParentAccount -> ConfirmNewParentAccount(confirm = screen.confirm, reject = screen.reject, modifier = modifier.padding(paddingValues))
|
is Screen.ConfirmNewParentAccount -> ConfirmNewParentAccount(confirm = screen.confirm, reject = screen.reject, modifier = modifier)
|
||||||
is Screen.ParentBaseConfiguration -> ParentBaseConfiguration(content = screen.content, modifier = modifier.padding(paddingValues))
|
is Screen.ParentBaseConfiguration -> ParentBaseConfiguration(content = screen.content, modifier = modifier)
|
||||||
is Screen.ParentSetupConsent -> ParentSetupConsent(content = screen.content, errorDialog = screen.errorDialog, modifier = modifier.padding(paddingValues))
|
is Screen.ParentSetupConsent -> ParentSetupConsent(content = screen.content, errorDialog = screen.errorDialog, modifier = modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,13 +17,8 @@ package io.timelimit.android.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsIgnoringVisibility
|
|
||||||
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
|
|
||||||
import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -42,7 +37,6 @@ import io.timelimit.android.ui.model.Screen
|
||||||
import io.timelimit.android.ui.model.Title
|
import io.timelimit.android.ui.model.Title
|
||||||
import io.timelimit.android.ui.model.UpdateStateCommand
|
import io.timelimit.android.ui.model.UpdateStateCommand
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScreenScaffold(
|
fun ScreenScaffold(
|
||||||
screen: Screen?,
|
screen: Screen?,
|
||||||
|
@ -51,7 +45,6 @@ fun ScreenScaffold(
|
||||||
backStack: List<BackStackItem>,
|
backStack: List<BackStackItem>,
|
||||||
snackbarHostState: SnackbarHostState?,
|
snackbarHostState: SnackbarHostState?,
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
extraBars: (@Composable () -> Unit)? = null,
|
|
||||||
executeCommand: (UpdateStateCommand) -> Unit,
|
executeCommand: (UpdateStateCommand) -> Unit,
|
||||||
showAuthenticationDialog: (() -> Unit)?
|
showAuthenticationDialog: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
|
@ -59,73 +52,67 @@ fun ScreenScaffold(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column {
|
TopAppBar(
|
||||||
TopAppBar(
|
title = {
|
||||||
title = {
|
Column {
|
||||||
Column {
|
Text(
|
||||||
|
title,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
if (subtitle != null) {
|
||||||
Text(
|
Text(
|
||||||
title,
|
subtitle,
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
if (subtitle != null) {
|
|
||||||
Text(
|
|
||||||
subtitle,
|
|
||||||
style = MaterialTheme.typography.subtitle1,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
navigationIcon = if (screen?.state?.previous != null) ({
|
},
|
||||||
IconButton(onClick = { executeCommand(UpdateStateCommand.BackToPreviousScreen) }) {
|
navigationIcon = if (screen?.state?.previous != null) ({
|
||||||
Icon(Icons.Default.ArrowBack, stringResource(R.string.generic_back))
|
IconButton(onClick = { executeCommand(UpdateStateCommand.BackToPreviousScreen) }) {
|
||||||
}
|
Icon(Icons.Default.ArrowBack, stringResource(R.string.generic_back))
|
||||||
}) else null,
|
}
|
||||||
actions = {
|
}) else null,
|
||||||
for (icon in screen?.toolbarIcons ?: emptyList()) {
|
actions = {
|
||||||
IconButton(
|
for (icon in screen?.toolbarIcons ?: emptyList()) {
|
||||||
onClick = {
|
IconButton(
|
||||||
if (icon.action != null) executeCommand(icon.action)
|
onClick = {
|
||||||
|
if (icon.action != null) executeCommand(icon.action)
|
||||||
|
|
||||||
icon.handler()
|
icon.handler()
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(icon.icon, stringResource(icon.labelResource))
|
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
Icon(icon.icon, stringResource(icon.labelResource))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screen?.toolbarOptions?.isEmpty() == false) {
|
||||||
|
IconButton(onClick = { expandDropdown = true }) {
|
||||||
|
Icon(Icons.Default.MoreVert, stringResource(R.string.generic_menu))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screen?.toolbarOptions?.isEmpty() == false) {
|
DropdownMenu(
|
||||||
IconButton(onClick = { expandDropdown = true }) {
|
expanded = expandDropdown,
|
||||||
Icon(Icons.Default.MoreVert, stringResource(R.string.generic_menu))
|
onDismissRequest = { expandDropdown = false }
|
||||||
}
|
) {
|
||||||
|
for (option in screen.toolbarOptions) {
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
if (option.action != null) executeCommand(option.action)
|
||||||
|
|
||||||
DropdownMenu(
|
option.handler()
|
||||||
expanded = expandDropdown,
|
|
||||||
onDismissRequest = { expandDropdown = false }
|
|
||||||
) {
|
|
||||||
for (option in screen.toolbarOptions) {
|
|
||||||
DropdownMenuItem(onClick = {
|
|
||||||
if (option.action != null) executeCommand(option.action)
|
|
||||||
|
|
||||||
option.handler()
|
expandDropdown = false
|
||||||
|
}) {
|
||||||
expandDropdown = false
|
Text(stringResource(option.labelResource))
|
||||||
}) {
|
|
||||||
Text(stringResource(option.labelResource))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
modifier = Modifier,
|
}
|
||||||
windowInsets = WindowInsets.statusBarsIgnoringVisibility
|
)
|
||||||
)
|
|
||||||
|
|
||||||
extraBars?.invoke()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
val backStackColors = ButtonDefaults.textButtonColors(
|
val backStackColors = ButtonDefaults.textButtonColors(
|
||||||
|
@ -172,8 +159,7 @@ fun ScreenScaffold(
|
||||||
Text(title)
|
Text(title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
windowInsets = WindowInsets.navigationBarsIgnoringVisibility
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
|
@ -184,7 +170,6 @@ fun ScreenScaffold(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState ?: it) },
|
snackbarHost = { SnackbarHost(snackbarHostState ?: it) },
|
||||||
content = content,
|
content = content
|
||||||
contentWindowInsets = WindowInsets.systemBarsIgnoringVisibility
|
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package io.timelimit.android.ui.diagnose.exception
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
|
||||||
|
|
||||||
class DiagnoseExceptionActivity: FragmentActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val ex = DefaultAppLogic.with(this).backgroundTaskLogic.lastLoopException.value
|
|
||||||
|
|
||||||
if (ex != null) setContent {
|
|
||||||
DiagnoseExceptionDialog(
|
|
||||||
message = ExceptionUtil.formatInterpreted(this, ex),
|
|
||||||
close = { finish() }
|
|
||||||
)
|
|
||||||
} else finish()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -37,7 +37,7 @@ class DiagnoseExceptionDialogFragment: DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val message = ExceptionUtil.formatInterpreted(requireContext(), requireArguments().getSerializable(EXCEPTION) as Exception)
|
val message = ExceptionUtil.format(requireArguments().getSerializable(EXCEPTION) as Exception)
|
||||||
|
|
||||||
return AlertDialog.Builder(requireContext(), theme)
|
return AlertDialog.Builder(requireContext(), theme)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,26 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.ui.diagnose.exception
|
package io.timelimit.android.ui.diagnose.exception
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import io.timelimit.android.R
|
|
||||||
import io.timelimit.android.integration.platform.android.foregroundapp.InstanceIdForegroundAppHelper.InstanceIdException
|
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
|
|
||||||
object ExceptionUtil {
|
object ExceptionUtil {
|
||||||
fun formatInterpreted(context: Context, tr: Throwable): String {
|
fun format(tr: Throwable): String = StringWriter().let { sw ->
|
||||||
val explain = when (tr) {
|
|
||||||
is InstanceIdException.EventsNotSortedByTimestamp -> context.getString(R.string.background_logic_errpr_detailed_instanceid_sorting)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
val tr2 = formatSimple(tr)
|
|
||||||
|
|
||||||
return if (explain != null) "$explain\n\n$tr2"
|
|
||||||
else tr2
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatSimple(tr: Throwable): String = StringWriter().let { sw ->
|
|
||||||
PrintWriter(sw).let { pw ->
|
PrintWriter(sw).let { pw ->
|
||||||
tr.printStackTrace(pw)
|
tr.printStackTrace(pw)
|
||||||
pw.flush()
|
pw.flush()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -75,7 +75,7 @@ class HomescreenActivity: AppCompatActivity() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
model.handleLaunch(intent?.getBooleanExtra(FORCE_SELECTION, false) ?: false)
|
model.handleLaunch(intent?.getBooleanExtra(FORCE_SELECTION, false) ?: false)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -124,7 +124,7 @@ class LockActionFragment : Fragment() {
|
||||||
override fun setThisDeviceAsCurrentDevice() = this@LockActionFragment.setThisDeviceAsCurrentDevice()
|
override fun setThisDeviceAsCurrentDevice() = this@LockActionFragment.setThisDeviceAsCurrentDevice()
|
||||||
|
|
||||||
override fun requestLocationPermission() {
|
override fun requestLocationPermission() {
|
||||||
RequestWifiPermission.doRequest(this@LockActionFragment, LOCATION_REQUEST_CODE, auth.logic.platformIntegration)
|
RequestWifiPermission.doRequest(this@LockActionFragment, LOCATION_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disableLimitsTemporarily() {
|
override fun disableLimitsTemporarily() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,35 +18,15 @@ package io.timelimit.android.ui.lock
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.SystemBarStyle
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.viewpager.widget.ViewPager
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
|
||||||
import androidx.compose.material.Tab
|
|
||||||
import androidx.compose.material.TabRow
|
|
||||||
import androidx.compose.material.TabRowDefaults
|
|
||||||
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.fragment.compose.AndroidFragment
|
|
||||||
import androidx.lifecycle.asFlow
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.data.model.UserType
|
import io.timelimit.android.databinding.LockActivityBinding
|
||||||
import io.timelimit.android.extensions.showSafe
|
import io.timelimit.android.extensions.showSafe
|
||||||
import io.timelimit.android.logic.BlockingReason
|
import io.timelimit.android.logic.BlockingReason
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
|
@ -54,12 +34,11 @@ import io.timelimit.android.sync.network.UpdatePrimaryDeviceRequestType
|
||||||
import io.timelimit.android.u2f.U2fManager
|
import io.timelimit.android.u2f.U2fManager
|
||||||
import io.timelimit.android.u2f.protocol.U2FDevice
|
import io.timelimit.android.u2f.protocol.U2FDevice
|
||||||
import io.timelimit.android.ui.IsAppInForeground
|
import io.timelimit.android.ui.IsAppInForeground
|
||||||
import io.timelimit.android.ui.ScreenScaffold
|
|
||||||
import io.timelimit.android.ui.Theme
|
|
||||||
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
||||||
import io.timelimit.android.ui.login.NewLoginFragment
|
import io.timelimit.android.ui.login.NewLoginFragment
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
||||||
|
import io.timelimit.android.ui.main.AuthenticationFab
|
||||||
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
|
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
|
||||||
import io.timelimit.android.ui.util.SyncStatusModel
|
import io.timelimit.android.ui.util.SyncStatusModel
|
||||||
|
|
||||||
|
@ -106,115 +85,26 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val showAuth = MutableLiveData<Boolean>().apply { value = false }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isNightMode =
|
|
||||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
|
||||||
Configuration.UI_MODE_NIGHT_YES
|
|
||||||
|
|
||||||
enableEdgeToEdge(
|
|
||||||
statusBarStyle = SystemBarStyle.dark(
|
|
||||||
if (isNightMode) android.graphics.Color.TRANSPARENT
|
|
||||||
else resources.getColor(R.color.colorPrimaryDark)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
U2fManager.setupActivity(this)
|
||||||
|
|
||||||
val subtitleLive = syncModel.statusText.asFlow()
|
val adapter = LockActivityAdapter(supportFragmentManager, this)
|
||||||
val showTasksLive = model.content.map {
|
|
||||||
val isTimeOver = it is LockscreenContent.Blocked.BlockedCategory && it.blockingHandling.activityBlockingReason == BlockingReason.TimeOver
|
|
||||||
|
|
||||||
isTimeOver
|
val binding = LockActivityBinding.inflate(layoutInflater)
|
||||||
}.asFlow()
|
setContentView(binding.root)
|
||||||
|
|
||||||
setContent {
|
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
|
||||||
val subtitle by subtitleLive.collectAsState(null)
|
|
||||||
val showTasks by showTasksLive.collectAsState(false)
|
|
||||||
val pager = rememberPagerState(initialPage = 0, pageCount = {
|
|
||||||
if (showTasks) 3
|
|
||||||
else 2
|
|
||||||
})
|
|
||||||
val isAuthenticated by getActivityViewModel().authenticatedUser
|
|
||||||
.map { it?.second?.type == UserType.Parent }
|
|
||||||
.asFlow().collectAsState(initial = false)
|
|
||||||
|
|
||||||
Theme {
|
|
||||||
ScreenScaffold(
|
|
||||||
screen = null,
|
|
||||||
title = getString(R.string.app_name),
|
|
||||||
subtitle = subtitle,
|
|
||||||
backStack = emptyList(),
|
|
||||||
snackbarHostState = null,
|
|
||||||
extraBars = {
|
|
||||||
TabRow(
|
|
||||||
pager.currentPage,
|
|
||||||
indicator = { tabPositions ->
|
|
||||||
// workaround for bug
|
|
||||||
TabRowDefaults.Indicator(
|
|
||||||
Modifier.tabIndicatorOffset(tabPositions[
|
|
||||||
pager.currentPage.coerceAtMost(tabPositions.size - 1)
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Tab(
|
|
||||||
selected = pager.currentPage == 0,
|
|
||||||
onClick = { pager.requestScrollToPage(0) }
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.lock_tab_reason),
|
|
||||||
Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Tab(
|
|
||||||
selected = pager.currentPage == 1,
|
|
||||||
onClick = { pager.requestScrollToPage(1) }
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.lock_tab_action),
|
|
||||||
Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showTasks) Tab(
|
|
||||||
selected = pager.currentPage == 2,
|
|
||||||
onClick = { pager.requestScrollToPage(2) }
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.lock_tab_task),
|
|
||||||
Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
content = { padding ->
|
|
||||||
HorizontalPager(
|
|
||||||
pager,
|
|
||||||
Modifier.fillMaxSize().padding(padding),
|
|
||||||
pageContent = { index ->
|
|
||||||
when (index) {
|
|
||||||
0 -> AndroidFragment<LockReasonFragment>(Modifier.fillMaxSize())
|
|
||||||
1 -> AndroidFragment<LockActionFragment>(Modifier.fillMaxSize())
|
|
||||||
2 -> AndroidFragment<LockTaskFragment>(Modifier.fillMaxSize())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
executeCommand = {},
|
|
||||||
showAuthenticationDialog =
|
|
||||||
if (pager.currentPage == 1 && !isAuthenticated) ({ showAuthenticationScreen() })
|
|
||||||
else null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentInstances.add(this)
|
currentInstances.add(this)
|
||||||
|
|
||||||
model.init(blockedPackageName, blockedActivityName)
|
model.init(blockedPackageName, blockedActivityName)
|
||||||
|
|
||||||
|
binding.pager.adapter = adapter
|
||||||
|
|
||||||
model.content.observe(this) {
|
model.content.observe(this) {
|
||||||
if (isResumed && it is LockscreenContent.Blocked.BlockedCategory && it.reason == BlockingReason.RequiresCurrentDevice && !model.didOpenSetCurrentDeviceScreen) {
|
if (isResumed && it is LockscreenContent.Blocked.BlockedCategory && it.reason == BlockingReason.RequiresCurrentDevice && !model.didOpenSetCurrentDeviceScreen) {
|
||||||
model.didOpenSetCurrentDeviceScreen = true
|
model.didOpenSetCurrentDeviceScreen = true
|
||||||
|
@ -225,12 +115,30 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activityModel.shouldHighlightAuthenticationButton.observe(this) {
|
AuthenticationFab.manageAuthenticationFab(
|
||||||
if (it) {
|
fab = binding.fab,
|
||||||
activityModel.shouldHighlightAuthenticationButton.postValue(false)
|
shouldHighlight = activityModel.shouldHighlightAuthenticationButton,
|
||||||
|
authenticatedUser = activityModel.authenticatedUser,
|
||||||
|
activity = this,
|
||||||
|
doesSupportAuth = showAuth
|
||||||
|
)
|
||||||
|
|
||||||
showAuthenticationScreen()
|
binding.fab.setOnClickListener { showAuthenticationScreen() }
|
||||||
|
|
||||||
|
binding.pager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() {
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
super.onPageSelected(position)
|
||||||
|
|
||||||
|
showAuth.value = position == 1
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.tabs.setupWithViewPager(binding.pager)
|
||||||
|
|
||||||
|
model.content.observe(this) {
|
||||||
|
val isTimeOver = it is LockscreenContent.Blocked.BlockedCategory && it.blockingHandling.activityBlockingReason == BlockingReason.TimeOver
|
||||||
|
|
||||||
|
adapter.showTasksFragment = isTimeOver
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(object: OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(object: OnBackPressedCallback(true) {
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.timelimit.android.ui.lock
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.fragment.app.FragmentPagerAdapter
|
||||||
|
import io.timelimit.android.R
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class LockActivityAdapter(fragmentManager: FragmentManager, private val context: Context): FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||||
|
var showTasksFragment: Boolean by Delegates.observable(false) { _, _, _ -> notifyDataSetChanged() }
|
||||||
|
|
||||||
|
override fun getCount(): Int = if (showTasksFragment) 3 else 2
|
||||||
|
|
||||||
|
override fun getItem(position: Int): Fragment = when (position) {
|
||||||
|
0 -> LockReasonFragment()
|
||||||
|
1 -> LockActionFragment()
|
||||||
|
2 -> LockTaskFragment()
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPageTitle(position: Int): CharSequence? = context.getString(when (position) {
|
||||||
|
0 -> R.string.lock_tab_reason
|
||||||
|
1 -> R.string.lock_tab_action
|
||||||
|
2 -> R.string.lock_tab_task
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,21 +16,14 @@
|
||||||
package io.timelimit.android.ui.manage.category.apps.add
|
package io.timelimit.android.ui.manage.category.apps.add
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Build.VERSION
|
|
||||||
import android.os.Build.VERSION_CODES
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
@ -227,29 +220,9 @@ class AddCategoryAppsFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
|
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
|
|
||||||
v.updateLayoutParams<MarginLayoutParams> {
|
|
||||||
topMargin = insets.top
|
|
||||||
bottomMargin = insets.bottom
|
|
||||||
leftMargin = insets.left
|
|
||||||
rightMargin = insets.right
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
|
|
||||||
return AlertDialog.Builder(requireContext(), R.style.AppTheme)
|
return AlertDialog.Builder(requireContext(), R.style.AppTheme)
|
||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.create()
|
.create()
|
||||||
.also { dialog ->
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) dialog.setOnShowListener {
|
|
||||||
WindowInsetsControllerCompat(dialog.window!!, binding.root).run {
|
|
||||||
isAppearanceLightStatusBars = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun show(manager: FragmentManager) {
|
fun show(manager: FragmentManager) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,16 +16,9 @@
|
||||||
package io.timelimit.android.ui.manage.category.apps.addactivity
|
package io.timelimit.android.ui.manage.category.apps.addactivity
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Build.VERSION
|
|
||||||
import android.os.Build.VERSION_CODES
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
@ -121,30 +114,9 @@ class AddAppActivitiesDialogFragment: DialogFragment() {
|
||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
|
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
|
|
||||||
v.updateLayoutParams<MarginLayoutParams> {
|
|
||||||
topMargin = insets.top
|
|
||||||
bottomMargin = insets.bottom
|
|
||||||
leftMargin = insets.left
|
|
||||||
rightMargin = insets.right
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
|
|
||||||
return AlertDialog.Builder(requireContext(), R.style.AppTheme)
|
return AlertDialog.Builder(requireContext(), R.style.AppTheme)
|
||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.create()
|
.create()
|
||||||
.also { dialog ->
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) dialog.setOnShowListener {
|
|
||||||
WindowInsetsControllerCompat(dialog.window!!, binding.root).run {
|
|
||||||
isAppearanceLightStatusBars = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
|
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
|
||||||
|
|
|
@ -31,7 +31,6 @@ import io.timelimit.android.logic.RemainingTime
|
||||||
import io.timelimit.android.ui.manage.category.timelimit_rules.TimeLimitRulesHandlers
|
import io.timelimit.android.ui.manage.category.timelimit_rules.TimeLimitRulesHandlers
|
||||||
import io.timelimit.android.ui.util.DateUtil
|
import io.timelimit.android.ui.util.DateUtil
|
||||||
import io.timelimit.android.util.DayNameUtil
|
import io.timelimit.android.util.DayNameUtil
|
||||||
import io.timelimit.android.util.Option
|
|
||||||
import io.timelimit.android.util.TimeTextUtil
|
import io.timelimit.android.util.TimeTextUtil
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
@ -160,24 +159,12 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
|
||||||
val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding
|
val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding
|
||||||
val context = binding.root.context
|
val context = binding.root.context
|
||||||
val usedTime = date?.let { date ->
|
val usedTime = date?.let { date ->
|
||||||
val dayOfWeekForDailyRule: Option<Int?>? =
|
RemainingTime.getUsedTime(
|
||||||
if (rule.perDay) {
|
usedTimes = usedTimes,
|
||||||
(0 until 7)
|
rule = rule,
|
||||||
.map { (7 + date.dayOfWeek - it) % 7 } // make the current day the last one
|
firstDayOfWeekAsEpochDay = date.firstDayOfWeekAsEpochDay,
|
||||||
.firstOrNull { rule.dayMask.toInt() and (1 shl it) != 0 }
|
dayOfWeekForDailyRule = if (rule.perDay) date.dayOfWeek else null
|
||||||
?.let { Option.Some(it) } // skip calculation if no day matches
|
).toInt()
|
||||||
} else Option.Some(null) // use the value null
|
|
||||||
|
|
||||||
dayOfWeekForDailyRule?.let {
|
|
||||||
RemainingTime.getUsedTime(
|
|
||||||
usedTimes = usedTimes,
|
|
||||||
rule = rule,
|
|
||||||
firstDayOfWeekAsEpochDay = date.firstDayOfWeekAsEpochDay,
|
|
||||||
dayOfWeekForDailyRule =
|
|
||||||
if (it is Option.Some) it.value
|
|
||||||
else null
|
|
||||||
).toInt()
|
|
||||||
}
|
|
||||||
} ?: 0
|
} ?: 0
|
||||||
|
|
||||||
binding.maxTimeString = rule.maximumTimeInMillis.let { time ->
|
binding.maxTimeString = rule.maximumTimeInMillis.let { time ->
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -117,7 +117,7 @@ object ManageCategoryNetworksView {
|
||||||
}
|
}
|
||||||
|
|
||||||
view.grantPermissionButton.setOnClickListener {
|
view.grantPermissionButton.setOnClickListener {
|
||||||
RequestWifiPermission.doRequest(fragment, permissionRequestCode, auth.logic.platformIntegration)
|
RequestWifiPermission.doRequest(fragment, permissionRequestCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
isFullVersionLive.observe(lifecycleOwner, Observer { isFullVersion ->
|
isFullVersionLive.observe(lifecycleOwner, Observer { isFullVersion ->
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,7 +26,6 @@ import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.integration.platform.PlatformIntegration
|
|
||||||
|
|
||||||
object RequestWifiPermission {
|
object RequestWifiPermission {
|
||||||
private fun isLocationEnabled(context: Context): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
private fun isLocationEnabled(context: Context): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||||
|
@ -37,10 +36,8 @@ object RequestWifiPermission {
|
||||||
locationManager.isLocationEnabled
|
locationManager.isLocationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doRequest(fragment: Fragment, permissionRequestCode: Int, platformIntegration: PlatformIntegration) {
|
fun doRequest(fragment: Fragment, permissionRequestCode: Int) {
|
||||||
if (ContextCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
if (platformIntegration.deviceOwner.grantLocationAccess()) return
|
|
||||||
|
|
||||||
fragment.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), permissionRequestCode)
|
fragment.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), permissionRequestCode)
|
||||||
} else if (!isLocationEnabled(fragment.requireContext())) {
|
} else if (!isLocationEnabled(fragment.requireContext())) {
|
||||||
Toast.makeText(fragment.requireContext(), R.string.category_networks_toast_enable_location_service, Toast.LENGTH_SHORT).show()
|
Toast.makeText(fragment.requireContext(), R.string.category_networks_toast_enable_location_service, Toast.LENGTH_SHORT).show()
|
||||||
|
|
|
@ -22,7 +22,7 @@ import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.setFragmentResult
|
import androidx.fragment.app.setFragmentResult
|
||||||
import io.timelimit.android.extensions.showSafe
|
import io.timelimit.android.extensions.showSafe
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
|
|
||||||
class DatePickerDialogFragment: DialogFragment() {
|
class DatePickerDialogFragment: DialogFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -81,8 +81,8 @@ class DatePickerDialogFragment: DialogFragment() {
|
||||||
val requestKey = requireArguments().getString(REQUEST_KEY)!!
|
val requestKey = requireArguments().getString(REQUEST_KEY)!!
|
||||||
|
|
||||||
return DatePickerDialog(requireContext(), theme, { _, year, month, day ->
|
return DatePickerDialog(requireContext(), theme, { _, year, month, day ->
|
||||||
setFragmentResult(requestKey, Result(year, month + 1, day).bundle)
|
setFragmentResult(requestKey, Result(year, month, day).bundle)
|
||||||
}, startYear, startMonthOfYear - 1, startDayOfMonth)
|
}, startYear, startMonthOfYear, startDayOfMonth)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
|
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
|
||||||
|
|
|
@ -47,9 +47,9 @@ import io.timelimit.android.ui.util.DateUtil
|
||||||
import io.timelimit.android.ui.view.SelectDayViewHandlers
|
import io.timelimit.android.ui.view.SelectDayViewHandlers
|
||||||
import io.timelimit.android.ui.view.SelectTimeSpanViewListener
|
import io.timelimit.android.ui.view.SelectTimeSpanViewListener
|
||||||
import io.timelimit.android.util.TimeTextUtil
|
import io.timelimit.android.util.TimeTextUtil
|
||||||
import java.time.Instant
|
import org.threeten.bp.Instant
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment() {
|
class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -36,8 +36,8 @@ import io.timelimit.android.logic.RealTime
|
||||||
import io.timelimit.android.sync.actions.SetUserDisableLimitsUntilAction
|
import io.timelimit.android.sync.actions.SetUserDisableLimitsUntilAction
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.getActivityViewModel
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class DisableTimelimitsUntilDateDialogFragment: DialogFragment() {
|
class DisableTimelimitsUntilDateDialogFragment: DialogFragment() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -35,9 +35,9 @@ import io.timelimit.android.logic.RealTime
|
||||||
import io.timelimit.android.sync.actions.SetUserDisableLimitsUntilAction
|
import io.timelimit.android.sync.actions.SetUserDisableLimitsUntilAction
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.getActivityViewModel
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
import java.time.Instant
|
import org.threeten.bp.Instant
|
||||||
import java.time.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
|
|
||||||
class DisableTimelimitsUntilTimeDialogFragment: DialogFragment() {
|
class DisableTimelimitsUntilTimeDialogFragment: DialogFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,8 +29,8 @@ import io.timelimit.android.ui.help.HelpDialogFragment
|
||||||
import io.timelimit.android.ui.main.getActivityViewModel
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
|
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
|
||||||
import io.timelimit.android.ui.view.ManageDisableTimelimitsViewHandlers
|
import io.timelimit.android.ui.view.ManageDisableTimelimitsViewHandlers
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object ManageDisableTimelimitsViewHelper {
|
object ManageDisableTimelimitsViewHelper {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -34,11 +34,11 @@ import io.timelimit.android.extensions.toInstant
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.getActivityViewModel
|
import io.timelimit.android.ui.main.getActivityViewModel
|
||||||
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
|
import io.timelimit.android.ui.payment.RequiresPurchaseDialogFragment
|
||||||
import java.time.Instant
|
import org.threeten.bp.Instant
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.time.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import java.time.LocalTime
|
import org.threeten.bp.LocalTime
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
|
|
||||||
class SetCategorySpecialModeFragment: DialogFragment() {
|
class SetCategorySpecialModeFragment: DialogFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,7 +26,7 @@ import io.timelimit.android.logic.DefaultAppLogic
|
||||||
import io.timelimit.android.sync.actions.UpdateCategoryDisableLimitsAction
|
import io.timelimit.android.sync.actions.UpdateCategoryDisableLimitsAction
|
||||||
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
|
import io.timelimit.android.sync.actions.UpdateCategoryTemporarilyBlockedAction
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
|
|
||||||
class SetCategorySpecialModeModel(application: Application): AndroidViewModel(application) {
|
class SetCategorySpecialModeModel(application: Application): AndroidViewModel(application) {
|
||||||
private val logic = DefaultAppLogic.with(application)
|
private val logic = DefaultAppLogic.with(application)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,8 +19,8 @@ import android.content.Context
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.date.DateInTimezone
|
import io.timelimit.android.date.DateInTimezone
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.time.ZoneId
|
import org.threeten.bp.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
sealed class SpecialModeOption {
|
sealed class SpecialModeOption {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -48,8 +48,8 @@ import io.timelimit.android.R
|
||||||
import io.timelimit.android.extensions.MinuteOfDay
|
import io.timelimit.android.extensions.MinuteOfDay
|
||||||
import io.timelimit.android.ui.model.managechild.ManageChildUsageHistory
|
import io.timelimit.android.ui.model.managechild.ManageChildUsageHistory
|
||||||
import io.timelimit.android.util.TimeTextUtil
|
import io.timelimit.android.util.TimeTextUtil
|
||||||
import java.time.LocalDate
|
import org.threeten.bp.LocalDate
|
||||||
import java.time.ZoneOffset
|
import org.threeten.bp.ZoneOffset
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -49,7 +49,7 @@ fun ManageDeviceUserScreen(
|
||||||
Card(
|
Card(
|
||||||
onClick = { actions.select(item) },
|
onClick = { actions.select(item) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
backgroundColor = when (item.selected) {
|
backgroundColor = when (item.selected) {
|
||||||
true -> MaterialTheme.colors.secondary
|
true -> MaterialTheme.colors.secondary
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,21 +18,12 @@ package io.timelimit.android.ui.manipulation
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.SystemBarStyle
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import io.timelimit.android.BuildConfig
|
import io.timelimit.android.BuildConfig
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
|
@ -43,8 +34,6 @@ import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
import io.timelimit.android.u2f.U2fManager
|
import io.timelimit.android.u2f.U2fManager
|
||||||
import io.timelimit.android.u2f.protocol.U2FDevice
|
import io.timelimit.android.u2f.protocol.U2FDevice
|
||||||
import io.timelimit.android.ui.ScreenScaffold
|
|
||||||
import io.timelimit.android.ui.Theme
|
|
||||||
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
|
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
|
||||||
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
||||||
import io.timelimit.android.ui.login.NewLoginFragment
|
import io.timelimit.android.ui.login.NewLoginFragment
|
||||||
|
@ -76,73 +65,12 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
U2fManager.setupActivity(this)
|
||||||
|
|
||||||
val logic = DefaultAppLogic.with(this)
|
val logic = DefaultAppLogic.with(this)
|
||||||
|
|
||||||
val isNightMode =
|
val binding = AnnoyActivityBinding.inflate(layoutInflater)
|
||||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
setContentView(binding.root)
|
||||||
Configuration.UI_MODE_NIGHT_YES
|
|
||||||
|
|
||||||
enableEdgeToEdge(
|
|
||||||
statusBarStyle = SystemBarStyle.dark(
|
|
||||||
if (isNightMode) android.graphics.Color.TRANSPARENT
|
|
||||||
else resources.getColor(R.color.colorPrimaryDark)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
setContent {
|
|
||||||
Theme {
|
|
||||||
ScreenScaffold(
|
|
||||||
screen = null,
|
|
||||||
title = getString(R.string.app_name),
|
|
||||||
subtitle = null,
|
|
||||||
backStack = emptyList(),
|
|
||||||
snackbarHostState = null,
|
|
||||||
content = { padding ->
|
|
||||||
AndroidView(
|
|
||||||
factory = {
|
|
||||||
val binding = AnnoyActivityBinding.inflate(LayoutInflater.from(it))
|
|
||||||
|
|
||||||
logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown ->
|
|
||||||
binding.canRequestUnlock = countdown == 0L
|
|
||||||
binding.countdownText = getString(R.string.annoy_timer, TimeTextUtil.seconds((countdown / 1000).toInt(), this@AnnoyActivity))
|
|
||||||
}
|
|
||||||
|
|
||||||
logic.deviceEntry.map {
|
|
||||||
val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty)
|
|
||||||
.current
|
|
||||||
.map { getString(it.labelResourceId) }
|
|
||||||
|
|
||||||
if (reasonItems.isEmpty()) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", "))
|
|
||||||
}
|
|
||||||
}.observe(this) { binding.reasonText = it }
|
|
||||||
|
|
||||||
binding.unlockTemporarilyButton.setOnClickListener {
|
|
||||||
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Short)
|
|
||||||
.show(supportFragmentManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.parentUnlockButton.setOnClickListener {
|
|
||||||
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Long)
|
|
||||||
.show(supportFragmentManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.useBackdoorButton.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
|
|
||||||
|
|
||||||
binding.root
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxSize().padding(padding)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
executeCommand = {},
|
|
||||||
showAuthenticationDialog = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val systemImageApps = packageManager.getInstalledApplications(0)
|
val systemImageApps = packageManager.getInstalledApplications(0)
|
||||||
|
@ -164,6 +92,35 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
||||||
if (!shouldRun) shutdown()
|
if (!shouldRun) shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown ->
|
||||||
|
binding.canRequestUnlock = countdown == 0L
|
||||||
|
binding.countdownText = getString(R.string.annoy_timer, TimeTextUtil.seconds((countdown / 1000).toInt(), this@AnnoyActivity))
|
||||||
|
}
|
||||||
|
|
||||||
|
logic.deviceEntry.map {
|
||||||
|
val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty)
|
||||||
|
.current
|
||||||
|
.map { getString(it.labelResourceId) }
|
||||||
|
|
||||||
|
if (reasonItems.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", "))
|
||||||
|
}
|
||||||
|
}.observe(this) { binding.reasonText = it }
|
||||||
|
|
||||||
|
binding.unlockTemporarilyButton.setOnClickListener {
|
||||||
|
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Short)
|
||||||
|
.show(supportFragmentManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.parentUnlockButton.setOnClickListener {
|
||||||
|
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Long)
|
||||||
|
.show(supportFragmentManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.useBackdoorButton.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
|
||||||
|
|
||||||
model.authenticatedUser.observe(this) { user ->
|
model.authenticatedUser.observe(this) { user ->
|
||||||
if (user?.second?.type == UserType.Parent) {
|
if (user?.second?.type == UserType.Parent) {
|
||||||
logic.annoyLogic.doParentTempUnlock()
|
logic.annoyLogic.doParentTempUnlock()
|
||||||
|
|
|
@ -181,7 +181,7 @@ object AccountDeletion {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result == SnackbarResult.ActionPerformed) updateState {
|
if (result == SnackbarResult.ActionPerformed) updateState {
|
||||||
it.copy(errorDialog = ExceptionUtil.formatInterpreted(logic.context, ex))
|
it.copy(errorDialog = ExceptionUtil.format(ex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -160,7 +160,7 @@ object MailAuthentication {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result == SnackbarResult.ActionPerformed) {
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
val message = ExceptionUtil.formatInterpreted(logic.context, ex)
|
val message = ExceptionUtil.format(ex)
|
||||||
|
|
||||||
updateState { it.withError(error = ErrorDialog.ExceptionDetails(message)) }
|
updateState { it.withError(error = ErrorDialog.ExceptionDetails(message)) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -181,7 +181,7 @@ object DeviceOwnerHandling {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result == SnackbarResult.ActionPerformed) updateState {
|
if (result == SnackbarResult.ActionPerformed) updateState {
|
||||||
it.copy(dialog = OwnerState.ErrorDialog(ExceptionUtil.formatInterpreted(logic.context, ex)))
|
it.copy(dialog = OwnerState.ErrorDialog(ExceptionUtil.format(ex)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,7 +23,6 @@ import io.timelimit.android.async.Threads
|
||||||
import io.timelimit.android.coroutines.executeAndWait
|
import io.timelimit.android.coroutines.executeAndWait
|
||||||
import io.timelimit.android.data.backup.DatabaseBackup
|
import io.timelimit.android.data.backup.DatabaseBackup
|
||||||
import io.timelimit.android.data.devicename.DeviceName
|
import io.timelimit.android.data.devicename.DeviceName
|
||||||
import io.timelimit.android.data.model.ConsentFlags
|
|
||||||
import io.timelimit.android.logic.AppLogic
|
import io.timelimit.android.logic.AppLogic
|
||||||
import io.timelimit.android.sync.ApplyServerDataStatus
|
import io.timelimit.android.sync.ApplyServerDataStatus
|
||||||
import io.timelimit.android.sync.network.NewDeviceInfo
|
import io.timelimit.android.sync.network.NewDeviceInfo
|
||||||
|
@ -334,11 +333,6 @@ object SetupParentHandling {
|
||||||
database.config().setDeviceAuthTokenSync(result.deviceAuthToken)
|
database.config().setDeviceAuthTokenSync(result.deviceAuthToken)
|
||||||
database.config().setEnableBackgroundSync(state.backgroundSync)
|
database.config().setEnableBackgroundSync(state.backgroundSync)
|
||||||
|
|
||||||
database.config().setConsentFlagSync(
|
|
||||||
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
ApplyServerDataStatus.applyServerDataStatusSync(result.serverDataStatus, logic.database, logic.platformIntegration)
|
ApplyServerDataStatus.applyServerDataStatusSync(result.serverDataStatus, logic.database, logic.platformIntegration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,7 +363,7 @@ object SetupParentHandling {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (result == SnackbarResult.ActionPerformed) updateState {
|
if (result == SnackbarResult.ActionPerformed) updateState {
|
||||||
it.copy(error = ExceptionUtil.formatInterpreted(logic.context, ex))
|
it.copy(error = ExceptionUtil.format(ex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -35,7 +35,7 @@ import io.timelimit.android.ui.model.main.OverviewHandling
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
|
fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
|
||||||
item (key = Pair("devices", "header")) {
|
item (key = Pair("devices", "header")) {
|
||||||
ListCommon.SectionHeader(stringResource(R.string.overview_header_devices), Modifier.animateItem())
|
ListCommon.SectionHeader(stringResource(R.string.overview_header_devices), Modifier.animateItemPlacement())
|
||||||
}
|
}
|
||||||
|
|
||||||
items(screen.devices.list, key = { Pair("device", it.device.id) }) {
|
items(screen.devices.list, key = { Pair("device", it.device.id) }) {
|
||||||
|
@ -48,7 +48,7 @@ fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
|
||||||
icon = Icons.Default.Add,
|
icon = Icons.Default.Add,
|
||||||
label = stringResource(R.string.add_device),
|
label = stringResource(R.string.add_device),
|
||||||
action = screen.actions.addDevice,
|
action = screen.actions.addDevice,
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ fun LazyListScope.deviceItems(screen: OverviewHandling.OverviewScreen) {
|
||||||
if (screen.devices.canShowMore != null) {
|
if (screen.devices.canShowMore != null) {
|
||||||
item (key = Pair("devices", "more")) {
|
item (key = Pair("devices", "more")) {
|
||||||
ListCommon.ShowMoreItem(
|
ListCommon.ShowMoreItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
action = { screen.actions.showMoreDevices(screen.devices.canShowMore) }
|
action = { screen.actions.showMoreDevices(screen.devices.canShowMore) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ fun LazyItemScope.DeviceItem(
|
||||||
) {
|
) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
Modifier
|
Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.clickable(onClick = { openAction(item) })
|
.clickable(onClick = { openAction(item) })
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -40,7 +40,7 @@ fun LazyListScope.introItems(
|
||||||
item (key = Pair("intro", "finish setup")) {
|
item (key = Pair("intro", "finish setup")) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -62,7 +62,7 @@ fun LazyListScope.introItems(
|
||||||
item (key = Pair("intro", "outdated server")) {
|
item (key = Pair("intro", "outdated server")) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -79,7 +79,7 @@ fun LazyListScope.introItems(
|
||||||
item (key = Pair("intro", "server message")) {
|
item (key = Pair("intro", "server message")) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -108,7 +108,7 @@ fun LazyListScope.introItems(
|
||||||
SwipeToDismiss(
|
SwipeToDismiss(
|
||||||
state = state,
|
state = state,
|
||||||
background = {},
|
background = {},
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItemPlacement()
|
||||||
) {
|
) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
modifier = Modifier.padding(horizontal = 8.dp)
|
||||||
|
@ -133,7 +133,7 @@ fun LazyListScope.introItems(
|
||||||
item (key = Pair("intro", "task review")) {
|
item (key = Pair("intro", "task review")) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,24 +19,16 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.timelimit.android.ui.model.main.OverviewHandling
|
import io.timelimit.android.ui.model.main.OverviewHandling
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OverviewScreen(
|
fun OverviewScreen(
|
||||||
screen: OverviewHandling.OverviewScreen,
|
screen: OverviewHandling.OverviewScreen,
|
||||||
paddingValues: PaddingValues,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
LazyColumn (
|
LazyColumn (
|
||||||
contentPadding = object: PaddingValues {
|
contentPadding = PaddingValues(0.dp, 8.dp),
|
||||||
override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = paddingValues.calculateLeftPadding(layoutDirection)
|
|
||||||
override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = paddingValues.calculateRightPadding(layoutDirection)
|
|
||||||
override fun calculateTopPadding(): Dp = paddingValues.calculateTopPadding() + 8.dp
|
|
||||||
override fun calculateBottomPadding(): Dp = paddingValues.calculateBottomPadding() + 8.dp
|
|
||||||
},
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -35,7 +35,7 @@ import io.timelimit.android.ui.model.main.OverviewHandling
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun LazyListScope.userItems(screen: OverviewHandling.OverviewScreen) {
|
fun LazyListScope.userItems(screen: OverviewHandling.OverviewScreen) {
|
||||||
item (key = Pair("users", "header")) {
|
item (key = Pair("users", "header")) {
|
||||||
ListCommon.SectionHeader(stringResource(R.string.overview_header_users), Modifier.animateItem())
|
ListCommon.SectionHeader(stringResource(R.string.overview_header_users), Modifier.animateItemPlacement())
|
||||||
}
|
}
|
||||||
|
|
||||||
items(screen.users.list, key = { Pair("user", it.id) }) { UserItem(it, screen.actions) }
|
items(screen.users.list, key = { Pair("user", it.id) }) { UserItem(it, screen.actions) }
|
||||||
|
@ -45,13 +45,13 @@ fun LazyListScope.userItems(screen: OverviewHandling.OverviewScreen) {
|
||||||
icon = Icons.Default.Add,
|
icon = Icons.Default.Add,
|
||||||
label = stringResource(R.string.add_user_title),
|
label = stringResource(R.string.add_user_title),
|
||||||
action = screen.actions.addUser,
|
action = screen.actions.addUser,
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screen.users.canShowMore) item (key = Pair("users", "more")) {
|
if (screen.users.canShowMore) item (key = Pair("users", "more")) {
|
||||||
ListCommon.ShowMoreItem (
|
ListCommon.ShowMoreItem (
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
action = screen.actions.showMoreUsers
|
action = screen.actions.showMoreUsers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ fun LazyItemScope.UserItem(
|
||||||
) {
|
) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
Modifier
|
Modifier
|
||||||
.animateItem()
|
.animateItemPlacement()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.clickable(onClick = { actions.openUser(user) })
|
.clickable(onClick = { actions.openUser(user) })
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -74,14 +74,9 @@ class ActivityPurchaseModel(application: Application): AndroidViewModel(applicat
|
||||||
clientMutex.withLock {
|
clientMutex.withLock {
|
||||||
if (_billingClient == null) {
|
if (_billingClient == null) {
|
||||||
_billingClient = BillingClient.newBuilder(getApplication())
|
_billingClient = BillingClient.newBuilder(getApplication())
|
||||||
.enablePendingPurchases(
|
.enablePendingPurchases()
|
||||||
PendingPurchasesParams
|
.setListener(purchaseUpdatedListener)
|
||||||
.newBuilder()
|
.build()
|
||||||
.enableOneTimeProducts()
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.setListener(purchaseUpdatedListener)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val initBillingClient = _billingClient!!
|
val initBillingClient = _billingClient!!
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -24,7 +24,6 @@ import io.timelimit.android.coroutines.executeAndWait
|
||||||
import io.timelimit.android.coroutines.runAsync
|
import io.timelimit.android.coroutines.runAsync
|
||||||
import io.timelimit.android.data.backup.DatabaseBackup
|
import io.timelimit.android.data.backup.DatabaseBackup
|
||||||
import io.timelimit.android.data.devicename.DeviceName
|
import io.timelimit.android.data.devicename.DeviceName
|
||||||
import io.timelimit.android.data.model.ConsentFlags
|
|
||||||
import io.timelimit.android.livedata.castDown
|
import io.timelimit.android.livedata.castDown
|
||||||
import io.timelimit.android.logic.AppLogic
|
import io.timelimit.android.logic.AppLogic
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
|
@ -71,11 +70,6 @@ class SetupRemoteChildViewModel(application: Application): AndroidViewModel(appl
|
||||||
logic.database.config().setOwnDeviceIdSync(registerResponse.ownDeviceId)
|
logic.database.config().setOwnDeviceIdSync(registerResponse.ownDeviceId)
|
||||||
logic.database.config().setDeviceAuthTokenSync(registerResponse.deviceAuthToken)
|
logic.database.config().setDeviceAuthTokenSync(registerResponse.deviceAuthToken)
|
||||||
|
|
||||||
logic.database.config().setConsentFlagSync(
|
|
||||||
ConsentFlags.BLOCK_USER_SWITCH_BY_DEFAULT,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
ApplyServerDataStatus.applyServerDataStatusSync(clientStatusResponse, logic.database, logic.platformIntegration)
|
ApplyServerDataStatus.applyServerDataStatusSync(clientStatusResponse, logic.database, logic.platformIntegration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +1,23 @@
|
||||||
/*
|
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package io.timelimit.android.ui.update
|
package io.timelimit.android.ui.update
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.activity.SystemBarStyle
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.databinding.UpdateActivityBinding
|
import io.timelimit.android.databinding.UpdateActivityBinding
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
import io.timelimit.android.ui.ScreenScaffold
|
|
||||||
import io.timelimit.android.ui.Theme
|
|
||||||
|
|
||||||
class UpdateActivity: AppCompatActivity() {
|
class UpdateActivity: AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isNightMode =
|
val binding = DataBindingUtil.setContentView<UpdateActivityBinding>(this, R.layout.update_activity)
|
||||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
|
||||||
Configuration.UI_MODE_NIGHT_YES
|
|
||||||
|
|
||||||
enableEdgeToEdge(
|
UpdateView.bind(
|
||||||
statusBarStyle = SystemBarStyle.dark(
|
view = binding.update,
|
||||||
if (isNightMode) android.graphics.Color.TRANSPARENT
|
lifecycleOwner = this,
|
||||||
else resources.getColor(R.color.colorPrimaryDark)
|
fragmentManager = supportFragmentManager,
|
||||||
)
|
appLogic = DefaultAppLogic.with(this)
|
||||||
)
|
)
|
||||||
|
|
||||||
setContent {
|
|
||||||
Theme {
|
|
||||||
ScreenScaffold(
|
|
||||||
screen = null,
|
|
||||||
title = getString(R.string.app_name),
|
|
||||||
subtitle = null,
|
|
||||||
backStack = emptyList(),
|
|
||||||
snackbarHostState = null,
|
|
||||||
content = { padding ->
|
|
||||||
AndroidView(
|
|
||||||
factory = {
|
|
||||||
val binding = UpdateActivityBinding.inflate(LayoutInflater.from(it))
|
|
||||||
|
|
||||||
UpdateView.bind(
|
|
||||||
view = binding.update,
|
|
||||||
lifecycleOwner = this,
|
|
||||||
fragmentManager = supportFragmentManager,
|
|
||||||
appLogic = DefaultAppLogic.with(this)
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.root
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxSize().padding(padding)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
executeCommand = {},
|
|
||||||
showAuthenticationDialog = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,7 +19,6 @@ import android.appwidget.AppWidgetManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
|
@ -31,8 +30,6 @@ class WidgetConfigActivity: FragmentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
|
||||||
|
|
||||||
if (model.state.value == WidgetConfigModel.State.WaitingForInit) {
|
if (model.state.value == WidgetConfigModel.State.WaitingForInit) {
|
||||||
model.init(
|
model.init(
|
||||||
intent?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
intent?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -70,12 +70,12 @@ object UpdateIntegration {
|
||||||
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
// new signature
|
// new signature
|
||||||
|
|
||||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo!!.apkContentsSigners
|
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo.apkContentsSigners
|
||||||
} else {
|
} else {
|
||||||
// old signature
|
// old signature
|
||||||
// this is "unsafe", but it is not used for security features
|
// this is "unsafe", but it is not used for security features
|
||||||
|
|
||||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures!!
|
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures
|
||||||
}
|
}
|
||||||
|
|
||||||
return signatures.map { HexString.toHex(MessageDigest.getInstance("SHA-256").digest(it.toByteArray())) }
|
return signatures.map { HexString.toHex(MessageDigest.getInstance("SHA-256").digest(it.toByteArray())) }
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
- Funktionsumfang bei Verwendung der Geräte-Besitzer-Berechtigung erweitert
|
- Zeitbegrenzungsregeln mit Ablaufzeitpunkt
|
||||||
- enthaltene Komponenten aktualisiert
|
- Vorbereitung für technische Änderungen seitens Let's Encrypt
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
- add more features for users of the device owner permission
|
- add support for time limit rules that expire
|
||||||
- update contained software
|
- prepare for technical adjustments at Let's Encrypt
|
||||||
|
|
50
app/src/main/res/layout/lock_activity.xml
Normal file
50
app/src/main/res/layout/lock_activity.xml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".ui.lock.LockActivity" >
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/tabs"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.viewpager.widget.ViewPager
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/pager" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
app:fabSize="normal"
|
||||||
|
android:src="@drawable/ic_lock_open_white_24dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
</LinearLayout>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -197,12 +197,6 @@
|
||||||
<string name="background_logic_opening_lockscreen">Sperrbildschirm wird geöffnet</string>
|
<string name="background_logic_opening_lockscreen">Sperrbildschirm wird geöffnet</string>
|
||||||
<string name="background_logic_permission_sanction_title">Berechtigung fehlt</string>
|
<string name="background_logic_permission_sanction_title">Berechtigung fehlt</string>
|
||||||
<string name="background_logic_permission_sanction_text">nehme an, dass alles verwendet wird</string>
|
<string name="background_logic_permission_sanction_text">nehme an, dass alles verwendet wird</string>
|
||||||
<string name="background_logic_errpr_detailed_instanceid_sorting">Es gibt ein Problem mit der
|
|
||||||
Nutzungsstatisitkdatenbank Ihres Gerätes.
|
|
||||||
Deshalb kann TimeLimit nicht zuverlässig erkennen, welche App verwendet wird.
|
|
||||||
Dieses Problem kann nur behoben werden durch vier Tagen Geduld oder das Zurücksetzen des Gerätes.
|
|
||||||
Eine Neuinstallation von TimeLimit hilft NICHT.
|
|
||||||
</string>
|
|
||||||
|
|
||||||
<string name="background_logic_temporarily_allowed_title">Apps wurden vorübergehend erlaubt</string>
|
<string name="background_logic_temporarily_allowed_title">Apps wurden vorübergehend erlaubt</string>
|
||||||
<string name="background_logic_temporarily_allowed_text">Zum Rückgängig machen hier tippen oder Bildschirm ausschalten</string>
|
<string name="background_logic_temporarily_allowed_text">Zum Rückgängig machen hier tippen oder Bildschirm ausschalten</string>
|
||||||
|
@ -1213,9 +1207,6 @@
|
||||||
<string name="notification_channel_new_device_title">neue Geräte</string>
|
<string name="notification_channel_new_device_title">neue Geräte</string>
|
||||||
<string name="notification_channel_new_device_description">Zeigt eine Benachrichtigung an, wenn TimeLimit mit Vernetzung verwendet wird und ein neues Gerät verknüpft wurde</string>
|
<string name="notification_channel_new_device_description">Zeigt eine Benachrichtigung an, wenn TimeLimit mit Vernetzung verwendet wird und ein neues Gerät verknüpft wurde</string>
|
||||||
|
|
||||||
<string name="notification_channel_extra_time_started_title">Extrazeit beginnt</string>
|
|
||||||
<string name="notification_channel_extra_time_started_description">Informiert, wenn des reguläre Zeitkontingent verbraucht ist und nun Extrazeit verwendet wird</string>
|
|
||||||
|
|
||||||
<string name="notification_filter_not_blocked_title">TimeLimit hat eine Benachrichtigung blockiert</string>
|
<string name="notification_filter_not_blocked_title">TimeLimit hat eine Benachrichtigung blockiert</string>
|
||||||
<string name="notification_filter_blocking_failed_title">TimeLimit konnte eine Benachrichtigung nicht blockieren</string>
|
<string name="notification_filter_blocking_failed_title">TimeLimit konnte eine Benachrichtigung nicht blockieren</string>
|
||||||
|
|
||||||
|
@ -1231,8 +1222,6 @@
|
||||||
|
|
||||||
<string name="notification_new_device_title">Gerät hinzugefügt</string>
|
<string name="notification_new_device_title">Gerät hinzugefügt</string>
|
||||||
|
|
||||||
<string name="notification_extra_time_started">Extrazeit beginnt</string>
|
|
||||||
|
|
||||||
<string name="obsolete_message">Sie verwenden TimeLimit auf einer älteren Android-Version.
|
<string name="obsolete_message">Sie verwenden TimeLimit auf einer älteren Android-Version.
|
||||||
Das kann funktionieren, aber es wird nicht empfohlen.
|
Das kann funktionieren, aber es wird nicht empfohlen.
|
||||||
</string>
|
</string>
|
||||||
|
@ -1752,10 +1741,6 @@
|
||||||
<string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string>
|
<string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string>
|
||||||
<string name="dummy_app_feature_adb">Entwickleroptionen</string>
|
<string name="dummy_app_feature_adb">Entwickleroptionen</string>
|
||||||
<string name="dummy_app_feature_dns">DNS-Einstellungen</string>
|
<string name="dummy_app_feature_dns">DNS-Einstellungen</string>
|
||||||
<string name="dummy_app_feature_add_user">Systembenutzer erstellen</string>
|
|
||||||
<string name="dummy_app_feature_switch_user">Systembenutzer wechseln</string>
|
|
||||||
<string name="dummy_app_feature_vpn">VPN konfigurieren</string>
|
|
||||||
<string name="dummy_app_feature_unknown_sources">Apps aus unbekannten Quellen installieren</string>
|
|
||||||
<string name="dummy_app_activity_audio">Hintergrundmusikwiedergabe</string>
|
<string name="dummy_app_activity_audio">Hintergrundmusikwiedergabe</string>
|
||||||
|
|
||||||
<string name="notify_permission_title">Benachrichtigungen</string>
|
<string name="notify_permission_title">Benachrichtigungen</string>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -240,12 +240,6 @@
|
||||||
<string name="background_logic_opening_lockscreen">Opening lock screen</string>
|
<string name="background_logic_opening_lockscreen">Opening lock screen</string>
|
||||||
<string name="background_logic_permission_sanction_title">Missing permission</string>
|
<string name="background_logic_permission_sanction_title">Missing permission</string>
|
||||||
<string name="background_logic_permission_sanction_text">assuming that everything is used</string>
|
<string name="background_logic_permission_sanction_text">assuming that everything is used</string>
|
||||||
<string name="background_logic_errpr_detailed_instanceid_sorting">There is an issue with
|
|
||||||
the data in the usage stats database of your device.
|
|
||||||
Due to that, TimeLimit can not reliable detect the running Apps.
|
|
||||||
You can do nothing to solve this except waiting for four days or doing a reset of the whole device.
|
|
||||||
Reinstalling TimeLimit does NOT help.
|
|
||||||
</string>
|
|
||||||
|
|
||||||
<string name="background_logic_temporarily_allowed_title">Apps are temporarily allowed</string>
|
<string name="background_logic_temporarily_allowed_title">Apps are temporarily allowed</string>
|
||||||
<string name="background_logic_temporarily_allowed_text">Tap here or turn screen off to undo</string>
|
<string name="background_logic_temporarily_allowed_text">Tap here or turn screen off to undo</string>
|
||||||
|
@ -1262,9 +1256,6 @@
|
||||||
<string name="notification_channel_new_device_title">New Device</string>
|
<string name="notification_channel_new_device_title">New Device</string>
|
||||||
<string name="notification_channel_new_device_description">Shows a notification if the connected mode is used and a new device was linked</string>
|
<string name="notification_channel_new_device_description">Shows a notification if the connected mode is used and a new device was linked</string>
|
||||||
|
|
||||||
<string name="notification_channel_extra_time_started_title">Extra time starts</string>
|
|
||||||
<string name="notification_channel_extra_time_started_description">Informs when the regular time contingent was consumed and the extra time starts</string>
|
|
||||||
|
|
||||||
<string name="notification_filter_not_blocked_title">TimeLimit has blocked a notification</string>
|
<string name="notification_filter_not_blocked_title">TimeLimit has blocked a notification</string>
|
||||||
<string name="notification_filter_blocking_failed_title">TimeLimit could not block a notification</string>
|
<string name="notification_filter_blocking_failed_title">TimeLimit could not block a notification</string>
|
||||||
|
|
||||||
|
@ -1280,8 +1271,6 @@
|
||||||
|
|
||||||
<string name="notification_new_device_title">Device added</string>
|
<string name="notification_new_device_title">Device added</string>
|
||||||
|
|
||||||
<string name="notification_extra_time_started">Extra time starts</string>
|
|
||||||
|
|
||||||
<string name="obsolete_message">You are using TimeLimit at a obsolete Android version.
|
<string name="obsolete_message">You are using TimeLimit at a obsolete Android version.
|
||||||
Although this can work, it is not recommend.
|
Although this can work, it is not recommend.
|
||||||
</string>
|
</string>
|
||||||
|
@ -1649,7 +1638,7 @@
|
||||||
<a href="https://legal.timelimit.io/en/privacy/">https://legal.timelimit.io/en/privacy/</a>
|
<a href="https://legal.timelimit.io/en/privacy/">https://legal.timelimit.io/en/privacy/</a>
|
||||||
</string>
|
</string>
|
||||||
<string name="terms_gpl" translatable="false">
|
<string name="terms_gpl" translatable="false">
|
||||||
TimeLimit Copyright © 2019 - 2025 Jonas Lochmann
|
TimeLimit Copyright © 2019 - 2024 Jonas Lochmann
|
||||||
\nThis program is free software: you can redistribute it and/or modify
|
\nThis program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -1804,10 +1793,6 @@
|
||||||
<string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string>
|
<string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string>
|
||||||
<string name="dummy_app_feature_adb">Developer Options</string>
|
<string name="dummy_app_feature_adb">Developer Options</string>
|
||||||
<string name="dummy_app_feature_dns">DNS Settings</string>
|
<string name="dummy_app_feature_dns">DNS Settings</string>
|
||||||
<string name="dummy_app_feature_add_user">Create System User</string>
|
|
||||||
<string name="dummy_app_feature_switch_user">Switch System User</string>
|
|
||||||
<string name="dummy_app_feature_vpn">Configure VPN</string>
|
|
||||||
<string name="dummy_app_feature_unknown_sources">Install Apps from unknown sources</string>
|
|
||||||
<string name="dummy_app_activity_audio">Background Audio Playback</string>
|
<string name="dummy_app_activity_audio">Background Audio Playback</string>
|
||||||
|
|
||||||
<string name="notify_permission_title">Notifications</string>
|
<string name="notify_permission_title">Notifications</string>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!--
|
<!--
|
||||||
TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation version 3 of the License.
|
the Free Software Foundation version 3 of the License.
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
<item name="colorSecondary">@color/colorAccent</item>
|
<item name="colorSecondary">@color/colorAccent</item>
|
||||||
|
@ -32,10 +32,6 @@
|
||||||
<item name="colorAccent">@color/white</item>
|
<item name="colorAccent">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Translucent" parent="AppTheme">
|
|
||||||
<item name="android:windowIsFloating">true</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- from https://stackoverflow.com/a/46286184 -->
|
<!-- from https://stackoverflow.com/a/46286184 -->
|
||||||
<style name="BottomSheetDialog" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
|
<style name="BottomSheetDialog" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -42,7 +42,7 @@ object BillingClient {
|
||||||
enum class ProductType { INAPP }
|
enum class ProductType { INAPP }
|
||||||
|
|
||||||
object Builder {
|
object Builder {
|
||||||
fun enablePendingPurchases(params: PendingPurchasesParams) = this
|
fun enablePendingPurchases() = this
|
||||||
fun setListener(listener: PurchasesUpdatedListener) = this
|
fun setListener(listener: PurchasesUpdatedListener) = this
|
||||||
fun build() = BillingClient
|
fun build() = BillingClient
|
||||||
}
|
}
|
||||||
|
@ -143,13 +143,4 @@ object QueryPurchasesParams {
|
||||||
fun newBuilder() = this
|
fun newBuilder() = this
|
||||||
fun setProductType(type: BillingClient.ProductType) = this
|
fun setProductType(type: BillingClient.ProductType) = this
|
||||||
fun build() = this
|
fun build() = this
|
||||||
}
|
|
||||||
|
|
||||||
object PendingPurchasesParams {
|
|
||||||
object Builder {
|
|
||||||
fun enableOneTimeProducts() = this
|
|
||||||
fun build() = PendingPurchasesParams
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newBuilder() = Builder
|
|
||||||
}
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.timelimit.android.ui.manage.category.blocked_times
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class MinutesOfWeekItemsTest {
|
||||||
|
@Test
|
||||||
|
fun canGetAllItems() {
|
||||||
|
for (i in 0 until MinuteOfWeekItems.itemsPerWeek) {
|
||||||
|
MinuteOfWeekItems.getItemAtPosition(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reverseLookupReturnsSameItem() {
|
||||||
|
for (i in 0 until MinuteOfWeekItems.itemsPerWeek) {
|
||||||
|
val item = MinuteOfWeekItems.getItemAtPosition(i)
|
||||||
|
val index = MinuteOfWeekItems.getPositionOfItem(item)
|
||||||
|
|
||||||
|
assertEquals(item.toString(), i, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
build.gradle
12
build.gradle
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2025 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -15,10 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.11.1' apply false
|
id 'com.android.application' version '8.3.1' apply false
|
||||||
id 'com.android.library' version '8.11.1' apply false
|
id 'com.android.library' version '8.3.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version "2.0.21" apply false
|
id 'org.jetbrains.kotlin.android' version "1.9.20" apply false
|
||||||
id 'com.google.devtools.ksp' version '1.9.21-1.0.16' apply false
|
id 'com.google.devtools.ksp' version '1.8.21-1.0.11' apply false
|
||||||
id 'androidx.navigation.safeargs' version '2.6.0' apply false
|
id 'androidx.navigation.safeargs' version '2.6.0' apply false
|
||||||
id 'com.squareup.wire' version '5.3.5' apply false
|
id 'com.squareup.wire' version '4.4.3' apply false
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
org.gradle.jvmargs=-Xmx4096m
|
org.gradle.jvmargs=-Xmx4096m
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||||
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
|
distributionSha256Sum=f2b9ed0faf8472cbe469255ae6c86eddb77076c75191741b4a462f33128dd419
|
Loading…
Add table
Add a link
Reference in a new issue