diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt index c2e60aa..0340f66 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt @@ -241,7 +241,8 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat val writtenPackages = MutableStateFlow(setOf()) val writingPackage = MutableStateFlow(null) fun startInstallationProcess(activity: FragmentActivity) { - startAuth(activity, object : BiometricPrompt.AuthenticationCallback() { + val sp = SharedPrefs(getApplication()) + if(sp.auth) startAuth(activity, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) startInstall() @@ -251,6 +252,7 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat Toast.makeText(activity, R.string.failed_to_authenticate, Toast.LENGTH_SHORT).show() } }) + else startInstall() } private fun startInstall() { if(installing.value) return diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index 5c9810c..c7d64fb 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -76,8 +76,6 @@ import com.bintianqi.owndroid.dpm.ChangeTime import com.bintianqi.owndroid.dpm.ChangeTimeScreen import com.bintianqi.owndroid.dpm.ChangeTimeZone import com.bintianqi.owndroid.dpm.ChangeTimeZoneScreen -import com.bintianqi.owndroid.dpm.ChangeUserIcon -import com.bintianqi.owndroid.dpm.ChangeUserIconScreen import com.bintianqi.owndroid.dpm.ChangeUsername import com.bintianqi.owndroid.dpm.ChangeUsernameScreen import com.bintianqi.owndroid.dpm.ContentProtectionPolicy @@ -255,31 +253,19 @@ class MainActivity : FragmentActivity() { @ExperimentalMaterial3Api @Composable fun Home(activity: FragmentActivity, vm: MyViewModel) { - val navCtrl = rememberNavController() + val navController = rememberNavController() val context = LocalContext.current - val dpm = context.getDPM() val receiver = context.getReceiver() - val sp = SharedPrefs(context) val focusMgr = LocalFocusManager.current val backToHome by backToHomeStateFlow.collectAsState() val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(backToHome) { - if(backToHome) { navCtrl.navigateUp(); backToHomeStateFlow.value = false } + if(backToHome) { navController.navigateUp(); backToHomeStateFlow.value = false } } val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle() - fun onUserRestrictionsChange(id: String, status: Boolean) { - try { - if(status) dpm.addUserRestriction(receiver, id) - else dpm.clearUserRestriction(receiver, id) - @SuppressLint("NewApi") - vm.userRestrictions.value = dpm.getUserRestrictions(receiver) - } catch(_: Exception) { - context.showOperationResultToast(false) - } - } - fun navigateUp() { navCtrl.navigateUp() } + fun navigateUp() { navController.navigateUp() } @Suppress("NewApi") NavHost( - navController = navCtrl, + navController = navController, startDestination = Home, modifier = Modifier .fillMaxSize() @@ -291,15 +277,15 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { popEnterTransition = Animations.navHostPopEnterTransition, popExitTransition = Animations.navHostPopExitTransition ) { - composable { HomeScreen { navCtrl.navigate(it) } } + composable { HomeScreen { navController.navigate(it) } } composable { - PermissionsScreen(::navigateUp, { navCtrl.navigate(it) }) { - val dest = navCtrl.graph.findNode(ShizukuScreen)!!.id - navCtrl.navigate(dest, it) + PermissionsScreen(::navigateUp, { navController.navigate(it) }) { + val dest = navController.graph.findNode(ShizukuScreen)!!.id + navController.navigate(dest, it) } } - composable { ShizukuScreen(it.arguments!!, ::navigateUp) { navCtrl.navigate(it) } } + composable { ShizukuScreen(it.arguments!!, ::navigateUp) { navController.navigate(it) } } composable(mapOf(serializableNavTypePair>())) { AccountsScreen(it.toRoute(), ::navigateUp) } composable { DeviceAdminScreen(::navigateUp) } composable { ProfileOwnerScreen(::navigateUp) } @@ -310,7 +296,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { SupportMessageScreen(::navigateUp) } composable { TransferOwnershipScreen(::navigateUp) } - composable { SystemManagerScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { SystemManagerScreen(::navigateUp) { navController.navigate(it) } } composable { SystemOptionsScreen(::navigateUp) } composable { KeyguardScreen(::navigateUp) } composable { HardwareMonitorScreen(::navigateUp) } @@ -330,20 +316,20 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { FrpPolicyScreen(::navigateUp) } composable { WipeDataScreen(::navigateUp) } - composable { NetworkScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { NetworkScreen(::navigateUp) { navController.navigate(it) } } composable { - WifiScreen(::navigateUp, { navCtrl.navigate(it) }) { - val dest = navCtrl.graph.findNode(AddNetwork)!!.id - navCtrl.navigate(dest, it) + WifiScreen(::navigateUp, { navController.navigate(it) }) { + val dest = navController.graph.findNode(AddNetwork)!!.id + navController.navigate(dest, it) } } composable { NetworkOptionsScreen(::navigateUp) } composable { AddNetworkScreen(it.arguments!!, ::navigateUp) } composable { WifiSecurityLevelScreen(::navigateUp) } composable { WifiSsidPolicyScreen(::navigateUp) } - composable { NetworkStatsScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { NetworkStatsScreen(::navigateUp) { navController.navigate(it) } } composable(mapOf(serializableNavTypePair>())) { - NetworkStatsViewerScreen(it.toRoute()) { navCtrl.navigateUp() } + NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() } } composable { PrivateDnsScreen(::navigateUp) } composable { AlwaysOnVpnPackageScreen(::navigateUp) } @@ -353,7 +339,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { PreferentialNetworkServiceScreen(::navigateUp) } composable { OverrideApnScreen(::navigateUp) } - composable { WorkProfileScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { WorkProfileScreen(::navigateUp) { navController.navigate(it) } } composable { OrganizationOwnedProfileScreen(::navigateUp) } composable { CreateWorkProfileScreen(::navigateUp) } composable { SuspendPersonalAppScreen(::navigateUp) } @@ -364,27 +350,36 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { LaunchedEffect(Unit) { - vm.userRestrictions.value = dpm.getUserRestrictions(receiver) + vm.userRestrictions.value = context.getDPM().getUserRestrictions(receiver) } UserRestrictionScreen(::navigateUp) { title, items -> - navCtrl.navigate(UserRestrictionOptions(title, items)) + navController.navigate(UserRestrictionOptions(title, items)) } } composable(mapOf(serializableNavTypePair>())) { - UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::onUserRestrictionsChange, ::navigateUp) + UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::navigateUp) { id, status -> + try { + val dpm = context.getDPM() + if(status) dpm.addUserRestriction(receiver, id) + else dpm.clearUserRestriction(receiver, id) + @SuppressLint("NewApi") + vm.userRestrictions.value = dpm.getUserRestrictions(receiver) + } catch(_: Exception) { + context.showOperationResultToast(false) + } + } } - composable { UsersScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { UsersScreen(::navigateUp) { navController.navigate(it) } } composable { UserInfoScreen(::navigateUp) } composable { UsersOptionsScreen(::navigateUp) } composable { UserOperationScreen(::navigateUp) } composable { CreateUserScreen(::navigateUp) } composable { ChangeUsernameScreen(::navigateUp) } - composable { ChangeUserIconScreen(::navigateUp) } composable { UserSessionMessageScreen(::navigateUp) } composable { AffiliationIdScreen(::navigateUp) } - composable { PasswordScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { PasswordScreen(::navigateUp) { navController.navigate(it) } } composable { PasswordInfoScreen(::navigateUp) } composable { ResetPasswordTokenScreen(::navigateUp) } composable { ResetPasswordScreen(::navigateUp) } @@ -392,7 +387,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { KeyguardDisabledFeaturesScreen(::navigateUp) } composable { RequiredPasswordQualityScreen(::navigateUp) } - composable { SettingsScreen(::navigateUp) { navCtrl.navigate(it) } } + composable { SettingsScreen(::navigateUp) { navController.navigate(it) } } composable { SettingsOptionsScreen(::navigateUp) } composable { val theme by vm.theme.collectAsStateWithLifecycle() @@ -409,11 +404,12 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { } DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> + val sp = SharedPrefs(context) if( (event == Lifecycle.Event.ON_RESUME && sp.auth && sp.lockInBackground) || (event == Lifecycle.Event.ON_CREATE && sp.auth) ) { - navCtrl.navigate(Authenticate) { launchSingleTop = true } + navController.navigate(Authenticate) { launchSingleTop = true } } } lifecycleOwner.lifecycle.addObserver(observer) @@ -422,6 +418,8 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { } } LaunchedEffect(Unit) { + val dpm = context.getDPM() + val sp = SharedPrefs(context) val profileNotActivated = !sp.managedProfileActivated && context.isProfileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver)) if(profileNotActivated) { dpm.setProfileEnabled(receiver) diff --git a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt index 8c2f0f7..bf63c73 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Receiver.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Receiver.kt @@ -11,7 +11,6 @@ import android.os.PersistableBundle import android.widget.Toast import androidx.core.app.NotificationCompat import com.bintianqi.owndroid.dpm.handleNetworkLogs -import com.bintianqi.owndroid.dpm.isDeviceAdmin import com.bintianqi.owndroid.dpm.isDeviceOwner import com.bintianqi.owndroid.dpm.isProfileOwner import com.bintianqi.owndroid.dpm.processSecurityLogs @@ -29,11 +28,12 @@ class Receiver : DeviceAdminReceiver() { dpm.setLockTaskPackages(receiver, arrayOf()) dpm.setLockTaskPackages(receiver, packages) } + if(!context.isDeviceOwner && !context.isProfileOwner) SharedPrefs(context).isApiEnabled = false } override fun onEnabled(context: Context, intent: Intent) { super.onEnabled(context, intent) - if(context.isDeviceAdmin || context.isProfileOwner || context.isDeviceOwner){ + if(context.isProfileOwner || context.isDeviceOwner){ Toast.makeText(context, context.getString(R.string.onEnabled), Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt index 4fd2de7..195a992 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt @@ -69,8 +69,9 @@ fun SettingsOptionsScreen(onNavigateUp: () -> Unit) { @Serializable object Appearance @Composable -fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) { +fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) { var darkThemeMenu by remember { mutableStateOf(false) } + var theme by remember { mutableStateOf(currentTheme) } val darkThemeTextID = when(theme.darkTheme) { 1 -> R.string.on 0 -> R.string.off @@ -78,7 +79,11 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChan } MyScaffold(R.string.appearance, 0.dp, onNavigateUp) { if(VERSION.SDK_INT >= 31) { - SwitchItem(R.string.material_you_color, state = theme.materialYou, onCheckedChange = { onThemeChange(theme.copy(materialYou = it)) }) + SwitchItem( + R.string.material_you_color, + state = theme.materialYou, + onCheckedChange = { theme = theme.copy(materialYou = it) } + ) } Box { FunctionItem(R.string.dark_theme, stringResource(darkThemeTextID)) { darkThemeMenu = true } @@ -89,28 +94,33 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChan DropdownMenuItem( text = { Text(stringResource(R.string.follow_system)) }, onClick = { - onThemeChange(theme.copy(darkTheme = -1)) + theme = theme.copy(darkTheme = -1) darkThemeMenu = false } ) DropdownMenuItem( text = { Text(stringResource(R.string.on)) }, onClick = { - onThemeChange(theme.copy(darkTheme = 1)) + theme = theme.copy(darkTheme = 1) darkThemeMenu = false } ) DropdownMenuItem( text = { Text(stringResource(R.string.off)) }, onClick = { - onThemeChange(theme.copy(darkTheme = 0)) + theme = theme.copy(darkTheme = 0) darkThemeMenu = false } ) } } AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) { - SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { onThemeChange(theme.copy(blackTheme = it)) }) + SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { theme = theme.copy(blackTheme = it) }) + } + AnimatedVisibility(theme != currentTheme, Modifier.fillMaxWidth().padding(8.dp)) { + Button({onThemeChange(theme)}) { + Text(stringResource(R.string.apply)) + } } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt index 24f4a64..b17dfa4 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -1598,14 +1598,12 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) { val logFile = context.filesDir.resolve("NetworkLogs.json") var fileSize by remember { mutableLongStateOf(0) } LaunchedEffect(Unit) { fileSize = logFile.length() } - val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - result.data?.data?.let { uri -> - context.contentResolver.openOutputStream(uri)?.use { outStream -> - outStream.write("[".encodeToByteArray()) - logFile.inputStream().use { it.copyTo(outStream) } - outStream.write("]".encodeToByteArray()) - context.showOperationResultToast(true) - } + val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri -> + if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream -> + outStream.write("[".encodeToByteArray()) + logFile.inputStream().use { it.copyTo(outStream) } + outStream.write("]".encodeToByteArray()) + context.showOperationResultToast(true) } } MyScaffold(R.string.network_logging, 8.dp, onNavigateUp) { @@ -1619,11 +1617,7 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Button( onClick = { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.setType("application/json") - intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json") - exportNetworkLogsLauncher.launch(intent) + exportNetworkLogsLauncher.launch("NetworkLogs.json") }, enabled = fileSize > 0, modifier = Modifier.fillMaxWidth(0.49F) diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index c3e9c4f..537b81c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -1299,21 +1299,17 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) { var fileSize by remember { mutableLongStateOf(0) } LaunchedEffect(Unit) { fileSize = logFile.length() } var preRebootSecurityLogs by remember { mutableStateOf(byteArrayOf()) } - val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - result.data?.data?.let { uri -> - context.contentResolver.openOutputStream(uri)?.use { outStream -> - preRebootSecurityLogs.inputStream().copyTo(outStream) - } + val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri -> + if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream -> + preRebootSecurityLogs.inputStream().copyTo(outStream) } } - val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - result.data?.data?.let { uri -> - context.contentResolver.openOutputStream(uri)?.use { outStream -> - outStream.write("[".toByteArray()) - logFile.inputStream().use { it.copyTo(outStream) } - outStream.write("]".toByteArray()) - context.showOperationResultToast(true) - } + val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri -> + if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream -> + outStream.write("[".toByteArray()) + logFile.inputStream().use { it.copyTo(outStream) } + outStream.write("]".toByteArray()) + context.showOperationResultToast(true) } } MyScaffold(R.string.security_logging, 8.dp, onNavigateUp) { @@ -1326,11 +1322,7 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Button( onClick = { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.setType("application/json") - intent.putExtra(Intent.EXTRA_TITLE, "SecurityLogs.json") - exportSecurityLogs.launch(intent) + exportSecurityLogs.launch("SecurityLogs.json") }, enabled = fileSize > 0, modifier = Modifier.fillMaxWidth(0.49F) @@ -1362,11 +1354,7 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) { processSecurityLogs(logs, outputStream) outputStream.write("]".encodeToByteArray()) preRebootSecurityLogs = outputStream.toByteArray() - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.setType("application/json") - intent.putExtra(Intent.EXTRA_TITLE, "PreRebootSecurityLogs.json") - exportPreRebootSecurityLogs.launch(intent) + exportPreRebootSecurityLogs.launch("PreRebootSecurityLogs.json") } }, modifier = Modifier.fillMaxWidth() @@ -1742,16 +1730,11 @@ fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) { } } var uri by remember { mutableStateOf(null) } - val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - uri = it.data?.data - } + val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it } MyScaffold(R.string.install_system_update, 8.dp, onNavigateUp) { Button( onClick = { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.setType("application/zip") - intent.addCategory(Intent.CATEGORY_OPENABLE) - getFileLauncher.launch(intent) + getFileLauncher.launch("application/zip") }, modifier = Modifier.fillMaxWidth() ) { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt index 221ec6c..273d986 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt @@ -73,7 +73,7 @@ data class UserRestrictionOptions( @Composable fun UserRestrictionOptionsScreen( data: UserRestrictionOptions, restrictions: Bundle, - onRestrictionChange: (String, Boolean) -> Unit, onNavigateUp: () -> Unit + onNavigateUp: () -> Unit, onRestrictionChange: (String, Boolean) -> Unit ) { MyScaffold(data.title, 0.dp, onNavigateUp, false) { data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction -> diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt index 69887bb..67f5314 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt @@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm import android.app.admin.DevicePolicyManager import android.content.Context -import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Binder @@ -10,13 +9,11 @@ import android.os.Build.VERSION import android.os.Process import android.os.UserHandle import android.os.UserManager -import android.provider.MediaStore import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.annotation.StringRes -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement @@ -36,7 +33,6 @@ import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button -import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -56,7 +52,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.asImageBitmap @@ -115,7 +110,16 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) } } if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) { - FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { onNavigate(ChangeUserIcon) } + var changeUserIconDialog by remember { mutableStateOf(false) } + var bitmap: Bitmap? by remember { mutableStateOf(null) } + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { + if(it != null) uriToStream(context, it) { stream -> + bitmap = BitmapFactory.decodeStream(stream) + if(bitmap != null) changeUserIconDialog = true + } + } + FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { launcher.launch("image/*") } + if(changeUserIconDialog == true) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false } } if(VERSION.SDK_INT >= 28 && deviceOwner) { FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) } @@ -568,56 +572,36 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) { } } -@Serializable object ChangeUserIcon - @RequiresApi(23) @Composable -fun ChangeUserIconScreen(onNavigateUp: () -> Unit) { +private fun ChangeUserIconDialog(bitmap: Bitmap, onClose: () -> Unit) { val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - var getContent by remember { mutableStateOf(false) } - var bitmap by remember { mutableStateOf(null) } - val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - it.data?.data?.let { - uriToStream(context, it) { stream -> - bitmap = BitmapFactory.decodeStream(stream) + AlertDialog( + title = { Text(stringResource(R.string.change_user_icon)) }, + text = { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Image( + bitmap = bitmap.asImageBitmap(), contentDescription = null, + modifier = Modifier.size(80.dp).clip(RoundedCornerShape(50)) + ) } - } - } - MyScaffold(R.string.change_user_icon, 8.dp, onNavigateUp) { - CheckBoxItem(R.string.file_picker_instead_gallery, getContent) { getContent = it } - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { - val intent = Intent(if(getContent) Intent.ACTION_GET_CONTENT else Intent.ACTION_PICK) - if(getContent) intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*") - getFileLauncher.launch(intent) - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.select_picture)) - } - AnimatedVisibility(visible = bitmap != null, modifier = Modifier.align(Alignment.CenterHorizontally)) { - Card(modifier = Modifier.padding(top = 8.dp)) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) { - Image( - bitmap = bitmap!!.asImageBitmap(), contentDescription = "User icon", - modifier = Modifier.padding(end = 12.dp).size(80.dp).clip(RoundedCornerShape(50)) - ) - Button( - onClick = { - dpm.setUserIcon(receiver, bitmap) - context.showOperationResultToast(true) - } - ) { - Text(stringResource(R.string.apply)) - } - } + }, + confirmButton = { + TextButton({ + context.getDPM().setUserIcon(context.getReceiver(), bitmap) + context.showOperationResultToast(true) + onClose() + }) { + Text(stringResource(R.string.confirm)) } - } - } + }, + dismissButton = { + TextButton(onClose) { + Text(stringResource(R.string.cancel)) + } + }, + onDismissRequest = onClose + ) } @StringRes diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt index e469dff..399c4cc 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.* @@ -67,16 +68,10 @@ fun FunctionItem( } @Composable -fun NavIcon(operation: () -> Unit) { - Icon( - painter = painterResource(R.drawable.arrow_back_fill0), - contentDescription = "Back arrow", - modifier = Modifier - .padding(horizontal = 6.dp) - .clip(RoundedCornerShape(50)) - .clickable(onClick = operation) - .padding(5.dp) - ) +fun NavIcon(onClick: () -> Unit) { + IconButton(onClick) { + Icon(Icons.AutoMirrored.Default.ArrowBack, null) + } } @Composable diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt b/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt index 02bac15..4fcc2e0 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/theme/Theme.kt @@ -6,12 +6,19 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State import androidx.compose.ui.graphics.Color +import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.ThemeSettings +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow private val lightScheme = lightColorScheme( primary = primaryLight, @@ -96,28 +103,24 @@ fun OwnDroidTheme( ) { val darkTheme = theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme()) val context = LocalContext.current - var colorScheme = when { + val colorScheme = when { theme.materialYou && VERSION.SDK_INT >= 31 -> { if(darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> darkScheme else -> lightScheme - } - if(darkTheme && theme.blackTheme) { - colorScheme = colorScheme.copy(background = Color.Black) - } - if(!darkTheme) { - colorScheme = colorScheme.copy(background = colorScheme.primary.copy(alpha = 0.05f)) + }.let { + if(darkTheme && theme.blackTheme) it.copy(background = Color.Black) else it + }.let { + if(!darkTheme) it.copy(background = it.primary.copy(alpha = 0.05f)) else it } val view = LocalView.current SideEffect { val window = (view.context as Activity).window - window.statusBarColor = Color.Transparent.toArgb() WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme } MaterialTheme( colorScheme = colorScheme, - typography = Typography, content = content ) } \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/theme/Type.kt b/app/src/main/java/com/bintianqi/owndroid/ui/theme/Type.kt deleted file mode 100644 index ac461cc..0000000 --- a/app/src/main/java/com/bintianqi/owndroid/ui/theme/Type.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.bintianqi.owndroid.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) - /* Other default text styles to override - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp - ), - labelSmall = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp - ) - */ -) \ No newline at end of file