diff --git a/app/src/main/java/com/bintianqi/owndroid/AppLock.kt b/app/src/main/java/com/bintianqi/owndroid/AppLock.kt index d3c0052..e25a6c3 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppLock.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppLock.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -44,8 +44,8 @@ import androidx.compose.ui.window.DialogProperties fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismiss, DialogProperties(true, false)) { val context = LocalContext.current val fm = LocalFocusManager.current - var input by remember { mutableStateOf("") } - var isError by remember { mutableStateOf(false) } + var input by rememberSaveable { mutableStateOf("") } + var isError by rememberSaveable { mutableStateOf(false) } fun unlock() { if(input.hash() == SP.lockPasswordHash) { fm.clearFocus() diff --git a/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt b/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt index efe379f..51bce41 100644 --- a/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt +++ b/app/src/main/java/com/bintianqi/owndroid/DhizukuServer.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -103,7 +104,7 @@ class DhizukuActivity : ComponentActivity() { enableEdgeToEdge() val theme = ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme) setContent { - var appLockDialog by remember { mutableStateOf(false) } + var appLockDialog by rememberSaveable { mutableStateOf(false) } OwnDroidTheme(theme) { if (!appLockDialog) AlertDialog( icon = { diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index 165e88e..8e0fbcc 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -14,10 +14,9 @@ import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -400,7 +399,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) { InstallSystemUpdateScreen(vm::installSystemUpdate, ::navigateUp) } composable { - FrpPolicyScreen(vm::getFrpPolicy, vm::setFrpPolicy, ::navigateUp) + FrpPolicyScreen(vm.getFrpPolicy(), vm::setFrpPolicy, ::navigateUp) } composable { WipeDataScreen(vm::wipeData, ::navigateUp) } @@ -641,13 +640,14 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) { AppearanceScreen(::navigateUp, vm.theme, vm::changeTheme) } composable { - AppLockSettingsScreen(vm::getAppLockConfig, vm::setAppLockConfig, ::navigateUp) + AppLockSettingsScreen(vm.getAppLockConfig(), vm::setAppLockConfig, ::navigateUp) } composable { ApiSettings(vm::getApiEnabled, vm::setApiKey, ::navigateUp) } composable { - NotificationsScreen(vm::getEnabledNotifications, vm::setNotificationEnabled, ::navigateUp) + NotificationsScreen(vm.enabledNotifications, vm::getEnabledNotifications, + vm::setNotificationEnabled, ::navigateUp) } composable { AboutScreen(::navigateUp) } } @@ -702,7 +702,7 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) { scrollBehavior = sb ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { Column(Modifier .fillMaxSize() @@ -727,7 +727,7 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) { HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) } HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) } } - Spacer(Modifier.padding(vertical = 20.dp)) + Spacer(Modifier.height(BottomPadding)) } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt index 929ff3b..5083798 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt @@ -163,21 +163,16 @@ class MyViewModel(application: Application): AndroidViewModel(application) { fun setApiKey(key: String) { SP.apiKeyHash = if (key.isEmpty()) "" else key.hash() } - fun getEnabledNotifications(): List { + val enabledNotifications = MutableStateFlow(emptyList()) + fun getEnabledNotifications() { val list = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() } - return if (list == null) { - NotificationType.entries - } else { - NotificationType.entries.filter { it.id in list } - } + enabledNotifications.value = list ?: NotificationType.entries.map { it.id } } fun setNotificationEnabled(type: NotificationType, enabled: Boolean) { - val list = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() } - SP.notifications = if (list == null) { - NotificationType.entries.minus(type).map { it.id } - } else { - list.run { if (enabled) plus(type.id) else minus(type.id) } - }.joinToString { it.toString() } + enabledNotifications.update { list -> + if (enabled) list.plus(type.id) else list.minus(type.id) + } + SP.notifications = enabledNotifications.value.joinToString(",") { it.toString() } } val chosenPackage = Channel(1, BufferOverflow.DROP_LATEST) @@ -267,7 +262,7 @@ class MyViewModel(application: Application): AndroidViewModel(application) { fun getPackagePermissions(name: String) { if (name.isValidPackageName) { packagePermissions.value = runtimePermissions.associate { - it.permission to DPM.getPermissionGrantState(DAR, name, it.permission) + it.id to DPM.getPermissionGrantState(DAR, name, it.id) } } else { packagePermissions.value = emptyMap() @@ -1027,7 +1022,8 @@ class MyViewModel(application: Application): AndroidViewModel(application) { } else { Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() { override fun onRequestPermission(grantResult: Int) { - if(grantResult == PackageManager.PERMISSION_GRANTED) onSucceed() + if (grantResult == PackageManager.PERMISSION_GRANTED) onSucceed() + else callback(false, application.getString(R.string.dhizuku_permission_not_granted)) } }) } diff --git a/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt b/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt index 5e90d18..2b664c8 100644 --- a/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/NotificationUtils.kt @@ -16,9 +16,9 @@ object NotificationUtils { NotificationManagerCompat.from(context).createNotificationChannelsCompat(channels) } fun sendBasicNotification( - context: Context, type: NotificationType, channel: MyNotificationChannel, text: String + context: Context, type: NotificationType, text: String ) { - val notification = NotificationCompat.Builder(context, channel.id) + val notification = NotificationCompat.Builder(context, type.channel.id) .setSmallIcon(type.icon) .setContentTitle(context.getString(type.text)) .setContentText(text) @@ -29,7 +29,7 @@ object NotificationUtils { fun notifyEvent(context: Context, type: NotificationType, text: String) { val enabledNotifications = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() } if (enabledNotifications == null || type.id in enabledNotifications) { - sendBasicNotification(context, type, MyNotificationChannel.Events, text) + sendBasicNotification(context, type, text) } } fun cancel(context: Context, type: NotificationType) { @@ -38,19 +38,40 @@ object NotificationUtils { } } -enum class NotificationType(val id: Int, val text: Int, val icon: Int) { - LockTaskMode(1, R.string.lock_task_mode, R.drawable.lock_fill0), - PasswordChanged(2, R.string.password_changed, R.drawable.password_fill0), - UserAdded(3, R.string.user_added, R.drawable.person_add_fill0), - UserStarted(4, R.string.user_started, R.drawable.person_fill0), - UserSwitched(5, R.string.user_switched, R.drawable.person_fill0), - UserStopped(6, R.string.user_stopped, R.drawable.person_off), - UserRemoved(7, R.string.user_removed, R.drawable.person_remove_fill0), - BugReportShared(8, R.string.bug_report_shared, R.drawable.bug_report_fill0), - BugReportSharingDeclined(9, R.string.bug_report_sharing_declined, R.drawable.bug_report_fill0), - BugReportFailed(10, R.string.bug_report_failed, R.drawable.bug_report_fill0), - SystemUpdatePending(11, R.string.system_update_pending, R.drawable.system_update_fill0), - SecurityLogsCollected(12, R.string.security_logs_collected, R.drawable.description_fill0), +enum class NotificationType( + val id: Int, val text: Int, val icon: Int, val channel: MyNotificationChannel +) { + LockTaskMode( + 1, R.string.lock_task_mode, R.drawable.lock_fill0, MyNotificationChannel.LockTaskMode + ), + PasswordChanged( + 2, R.string.password_changed, R.drawable.password_fill0, MyNotificationChannel.Events + ), + UserAdded(3, R.string.user_added, R.drawable.person_add_fill0, MyNotificationChannel.Events), + UserStarted(4, R.string.user_started, R.drawable.person_fill0, MyNotificationChannel.Events), + UserSwitched(5, R.string.user_switched, R.drawable.person_fill0, MyNotificationChannel.Events), + UserStopped(6, R.string.user_stopped, R.drawable.person_off, MyNotificationChannel.Events), + UserRemoved( + 7, R.string.user_removed, R.drawable.person_remove_fill0, MyNotificationChannel.Events + ), + BugReportShared( + 8, R.string.bug_report_shared, R.drawable.bug_report_fill0, MyNotificationChannel.Events + ), + BugReportSharingDeclined( + 9, R.string.bug_report_sharing_declined, R.drawable.bug_report_fill0, + MyNotificationChannel.Events + ), + BugReportFailed( + 10, R.string.bug_report_failed, R.drawable.bug_report_fill0, MyNotificationChannel.Events + ), + SystemUpdatePending( + 11, R.string.system_update_pending, R.drawable.system_update_fill0, + MyNotificationChannel.Events + ), + SecurityLogsCollected( + 12, R.string.security_logs_collected, R.drawable.description_fill0, + MyNotificationChannel.SecurityLogging + ), } enum class MyNotificationChannel(val id: String, val text: Int, val importance: Int) { diff --git a/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt b/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt index 10c95ed..a47eba3 100644 --- a/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt +++ b/app/src/main/java/com/bintianqi/owndroid/PackageChooser.kt @@ -10,10 +10,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -146,7 +145,7 @@ fun AppChooserScreen( colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceContainer) ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> LazyColumn(Modifier.fillMaxSize().padding(paddingValues)) { if (progress < 1F) stickyHeader { @@ -174,7 +173,7 @@ fun AppChooserScreen( } } } - item { Spacer(Modifier.padding(vertical = 30.dp)) } + item { Spacer(Modifier.height(60.dp)) } } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/Settings.kt b/app/src/main/java/com/bintianqi/owndroid/Settings.kt index 24e0185..a004147 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Settings.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Settings.kt @@ -12,10 +12,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions @@ -39,9 +37,9 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -115,7 +113,7 @@ fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { } ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier @@ -231,22 +229,15 @@ data class AppLockConfig( @Composable fun AppLockSettingsScreen( - getConfig: () -> AppLockConfig, setConfig: (AppLockConfig) -> Unit, + config: AppLockConfig, setConfig: (AppLockConfig) -> Unit, onNavigateUp: () -> Unit ) = MyScaffold(R.string.app_lock, onNavigateUp) { - var password by remember { mutableStateOf("") } - var confirmPassword by remember { mutableStateOf("") } - var allowBiometrics by remember { mutableStateOf(false) } - var lockWhenLeaving by remember { mutableStateOf(false) } - var alreadySet by remember { mutableStateOf(false) } + var password by rememberSaveable { mutableStateOf(config.password ?: "") } + var confirmPassword by rememberSaveable { mutableStateOf("") } + var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) } + var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) } + var alreadySet by rememberSaveable { mutableStateOf(config.password != null) } val isInputLegal = password.length !in 1..3 && (alreadySet || password.isNotBlank()) - LaunchedEffect(Unit) { - val config = getConfig() - password = config.password ?: "" - allowBiometrics = config.biometrics - lockWhenLeaving = config.whenLeaving - alreadySet = config.password != null - } OutlinedTextField( password, { password = it }, Modifier.fillMaxWidth().padding(vertical = 4.dp), label = { Text(stringResource(R.string.password)) }, @@ -305,7 +296,7 @@ fun ApiSettings( var alreadyEnabled by remember { mutableStateOf(getEnabled()) } MyScaffold(R.string.api, onNavigateUp) { var enabled by remember { mutableStateOf(alreadyEnabled) } - var key by remember { mutableStateOf("") } + var key by rememberSaveable { mutableStateOf("") } SwitchItem(R.string.enable, state = enabled, onCheckedChange = { enabled = it }, padding = false) @@ -339,15 +330,17 @@ fun ApiSettings( @Composable fun NotificationsScreen( - getState: () -> List, setNotification: (NotificationType, Boolean) -> Unit, - onNavigateUp: () -> Unit + enabledNotifications: StateFlow>, getState: () -> Unit, + setNotification: (NotificationType, Boolean) -> Unit, onNavigateUp: () -> Unit ) = MyScaffold(R.string.notifications, onNavigateUp, 0.dp) { - val enabledNotifications = remember { mutableStateListOf(*getState().toTypedArray()) } - NotificationType.entries.forEach { type -> - SwitchItem(type.text, type in enabledNotifications, { - setNotification(type, it) - enabledNotifications.run { if (it) plusAssign(type) else minusAssign(type) } - }) + val notifications by enabledNotifications.collectAsStateWithLifecycle() + LaunchedEffect(Unit) { + getState() + } + NotificationType.entries.filter { + it.channel == MyNotificationChannel.Events + }.forEach { type -> + SwitchItem(type.text, type.id in notifications, { setNotification(type, it) }) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/ShortcutUtils.kt b/app/src/main/java/com/bintianqi/owndroid/ShortcutUtils.kt index edc0056..efff2dd 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ShortcutUtils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ShortcutUtils.kt @@ -5,8 +5,6 @@ import android.content.Intent import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat -import java.security.SecureRandom -import kotlin.io.encoding.Base64 object ShortcutUtils { fun setAllShortcuts(context: Context, enabled: Boolean) { diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index ed81a05..197244d 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -9,8 +9,22 @@ import android.net.Uri import android.os.Build import android.widget.Toast import androidx.annotation.StringRes +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.union +import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.SaverScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json @@ -91,6 +105,8 @@ fun exportLogs(context: Context, uri: Uri) { val HorizontalPadding = 16.dp +val BottomPadding = 60.dp + @OptIn(ExperimentalStdlibApi::class) fun String.hash(): String { val md = MessageDigest.getInstance("SHA-256") @@ -129,3 +145,18 @@ fun generateBase64Key(length: Int): String { SecureRandom().nextBytes(ba) return Base64.withPadding(Base64.PaddingOption.ABSENT).encode(ba) } + +fun Modifier.clickableTextField(onClick: () -> Unit) = + pointerInput(Unit) { + awaitEachGesture { + awaitFirstDown(pass = PointerEventPass.Initial) + val upEvent = waitForUpOrCancellation(pass = PointerEventPass.Initial) + if (upEvent != null) onClick() + } + } + +@Composable +fun adaptiveInsets(): WindowInsets { + val navbar = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) + return WindowInsets.ime.union(navbar).union(WindowInsets.displayCutout) +} \ No newline at end of file diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt index b95f472..cb8f9a1 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Applications.kt @@ -15,15 +15,14 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions @@ -53,7 +52,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -73,10 +71,12 @@ import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.AppInfo import com.bintianqi.owndroid.AppInstallerActivity +import com.bintianqi.owndroid.BottomPadding import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.adaptiveInsets import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem import com.bintianqi.owndroid.ui.FunctionItem @@ -156,7 +156,7 @@ fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Un scrollBehavior = sb ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( Modifier @@ -231,7 +231,7 @@ fun ApplicationDetailsScreen( ) { val packageName = param.packageName val privilege by Privilege.status.collectAsStateWithLifecycle() - var dialog by remember { mutableIntStateOf(0) } // 1: clear storage, 2: uninstall + var dialog by rememberSaveable { mutableIntStateOf(0) } // 1: clear storage, 2: uninstall val info = vm.getAppInfo(packageName) val status by vm.appStatus.collectAsStateWithLifecycle() LaunchedEffect(Unit) { vm.getAppStatus(packageName) } @@ -299,8 +299,8 @@ fun PermissionsManagerScreen( ) { val packageNameParam = param.packageName val privilege by Privilege.status.collectAsStateWithLifecycle() - var packageName by remember { mutableStateOf(packageNameParam ?: "") } - var selectedPermission by remember { mutableStateOf(null) } + var packageName by rememberSaveable { mutableStateOf(packageNameParam ?: "") } + var selectedPermission by rememberSaveable { mutableIntStateOf(-1) } val permissions by packagePermissions.collectAsStateWithLifecycle() LaunchedEffect(Unit) { packageName = chosenPackage.receive() @@ -316,19 +316,19 @@ fun PermissionsManagerScreen( Spacer(Modifier.padding(vertical = 4.dp)) } } - items(runtimePermissions, { it.permission }) { + itemsIndexed(runtimePermissions, { _, it -> it.id }) { index, it -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .clickable(packageName.isValidPackageName) { - selectedPermission = it + selectedPermission = index } .padding(8.dp) ) { Icon(painterResource(it.icon), null, Modifier.padding(horizontal = 12.dp)) Column { - val state = when(permissions[it.permission]) { + val state = when(permissions[it.id]) { PERMISSION_GRANT_STATE_DEFAULT -> R.string.default_stringres PERMISSION_GRANT_STATE_GRANTED -> R.string.granted PERMISSION_GRANT_STATE_DENIED -> R.string.denied @@ -340,17 +340,18 @@ fun PermissionsManagerScreen( } } item { - Spacer(Modifier.padding(vertical = 30.dp)) + Spacer(Modifier.height(BottomPadding)) } } - if(selectedPermission != null) { + if(selectedPermission != -1) { + val permission = runtimePermissions[selectedPermission] fun changeState(state: Int) { - val result = setPackagePermission(packageName, selectedPermission!!.permission, state) - if (result) selectedPermission = null + val result = setPackagePermission(packageName, permission.id, state) + if (result) selectedPermission = -1 } @Composable fun GrantPermissionItem(label: Int, status: Int) { - val selected = permissions[selectedPermission!!.permission] == status + val selected = permissions[permission.id] == status Row( Modifier .fillMaxWidth() @@ -365,14 +366,14 @@ fun PermissionsManagerScreen( } } AlertDialog( - onDismissRequest = { selectedPermission = null }, - confirmButton = { TextButton({ selectedPermission = null }) { Text(stringResource(R.string.cancel)) } }, - title = { Text(stringResource(selectedPermission!!.label)) }, + onDismissRequest = { selectedPermission = -1 }, + confirmButton = { TextButton({ selectedPermission = -1 }) { Text(stringResource(R.string.cancel)) } }, + title = { Text(stringResource(permission.label)) }, text = { Column { - Text(selectedPermission!!.permission) + Text(permission.id) Spacer(Modifier.padding(vertical = 4.dp)) - if(!(VERSION.SDK_INT >= 31 && selectedPermission!!.profileOwnerRestricted && privilege.profile)) { + if(!(VERSION.SDK_INT >= 31 && permission.profileOwnerRestricted && privilege.profile)) { GrantPermissionItem(R.string.granted, PERMISSION_GRANT_STATE_GRANTED) } GrantPermissionItem(R.string.denied, PERMISSION_GRANT_STATE_DENIED) @@ -393,8 +394,8 @@ fun ClearAppStorageScreen( chosenPackage: Channel, onChoosePackage: () -> Unit, onClear: (String, (Boolean) -> Unit) -> Unit, onNavigateUp: () -> Unit ) { - var dialog by remember { mutableStateOf(false) } - var packageName by remember { mutableStateOf("") } + var dialog by rememberSaveable { mutableStateOf(false) } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { packageName = chosenPackage.receive() } @@ -418,7 +419,7 @@ private fun ClearAppStorageDialog( packageName: String, onClear: (String, (Boolean) -> Unit) -> Unit, onClose: () -> Unit ) { val context = LocalContext.current - var clearing by remember { mutableStateOf(false) } + var clearing by rememberSaveable { mutableStateOf(false) } AlertDialog( title = { Text(stringResource(R.string.clear_app_storage)) }, text = { @@ -457,8 +458,8 @@ fun UninstallAppScreen( chosenPackage: Channel, onChoosePackage: () -> Unit, onUninstall: (String, (String?) -> Unit) -> Unit, onNavigateUp: () -> Unit ) { - var dialog by remember { mutableStateOf(false) } - var packageName by remember { mutableStateOf("") } + var dialog by rememberSaveable { mutableStateOf(false) } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { packageName = chosenPackage.receive() } @@ -483,8 +484,8 @@ fun UninstallAppScreen( private fun UninstallAppDialog( packageName: String, onUninstall: (String, (String?) -> Unit) -> Unit, onClose: () -> Unit ) { - var uninstalling by remember { mutableStateOf(false) } - var errorMessage by remember { mutableStateOf(null) } + var uninstalling by rememberSaveable { mutableStateOf(false) } + var errorMessage by rememberSaveable { mutableStateOf(null) } AlertDialog( title = { Text(stringResource(R.string.uninstall)) }, text = { @@ -525,7 +526,7 @@ fun InstallExistingAppScreen( onInstall: (String) -> Boolean, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var packageName by remember { mutableStateOf("") } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { packageName = chosenPackage.receive() } @@ -561,7 +562,7 @@ fun CredentialManagerPolicyScreen( val context = LocalContext.current var policy by rememberSaveable { mutableIntStateOf(getCmPolicy()) } val packages by cmPackages.collectAsStateWithLifecycle() - var packageName by remember { mutableStateOf("") } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { packageName = chosenPackage.receive() } @@ -623,7 +624,7 @@ fun PermittedAsAndImPackages( ) { val context = LocalContext.current val packages by packagesState.collectAsStateWithLifecycle() - var packageName by remember { mutableStateOf("") } + var packageName by rememberSaveable { mutableStateOf("") } var allowAll by rememberSaveable { mutableStateOf(getPackages()) } LaunchedEffect(Unit) { packageName = chosenPackage.receive() @@ -673,7 +674,7 @@ fun EnableSystemAppScreen( onEnable: (String) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var packageName by remember { mutableStateOf("") } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { packageName = chosenPackage.receive() } @@ -704,7 +705,7 @@ fun SetDefaultDialerScreen( chosenPackage: Channel, onChoosePackage: () -> Unit, onSet: (String) -> Unit, onNavigateUp: () -> Unit ) { - var packageName by remember { mutableStateOf("") } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { packageName = chosenPackage.receive() } @@ -743,7 +744,7 @@ fun PackageFunctionScreen( chosenPackage: Channel, onChoosePackage: () -> Unit, notes: Int? = null ) { val packages by packagesState.collectAsStateWithLifecycle() - var packageName by remember { mutableStateOf("") } + var packageName by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { onGet() packageName = chosenPackage.receive() diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt index c075f31..89c9ef3 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt @@ -15,7 +15,6 @@ import android.os.Build.VERSION import android.util.Log import androidx.annotation.RequiresApi import com.bintianqi.owndroid.MyApplication -import com.bintianqi.owndroid.MyNotificationChannel import com.bintianqi.owndroid.NotificationType import com.bintianqi.owndroid.NotificationUtils import com.bintianqi.owndroid.Privilege @@ -93,7 +92,7 @@ fun Context.getPackageInstaller(): PackageInstaller { val dhizukuErrorStatus = MutableStateFlow(0) data class PermissionItem( - val permission: String, + val id: String, val label: Int, val icon: Int, val profileOwnerRestricted: Boolean = false, @@ -497,7 +496,7 @@ fun retrieveSecurityLogs(app: MyApplication) { val logs = Privilege.DPM.retrieveSecurityLogs(Privilege.DAR) ?: return@launch app.myRepo.writeSecurityLogs(logs) NotificationUtils.sendBasicNotification( - app, NotificationType.SecurityLogsCollected, MyNotificationChannel.SecurityLogging, + app, NotificationType.SecurityLogsCollected, app.getString(R.string.n_logs_in_total, logs.size) ) } 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 5be58f1..85368d9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -24,18 +24,14 @@ import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -65,6 +61,7 @@ import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon @@ -113,12 +110,13 @@ import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.R -import com.bintianqi.owndroid.formatFileSize +import com.bintianqi.owndroid.clickableTextField import com.bintianqi.owndroid.formatDate +import com.bintianqi.owndroid.formatFileSize +import com.bintianqi.owndroid.adaptiveInsets import com.bintianqi.owndroid.popToast import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.ErrorDialog -import com.bintianqi.owndroid.ui.ExpandExposedTextFieldIcon import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem import com.bintianqi.owndroid.ui.FunctionItem @@ -181,8 +179,8 @@ fun NetworkOptionsScreen( getLanEnabled: () -> Boolean, setLanEnabled: (Boolean) -> Unit, onNavigateUp: () -> Unit ) { val privilege by Privilege.status.collectAsStateWithLifecycle() - var dialog by remember { mutableIntStateOf(0) } - var lanEnabled by remember { mutableStateOf(getLanEnabled()) } + var dialog by rememberSaveable { mutableIntStateOf(0) } + var lanEnabled by rememberSaveable { mutableStateOf(getLanEnabled()) } MyScaffold(R.string.options, onNavigateUp, 0.dp) { if(VERSION.SDK_INT >= 30 && (privilege.device || privilege.org)) { SwitchItem(R.string.lockdown_admin_configured_network, icon = R.drawable.wifi_password_fill0, @@ -224,7 +222,7 @@ fun WifiScreen( colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceContainer) ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier.fillMaxSize().padding(paddingValues) @@ -266,7 +264,7 @@ fun WifiOverviewScreen( ) { val context = LocalContext.current val privilege by Privilege.status.collectAsStateWithLifecycle() - var macDialog by remember { mutableStateOf(false) } + var macDialog by rememberSaveable { mutableStateOf(false) } Column(Modifier.fillMaxSize()) { Spacer(Modifier.height(10.dp)) Row( @@ -384,7 +382,7 @@ private fun SavedNetworks( removeNetwork: (Int) -> Boolean, editNetwork: (Int) -> Unit ) { val context = LocalContext.current - var dialog by remember { mutableIntStateOf(-1) } + var dialog by rememberSaveable { mutableIntStateOf(-1) } val list by configuredNetworks.collectAsStateWithLifecycle() LaunchedEffect(Unit) { getConfiguredNetworks() @@ -513,7 +511,7 @@ fun UpdateNetworkScreen(info: WifiInfo, setNetwork: (WifiInfo) -> Boolean, onNav colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceContainer) ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier.fillMaxSize().padding(paddingValues) @@ -537,21 +535,21 @@ private fun AddNetworkScreen( val context = LocalContext.current val fm = LocalFocusManager.current /** 0: None, 1:Status, 2:Security, 3:MAC randomization, 4:Static IP, 5:Proxy, 6:Hidden SSID */ - var menu by remember { mutableIntStateOf(0) } - var status by remember { mutableStateOf(WifiStatus.Enabled) } - var ssid by remember { mutableStateOf("") } - var hiddenSsid by remember { mutableStateOf(false) } - var security by remember { mutableStateOf(WifiSecurity.Open) } - var password by remember { mutableStateOf("") } - var macRandomization by remember { mutableStateOf(WifiMacRandomization.None) } - var ipMode by remember { mutableStateOf(IpMode.Dhcp) } - var ipAddress by remember { mutableStateOf("") } - var gatewayAddress by remember { mutableStateOf("") } - var dnsServers by remember { mutableStateOf("") } - var proxyMode by remember { mutableStateOf(ProxyMode.None) } - var httpProxyHost by remember { mutableStateOf("") } - var httpProxyPort by remember { mutableStateOf("") } - var httpProxyExclList by remember { mutableStateOf("") } + var menu by rememberSaveable { mutableIntStateOf(0) } + var status by rememberSaveable { mutableStateOf(WifiStatus.Enabled) } + var ssid by rememberSaveable { mutableStateOf("") } + var hiddenSsid by rememberSaveable { mutableStateOf(false) } + var security by rememberSaveable { mutableStateOf(WifiSecurity.Open) } + var password by rememberSaveable { mutableStateOf("") } + var macRandomization by rememberSaveable { mutableStateOf(WifiMacRandomization.None) } + var ipMode by rememberSaveable { mutableStateOf(IpMode.Dhcp) } + var ipAddress by rememberSaveable { mutableStateOf("") } + var gatewayAddress by rememberSaveable { mutableStateOf("") } + var dnsServers by rememberSaveable { mutableStateOf("") } + var proxyMode by rememberSaveable { mutableStateOf(ProxyMode.None) } + var httpProxyHost by rememberSaveable { mutableStateOf("") } + var httpProxyPort by rememberSaveable { mutableStateOf("") } + var httpProxyExclList by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { if (updating) { hiddenSsid = null @@ -575,7 +573,7 @@ private fun AddNetworkScreen( Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.status)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == 1) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == 1) }, ) ExposedDropdownMenu(menu == 1, { menu = 0 }) { WifiStatus.entries.forEach { @@ -600,7 +598,7 @@ private fun AddNetworkScreen( stringResource(hiddenSsid?.yesOrNo ?: R.string.unchanged), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.hidden_ssid)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == 1) } + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == 1) } ) DropdownMenu(menu == 6, { menu = 0 }) { if (updating) DropdownMenuItem( @@ -633,7 +631,7 @@ private fun AddNetworkScreen( stringResource(security?.text ?: R.string.unchanged), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.security)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == 1) } + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == 1) } ) ExposedDropdownMenu(menu == 2, { menu = 0 }) { if (updating) UnchangedMenuItem { security = null } @@ -662,7 +660,7 @@ private fun AddNetworkScreen( stringResource(macRandomization?.text ?: R.string.unchanged), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.mac_randomization)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == 3) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == 3) }, ) ExposedDropdownMenu(menu == 3, { menu = 0 }) { if (updating) UnchangedMenuItem { macRandomization = null } @@ -686,7 +684,7 @@ private fun AddNetworkScreen( stringResource(ipMode?.text ?: R.string.unchanged), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.ip_settings)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == 4) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == 4) }, ) ExposedDropdownMenu(menu == 4, { menu = 0 }) { if (updating) UnchangedMenuItem { ipMode = null } @@ -737,7 +735,7 @@ private fun AddNetworkScreen( stringResource(proxyMode?.text ?: R.string.unchanged), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.proxy)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == 5) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == 5) }, ) ExposedDropdownMenu(menu == 5, { menu = 0 }) { if (updating) UnchangedMenuItem { proxyMode = null } @@ -783,19 +781,29 @@ private fun AddNetworkScreen( } Button( onClick = { + val proxyConf = if (proxyMode == ProxyMode.Http) { + ProxyConf( + httpProxyHost, httpProxyPort.toInt(), + httpProxyExclList.lines().filter { it.isNotBlank() } + ) + } else null + val ipConf = if (ipMode == IpMode.Static) { + IpConf(ipAddress, gatewayAddress, dnsServers.lines().filter { it.isNotBlank() }) + } else null val result = setNetwork(WifiInfo( -1, ssid, hiddenSsid, "", macRandomization, status, security, password, ipMode, - IpConf(ipAddress, gatewayAddress, dnsServers.lines().filter { it.isNotBlank() }), - proxyMode, ProxyConf(httpProxyHost, httpProxyPort.toInt(), httpProxyExclList.lines().filter { it.isNotBlank() }) + ipConf, proxyMode, proxyConf )) context.showOperationResultToast(result) if (result) onNavigateUp() }, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + enabled = (proxyMode != ProxyMode.Http || + (httpProxyPort.toIntOrNull() != null && httpProxyHost.isNotBlank())) ) { Text(stringResource(if (updating) R.string.update else R.string.add)) } - Spacer(Modifier.height(40.dp)) + Spacer(Modifier.height(60.dp)) } } @@ -807,7 +815,7 @@ fun WifiSecurityLevelScreen( getLevel: () -> Int, setLevel: (Int) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var level by remember { mutableIntStateOf(getLevel()) } + var level by rememberSaveable { mutableIntStateOf(getLevel()) } MyScaffold(R.string.min_wifi_security_level, onNavigateUp, 0.dp) { FullWidthRadioButtonItem(R.string.wifi_security_open, level == WIFI_SECURITY_OPEN) { level = WIFI_SECURITY_OPEN } FullWidthRadioButtonItem("WEP, WPA(2)-PSK", level == WIFI_SECURITY_PERSONAL) { level = WIFI_SECURITY_PERSONAL } @@ -845,8 +853,8 @@ fun WifiSsidPolicyScreen( val context = LocalContext.current val focusMgr = LocalFocusManager.current MyScaffold(R.string.wifi_ssid_policy, onNavigateUp, 0.dp) { - var type by remember { mutableStateOf(SsidPolicyType.None) } - val list = remember { mutableStateListOf() } + var type by rememberSaveable { mutableStateOf(SsidPolicyType.None) } + val list = rememberSaveable { mutableStateListOf() } LaunchedEffect(Unit) { getPolicy().let { type = it.type @@ -959,11 +967,7 @@ fun NetworkStatsScreen( var uid by rememberSaveable { mutableIntStateOf(NetworkStats.Bucket.UID_ALL) } var tag by rememberSaveable { mutableIntStateOf(NetworkStats.Bucket.TAG_NONE) } var state by rememberSaveable { mutableStateOf(NetworkStatsState.All) } - val startTimeIs = remember { MutableInteractionSource() } - val endTimeIs = remember { MutableInteractionSource() } - if (startTimeIs.collectIsPressedAsState().value) menu = NetworkStatsMenu.StartTime - if (endTimeIs.collectIsPressedAsState().value) menu = NetworkStatsMenu.EndTime - var errorMessage by remember { mutableStateOf(null) } + var errorMessage by rememberSaveable { mutableStateOf(null) } MyScaffold(R.string.network_stats, onNavigateUp) { ExposedDropdownMenuBox( menu == NetworkStatsMenu.Type, @@ -974,7 +978,9 @@ fun NetworkStatsScreen( stringResource(type.text), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.type)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == NetworkStatsMenu.Type) } + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == NetworkStatsMenu.Type) + } ) ExposedDropdownMenu( menu == NetworkStatsMenu.Type, { menu = NetworkStatsMenu.None } @@ -1001,7 +1007,9 @@ fun NetworkStatsScreen( stringResource(target.text), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.target)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == NetworkStatsMenu.Target) } + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == NetworkStatsMenu.Target) + } ) ExposedDropdownMenu( menu == NetworkStatsMenu.Target, { menu = NetworkStatsMenu.None } @@ -1028,7 +1036,9 @@ fun NetworkStatsScreen( stringResource(networkType.text), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.network_type)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == NetworkStatsMenu.NetworkType) } + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == NetworkStatsMenu.NetworkType) + } ) ExposedDropdownMenu( menu == NetworkStatsMenu.NetworkType, { menu = NetworkStatsMenu.None } @@ -1045,18 +1055,22 @@ fun NetworkStatsScreen( } } OutlinedTextField( - value = startTime.let { if(it == -1L) "" else formatDate(it) }, onValueChange = {}, readOnly = true, - label = { Text(stringResource(R.string.start_time)) }, - interactionSource = startTimeIs, - isError = startTime >= endTime, - modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) + formatDate(startTime), {}, + Modifier + .fillMaxWidth() + .clickableTextField { menu = NetworkStatsMenu.StartTime } + .padding(bottom = 4.dp), + readOnly = true, label = { Text(stringResource(R.string.start_time)) }, + isError = startTime >= endTime ) OutlinedTextField( - value = formatDate(endTime), onValueChange = {}, readOnly = true, - label = { Text(stringResource(R.string.end_time)) }, - interactionSource = endTimeIs, - isError = startTime >= endTime, - modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) + formatDate(endTime), {}, + Modifier + .fillMaxWidth() + .clickableTextField { menu = NetworkStatsMenu.EndTime } + .padding(bottom = 4.dp), + readOnly = true, label = { Text(stringResource(R.string.end_time)) }, + isError = startTime >= endTime ) if(target == NetworkStatsTarget.Uid || target == NetworkStatsTarget.UidTag || target == NetworkStatsTarget.UidTagState) ExposedDropdownMenuBox( @@ -1077,7 +1091,7 @@ fun NetworkStatsScreen( it.toIntOrNull()?.let { num -> uid = num } }, readOnly = readOnly, label = { Text(stringResource(R.string.uid)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == NetworkStatsMenu.Uid) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == NetworkStatsMenu.Uid) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), isError = !readOnly && uidText.toIntOrNull() == null, modifier = Modifier @@ -1131,7 +1145,9 @@ fun NetworkStatsScreen( it.toIntOrNull()?.let { num -> tag = num } }, readOnly = readOnly, label = { Text(stringResource(R.string.uid)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == NetworkStatsMenu.Tag) }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == NetworkStatsMenu.Tag) + }, isError = !readOnly && tagText.toIntOrNull() == null, modifier = Modifier .menuAnchor(if(readOnly) MenuAnchorType.PrimaryNotEditable else MenuAnchorType.PrimaryEditable) @@ -1168,7 +1184,9 @@ fun NetworkStatsScreen( stringResource(state.text), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.uid)) }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == NetworkStatsMenu.State) } + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == NetworkStatsMenu.State) + } ) ExposedDropdownMenu( menu == NetworkStatsMenu.State, { menu = NetworkStatsMenu.None } @@ -1251,7 +1269,7 @@ data class NetworkStatsData( fun NetworkStatsViewerScreen( data: List, clearData: () -> Unit, onNavigateUp: () -> Unit ) { - var index by remember { mutableIntStateOf(0) } + var index by rememberSaveable { mutableIntStateOf(0) } val size = data.size val ps = rememberPagerState { size } index = ps.currentPage @@ -1362,7 +1380,7 @@ fun PrivateDnsScreen( val context = LocalContext.current val focusMgr = LocalFocusManager.current var mode by remember { mutableStateOf(PrivateDnsMode.Opportunistic) } - var inputHost by remember { mutableStateOf("") } + var inputHost by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { val conf = getPrivateDns() mode = conf.mode @@ -1451,12 +1469,12 @@ fun RecommendedGlobalProxyScreen( ) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var type by remember { mutableStateOf(ProxyType.Off) } - var pacUrl by remember { mutableStateOf("") } - var specifyPort by remember { mutableStateOf(false) } - var host by remember { mutableStateOf("") } - var port by remember { mutableStateOf("") } - var exclList by remember { mutableStateOf("") } + var type by rememberSaveable { mutableStateOf(ProxyType.Off) } + var pacUrl by rememberSaveable { mutableStateOf("") } + var specifyPort by rememberSaveable { mutableStateOf(false) } + var host by rememberSaveable { mutableStateOf("") } + var port by rememberSaveable { mutableStateOf("") } + var exclList by rememberSaveable { mutableStateOf("") } MyScaffold(R.string.recommended_global_proxy, onNavigateUp, 0.dp) { ProxyType.entries.forEach { FullWidthRadioButtonItem(it.text, type == it) { type = it } @@ -1576,7 +1594,7 @@ fun PreferentialNetworkServiceScreen( pnsConfigs: StateFlow>, getConfigs: () -> Unit, onNavigateUp: () -> Unit, onNavigate: (AddPreferentialNetworkServiceConfig) -> Unit ) { - var masterEnabled by remember { mutableStateOf(getEnabled()) } + var masterEnabled by rememberSaveable { mutableStateOf(getEnabled()) } val configs by pnsConfigs.collectAsStateWithLifecycle() LaunchedEffect(Unit) { getConfigs() @@ -1633,12 +1651,12 @@ fun AddPreferentialNetworkServiceConfigScreen( setConfig: (PreferentialNetworkServiceInfo, Boolean) -> Unit, onNavigateUp: () -> Unit ) { val updateMode = origin.id != -1 - var enabled by remember { mutableStateOf(origin.enabled) } - var id by remember { mutableIntStateOf(origin.id) } - var allowFallback by remember { mutableStateOf(origin.allowFallback) } - var blockNonMatching by remember { mutableStateOf(origin.blockNonMatching) } - var excludedUids by remember { mutableStateOf(origin.excludedUids.joinToString("\n")) } - var includedUids by remember { mutableStateOf(origin.includedUids.joinToString("\n")) } + var enabled by rememberSaveable { mutableStateOf(origin.enabled) } + var id by rememberSaveable { mutableIntStateOf(origin.id) } + var allowFallback by rememberSaveable { mutableStateOf(origin.allowFallback) } + var blockNonMatching by rememberSaveable { mutableStateOf(origin.blockNonMatching) } + var excludedUids by rememberSaveable { mutableStateOf(origin.excludedUids.joinToString("\n")) } + var includedUids by rememberSaveable { mutableStateOf(origin.includedUids.joinToString("\n")) } var dropdown by remember { mutableStateOf(false) } MySmallTitleScaffold(R.string.preferential_network_service, onNavigateUp) { SwitchItem(title = R.string.enabled, state = enabled, onCheckedChange = { enabled = it }, padding = false) @@ -1647,7 +1665,7 @@ fun AddPreferentialNetworkServiceConfigScreen( if (id == -1) "" else id.toString(), {}, Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable), readOnly = true, label = { Text("id") }, - trailingIcon = { ExpandExposedTextFieldIcon(dropdown) } + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(dropdown) } ) ExposedDropdownMenu(dropdown, { dropdown = false }) { for (i in 1..5) { @@ -1724,7 +1742,7 @@ fun OverrideApnScreen( apnConfigs: StateFlow>, getConfigs: () -> Unit, getEnabled: () -> Boolean, setEnabled: (Boolean) -> Unit, onNavigateUp: () -> Unit, onNavigateToAddSetting: (Int) -> Unit ) { - var enabled by remember { mutableStateOf(getEnabled()) } + var enabled by rememberSaveable { mutableStateOf(getEnabled()) } val configs by apnConfigs.collectAsStateWithLifecycle() LaunchedEffect(Unit) { getConfigs() } MyScaffold(R.string.override_apn, onNavigateUp, 0.dp) { @@ -1859,30 +1877,30 @@ fun AddApnSettingScreen( ) { val context = LocalContext.current var menu by remember { mutableStateOf(ApnMenu.None) } - var enabled by remember { mutableStateOf(true) } - var entryName by remember { mutableStateOf(origin?.name ?: "") } - var apnName by remember { mutableStateOf(origin?.apn ?: "") } - var apnType by remember { mutableIntStateOf(origin?.apnType ?: 0) } - var profileId by remember { mutableStateOf(origin?.profileId?.toString() ?: "") } - var carrierId by remember { mutableStateOf(origin?.carrierId?.toString() ?: "") } - var authType by remember { mutableStateOf(ApnAuthType.None) } - var user by remember { mutableStateOf(origin?.username ?: "") } - var password by remember { mutableStateOf(origin?.password ?: "") } - var proxy by remember { mutableStateOf(origin?.proxy ?: "") } - var port by remember { mutableStateOf(origin?.port?.toString() ?: "") } - var mmsProxy by remember { mutableStateOf(origin?.mmsProxy ?: "") } - var mmsPort by remember { mutableStateOf(origin?.mmsPort?.toString() ?: "") } - var mmsc by remember { mutableStateOf(origin?.mmsc ?: "") } - var mtuV4 by remember { mutableStateOf(origin?.mtuV4?.toString() ?: "") } - var mtuV6 by remember { mutableStateOf(origin?.mtuV6?.toString() ?: "") } - var mvnoType by remember { mutableStateOf(origin?.mvno ?: ApnMvnoType.SPN) } - var networkType by remember { mutableIntStateOf(origin?.networkType ?: 0) } - var operatorNumeric by remember { mutableStateOf(origin?.operatorNumeric ?: "") } - var protocol by remember { mutableStateOf(origin?.protocol ?: ApnProtocol.Ip) } - var roamingProtocol by remember { mutableStateOf(origin?.roamingProtocol ?: ApnProtocol.Ip) } - var persistent by remember { mutableStateOf(origin?.persistent == true) } - var alwaysOn by remember { mutableStateOf(origin?.alwaysOn == true) } - var errorMessage: String? by remember { mutableStateOf(null) } + var enabled by rememberSaveable { mutableStateOf(true) } + var entryName by rememberSaveable { mutableStateOf(origin?.name ?: "") } + var apnName by rememberSaveable { mutableStateOf(origin?.apn ?: "") } + var apnType by rememberSaveable { mutableIntStateOf(origin?.apnType ?: 0) } + var profileId by rememberSaveable { mutableStateOf(origin?.profileId?.toString() ?: "") } + var carrierId by rememberSaveable { mutableStateOf(origin?.carrierId?.toString() ?: "") } + var authType by rememberSaveable { mutableStateOf(ApnAuthType.None) } + var user by rememberSaveable { mutableStateOf(origin?.username ?: "") } + var password by rememberSaveable { mutableStateOf(origin?.password ?: "") } + var proxy by rememberSaveable { mutableStateOf(origin?.proxy ?: "") } + var port by rememberSaveable { mutableStateOf(origin?.port?.toString() ?: "") } + var mmsProxy by rememberSaveable { mutableStateOf(origin?.mmsProxy ?: "") } + var mmsPort by rememberSaveable { mutableStateOf(origin?.mmsPort?.toString() ?: "") } + var mmsc by rememberSaveable { mutableStateOf(origin?.mmsc ?: "") } + var mtuV4 by rememberSaveable { mutableStateOf(origin?.mtuV4?.toString() ?: "") } + var mtuV6 by rememberSaveable { mutableStateOf(origin?.mtuV6?.toString() ?: "") } + var mvnoType by rememberSaveable { mutableStateOf(origin?.mvno ?: ApnMvnoType.SPN) } + var networkType by rememberSaveable { mutableIntStateOf(origin?.networkType ?: 0) } + var operatorNumeric by rememberSaveable { mutableStateOf(origin?.operatorNumeric ?: "") } + var protocol by rememberSaveable { mutableStateOf(origin?.protocol ?: ApnProtocol.Ip) } + var roamingProtocol by rememberSaveable { mutableStateOf(origin?.roamingProtocol ?: ApnProtocol.Ip) } + var persistent by rememberSaveable { mutableStateOf(origin?.persistent == true) } + var alwaysOn by rememberSaveable { mutableStateOf(origin?.alwaysOn == true) } + var errorMessage: String? by rememberSaveable { mutableStateOf(null) } MySmallTitleScaffold(R.string.apn_setting, onNavigateUp) { SwitchItem(R.string.enabled, state = enabled, onCheckedChange = { enabled = it }, padding = false) OutlinedTextField( @@ -1950,7 +1968,7 @@ fun AddApnSettingScreen( OutlinedTextField( authType.text, {}, Modifier.fillMaxWidth(), label = { Text("Authentication type") }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == ApnMenu.AuthType) } + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == ApnMenu.AuthType) } ) ExposedDropdownMenu(menu == ApnMenu.AuthType, { menu = ApnMenu.None }) { ApnAuthType.entries.forEach { @@ -1970,7 +1988,7 @@ fun AddApnSettingScreen( OutlinedTextField( protocol.text, {}, Modifier.fillMaxWidth(), label = { Text("APN protocol") }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == ApnMenu.Protocol) } + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(menu == ApnMenu.Protocol) } ) ExposedDropdownMenu(menu == ApnMenu.Protocol, { menu = ApnMenu.None }) { ApnProtocol.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { @@ -1991,7 +2009,9 @@ fun AddApnSettingScreen( OutlinedTextField( roamingProtocol.text, {}, Modifier.fillMaxWidth(), label = { Text("APN roaming protocol") }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == ApnMenu.RoamingProtocol) } + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == ApnMenu.RoamingProtocol) + } ) ExposedDropdownMenu(menu == ApnMenu.RoamingProtocol, { menu = ApnMenu.None }) { ApnProtocol.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { @@ -2050,7 +2070,9 @@ fun AddApnSettingScreen( mvnoType.text, {}, Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable), readOnly = true, label = { Text("MVNO type") }, - trailingIcon = { ExpandExposedTextFieldIcon(menu == ApnMenu.RoamingProtocol) } + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(menu == ApnMenu.RoamingProtocol) + } ) ExposedDropdownMenu(menu == ApnMenu.MvnoType, { menu = ApnMenu.None }) { ApnMvnoType.entries.forEach { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt index 13bdc79..275362c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -43,6 +43,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -80,7 +81,7 @@ import kotlinx.serialization.Serializable fun PasswordScreen(vm: MyViewModel,onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { val context = LocalContext.current val privilege by Privilege.status.collectAsStateWithLifecycle() - var dialog by remember { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } MyScaffold(R.string.password_and_keyguard, onNavigateUp, 0.dp) { FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { onNavigate(PasswordInfo) } if (SP.displayDangerousFeatures) { @@ -210,7 +211,7 @@ fun PasswordInfoScreen( onNavigateUp: () -> Unit ) { val privilege by Privilege.status.collectAsStateWithLifecycle() - var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity + var dialog by rememberSaveable { mutableIntStateOf(0) } // 0:none, 1:password complexity MyScaffold(R.string.password_info, onNavigateUp, 0.dp) { if (VERSION.SDK_INT >= 29) { InfoItem(R.string.current_password_complexity, getComplexity().text, true) { dialog = 1 } @@ -242,8 +243,8 @@ fun ResetPasswordTokenScreen( clearToken: () -> Boolean, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var token by remember { mutableStateOf("") } - var state by remember { mutableStateOf(getState()) } + var token by rememberSaveable { mutableStateOf("") } + var state by rememberSaveable { mutableStateOf(getState()) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { context.popToast(R.string.token_activated) @@ -305,10 +306,10 @@ fun ResetPasswordTokenScreen( @Composable fun ResetPasswordScreen(resetPassword: (String, String, Int) -> Boolean, onNavigateUp: () -> Unit) { val context = LocalContext.current - var password by remember { mutableStateOf("") } - var token by remember { mutableStateOf("") } - var flags by remember { mutableIntStateOf(0) } - var confirmPassword by remember { mutableStateOf("") } + var password by rememberSaveable { mutableStateOf("") } + var token by rememberSaveable { mutableStateOf("") } + var flags by rememberSaveable { mutableIntStateOf(0) } + var confirmPassword by rememberSaveable { mutableStateOf("") } MyScaffold(R.string.reset_password, onNavigateUp) { if (VERSION.SDK_INT >= 26) { OutlinedTextField( @@ -366,7 +367,7 @@ fun RequiredPasswordComplexityScreen( onNavigateUp: () -> Unit ) { val context = LocalContext.current - var complexity by remember { mutableStateOf(PasswordComplexity.None) } + var complexity by rememberSaveable { mutableStateOf(PasswordComplexity.None) } LaunchedEffect(Unit) { complexity = getComplexity() } MyScaffold(R.string.required_password_complexity, onNavigateUp, 0.dp) { PasswordComplexity.entries.forEach { @@ -415,8 +416,8 @@ fun KeyguardDisabledFeaturesScreen( onNavigateUp: () -> Unit ) { val context = LocalContext.current - var mode by remember { mutableStateOf(KeyguardDisableMode.None) } - var flags by remember { mutableIntStateOf(0) } + var mode by rememberSaveable { mutableStateOf(KeyguardDisableMode.None) } + var flags by rememberSaveable { mutableIntStateOf(0) } LaunchedEffect(Unit) { val config = getConfig() mode = config.mode @@ -462,7 +463,7 @@ fun RequiredPasswordQualityScreen(onNavigateUp: () -> Unit) { PASSWORD_QUALITY_BIOMETRIC_WEAK to R.string.password_quality_biometrics_weak, PASSWORD_QUALITY_NUMERIC_COMPLEX to R.string.password_quality_numeric_complex ) - var selectedItem by remember { mutableIntStateOf(PASSWORD_QUALITY_UNSPECIFIED) } + var selectedItem by rememberSaveable { mutableIntStateOf(PASSWORD_QUALITY_UNSPECIFIED) } LaunchedEffect(Unit) { selectedItem = Privilege.DPM.getPasswordQuality(Privilege.DAR) } MyScaffold(R.string.required_password_quality, onNavigateUp) { passwordQuality.forEach { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index 9982458..55005d1 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -16,11 +16,9 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.items @@ -90,6 +88,7 @@ import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.R import com.bintianqi.owndroid.Settings +import com.bintianqi.owndroid.adaptiveInsets import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CircularProgressDialog import com.bintianqi.owndroid.ui.InfoItem @@ -116,9 +115,9 @@ fun WorkModesScreen( ) { val privilege by Privilege.status.collectAsStateWithLifecycle() /** 0: none, 1: device owner, 2: circular progress indicator, 3: result, 4: deactivate, 5: command */ - var dialog by remember { mutableIntStateOf(0) } - var operationSucceed by remember { mutableStateOf(false) } - var resultText by remember { mutableStateOf("") } + var dialog by rememberSaveable { mutableIntStateOf(0) } + var operationSucceed by rememberSaveable { mutableStateOf(false) } + var resultText by rememberSaveable { mutableStateOf("") } LaunchedEffect(privilege) { if (!params.canNavigateUp && privilege.device) { delay(1000) @@ -182,7 +181,7 @@ fun WorkModesScreen( } ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> fun handleResult(succeeded: Boolean, output: String?) { operationSucceed = succeeded @@ -367,7 +366,7 @@ fun DhizukuServerSettingsScreen( getDhizukuClients: () -> Unit, updateDhizukuClient: (DhizukuClientInfo) -> Unit, getServerEnabled: () -> Boolean, setServerEnabled: (Boolean) -> Unit, onNavigateUp: () -> Unit ) { - var enabled by remember { mutableStateOf(getServerEnabled()) } + var enabled by rememberSaveable { mutableStateOf(getServerEnabled()) } val clients by dhizukuClients.collectAsStateWithLifecycle() LaunchedEffect(Unit) { getDhizukuClients() } MyLazyScaffold(R.string.dhizuku_server, onNavigateUp) { @@ -455,7 +454,7 @@ fun LockScreenInfoScreen( ) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var infoText by remember { mutableStateOf(getText()) } + var infoText by rememberSaveable { mutableStateOf(getText()) } MyScaffold(R.string.lock_screen_info, onNavigateUp) { OutlinedTextField( value = infoText, @@ -566,7 +565,7 @@ fun AddDelegatedAdminScreen( setDelegatedAdmin: (String, List) -> Unit, onNavigateUp: () -> Unit ) { val updateMode = data.pkg.isNotEmpty() - var input by remember { mutableStateOf(data.pkg) } + var input by rememberSaveable { mutableStateOf(data.pkg) } val scopes = rememberSaveable { mutableStateListOf(*data.scopes.toTypedArray()) } LaunchedEffect(Unit) { input = chosenPackage.receive() @@ -625,7 +624,7 @@ fun AddDelegatedAdminScreen( @Composable fun DeviceInfoScreen(vm: MyViewModel, onNavigateUp: () -> Unit) { val privilege by Privilege.status.collectAsStateWithLifecycle() - var dialog by remember { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } MyScaffold(R.string.device_info, onNavigateUp, 0.dp) { if (VERSION.SDK_INT >= 34 && (privilege.device || privilege.org)) { InfoItem(R.string.financed_device, vm.getDeviceFinanced().yesOrNo) @@ -666,8 +665,8 @@ fun SupportMessageScreen( setLongMessage: (String?) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var shortMsg by remember { mutableStateOf("") } - var longMsg by remember { mutableStateOf("") } + var shortMsg by rememberSaveable { mutableStateOf("") } + var longMsg by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { shortMsg = getShortMessage() longMsg = getLongMessage() @@ -750,8 +749,8 @@ fun TransferOwnershipScreen( transferOwnership: (ComponentName) -> Unit, onNavigateUp: () -> Unit, onTransferred: () -> Unit ) { val privilege by Privilege.status.collectAsStateWithLifecycle() - var selectedIndex by remember { mutableIntStateOf(-1) } - var dialog by remember { mutableStateOf(false) } + var selectedIndex by rememberSaveable { mutableIntStateOf(-1) } + var dialog by rememberSaveable { mutableStateOf(false) } val receivers by deviceAdmins.collectAsStateWithLifecycle() LaunchedEffect(Unit) { getDeviceAdmins() } MyLazyScaffold(R.string.transfer_ownership, onNavigateUp) { 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 45b3555..db4dcac 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -31,17 +31,13 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -110,7 +106,9 @@ import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.R import com.bintianqi.owndroid.SP +import com.bintianqi.owndroid.clickableTextField import com.bintianqi.owndroid.formatDate +import com.bintianqi.owndroid.adaptiveInsets import com.bintianqi.owndroid.popToast import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem @@ -146,7 +144,7 @@ fun SystemManagerScreen( val context = LocalContext.current val privilege by Privilege.status.collectAsStateWithLifecycle() /** 1: reboot, 2: bug report, 3: org name, 4: org id, 5: enrollment specific id*/ - var dialog by remember { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } MyScaffold(R.string.system, onNavigateUp, 0.dp) { FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) } FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) } @@ -242,7 +240,7 @@ fun SystemManagerScreen( modifier = Modifier.fillMaxWidth() ) if(dialog in 3..5) { - var input by remember { mutableStateOf("") } + var input by rememberSaveable { mutableStateOf("") } AlertDialog( text = { val focusMgr = LocalFocusManager.current @@ -323,7 +321,7 @@ data class SystemOptionsStatus( @Composable fun SystemOptionsScreen(vm: MyViewModel, onNavigateUp: () -> Unit) { val privilege by Privilege.status.collectAsStateWithLifecycle() - var dialog by remember { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } val status by vm.systemOptionsStatus.collectAsStateWithLifecycle() LaunchedEffect(Unit) { vm.getSystemOptionsStatus() } MyScaffold(R.string.options, onNavigateUp, 0.dp) { @@ -436,7 +434,7 @@ fun KeyguardScreen( } if(VERSION.SDK_INT >= 23) Text(text = stringResource(R.string.lock_now), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 2.dp)) - var evictKey by remember { mutableStateOf(false) } + var evictKey by rememberSaveable { mutableStateOf(false) } Button( onClick = { lock(evictKey) }, modifier = Modifier.fillMaxWidth() @@ -475,7 +473,7 @@ fun HardwareMonitorScreen( onNavigateUp: () -> Unit ) { val properties by hardwareProperties.collectAsStateWithLifecycle() - var refreshInterval by remember { mutableFloatStateOf(1F) } + var refreshInterval by rememberSaveable { mutableFloatStateOf(1F) } val refreshIntervalMs = (refreshInterval * 1000).roundToLong() LaunchedEffect(Unit) { getHardwareProperties() @@ -540,18 +538,14 @@ fun HardwareMonitorScreen( fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Unit) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var tab by remember { mutableIntStateOf(0) } + var tab by rememberSaveable { mutableIntStateOf(0) } val pagerState = rememberPagerState { 2 } tab = pagerState.currentPage val coroutine = rememberCoroutineScope() - var picker by remember { mutableIntStateOf(0) } //0:None, 1:DatePicker, 2:TimePicker - var useCurrentTz by remember { mutableStateOf(true) } + var picker by rememberSaveable { mutableIntStateOf(0) } //0:None, 1:DatePicker, 2:TimePicker + var useCurrentTz by rememberSaveable { mutableStateOf(true) } val datePickerState = rememberDatePickerState() val timePickerState = rememberTimePickerState(is24Hour = true) - val dateInteractionSource = remember { MutableInteractionSource() } - val timeInteractionSource = remember { MutableInteractionSource() } - if(dateInteractionSource.collectIsPressedAsState().value) picker = 1 - if(timeInteractionSource.collectIsPressedAsState().value) picker = 2 Scaffold( topBar = { TopAppBar( @@ -560,7 +554,7 @@ fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Un colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer) ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( Modifier @@ -592,17 +586,16 @@ fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Un value = datePickerState.selectedDateMillis?.let { formatDate(it) } ?: "", onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.date)) }, - interactionSource = dateInteractionSource, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().clickableTextField { picker = 1 } ) OutlinedTextField( value = timePickerState.hour.toString().padStart(2, '0') + ":" + timePickerState.minute.toString().padStart(2, '0'), onValueChange = {}, readOnly = true, label = { Text(stringResource(R.string.time)) }, - interactionSource = timeInteractionSource, modifier = Modifier .fillMaxWidth() + .clickableTextField { picker = 2 } .padding(vertical = 4.dp) ) CheckBoxItem(R.string.use_current_timezone, useCurrentTz) { @@ -620,13 +613,12 @@ fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Un Text(stringResource(R.string.apply)) } } else { - var inputTime by remember { mutableStateOf("") } + var inputTime by rememberSaveable { mutableStateOf("") } OutlinedTextField( value = inputTime, label = { Text(stringResource(R.string.time_unit_ms)) }, onValueChange = { inputTime = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), modifier = Modifier.fillMaxWidth() ) Button( @@ -673,8 +665,8 @@ fun ChangeTimeScreen(setTime: (Long, Boolean) -> Boolean, onNavigateUp: () -> Un fun ChangeTimeZoneScreen(setTimeZone: (String) -> Boolean, onNavigateUp: () -> Unit) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var inputTimezone by remember { mutableStateOf("") } - var dialog by remember { mutableStateOf(false) } + var inputTimezone by rememberSaveable { mutableStateOf("") } + var dialog by rememberSaveable { mutableStateOf(false) } val availableIds = TimeZone.getAvailableIDs() val validInput = inputTimezone in availableIds MyScaffold(R.string.change_timezone, onNavigateUp) { @@ -741,7 +733,7 @@ fun AutoTimePolicyScreen( getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit ) = MyScaffold(R.string.auto_time_policy, onNavigateUp, 0.dp) { val context = LocalContext.current - var policy by remember { mutableIntStateOf(getPolicy()) } + var policy by rememberSaveable { mutableIntStateOf(getPolicy()) } listOf( DevicePolicyManager.AUTO_TIME_ENABLED to R.string.enable, DevicePolicyManager.AUTO_TIME_DISABLED to R.string.disabled, @@ -772,7 +764,7 @@ fun AutoTimeZonePolicyScreen( getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit ) = MyScaffold(R.string.auto_timezone_policy, onNavigateUp, 0.dp) { val context = LocalContext.current - var policy by remember { mutableIntStateOf(getPolicy()) } + var policy by rememberSaveable { mutableIntStateOf(getPolicy()) } listOf( DevicePolicyManager.AUTO_TIME_ZONE_ENABLED to R.string.enable, DevicePolicyManager.AUTO_TIME_ZONE_DISABLED to R.string.disabled, @@ -980,7 +972,7 @@ fun ContentProtectionPolicyScreen( getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var policy by remember { mutableIntStateOf(getPolicy()) } + var policy by rememberSaveable { mutableIntStateOf(getPolicy()) } MyScaffold(R.string.content_protection_policy, onNavigateUp, 0.dp) { mapOf( DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY to R.string.not_controlled_by_policy, @@ -1012,7 +1004,7 @@ fun PermissionPolicyScreen( getPolicy: () -> Int, setPolicy: (Int) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var selectedPolicy by remember { mutableIntStateOf(getPolicy()) } + var selectedPolicy by rememberSaveable { mutableIntStateOf(getPolicy()) } MyScaffold(R.string.permission_policy, onNavigateUp, 0.dp) { FullWidthRadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) { selectedPolicy = PERMISSION_POLICY_PROMPT @@ -1045,7 +1037,7 @@ fun PermissionPolicyScreen( fun MtePolicyScreen( getPolicy: () -> Int, setPolicy: (Int) -> Boolean, onNavigateUp: () -> Unit ) { - var policy by remember { mutableIntStateOf(getPolicy()) } + var policy by rememberSaveable { mutableIntStateOf(getPolicy()) } MyScaffold(R.string.mte_policy, onNavigateUp, 0.dp) { FullWidthRadioButtonItem(R.string.decide_by_user, policy == MTE_NOT_CONTROLLED_BY_POLICY) { policy = MTE_NOT_CONTROLLED_BY_POLICY @@ -1075,7 +1067,7 @@ fun NearbyStreamingPolicyScreen( setNotificationPolicy: (Int) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var appPolicy by remember { mutableIntStateOf(getAppPolicy()) } + var appPolicy by rememberSaveable { mutableIntStateOf(getAppPolicy()) } MySmallTitleScaffold(R.string.nearby_streaming_policy, onNavigateUp, 0.dp) { Text( stringResource(R.string.nearby_app_streaming), @@ -1104,7 +1096,7 @@ fun NearbyStreamingPolicyScreen( } Notes(R.string.info_nearby_app_streaming_policy, HorizontalPadding) Spacer(Modifier.height(20.dp)) - var notificationPolicy by remember { mutableIntStateOf(getNotificationPolicy()) } + var notificationPolicy by rememberSaveable { mutableIntStateOf(getNotificationPolicy()) } Text( stringResource(R.string.nearby_notification_streaming), Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp), style = typography.titleLarge @@ -1153,7 +1145,7 @@ fun LockTaskModeScreen( ) { val coroutine = rememberCoroutineScope() val pagerState = rememberPagerState { 3 } - var tabIndex by remember { mutableIntStateOf(0) } + var tabIndex by rememberSaveable { mutableIntStateOf(0) } tabIndex = pagerState.targetPage LaunchedEffect(Unit) { getLockTaskPackages() @@ -1166,7 +1158,7 @@ fun LockTaskModeScreen( colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer) ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier @@ -1301,8 +1293,8 @@ private fun LockTaskFeatures( getLockTaskFeatures: () -> Int, setLockTaskFeature: (Int) -> String? ) { val context = LocalContext.current - var flags by remember { mutableIntStateOf(getLockTaskFeatures()) } - var errorMessage by remember { mutableStateOf(null) } + var flags by rememberSaveable { mutableIntStateOf(getLockTaskFeatures()) } + var errorMessage by rememberSaveable { mutableStateOf(null) } Column( Modifier .fillMaxWidth() @@ -1366,9 +1358,9 @@ fun CaCertScreen( ) { val context = LocalContext.current /** 0:none, 1:install, 2:info, 3:uninstall all */ - var dialog by remember { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } val caCerts by caCertificates.collectAsStateWithLifecycle() - var selectedCaCert by remember { mutableStateOf(null) } + var selectedCaCert by rememberSaveable { mutableStateOf(null) } val getCertLauncher = rememberLauncherForActivityResult( ActivityResultContracts.OpenDocument()) { uri -> if(uri != null) { @@ -1400,7 +1392,8 @@ fun CaCertScreen( }) { Icon(Icons.Default.Add, stringResource(R.string.install)) } - } + }, + contentWindowInsets = adaptiveInsets() ) { paddingValues -> LazyColumn( Modifier @@ -1534,14 +1527,10 @@ fun SecurityLoggingScreen( exportPRLogs: (Uri, () -> Unit) -> Unit, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var enabled by remember { mutableStateOf(false) } - var logsCount by remember { mutableIntStateOf(0) } - var exporting by remember { mutableStateOf(false) } - var dialog by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - enabled = getEnabled() - logsCount = getCount() - } + var enabled by rememberSaveable { mutableStateOf(getEnabled()) } + var logsCount by rememberSaveable { mutableIntStateOf(getCount()) } + var exporting by rememberSaveable { mutableStateOf(false) } + var dialog by rememberSaveable { mutableStateOf(false) } val exportLauncher = rememberLauncherForActivityResult( ActivityResultContracts.CreateDocument("application/json") ) { @@ -1686,24 +1675,16 @@ data class FrpPolicyInfo( @RequiresApi(30) @Composable fun FrpPolicyScreen( - getFrpPolicy: () -> FrpPolicyInfo, setFrpPolicy: (FrpPolicyInfo) -> Unit, + frpPolicy: FrpPolicyInfo, setFrpPolicy: (FrpPolicyInfo) -> Unit, onNavigateUp: () -> Unit ) { + val context = LocalContext.current val focusMgr = LocalFocusManager.current - var usePolicy by remember { mutableStateOf(false) } - var enabled by remember { mutableStateOf(false) } - var supported by remember { mutableStateOf(false) } - val accountList = remember { mutableStateListOf() } - var inputAccount by remember { mutableStateOf("") } - LaunchedEffect(Unit) { - val info = getFrpPolicy() - supported = info.supported - if (info.supported) { - usePolicy = info.usePolicy - enabled = info.enabled - accountList.addAll(info.accounts) - } - } + var usePolicy by rememberSaveable { mutableStateOf(frpPolicy.usePolicy) } + var enabled by rememberSaveable { mutableStateOf(frpPolicy.enabled) } + var supported by rememberSaveable { mutableStateOf(frpPolicy.supported) } + val accountList = rememberSaveable { mutableStateListOf(*frpPolicy.accounts.toTypedArray()) } + var inputAccount by rememberSaveable { mutableStateOf("") } MyScaffold(R.string.frp_policy, onNavigateUp, 0.dp) { if (!supported) { Column( @@ -1751,6 +1732,7 @@ fun FrpPolicyScreen( onClick = { focusMgr.clearFocus() setFrpPolicy(FrpPolicyInfo(true, usePolicy, enabled, accountList)) + context.showOperationResultToast(true) }, modifier = Modifier .fillMaxWidth() @@ -1774,9 +1756,9 @@ fun WipeDataScreen( val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager val privilege by Privilege.status.collectAsStateWithLifecycle() val focusMgr = LocalFocusManager.current - var flag by remember { mutableIntStateOf(WIPE_SILENTLY) } - var dialog by remember { mutableIntStateOf(0) } // 0: none, 1: wipe data, 2: wipe device - var reason by remember { mutableStateOf("") } + var flag by rememberSaveable { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } // 0: none, 1: wipe data, 2: wipe device + var reason by rememberSaveable { mutableStateOf("") } MyScaffold(R.string.wipe_data, onNavigateUp, 0.dp) { FullWidthCheckBoxItem(R.string.wipe_external_storage, flag and WIPE_EXTERNAL_STORAGE != 0) { flag = flag xor WIPE_EXTERNAL_STORAGE @@ -1978,8 +1960,8 @@ fun InstallSystemUpdateScreen( installSystemUpdate: (Uri, (String) -> Unit) -> Unit, onNavigateUp: () -> Unit ) { var uri by remember { mutableStateOf(null) } - var installing by remember { mutableStateOf(false) } - var errorMessage by remember { mutableStateOf(null) } + var installing by rememberSaveable { mutableStateOf(false) } + var errorMessage by rememberSaveable { mutableStateOf(null) } val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it } MyScaffold(R.string.install_system_update, onNavigateUp) { Button( 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 6662ba9..497e23c 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt @@ -7,11 +7,9 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -41,7 +39,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -53,11 +51,13 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.bintianqi.owndroid.BottomPadding import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.R import com.bintianqi.owndroid.UserRestrictionCategory import com.bintianqi.owndroid.UserRestrictionsRepository +import com.bintianqi.owndroid.adaptiveInsets import com.bintianqi.owndroid.popToast import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.FunctionItem @@ -99,7 +99,7 @@ fun UserRestrictionScreen( scrollBehavior = sb ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier @@ -183,7 +183,7 @@ fun UserRestrictionOptionsScreen( } } item { - Spacer(Modifier.padding(vertical = 30.dp)) + Spacer(Modifier.height(BottomPadding)) } } } @@ -207,7 +207,7 @@ fun UserRestrictionEditorScreen( navigationIcon = { NavIcon(onNavigateUp) } ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> LazyColumn(Modifier.fillMaxSize().padding(paddingValues)) { items(list, { it }) { @@ -224,7 +224,7 @@ fun UserRestrictionEditorScreen( } } item { - var input by remember { mutableStateOf("") } + var input by rememberSaveable { mutableStateOf("") } fun add() { if (!setRestriction(input, false)) context.showOperationResultToast(false) } 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 30fd057..5c53b99 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt @@ -42,6 +42,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -81,7 +82,7 @@ fun UsersScreen(vm: MyViewModel, onNavigateUp: () -> Unit, onNavigate: (Any) -> val context = LocalContext.current val privilege by Privilege.status.collectAsStateWithLifecycle() /** 1: secondary users, 2: logout*/ - var dialog by remember { mutableIntStateOf(0) } + var dialog by rememberSaveable { mutableIntStateOf(0) } MyScaffold(R.string.users, onNavigateUp, 0.dp) { if(VERSION.SDK_INT >= 28 && privilege.profile && privilege.affiliated) { FunctionItem(R.string.logout, icon = R.drawable.logout_fill0) { dialog = 2 } @@ -194,7 +195,7 @@ data class UserInformation( @Composable fun UserInfoScreen(getInfo: () -> UserInformation, onNavigateUp: () -> Unit) { var info by remember { mutableStateOf(UserInformation()) } - var infoDialog by remember { mutableIntStateOf(0) } + var infoDialog by rememberSaveable { mutableIntStateOf(0) } LaunchedEffect(Unit) { info = getInfo() } @@ -234,10 +235,10 @@ fun UserOperationScreen( stopUser: (Int, Boolean) -> Int, deleteUser: (Int, Boolean) -> Boolean, onNavigateUp: () -> Unit ) { val context = LocalContext.current - var input by remember { mutableStateOf("") } + var input by rememberSaveable { mutableStateOf("") } val focusMgr = LocalFocusManager.current - var useUserId by remember { mutableStateOf(false) } - var dialog by remember { mutableStateOf(false) } + var useUserId by rememberSaveable { mutableStateOf(false) } + var dialog by rememberSaveable { mutableStateOf(false) } val legalInput = input.toIntOrNull() != null MyScaffold(R.string.user_operation, onNavigateUp) { if(VERSION.SDK_INT >= 24) SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { @@ -335,9 +336,9 @@ fun CreateUserScreen( ) { var result by remember { mutableStateOf(null) } val focusMgr = LocalFocusManager.current - var userName by remember { mutableStateOf("") } - var creating by remember { mutableStateOf(false) } - var flags by remember { mutableIntStateOf(0) } + var userName by rememberSaveable { mutableStateOf("") } + var creating by rememberSaveable { mutableStateOf(false) } + var flags by rememberSaveable { mutableIntStateOf(0) } MyScaffold(R.string.create_user, onNavigateUp, 0.dp) { OutlinedTextField( userName, { userName= it }, Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding), @@ -401,7 +402,7 @@ fun AffiliationIdScreen( onNavigateUp: () -> Unit ) { val focusMgr = LocalFocusManager.current - var input by remember { mutableStateOf("") } + var input by rememberSaveable { mutableStateOf("") } val list by affiliationIds.collectAsStateWithLifecycle() LaunchedEffect(Unit) { getIds() } MyScaffold(R.string.affiliation_id, onNavigateUp) { @@ -440,7 +441,7 @@ fun AffiliationIdScreen( fun ChangeUsernameScreen(setName: (String) -> Unit, onNavigateUp: () -> Unit) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var inputUsername by remember { mutableStateOf("") } + var inputUsername by rememberSaveable { mutableStateOf("") } MyScaffold(R.string.change_username, onNavigateUp) { OutlinedTextField( value = inputUsername, @@ -473,8 +474,8 @@ fun UserSessionMessageScreen( ) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var start by remember { mutableStateOf("") } - var end by remember { mutableStateOf("") } + var start by rememberSaveable { mutableStateOf("") } + var end by rememberSaveable { mutableStateOf("") } LaunchedEffect(Unit) { val messages = getMessages() start = messages.first diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt index 642920d..180deed 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/WorkProfile.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MenuAnchorType @@ -55,7 +56,6 @@ import com.bintianqi.owndroid.R import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.CircularProgressDialog -import com.bintianqi.owndroid.ui.ExpandExposedTextFieldIcon import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.MyScaffold @@ -301,7 +301,7 @@ fun CrossProfileIntentFilterScreen( stringResource(direction.text), {}, Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth(), label = { Text(stringResource(R.string.direction)) }, readOnly = true, - trailingIcon = { ExpandExposedTextFieldIcon(dropdown) } + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(dropdown) } ) ExposedDropdownMenu(dropdown, { dropdown = false }) { IntentFilterDirection.entries.forEach { diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/AppInstaller.kt b/app/src/main/java/com/bintianqi/owndroid/ui/AppInstaller.kt index 6b68957..3d52595 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/AppInstaller.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/AppInstaller.kt @@ -45,7 +45,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -102,7 +101,7 @@ fun AppInstaller( ) } ) { paddingValues -> - var tab by remember { mutableIntStateOf(0) } + var tab by rememberSaveable { mutableIntStateOf(0) } val pagerState = rememberPagerState { 2 } val scrollState = rememberScrollState() tab = pagerState.targetPage 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 8c6507c..d0cb905 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -2,7 +2,6 @@ package com.bintianqi.owndroid.ui import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -10,10 +9,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -23,7 +20,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape 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.AlertDialog import androidx.compose.material3.Card @@ -51,7 +47,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -60,6 +55,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.adaptiveInsets import com.bintianqi.owndroid.zhCN @Composable @@ -313,7 +309,7 @@ fun MyScaffold( scrollBehavior = sb ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier @@ -345,7 +341,7 @@ fun MyLazyScaffold( scrollBehavior = sb ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> LazyColumn(Modifier.fillMaxSize().padding(paddingValues), content = content) } @@ -367,7 +363,7 @@ fun MySmallTitleScaffold( colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer) ) }, - contentWindowInsets = WindowInsets.ime + contentWindowInsets = adaptiveInsets() ) { paddingValues -> Column( modifier = Modifier @@ -382,15 +378,6 @@ fun MySmallTitleScaffold( } } -@Composable -fun ExpandExposedTextFieldIcon(active: Boolean) { - val degrees by animateFloatAsState(if(active) 180F else 0F) - Icon( - imageVector = Icons.Default.ArrowDropDown, contentDescription = null, - modifier = Modifier.rotate(degrees) - ) -} - @Composable fun ErrorDialog(message: String?, onDismiss: () -> Unit) { if(!message.isNullOrEmpty()) AlertDialog(