Add delegated admin in a new screen

Some UI improvements
Info of password complexity
This commit is contained in:
BinTianqi
2025-02-16 16:13:44 +08:00
parent fd0073f907
commit 3d6e12581b
11 changed files with 299 additions and 260 deletions

View File

@@ -62,6 +62,8 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import com.bintianqi.owndroid.dpm.Accounts
import com.bintianqi.owndroid.dpm.AccountsScreen
import com.bintianqi.owndroid.dpm.AddDelegatedAdmin
import com.bintianqi.owndroid.dpm.AddDelegatedAdminScreen
import com.bintianqi.owndroid.dpm.AddNetwork
import com.bintianqi.owndroid.dpm.AddNetworkScreen
import com.bintianqi.owndroid.dpm.AffiliationId
@@ -167,7 +169,7 @@ import com.bintianqi.owndroid.dpm.SystemManager
import com.bintianqi.owndroid.dpm.SystemManagerScreen
import com.bintianqi.owndroid.dpm.SystemOptions
import com.bintianqi.owndroid.dpm.SystemOptionsScreen
import com.bintianqi.owndroid.dpm.SystemUpdatePolicy
import com.bintianqi.owndroid.dpm.SystemUpdatePolicyScreen
import com.bintianqi.owndroid.dpm.TransferOwnership
import com.bintianqi.owndroid.dpm.TransferOwnershipScreen
import com.bintianqi.owndroid.dpm.UserInfo
@@ -264,6 +266,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
}
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
fun navigateUp() { navController.navigateUp() }
fun navigate(destination: Any) { navController.navigate(destination) }
@Suppress("NewApi") NavHost(
navController = navController,
startDestination = Home,
@@ -290,13 +293,14 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<DeviceAdmin> { DeviceAdminScreen(::navigateUp) }
composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) }
composable<DeviceOwner> { DeviceOwnerScreen(::navigateUp) }
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp) }
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) }
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) }
composable<SystemManager> { SystemManagerScreen(::navigateUp) { navController.navigate(it) } }
composable<SystemManager> { SystemManagerScreen(::navigateUp, ::navigate) }
composable<SystemOptions> { SystemOptionsScreen(::navigateUp) }
composable<Keyguard> { KeyguardScreen(::navigateUp) }
composable<HardwareMonitor> { HardwareMonitorScreen(::navigateUp) }
@@ -311,12 +315,12 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<CaCert> { CaCertScreen(::navigateUp) }
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
composable<SetSystemUpdatePolicy> { SystemUpdatePolicy(::navigateUp) }
composable<SetSystemUpdatePolicy> { SystemUpdatePolicyScreen(::navigateUp) }
composable<InstallSystemUpdate> { InstallSystemUpdateScreen(::navigateUp) }
composable<FrpPolicy> { FrpPolicyScreen(::navigateUp) }
composable<WipeData> { WipeDataScreen(::navigateUp) }
composable<Network> { NetworkScreen(::navigateUp) { navController.navigate(it) } }
composable<Network> { NetworkScreen(::navigateUp, ::navigate) }
composable<WiFi> {
WifiScreen(::navigateUp, { navController.navigate(it) }) {
val dest = navController.graph.findNode(AddNetwork)!!.id
@@ -327,7 +331,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navController.navigate(it) } }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp, ::navigate) }
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() }
}
@@ -339,7 +343,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) }
composable<OverrideApn> { OverrideApnScreen(::navigateUp) }
composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navController.navigate(it) } }
composable<WorkProfile> { WorkProfileScreen(::navigateUp, ::navigate) }
composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) }
composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) }
composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) }
@@ -370,7 +374,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
}
}
composable<Users> { UsersScreen(::navigateUp) { navController.navigate(it) } }
composable<Users> { UsersScreen(::navigateUp, ::navigate) }
composable<UserInfo> { UserInfoScreen(::navigateUp) }
composable<UsersOptions> { UsersOptionsScreen(::navigateUp) }
composable<UserOperation> { UserOperationScreen(::navigateUp) }
@@ -379,7 +383,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) }
composable<AffiliationId> { AffiliationIdScreen(::navigateUp) }
composable<Password> { PasswordScreen(::navigateUp) { navController.navigate(it) } }
composable<Password> { PasswordScreen(::navigateUp, ::navigate) }
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) }
composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) }
composable<ResetPassword> { ResetPasswordScreen(::navigateUp) }
@@ -387,7 +391,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) }
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
composable<Settings> { SettingsScreen(::navigateUp) { navController.navigate(it) } }
composable<Settings> { SettingsScreen(::navigateUp, ::navigate) }
composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) }
composable<Appearance> {
val theme by vm.theme.collectAsStateWithLifecycle()

View File

@@ -121,6 +121,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
@@ -483,6 +485,7 @@ fun AddNetworkScreen(data: Bundle, onNavigateUp: () -> Unit) {
@Composable
private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp: () -> Unit) {
val context = LocalContext.current
val fm = LocalFocusManager.current
var resultDialog by remember { mutableStateOf(false) }
var createdNetworkId by remember { mutableIntStateOf(-1) }
var createNetworkResult by remember { mutableIntStateOf(0) }
@@ -614,21 +617,29 @@ private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp
}
}
AnimatedVisibility(visible = useStaticIp, modifier = Modifier.padding(bottom = 8.dp)) {
val gatewayFr = FocusRequester()
val dnsFr = FocusRequester()
Column {
OutlinedTextField(
value = ipAddress, onValueChange = { ipAddress = it },
placeholder = { Text("192.168.1.2/24") }, label = { Text(stringResource(R.string.ip_address)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions { gatewayFr.requestFocus() },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
)
OutlinedTextField(
value = gatewayAddress, onValueChange = { gatewayAddress = it },
placeholder = { Text("192.168.1.1") }, label = { Text(stringResource(R.string.gateway_address)) },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions { dnsFr.requestFocus() },
modifier = Modifier.focusRequester(gatewayFr).fillMaxWidth().padding(bottom = 4.dp)
)
OutlinedTextField(
value = dnsServers, onValueChange = { dnsServers = it },
label = { Text(stringResource(R.string.dns_servers)) }, minLines = 2,
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() },
modifier = Modifier.focusRequester(dnsFr).fillMaxWidth().padding(bottom = 4.dp)
)
}
}
@@ -648,19 +659,27 @@ private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp
}
}
AnimatedVisibility(visible = useHttpProxy, modifier = Modifier.padding(bottom = 8.dp)) {
val portFr = FocusRequester()
val exclListFr = FocusRequester()
Column {
OutlinedTextField(
value = httpProxyHost, onValueChange = { httpProxyHost = it }, label = { Text(stringResource(R.string.host)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions { portFr.requestFocus() },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
)
OutlinedTextField(
value = httpProxyPort, onValueChange = { httpProxyPort = it }, label = { Text(stringResource(R.string.port)) },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number),
keyboardActions = KeyboardActions { exclListFr.requestFocus() },
modifier = Modifier.focusRequester(portFr).fillMaxWidth().padding(bottom = 4.dp)
)
OutlinedTextField(
value = httpProxyExclList, onValueChange = { httpProxyExclList = it }, label = { Text(stringResource(R.string.excluded_hosts)) },
minLines = 2, placeholder = { Text("example.com\n*.example.com") },
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() },
modifier = Modifier.focusRequester(exclListFr).fillMaxWidth().padding(bottom = 4.dp)
)
}
}
@@ -884,6 +903,7 @@ fun NetworkStats.toBucketList(): List<NetworkStats.Bucket> {
fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit) {
val context = LocalContext.current
val deviceOwner = context.isDeviceOwner
val fm = LocalFocusManager.current
val nsm = context.getSystemService(NetworkStatsManager::class.java)
val coroutine = rememberCoroutineScope()
var activeTextField by remember { mutableStateOf(NetworkStatsActiveTextField.None) } //0:None, 1:Network type, 2:Start time, 3:End time
@@ -999,6 +1019,8 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
label = { Text(stringResource(R.string.subscriber_id)) },
isError = !readOnly && subscriberId.isNullOrBlank(),
trailingIcon = { ExpandExposedTextFieldIcon(activeTextField == NetworkStatsActiveTextField.SubscriberId) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() },
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth().padding(bottom = 4.dp)
)
ExposedDropdownMenu(

View File

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL
@@ -29,7 +28,6 @@ import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
import android.app.admin.DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT
import android.app.admin.DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION
import android.os.UserManager
import android.widget.Toast
@@ -67,12 +65,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SharedPrefs
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CardItem
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.MyScaffold
@@ -219,15 +218,17 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) {
val receiver = context.getReceiver()
val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner
var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity
MyScaffold(R.string.password_info, 8.dp, onNavigateUp) {
if(VERSION.SDK_INT >= 29) {
val passwordComplexity = mapOf(
PASSWORD_COMPLEXITY_NONE to R.string.password_complexity_none,
PASSWORD_COMPLEXITY_LOW to R.string.password_complexity_low,
PASSWORD_COMPLEXITY_MEDIUM to R.string.password_complexity_medium,
PASSWORD_COMPLEXITY_HIGH to R.string.password_complexity_high
)
CardItem(R.string.current_password_complexity, passwordComplexity[dpm.passwordComplexity] ?: R.string.unknown)
val text = when(dpm.passwordComplexity) {
PASSWORD_COMPLEXITY_NONE -> R.string.none
PASSWORD_COMPLEXITY_LOW -> R.string.low
PASSWORD_COMPLEXITY_MEDIUM -> R.string.medium
PASSWORD_COMPLEXITY_HIGH -> R.string.high
else -> R.string.unknown
}
CardItem(R.string.current_password_complexity, text) { dialog = 1 }
}
if(deviceOwner || profileOwner) {
CardItem(R.string.password_sufficient, dpm.isActivePasswordSufficient.yesOrNo)
@@ -236,6 +237,15 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) {
CardItem(R.string.unified_password, dpm.isUsingUnifiedPassword(receiver).yesOrNo)
}
}
if(dialog != 0) AlertDialog(
text = { Text(stringResource(R.string.info_password_complexity)) },
confirmButton = {
TextButton({ dialog = 0 }) {
Text(stringResource(R.string.confirm))
}
},
onDismissRequest = { dialog = 0 }
)
}
@Serializable object ResetPasswordToken
@@ -430,34 +440,29 @@ fun RequiredPasswordComplexityScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val passwordComplexity = mapOf(
PASSWORD_COMPLEXITY_NONE to R.string.password_complexity_none,
PASSWORD_COMPLEXITY_LOW to R.string.password_complexity_low,
PASSWORD_COMPLEXITY_MEDIUM to R.string.password_complexity_medium,
PASSWORD_COMPLEXITY_HIGH to R.string.password_complexity_high
PASSWORD_COMPLEXITY_NONE to R.string.none,
PASSWORD_COMPLEXITY_LOW to R.string.low,
PASSWORD_COMPLEXITY_MEDIUM to R.string.medium,
PASSWORD_COMPLEXITY_HIGH to R.string.high
)
var selectedItem by remember { mutableIntStateOf(PASSWORD_COMPLEXITY_NONE) }
LaunchedEffect(Unit) { selectedItem = dpm.requiredPasswordComplexity }
MyScaffold(R.string.required_password_complexity, 8.dp, onNavigateUp) {
MyScaffold(R.string.required_password_complexity, 0.dp, onNavigateUp) {
passwordComplexity.forEach {
RadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key }
FullWidthRadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key }
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.requiredPasswordComplexity = selectedItem
selectedItem = dpm.requiredPasswordComplexity
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)
) {
Text(text = stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = { context.startActivity(Intent(ACTION_SET_NEW_PASSWORD)) },
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.require_set_new_password))
}
InfoCard(R.string.info_password_complexity, 8.dp)
}
}
@@ -494,20 +499,19 @@ fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) {
}
LaunchedEffect(mode) { if(mode != 2) flag = dpm.getKeyguardDisabledFeatures(receiver) }
LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.disable_keyguard_features, 8.dp, onNavigateUp) {
RadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 }
RadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 }
RadioButtonItem(R.string.custom, mode == 2) { mode = 2 }
MyScaffold(R.string.disable_keyguard_features, 0.dp, onNavigateUp) {
FullWidthRadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 }
FullWidthRadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 }
FullWidthRadioButtonItem(R.string.custom, mode == 2) { mode = 2 }
AnimatedVisibility(mode == 2) {
Column {
flagsLiat.forEach {
CheckBoxItem(it.first, flag and it.second == it.second) { checked ->
FullWidthCheckBoxItem(it.first, flag and it.second == it.second) { checked ->
flag = if(checked) flag or it.second else flag and (flag xor it.second)
}
}
}
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val disabledFeatures = if(mode == 0) KEYGUARD_DISABLE_FEATURES_NONE else if(mode == 1) KEYGUARD_DISABLE_FEATURES_ALL else flag
@@ -515,7 +519,7 @@ fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) {
refresh()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
) {
Text(text = stringResource(R.string.apply))
}

View File

@@ -20,18 +20,17 @@ import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -497,7 +496,7 @@ fun DeviceOwnerScreen(onNavigateUp: () -> Unit) {
}
@Suppress("InlinedApi")
private enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) {
enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) {
AppRestrictions(DevicePolicyManager.DELEGATION_APP_RESTRICTIONS, R.string.manage_application_restrictions),
BlockUninstall(DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL, R.string.block_uninstall),
CertInstall(DevicePolicyManager.DELEGATION_CERT_INSTALL, R.string.manage_certificates),
@@ -515,13 +514,10 @@ private enum class DelegatedScope(val id: String, @StringRes val string: Int, va
@RequiresApi(26)
@Composable
fun DelegatedAdminsScreen(onNavigateUp: () -> Unit) {
fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdmin) -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var dialog by rememberSaveable { mutableIntStateOf(0) } // 0:None, 1:Edit, 2:Add
var inputPackageName by rememberSaveable { mutableStateOf("") }
var selectedScopes by rememberSaveable { mutableStateOf(listOf<String>()) }
val packages = remember { mutableStateMapOf<String, MutableList<DelegatedScope>>() }
fun refresh() {
val list = mutableMapOf<String, MutableList<DelegatedScope>>()
@@ -542,81 +538,91 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit) {
LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.delegated_admins, 0.dp, onNavigateUp) {
packages.forEach { (pkg, scopes) ->
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { inputPackageName = pkg; selectedScopes = scopes.map { it.id }; dialog = 1 }
.padding(horizontal = 12.dp, vertical = 8.dp)
Row(
Modifier.fillMaxWidth().padding(vertical = 8.dp).padding(start = 14.dp, end = 8.dp),
Arrangement.SpaceBetween
) {
Text(pkg, style = typography.titleLarge)
Text(scopes.size.toString() + " " + stringResource(R.string.delegated_scope))
Column {
Text(pkg, style = typography.titleMedium)
Text(
scopes.size.toString() + " " + stringResource(R.string.delegated_scope),
color = colorScheme.onSurfaceVariant, style = typography.bodyMedium
)
}
IconButton({ onNavigate(AddDelegatedAdmin(pkg, scopes)) }) {
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
}
}
}
if(packages.isEmpty())
Text(
stringResource(R.string.none),
color = colorScheme.onSurfaceVariant,
modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 4.dp)
)
if(packages.isEmpty()) Text(
stringResource(R.string.none),
color = colorScheme.onSurfaceVariant,
modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 4.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { inputPackageName = ""; selectedScopes = emptyList(); dialog = 2 }
.padding(vertical = 10.dp, horizontal = 12.dp),
.clickable { onNavigate(AddDelegatedAdmin()) }
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Add, null, modifier = Modifier.padding(end = 12.dp))
Text(stringResource(R.string.add_delegated_admin), style = typography.titleLarge)
Text(stringResource(R.string.add_delegated_admin), style = typography.titleMedium)
}
if(dialog != 0) {
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
result?.let { inputPackageName = it }
}
}
@Serializable data class AddDelegatedAdmin(val pkg: String = "", val scopes: List<DelegatedScope> = emptyList())
@RequiresApi(26)
@Composable
fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) {
val updateMode = data.pkg.isNotEmpty()
val fm = LocalFocusManager.current
val context = LocalContext.current
var input by remember { mutableStateOf(data.pkg) }
val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) }
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
result?.let { input = it }
}
MyScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, 0.dp, onNavigateUp, !updateMode) {
OutlinedTextField(
value = input, onValueChange = { input = it },
label = { Text(stringResource(R.string.package_name)) },
trailingIcon = {
if(!updateMode) IconButton({ choosePackage.launch(null) }) {
Icon(painterResource(R.drawable.list_fill0), null)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() },
readOnly = updateMode,
modifier = Modifier.fillMaxWidth().padding(8.dp)
)
DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach {scope ->
FullWidthCheckBoxItem(scope.string, scope in scopes) {
if(it) scopes += scope else scopes -= scope
}
AlertDialog(
text = {
val fm = LocalFocusManager.current
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
OutlinedTextField(
value = inputPackageName, onValueChange = { inputPackageName = it },
label = { Text(stringResource(R.string.package_name)) },
trailingIcon = {
if(dialog == 2) IconButton({ choosePackage.launch(null) }) {
Icon(painterResource(R.drawable.list_fill0), null)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { fm.clearFocus() },
readOnly = dialog == 1,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
)
DelegatedScope.entries.forEach { scope ->
if(VERSION.SDK_INT >= scope.requiresApi) {
CheckBoxItem(scope.string, scope.id in selectedScopes) {
if(it) selectedScopes += scope.id else selectedScopes -= scope.id
}
}
}
}
},
confirmButton = {
TextButton(
onClick = {
dpm.setDelegatedScopes(receiver, inputPackageName, selectedScopes)
refresh()
dialog = 0
},
enabled = inputPackageName.isNotBlank()
) {
Text(stringResource(if(dialog == 1) R.string.apply else R.string.add))
}
},
dismissButton = {
TextButton({ dialog = 0 }) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = { dialog = 0 }
)
}
Button(
onClick = {
context.getDPM().setDelegatedScopes(context.getReceiver(), input, scopes.map { it.id })
onNavigateUp()
},
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp),
enabled = input.isNotBlank() && (!updateMode || scopes.toList() != data.scopes)
) {
Text(stringResource(if(updateMode) R.string.update else R.string.add))
}
if(updateMode) Button(
onClick = {
context.getDPM().setDelegatedScopes(context.getReceiver(), input, emptyList())
onNavigateUp()
},
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError)
) {
Text(stringResource(R.string.delete))
}
}
}

