manager: several UI improvements (#515)

This commit is contained in:
Nullptr
2023-05-17 09:34:08 +08:00
committed by GitHub
parent d162221fac
commit 31a9189d80
3 changed files with 119 additions and 95 deletions

View File

@@ -2,9 +2,13 @@ package me.weishu.kernelsu.ui.component.profile
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -37,7 +41,7 @@ fun RootProfileConfig(
) )
} }
var namespaceBoxExpanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
val currentNamespace = when (profile.namespace) { val currentNamespace = when (profile.namespace) {
RootProfile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited) RootProfile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited)
RootProfile.Namespace.Global -> stringResource(R.string.profile_namespace_global) RootProfile.Namespace.Global -> stringResource(R.string.profile_namespace_global)
@@ -45,8 +49,8 @@ fun RootProfileConfig(
} }
ListItem(headlineContent = { ListItem(headlineContent = {
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
expanded = namespaceBoxExpanded, expanded = expanded,
onExpandedChange = { namespaceBoxExpanded = it } onExpandedChange = { expanded = !expanded }
) { ) {
OutlinedTextField( OutlinedTextField(
modifier = Modifier.menuAnchor(), modifier = Modifier.menuAnchor(),
@@ -54,30 +58,34 @@ fun RootProfileConfig(
label = { Text(stringResource(R.string.profile_namespace)) }, label = { Text(stringResource(R.string.profile_namespace)) },
value = currentNamespace, value = currentNamespace,
onValueChange = {}, onValueChange = {},
trailingIcon = {
if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
else Icon(Icons.Filled.ArrowDropDown, null)
},
) )
ExposedDropdownMenu( ExposedDropdownMenu(
expanded = namespaceBoxExpanded, expanded = expanded,
onDismissRequest = { namespaceBoxExpanded = false } onDismissRequest = { expanded = false }
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_inherited)) }, text = { Text(stringResource(R.string.profile_namespace_inherited)) },
onClick = { onClick = {
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Inherited)) onProfileChange(profile.copy(namespace = RootProfile.Namespace.Inherited))
namespaceBoxExpanded = false expanded = false
}, },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_global)) }, text = { Text(stringResource(R.string.profile_namespace_global)) },
onClick = { onClick = {
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Global)) onProfileChange(profile.copy(namespace = RootProfile.Namespace.Global))
namespaceBoxExpanded = false expanded = false
}, },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_individual)) }, text = { Text(stringResource(R.string.profile_namespace_individual)) },
onClick = { onClick = {
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Individual)) onProfileChange(profile.copy(namespace = RootProfile.Namespace.Individual))
namespaceBoxExpanded = false expanded = false
}, },
) )
} }

View File

