manager: Add the function of hiding module label rows, optimize the module lagging problem

This commit is contained in:
ShirkNeko
2025-06-15 21:50:09 +08:00
parent e1bd16d94f
commit a0a9fb01f4
5 changed files with 268 additions and 81 deletions

View File

@@ -112,6 +112,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val confirmDialog = rememberConfirmDialog() val confirmDialog = rememberConfirmDialog()
var lastClickTime by remember { mutableStateOf(0L) } var lastClickTime by remember { mutableStateOf(0L) }
// 初始化缓存系统
LaunchedEffect(Unit) {
viewModel.initializeCache(context)
}
// BottomSheet状态 // BottomSheet状态
val bottomSheetState = rememberModalBottomSheetState( val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true skipPartiallyExpanded = true
@@ -742,6 +747,7 @@ private fun ModuleList(
if (success) { if (success) {
viewModel.fetchModuleList() viewModel.fetchModuleList()
viewModel.markNeedRefresh()
} }
if (!isUninstall) return if (!isUninstall) return
val message = if (success) { val message = if (success) {
@@ -889,6 +895,10 @@ fun ModuleItem(
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit, onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
onClick: (ModuleViewModel.ModuleInfo) -> Unit onClick: (ModuleViewModel.ModuleInfo) -> Unit
) { ) {
val context = LocalContext.current
val prefs = context.getSharedPreferences("settings", MODE_PRIVATE)
val isHideTagRow = prefs.getBoolean("is_hide_tag_row", false)
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh), colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
elevation = getCardElevation(), elevation = getCardElevation(),
@@ -897,8 +907,10 @@ fun ModuleItem(
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val indication = LocalIndication.current val indication = LocalIndication.current
val viewModel = viewModel<ModuleViewModel>() val viewModel = viewModel<ModuleViewModel>()
val moduleSize by remember(module.dirId) {
mutableStateOf(viewModel.getModuleSize(module.dirId)) // 使用缓存系统获取模块大小
val sizeStr = remember(module.dirId) {
viewModel.getModuleSize(module.dirId)
} }
Column( Column(
@@ -987,45 +999,47 @@ fun ModuleItem(
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
// 标签行 // 标签行
Row( if (!isHideTagRow) {
verticalAlignment = Alignment.CenterVertically, Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth() horizontalArrangement = Arrangement.spacedBy(8.dp),
) { modifier = Modifier.fillMaxWidth()
// 文件夹名称标签
Surface(
shape = RoundedCornerShape(4.dp),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
) { ) {
Text( // 文件夹名称标签
text = module.dirId, Surface(
style = MaterialTheme.typography.labelSmall, shape = RoundedCornerShape(4.dp),
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp), color = MaterialTheme.colorScheme.primary,
color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier
maxLines = 1, ) {
overflow = TextOverflow.Ellipsis Text(
) text = module.dirId,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp),
color = MaterialTheme.colorScheme.onPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
// 大小标签
Surface(
shape = RoundedCornerShape(4.dp),
color = MaterialTheme.colorScheme.secondaryContainer,
modifier = Modifier
) {
Text(
text = sizeStr,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp),
color = MaterialTheme.colorScheme.onSecondaryContainer,
maxLines = 1
)
}
} }
// 大小标签 Spacer(modifier = Modifier.height(16.dp))
Surface(
shape = RoundedCornerShape(4.dp),
color = MaterialTheme.colorScheme.secondaryContainer,
modifier = Modifier
) {
Text(
text = moduleSize,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp),
color = MaterialTheme.colorScheme.onSecondaryContainer,
maxLines = 1
)
}
} }
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(thickness = Dp.Hairline) HorizontalDivider(thickness = Dp.Hairline)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -1060,7 +1074,7 @@ fun ModuleItem(
interactionSource = interactionSource, interactionSource = interactionSource,
contentPadding = ButtonDefaults.TextButtonContentPadding, contentPadding = ButtonDefaults.TextButtonContentPadding,
) { ) {
Icon( Icon(
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
imageVector = Icons.AutoMirrored.Outlined.Wysiwyg, imageVector = Icons.AutoMirrored.Outlined.Wysiwyg,

View File

@@ -1,5 +1,6 @@
package com.sukisu.ultra.ui.viewmodel package com.sukisu.ultra.ui.viewmodel
import android.content.Context
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@@ -18,7 +19,6 @@ import kotlinx.coroutines.withContext
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.text.Collator import java.text.Collator
import java.text.DecimalFormat import java.text.DecimalFormat
@@ -26,6 +26,7 @@ import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
import androidx.core.content.edit
/** /**
* @author ShirkNeko * @author ShirkNeko
@@ -39,11 +40,38 @@ class ModuleViewModel : ViewModel() {
private const val CUSTOM_USER_AGENT = "SukiSU-Ultra/2.0" private const val CUSTOM_USER_AGENT = "SukiSU-Ultra/2.0"
} }
// 模块大小缓存管理器
private lateinit var moduleSizeCache: ModuleSizeCache
fun initializeCache(context: Context) {
if (!::moduleSizeCache.isInitialized) {
moduleSizeCache = ModuleSizeCache(context)
}
}
fun getModuleSize(dirId: String): String { fun getModuleSize(dirId: String): String {
val size = getModuleFolderSize(dirId) if (!::moduleSizeCache.isInitialized) {
return "0 KB"
}
val size = moduleSizeCache.getModuleSize(dirId)
return formatFileSize(size) return formatFileSize(size)
} }
/**
* 刷新所有模块的大小缓存
* 只在安装、卸载、更新模块后调用
*/
fun refreshModuleSizeCache() {
if (!::moduleSizeCache.isInitialized) return
viewModelScope.launch(Dispatchers.IO) {
Log.d(TAG, "开始刷新模块大小缓存")
val currentModules = modules.map { it.dirId }
moduleSizeCache.refreshCache(currentModules)
Log.d(TAG, "模块大小缓存刷新完成")
}
}
class ModuleInfo( class ModuleInfo(
val id: String, val id: String,
val name: String, val name: String,
@@ -86,6 +114,8 @@ class ModuleViewModel : ViewModel() {
fun markNeedRefresh() { fun markNeedRefresh() {
isNeedRefresh = true isNeedRefresh = true
// 标记需要刷新时,同时刷新大小缓存
refreshModuleSizeCache()
} }
fun fetchModuleList() { fun fetchModuleList() {
@@ -155,6 +185,13 @@ class ModuleViewModel : ViewModel() {
} }
} }
} }
// 首次加载模块列表时,初始化缓存
if (::moduleSizeCache.isInitialized) {
val currentModules = modules.map { it.dirId }
moduleSizeCache.initializeCacheIfNeeded(currentModules)
}
isNeedRefresh = false isNeedRefresh = false
}.onFailure { e -> }.onFailure { e ->
Log.e(TAG, "fetchModuleList: ", e) Log.e(TAG, "fetchModuleList: ", e)
@@ -233,6 +270,160 @@ class ModuleViewModel : ViewModel() {
} }
} }
/**
* 模块大小缓存管理器
*/
class ModuleSizeCache(context: Context) {
companion object {
private const val TAG = "ModuleSizeCache"
private const val CACHE_PREFS_NAME = "module_size_cache"
private const val CACHE_VERSION_KEY = "cache_version"
private const val CACHE_INITIALIZED_KEY = "cache_initialized"
private const val CURRENT_CACHE_VERSION = 1
}
private val cachePrefs = context.getSharedPreferences(CACHE_PREFS_NAME, Context.MODE_PRIVATE)
private val sizeCache = mutableMapOf<String, Long>()
init {
loadCacheFromPrefs()
}
/**
* 从SharedPreferences加载缓存
*/
private fun loadCacheFromPrefs() {
try {
val cacheVersion = cachePrefs.getInt(CACHE_VERSION_KEY, 0)
if (cacheVersion != CURRENT_CACHE_VERSION) {
Log.d(TAG, "缓存版本不匹配,清空缓存")
clearCache()
return
}
val allEntries = cachePrefs.all
for ((key, value) in allEntries) {
if (key != CACHE_VERSION_KEY && key != CACHE_INITIALIZED_KEY && value is Long) {
sizeCache[key] = value
}
}
Log.d(TAG, "从缓存加载了 ${sizeCache.size} 个模块大小数据")
} catch (e: Exception) {
Log.e(TAG, "加载缓存失败", e)
clearCache()
}
}
/**
* 保存缓存到SharedPreferences
*/
private fun saveCacheToPrefs() {
try {
cachePrefs.edit {
putInt(CACHE_VERSION_KEY, CURRENT_CACHE_VERSION)
putBoolean(CACHE_INITIALIZED_KEY, true)
for ((dirId, size) in sizeCache) {
putLong(dirId, size)
}
}
Log.d(TAG, "保存了 ${sizeCache.size} 个模块大小到缓存")
} catch (e: Exception) {
Log.e(TAG, "保存缓存失败", e)
}
}
/**
* 获取模块大小(从缓存)
*/
fun getModuleSize(dirId: String): Long {
return sizeCache[dirId] ?: 0L
}
/**
* 检查缓存是否已初始化,如果没有则初始化
*/
fun initializeCacheIfNeeded(currentModules: List<String>) {
val isInitialized = cachePrefs.getBoolean(CACHE_INITIALIZED_KEY, false)
if (!isInitialized || sizeCache.isEmpty()) {
Log.d(TAG, "首次初始化缓存,计算所有模块大小")
refreshCache(currentModules)
} else {
// 检查是否有新模块需要计算大小
val newModules = currentModules.filter { !sizeCache.containsKey(it) }
if (newModules.isNotEmpty()) {
Log.d(TAG, "发现 ${newModules.size} 个新模块,计算大小: $newModules")
for (dirId in newModules) {
val size = calculateModuleFolderSize(dirId)
sizeCache[dirId] = size
Log.d(TAG, "新模块 $dirId 大小: ${formatFileSize(size)}")
}
saveCacheToPrefs()
}
}
}
/**
* 刷新所有模块的大小缓存
*/
fun refreshCache(currentModules: List<String>) {
try {
// 清理不存在的模块缓存
val toRemove = sizeCache.keys.filter { it !in currentModules }
toRemove.forEach { sizeCache.remove(it) }
if (toRemove.isNotEmpty()) {
Log.d(TAG, "清理了 ${toRemove.size} 个不存在的模块缓存: $toRemove")
}
// 计算所有当前模块的大小
for (dirId in currentModules) {
val size = calculateModuleFolderSize(dirId)
sizeCache[dirId] = size
Log.d(TAG, "更新模块 $dirId 大小: ${formatFileSize(size)}")
}
// 保存到持久化存储
saveCacheToPrefs()
} catch (e: Exception) {
Log.e(TAG, "刷新缓存失败", e)
}
}
/**
* 清空所有缓存
*/
private fun clearCache() {
sizeCache.clear()
cachePrefs.edit { clear() }
Log.d(TAG, "清空所有缓存")
}
/**
* 实际计算模块文件夹大小
*/
private fun calculateModuleFolderSize(dirId: String): Long {
return try {
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "du -sb /data/adb/modules/$dirId"))
val reader = BufferedReader(InputStreamReader(process.inputStream))
val output = reader.readLine()
process.waitFor()
reader.close()
if (output != null) {
val sizeStr = output.split("\t").firstOrNull()
sizeStr?.toLongOrNull() ?: 0L
} else {
0L
}
} catch (e: Exception) {
Log.e(TAG, "计算模块大小失败 $dirId: ${e.message}")
0L
}
}
}
/** /**
* 格式化文件大小的工具函数 * 格式化文件大小的工具函数
*/ */
@@ -246,45 +437,3 @@ fun formatFileSize(bytes: Long): String {
bytes / 1024.0.pow(digitGroups.toDouble()) bytes / 1024.0.pow(digitGroups.toDouble())
) + " " + units[digitGroups] ) + " " + units[digitGroups]
} }
/**
* 使用 su 权限调用 du 命令获取模块文件夹大小
*/
fun getModuleFolderSize(dirId: String): Long {
return try {
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "du -sb /data/adb/modules/$dirId"))
val reader = BufferedReader(InputStreamReader(process.inputStream))
val output = reader.readLine()
process.waitFor()
reader.close()
if (output != null) {
val sizeStr = output.split("\t").firstOrNull()
sizeStr?.toLongOrNull() ?: 0L
} else {
0L
}
} catch (e: Exception) {
Log.e("ModuleItem", "Error calculating module size with su for $dirId: ${e.message}")
0L
}
}
/**
* 递归计算目录大小(备用)
*/
private fun calculateDirectorySize(directory: File): Long {
var size = 0L
try {
directory.listFiles()?.forEach { file ->
size += if (file.isDirectory) {
calculateDirectorySize(file)
} else {
file.length()
}
}
} catch (e: Exception) {
Log.e("ModuleItem", "Error calculating directory size: ${e.message}")
}
return size
}