View File

@@ -133,6 +133,7 @@ import com.bintianqi.owndroid.humanReadableDate
import com.bintianqi.owndroid.parseDate
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.ListItem
@@ -847,13 +848,13 @@ fun ContentProtectionPolicyScreen(onNavigateUp: () -> Unit) {
var policy by remember { mutableIntStateOf(DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY) }
fun refresh() { policy = dpm.getContentProtectionPolicy(receiver) }
LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.content_protection_policy, 8.dp, onNavigateUp) {
MyScaffold(R.string.content_protection_policy, 0.dp, onNavigateUp) {
mapOf(
DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY to R.string.not_controlled_by_policy,
DevicePolicyManager.CONTENT_PROTECTION_ENABLED to R.string.enabled,
DevicePolicyManager.CONTENT_PROTECTION_DISABLED to R.string.disabled
).forEach { (policyId, string) ->
RadioButtonItem(string, policy == policyId) { policy = policyId }
FullWidthRadioButtonItem(string, policy == policyId) { policy = policyId }
}
Button(
onClick = {
@@ -861,11 +862,11 @@ fun ContentProtectionPolicyScreen(onNavigateUp: () -> Unit) {
refresh()
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_content_protection_policy)
InfoCard(R.string.info_content_protection_policy, 8.dp)
}
}
@@ -878,21 +879,27 @@ fun PermissionPolicyScreen(onNavigateUp: () -> Unit) {
val dpm = context.getDPM()
val receiver = context.getReceiver()
var selectedPolicy by remember { mutableIntStateOf(dpm.getPermissionPolicy(receiver)) }
MyScaffold(R.string.permission_policy, 8.dp, onNavigateUp) {
RadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) { selectedPolicy = PERMISSION_POLICY_PROMPT }
RadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT) { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT }
RadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY) { selectedPolicy = PERMISSION_POLICY_AUTO_DENY }
MyScaffold(R.string.permission_policy, 0.dp, onNavigateUp) {
FullWidthRadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) {
selectedPolicy = PERMISSION_POLICY_PROMPT
}
FullWidthRadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT) {
selectedPolicy = PERMISSION_POLICY_AUTO_GRANT
}
FullWidthRadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY) {
selectedPolicy = PERMISSION_POLICY_AUTO_DENY
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.setPermissionPolicy(receiver,selectedPolicy)
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_permission_policy)
InfoCard(R.string.info_permission_policy, 8.dp)
}
}
@@ -904,10 +911,12 @@ fun MtePolicyScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
var selectedMtePolicy by remember { mutableIntStateOf(dpm.mtePolicy) }
MyScaffold(R.string.mte_policy, 8.dp, onNavigateUp) {
RadioButtonItem(R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY) { selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY }
RadioButtonItem(R.string.enabled, selectedMtePolicy == MTE_ENABLED) { selectedMtePolicy = MTE_ENABLED }
RadioButtonItem(R.string.disabled, selectedMtePolicy == MTE_DISABLED) { selectedMtePolicy = MTE_DISABLED }
MyScaffold(R.string.mte_policy, 0.dp, onNavigateUp) {
FullWidthRadioButtonItem(R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY) {
selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY
}
FullWidthRadioButtonItem(R.string.enabled, selectedMtePolicy == MTE_ENABLED) { selectedMtePolicy = MTE_ENABLED }
FullWidthRadioButtonItem(R.string.disabled, selectedMtePolicy == MTE_DISABLED) { selectedMtePolicy = MTE_DISABLED }
Button(
onClick = {
try {
@@ -918,11 +927,11 @@ fun MtePolicyScreen(onNavigateUp: () -> Unit) {
}
selectedMtePolicy = dpm.mtePolicy
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_mte_policy)
InfoCard(R.string.info_mte_policy, 8.dp)
}
}
@@ -934,62 +943,64 @@ fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
var appPolicy by remember { mutableIntStateOf(dpm.nearbyAppStreamingPolicy) }
MyScaffold(R.string.nearby_streaming_policy, 8.dp, onNavigateUp) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 3.dp))
RadioButtonItem(
MyScaffold(R.string.nearby_streaming_policy, 0.dp, onNavigateUp, false) {
Text(
stringResource(R.string.nearby_app_streaming),
Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp), style = typography.titleLarge
)
FullWidthRadioButtonItem(
R.string.decide_by_user,
appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY
) { appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY }
RadioButtonItem(R.string.enabled, appPolicy == NEARBY_STREAMING_ENABLED) { appPolicy = NEARBY_STREAMING_ENABLED }
RadioButtonItem(R.string.disabled, appPolicy == NEARBY_STREAMING_DISABLED) { appPolicy = NEARBY_STREAMING_DISABLED }
RadioButtonItem(
FullWidthRadioButtonItem(R.string.enabled, appPolicy == NEARBY_STREAMING_ENABLED) { appPolicy = NEARBY_STREAMING_ENABLED }
FullWidthRadioButtonItem(R.string.disabled, appPolicy == NEARBY_STREAMING_DISABLED) { appPolicy = NEARBY_STREAMING_DISABLED }
FullWidthRadioButtonItem(
R.string.enable_if_secure_enough,
appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
) { appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY }
Spacer(Modifier.padding(vertical = 3.dp))
Button(
onClick = {
dpm.nearbyAppStreamingPolicy = appPolicy
appPolicy = dpm.nearbyAppStreamingPolicy
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_nearby_app_streaming_policy)
InfoCard(R.string.info_nearby_app_streaming_policy, 8.dp)
var notificationPolicy by remember { mutableIntStateOf(dpm.nearbyNotificationStreamingPolicy) }
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.nearby_notification_streaming), style = typography.titleLarge)
Spacer(Modifier.padding(vertical = 3.dp))
RadioButtonItem(
Text(
stringResource(R.string.nearby_notification_streaming),
Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp), style = typography.titleLarge
)
FullWidthRadioButtonItem(
R.string.decide_by_user,
notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY
) { notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY }
RadioButtonItem(
FullWidthRadioButtonItem(
R.string.enabled,
notificationPolicy == NEARBY_STREAMING_ENABLED
) { notificationPolicy = NEARBY_STREAMING_ENABLED }
RadioButtonItem(
FullWidthRadioButtonItem(
R.string.disabled,
notificationPolicy == NEARBY_STREAMING_DISABLED
) { notificationPolicy = NEARBY_STREAMING_DISABLED }
RadioButtonItem(
FullWidthRadioButtonItem(
R.string.enable_if_secure_enough,
notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
) { notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY }
Spacer(Modifier.padding(vertical = 3.dp))
Button(
onClick = {
dpm.nearbyNotificationStreamingPolicy = notificationPolicy
notificationPolicy = dpm.nearbyNotificationStreamingPolicy
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_nearby_notification_streaming_policy)
InfoCard(R.string.info_nearby_notification_streaming_policy, 8.dp)
}
}
@@ -1586,7 +1597,8 @@ fun FrpPolicyScreen(onNavigateUp: () -> Unit) {
onClick = {
accountList += inputAccount
inputAccount = ""
}
},
enabled = inputAccount.isNotBlank()
) {
Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add))
}
@@ -1733,87 +1745,81 @@ fun WipeDataScreen(onNavigateUp: () -> Unit) {
@Serializable object SetSystemUpdatePolicy
@RequiresApi(23)
@Composable
fun SystemUpdatePolicy(onNavigateUp: () -> Unit) {
fun SystemUpdatePolicyScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
MyScaffold(R.string.system_update_policy, 8.dp, onNavigateUp) {
if(VERSION.SDK_INT >= 23) {
Column {
var selectedPolicy by remember { mutableStateOf(dpm.systemUpdatePolicy?.policyType) }
RadioButtonItem(
R.string.system_update_policy_automatic,
selectedPolicy == TYPE_INSTALL_AUTOMATIC
) { selectedPolicy = TYPE_INSTALL_AUTOMATIC }
RadioButtonItem(
R.string.system_update_policy_install_windowed,
selectedPolicy == TYPE_INSTALL_WINDOWED
) { selectedPolicy = TYPE_INSTALL_WINDOWED }
RadioButtonItem(
R.string.system_update_policy_postpone,
selectedPolicy == TYPE_POSTPONE
) { selectedPolicy = TYPE_POSTPONE }
RadioButtonItem(R.string.none, selectedPolicy == null) { selectedPolicy = null }
var windowedPolicyStart by remember { mutableStateOf("") }
var windowedPolicyEnd by remember { mutableStateOf("") }
AnimatedVisibility(selectedPolicy == 2) {
Column {
Row(
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedTextField(
value = windowedPolicyStart,
label = { Text(stringResource(R.string.start_time)) },
onValueChange = { windowedPolicyStart = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth(0.49F)
)
OutlinedTextField(
value = windowedPolicyEnd,
onValueChange = {windowedPolicyEnd = it },
label = { Text(stringResource(R.string.end_time)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth(0.96F).padding(bottom = 2.dp)
)
}
Text(text = stringResource(R.string.minutes_in_one_day))
}
}
Button(
onClick = {
val policy =
when(selectedPolicy) {
TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy()
TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(), windowedPolicyEnd.toInt())
TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy()
else -> null
}
dpm.setSystemUpdatePolicy(receiver,policy)
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
) {
Text(stringResource(R.string.apply))
MyScaffold(R.string.system_update_policy, 0.dp, onNavigateUp) {
var selectedPolicy by remember { mutableStateOf(dpm.systemUpdatePolicy?.policyType) }
FullWidthRadioButtonItem(
R.string.system_update_policy_automatic,
selectedPolicy == TYPE_INSTALL_AUTOMATIC
) { selectedPolicy = TYPE_INSTALL_AUTOMATIC }
FullWidthRadioButtonItem(
R.string.system_update_policy_install_windowed,
selectedPolicy == TYPE_INSTALL_WINDOWED
) { selectedPolicy = TYPE_INSTALL_WINDOWED }
FullWidthRadioButtonItem(
R.string.system_update_policy_postpone,
selectedPolicy == TYPE_POSTPONE
) { selectedPolicy = TYPE_POSTPONE }
FullWidthRadioButtonItem(R.string.none, selectedPolicy == null) { selectedPolicy = null }
var windowedPolicyStart by remember { mutableStateOf("") }
var windowedPolicyEnd by remember { mutableStateOf("") }
AnimatedVisibility(selectedPolicy == 2) {
Column(Modifier.padding(horizontal = 8.dp)) {
Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) {
OutlinedTextField(
value = windowedPolicyStart,
label = { Text(stringResource(R.string.start_time)) },
onValueChange = { windowedPolicyStart = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth(0.49F)
)
OutlinedTextField(
value = windowedPolicyEnd,
onValueChange = {windowedPolicyEnd = it },
label = { Text(stringResource(R.string.end_time)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth(0.96F).padding(bottom = 2.dp)
)
}
Text(stringResource(R.string.minutes_in_one_day), color = colorScheme.onSurfaceVariant, style = typography.bodyMedium)
}
}
Button(
onClick = {
val policy =
when(selectedPolicy) {
TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy()
TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(), windowedPolicyEnd.toInt())
TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy()
else -> null
}
dpm.setSystemUpdatePolicy(receiver,policy)
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)
) {
Text(stringResource(R.string.apply))
}
if(VERSION.SDK_INT >= 26) {
Spacer(Modifier.padding(vertical = 10.dp))
val sysUpdateInfo = dpm.getPendingSystemUpdate(receiver)
Column {
Column(Modifier.padding(8.dp)) {
if(sysUpdateInfo != null) {
Text(text = stringResource(R.string.update_received_time, Date(sysUpdateInfo.receivedTime)))
val securityStateDesc = when(sysUpdateInfo.securityPatchState) {
SystemUpdateInfo.SECURITY_PATCH_STATE_UNKNOWN -> stringResource(R.string.unknown)
SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE -> "true"
else->"false"
val securityPatchStateText = when(sysUpdateInfo.securityPatchState) {
SystemUpdateInfo.SECURITY_PATCH_STATE_FALSE -> R.string.no
SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE -> R.string.yes
else -> R.string.unknown
}
Text(text = stringResource(R.string.is_security_patch, securityStateDesc))
}else{
Text(text = stringResource(R.string.is_security_patch, stringResource(securityPatchStateText)))
} else {
Text(text = stringResource(R.string.no_system_update))
}
}

View File

@@ -118,7 +118,10 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
if(bitmap != null) changeUserIconDialog = true
}
}
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { launcher.launch("image/*") }
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) {
Toast.makeText(context, R.string.select_an_image, Toast.LENGTH_SHORT).show()
launcher.launch("image/*")
}
if(changeUserIconDialog == true) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
@@ -432,7 +435,8 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
onClick = {
list += input
input = ""
}
},
enabled = input.isNotEmpty()
) {
Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add))
}
@@ -453,7 +457,7 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) {
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_affiliated_id)
InfoCard(R.string.info_affiliation_id)
}
}

View File

@@ -272,12 +272,12 @@ fun ListItem(text: String, onDelete: () -> Unit) {
}
@Composable
fun InfoCard(@StringRes strID: Int) {
fun InfoCard(@StringRes strID: Int, horizonPadding: Dp = 0.dp) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.clip(RoundedCornerShape(10))
.padding(vertical = 8.dp, horizontal = horizonPadding)
.clip(RoundedCornerShape(12.dp))
.background(color = colorScheme.tertiaryContainer)
.padding(8.dp)
) {