@@ -1,19 +1,25 @@
package me.weishu.kernelsu.ui.screen package me.weishu.kernelsu.ui.screen
import android.os.Parcelable import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Android import androidx.compose.material.icons.filled.Android
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Security
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
@@ -39,12 +45,10 @@ import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.AppProfile import me.weishu.kernelsu.profile.AppProfile
import me.weishu.kernelsu.profile.RootProfile import me.weishu.kernelsu.profile.RootProfile
import me.weishu.kernelsu.ui.component.RadioItem
import me.weishu.kernelsu.ui.component.SwitchItem import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
@@ -126,96 +130,69 @@ private fun AppProfileInner(
onCheckedChange = onSwitchRootPermission, onCheckedChange = onSwitchRootPermission,
) )
Divider(thickness = Dp.Hairline)
Crossfade(targetState = isRootGranted, label = "") { current -> Crossfade(targetState = isRootGranted, label = "") { current ->
if (current) { Column {
var mode: Mode<RootProfile> by rememberSaveable { mutableStateOf(Mode.Default()) } if (current) {
var template by rememberSaveable { mutableStateOf("None") } var mode by rememberSaveable { mutableStateOf(Mode.Default) }
var profile by rememberSaveable { mutableStateOf(RootProfile("@$packageName")) } ProfileBox(mode, true) { mode = it }
Crossfade(targetState = mode, label = "") { currentMode ->
Column { if (currentMode == Mode.Template) {
RadioItem( var expanded by remember { mutableStateOf(false) }
title = stringResource(R.string.profile_default), var template by rememberSaveable { mutableStateOf("None") }
selected = mode is Mode.Default, ListItem(headlineContent = {
onClick = { mode = Mode.Default() } ExposedDropdownMenuBox(
) expanded = expanded,
onExpandedChange = { expanded = it },
RadioItem( ) {
title = stringResource(R.string.profile_template), OutlinedTextField(
selected = mode is Mode.Template, modifier = Modifier.menuAnchor(),
onClick = { mode = Mode.Template("") } readOnly = true,
) label = { Text(stringResource(R.string.profile_template)) },
AnimatedVisibility(mode is Mode.Template) { value = template,
var expanded by remember { mutableStateOf(false) } onValueChange = {},
ListItem(headlineContent = { trailingIcon = {
ExposedDropdownMenuBox( if (expanded) Icon(Icons.Filled.ArrowDropUp, null)
expanded = expanded, else Icon(Icons.Filled.ArrowDropDown, null)
onExpandedChange = { expanded = it }, },
) { )
OutlinedTextField( // TODO: Template
modifier = Modifier.menuAnchor(), }
readOnly = true, })
label = { Text(stringResource(R.string.profile_template)) }, } else if (mode == Mode.Custom) {
value = template, var profile by rememberSaveable { mutableStateOf(RootProfile("@$packageName")) }
onValueChange = {} RootProfileConfig(
) fixedName = true,
// TODO: Template profile = profile,
} onProfileChange = { profile = it }
}) )
}
} }
} else {
RadioItem( var mode by rememberSaveable { mutableStateOf(Mode.Default) }
title = stringResource(R.string.profile_custom), ProfileBox(mode, false) { mode = it }
selected = mode is Mode.Custom, Crossfade(targetState = mode, label = "") { currentMode ->
onClick = { mode = Mode.Custom(profile) } if (currentMode == Mode.Custom) {
) var profile by rememberSaveable { mutableStateOf(AppProfile(packageName)) }
AnimatedVisibility(mode is Mode.Custom) { AppProfileConfig(
RootProfileConfig( fixedName = true,
fixedName = true, profile = profile,
profile = profile, onProfileChange = { profile = it }
onProfileChange = { profile = it } )
) }
} }
} }
} else {
var mode: Mode<AppProfile> by rememberSaveable { mutableStateOf(Mode.Default()) }
var profile by rememberSaveable { mutableStateOf(AppProfile("@$packageName")) }
Column {
RadioItem(
title = stringResource(R.string.profile_default),
selected = mode is Mode.Default,
onClick = { mode = Mode.Default() }
)
RadioItem(
title = stringResource(R.string.profile_custom),
selected = mode is Mode.Custom,
onClick = { mode = Mode.Custom(profile) }
)
AnimatedVisibility(mode is Mode.Custom) {
AppProfileConfig(
fixedName = true,
profile = profile,
onProfileChange = { profile = it }
)
}
}
} }
} }
} }
} }
@Parcelize private enum class Mode(@StringRes private val res: Int) {
private sealed class Mode<P : Parcelable> : Parcelable { Default(R.string.profile_default),
Template(R.string.profile_template),
Custom(R.string.profile_custom);
class Default<P : Parcelable> : Mode<P>() val text: String
@Composable get() = stringResource(res)
class Template<P : Parcelable>(val template: String) : Mode<P>()
class Custom<P : Parcelable>(val profile: P) : Mode<P>()
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -233,6 +210,45 @@ private fun TopBar(onBack: () -> Unit) {
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ProfileBox(
mode: Mode,
hasTemplate: Boolean,
onModeChange: (Mode) -> Unit,
) {
ListItem(
headlineContent = { Text(stringResource(R.string.profile)) },
supportingContent = { Text(mode.text) },
leadingContent = { Icon(Icons.Filled.AccountCircle, null) },
)
Divider(thickness = Dp.Hairline)
ListItem(headlineContent = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
FilterChip(
selected = mode == Mode.Default,
label = { Text(stringResource(R.string.profile_default)) },
onClick = { onModeChange(Mode.Default) },
)
if (hasTemplate) {
FilterChip(
selected = mode == Mode.Template,
label = { Text(stringResource(R.string.profile_template)) },
onClick = { onModeChange(Mode.Template) },
)
}
FilterChip(
selected = mode == Mode.Custom,
label = { Text(stringResource(R.string.profile_custom)) },
onClick = { onModeChange(Mode.Custom) },
)
}
})
}
@Preview @Preview
@Composable @Composable
private fun AppProfilePreview() { private fun AppProfilePreview() {

View File

@@ -66,9 +66,9 @@
<string name="home_support_content">KernelSU is, and always will be, free, and open source. You can however show us that you care by making a donation.</string> <string name="home_support_content">KernelSU is, and always will be, free, and open source. You can however show us that you care by making a donation.</string>
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Join our %2$s channel]]></string> <string name="about_source_code"><![CDATA[View source code at %1$s<br/>Join our %2$s channel]]></string>
<string name="profile">App profile</string> <string name="profile">App profile</string>
<string name="profile_default">Use default profile</string> <string name="profile_default">Default</string>
<string name="profile_template">Use template profile</string> <string name="profile_template">Template</string>
<string name="profile_custom">Use custom profile</string> <string name="profile_custom">Custom</string>
<string name="profile_name">Profile name</string> <string name="profile_name">Profile name</string>
<string name="profile_namespace">Mount namespace</string> <string name="profile_namespace">Mount namespace</string>
<string name="profile_namespace_inherited">Inherited</string> <string name="profile_namespace_inherited">Inherited</string>