mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Compare commits
23 commits
release-7.
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6c02c4e4c7 | ||
![]() |
c746770e89 | ||
![]() |
0505fa0f8b | ||
![]() |
4498aa191f | ||
![]() |
3d49bd8229 | ||
![]() |
abe6d47a96 | ||
![]() |
6ba9614557 | ||
![]() |
53732e47dd | ||
![]() |
acdd83c39a | ||
![]() |
f4c35dec55 | ||
![]() |
5f69014850 | ||
![]() |
d02631d5c1 | ||
![]() |
801bf760dc | ||
![]() |
daff66b26b | ||
![]() |
a36ffa861a | ||
![]() |
de0b6d1c8f | ||
![]() |
2e68798e2c | ||
![]() |
73a82f3cf8 | ||
![]() |
bd88da38d2 | ||
![]() |
e54ff92cbb | ||
![]() |
8e02eb3fb3 | ||
![]() |
11a47c5f30 | ||
![]() |
15ae018589 |
34 changed files with 380 additions and 236 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,3 +10,4 @@
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.idea
|
.idea
|
||||||
|
.kotlin
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,17 +21,18 @@ 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 35
|
compileSdk 36
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.timelimit.android"
|
applicationId "io.timelimit.android"
|
||||||
minSdkVersion 26
|
minSdkVersion 26
|
||||||
targetSdkVersion 35
|
targetSdkVersion 36
|
||||||
versionCode 220
|
versionCode 224
|
||||||
versionName "7.2.1"
|
versionName "7.3.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
kapt {
|
kapt {
|
||||||
arguments {
|
arguments {
|
||||||
|
@ -166,24 +167,24 @@ wire {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def nav_version = "2.5.3"
|
def nav_version = "2.5.3"
|
||||||
def room_version = "2.6.1"
|
def room_version = "2.7.2"
|
||||||
def work_version = '2.9.1'
|
def work_version = '2.10.2'
|
||||||
def paging_version = "3.3.2"
|
def paging_version = "3.3.6"
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.10"
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||||
implementation 'androidx.core:core:1.15.0'
|
implementation 'androidx.core:core:1.16.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.1.0'
|
||||||
implementation "com.google.android.material:material:1.12.0"
|
implementation "com.google.android.material:material:1.12.0"
|
||||||
implementation 'androidx.compose.material:material:1.7.5'
|
implementation 'androidx.compose.material:material:1.8.3'
|
||||||
implementation 'androidx.activity:activity-compose:1.9.3'
|
implementation 'androidx.activity:activity-compose:1.10.1'
|
||||||
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.5'
|
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
|
||||||
debugImplementation "androidx.compose.ui:ui-tooling:1.7.5"
|
debugImplementation "androidx.compose.ui:ui-tooling:1.8.3"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.8.5'
|
implementation 'androidx.fragment:fragment-ktx:1.8.8'
|
||||||
implementation 'androidx.fragment:fragment-compose:1.8.5'
|
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"
|
||||||
|
@ -200,8 +201,8 @@ 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.7.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.6.2'
|
androidTestImplementation 'androidx.test:runner:1.6.2'
|
||||||
|
@ -217,7 +218,7 @@ dependencies {
|
||||||
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:7.1.1"
|
googleApiImplementation "com.android.billingclient:billing-ktx:8.0.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'
|
||||||
|
@ -229,5 +230,5 @@ dependencies {
|
||||||
|
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
|
|
||||||
api "com.squareup.wire:wire-runtime:4.4.3"
|
api "com.squareup.wire:wire-runtime:5.3.5"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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
|
||||||
|
@ -69,13 +69,13 @@ abstract class UsedTimeDao {
|
||||||
|
|
||||||
// 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
|
// this warns about an unused column, but this column is used in the ORDER BY
|
||||||
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
@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
|
// this warns about an unused column, but this column is used in the ORDER BY
|
||||||
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
|
@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 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,4 +295,7 @@ 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 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,4 +73,5 @@ 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
|
||||||
}
|
}
|
|
@ -134,9 +134,19 @@ class AndroidDeviceOwnerApi(
|
||||||
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) return false
|
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) return false
|
||||||
if (!devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) return false
|
if (!devicePolicyManager.isDeviceOwnerApp(componentName.packageName)) return false
|
||||||
|
|
||||||
return devicePolicyManager.setPermissionGrantState(
|
try {
|
||||||
componentName, componentName.packageName, Manifest.permission.ACCESS_FINE_LOCATION,
|
return devicePolicyManager.setPermissionGrantState(
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
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 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,6 +27,10 @@ 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) {
|
||||||
|
@ -40,6 +44,18 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +76,30 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -507,7 +507,6 @@ 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) {
|
||||||
|
@ -519,16 +518,24 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
||||||
context.packageName,
|
context.packageName,
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
).let {
|
).let {
|
||||||
if (it == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
|
try {
|
||||||
policyManager.setPermissionGrantState(
|
if (it == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
|
||||||
deviceAdmin,
|
policyManager.setPermissionGrantState(
|
||||||
context.packageName,
|
deviceAdmin,
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
context.packageName,
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
if (ContextCompat.checkSelfPermission(
|
||||||
else
|
context,
|
||||||
DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
)
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
)
|
||||||
|
DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
||||||
|
else
|
||||||
|
DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (ex: SecurityException) {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +556,6 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
|
||||||
}
|
}
|
||||||
} 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) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,6 +65,11 @@ 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 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,10 +19,12 @@ 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
|
||||||
|
@ -107,12 +109,23 @@ 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(emptySet())
|
applyBlockedFeatures(
|
||||||
|
featureToAllowDefaults.filter { !it.value }.map { it.key }.toSet()
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -191,9 +204,15 @@ 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) }
|
|
||||||
.map { it.appSpecifierString.substring(DummyApps.FEATURE_APP_PREFIX.length) }
|
val featuresToAllow = featureToAllowDefaults + effectiveFeatures.associate {
|
||||||
.toSet()
|
Pair(
|
||||||
|
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)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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].toUInt() or
|
val counter = rawResponse.payload[4].toUByte().toUInt() or
|
||||||
rawResponse.payload[3].toUInt().shl(8) or
|
rawResponse.payload[3].toUByte().toUInt().shl(8) or
|
||||||
rawResponse.payload[2].toUInt().shl(16) or
|
rawResponse.payload[2].toUByte().toUInt().shl(16) or
|
||||||
rawResponse.payload[1].toUInt().shl(24)
|
rawResponse.payload[1].toUByte().toUInt().shl(24)
|
||||||
|
|
||||||
val signature = rawResponse.payload.sliceArray(5 until rawResponse.payload.size)
|
val signature = rawResponse.payload.sliceArray(5 until rawResponse.payload.size)
|
||||||
|
|
||||||
|
|
|
@ -144,8 +144,6 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
supportActionBar!!.hide()
|
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
U2fManager.setupActivity(this)
|
||||||
|
|
||||||
NotificationChannels.createNotificationChannels(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager, this)
|
NotificationChannels.createNotificationChannels(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager, this)
|
||||||
|
|
|
@ -51,6 +51,7 @@ 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)?
|
||||||
) {
|
) {
|
||||||
|
@ -58,69 +59,73 @@ fun ScreenScaffold(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
Column {
|
||||||
title = {
|
TopAppBar(
|
||||||
Column {
|
title = {
|
||||||
Text(
|
Column {
|
||||||
title,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
if (subtitle != null) {
|
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
title,
|
||||||
style = MaterialTheme.typography.subtitle1,
|
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navigationIcon = if (screen?.state?.previous != null) ({
|
|
||||||
IconButton(onClick = { executeCommand(UpdateStateCommand.BackToPreviousScreen) }) {
|
|
||||||
Icon(Icons.Default.ArrowBack, stringResource(R.string.generic_back))
|
|
||||||
}
|
|
||||||
}) else null,
|
|
||||||
actions = {
|
|
||||||
for (icon in screen?.toolbarIcons ?: emptyList()) {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
if (icon.action != null) executeCommand(icon.action)
|
|
||||||
|
|
||||||
icon.handler()
|
if (subtitle != null) {
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Icon(icon.icon, stringResource(icon.labelResource))
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
navigationIcon = if (screen?.state?.previous != null) ({
|
||||||
|
IconButton(onClick = { executeCommand(UpdateStateCommand.BackToPreviousScreen) }) {
|
||||||
|
Icon(Icons.Default.ArrowBack, stringResource(R.string.generic_back))
|
||||||
|
}
|
||||||
|
}) else null,
|
||||||
|
actions = {
|
||||||
|
for (icon in screen?.toolbarIcons ?: emptyList()) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
if (icon.action != null) executeCommand(icon.action)
|
||||||
|
|
||||||
if (screen?.toolbarOptions?.isEmpty() == false) {
|
icon.handler()
|
||||||
IconButton(onClick = { expandDropdown = true }) {
|
}
|
||||||
Icon(Icons.Default.MoreVert, stringResource(R.string.generic_menu))
|
) {
|
||||||
|
Icon(icon.icon, stringResource(icon.labelResource))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
if (screen?.toolbarOptions?.isEmpty() == false) {
|
||||||
expanded = expandDropdown,
|
IconButton(onClick = { expandDropdown = true }) {
|
||||||
onDismissRequest = { expandDropdown = false }
|
Icon(Icons.Default.MoreVert, stringResource(R.string.generic_menu))
|
||||||
) {
|
}
|
||||||
for (option in screen.toolbarOptions) {
|
|
||||||
DropdownMenuItem(onClick = {
|
|
||||||
if (option.action != null) executeCommand(option.action)
|
|
||||||
|
|
||||||
option.handler()
|
DropdownMenu(
|
||||||
|
expanded = expandDropdown,
|
||||||
|
onDismissRequest = { expandDropdown = false }
|
||||||
|
) {
|
||||||
|
for (option in screen.toolbarOptions) {
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
if (option.action != null) executeCommand(option.action)
|
||||||
|
|
||||||
expandDropdown = false
|
option.handler()
|
||||||
}) {
|
|
||||||
Text(stringResource(option.labelResource))
|
expandDropdown = false
|
||||||
|
}) {
|
||||||
|
Text(stringResource(option.labelResource))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
modifier = Modifier,
|
||||||
modifier = Modifier,
|
windowInsets = WindowInsets.statusBarsIgnoringVisibility
|
||||||
windowInsets = WindowInsets.statusBarsIgnoringVisibility
|
)
|
||||||
)
|
|
||||||
|
extraBars?.invoke()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
val backStackColors = ButtonDefaults.textButtonColors(
|
val backStackColors = ButtonDefaults.textButtonColors(
|
||||||
|
|
|
@ -120,8 +120,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
supportActionBar!!.hide()
|
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
U2fManager.setupActivity(this)
|
||||||
|
|
||||||
val subtitleLive = syncModel.statusText.asFlow()
|
val subtitleLive = syncModel.statusText.asFlow()
|
||||||
|
@ -149,63 +147,62 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
backStack = emptyList(),
|
backStack = emptyList(),
|
||||||
snackbarHostState = null,
|
snackbarHostState = null,
|
||||||
content = { padding ->
|
extraBars = {
|
||||||
Column (Modifier.fillMaxSize().padding(padding)) {
|
TabRow(
|
||||||
TabRow(
|
pager.currentPage,
|
||||||
pager.currentPage,
|
indicator = { tabPositions ->
|
||||||
indicator = { tabPositions ->
|
// workaround for bug
|
||||||
// workaround for bug
|
TabRowDefaults.Indicator(
|
||||||
TabRowDefaults.Indicator(
|
Modifier.tabIndicatorOffset(tabPositions[
|
||||||
Modifier.tabIndicatorOffset(tabPositions[
|
pager.currentPage.coerceAtMost(tabPositions.size - 1)
|
||||||
pager.currentPage.coerceAtMost(tabPositions.size - 1)
|
])
|
||||||
])
|
)
|
||||||
)
|
}
|
||||||
}
|
) {
|
||||||
|
Tab(
|
||||||
|
selected = pager.currentPage == 0,
|
||||||
|
onClick = { pager.requestScrollToPage(0) }
|
||||||
) {
|
) {
|
||||||
Tab(
|
Text(
|
||||||
selected = pager.currentPage == 0,
|
stringResource(R.string.lock_tab_reason),
|
||||||
onClick = { pager.requestScrollToPage(0) }
|
Modifier.padding(16.dp)
|
||||||
) {
|
)
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalPager(
|
Tab(
|
||||||
pager,
|
selected = pager.currentPage == 1,
|
||||||
Modifier.weight(1.0F, fill = true),
|
onClick = { pager.requestScrollToPage(1) }
|
||||||
pageContent = { index ->
|
) {
|
||||||
when (index) {
|
Text(
|
||||||
0 -> AndroidFragment<LockReasonFragment>(Modifier.fillMaxSize())
|
stringResource(R.string.lock_tab_action),
|
||||||
1 -> AndroidFragment<LockActionFragment>(Modifier.fillMaxSize())
|
Modifier.padding(16.dp)
|
||||||
2 -> AndroidFragment<LockTaskFragment>(Modifier.fillMaxSize())
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
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 = {},
|
executeCommand = {},
|
||||||
showAuthenticationDialog =
|
showAuthenticationDialog =
|
||||||
if (pager.currentPage == 1 && !isAuthenticated) ({ showAuthenticationScreen() })
|
if (pager.currentPage == 1 && !isAuthenticated) ({ showAuthenticationScreen() })
|
||||||
|
@ -214,8 +211,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
|
|
||||||
|
|
||||||
currentInstances.add(this)
|
currentInstances.add(this)
|
||||||
|
|
||||||
model.init(blockedPackageName, blockedActivityName)
|
model.init(blockedPackageName, blockedActivityName)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2022 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
|
||||||
|
@ -16,14 +16,21 @@
|
||||||
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
|
||||||
|
@ -220,9 +227,29 @@ 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 - 2022 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
|
||||||
|
@ -16,9 +16,16 @@
|
||||||
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
|
||||||
|
@ -114,9 +121,30 @@ 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)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
backgroundColor = when (item.selected) {
|
backgroundColor = when (item.selected) {
|
||||||
true -> MaterialTheme.colors.secondary
|
true -> MaterialTheme.colors.secondary
|
||||||
|
|
|
@ -89,8 +89,6 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
supportActionBar!!.hide()
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
Theme {
|
Theme {
|
||||||
ScreenScaffold(
|
ScreenScaffold(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,6 +23,7 @@ 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
|
||||||
|
@ -333,6 +334,11 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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.animateItemPlacement())
|
ListCommon.SectionHeader(stringResource(R.string.overview_header_devices), Modifier.animateItem())
|
||||||
}
|
}
|
||||||
|
|
||||||
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.animateItemPlacement()
|
modifier = Modifier.animateItem()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
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
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
.clickable(onClick = { openAction(item) })
|
.clickable(onClick = { openAction(item) })
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.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
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.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
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.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.animateItemPlacement()
|
modifier = Modifier.animateItem()
|
||||||
) {
|
) {
|
||||||
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
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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.animateItemPlacement())
|
ListCommon.SectionHeader(stringResource(R.string.overview_header_users), Modifier.animateItem())
|
||||||
}
|
}
|
||||||
|
|
||||||
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.animateItemPlacement()
|
modifier = Modifier.animateItem()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screen.users.canShowMore) item (key = Pair("users", "more")) {
|
if (screen.users.canShowMore) item (key = Pair("users", "more")) {
|
||||||
ListCommon.ShowMoreItem (
|
ListCommon.ShowMoreItem (
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
action = screen.actions.showMoreUsers
|
action = screen.actions.showMoreUsers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ fun LazyItemScope.UserItem(
|
||||||
) {
|
) {
|
||||||
ListCardCommon.Card(
|
ListCardCommon.Card(
|
||||||
Modifier
|
Modifier
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.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 - 2023 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,9 +74,14 @@ 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(
|
||||||
.setListener(purchaseUpdatedListener)
|
PendingPurchasesParams
|
||||||
.build()
|
.newBuilder()
|
||||||
|
.enableOneTimeProducts()
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.setListener(purchaseUpdatedListener)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
val initBillingClient = _billingClient!!
|
val initBillingClient = _billingClient!!
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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,6 +24,7 @@ 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
|
||||||
|
@ -70,6 +71,11 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,6 @@ class UpdateActivity: AppCompatActivity() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
supportActionBar!!.hide()
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
Theme {
|
Theme {
|
||||||
ScreenScaffold(
|
ScreenScaffold(
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
- Anpassungen für Android 15
|
- Funktionsumfang bei Verwendung der Geräte-Besitzer-Berechtigung erweitert
|
||||||
- falsch angezeigte Nutzungsdauer bei Regeln die je Tag gelten an Tagen, an denen diese nicht gelten, behoben
|
|
||||||
- enthaltene Komponenten aktualisiert
|
- enthaltene Komponenten aktualisiert
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
- adjustments for Android 15
|
- add more features for users of the device owner permission
|
||||||
- fix incorrect used time at rules that apply per day at days where they do not apply
|
|
||||||
- update contained software
|
- update contained software
|
||||||
|
|
|
@ -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 - 2025 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.
|
||||||
|
@ -1752,6 +1752,10 @@
|
||||||
<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 - 2024 Jonas Lochmann
|
TimeLimit Copyright <C> 2019 - 2025 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.
|
||||||
|
@ -1649,7 +1649,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 - 2024 Jonas Lochmann
|
TimeLimit Copyright © 2019 - 2025 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,6 +1804,10 @@
|
||||||
<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 - 2020 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.
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
<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>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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() = this
|
fun enablePendingPurchases(params: PendingPurchasesParams) = this
|
||||||
fun setListener(listener: PurchasesUpdatedListener) = this
|
fun setListener(listener: PurchasesUpdatedListener) = this
|
||||||
fun build() = BillingClient
|
fun build() = BillingClient
|
||||||
}
|
}
|
||||||
|
@ -143,4 +143,13 @@ 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
|
||||||
}
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
10
build.gradle
10
build.gradle
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
* TimeLimit Copyright <C> 2019 - 2025 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.7.2' apply false
|
id 'com.android.application' version '8.11.1' apply false
|
||||||
id 'com.android.library' version '8.7.2' apply false
|
id 'com.android.library' version '8.11.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version "1.9.21" apply false
|
id 'org.jetbrains.kotlin.android' version "2.0.21" apply false
|
||||||
id 'com.google.devtools.ksp' version '1.9.21-1.0.16' 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
|
id 'androidx.navigation.safeargs' version '2.6.0' apply false
|
||||||
id 'com.squareup.wire' version '4.4.3' apply false
|
id 'com.squareup.wire' version '5.3.5' apply false
|
||||||
}
|
}
|
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.9-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
distributionSha256Sum=258e722ec21e955201e31447b0aed14201765a3bfbae296a46cf60b70e66db70
|
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
|
Loading…
Add table
Add a link
Reference in a new issue