View File

@@ -233,6 +233,11 @@ fun MoreSettingsScreen(
mutableStateOf(prefs.getBoolean("is_hide_link_card", false)) mutableStateOf(prefs.getBoolean("is_hide_link_card", false))
} }
// 隐藏标签行开关状态
var isHideTagRow by remember {
mutableStateOf(prefs.getBoolean("is_hide_tag_row", false))
}
// SELinux状态 // SELinux状态
var selinuxEnabled by remember { var selinuxEnabled by remember {
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing") mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
@@ -315,6 +320,12 @@ fun MoreSettingsScreen(
isHideLinkCard = newValue isHideLinkCard = newValue
} }
// 隐藏标签行开关状态
val onHideTagRowChange = { newValue: Boolean ->
prefs.edit { putBoolean("is_hide_tag_row", newValue) }
isHideTagRow = newValue
}
// 备用图标开关状态 // 备用图标开关状态
val onUseAltIconChange = { newValue: Boolean -> val onUseAltIconChange = { newValue: Boolean ->
prefs.edit { putBoolean("use_alt_icon", newValue) } prefs.edit { putBoolean("use_alt_icon", newValue) }
@@ -1080,6 +1091,15 @@ fun MoreSettingsScreen(
checked = isHideLinkCard, checked = isHideLinkCard,
onChange = onHideLinkCardChange onChange = onHideLinkCardChange
) )
// 隐藏标签行
SwitchSettingItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(R.string.hide_tag_card),
summary = stringResource(R.string.hide_tag_card_summary),
checked = isHideTagRow,
onChange = onHideTagRowChange
)
} }
KsuIsValid { KsuIsValid {
// 高级设置 // 高级设置

View File

@@ -195,6 +195,8 @@
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string> <string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
<string name="hide_link_card">隐藏链接卡片</string> <string name="hide_link_card">隐藏链接卡片</string>
<string name="hide_link_card_summary">隐藏主页上的链接卡片信息</string> <string name="hide_link_card_summary">隐藏主页上的链接卡片信息</string>
<string name="hide_tag_card">隐藏模块标签行</string>
<string name="hide_tag_card_summary">隐藏模块卡片中的文件夹名称和大小标签</string>
<string name="theme_mode">主题模式</string> <string name="theme_mode">主题模式</string>
<string name="theme_follow_system">跟随系统</string> <string name="theme_follow_system">跟随系统</string>
<string name="theme_light">浅色</string> <string name="theme_light">浅色</string>

View File

@@ -197,6 +197,8 @@
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string> <string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
<string name="hide_link_card">Hide Link Card Status</string> <string name="hide_link_card">Hide Link Card Status</string>
<string name="hide_link_card_summary">Hide link card information on the home page</string> <string name="hide_link_card_summary">Hide link card information on the home page</string>
<string name="hide_tag_card">Hide module label rows</string>
<string name="hide_tag_card_summary">Hide folder name and size labels in module cards</string>
<string name="theme_mode">Theme</string> <string name="theme_mode">Theme</string>
<string name="theme_follow_system">Follow system</string> <string name="theme_follow_system">Follow system</string>
<string name="theme_light">Light</string> <string name="theme_light">Light</string>