Fix Managed configurations bugs, add search bar (#198)

This commit is contained in:
BinTianqi
2025-11-25 13:31:20 +08:00
parent 4bcd2d8150
commit b242488a2a
4 changed files with 89 additions and 40 deletions

View File

@@ -602,7 +602,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
} }
composable<ManagedConfiguration> { composable<ManagedConfiguration> {
ManagedConfigurationScreen( ManagedConfigurationScreen(
it.toRoute(), vm.appRestrictions, vm::getAppRestrictions, vm::setAppRestrictions, it.toRoute(), vm.appRestrictions, vm::setAppRestrictions,
vm::clearAppRestrictions, ::navigateUp vm::clearAppRestrictions, ::navigateUp
) )
} }

View File

@@ -518,12 +518,11 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
@RequiresApi(23) @RequiresApi(23)
fun getAppRestrictions(name: String) { fun getAppRestrictions(name: String) {
val rm = application.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager val rm = application.getSystemService(RestrictionsManager::class.java)
val bundle = DPM.getApplicationRestrictions(DAR, name) val bundle = DPM.getApplicationRestrictions(DAR, name)
println(bundle.keySet()) appRestrictions.value = rm.getManifestRestrictions(name)?.mapNotNull {
appRestrictions.value = rm.getManifestRestrictions(name).mapNotNull {
transformRestrictionEntry(it) transformRestrictionEntry(it)
}.map { }?.map {
if (bundle.containsKey(it.key)) { if (bundle.containsKey(it.key)) {
when (it) { when (it) {
is AppRestriction.BooleanItem -> it.value = bundle.getBoolean(it.key) is AppRestriction.BooleanItem -> it.value = bundle.getBoolean(it.key)
@@ -534,17 +533,16 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
it it
} } ?: emptyList()
} }
@RequiresApi(23) @RequiresApi(23)
fun setAppRestrictions(name: String, item: AppRestriction) { fun setAppRestrictions(name: String, item: AppRestriction) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
appRestrictions.value = emptyList() val bundle = transformAppRestriction(
DPM.setApplicationRestrictions( appRestrictions.value.filter { it.key != item.key }.plus(item)
DAR, name,
transformAppRestriction(appRestrictions.value.filter { it.key != item.key }.plus(item))
) )
DPM.setApplicationRestrictions(DAR, name, bundle)
getAppRestrictions(name) getAppRestrictions(name)
} }
} }

View File

@@ -22,6 +22,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -122,15 +123,12 @@ fun AppChooserScreen(
keyboardActions = KeyboardActions { focusMgr.clearFocus() }, keyboardActions = KeyboardActions { focusMgr.clearFocus() },
placeholder = { Text(stringResource(R.string.search)) }, placeholder = { Text(stringResource(R.string.search)) },
trailingIcon = { trailingIcon = {
Icon( IconButton({
painter = painterResource(R.drawable.close_fill0), query = ""
contentDescription = null, searchMode = false
modifier = Modifier.clickable { }) {
focusMgr.clearFocus() Icon(Icons.Outlined.Clear, null)
query = "" }
searchMode = false
}
)
}, },
textStyle = typography.bodyLarge, textStyle = typography.bodyLarge,
modifier = Modifier.fillMaxWidth().focusRequester(fr) modifier = Modifier.fillMaxWidth().focusRequester(fr)

View File

@@ -40,7 +40,9 @@ import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -79,6 +81,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -258,7 +262,11 @@ fun ApplicationDetailsScreen(
var dialog by rememberSaveable { mutableIntStateOf(0) } // 1: clear storage, 2: uninstall var dialog by rememberSaveable { mutableIntStateOf(0) } // 1: clear storage, 2: uninstall
val info = vm.getAppInfo(packageName) val info = vm.getAppInfo(packageName)
val status by vm.appStatus.collectAsStateWithLifecycle() val status by vm.appStatus.collectAsStateWithLifecycle()
LaunchedEffect(Unit) { vm.getAppStatus(packageName) } val appRestrictions by vm.appRestrictions.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
vm.getAppStatus(packageName)
vm.getAppRestrictions(packageName)
}
MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) { MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) {
Column(Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
Image(rememberDrawablePainter(info.icon), null, Modifier.size(50.dp)) Image(rememberDrawablePainter(info.icon), null, Modifier.size(50.dp))
@@ -295,7 +303,7 @@ fun ApplicationDetailsScreen(
state = status.keepUninstalled, state = status.keepUninstalled,
onCheckedChange = { vm.adSetPackageKu(packageName, it) } onCheckedChange = { vm.adSetPackageKu(packageName, it) }
) )
if (VERSION.SDK_INT >= 23) { if (VERSION.SDK_INT >= 23 && appRestrictions.isNotEmpty()) {
FunctionItem(R.string.managed_configuration, icon = R.drawable.description_fill0) { FunctionItem(R.string.managed_configuration, icon = R.drawable.description_fill0) {
onNavigate(ManagedConfiguration(packageName)) onNavigate(ManagedConfiguration(packageName))
} }
@@ -1003,35 +1011,74 @@ fun EditAppGroupScreen(
@Composable @Composable
fun ManagedConfigurationScreen( fun ManagedConfigurationScreen(
params: ManagedConfiguration, appRestrictions: StateFlow<List<AppRestriction>>, params: ManagedConfiguration, appRestrictions: StateFlow<List<AppRestriction>>,
getRestriction: (String) -> Unit, setRestriction: (String, AppRestriction) -> Unit, setRestriction: (String, AppRestriction) -> Unit, clearRestriction: (String) -> Unit,
clearRestriction: (String) -> Unit, navigateUp: () -> Unit navigateUp: () -> Unit
) { ) {
val restrictions by appRestrictions.collectAsStateWithLifecycle() val restrictions by appRestrictions.collectAsStateWithLifecycle()
var dialog by remember { mutableIntStateOf(-1) } var searchMode by remember { mutableStateOf(false) }
var clearRestrictionDialog by remember { mutableStateOf(false) } var searchKeyword by remember { mutableStateOf("") }
LaunchedEffect(Unit) { val displayRestrictions = if (searchKeyword.isEmpty()) {
getRestriction(params.packageName) restrictions
} else {
restrictions.filter {
it.key.contentEquals(searchKeyword, true) ||
it.title?.contains(searchKeyword, true) ?: true
}
} }
var dialog by remember { mutableStateOf<AppRestriction?>(null) }
var clearRestrictionDialog by remember { mutableStateOf(false) }
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
{ Text(stringResource(R.string.managed_configuration)) }, {
if (searchMode) {
val fr = remember { FocusRequester() }
LaunchedEffect(Unit) {
fr.requestFocus()
}
OutlinedTextField(
searchKeyword, { searchKeyword = it },
Modifier.fillMaxWidth().focusRequester(fr),
textStyle = typography.bodyLarge,
placeholder = { Text(stringResource(R.string.search)) },
trailingIcon = {
IconButton({
searchKeyword = ""
searchMode = false
}) {
Icon(Icons.Outlined.Clear, null)
}
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
)
} else {
Text(stringResource(R.string.managed_configuration))
}
},
navigationIcon = { NavIcon(navigateUp) }, navigationIcon = { NavIcon(navigateUp) },
actions = { actions = {
IconButton({ if (!searchMode) {
clearRestrictionDialog = true IconButton({
}) { searchMode = true
Icon(Icons.Outlined.Delete, null) }) {
Icon(Icons.Outlined.Search, null)
}
IconButton({
clearRestrictionDialog = true
}) {
Icon(Icons.Outlined.Delete, null)
}
} }
} }
) )
} },
contentWindowInsets = adaptiveInsets()
) { paddingValues -> ) { paddingValues ->
LazyColumn(Modifier.padding(paddingValues)) { LazyColumn(Modifier.padding(paddingValues)) {
itemsIndexed(restrictions) { index, entry -> items(displayRestrictions, { it.key }) { entry ->
Row( Row(
Modifier.fillMaxWidth().clickable { Modifier.fillMaxWidth().clickable {
dialog = index dialog = entry
}.padding(HorizontalPadding, 8.dp), }.padding(HorizontalPadding, 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -1055,7 +1102,13 @@ fun ManagedConfigurationScreen(
is AppRestriction.StringItem -> entry.value?.take(30) is AppRestriction.StringItem -> entry.value?.take(30)
is AppRestriction.BooleanItem -> entry.value?.toString() is AppRestriction.BooleanItem -> entry.value?.toString()
is AppRestriction.ChoiceItem -> entry.value is AppRestriction.ChoiceItem -> entry.value
is AppRestriction.MultiSelectItem -> entry.value?.joinToString(limit = 30) is AppRestriction.MultiSelectItem -> {
if (entry.value != null) {
entry.entryValues
.filter { entry.value?.contains(it) ?: false }
.joinToString(limit = 30)
} else null
}
} }
Text( Text(
text ?: "null", Modifier.alpha(0.7F), text ?: "null", Modifier.alpha(0.7F),
@@ -1070,8 +1123,8 @@ fun ManagedConfigurationScreen(
} }
} }
} }
if (dialog != -1) Dialog({ if (dialog != null) Dialog({
dialog = -1 dialog = null
}) { }) {
Surface( Surface(
color = AlertDialogDefaults.containerColor, color = AlertDialogDefaults.containerColor,
@@ -1079,11 +1132,11 @@ fun ManagedConfigurationScreen(
tonalElevation = AlertDialogDefaults.TonalElevation, tonalElevation = AlertDialogDefaults.TonalElevation,
) { ) {
Column(Modifier.verticalScroll(rememberScrollState()).padding(12.dp)) { Column(Modifier.verticalScroll(rememberScrollState()).padding(12.dp)) {
ManagedConfigurationDialog(restrictions[dialog]) { ManagedConfigurationDialog(dialog!!) {
if (it != null) { if (it != null) {
setRestriction(params.packageName, it) setRestriction(params.packageName, it)
} }
dialog = -1 dialog = null
} }
} }
} }