manager: Add the function of hiding module label rows, optimize the module lagging problem
This commit is contained in:
@@ -112,6 +112,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
// 初始化缓存系统
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.initializeCache(context)
|
||||
}
|
||||
|
||||
// BottomSheet状态
|
||||
val bottomSheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
@@ -742,6 +747,7 @@ private fun ModuleList(
|
||||
|
||||
if (success) {
|
||||
viewModel.fetchModuleList()
|
||||
viewModel.markNeedRefresh()
|
||||
}
|
||||
if (!isUninstall) return
|
||||
val message = if (success) {
|
||||
@@ -889,6 +895,10 @@ fun ModuleItem(
|
||||
onUpdate: (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(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
elevation = getCardElevation(),
|
||||
@@ -897,8 +907,10 @@ fun ModuleItem(
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val indication = LocalIndication.current
|
||||
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(
|
||||
@@ -987,45 +999,47 @@ fun ModuleItem(
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 标签行
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
// 文件夹名称标签
|
||||
Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
if (!isHideTagRow) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
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.primary,
|
||||
modifier = Modifier
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 大小标签
|
||||
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))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
HorizontalDivider(thickness = Dp.Hairline)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
@@ -1060,7 +1074,7 @@ fun ModuleItem(
|
||||
interactionSource = interactionSource,
|
||||
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
||||
|
||||
) {
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = Icons.AutoMirrored.Outlined.Wysiwyg,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -18,7 +19,6 @@ import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
import java.text.Collator
|
||||
import java.text.DecimalFormat
|
||||
@@ -26,6 +26,7 @@ import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* @author ShirkNeko
|
||||
@@ -39,11 +40,38 @@ class ModuleViewModel : ViewModel() {
|
||||
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 {
|
||||
val size = getModuleFolderSize(dirId)
|
||||
if (!::moduleSizeCache.isInitialized) {
|
||||
return "0 KB"
|
||||
}
|
||||
val size = moduleSizeCache.getModuleSize(dirId)
|
||||
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(
|
||||
val id: String,
|
||||
val name: String,
|
||||
@@ -86,6 +114,8 @@ class ModuleViewModel : ViewModel() {
|
||||
|
||||
fun markNeedRefresh() {
|
||||
isNeedRefresh = true
|
||||
// 标记需要刷新时,同时刷新大小缓存
|
||||
refreshModuleSizeCache()
|
||||
}
|
||||
|
||||
fun fetchModuleList() {
|
||||
@@ -155,6 +185,13 @@ class ModuleViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 首次加载模块列表时,初始化缓存
|
||||
if (::moduleSizeCache.isInitialized) {
|
||||
val currentModules = modules.map { it.dirId }
|
||||
moduleSizeCache.initializeCacheIfNeeded(currentModules)
|
||||
}
|
||||
|
||||
isNeedRefresh = false
|
||||
}.onFailure { 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小的工具函数
|
||||
*/
|
||||
@@ -245,46 +436,4 @@ fun formatFileSize(bytes: Long): String {
|
||||
return DecimalFormat("#,##0.#").format(
|
||||
bytes / 1024.0.pow(digitGroups.toDouble())
|
||||
) + " " + 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
|
||||
}
|
||||
@@ -233,6 +233,11 @@ fun MoreSettingsScreen(
|
||||
mutableStateOf(prefs.getBoolean("is_hide_link_card", false))
|
||||
}
|
||||
|
||||
// 隐藏标签行开关状态
|
||||
var isHideTagRow by remember {
|
||||
mutableStateOf(prefs.getBoolean("is_hide_tag_row", false))
|
||||
}
|
||||
|
||||
// SELinux状态
|
||||
var selinuxEnabled by remember {
|
||||
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
|
||||
@@ -315,6 +320,12 @@ fun MoreSettingsScreen(
|
||||
isHideLinkCard = newValue
|
||||
}
|
||||
|
||||
// 隐藏标签行开关状态
|
||||
val onHideTagRowChange = { newValue: Boolean ->
|
||||
prefs.edit { putBoolean("is_hide_tag_row", newValue) }
|
||||
isHideTagRow = newValue
|
||||
}
|
||||
|
||||
// 备用图标开关状态
|
||||
val onUseAltIconChange = { newValue: Boolean ->
|
||||
prefs.edit { putBoolean("use_alt_icon", newValue) }
|
||||
@@ -1080,6 +1091,15 @@ fun MoreSettingsScreen(
|
||||
checked = isHideLinkCard,
|
||||
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 {
|
||||
// 高级设置
|
||||
|
||||
@@ -195,6 +195,8 @@
|
||||
<string name="hide_susfs_status_summary">隐藏主页上的 SuSFS 状态信息</string>
|
||||
<string name="hide_link_card">隐藏链接卡片</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_follow_system">跟随系统</string>
|
||||
<string name="theme_light">浅色</string>
|
||||
|
||||
@@ -197,6 +197,8 @@
|
||||
<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_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_follow_system">Follow system</string>
|
||||
<string name="theme_light">Light</string>
|
||||
|
||||
Reference in New Issue
Block a user