manager: make group shows like upstream & add some animation to SuperUser menu i18n file pick from upstream

Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com>
This commit is contained in:
AlexLiuDev233
2025-11-16 02:55:20 +08:00
committed by ShirkNeko
parent 98e617f1bd
commit 3a2d55237d
2 changed files with 113 additions and 47 deletions

View File

@@ -1,14 +1,46 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.animation.* import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.* import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.foundation.background import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
@@ -20,10 +52,46 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.* import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.RestoreFromTrash
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.SearchOff
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SheetState
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.* import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.rememberTopAppBarState
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
@@ -87,9 +155,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
LaunchedEffect(navigator) { LaunchedEffect(navigator) {
viewModel.search = "" viewModel.search = ""
if (viewModel.appList.isEmpty()) {
// viewModel.fetchAppList()
}
} }
LaunchedEffect(viewModel.selectedApps, viewModel.showBatchActions) { LaunchedEffect(viewModel.selectedApps, viewModel.showBatchActions) {
@@ -344,18 +409,20 @@ private fun SuperUserContent(
appGroup.packageNames.forEach { viewModel.toggleAppSelection(it) } appGroup.packageNames.forEach { viewModel.toggleAppSelection(it) }
} }
}, },
viewModel = viewModel, viewModel = viewModel
navigator = navigator,
isExpanded = expandedGroups.value.contains(appGroup.uid)
) )
} }
if (expandedGroups.value.contains(appGroup.uid) && appGroup.apps.size > 1) { items(appGroup.apps, key = { it.packageName }) { app ->
items(appGroup.apps.drop(1), key = { it.packageName }) { app -> AnimatedVisibility(
visible = expandedGroups.value.contains(appGroup.uid) && appGroup.apps.size > 1,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
ListItem( ListItem(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.2f)) .padding(start = 10.dp)
.clickable { .clickable {
navigator.navigate(AppProfileScreenDestination(app)) navigator.navigate(AppProfileScreenDestination(app))
}, },
@@ -786,9 +853,7 @@ private fun AppGroupItem(
onToggleSelection: () -> Unit, onToggleSelection: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
viewModel: SuperUserViewModel, viewModel: SuperUserViewModel
navigator: DestinationsNavigator,
isExpanded: Boolean = false
) { ) {
val mainApp = appGroup.mainApp val mainApp = appGroup.mainApp
@@ -800,37 +865,16 @@ private fun AppGroupItem(
) )
}, },
headlineContent = { headlineContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(mainApp.label) Text(mainApp.label)
if (appGroup.apps.size > 1) {
Spacer(modifier = Modifier.width(8.dp))
Surface(
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.secondaryContainer,
) {
Text(
text = "${appGroup.apps.size} apps",
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
Icon(
imageVector = if (isExpanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
}
}
}, },
supportingContent = { supportingContent = {
Column { Column {
Row(verticalAlignment = Alignment.CenterVertically) { val summaryText = if (appGroup.apps.size > 1) {
Text("UID: ${appGroup.uid}") stringResource(R.string.group_contains_apps, appGroup.apps.size)
} } else {
if (appGroup.apps.size == 1) { mainApp.packageName
Text(mainApp.packageName)
} }
Text(summaryText)
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -864,6 +908,17 @@ private fun AppGroupItem(
) )
) )
} }
if (appGroup.apps.size > 1) {
Natives.getUserName(appGroup.uid)?.let {
LabelItem(
text = it,
style = LabelItemDefaults.style.copy(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
)
}
}
} }
} }
}, },
@@ -878,7 +933,17 @@ private fun AppGroupItem(
) )
}, },
trailingContent = { trailingContent = {
if (viewModel.showBatchActions) { AnimatedVisibility(
visible = viewModel.showBatchActions,
enter = fadeIn(animationSpec = tween(200)) + scaleIn(
animationSpec = tween(200),
initialScale = 0.6f
),
exit = fadeOut(animationSpec = tween(200)) + scaleOut(
animationSpec = tween(200),
targetScale = 0.6f
)
) {
val checkboxInteractionSource = remember { MutableInteractionSource() } val checkboxInteractionSource = remember { MutableInteractionSource() }
val isCheckboxPressed by checkboxInteractionSource.collectIsPressedAsState() val isCheckboxPressed by checkboxInteractionSource.collectIsPressedAsState()

View File

@@ -750,4 +750,5 @@
<string name="clear_custom_paths">清除自定义</string> <string name="clear_custom_paths">清除自定义</string>
<string name="apply_config">应用配置</string> <string name="apply_config">应用配置</string>
<string name="config_applied">配置已应用到内核</string> <string name="config_applied">配置已应用到内核</string>
<string name="group_contains_apps">包含 %1$d 个应用</string>
</resources> </resources>