manager: Updated superuser interface, added sidebar categories

This commit is contained in:
ShirkNeko
2025-06-08 20:28:05 +08:00
parent fa060dca58
commit 5bbd95e821
5 changed files with 901 additions and 540 deletions

View File

@@ -0,0 +1,277 @@
package com.sukisu.ultra.ui.component
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
import com.sukisu.ultra.R
// 菜单项数据类
data class FabMenuItem(
val icon: ImageVector,
val labelRes: Int,
val color: Color = Color.Unspecified,
val onClick: () -> Unit
)
// 动画配置
object FabAnimationConfig {
const val ANIMATION_DURATION = 300
const val STAGGER_DELAY = 50
val BUTTON_SPACING = 72.dp
val BUTTON_SIZE = 56.dp
val SMALL_BUTTON_SIZE = 48.dp
}
@Composable
fun VerticalExpandableFab(
menuItems: List<FabMenuItem>,
modifier: Modifier = Modifier,
buttonSize: Dp = FabAnimationConfig.BUTTON_SIZE,
smallButtonSize: Dp = FabAnimationConfig.SMALL_BUTTON_SIZE,
buttonSpacing: Dp = FabAnimationConfig.BUTTON_SPACING,
animationDurationMs: Int = FabAnimationConfig.ANIMATION_DURATION,
staggerDelayMs: Int = FabAnimationConfig.STAGGER_DELAY,
mainButtonIcon: ImageVector = Icons.Filled.Add,
mainButtonExpandedIcon: ImageVector = Icons.Filled.Close,
onMainButtonClick: (() -> Unit)? = null,
) {
var isExpanded by remember { mutableStateOf(false) }
// 主按钮旋转动画
val rotationAngle by animateFloatAsState(
targetValue = if (isExpanded) 45f else 0f,
animationSpec = tween(
durationMillis = animationDurationMs,
easing = FastOutSlowInEasing
),
label = "mainButtonRotation"
)
// 主按钮缩放动画
val mainButtonScale by animateFloatAsState(
targetValue = if (isExpanded) 1.1f else 1f,
animationSpec = tween(
durationMillis = animationDurationMs,
easing = FastOutSlowInEasing
),
label = "mainButtonScale"
)
Box(
modifier = modifier.wrapContentSize(),
contentAlignment = Alignment.BottomEnd
) {
// 子菜单按钮
menuItems.forEachIndexed { index, menuItem ->
val animatedOffsetY by animateFloatAsState(
targetValue = if (isExpanded) {
-(buttonSpacing.value * (index + 1))
} else {
0f
},
animationSpec = tween(
durationMillis = animationDurationMs,
delayMillis = if (isExpanded) {
index * staggerDelayMs
} else {
(menuItems.size - index - 1) * staggerDelayMs
},
easing = FastOutSlowInEasing
),
label = "fabOffset$index"
)
val animatedScale by animateFloatAsState(
targetValue = if (isExpanded) 1f else 0f,
animationSpec = tween(
durationMillis = animationDurationMs,
delayMillis = if (isExpanded) {
index * staggerDelayMs + 100
} else {
(menuItems.size - index - 1) * staggerDelayMs
},
easing = FastOutSlowInEasing
),
label = "fabScale$index"
)
val animatedAlpha by animateFloatAsState(
targetValue = if (isExpanded) 1f else 0f,
animationSpec = tween(
durationMillis = animationDurationMs,
delayMillis = if (isExpanded) {
index * staggerDelayMs + 150
} else {
(menuItems.size - index - 1) * staggerDelayMs
},
easing = FastOutSlowInEasing
),
label = "fabAlpha$index"
)
// 子按钮容器(包含标签)
Row(
modifier = Modifier
.offset(y = animatedOffsetY.dp)
.scale(animatedScale)
.alpha(animatedAlpha),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
// 标签
AnimatedVisibility(
visible = isExpanded && animatedScale > 0.5f,
enter = slideInHorizontally(
initialOffsetX = { it / 2 },
animationSpec = tween(200)
) + fadeIn(animationSpec = tween(200)),
exit = slideOutHorizontally(
targetOffsetX = { it / 2 },
animationSpec = tween(150)
) + fadeOut(animationSpec = tween(150))
) {
Surface(
modifier = Modifier.padding(end = 16.dp),
shape = MaterialTheme.shapes.small,
color = MaterialTheme.colorScheme.inverseSurface,
tonalElevation = 6.dp
) {
Text(
text = stringResource(menuItem.labelRes),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.inverseOnSurface
)
}
}
// 子按钮
SmallFloatingActionButton(
onClick = {
menuItem.onClick()
isExpanded = false
},
modifier = Modifier.size(smallButtonSize),
containerColor = if (menuItem.color != Color.Unspecified) {
menuItem.color
} else {
MaterialTheme.colorScheme.secondary
},
contentColor = if (menuItem.color != Color.Unspecified) {
if (menuItem.color == Color.Gray) Color.White
else MaterialTheme.colorScheme.onSecondary
} else {
MaterialTheme.colorScheme.onSecondary
},
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 4.dp,
pressedElevation = 6.dp
)
) {
Icon(
imageVector = menuItem.icon,
contentDescription = stringResource(menuItem.labelRes),
modifier = Modifier.size(20.dp)
)
}
}
}
// 主按钮
FloatingActionButton(
onClick = {
onMainButtonClick?.invoke()
isExpanded = !isExpanded
},
modifier = Modifier
.size(buttonSize)
.scale(mainButtonScale),
elevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 6.dp,
pressedElevation = 8.dp,
hoveredElevation = 8.dp
)
) {
Icon(
imageVector = if (isExpanded) mainButtonExpandedIcon else mainButtonIcon,
contentDescription = stringResource(
if (isExpanded) R.string.collapse_menu else R.string.expand_menu
),
modifier = Modifier
.size(24.dp)
.rotate(if (mainButtonIcon == Icons.Filled.Add) rotationAngle else 0f)
)
}
}
}
// 预设菜单项
object FabMenuPresets {
fun getScrollMenuItems(
onScrollToTop: () -> Unit,
onScrollToBottom: () -> Unit
) = listOf(
FabMenuItem(
icon = Icons.Filled.KeyboardArrowUp,
labelRes = R.string.scroll_to_top,
onClick = onScrollToTop
),
FabMenuItem(
icon = Icons.Filled.KeyboardArrowDown,
labelRes = R.string.scroll_to_bottom,
onClick = onScrollToBottom
)
)
@Composable
fun getBatchActionMenuItems(
onCancel: () -> Unit,
onDeny: () -> Unit,
onAllow: () -> Unit,
onUnmountModules: () -> Unit,
onDisableUnmount: () -> Unit
) = listOf(
FabMenuItem(
icon = Icons.Filled.Close,
labelRes = R.string.cancel,
color = Color.Gray,
onClick = onCancel
),
FabMenuItem(
icon = Icons.Filled.Block,
labelRes = R.string.deny_authorization,
color = MaterialTheme.colorScheme.error,
onClick = onDeny
),
FabMenuItem(
icon = Icons.Filled.Check,
labelRes = R.string.grant_authorization,
color = MaterialTheme.colorScheme.primary,
onClick = onAllow
),
FabMenuItem(
icon = Icons.Filled.FolderOff,
labelRes = R.string.unmount_modules,
onClick = onUnmountModules
),
FabMenuItem(
icon = Icons.Filled.Folder,
labelRes = R.string.disable_unmount,
onClick = onDisableUnmount
)
)
}

