From 6b09de4c59280f9d56d811823a4ab0defca89b47 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 14 Oct 2024 02:00:00 +0200 Subject: [PATCH 01/12] Update buildtools --- app/build.gradle | 4 ++-- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 566f68b..5994748 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -146,8 +146,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } kotlinOptions { diff --git a/build.gradle b/build.gradle index 703c8bc..00a3688 100644 --- a/build.gradle +++ b/build.gradle @@ -15,8 +15,8 @@ */ plugins { - id 'com.android.application' version '8.5.1' apply false - id 'com.android.library' version '8.5.1' apply false + id 'com.android.application' version '8.7.1' apply false + id 'com.android.library' version '8.7.1' apply false id 'org.jetbrains.kotlin.android' version "1.9.21" apply false id 'com.google.devtools.ksp' version '1.9.21-1.0.16' apply false id 'androidx.navigation.safeargs' version '2.6.0' apply false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9118fc8..bfe49c6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip -distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0 \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip +distributionSha256Sum=258e722ec21e955201e31447b0aed14201765a3bfbae296a46cf60b70e66db70 \ No newline at end of file From 6a4b4505bb61a03ee5dbe1abd2ae5e4c54d324ab Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 14 Oct 2024 02:00:00 +0200 Subject: [PATCH 02/12] Update dependencies --- app/build.gradle | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5994748..27898e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -167,8 +167,8 @@ wire { dependencies { def nav_version = "2.5.3" def room_version = "2.6.1" - def work_version = '2.9.0' - def paging_version = "3.3.1" + def work_version = '2.9.1' + def paging_version = "3.3.2" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21" @@ -177,12 +177,12 @@ dependencies { implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation "com.google.android.material:material:1.12.0" - implementation 'androidx.compose.material:material:1.6.8' - implementation 'androidx.activity:activity-compose:1.9.1' + implementation 'androidx.compose.material:material:1.7.4' + implementation 'androidx.activity:activity-compose:1.9.3' implementation "com.google.accompanist:accompanist-flowlayout:0.30.0" - implementation 'androidx.compose.material:material-icons-extended:1.6.8' - debugImplementation "androidx.compose.ui:ui-tooling:1.6.8" - implementation 'androidx.fragment:fragment-ktx:1.8.2' + implementation 'androidx.compose.material:material-icons-extended:1.7.4' + debugImplementation "androidx.compose.ui:ui-tooling:1.7.4" + implementation 'androidx.fragment:fragment-ktx:1.8.4' implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version" @@ -203,7 +203,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.6.1' + androidTestImplementation 'androidx.test:runner:1.6.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' implementation 'org.mindrot:jbcrypt:0.4' @@ -216,7 +216,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp-tls:4.9.3' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' - googleApiImplementation "com.android.billingclient:billing-ktx:7.0.0" + googleApiImplementation "com.android.billingclient:billing-ktx:7.1.1" implementation('io.socket:socket.io-client:2.0.0') { exclude group: 'org.json', module: 'json' From 6e9641638fe865a3f1fdb8a7adf684ce156eefd5 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 14 Oct 2024 02:00:00 +0200 Subject: [PATCH 03/12] Update target sdk --- app/build.gradle | 4 ++-- .../android/foregroundapp/LollipopForegroundAppHelper.kt | 4 ++-- .../java/io/timelimit/android/update/UpdateIntegration.kt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 27898e2..786d148 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,11 +25,11 @@ plugins { android { namespace 'io.timelimit.android' - compileSdkVersion 34 + compileSdk 35 defaultConfig { applicationId "io.timelimit.android" minSdkVersion 26 - targetSdkVersion 34 + targetSdkVersion 35 versionCode 218 versionName "7.1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/io/timelimit/android/integration/platform/android/foregroundapp/LollipopForegroundAppHelper.kt b/app/src/main/java/io/timelimit/android/integration/platform/android/foregroundapp/LollipopForegroundAppHelper.kt index 417e86e..ddabc3f 100644 --- a/app/src/main/java/io/timelimit/android/integration/platform/android/foregroundapp/LollipopForegroundAppHelper.kt +++ b/app/src/main/java/io/timelimit/android/integration/platform/android/foregroundapp/LollipopForegroundAppHelper.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * TimeLimit Copyright 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 @@ -184,7 +184,7 @@ class LollipopForegroundAppHelper(context: Context) : UsageStatsForegroundAppHel } 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 } != null } catch (ex: PackageManager.NameNotFoundException) { diff --git a/app/src/main/java/io/timelimit/android/update/UpdateIntegration.kt b/app/src/main/java/io/timelimit/android/update/UpdateIntegration.kt index 33a3684..f1bc4f8 100644 --- a/app/src/main/java/io/timelimit/android/update/UpdateIntegration.kt +++ b/app/src/main/java/io/timelimit/android/update/UpdateIntegration.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 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 @@ -70,12 +70,12 @@ object UpdateIntegration { val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // 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 { // old signature // 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())) } From d5bc1f98810f0408e00524645b33ff9cd797bff7 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 14 Oct 2024 02:00:00 +0200 Subject: [PATCH 04/12] Enable predictive back gesture --- app/src/main/AndroidManifest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7bb37a4..74a652d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,7 +55,9 @@ android:roundIcon="@mipmap/ic_launcher_round" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:enableOnBackInvokedCallback="true" + tools:targetApi="tiramisu"> From 1cdaed60e1b4c365e7d1f82ed173f5b68b95b5fe Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 05/12] Fix incorrect used time at rules that apply per day at days where they do not apply --- .../appsandrules/AppAndRuleAdapter.kt | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/timelimit/android/ui/manage/category/appsandrules/AppAndRuleAdapter.kt b/app/src/main/java/io/timelimit/android/ui/manage/category/appsandrules/AppAndRuleAdapter.kt index 2312e86..be2a04a 100644 --- a/app/src/main/java/io/timelimit/android/ui/manage/category/appsandrules/AppAndRuleAdapter.kt +++ b/app/src/main/java/io/timelimit/android/ui/manage/category/appsandrules/AppAndRuleAdapter.kt @@ -31,6 +31,7 @@ import io.timelimit.android.logic.RemainingTime import io.timelimit.android.ui.manage.category.timelimit_rules.TimeLimitRulesHandlers import io.timelimit.android.ui.util.DateUtil import io.timelimit.android.util.DayNameUtil +import io.timelimit.android.util.Option import io.timelimit.android.util.TimeTextUtil import kotlin.properties.Delegates @@ -159,12 +160,24 @@ class AppAndRuleAdapter: RecyclerView.Adapter() { val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding val context = binding.root.context val usedTime = date?.let { date -> - RemainingTime.getUsedTime( - usedTimes = usedTimes, - rule = rule, - firstDayOfWeekAsEpochDay = date.firstDayOfWeekAsEpochDay, - dayOfWeekForDailyRule = if (rule.perDay) date.dayOfWeek else null - ).toInt() + val dayOfWeekForDailyRule: Option? = + if (rule.perDay) { + (0 until 7) + .map { (7 + date.dayOfWeek - it) % 7 } // make the current day the last one + .firstOrNull { rule.dayMask.toInt() and (1 shl it) != 0 } + ?.let { Option.Some(it) } // skip calculation if no day matches + } 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 binding.maxTimeString = rule.maximumTimeInMillis.let { time -> From 376b7ca6dec9c5012c3a89132bf6579dae60e5d7 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 06/12] Release 7.2.0 --- app/build.gradle | 4 ++-- app/src/main/play/de-DE/whatsnew | 3 ++- app/src/main/play/en-US/whatsnew | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 786d148..cb2defa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,8 +30,8 @@ android { applicationId "io.timelimit.android" minSdkVersion 26 targetSdkVersion 35 - versionCode 218 - versionName "7.1.0" + versionCode 219 + versionName "7.2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" kapt { arguments { diff --git a/app/src/main/play/de-DE/whatsnew b/app/src/main/play/de-DE/whatsnew index afeb2da..7996b5a 100644 --- a/app/src/main/play/de-DE/whatsnew +++ b/app/src/main/play/de-DE/whatsnew @@ -1,2 +1,3 @@ -- verbesserter Umgang mit Fehlern im Hintergrund +- Anpassungen für Android 15 +- falsch angezeigte Nutzungsdauer bei Regeln die je Tag gelten an Tagen, an denen diese nicht gelten, behoben - enthaltene Komponenten aktualisiert diff --git a/app/src/main/play/en-US/whatsnew b/app/src/main/play/en-US/whatsnew index 4ff2027..09107ef 100644 --- a/app/src/main/play/en-US/whatsnew +++ b/app/src/main/play/en-US/whatsnew @@ -1,2 +1,3 @@ -- improved handling of errors in the background +- adjustments for Android 15 +- fix incorrect used time at rules that apply per day at days where they do not apply - update contained software From b0e5a338e61c0118eddcd1d88838615be566e100 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 07/12] Update buildtools --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 00a3688..406bfde 100644 --- a/build.gradle +++ b/build.gradle @@ -15,8 +15,8 @@ */ plugins { - id 'com.android.application' version '8.7.1' apply false - id 'com.android.library' version '8.7.1' apply false + id 'com.android.application' version '8.7.2' apply false + id 'com.android.library' version '8.7.2' apply false id 'org.jetbrains.kotlin.android' version "1.9.21" apply false id 'com.google.devtools.ksp' version '1.9.21-1.0.16' apply false id 'androidx.navigation.safeargs' version '2.6.0' apply false From 3206d925e3c10e3af9b11e2de3ae22d66231a3fa Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 08/12] Update dependencies --- app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cb2defa..775ce46 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -173,16 +173,16 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21" implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'androidx.core:core:1.13.1' + implementation 'androidx.core:core:1.15.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation "com.google.android.material:material:1.12.0" - implementation 'androidx.compose.material:material:1.7.4' + implementation 'androidx.compose.material:material:1.7.5' implementation 'androidx.activity:activity-compose:1.9.3' implementation "com.google.accompanist:accompanist-flowlayout:0.30.0" - implementation 'androidx.compose.material:material-icons-extended:1.7.4' - debugImplementation "androidx.compose.ui:ui-tooling:1.7.4" - implementation 'androidx.fragment:fragment-ktx:1.8.4' + implementation 'androidx.compose.material:material-icons-extended:1.7.5' + debugImplementation "androidx.compose.ui:ui-tooling:1.7.5" + implementation 'androidx.fragment:fragment-ktx:1.8.5' implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version" From 679276e3cffd5d9ca1358d77e3a217ff79e40944 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 09/12] Enable edge to edge support --- .../io/timelimit/android/ui/MainActivity.kt | 22 +++++++--- .../timelimit/android/ui/ScreenMultiplexer.kt | 43 ++++++++++--------- .../io/timelimit/android/ui/ScreenScaffold.kt | 18 ++++++-- .../timelimit/android/ui/lock/LockActivity.kt | 27 +++++++++++- .../android/ui/manipulation/AnnoyActivity.kt | 27 +++++++++++- .../ui/overview/overview/OverviewScreen.kt | 12 +++++- .../android/ui/update/UpdateActivity.kt | 40 +++++++++++++++++ .../ui/widget/config/WidgetConfigActivity.kt | 5 ++- 8 files changed, 159 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/io/timelimit/android/ui/MainActivity.kt b/app/src/main/java/io/timelimit/android/ui/MainActivity.kt index f5e1408..3bd2739 100644 --- a/app/src/main/java/io/timelimit/android/ui/MainActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/MainActivity.kt @@ -19,23 +19,24 @@ import android.Manifest import android.app.NotificationManager import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle import android.os.SystemClock import android.provider.Settings import android.util.Log +import androidx.activity.SystemBarStyle import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier @@ -129,10 +130,20 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De if (granted) mainModel.reportPermissionsChanged() } - @OptIn(ExperimentalAnimationApi::class) override fun onCreate(savedInstanceState: Bundle?) { 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) + ) + ) + supportActionBar!!.hide() U2fManager.setupActivity(this) @@ -314,9 +325,8 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De screen = screen, fragmentManager = supportFragmentManager, fragmentIds = mainModel.fragmentIds, - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) + modifier = Modifier.fillMaxSize(), + paddingValues = paddingValues ) }, showAuthenticationDialog = showAuthenticationDialog, diff --git a/app/src/main/java/io/timelimit/android/ui/ScreenMultiplexer.kt b/app/src/main/java/io/timelimit/android/ui/ScreenMultiplexer.kt index 301338e..5dad22f 100644 --- a/app/src/main/java/io/timelimit/android/ui/ScreenMultiplexer.kt +++ b/app/src/main/java/io/timelimit/android/ui/ScreenMultiplexer.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2023 Jonas Lochmann + * TimeLimit Copyright 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 @@ -15,6 +15,8 @@ */ 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.ui.Modifier import androidx.fragment.app.FragmentManager @@ -42,27 +44,28 @@ fun ScreenMultiplexer( screen: Screen?, fragmentManager: FragmentManager, fragmentIds: MutableSet, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + paddingValues: PaddingValues ) { when (screen) { null -> {/* nothing to do */ } - is Screen.FragmentScreen -> FragmentScreen(screen, fragmentManager, fragmentIds, modifier = modifier) - is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier) - is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier) - is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier) - is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen, modifier) - is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier) - is Screen.SetupConnectModePrivacyScreen -> SetupConnectedModePrivacyScreen(screen.customServerDomain, screen.accept, modifier) - 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) - is Screen.DeleteRegistration -> DeleteRegistrationScreen(screen.content, modifier) - is Screen.ManageBlockedTimes -> BlockedTimesScreen(screen.content, screen.intro, modifier) - is Screen.ChildUsageHistory -> UsageHistoryScreen(screen.content, modifier) - is Screen.SetupParentMailAuthentication -> AuthenticateByMailScreen(screen.content, modifier) - is Screen.SignupBlocked -> SignupBlockedScreen(modifier) - is Screen.SignInWrongMailAddress -> SignInWrongMailAddress(modifier) - is Screen.ConfirmNewParentAccount -> ConfirmNewParentAccount(confirm = screen.confirm, reject = screen.reject, modifier = modifier) - is Screen.ParentBaseConfiguration -> ParentBaseConfiguration(content = screen.content, modifier = modifier) - is Screen.ParentSetupConsent -> ParentSetupConsent(content = screen.content, errorDialog = screen.errorDialog, modifier = modifier) + is Screen.FragmentScreen -> FragmentScreen(screen, fragmentManager, fragmentIds, modifier = modifier.padding(paddingValues)) + is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier, paddingValues = paddingValues) + is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier.padding(paddingValues)) + is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier.padding(paddingValues)) + is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen, modifier.padding(paddingValues)) + is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier.padding(paddingValues)) + is Screen.SetupConnectModePrivacyScreen -> SetupConnectedModePrivacyScreen(screen.customServerDomain, screen.accept, modifier.padding(paddingValues)) + is Screen.SetupSelectConnectedModeScreen -> SelectConnectedModeScreen(mailLogin = screen.mailLogin, codeLogin = screen.codeLogin, modifier = modifier.padding(paddingValues)) + is Screen.SetupSelectModeScreen -> SelectModeScreen(selectLocal = screen.selectLocal, selectConnected = screen.selectConnected, selectUninstall = screen.selectUninstall, modifier = modifier.padding(paddingValues)) + is Screen.DeleteRegistration -> DeleteRegistrationScreen(screen.content, modifier.padding(paddingValues)) + is Screen.ManageBlockedTimes -> BlockedTimesScreen(screen.content, screen.intro, modifier.padding(paddingValues)) + is Screen.ChildUsageHistory -> UsageHistoryScreen(screen.content, modifier.padding(paddingValues)) + is Screen.SetupParentMailAuthentication -> AuthenticateByMailScreen(screen.content, modifier.padding(paddingValues)) + is Screen.SignupBlocked -> SignupBlockedScreen(modifier.padding(paddingValues)) + is Screen.SignInWrongMailAddress -> SignInWrongMailAddress(modifier.padding(paddingValues)) + is Screen.ConfirmNewParentAccount -> ConfirmNewParentAccount(confirm = screen.confirm, reject = screen.reject, modifier = modifier.padding(paddingValues)) + is Screen.ParentBaseConfiguration -> ParentBaseConfiguration(content = screen.content, modifier = modifier.padding(paddingValues)) + is Screen.ParentSetupConsent -> ParentSetupConsent(content = screen.content, errorDialog = screen.errorDialog, modifier = modifier.padding(paddingValues)) } } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/ScreenScaffold.kt b/app/src/main/java/io/timelimit/android/ui/ScreenScaffold.kt index 8429b74..583136f 100644 --- a/app/src/main/java/io/timelimit/android/ui/ScreenScaffold.kt +++ b/app/src/main/java/io/timelimit/android/ui/ScreenScaffold.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2023 Jonas Lochmann + * TimeLimit Copyright 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 @@ -17,8 +17,13 @@ package io.timelimit.android.ui import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.PaddingValues 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.material.* import androidx.compose.material.icons.Icons @@ -37,6 +42,7 @@ import io.timelimit.android.ui.model.Screen import io.timelimit.android.ui.model.Title import io.timelimit.android.ui.model.UpdateStateCommand +@OptIn(ExperimentalLayoutApi::class) @Composable fun ScreenScaffold( screen: Screen?, @@ -111,7 +117,9 @@ fun ScreenScaffold( } } } - } + }, + modifier = Modifier, + windowInsets = WindowInsets.statusBarsIgnoringVisibility ) }, bottomBar = { @@ -159,7 +167,8 @@ fun ScreenScaffold( Text(title) } } - } + }, + windowInsets = WindowInsets.navigationBarsIgnoringVisibility ) }, floatingActionButton = { @@ -170,6 +179,7 @@ fun ScreenScaffold( } }, snackbarHost = { SnackbarHost(snackbarHostState ?: it) }, - content = content + content = content, + contentWindowInsets = WindowInsets.systemBarsIgnoringVisibility ) } \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt b/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt index 3e7d114..18aa130 100644 --- a/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * TimeLimit Copyright 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 @@ -18,11 +18,17 @@ package io.timelimit.android.ui.lock import android.app.ActivityManager import android.content.Context import android.content.Intent +import android.content.res.Configuration import android.os.Build import android.os.Bundle import androidx.activity.OnBackPressedCallback +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import androidx.lifecycle.MutableLiveData import androidx.viewpager.widget.ViewPager import io.timelimit.android.R @@ -90,6 +96,17 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De override fun onCreate(savedInstanceState: Bundle?) { 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) val adapter = LockActivityAdapter(supportFragmentManager, this) @@ -97,6 +114,14 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De val binding = LockActivityBinding.inflate(layoutInflater) setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + + view.updatePadding(insets.left, insets.top, insets.right, insets.bottom) + + WindowInsetsCompat.CONSUMED + } + syncModel.statusText.observe(this) { supportActionBar?.subtitle = it } currentInstances.add(this) diff --git a/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt b/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt index 612ceb1..0a57ef9 100644 --- a/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2023 Jonas Lochmann + * TimeLimit Copyright 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 @@ -18,12 +18,18 @@ package io.timelimit.android.ui.manipulation import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo +import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.OnBackPressedCallback +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import androidx.lifecycle.map import io.timelimit.android.BuildConfig import io.timelimit.android.R @@ -65,6 +71,17 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D override fun onCreate(savedInstanceState: Bundle?) { 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) val logic = DefaultAppLogic.with(this) @@ -72,6 +89,14 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D val binding = AnnoyActivityBinding.inflate(layoutInflater) setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + + view.updatePadding(insets.left, insets.top, insets.right, insets.bottom) + + WindowInsetsCompat.CONSUMED + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val systemImageApps = packageManager.getInstalledApplications(0) .filter { it.flags and ApplicationInfo.FLAG_SYSTEM == ApplicationInfo.FLAG_SYSTEM } diff --git a/app/src/main/java/io/timelimit/android/ui/overview/overview/OverviewScreen.kt b/app/src/main/java/io/timelimit/android/ui/overview/overview/OverviewScreen.kt index 087d884..a1635f4 100644 --- a/app/src/main/java/io/timelimit/android/ui/overview/overview/OverviewScreen.kt +++ b/app/src/main/java/io/timelimit/android/ui/overview/overview/OverviewScreen.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2023 Jonas Lochmann + * TimeLimit Copyright 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 @@ -19,16 +19,24 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.timelimit.android.ui.model.main.OverviewHandling @Composable fun OverviewScreen( screen: OverviewHandling.OverviewScreen, + paddingValues: PaddingValues, modifier: Modifier = Modifier ) { LazyColumn ( - contentPadding = PaddingValues(0.dp, 8.dp), + contentPadding = object: PaddingValues { + 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), modifier = modifier ) { diff --git a/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt b/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt index bed632a..53e11c1 100644 --- a/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt @@ -1,7 +1,28 @@ +/* + * TimeLimit Copyright 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 . + */ package io.timelimit.android.ui.update +import android.content.res.Configuration import android.os.Bundle +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import androidx.databinding.DataBindingUtil import io.timelimit.android.R import io.timelimit.android.databinding.UpdateActivityBinding @@ -11,8 +32,27 @@ class UpdateActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { 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) + ) + ) + val binding = DataBindingUtil.setContentView(this, R.layout.update_activity) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + + view.updatePadding(insets.left, insets.top, insets.right, insets.bottom) + + WindowInsetsCompat.CONSUMED + } + UpdateView.bind( view = binding.update, lifecycleOwner = this, diff --git a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt index cfa4da5..2373c02 100644 --- a/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/widget/config/WidgetConfigActivity.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * TimeLimit Copyright 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 @@ -19,6 +19,7 @@ import android.appwidget.AppWidgetManager import android.content.Intent import android.os.Bundle import android.widget.Toast +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.fragment.app.FragmentActivity import io.timelimit.android.R @@ -30,6 +31,8 @@ class WidgetConfigActivity: FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() + if (model.state.value == WidgetConfigModel.State.WaitingForInit) { model.init( intent?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) From bc0e83b916f5ef03ea974ea01bfed1abbec660ea Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 10/12] Improve edge to edge support --- app/build.gradle | 1 + .../timelimit/android/ui/lock/LockActivity.kt | 152 +++++++++++++----- .../android/ui/lock/LockActivityAdapter.kt | 44 ----- .../android/ui/manipulation/AnnoyActivity.kt | 100 +++++++----- .../android/ui/update/UpdateActivity.kt | 52 ++++-- app/src/main/res/layout/lock_activity.xml | 50 ------ 6 files changed, 209 insertions(+), 190 deletions(-) delete mode 100644 app/src/main/java/io/timelimit/android/ui/lock/LockActivityAdapter.kt delete mode 100644 app/src/main/res/layout/lock_activity.xml diff --git a/app/build.gradle b/app/build.gradle index 775ce46..13e6668 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -183,6 +183,7 @@ dependencies { implementation 'androidx.compose.material:material-icons-extended:1.7.5' debugImplementation "androidx.compose.ui:ui-tooling:1.7.5" implementation 'androidx.fragment:fragment-ktx:1.8.5' + implementation 'androidx.fragment:fragment-compose:1.8.5' implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version" diff --git a/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt b/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt index 18aa130..62e6c5d 100644 --- a/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/lock/LockActivity.kt @@ -23,16 +23,30 @@ import android.os.Build import android.os.Bundle import androidx.activity.OnBackPressedCallback import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.lifecycle.MutableLiveData -import androidx.viewpager.widget.ViewPager +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +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.databinding.LockActivityBinding +import io.timelimit.android.data.model.UserType import io.timelimit.android.extensions.showSafe import io.timelimit.android.logic.BlockingReason import io.timelimit.android.logic.DefaultAppLogic @@ -40,11 +54,12 @@ import io.timelimit.android.sync.network.UpdatePrimaryDeviceRequestType import io.timelimit.android.u2f.U2fManager import io.timelimit.android.u2f.protocol.U2FDevice 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.NewLoginFragment import io.timelimit.android.ui.main.ActivityViewModel import io.timelimit.android.ui.main.ActivityViewModelHolder -import io.timelimit.android.ui.main.AuthenticationFab import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment import io.timelimit.android.ui.util.SyncStatusModel @@ -91,8 +106,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De null } - private val showAuth = MutableLiveData().apply { value = false } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -107,19 +120,98 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De ) ) + supportActionBar!!.hide() + U2fManager.setupActivity(this) - val adapter = LockActivityAdapter(supportFragmentManager, this) + val subtitleLive = syncModel.statusText.asFlow() + val showTasksLive = model.content.map { + val isTimeOver = it is LockscreenContent.Blocked.BlockedCategory && it.blockingHandling.activityBlockingReason == BlockingReason.TimeOver - val binding = LockActivityBinding.inflate(layoutInflater) - setContentView(binding.root) + isTimeOver + }.asFlow() - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + setContent { + 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) - view.updatePadding(insets.left, insets.top, insets.right, insets.bottom) + Theme { + ScreenScaffold( + screen = null, + title = getString(R.string.app_name), + subtitle = subtitle, + backStack = emptyList(), + snackbarHostState = null, + content = { padding -> + Column (Modifier.fillMaxSize().padding(padding)) { + 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) + ) + } - WindowInsetsCompat.CONSUMED + 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) + ) + } + } + + HorizontalPager( + pager, + Modifier.weight(1.0F, fill = true), + pageContent = { index -> + when (index) { + 0 -> AndroidFragment(Modifier.fillMaxSize()) + 1 -> AndroidFragment(Modifier.fillMaxSize()) + 2 -> AndroidFragment(Modifier.fillMaxSize()) + } + } + ) + } + }, + executeCommand = {}, + showAuthenticationDialog = + if (pager.currentPage == 1 && !isAuthenticated) ({ showAuthenticationScreen() }) + else null + ) + } } syncModel.statusText.observe(this) { supportActionBar?.subtitle = it } @@ -128,8 +220,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De model.init(blockedPackageName, blockedActivityName) - binding.pager.adapter = adapter - model.content.observe(this) { if (isResumed && it is LockscreenContent.Blocked.BlockedCategory && it.reason == BlockingReason.RequiresCurrentDevice && !model.didOpenSetCurrentDeviceScreen) { model.didOpenSetCurrentDeviceScreen = true @@ -140,30 +230,12 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De } } - AuthenticationFab.manageAuthenticationFab( - fab = binding.fab, - shouldHighlight = activityModel.shouldHighlightAuthenticationButton, - authenticatedUser = activityModel.authenticatedUser, - activity = this, - doesSupportAuth = showAuth - ) + activityModel.shouldHighlightAuthenticationButton.observe(this) { + if (it) { + activityModel.shouldHighlightAuthenticationButton.postValue(false) - binding.fab.setOnClickListener { showAuthenticationScreen() } - - binding.pager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - - showAuth.value = position == 1 + showAuthenticationScreen() } - }) - - 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) { diff --git a/app/src/main/java/io/timelimit/android/ui/lock/LockActivityAdapter.kt b/app/src/main/java/io/timelimit/android/ui/lock/LockActivityAdapter.kt deleted file mode 100644 index 04080e3..0000000 --- a/app/src/main/java/io/timelimit/android/ui/lock/LockActivityAdapter.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * TimeLimit Copyright 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 . - */ - -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() - }) -} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt b/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt index 0a57ef9..6bf0fa7 100644 --- a/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/manipulation/AnnoyActivity.kt @@ -22,14 +22,17 @@ import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.util.Log +import android.view.LayoutInflater import androidx.activity.OnBackPressedCallback import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding +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 io.timelimit.android.BuildConfig import io.timelimit.android.R @@ -40,6 +43,8 @@ import io.timelimit.android.integration.platform.android.AndroidIntegrationApps import io.timelimit.android.logic.DefaultAppLogic import io.timelimit.android.u2f.U2fManager 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.login.AuthTokenLoginProcessor import io.timelimit.android.ui.login.NewLoginFragment @@ -71,6 +76,8 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val logic = DefaultAppLogic.with(this) + val isNightMode = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES @@ -82,21 +89,63 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D ) ) - U2fManager.setupActivity(this) + supportActionBar!!.hide() - val logic = 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 = AnnoyActivityBinding.inflate(LayoutInflater.from(it)) - val binding = AnnoyActivityBinding.inflate(layoutInflater) - setContentView(binding.root) + logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown -> + binding.canRequestUnlock = countdown == 0L + binding.countdownText = getString(R.string.annoy_timer, TimeTextUtil.seconds((countdown / 1000).toInt(), this@AnnoyActivity)) + } - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + logic.deviceEntry.map { + val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty) + .current + .map { getString(it.labelResourceId) } - view.updatePadding(insets.left, insets.top, insets.right, insets.bottom) + if (reasonItems.isEmpty()) { + null + } else { + getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", ")) + } + }.observe(this) { binding.reasonText = it } - WindowInsetsCompat.CONSUMED + 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) { val systemImageApps = packageManager.getInstalledApplications(0) .filter { it.flags and ApplicationInfo.FLAG_SYSTEM == ApplicationInfo.FLAG_SYSTEM } @@ -117,35 +166,6 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D 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 -> if (user?.second?.type == UserType.Parent) { logic.annoyLogic.doParentTempUnlock() diff --git a/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt b/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt index 53e11c1..e213776 100644 --- a/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/update/UpdateActivity.kt @@ -17,16 +17,20 @@ package io.timelimit.android.ui.update import android.content.res.Configuration 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.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updatePadding -import androidx.databinding.DataBindingUtil +import androidx.compose.foundation.layout.fillMaxSize +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.databinding.UpdateActivityBinding import io.timelimit.android.logic.DefaultAppLogic +import io.timelimit.android.ui.ScreenScaffold +import io.timelimit.android.ui.Theme class UpdateActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -43,21 +47,37 @@ class UpdateActivity: AppCompatActivity() { ) ) - val binding = DataBindingUtil.setContentView(this, R.layout.update_activity) + supportActionBar!!.hide() - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + 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)) - view.updatePadding(insets.left, insets.top, insets.right, insets.bottom) + UpdateView.bind( + view = binding.update, + lifecycleOwner = this, + fragmentManager = supportFragmentManager, + appLogic = DefaultAppLogic.with(this) + ) - WindowInsetsCompat.CONSUMED + binding.root + }, + modifier = Modifier.fillMaxSize().padding(padding) + ) + }, + executeCommand = {}, + showAuthenticationDialog = null + ) + } } - - UpdateView.bind( - view = binding.update, - lifecycleOwner = this, - fragmentManager = supportFragmentManager, - appLogic = DefaultAppLogic.with(this) - ) } } \ No newline at end of file diff --git a/app/src/main/res/layout/lock_activity.xml b/app/src/main/res/layout/lock_activity.xml deleted file mode 100644 index 1ec69cf..0000000 --- a/app/src/main/res/layout/lock_activity.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file From 13fe10b543b3fd57211a45c5f409a18be587dd74 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 11/12] Disable room warnings regarding internally used column --- app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt b/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt index e1d20d7..8b707f8 100644 --- a/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt +++ b/app/src/main/java/io/timelimit/android/data/dao/UsedTimeDao.kt @@ -19,6 +19,7 @@ import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import androidx.room.RoomWarnings import io.timelimit.android.data.model.UsedTimeItem import io.timelimit.android.data.model.UsedTimeListItem import io.timelimit.android.livedata.ignoreUnchanged @@ -68,11 +69,13 @@ abstract class UsedTimeDao { // 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.CURSOR_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") abstract fun getUsedTimeListItemsByCategoryId(categoryId: String): Flow> // 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.CURSOR_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") abstract fun getUsedTimeListItemsByUserId(userId: String): Flow> } From 2ed75c7f0feaf834ab210740085e9eb4c92bb134 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 28 Oct 2024 01:00:00 +0100 Subject: [PATCH 12/12] Release 7.2.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 13e6668..ac9ed68 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,8 +30,8 @@ android { applicationId "io.timelimit.android" minSdkVersion 26 targetSdkVersion 35 - versionCode 219 - versionName "7.2.0" + versionCode 220 + versionName "7.2.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" kapt { arguments {