mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Compare commits
12 commits
e01cf09f51
...
2ed75c7f0f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2ed75c7f0f | ||
![]() |
13fe10b543 | ||
![]() |
bc0e83b916 | ||
![]() |
679276e3cf | ||
![]() |
3206d925e3 | ||
![]() |
b0e5a338e6 | ||
![]() |
376b7ca6de | ||
![]() |
1cdaed60e1 | ||
![]() |
d5bc1f9881 | ||
![]() |
6e9641638f | ||
![]() |
6a4b4505bb | ||
![]() |
6b09de4c59 |
20 changed files with 399 additions and 236 deletions
|
@ -25,13 +25,13 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'io.timelimit.android'
|
namespace 'io.timelimit.android'
|
||||||
compileSdkVersion 34
|
compileSdk 35
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.timelimit.android"
|
applicationId "io.timelimit.android"
|
||||||
minSdkVersion 26
|
minSdkVersion 26
|
||||||
targetSdkVersion 34
|
targetSdkVersion 35
|
||||||
versionCode 218
|
versionCode 220
|
||||||
versionName "7.1.0"
|
versionName "7.2.1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
kapt {
|
kapt {
|
||||||
arguments {
|
arguments {
|
||||||
|
@ -146,8 +146,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
sourceCompatibility JavaVersion.VERSION_21
|
||||||
targetCompatibility JavaVersion.VERSION_17
|
targetCompatibility JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
@ -167,22 +167,23 @@ 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.6.1"
|
||||||
def work_version = '2.9.0'
|
def work_version = '2.9.1'
|
||||||
def paging_version = "3.3.1"
|
def paging_version = "3.3.2"
|
||||||
|
|
||||||
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:1.9.21"
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
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.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.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.6.8'
|
implementation 'androidx.compose.material:material:1.7.5'
|
||||||
implementation 'androidx.activity:activity-compose:1.9.1'
|
implementation 'androidx.activity:activity-compose:1.9.3'
|
||||||
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.6.8'
|
implementation 'androidx.compose.material:material-icons-extended:1.7.5'
|
||||||
debugImplementation "androidx.compose.ui:ui-tooling:1.6.8"
|
debugImplementation "androidx.compose.ui:ui-tooling:1.7.5"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.8.2'
|
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-fragment-ktx:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||||
|
@ -203,7 +204,7 @@ dependencies {
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
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'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
|
|
||||||
implementation 'org.mindrot:jbcrypt:0.4'
|
implementation 'org.mindrot:jbcrypt:0.4'
|
||||||
|
@ -216,7 +217,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.0.0"
|
googleApiImplementation "com.android.billingclient:billing-ktx:7.1.1"
|
||||||
|
|
||||||
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'
|
||||||
|
|
|
@ -55,7 +55,9 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
|
tools:targetApi="tiramisu">
|
||||||
|
|
||||||
<!-- UI -->
|
<!-- UI -->
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
|
import androidx.room.RoomWarnings
|
||||||
import io.timelimit.android.data.model.UsedTimeItem
|
import io.timelimit.android.data.model.UsedTimeItem
|
||||||
import io.timelimit.android.data.model.UsedTimeListItem
|
import io.timelimit.android.data.model.UsedTimeListItem
|
||||||
import io.timelimit.android.livedata.ignoreUnchanged
|
import io.timelimit.android.livedata.ignoreUnchanged
|
||||||
|
@ -68,11 +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)
|
||||||
@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)
|
||||||
@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 - 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
|
||||||
|
@ -184,7 +184,7 @@ class LollipopForegroundAppHelper(context: Context) : UsageStatsForegroundAppHel
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doesActivityExistAsAlias(app: ForegroundApp) = try {
|
private fun doesActivityExistAsAlias(app: ForegroundApp) = try {
|
||||||
packageManager.getPackageInfo(app.packageName, PackageManager.GET_ACTIVITIES).activities.find {
|
packageManager.getPackageInfo(app.packageName, PackageManager.GET_ACTIVITIES).activities?.find {
|
||||||
it.enabled && it.targetActivity == app.activityName
|
it.enabled && it.targetActivity == app.activityName
|
||||||
} != null
|
} != null
|
||||||
} catch (ex: PackageManager.NameNotFoundException) {
|
} catch (ex: PackageManager.NameNotFoundException) {
|
||||||
|
|
|
@ -19,23 +19,24 @@ import android.Manifest
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -129,10 +130,20 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
if (granted) mainModel.reportPermissionsChanged()
|
if (granted) mainModel.reportPermissionsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val isNightMode =
|
||||||
|
(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()
|
supportActionBar!!.hide()
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
U2fManager.setupActivity(this)
|
||||||
|
@ -314,9 +325,8 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
screen = screen,
|
screen = screen,
|
||||||
fragmentManager = supportFragmentManager,
|
fragmentManager = supportFragmentManager,
|
||||||
fragmentIds = mainModel.fragmentIds,
|
fragmentIds = mainModel.fragmentIds,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.fillMaxSize()
|
paddingValues = paddingValues
|
||||||
.padding(paddingValues)
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
showAuthenticationDialog = showAuthenticationDialog,
|
showAuthenticationDialog = showAuthenticationDialog,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 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
|
||||||
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package io.timelimit.android.ui
|
package io.timelimit.android.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
|
@ -42,27 +44,28 @@ fun ScreenMultiplexer(
|
||||||
screen: Screen?,
|
screen: Screen?,
|
||||||
fragmentManager: FragmentManager,
|
fragmentManager: FragmentManager,
|
||||||
fragmentIds: MutableSet<Int>,
|
fragmentIds: MutableSet<Int>,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
paddingValues: PaddingValues
|
||||||
) {
|
) {
|
||||||
when (screen) {
|
when (screen) {
|
||||||
null -> {/* nothing to do */ }
|
null -> {/* nothing to do */ }
|
||||||
is Screen.FragmentScreen -> FragmentScreen(screen, fragmentManager, fragmentIds, modifier = modifier)
|
is Screen.FragmentScreen -> FragmentScreen(screen, fragmentManager, fragmentIds, modifier = modifier.padding(paddingValues))
|
||||||
is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier)
|
is Screen.OverviewScreen -> OverviewScreen(screen.content, modifier = modifier, paddingValues = paddingValues)
|
||||||
is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier)
|
is Screen.ManageDeviceUserScreen -> ManageDeviceUserScreen(screen.items, screen.actions, screen.overlay, modifier.padding(paddingValues))
|
||||||
is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier)
|
is Screen.DeviceOwnerScreen -> DeviceOwnerScreen(screen.content, modifier = modifier.padding(paddingValues))
|
||||||
is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen, modifier)
|
is Screen.SetupDevicePermissionsScreen -> SetupDevicePermissionsScreen(screen, modifier.padding(paddingValues))
|
||||||
is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier)
|
is Screen.ManageDevicePermissions -> ManageDevicePermissionScreen(screen.content, modifier.padding(paddingValues))
|
||||||
is Screen.SetupConnectModePrivacyScreen -> SetupConnectedModePrivacyScreen(screen.customServerDomain, screen.accept, modifier)
|
is Screen.SetupConnectModePrivacyScreen -> SetupConnectedModePrivacyScreen(screen.customServerDomain, screen.accept, modifier.padding(paddingValues))
|
||||||
is Screen.SetupSelectConnectedModeScreen -> SelectConnectedModeScreen(mailLogin = screen.mailLogin, codeLogin = screen.codeLogin, modifier = modifier)
|
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)
|
is Screen.SetupSelectModeScreen -> SelectModeScreen(selectLocal = screen.selectLocal, selectConnected = screen.selectConnected, selectUninstall = screen.selectUninstall, modifier = modifier.padding(paddingValues))
|
||||||
is Screen.DeleteRegistration -> DeleteRegistrationScreen(screen.content, modifier)
|
is Screen.DeleteRegistration -> DeleteRegistrationScreen(screen.content, modifier.padding(paddingValues))
|
||||||
is Screen.ManageBlockedTimes -> BlockedTimesScreen(screen.content, screen.intro, modifier)
|
is Screen.ManageBlockedTimes -> BlockedTimesScreen(screen.content, screen.intro, modifier.padding(paddingValues))
|
||||||
is Screen.ChildUsageHistory -> UsageHistoryScreen(screen.content, modifier)
|
is Screen.ChildUsageHistory -> UsageHistoryScreen(screen.content, modifier.padding(paddingValues))
|
||||||
is Screen.SetupParentMailAuthentication -> AuthenticateByMailScreen(screen.content, modifier)
|
is Screen.SetupParentMailAuthentication -> AuthenticateByMailScreen(screen.content, modifier.padding(paddingValues))
|
||||||
is Screen.SignupBlocked -> SignupBlockedScreen(modifier)
|
is Screen.SignupBlocked -> SignupBlockedScreen(modifier.padding(paddingValues))
|
||||||
is Screen.SignInWrongMailAddress -> SignInWrongMailAddress(modifier)
|
is Screen.SignInWrongMailAddress -> SignInWrongMailAddress(modifier.padding(paddingValues))
|
||||||
is Screen.ConfirmNewParentAccount -> ConfirmNewParentAccount(confirm = screen.confirm, reject = screen.reject, modifier = modifier)
|
is Screen.ConfirmNewParentAccount -> ConfirmNewParentAccount(confirm = screen.confirm, reject = screen.reject, modifier = modifier.padding(paddingValues))
|
||||||
is Screen.ParentBaseConfiguration -> ParentBaseConfiguration(content = screen.content, modifier = modifier)
|
is Screen.ParentBaseConfiguration -> ParentBaseConfiguration(content = screen.content, modifier = modifier.padding(paddingValues))
|
||||||
is Screen.ParentSetupConsent -> ParentSetupConsent(content = screen.content, errorDialog = screen.errorDialog, modifier = modifier)
|
is Screen.ParentSetupConsent -> ParentSetupConsent(content = screen.content, errorDialog = screen.errorDialog, modifier = modifier.padding(paddingValues))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 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
|
||||||
|
@ -17,8 +17,13 @@ package io.timelimit.android.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsIgnoringVisibility
|
||||||
|
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
|
||||||
|
import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -37,6 +42,7 @@ import io.timelimit.android.ui.model.Screen
|
||||||
import io.timelimit.android.ui.model.Title
|
import io.timelimit.android.ui.model.Title
|
||||||
import io.timelimit.android.ui.model.UpdateStateCommand
|
import io.timelimit.android.ui.model.UpdateStateCommand
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ScreenScaffold(
|
fun ScreenScaffold(
|
||||||
screen: Screen?,
|
screen: Screen?,
|
||||||
|
@ -111,7 +117,9 @@ fun ScreenScaffold(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
modifier = Modifier,
|
||||||
|
windowInsets = WindowInsets.statusBarsIgnoringVisibility
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
@ -159,7 +167,8 @@ fun ScreenScaffold(
|
||||||
Text(title)
|
Text(title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
windowInsets = WindowInsets.navigationBarsIgnoringVisibility
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
|
@ -170,6 +179,7 @@ fun ScreenScaffold(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState ?: it) },
|
snackbarHost = { SnackbarHost(snackbarHostState ?: it) },
|
||||||
content = content
|
content = content,
|
||||||
|
contentWindowInsets = WindowInsets.systemBarsIgnoringVisibility
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
@ -18,15 +18,35 @@ package io.timelimit.android.ui.lock
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.viewpager.widget.ViewPager
|
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.R
|
||||||
import io.timelimit.android.databinding.LockActivityBinding
|
import io.timelimit.android.data.model.UserType
|
||||||
import io.timelimit.android.extensions.showSafe
|
import io.timelimit.android.extensions.showSafe
|
||||||
import io.timelimit.android.logic.BlockingReason
|
import io.timelimit.android.logic.BlockingReason
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
|
@ -34,11 +54,12 @@ import io.timelimit.android.sync.network.UpdatePrimaryDeviceRequestType
|
||||||
import io.timelimit.android.u2f.U2fManager
|
import io.timelimit.android.u2f.U2fManager
|
||||||
import io.timelimit.android.u2f.protocol.U2FDevice
|
import io.timelimit.android.u2f.protocol.U2FDevice
|
||||||
import io.timelimit.android.ui.IsAppInForeground
|
import io.timelimit.android.ui.IsAppInForeground
|
||||||
|
import io.timelimit.android.ui.ScreenScaffold
|
||||||
|
import io.timelimit.android.ui.Theme
|
||||||
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
||||||
import io.timelimit.android.ui.login.NewLoginFragment
|
import io.timelimit.android.ui.login.NewLoginFragment
|
||||||
import io.timelimit.android.ui.main.ActivityViewModel
|
import io.timelimit.android.ui.main.ActivityViewModel
|
||||||
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
||||||
import io.timelimit.android.ui.main.AuthenticationFab
|
|
||||||
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
|
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
|
||||||
import io.timelimit.android.ui.util.SyncStatusModel
|
import io.timelimit.android.ui.util.SyncStatusModel
|
||||||
|
|
||||||
|
@ -85,17 +106,113 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val showAuth = MutableLiveData<Boolean>().apply { value = false }
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val isNightMode =
|
||||||
|
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
||||||
|
Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
||||||
|
enableEdgeToEdge(
|
||||||
|
statusBarStyle = SystemBarStyle.dark(
|
||||||
|
if (isNightMode) android.graphics.Color.TRANSPARENT
|
||||||
|
else resources.getColor(R.color.colorPrimaryDark)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
supportActionBar!!.hide()
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
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)
|
isTimeOver
|
||||||
setContentView(binding.root)
|
}.asFlow()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LockReasonFragment>(Modifier.fillMaxSize())
|
||||||
|
1 -> AndroidFragment<LockActionFragment>(Modifier.fillMaxSize())
|
||||||
|
2 -> AndroidFragment<LockTaskFragment>(Modifier.fillMaxSize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
executeCommand = {},
|
||||||
|
showAuthenticationDialog =
|
||||||
|
if (pager.currentPage == 1 && !isAuthenticated) ({ showAuthenticationScreen() })
|
||||||
|
else null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
|
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
|
||||||
|
|
||||||
|
@ -103,8 +220,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
|
|
||||||
model.init(blockedPackageName, blockedActivityName)
|
model.init(blockedPackageName, blockedActivityName)
|
||||||
|
|
||||||
binding.pager.adapter = adapter
|
|
||||||
|
|
||||||
model.content.observe(this) {
|
model.content.observe(this) {
|
||||||
if (isResumed && it is LockscreenContent.Blocked.BlockedCategory && it.reason == BlockingReason.RequiresCurrentDevice && !model.didOpenSetCurrentDeviceScreen) {
|
if (isResumed && it is LockscreenContent.Blocked.BlockedCategory && it.reason == BlockingReason.RequiresCurrentDevice && !model.didOpenSetCurrentDeviceScreen) {
|
||||||
model.didOpenSetCurrentDeviceScreen = true
|
model.didOpenSetCurrentDeviceScreen = true
|
||||||
|
@ -115,30 +230,12 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationFab.manageAuthenticationFab(
|
activityModel.shouldHighlightAuthenticationButton.observe(this) {
|
||||||
fab = binding.fab,
|
if (it) {
|
||||||
shouldHighlight = activityModel.shouldHighlightAuthenticationButton,
|
activityModel.shouldHighlightAuthenticationButton.postValue(false)
|
||||||
authenticatedUser = activityModel.authenticatedUser,
|
|
||||||
activity = this,
|
|
||||||
doesSupportAuth = showAuth
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.fab.setOnClickListener { showAuthenticationScreen() }
|
showAuthenticationScreen()
|
||||||
|
|
||||||
binding.pager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() {
|
|
||||||
override fun onPageSelected(position: Int) {
|
|
||||||
super.onPageSelected(position)
|
|
||||||
|
|
||||||
showAuth.value = position == 1
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
binding.tabs.setupWithViewPager(binding.pager)
|
|
||||||
|
|
||||||
model.content.observe(this) {
|
|
||||||
val isTimeOver = it is LockscreenContent.Blocked.BlockedCategory && it.blockingHandling.activityBlockingReason == BlockingReason.TimeOver
|
|
||||||
|
|
||||||
adapter.showTasksFragment = isTimeOver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(object: OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(object: OnBackPressedCallback(true) {
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation version 3 of the License.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.timelimit.android.ui.lock
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
|
||||||
import io.timelimit.android.R
|
|
||||||
import kotlin.properties.Delegates
|
|
||||||
|
|
||||||
class LockActivityAdapter(fragmentManager: FragmentManager, private val context: Context): FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
|
||||||
var showTasksFragment: Boolean by Delegates.observable(false) { _, _, _ -> notifyDataSetChanged() }
|
|
||||||
|
|
||||||
override fun getCount(): Int = if (showTasksFragment) 3 else 2
|
|
||||||
|
|
||||||
override fun getItem(position: Int): Fragment = when (position) {
|
|
||||||
0 -> LockReasonFragment()
|
|
||||||
1 -> LockActionFragment()
|
|
||||||
2 -> LockTaskFragment()
|
|
||||||
else -> throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPageTitle(position: Int): CharSequence? = context.getString(when (position) {
|
|
||||||
0 -> R.string.lock_tab_reason
|
|
||||||
1 -> R.string.lock_tab_action
|
|
||||||
2 -> R.string.lock_tab_task
|
|
||||||
else -> throw IllegalArgumentException()
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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.manage.category.timelimit_rules.TimeLimitRulesHandlers
|
||||||
import io.timelimit.android.ui.util.DateUtil
|
import io.timelimit.android.ui.util.DateUtil
|
||||||
import io.timelimit.android.util.DayNameUtil
|
import io.timelimit.android.util.DayNameUtil
|
||||||
|
import io.timelimit.android.util.Option
|
||||||
import io.timelimit.android.util.TimeTextUtil
|
import io.timelimit.android.util.TimeTextUtil
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
@ -159,12 +160,24 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
|
||||||
val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding
|
val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding
|
||||||
val context = binding.root.context
|
val context = binding.root.context
|
||||||
val usedTime = date?.let { date ->
|
val usedTime = date?.let { date ->
|
||||||
RemainingTime.getUsedTime(
|
val dayOfWeekForDailyRule: Option<Int?>? =
|
||||||
usedTimes = usedTimes,
|
if (rule.perDay) {
|
||||||
rule = rule,
|
(0 until 7)
|
||||||
firstDayOfWeekAsEpochDay = date.firstDayOfWeekAsEpochDay,
|
.map { (7 + date.dayOfWeek - it) % 7 } // make the current day the last one
|
||||||
dayOfWeekForDailyRule = if (rule.perDay) date.dayOfWeek else null
|
.firstOrNull { rule.dayMask.toInt() and (1 shl it) != 0 }
|
||||||
).toInt()
|
?.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
|
} ?: 0
|
||||||
|
|
||||||
binding.maxTimeString = rule.maximumTimeInMillis.let { time ->
|
binding.maxTimeString = rule.maximumTimeInMillis.let { time ->
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 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
|
||||||
|
@ -18,12 +18,21 @@ package io.timelimit.android.ui.manipulation
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import io.timelimit.android.BuildConfig
|
import io.timelimit.android.BuildConfig
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
|
@ -34,6 +43,8 @@ import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
import io.timelimit.android.u2f.U2fManager
|
import io.timelimit.android.u2f.U2fManager
|
||||||
import io.timelimit.android.u2f.protocol.U2FDevice
|
import io.timelimit.android.u2f.protocol.U2FDevice
|
||||||
|
import io.timelimit.android.ui.ScreenScaffold
|
||||||
|
import io.timelimit.android.ui.Theme
|
||||||
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
|
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
|
||||||
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
||||||
import io.timelimit.android.ui.login.NewLoginFragment
|
import io.timelimit.android.ui.login.NewLoginFragment
|
||||||
|
@ -65,12 +76,75 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
U2fManager.setupActivity(this)
|
|
||||||
|
|
||||||
val logic = DefaultAppLogic.with(this)
|
val logic = DefaultAppLogic.with(this)
|
||||||
|
|
||||||
val binding = AnnoyActivityBinding.inflate(layoutInflater)
|
val isNightMode =
|
||||||
setContentView(binding.root)
|
(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()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
Theme {
|
||||||
|
ScreenScaffold(
|
||||||
|
screen = null,
|
||||||
|
title = getString(R.string.app_name),
|
||||||
|
subtitle = null,
|
||||||
|
backStack = emptyList(),
|
||||||
|
snackbarHostState = null,
|
||||||
|
content = { padding ->
|
||||||
|
AndroidView(
|
||||||
|
factory = {
|
||||||
|
val binding = AnnoyActivityBinding.inflate(LayoutInflater.from(it))
|
||||||
|
|
||||||
|
logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown ->
|
||||||
|
binding.canRequestUnlock = countdown == 0L
|
||||||
|
binding.countdownText = getString(R.string.annoy_timer, TimeTextUtil.seconds((countdown / 1000).toInt(), this@AnnoyActivity))
|
||||||
|
}
|
||||||
|
|
||||||
|
logic.deviceEntry.map {
|
||||||
|
val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty)
|
||||||
|
.current
|
||||||
|
.map { getString(it.labelResourceId) }
|
||||||
|
|
||||||
|
if (reasonItems.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", "))
|
||||||
|
}
|
||||||
|
}.observe(this) { binding.reasonText = it }
|
||||||
|
|
||||||
|
binding.unlockTemporarilyButton.setOnClickListener {
|
||||||
|
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Short)
|
||||||
|
.show(supportFragmentManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.parentUnlockButton.setOnClickListener {
|
||||||
|
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Long)
|
||||||
|
.show(supportFragmentManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.useBackdoorButton.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
|
||||||
|
|
||||||
|
binding.root
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize().padding(padding)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
executeCommand = {},
|
||||||
|
showAuthenticationDialog = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U2fManager.setupActivity(this)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val systemImageApps = packageManager.getInstalledApplications(0)
|
val systemImageApps = packageManager.getInstalledApplications(0)
|
||||||
|
@ -92,35 +166,6 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
||||||
if (!shouldRun) shutdown()
|
if (!shouldRun) shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown ->
|
|
||||||
binding.canRequestUnlock = countdown == 0L
|
|
||||||
binding.countdownText = getString(R.string.annoy_timer, TimeTextUtil.seconds((countdown / 1000).toInt(), this@AnnoyActivity))
|
|
||||||
}
|
|
||||||
|
|
||||||
logic.deviceEntry.map {
|
|
||||||
val reasonItems = (it?.let { ManipulationWarnings.getFromDevice(it) } ?: ManipulationWarnings.empty)
|
|
||||||
.current
|
|
||||||
.map { getString(it.labelResourceId) }
|
|
||||||
|
|
||||||
if (reasonItems.isEmpty()) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
getString(R.string.annoy_reason, reasonItems.joinToString(separator = ", "))
|
|
||||||
}
|
|
||||||
}.observe(this) { binding.reasonText = it }
|
|
||||||
|
|
||||||
binding.unlockTemporarilyButton.setOnClickListener {
|
|
||||||
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Short)
|
|
||||||
.show(supportFragmentManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.parentUnlockButton.setOnClickListener {
|
|
||||||
AnnoyUnlockDialogFragment.newInstance(AnnoyUnlockDialogFragment.UnlockDuration.Long)
|
|
||||||
.show(supportFragmentManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.useBackdoorButton.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
|
|
||||||
|
|
||||||
model.authenticatedUser.observe(this) { user ->
|
model.authenticatedUser.observe(this) { user ->
|
||||||
if (user?.second?.type == UserType.Parent) {
|
if (user?.second?.type == UserType.Parent) {
|
||||||
logic.annoyLogic.doParentTempUnlock()
|
logic.annoyLogic.doParentTempUnlock()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 2023 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
|
||||||
|
@ -19,16 +19,24 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.timelimit.android.ui.model.main.OverviewHandling
|
import io.timelimit.android.ui.model.main.OverviewHandling
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OverviewScreen(
|
fun OverviewScreen(
|
||||||
screen: OverviewHandling.OverviewScreen,
|
screen: OverviewHandling.OverviewScreen,
|
||||||
|
paddingValues: PaddingValues,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
LazyColumn (
|
LazyColumn (
|
||||||
contentPadding = 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),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,23 +1,83 @@
|
||||||
|
/*
|
||||||
|
* TimeLimit Copyright <C> 2019 - 2024 Jonas Lochmann
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation version 3 of the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
package io.timelimit.android.ui.update
|
package io.timelimit.android.ui.update
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.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.R
|
||||||
import io.timelimit.android.databinding.UpdateActivityBinding
|
import io.timelimit.android.databinding.UpdateActivityBinding
|
||||||
import io.timelimit.android.logic.DefaultAppLogic
|
import io.timelimit.android.logic.DefaultAppLogic
|
||||||
|
import io.timelimit.android.ui.ScreenScaffold
|
||||||
|
import io.timelimit.android.ui.Theme
|
||||||
|
|
||||||
class UpdateActivity: AppCompatActivity() {
|
class UpdateActivity: AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val binding = DataBindingUtil.setContentView<UpdateActivityBinding>(this, R.layout.update_activity)
|
val isNightMode =
|
||||||
|
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
||||||
|
Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
||||||
UpdateView.bind(
|
enableEdgeToEdge(
|
||||||
view = binding.update,
|
statusBarStyle = SystemBarStyle.dark(
|
||||||
lifecycleOwner = this,
|
if (isNightMode) android.graphics.Color.TRANSPARENT
|
||||||
fragmentManager = supportFragmentManager,
|
else resources.getColor(R.color.colorPrimaryDark)
|
||||||
appLogic = DefaultAppLogic.with(this)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
supportActionBar!!.hide()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
Theme {
|
||||||
|
ScreenScaffold(
|
||||||
|
screen = null,
|
||||||
|
title = getString(R.string.app_name),
|
||||||
|
subtitle = null,
|
||||||
|
backStack = emptyList(),
|
||||||
|
snackbarHostState = null,
|
||||||
|
content = { padding ->
|
||||||
|
AndroidView(
|
||||||
|
factory = {
|
||||||
|
val binding = UpdateActivityBinding.inflate(LayoutInflater.from(it))
|
||||||
|
|
||||||
|
UpdateView.bind(
|
||||||
|
view = binding.update,
|
||||||
|
lifecycleOwner = this,
|
||||||
|
fragmentManager = supportFragmentManager,
|
||||||
|
appLogic = DefaultAppLogic.with(this)
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.root
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize().padding(padding)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
executeCommand = {},
|
||||||
|
showAuthenticationDialog = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 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
|
||||||
|
@ -19,6 +19,7 @@ import android.appwidget.AppWidgetManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
|
@ -30,6 +31,8 @@ class WidgetConfigActivity: FragmentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
if (model.state.value == WidgetConfigModel.State.WaitingForInit) {
|
if (model.state.value == WidgetConfigModel.State.WaitingForInit) {
|
||||||
model.init(
|
model.init(
|
||||||
intent?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
intent?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TimeLimit Copyright <C> 2019 - 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
|
||||||
|
@ -70,12 +70,12 @@ object UpdateIntegration {
|
||||||
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
// new signature
|
// new signature
|
||||||
|
|
||||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo.apkContentsSigners
|
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo!!.apkContentsSigners
|
||||||
} else {
|
} else {
|
||||||
// old signature
|
// old signature
|
||||||
// this is "unsafe", but it is not used for security features
|
// this is "unsafe", but it is not used for security features
|
||||||
|
|
||||||
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures
|
context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES).signatures!!
|
||||||
}
|
}
|
||||||
|
|
||||||
return signatures.map { HexString.toHex(MessageDigest.getInstance("SHA-256").digest(it.toByteArray())) }
|
return signatures.map { HexString.toHex(MessageDigest.getInstance("SHA-256").digest(it.toByteArray())) }
|
||||||
|
|
|
@ -1,2 +1,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
|
- enthaltene Komponenten aktualisiert
|
||||||
|
|
|
@ -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
|
- update contained software
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
TimeLimit Copyright <C> 2019 Jonas Lochmann
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation version 3 of the License.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".ui.lock.LockActivity" >
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/tabs"
|
|
||||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/pager" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/fab"
|
|
||||||
app:fabSize="normal"
|
|
||||||
android:src="@drawable/ic_lock_open_white_24dp"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:layout_gravity="end|bottom"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
</LinearLayout>
|
|
|
@ -15,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.5.1' apply false
|
id 'com.android.application' version '8.7.2' apply false
|
||||||
id 'com.android.library' version '8.5.1' apply false
|
id 'com.android.library' version '8.7.2' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version "1.9.21" 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 '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
|
||||||
|
|
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.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
|
||||||
distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0
|
distributionSha256Sum=258e722ec21e955201e31447b0aed14201765a3bfbae296a46cf60b70e66db70
|
Loading…
Add table
Add a link
Reference in a new issue