manager: increase appGroup performance & add appGroup status notice

This commit is contained in:
AlexLiuDev233
2025-11-16 22:59:43 +08:00
committed by ShirkNeko
parent cbcaa07fd5
commit 0439a00f1d
2 changed files with 86 additions and 37 deletions

View File

@@ -6,6 +6,7 @@ import androidx.compose.animation.core.*
import androidx.compose.animation.expandHorizontally import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.expandVertically import androidx.compose.animation.expandVertically
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.Image
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
@@ -31,6 +32,7 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -38,12 +40,15 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue 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.rotate
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -51,6 +56,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest import coil.request.ImageRequest
import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItem
import com.dergoogler.mmrl.ui.component.LabelItemDefaults import com.dergoogler.mmrl.ui.component.LabelItemDefaults
@@ -314,6 +320,9 @@ private fun SuperUserContent(
scope: CoroutineScope scope: CoroutineScope
) { ) {
val expandedGroups = remember { mutableStateOf(setOf<Int>()) } val expandedGroups = remember { mutableStateOf(setOf<Int>()) }
val density = LocalDensity.current
val targetSizePx = remember(density) { with(density) { 36.dp.roundToPx() } }
val context = LocalContext.current
PullToRefreshBox( PullToRefreshBox(
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
@@ -329,6 +338,7 @@ private fun SuperUserContent(
filteredAndSortedAppGroups.forEachIndexed { _, appGroup -> filteredAndSortedAppGroups.forEachIndexed { _, appGroup ->
item(key = "${appGroup.uid}-${appGroup.mainApp.packageName}") { item(key = "${appGroup.uid}-${appGroup.mainApp.packageName}") {
AppGroupItem( AppGroupItem(
expandedGroups = expandedGroups,
appGroup = appGroup, appGroup = appGroup,
isSelected = appGroup.packageNames.any { viewModel.selectedApps.contains(it) }, isSelected = appGroup.packageNames.any { viewModel.selectedApps.contains(it) },
onToggleSelection = { onToggleSelection = {
@@ -357,32 +367,46 @@ private fun SuperUserContent(
) )
} }
if (appGroup.apps.size <= 1) return@forEachIndexed
items(appGroup.apps, key = { "${it.packageName}-${it.uid}" }) { app -> items(appGroup.apps, key = { "${it.packageName}-${it.uid}" }) { app ->
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(context)
.data(app.packageInfo)
.size(targetSizePx)
.crossfade(true)
.build()
)
val listItemContent = remember(app.packageName, appGroup.uid) {
@Composable {
ListItem(
modifier = Modifier
.clickable { navigator.navigate(AppProfileScreenDestination(app)) }
.fillMaxWidth()
.padding(start = 10.dp),
headlineContent = { Text(app.label, style = MaterialTheme.typography.bodyMedium) },
supportingContent = { Text(app.packageName, style = MaterialTheme.typography.bodySmall) },
leadingContent = {
Image(
painter = painter,
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.size(36.dp),
contentScale = ContentScale.Crop
)
}
)
}
}
AnimatedVisibility( AnimatedVisibility(
visible = expandedGroups.value.contains(appGroup.uid) && appGroup.apps.size > 1, visible = expandedGroups.value.contains(appGroup.uid),
enter = fadeIn() + expandVertically(), enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically() exit = fadeOut() + shrinkVertically()
) { ) {
ListItem( listItemContent()
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp)
.clickable {
navigator.navigate(AppProfileScreenDestination(app))
},
headlineContent = { Text(app.label, style = MaterialTheme.typography.bodyMedium) },
supportingContent = { Text(app.packageName, style = MaterialTheme.typography.bodySmall) },
leadingContent = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo)
.crossfade(true)
.build(),
contentDescription = app.label,
modifier = Modifier.padding(4.dp).width(36.dp).height(36.dp)
)
}
)
} }
} }
} }
@@ -797,7 +821,8 @@ private fun AppGroupItem(
onToggleSelection: () -> Unit, onToggleSelection: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
viewModel: SuperUserViewModel viewModel: SuperUserViewModel,
expandedGroups: MutableState<Set<Int>>
) { ) {
val mainApp = appGroup.mainApp val mainApp = appGroup.mainApp
@@ -818,9 +843,27 @@ private fun AppGroupItem(
} else { } else {
mainApp.packageName mainApp.packageName
} }
Text(summaryText)
Spacer(modifier = Modifier.height(4.dp)) Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(summaryText)
if (appGroup.apps.size > 1) {
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = null,
modifier = Modifier.rotate(
animateFloatAsState(
targetValue = if (expandedGroups.value.contains(appGroup.uid)) 180f else 0f,
animationSpec = tween(200, easing = LinearOutSlowInEasing),
label = ""
).value
)
)
}
}
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) { FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
if (appGroup.allowSu) { if (appGroup.allowSu) {
@@ -853,7 +896,7 @@ private fun AppGroupItem(
) )
} }
if (appGroup.apps.size > 1) { if (appGroup.apps.size > 1) {
Natives.getUserName(appGroup.uid)?.let { appGroup.userName?.let {
LabelItem( LabelItem(
text = it, text = it,
style = LabelItemDefaults.style.copy( style = LabelItemDefaults.style.copy(

View File

@@ -18,7 +18,6 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.parcelize.Parcelize
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
@@ -27,6 +26,8 @@ import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import com.sukisu.zako.IKsuInterface import com.sukisu.zako.IKsuInterface
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
enum class AppCategory(val displayNameRes: Int, val persistKey: String) { enum class AppCategory(val displayNameRes: Int, val persistKey: String) {
ALL(com.sukisu.ultra.R.string.category_all_apps, "ALL"), ALL(com.sukisu.ultra.R.string.category_all_apps, "ALL"),
@@ -77,31 +78,36 @@ class SuperUserViewModel : ViewModel() {
private const val BATCH_SIZE = 20 private const val BATCH_SIZE = 20
} }
@Immutable
@Parcelize @Parcelize
data class AppInfo( data class AppInfo(
val label: String, val label: String,
val packageInfo: PackageInfo, val packageInfo: PackageInfo,
val profile: Natives.Profile?, val profile: Natives.Profile?,
) : Parcelable { ) : Parcelable {
val packageName: String get() = packageInfo.packageName @IgnoredOnParcel
val uid: Int get() = packageInfo.applicationInfo!!.uid val packageName: String = packageInfo.packageName
@IgnoredOnParcel
val uid: Int = packageInfo.applicationInfo!!.uid
} }
@Immutable
@Parcelize @Parcelize
data class AppGroup( data class AppGroup(
val uid: Int, val uid: Int,
val apps: List<AppInfo>, val apps: List<AppInfo>,
val profile: Natives.Profile? val profile: Natives.Profile?
) : Parcelable { ) : Parcelable {
val mainApp: AppInfo get() = apps.first() @IgnoredOnParcel
val packageNames: List<String> get() = apps.map { it.packageName } val mainApp: AppInfo = apps.first()
val allowSu: Boolean get() = profile?.allowSu == true @IgnoredOnParcel
val packageNames: List<String> = apps.map { it.packageName }
val userName: String? get() = Natives.getUserName(uid) @IgnoredOnParcel
val hasCustomProfile: Boolean val allowSu: Boolean = profile?.allowSu == true
get() = profile?.let { @IgnoredOnParcel
if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault val userName: String? = Natives.getUserName(uid)
} ?: false @IgnoredOnParcel
val hasCustomProfile : Boolean = profile?.let { if (it.allowSu) !it.rootUseDefault else !it.nonRootUseDefault } ?: false
} }
private val appProcessingThreadPool = ThreadPoolExecutor( private val appProcessingThreadPool = ThreadPoolExecutor(