View File

@@ -61,7 +61,6 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.MainActivity
import com.sukisu.ultra.ui.component.ImageEditorDialog
import com.sukisu.ultra.ui.component.KsuIsValid
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation

View File

@@ -304,9 +304,6 @@
<string name="tools">工具</string>
<!-- String resources used in SuperUser -->
<string name="clear">清除</string>
<string name="apps_with_root">Root 权限应用</string>
<string name="apps_with_custom_profile">自定义配置应用</string>
<string name="other_apps">默认配置应用</string>
<string name="no_apps_found">未找到应用</string>
<string name="selinux_enabled_toast">SELinux 已设置为启用状态</string>
<string name="selinux_disabled_toast">SELinux 已设置为禁用状态</string>
@@ -353,7 +350,39 @@
<string name="language_follow_system">跟随系统</string>
<string name="language_changed">语言已更改,重启应用以应用更改</string>
<string name="settings_card_dim">卡片暗度调节</string>
<!-- Super User Related -->
<!-- Flash related -->
<string name="error_code">错误代码</string>
<string name="check_log">请查看日志</string>
<string name="installing_module">正在安装模块 %1$d/%2$d</string>
<string name="module_failed_count">%d 个模块安装失败</string>
<string name="module_download_error">模块下载失败</string>
<string name="kernel_flashing">内核刷写</string>
<!-- 分类相关 -->
<string name="category_all_apps">全部</string>
<string name="category_root_apps">Root</string>
<string name="category_custom_apps">自定义</string>
<string name="category_default_apps">默认</string>
<!-- 排序相关 -->
<string name="sort_name_asc">名称升序</string>
<string name="sort_name_desc">名称降序</string>
<string name="sort_install_time_new">安装时间(新)</string>
<string name="sort_install_time_old">安装时间(旧)</string>
<string name="sort_size_desc">大小降序</string>
<string name="sort_size_asc">大小升序</string>
<string name="sort_usage_freq">使用频率</string>
<!-- 状态相关 -->
<string name="no_apps_in_category">此分类中没有应用</string>
<!-- 标签相关 -->
<string name="label_root">Root</string>
<string name="label_unmount">卸载</string>
<string name="label_custom">自定义</string>
<!-- FAB菜单相关 -->
<string name="deny_authorization">取消授权</string>
<string name="grant_authorization">授权</string>
<string name="unmount_modules">卸载模块挂载</string>
<string name="disable_unmount">禁用卸载模块挂载</string>
<string name="expand_menu">展开菜单</string>
<string name="collapse_menu">收起菜单</string>
<string name="scroll_to_top">顶部</string>
<string name="scroll_to_bottom">底部</string>
<string name="scroll_to_top_description">滚动到顶部</string>
@@ -363,11 +392,7 @@
<string name="selected">已选择</string>
<string name="select">选择</string>
<string name="profile_umount_modules_disable">禁用自定义卸载模块</string>
<!-- Flash related -->
<string name="error_code">错误代码</string>
<string name="check_log">请查看日志</string>
<string name="installing_module">正在安装模块 %1$d/%2$d</string>
<string name="module_failed_count">%d 个模块安装失败</string>
<string name="module_download_error">模块下载失败</string>
<string name="kernel_flashing">内核刷写</string>
<!-- BottomSheet相关 -->
<string name="menu_options">菜单选项</string>
<string name="sort_options">排序方式</string>
</resources>

