This commit is contained in:
BinTianqi
2025-10-16 17:45:01 +08:00
parent 9c796690e3
commit b5218d7ee5
20 changed files with 377 additions and 347 deletions

View File

@@ -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()

View File

@@ -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 = {

View File

@@ -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<FrpPolicy> {
FrpPolicyScreen(vm::getFrpPolicy, vm::setFrpPolicy, ::navigateUp)
FrpPolicyScreen(vm.getFrpPolicy(), vm::setFrpPolicy, ::navigateUp)
}
composable<WipeData> { WipeDataScreen(vm::wipeData, ::navigateUp) }
@@ -641,13 +640,14 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
AppearanceScreen(::navigateUp, vm.theme, vm::changeTheme)
}
composable<AppLockSettings> {
AppLockSettingsScreen(vm::getAppLockConfig, vm::setAppLockConfig, ::navigateUp)
AppLockSettingsScreen(vm.getAppLockConfig(), vm::setAppLockConfig, ::navigateUp)
}
composable<ApiSettings> {
ApiSettings(vm::getApiEnabled, vm::setApiKey, ::navigateUp)
}
composable<Notifications> {
NotificationsScreen(vm::getEnabledNotifications, vm::setNotificationEnabled, ::navigateUp)
NotificationsScreen(vm.enabledNotifications, vm::getEnabledNotifications,
vm::setNotificationEnabled, ::navigateUp)
}
composable<About> { 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))
}
}
}

View File

@@ -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<NotificationType> {
val enabledNotifications = MutableStateFlow(emptyList<Int>())
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<String>(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))
}
})
}

View File

@@ -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) {

View File

@@ -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)) }
}
}
}

View File

@@ -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<NotificationType>, setNotification: (NotificationType, Boolean) -> Unit,
onNavigateUp: () -> Unit
enabledNotifications: StateFlow<List<Int>>, 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) })
}
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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<PermissionItem?>(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<String>, 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<String>, 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<String?>(null) }
var uninstalling by rememberSaveable { mutableStateOf(false) }
var errorMessage by rememberSaveable { mutableStateOf<String?>(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<String>, 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<String>, 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()

View File

@@ -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)
)
}

View File

@@ -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<Boolean?>(false) }
var security by remember { mutableStateOf<WifiSecurity?>(WifiSecurity.Open) }
var password by remember { mutableStateOf("") }
var macRandomization by remember { mutableStateOf<WifiMacRandomization?>(WifiMacRandomization.None) }
var ipMode by remember { mutableStateOf<IpMode?>(IpMode.Dhcp) }
var ipAddress by remember { mutableStateOf("") }
var gatewayAddress by remember { mutableStateOf("") }
var dnsServers by remember { mutableStateOf("") }
var proxyMode by remember { mutableStateOf<ProxyMode?>(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<Boolean?>(false) }
var security by rememberSaveable { mutableStateOf<WifiSecurity?>(WifiSecurity.Open) }
var password by rememberSaveable { mutableStateOf("") }
var macRandomization by rememberSaveable { mutableStateOf<WifiMacRandomization?>(WifiMacRandomization.None) }
var ipMode by rememberSaveable { mutableStateOf<IpMode?>(IpMode.Dhcp) }
var ipAddress by rememberSaveable { mutableStateOf("") }
var gatewayAddress by rememberSaveable { mutableStateOf("") }
var dnsServers by rememberSaveable { mutableStateOf("") }
var proxyMode by rememberSaveable { mutableStateOf<ProxyMode?>(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<String>() }
var type by rememberSaveable { mutableStateOf(SsidPolicyType.None) }
val list = rememberSaveable { mutableStateListOf<String>() }
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<String?>(null) }
var errorMessage by rememberSaveable { mutableStateOf<String?>(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<NetworkStatsData>, 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?>(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<List<PreferentialNetworkServiceInfo>>, 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<List<ApnConfig>>, 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 {

View File

@@ -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 {

View File

@@ -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<String>) -> 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) {

View File

@@ -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<String?>(null) }
var flags by rememberSaveable { mutableIntStateOf(getLockTaskFeatures()) }
var errorMessage by rememberSaveable { mutableStateOf<String?>(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<CaCertInfo?>(null) }
var selectedCaCert by rememberSaveable { mutableStateOf<CaCertInfo?>(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<String>() }
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<Uri?>(null) }
var installing by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf<String?>(null) }
var installing by rememberSaveable { mutableStateOf(false) }
var errorMessage by rememberSaveable { mutableStateOf<String?>(null) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it }
MyScaffold(R.string.install_system_update, onNavigateUp) {
Button(

View File

@@ -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)
}

View File

@@ -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<CreateUserResult?>(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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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(