View File

@@ -306,9 +306,6 @@
<string name="tools">Tools</string>
<!-- String resources used in SuperUser -->
<string name="clear">Removals</string>
<string name="apps_with_root">Applications with root privileges</string>
<string name="apps_with_custom_profile">Applications with customized configurations</string>
<string name="other_apps">Applications with unchanged defaults</string>
<string name="no_apps_found">Application not found</string>
<string name="selinux_enabled_toast">SELinux Enabled</string>
<string name="selinux_disabled_toast">SELinux Disabled</string>
@@ -355,7 +352,39 @@
<string name="language_follow_system">Follow System</string>
<string name="language_changed">Language changed, restarting to apply changes</string>
<string name="settings_card_dim">Card Darkness Adjustment</string>
<!-- Super User Related -->
<!-- Flash related -->
<string name="error_code">error code</string>
<string name="check_log">Please check the log</string>
<string name="installing_module">Module being installed %1$d/%2$d</string>
<string name="module_failed_count">%d Failed to install a new module</string>
<string name="module_download_error">Module download failed</string>
<string name="kernel_flashing">Kernel Flashing</string>
<!-- 分类相关 -->
<string name="category_all_apps">All</string>
<string name="category_root_apps">Root</string>
<string name="category_custom_apps">Custom</string>
<string name="category_default_apps">Default</string>
<!-- 排序相关 -->
<string name="sort_name_asc">Ascending order of name</string>
<string name="sort_name_desc">Name descending</string>
<string name="sort_install_time_new">Installation time (new)</string>
<string name="sort_install_time_old">Installation time (old)</string>
<string name="sort_size_desc">descending order of size</string>
<string name="sort_size_asc">ascending order of size</string>
<string name="sort_usage_freq">frequency of use</string>
<!-- 状态相关 -->
<string name="no_apps_in_category">No application in this category</string>
<!-- 标签相关 -->
<string name="label_root">Root</string>
<string name="label_unmount">Unmount</string>
<string name="label_custom">Custom</string>
<!-- FAB菜单相关 -->
<string name="deny_authorization">Delegation of authority</string>
<string name="grant_authorization">Authorizations</string>
<string name="unmount_modules">Unmounting Module Mounts</string>
<string name="disable_unmount">Disable uninstall module mounting</string>
<string name="expand_menu">Expand menu</string>
<string name="collapse_menu">Put away the menu</string>
<string name="scroll_to_top">Top</string>
<string name="scroll_to_bottom">Bottom</string>
<string name="scroll_to_top_description">Scroll to top</string>
@@ -365,11 +394,7 @@
<string name="selected">Selected</string>
<string name="select">option</string>
<string name="profile_umount_modules_disable">Disable custom uninstallation module</string>
<!-- Flash related -->
<string name="error_code">error code</string>
<string name="check_log">Please check the log</string>
<string name="installing_module">Module being installed %1$d/%2$d</string>
<string name="module_failed_count">%d Failed to install a new module</string>
<string name="module_download_error">Module download failed</string>
<string name="kernel_flashing">Kernel Flashing</string>
<!-- BottomSheet相关 -->
<string name="menu_options">Menu Options</string>
<string name="sort_options">Sort by</string>
</resources>