Manager: fmt

- Optimized homepage refresh logic and removed the caching mechanism
This commit is contained in:
ShirkNeko
2025-09-29 17:17:19 +08:00
parent 9e7aabf3f7
commit c950705044
10 changed files with 429 additions and 558 deletions

View File

@@ -187,14 +187,6 @@ class MainActivity : ComponentActivity() {
} }
} }
lifecycleScope.launch {
try {
homeViewModel.initializeData()
} catch (e: Exception) {
e.printStackTrace()
}
}
// 数据刷新协程 // 数据刷新协程
DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope) DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope)
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow) DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
@@ -228,7 +220,6 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch { lifecycleScope.launch {
try { try {
superUserViewModel.fetchAppList() superUserViewModel.fetchAppList()
homeViewModel.initializeData()
DataRefreshUtils.refreshData(lifecycleScope) DataRefreshUtils.refreshData(lifecycleScope)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()

View File

@@ -464,7 +464,7 @@ fun AddTryUmountDialog(
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.menuAnchor(MenuAnchorType.PrimaryEditable, true), .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true),
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
ExposedDropdownMenu( ExposedDropdownMenu(

View File

@@ -394,7 +394,6 @@ fun TryUmountContent(
tryUmounts: Set<String>, tryUmounts: Set<String>,
isLoading: Boolean, isLoading: Boolean,
onAddUmount: () -> Unit, onAddUmount: () -> Unit,
onRunUmount: () -> Unit,
onRemoveUmount: (String) -> Unit, onRemoveUmount: (String) -> Unit,
onEditUmount: ((String) -> Unit)? = null, onEditUmount: ((String) -> Unit)? = null,
) { ) {

View File

@@ -43,7 +43,7 @@ fun TemplateConfig(
) { ) {
OutlinedTextField( OutlinedTextField(
modifier = Modifier modifier = Modifier
.menuAnchor(MenuAnchorType.PrimaryNotEditable) .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(), .fillMaxWidth(),
readOnly = true, readOnly = true,
label = { Text(stringResource(R.string.profile_template)) }, label = { Text(stringResource(R.string.profile_template)) },

View File

@@ -57,13 +57,14 @@ import com.sukisu.ultra.ui.util.module.LatestVersionInfo
import com.sukisu.ultra.ui.util.reboot import com.sukisu.ultra.ui.util.reboot
import com.sukisu.ultra.ui.viewmodel.HomeViewModel import com.sukisu.ultra.ui.viewmodel.HomeViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.random.Random import kotlin.random.Random
/** /**
* @author ShirkNeko * @author ShirkNeko
* @date 2025/5/31. * @date 2025/9/29.
*/ */
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Destination<RootGraph>(start = true) @Destination<RootGraph>(start = true)
@@ -74,15 +75,12 @@ fun HomeScreen(navigator: DestinationsNavigator) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
LaunchedEffect(key1 = navigator) { LaunchedEffect(key1 = navigator) {
coroutineScope.launch {
viewModel.refreshAllData(context)
}
}
LaunchedEffect(Unit) {
viewModel.loadUserSettings(context) viewModel.loadUserSettings(context)
viewModel.initializeData() coroutineScope.launch {
viewModel.checkForUpdates(context) viewModel.loadCoreData()
delay(100)
viewModel.loadExtendedData(context)
}
} }
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
@@ -92,27 +90,18 @@ fun HomeScreen(navigator: DestinationsNavigator) {
topBar = { topBar = {
TopBar( TopBar(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigator = navigator navigator = navigator,
isDataLoaded = viewModel.isCoreDataLoaded
) )
}, },
contentWindowInsets = WindowInsets.safeDrawing.only( contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal WindowInsetsSides.Top + WindowInsetsSides.Horizontal
) )
) { innerPadding -> ) { innerPadding ->
val pullRefreshState = rememberPullRefreshState(
refreshing = false,
onRefresh = {
coroutineScope.launch {
viewModel.refreshAllData(context)
}
}
)
Box( Box(
modifier = Modifier modifier = Modifier
.padding(innerPadding) .padding(innerPadding)
.fillMaxSize() .fillMaxSize()
.pullRefresh(pullRefreshState)
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -121,50 +110,69 @@ fun HomeScreen(navigator: DestinationsNavigator) {
.padding(top = 12.dp, start = 16.dp, end = 16.dp), .padding(top = 12.dp, start = 16.dp, end = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
StatusCard( // 状态卡片
systemStatus = viewModel.systemStatus, if (viewModel.isCoreDataLoaded) {
onClickInstall = { StatusCard(
navigator.navigate(InstallScreenDestination) systemStatus = viewModel.systemStatus,
} onClickInstall = {
) navigator.navigate(InstallScreenDestination)
}
)
if (viewModel.systemStatus.requireNewKernel) { // 警告信息
WarningCard( if (viewModel.systemStatus.requireNewKernel) {
stringResource(id = R.string.require_kernel_version).format( WarningCard(
Natives.getSimpleVersionFull(), stringResource(id = R.string.require_kernel_version).format(
Natives.MINIMAL_SUPPORTED_KERNEL_FULL Natives.getSimpleVersionFull(),
Natives.MINIMAL_SUPPORTED_KERNEL_FULL
)
) )
}
if (viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable) {
WarningCard(
stringResource(id = R.string.grant_root_failed)
)
}
}
// 更新检查
if (viewModel.isExtendedDataLoaded) {
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("check_update", true)
if (checkUpdate) {
UpdateCard()
}
// 信息卡片
InfoCard(
systemInfo = viewModel.systemInfo,
isSimpleMode = viewModel.isSimpleMode,
isHideSusfsStatus = viewModel.isHideSusfsStatus,
isHideZygiskImplement = viewModel.isHideZygiskImplement,
showKpmInfo = viewModel.showKpmInfo,
lkmMode = viewModel.systemStatus.lkmMode,
) )
}
if (viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable) { // 链接卡片
WarningCard( if (!viewModel.isSimpleMode && !viewModel.isHideLinkCard) {
stringResource(id = R.string.grant_root_failed)
)
}
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("check_update", true)
if (checkUpdate) {
UpdateCard()
}
InfoCard(
systemInfo = viewModel.systemInfo,
isSimpleMode = viewModel.isSimpleMode,
isHideSusfsStatus = viewModel.isHideSusfsStatus,
isHideZygiskImplement = viewModel.isHideZygiskImplement,
showKpmInfo = viewModel.showKpmInfo,
lkmMode = viewModel.systemStatus.lkmMode,
)
if (!viewModel.isSimpleMode) {
if (!viewModel.isHideLinkCard) {
ContributionCard() ContributionCard()
DonateCard() DonateCard()
LearnMoreCard() LearnMoreCard()
} }
} }
if (!viewModel.isExtendedDataLoaded) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
} }
} }
@@ -231,7 +239,8 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
@Composable @Composable
private fun TopBar( private fun TopBar(
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
navigator: DestinationsNavigator navigator: DestinationsNavigator,
isDataLoaded: Boolean = false
) { ) {
val context = LocalContext.current val context = LocalContext.current
val colorScheme = MaterialTheme.colorScheme val colorScheme = MaterialTheme.colorScheme
@@ -253,44 +262,46 @@ private fun TopBar(
scrolledContainerColor = cardColor.copy(alpha = cardAlpha) scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
), ),
actions = { actions = {
// SuSFS 配置按钮 if (isDataLoaded) {
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) { // SuSFS 配置按钮
IconButton(onClick = { if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
navigator.navigate(SuSFSConfigScreenDestination) IconButton(onClick = {
}) { navigator.navigate(SuSFSConfigScreenDestination)
Icon(
imageVector = Icons.Filled.Tune,
contentDescription = stringResource(R.string.susfs_config_setting_title)
)
}
}
// 重启按钮
var showDropdown by remember { mutableStateOf(false) }
KsuIsValid {
IconButton(onClick = {
showDropdown = true
}) {
Icon(
imageVector = Icons.Filled.PowerSettingsNew,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) { }) {
RebootDropdownItem(id = R.string.reboot) Icon(
imageVector = Icons.Filled.Tune,
contentDescription = stringResource(R.string.susfs_config_setting_title)
)
}
}
val pm = // 重启按钮
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager? var showDropdown by remember { mutableStateOf(false) }
@Suppress("DEPRECATION") KsuIsValid {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { IconButton(onClick = {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") showDropdown = true
}) {
Icon(
imageVector = Icons.Filled.PowerSettingsNew,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
RebootDropdownItem(id = R.string.reboot)
val pm =
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
}
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
} }
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
} }
} }
} }
@@ -741,7 +752,6 @@ private fun InfoCard(
} }
if (!isSimpleMode) { if (!isSimpleMode) {
// 根据showKpmInfo决定是否显示KPM信息
if (lkmMode != true && !showKpmInfo) { if (lkmMode != true && !showKpmInfo) {
val displayVersion = val displayVersion =
if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) { if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {

View File

@@ -84,7 +84,6 @@ import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
// 菜单项数据类
data class ModuleBottomSheetMenuItem( data class ModuleBottomSheetMenuItem(
val icon: ImageVector, val icon: ImageVector,
val titleRes: Int, val titleRes: Int,
@@ -93,7 +92,7 @@ data class ModuleBottomSheetMenuItem(
/** /**
* @author ShirkNeko * @author ShirkNeko
* @date 2025/5/31. * @date 2025/9/29.
*/ */
@SuppressLint("ResourceType", "AutoboxingStateCreation") @SuppressLint("ResourceType", "AutoboxingStateCreation")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -102,24 +101,21 @@ data class ModuleBottomSheetMenuItem(
fun ModuleScreen(navigator: DestinationsNavigator) { fun ModuleScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<ModuleViewModel>() val viewModel = viewModel<ModuleViewModel>()
val context = LocalContext.current val context = LocalContext.current
val prefs = context.getSharedPreferences("settings",MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", MODE_PRIVATE)
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val confirmDialog = rememberConfirmDialog() val confirmDialog = rememberConfirmDialog()
var lastClickTime by remember { mutableStateOf(0L) } var lastClickTime by remember { mutableStateOf(0L) }
// 签名验证弹窗状态
var showSignatureDialog by remember { mutableStateOf(false) } var showSignatureDialog by remember { mutableStateOf(false) }
var signatureDialogMessage by remember { mutableStateOf("") } var signatureDialogMessage by remember { mutableStateOf("") }
var isForceVerificationFailed by remember { mutableStateOf(false) } var isForceVerificationFailed by remember { mutableStateOf(false) }
var pendingInstallAction by remember { mutableStateOf<(() -> Unit)?>(null) } var pendingInstallAction by remember { mutableStateOf<(() -> Unit)?>(null) }
// 初始化缓存系统
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.initializeCache(context) viewModel.initializeCache(context)
} }
// BottomSheet状态
val bottomSheetState = rememberModalBottomSheetState( val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true skipPartiallyExpanded = true
) )
@@ -280,7 +276,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost) val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost) val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) { if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
viewModel.sortEnabledFirst = prefs.getBoolean("module_sort_enabled_first", false) viewModel.sortEnabledFirst = prefs.getBoolean("module_sort_enabled_first", false)
@@ -291,7 +286,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val isSafeMode = Natives.isSafeMode val isSafeMode = Natives.isSafeMode
val hasMagisk = hasMagisk() val hasMagisk = hasMagisk()
val hideInstallButton = isSafeMode || hasMagisk val hideInstallButton = isSafeMode || hasMagisk
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
@@ -300,7 +294,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { viewModel.fetchModuleList() } ) { viewModel.fetchModuleList() }
// BottomSheet菜单项
val bottomSheetMenuItems = remember { val bottomSheetMenuItems = remember {
listOf( listOf(
ModuleBottomSheetMenuItem( ModuleBottomSheetMenuItem(
@@ -478,7 +471,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
} }
} }
// BottomSheet
if (showBottomSheet) { if (showBottomSheet) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { onDismissRequest = {
@@ -620,7 +612,6 @@ private fun ModuleBottomSheetContent(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
) )
// 排序选项
Column( Column(
modifier = Modifier.padding(horizontal = 24.dp), modifier = Modifier.padding(horizontal = 24.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -682,7 +673,6 @@ private fun ModuleBottomSheetContent(
@Composable @Composable
private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) { private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) {
// 添加交互状态
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState() val isPressed by interactionSource.collectIsPressedAsState()
@@ -810,7 +800,6 @@ private fun ModuleList(
return return
} }
// changelog is not empty, show it and wait for confirm
val confirmResult = confirmDialog.awaitConfirm( val confirmResult = confirmDialog.awaitConfirm(
changelogText, changelogText,
content = changelog, content = changelog,
@@ -901,6 +890,7 @@ private fun ModuleList(
reboot() reboot()
} }
} }
PullToRefreshBox( PullToRefreshBox(
modifier = boxModifier, modifier = boxModifier,
onRefresh = { onRefresh = {
@@ -1003,7 +993,6 @@ private fun ModuleList(
} }
) )
// fix last item shadow incomplete in LazyColumn
Spacer(Modifier.height(1.dp)) Spacer(Modifier.height(1.dp))
} }
} }
@@ -1011,7 +1000,6 @@ private fun ModuleList(
} }
DownloadListener(context, onInstallModule) DownloadListener(context, onInstallModule)
} }
} }
@@ -1044,7 +1032,6 @@ fun ModuleItem(
val indication = LocalIndication.current val indication = LocalIndication.current
val viewModel = viewModel<ModuleViewModel>() val viewModel = viewModel<ModuleViewModel>()
// 使用缓存系统获取模块大小
val sizeStr = remember(module.dirId) { val sizeStr = remember(module.dirId) {
viewModel.getModuleSize(module.dirId) viewModel.getModuleSize(module.dirId)
} }
@@ -1152,10 +1139,8 @@ fun ModuleItem(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.combinedClickable( .combinedClickable(
onClick = { onClick = { },
},
onLongClick = { onLongClick = {
// 长按复制updateJson地址
val clipData = ClipData.newPlainText( val clipData = ClipData.newPlainText(
"Update JSON URL", "Update JSON URL",
module.updateJson module.updateJson
@@ -1163,7 +1148,6 @@ fun ModuleItem(
clipboardManager.setPrimaryClip(clipData) clipboardManager.setPrimaryClip(clipData)
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
// 显示复制成功的提示
Toast.makeText( Toast.makeText(
context, context,
context.getString(R.string.module_update_json_copied), context.getString(R.string.module_update_json_copied),
@@ -1202,8 +1186,8 @@ fun ModuleItem(
maxLines = 4, maxLines = 4,
textDecoration = textDecoration, textDecoration = textDecoration,
) )
if (!isHideTagRow) {
if (!isHideTagRow) {
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
// 文件夹名称和大小标签 // 文件夹名称和大小标签
Row( Row(
@@ -1276,8 +1260,7 @@ fun ModuleItem(
onClick = { onClick(module) }, onClick = { onClick(module) },
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

@@ -56,7 +56,7 @@ import java.time.format.DateTimeFormatter
/** /**
* @author ShirkNeko * @author ShirkNeko
* @date 2025/5/31. * @date 2025/9/29.
*/ */
private val SPACING_SMALL = 3.dp private val SPACING_SMALL = 3.dp
private val SPACING_MEDIUM = 8.dp private val SPACING_MEDIUM = 8.dp
@@ -77,7 +77,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
Scaffold( Scaffold(
// containerColor = MaterialTheme.colorScheme.surfaceBright,
topBar = { topBar = {
TopBar(scrollBehavior = scrollBehavior) TopBar(scrollBehavior = scrollBehavior)
}, },
@@ -792,7 +791,6 @@ enum class UninstallType(val title: Int, val message: Int, val icon: ImageVector
fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle { fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
return rememberCustomDialog { dismiss -> return rememberCustomDialog { dismiss ->
val options = listOf( val options = listOf(
// UninstallType.TEMPORARY,
UninstallType.PERMANENT, UninstallType.PERMANENT,
UninstallType.RESTORE_STOCK_IMAGE UninstallType.RESTORE_STOCK_IMAGE
) )

View File

@@ -118,7 +118,6 @@ fun SuSFSConfigScreen(
var showAddAppPathDialog by remember { mutableStateOf(false) } var showAddAppPathDialog by remember { mutableStateOf(false) }
var showAddMountDialog by remember { mutableStateOf(false) } var showAddMountDialog by remember { mutableStateOf(false) }
var showAddUmountDialog by remember { mutableStateOf(false) } var showAddUmountDialog by remember { mutableStateOf(false) }
var showRunUmountDialog by remember { mutableStateOf(false) }
var showAddKstatStaticallyDialog by remember { mutableStateOf(false) } var showAddKstatStaticallyDialog by remember { mutableStateOf(false) }
var showAddKstatDialog by remember { mutableStateOf(false) } var showAddKstatDialog by remember { mutableStateOf(false) }
@@ -1090,12 +1089,12 @@ fun SuSFSConfigScreen(
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
) { ) {
// 标签页 // 标签页
ScrollableTabRow( PrimaryScrollableTabRow(
selectedTabIndex = allTabs.indexOf(selectedTab), selectedTabIndex = allTabs.indexOf(selectedTab),
edgePadding = 0.dp,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.onSurface contentColor = MaterialTheme.colorScheme.onSurface,
edgePadding = 0.dp
) { ) {
allTabs.forEach { tab -> allTabs.forEach { tab ->
Tab( Tab(
@@ -1262,7 +1261,6 @@ fun SuSFSConfigScreen(
tryUmounts = tryUmounts, tryUmounts = tryUmounts,
isLoading = isLoading, isLoading = isLoading,
onAddUmount = { showAddUmountDialog = true }, onAddUmount = { showAddUmountDialog = true },
onRunUmount = { showRunUmountDialog = true },
onRemoveUmount = { umountEntry -> onRemoveUmount = { umountEntry ->
coroutineScope.launch { coroutineScope.launch {
isLoading = true isLoading = true
@@ -1467,7 +1465,7 @@ private fun BasicSettingsContent(
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = scriptLocationExpanded) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = scriptLocationExpanded) },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.menuAnchor(MenuAnchorType.PrimaryEditable, true), .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
enabled = !isLoading enabled = !isLoading
) )

View File

@@ -67,16 +67,12 @@ enum class AppPriority(val value: Int) {
DEFAULT(3) // 默认应用 DEFAULT(3) // 默认应用
} }
// 菜单项数据类
data class BottomSheetMenuItem( data class BottomSheetMenuItem(
val icon: ImageVector, val icon: ImageVector,
val titleRes: Int, val titleRes: Int,
val onClick: () -> Unit val onClick: () -> Unit
) )
/**
* 获取应用的优先级
*/
private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority { private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority {
return when { return when {
app.allowSu -> AppPriority.ROOT app.allowSu -> AppPriority.ROOT
@@ -85,9 +81,6 @@ private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority {
} }
} }
/**
* 获取多选模式的主按钮图标
*/
private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector { private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector {
return if (isExpanded) { return if (isExpanded) {
Icons.Filled.Close Icons.Filled.Close
@@ -96,9 +89,6 @@ private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector {
} }
} }
/**
* 获取单选模式的主按钮图标
*/
private fun getSingleSelectMainIcon(isExpanded: Boolean): ImageVector { private fun getSingleSelectMainIcon(isExpanded: Boolean): ImageVector {
return if (isExpanded) { return if (isExpanded) {
Icons.Filled.Close Icons.Filled.Close
@@ -122,17 +112,14 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current val context = LocalContext.current
val snackBarHostState = remember { SnackbarHostState() } val snackBarHostState = remember { SnackbarHostState() }
// 使用ViewModel中的状态这些状态现在都会从SharedPreferences中加载并自动保存
val selectedCategory = viewModel.selectedCategory val selectedCategory = viewModel.selectedCategory
val currentSortType = viewModel.currentSortType val currentSortType = viewModel.currentSortType
// BottomSheet状态
val bottomSheetState = rememberModalBottomSheetState( val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true skipPartiallyExpanded = true
) )
var showBottomSheet by remember { mutableStateOf(false) } var showBottomSheet by remember { mutableStateOf(false) }
// 添加备份和还原启动器
val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState) val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState)
val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState) val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState)
@@ -145,8 +132,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
LaunchedEffect(viewModel.search) { LaunchedEffect(viewModel.search) {
if (viewModel.search.isEmpty()) { if (viewModel.search.isEmpty()) {
// 取消自动滚动到顶部的行为 // Optional: scroll to top when clearing search
// listState.scrollToItem(0)
} }
} }
@@ -234,7 +220,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
apps apps
} }
// 计算应用数量
val appCounts = remember(viewModel.appList, viewModel.showSystemApps) { val appCounts = remember(viewModel.appList, viewModel.showSystemApps) {
mapOf( mapOf(
AppCategory.ALL to viewModel.appList.size, AppCategory.ALL to viewModel.appList.size,
@@ -244,7 +229,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
) )
} }
// BottomSheet菜单项
val bottomSheetMenuItems = remember(viewModel.showSystemApps) { val bottomSheetMenuItems = remember(viewModel.showSystemApps) {
listOf( listOf(
BottomSheetMenuItem( BottomSheetMenuItem(
@@ -299,7 +283,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
) )
} }
// 记录FAB展开状态用于图标动画
var isFabExpanded by remember { mutableStateOf(false) } var isFabExpanded by remember { mutableStateOf(false) }
Scaffold( Scaffold(
@@ -311,7 +294,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text(stringResource(R.string.superuser)) Text(stringResource(R.string.superuser))
// 显示当前分类和应用数量
if (selectedCategory != AppCategory.ALL) { if (selectedCategory != AppCategory.ALL) {
Surface( Surface(
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
@@ -413,7 +396,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
buttonSpacing = 72.dp, buttonSpacing = 72.dp,
animationDurationMs = 300, animationDurationMs = 300,
staggerDelayMs = 50, staggerDelayMs = 50,
// 根据模式选择不同的图标
mainButtonIcon = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) { mainButtonIcon = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
getMultiSelectMainIcon(isFabExpanded) getMultiSelectMainIcon(isFabExpanded)
} else { } else {
@@ -458,7 +440,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
) )
} }
// 当没有应用显示时显示加载动画或空状态
if (filteredAndSortedApps.isEmpty()) { if (filteredAndSortedApps.isEmpty()) {
item { item {
Box( Box(
@@ -467,7 +448,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
.height(400.dp), .height(400.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// 根据加载状态显示不同内容
if ((viewModel.isRefreshing || viewModel.appList.isEmpty()) && viewModel.search.isEmpty()) { if ((viewModel.isRefreshing || viewModel.appList.isEmpty()) && viewModel.search.isEmpty()) {
LoadingAnimation( LoadingAnimation(
isLoading = true isLoading = true
@@ -484,7 +464,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
} }
} }
// BottomSheet
if (showBottomSheet) { if (showBottomSheet) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { onDismissRequest = {
@@ -594,7 +573,6 @@ private fun BottomSheetContent(
} }
} }
// 应用分类选项
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp)) HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp))
@@ -702,7 +680,6 @@ private fun CategoryChip(
} }
} }
// 应用数量
Text( Text(
text = "$appCount apps", text = "$appCount apps",
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
@@ -718,7 +695,6 @@ private fun CategoryChip(
@Composable @Composable
private fun BottomSheetMenuItemView(menuItem: BottomSheetMenuItem) { private fun BottomSheetMenuItemView(menuItem: BottomSheetMenuItem) {
// 添加交互状态
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState() val isPressed by interactionSource.collectIsPressedAsState()
@@ -897,9 +873,6 @@ fun LabelText(label: String) {
} }
} }
/**
* 加载动画组件
*/
@Composable @Composable
private fun LoadingAnimation( private fun LoadingAnimation(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -907,7 +880,6 @@ private fun LoadingAnimation(
) { ) {
val infiniteTransition = rememberInfiniteTransition(label = "loading") val infiniteTransition = rememberInfiniteTransition(label = "loading")
// 透明度动画
val alpha by infiniteTransition.animateFloat( val alpha by infiniteTransition.animateFloat(
initialValue = 0.3f, initialValue = 0.3f,
targetValue = 1f, targetValue = 1f,
@@ -928,7 +900,6 @@ private fun LoadingAnimation(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
// 进度指示器
LinearProgressIndicator( LinearProgressIndicator(
modifier = Modifier modifier = Modifier
.width(200.dp) .width(200.dp)
@@ -940,9 +911,6 @@ private fun LoadingAnimation(
} }
} }
/**
* 空状态组件
*/
@Composable @Composable
@SuppressLint("ModifierParameter") @SuppressLint("ModifierParameter")
private fun EmptyState( private fun EmptyState(

View File

@@ -4,16 +4,11 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.system.Os import android.system.Os
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.core.content.edit
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.dergoogler.mmrl.platform.Platform.Companion.context
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.sukisu.ultra.KernelVersion import com.sukisu.ultra.KernelVersion
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.getKernelVersion import com.sukisu.ultra.getKernelVersion
@@ -21,20 +16,12 @@ import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
import com.sukisu.ultra.ui.util.module.LatestVersionInfo import com.sukisu.ultra.ui.util.module.LatestVersionInfo
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
companion object {
private const val TAG = "HomeViewModel"
private const val PREFS_NAME = "home_cache"
private const val KEY_SYSTEM_STATUS = "system_status"
private const val KEY_SYSTEM_INFO = "system_info"
private const val KEY_VERSION_INFO = "version_info"
private const val KEY_LAST_UPDATE = "last_update_time"
private const val KEY_ERROR_COUNT = "error_count"
private const val MAX_ERROR_COUNT = 2
}
// 系统状态 // 系统状态
data class SystemStatus( data class SystemStatus(
@@ -69,9 +56,7 @@ class HomeViewModel : ViewModel() {
val zygiskImplement: String = "" val zygiskImplement: String = ""
) )
private val gson = Gson() // 状态变量
private val prefs by lazy { ksuApp.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) }
var systemStatus by mutableStateOf(SystemStatus()) var systemStatus by mutableStateOf(SystemStatus())
private set private set
@@ -98,199 +83,52 @@ class HomeViewModel : ViewModel() {
var showKpmInfo by mutableStateOf(false) var showKpmInfo by mutableStateOf(false)
private set private set
private fun clearAllCache() { var isCoreDataLoaded by mutableStateOf(false)
try { private set
prefs.edit { clear() } var isExtendedDataLoaded by mutableStateOf(false)
Log.i(TAG, "All cache cleared successfully") private set
} catch (e: Exception) { var isRefreshing by mutableStateOf(false)
Log.e(TAG, "Error clearing cache", e) private set
}
}
private fun resetToDefaults() { private var loadingJobs = mutableListOf<Job>()
systemStatus = SystemStatus()
systemInfo = SystemInfo()
latestVersionInfo = LatestVersionInfo()
isSimpleMode = false
isKernelSimpleMode = false
isHideVersion = false
isHideOtherInfo = false
isHideSusfsStatus = false
isHideZygiskImplement = false
isHideLinkCard = false
showKpmInfo = false
}
private fun handleError(error: Exception, operation: String) {
Log.e(TAG, "Error in $operation", error)
val errorCount = prefs.getInt(KEY_ERROR_COUNT, 0)
val newErrorCount = errorCount + 1
if (newErrorCount >= MAX_ERROR_COUNT) {
Log.w(TAG, "Too many errors ($newErrorCount), clearing cache and resetting")
clearAllCache()
resetToDefaults()
} else {
prefs.edit {
putInt(KEY_ERROR_COUNT, newErrorCount)
}
}
}
private fun String?.orSafe(default: String = ""): String {
return if (this.isNullOrBlank()) default else this
}
private fun <T, R> Pair<T?, R?>?.orSafe(default: Pair<T, R>): Pair<T, R> {
return if (this?.first == null || this.second == null) default else Pair(this.first!!, this.second!!)
}
fun loadUserSettings(context: Context) { fun loadUserSettings(context: Context) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false)
isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false) isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false)
isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false) isHideVersion = settingsPrefs.getBoolean("is_hide_version", false)
isHideVersion = settingsPrefs.getBoolean("is_hide_version", false) isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false)
isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false) isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false)
isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false) isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false)
isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false) isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false)
isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false) showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false)
showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false)
} catch (e: Exception) {
handleError(e, "loadUserSettings")
}
} }
} }
fun initializeData() { fun loadCoreData() {
viewModelScope.launch { if (isCoreDataLoaded) return
try {
loadCachedData()
// 成功加载后重置错误计数
prefs.edit {
putInt(KEY_ERROR_COUNT, 0)
}
} catch(e: Exception) {
handleError(e, "initializeData")
}
}
}
private fun loadCachedData() { val job = viewModelScope.launch(Dispatchers.IO) {
try {
prefs.getString(KEY_SYSTEM_STATUS, null)?.let { statusJson ->
try {
val cachedStatus = gson.fromJson(statusJson, SystemStatus::class.java)
if (cachedStatus != null) {
systemStatus = cachedStatus
}
} catch (e: JsonSyntaxException) {
Log.w(TAG, "Invalid system status JSON, using defaults", e)
}
}
prefs.getString(KEY_SYSTEM_INFO, null)?.let { infoJson ->
try {
val cachedInfo = gson.fromJson(infoJson, SystemInfo::class.java)
if (cachedInfo != null) {
systemInfo = cachedInfo
}
} catch (e: JsonSyntaxException) {
Log.w(TAG, "Invalid system info JSON, using defaults", e)
}
}
prefs.getString(KEY_VERSION_INFO, null)?.let { versionJson ->
try {
val cachedVersion = gson.fromJson(versionJson, LatestVersionInfo::class.java)
if (cachedVersion != null) {
latestVersionInfo = cachedVersion
}
} catch (e: JsonSyntaxException) {
Log.w(TAG, "Invalid version info JSON, using defaults", e)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error loading cached data", e)
throw e
}
}
private suspend fun fetchAndSaveData() {
try {
fetchSystemStatus()
fetchSystemInfo()
withContext(Dispatchers.IO) {
prefs.edit {
putString(KEY_SYSTEM_STATUS, gson.toJson(systemStatus))
putString(KEY_SYSTEM_INFO, gson.toJson(systemInfo))
putString(KEY_VERSION_INFO, gson.toJson(latestVersionInfo))
putLong(KEY_LAST_UPDATE, System.currentTimeMillis())
putInt(KEY_ERROR_COUNT, 0)
}
}
} catch (e: Exception) {
handleError(e, "fetchAndSaveData")
}
}
fun checkForUpdates(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val checkUpdate = settingsPrefs.getBoolean("check_update", true)
if (checkUpdate) {
val newVersionInfo = checkNewVersion()
latestVersionInfo = newVersionInfo
prefs.edit {
putString(KEY_VERSION_INFO, gson.toJson(newVersionInfo))
putLong(KEY_LAST_UPDATE, System.currentTimeMillis())
}
}
} catch (e: Exception) {
handleError(e, "checkForUpdates")
}
}
}
fun refreshAllData(context: Context) {
viewModelScope.launch {
try {
fetchAndSaveData()
checkForUpdates(context)
} catch (e: Exception) {
handleError(e, "refreshAllData")
}
}
}
private suspend fun fetchSystemStatus() {
withContext(Dispatchers.IO) {
try { try {
val kernelVersion = getKernelVersion() val kernelVersion = getKernelVersion()
val isManager = try { val isManager = try {
Natives.becomeManager(ksuApp.packageName.orSafe("com.sukisu.ultra")) Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to become manager", e)
false false
} }
val ksuVersion = if (isManager) { val ksuVersion = if (isManager) {
try { try {
Natives.version Natives.version
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to get KSU version", e)
null null
} }
} else null } else null
val fullVersion = try { val fullVersion = try {
Natives.getFullVersion().orSafe("Unknown") Natives.getFullVersion()
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to get full version", e)
"Unknown" "Unknown"
} }
@@ -309,8 +147,7 @@ class HomeViewModel : ViewModel() {
} else { } else {
fullVersion fullVersion
} }
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to process full version", e)
fullVersion fullVersion
} }
} else { } else {
@@ -322,30 +159,26 @@ class HomeViewModel : ViewModel() {
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) { if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) {
Natives.isLkmMode Natives.isLkmMode
} else null } else null
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to get LKM mode", e)
null null
} }
} }
val isRootAvailable = try { val isRootAvailable = try {
rootAvailable() rootAvailable()
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to check root availability", e)
false false
} }
val isKpmConfigured = try { val isKpmConfigured = try {
Natives.isKPMEnabled() Natives.isKPMEnabled()
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to check KPM status", e)
false false
} }
val requireNewKernel = try { val requireNewKernel = try {
isManager && Natives.requireNewKernel() isManager && Natives.requireNewKernel()
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to check kernel requirement", e)
false false
} }
@@ -359,198 +192,255 @@ class HomeViewModel : ViewModel() {
isKpmConfigured = isKpmConfigured, isKpmConfigured = isKpmConfigured,
requireNewKernel = requireNewKernel requireNewKernel = requireNewKernel
) )
} catch (e: Exception) {
Log.e(TAG, "Error fetching system status", e) isCoreDataLoaded = true
throw e } catch (_: Exception) {
} }
} }
loadingJobs.add(job)
} }
@SuppressLint("RestrictedApi") fun loadExtendedData(context: Context) {
private suspend fun fetchSystemInfo() { if (isExtendedDataLoaded) return
withContext(Dispatchers.IO) {
val job = viewModelScope.launch(Dispatchers.IO) {
try { try {
val uname = try { // 分批加载
Os.uname() delay(50)
} catch (e: Exception) {
Log.w(TAG, "Failed to get uname", e)
null
}
val kpmVersion = try { val basicInfo = loadBasicSystemInfo(context)
getKpmVersion().orSafe("Unknown") systemInfo = systemInfo.copy(
} catch (e: Exception) { kernelRelease = basicInfo.first,
Log.w(TAG, "Failed to get kpm version", e) androidVersion = basicInfo.second,
"Unknown" deviceModel = basicInfo.third,
} managerVersion = basicInfo.fourth,
seLinuxStatus = basicInfo.fifth
val suSFS = try {
getSuSFS().orSafe("Unknown")
} catch (e: Exception) {
Log.w(TAG, "Failed to get SuSFS", e)
"Unknown"
}
var suSFSVersion = ""
var suSFSVariant = ""
var suSFSFeatures = ""
var susSUMode = ""
if (suSFS == "Supported") {
suSFSVersion = try {
getSuSFSVersion().orSafe("")
} catch (e: Exception) {
Log.w(TAG, "Failed to get SuSFS version", e)
""
}
if (suSFSVersion.isNotEmpty()) {
suSFSVariant = try {
getSuSFSVariant().orSafe("")
} catch (e: Exception) {
Log.w(TAG, "Failed to get SuSFS variant", e)
""
}
suSFSFeatures = try {
getSuSFSFeatures().orSafe("")
} catch (e: Exception) {
Log.w(TAG, "Failed to get SuSFS features", e)
""
}
val isSUS_SU = suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
if (isSUS_SU) {
susSUMode = try {
susfsSUS_SU_Mode()
} catch (e: Exception) {
Log.w(TAG, "Failed to get SUS SU mode", e)
""
}
}
}
}
// 获取动态管理器状态和管理器列表
val dynamicSignConfig = try {
Natives.getDynamicManager()
} catch (e: Exception) {
Log.w(TAG, "Failed to get dynamic manager config", e)
null
}
val isDynamicSignEnabled = try {
dynamicSignConfig?.isValid() == true
} catch (e: Exception) {
Log.w(TAG, "Failed to check dynamic manager validity", e)
false
}
val managersList = if (isDynamicSignEnabled) {
try {
Natives.getManagersList()
} catch (e: Exception) {
Log.w(TAG, "Failed to get managers list", e)
null
}
} else {
null
}
val deviceModel = try {
getDeviceModel().orSafe("Unknown")
} catch (e: Exception) {
Log.w(TAG, "Failed to get device model", e)
"Unknown"
}
val managerVersion = try {
getManagerVersion(ksuApp.applicationContext).orSafe(Pair("Unknown", 0L))
} catch (e: Exception) {
Log.w(TAG, "Failed to get manager version", e)
Pair("Unknown", 0L)
}
val seLinuxStatus = try {
getSELinuxStatus(context).orSafe("Unknown")
} catch (e: Exception) {
Log.w(TAG, "Failed to get SELinux status", e)
"Unknown"
}
val superuserCount = try {
getSuperuserCount()
} catch (e: Exception) {
Log.w(TAG, "Failed to get superuser count", e)
0
}
val moduleCount = try {
getModuleCount()
} catch (e: Exception) {
Log.w(TAG, "Failed to get module count", e)
0
}
val kpmModuleCount = try {
getKpmModuleCount()
} catch (e: Exception) {
Log.w(TAG, "Failed to get kpm module count", e)
0
}
val zygiskImplement = try {
getZygiskImplement().orSafe("None")
} catch (e: Exception) {
Log.w(TAG, "Failed to get Zygisk implement", e)
"None"
}
systemInfo = SystemInfo(
kernelRelease = uname?.release.orSafe("Unknown"),
androidVersion = Build.VERSION.RELEASE.orSafe("Unknown"),
deviceModel = deviceModel,
managerVersion = managerVersion,
seLinuxStatus = seLinuxStatus,
kpmVersion = kpmVersion,
suSFSStatus = suSFS,
suSFSVersion = suSFSVersion,
suSFSVariant = suSFSVariant,
suSFSFeatures = suSFSFeatures,
susSUMode = susSUMode,
superuserCount = superuserCount,
moduleCount = moduleCount,
kpmModuleCount = kpmModuleCount,
managersList = managersList,
isDynamicSignEnabled = isDynamicSignEnabled,
zygiskImplement = zygiskImplement
) )
} catch (e: Exception) {
Log.e(TAG, "Error fetching system info", e) delay(100)
throw e
// 加载模块信息
if (!isSimpleMode) {
val moduleInfo = loadModuleInfo()
systemInfo = systemInfo.copy(
kpmVersion = moduleInfo.first,
superuserCount = moduleInfo.second,
moduleCount = moduleInfo.third,
kpmModuleCount = moduleInfo.fourth,
zygiskImplement = moduleInfo.fifth
)
}
delay(100)
// 加载SuSFS信息
if (!isHideSusfsStatus) {
val suSFSInfo = loadSuSFSInfo()
systemInfo = systemInfo.copy(
suSFSStatus = suSFSInfo.first,
suSFSVersion = suSFSInfo.second,
suSFSVariant = suSFSInfo.third,
suSFSFeatures = suSFSInfo.fourth,
susSUMode = suSFSInfo.fifth
)
}
delay(100)
// 加载管理器列表
val managerInfo = loadManagerInfo()
systemInfo = systemInfo.copy(
managersList = managerInfo.first,
isDynamicSignEnabled = managerInfo.second
)
isExtendedDataLoaded = true
} catch (_: Exception) {
// 静默处理错误
} }
} }
loadingJobs.add(job)
}
fun refreshData(context: Context) {
viewModelScope.launch {
isRefreshing = true
// 取消正在进行的加载任务
loadingJobs.forEach { it.cancel() }
loadingJobs.clear()
// 重置状态
isCoreDataLoaded = false
isExtendedDataLoaded = false
// 重新加载
loadCoreData()
delay(100)
loadExtendedData(context)
// 检查更新
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val checkUpdate = settingsPrefs.getBoolean("check_update", true)
if (checkUpdate) {
try {
val newVersionInfo = withContext(Dispatchers.IO) {
checkNewVersion()
}
latestVersionInfo = newVersionInfo
} catch (_: Exception) {
}
}
isRefreshing = false
}
} }
private fun getDeviceInfo(): String { private suspend fun loadBasicSystemInfo(context: Context): Tuple5<String, String, String, Pair<String, Long>, String> {
return try { return withContext(Dispatchers.IO) {
var manufacturer = Build.MANUFACTURER.orSafe("Unknown") val uname = try {
manufacturer = manufacturer[0].uppercaseChar().toString() + manufacturer.substring(1) Os.uname()
} catch (_: Exception) {
val brand = Build.BRAND.orSafe("") null
if (brand.isNotEmpty() && !brand.equals(Build.MANUFACTURER, ignoreCase = true)) {
manufacturer += " " + brand[0].uppercaseChar() + brand.substring(1)
} }
val model = Build.MODEL.orSafe("") val deviceModel = try {
if (model.isNotEmpty()) { getDeviceModel()
manufacturer += " $model " } catch (_: Exception) {
"Unknown"
} }
manufacturer val managerVersion = try {
} catch (e: Exception) { getManagerVersion(context)
Log.w(TAG, "Failed to get device info", e) } catch (_: Exception) {
"Unknown Device" Pair("Unknown", 0L)
}
val seLinuxStatus = try {
getSELinuxStatus(ksuApp.applicationContext)
} catch (_: Exception) {
"Unknown"
}
Tuple5(
uname?.release ?: "Unknown",
Build.VERSION.RELEASE ?: "Unknown",
deviceModel,
managerVersion,
seLinuxStatus
)
}
}
private suspend fun loadModuleInfo(): Tuple5<String, Int, Int, Int, String> {
return withContext(Dispatchers.IO) {
val kpmVersion = try {
getKpmVersion()
} catch (_: Exception) {
"Unknown"
}
val superuserCount = try {
getSuperuserCount()
} catch (_: Exception) {
0
}
val moduleCount = try {
getModuleCount()
} catch (_: Exception) {
0
}
val kpmModuleCount = try {
getKpmModuleCount()
} catch (_: Exception) {
0
}
val zygiskImplement = try {
getZygiskImplement()
} catch (_: Exception) {
"None"
}
Tuple5(kpmVersion, superuserCount, moduleCount, kpmModuleCount, zygiskImplement)
}
}
private suspend fun loadSuSFSInfo(): Tuple5<String, String, String, String, String> {
return withContext(Dispatchers.IO) {
val suSFS = try {
getSuSFS()
} catch (_: Exception) {
"Unknown"
}
if (suSFS != "Supported") {
return@withContext Tuple5(suSFS, "", "", "", "")
}
val suSFSVersion = try {
getSuSFSVersion()
} catch (_: Exception) {
""
}
if (suSFSVersion.isEmpty()) {
return@withContext Tuple5(suSFS, "", "", "", "")
}
val suSFSVariant = try {
getSuSFSVariant()
} catch (_: Exception) {
""
}
val suSFSFeatures = try {
getSuSFSFeatures()
} catch (_: Exception) {
""
}
val susSUMode = if (suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU") {
try {
susfsSUS_SU_Mode()
} catch (_: Exception) {
""
}
} else {
""
}
Tuple5(suSFS, suSFSVersion, suSFSVariant, suSFSFeatures, susSUMode)
}
}
private suspend fun loadManagerInfo(): Pair<Natives.ManagersList?, Boolean> {
return withContext(Dispatchers.IO) {
val dynamicSignConfig = try {
Natives.getDynamicManager()
} catch (_: Exception) {
null
}
val isDynamicSignEnabled = try {
dynamicSignConfig?.isValid() == true
} catch (_: Exception) {
false
}
val managersList = if (isDynamicSignEnabled) {
try {
Natives.getManagersList()
} catch (_: Exception) {
null
}
} else {
null
}
Pair(managersList, isDynamicSignEnabled)
} }
} }
@@ -560,10 +450,10 @@ class HomeViewModel : ViewModel() {
val systemProperties = Class.forName("android.os.SystemProperties") val systemProperties = Class.forName("android.os.SystemProperties")
val getMethod = systemProperties.getMethod("get", String::class.java, String::class.java) val getMethod = systemProperties.getMethod("get", String::class.java, String::class.java)
val marketNameKeys = listOf( val marketNameKeys = listOf(
"ro.product.marketname", // Xiaomi "ro.product.marketname",
"ro.vendor.oplus.market.name", // Oppo, OnePlus, Realme "ro.vendor.oplus.market.name",
"ro.vivo.market.name", // Vivo "ro.vivo.market.name",
"ro.config.marketing_name" // Huawei "ro.config.marketing_name"
) )
var result = getDeviceInfo() var result = getDeviceInfo()
for (key in marketNameKeys) { for (key in marketNameKeys) {
@@ -573,26 +463,60 @@ class HomeViewModel : ViewModel() {
result = marketName result = marketName
break break
} }
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Failed to get market name for key: $key", e)
} }
} }
result result
} catch (e: Exception) { } catch (
Log.w(TAG, "Error getting device model", e)
_: Exception) {
getDeviceInfo() getDeviceInfo()
} }
} }
private fun getDeviceInfo(): String {
return try {
var manufacturer = Build.MANUFACTURER ?: "Unknown"
manufacturer = manufacturer[0].uppercaseChar().toString() + manufacturer.substring(1)
val brand = Build.BRAND ?: ""
if (brand.isNotEmpty() && !brand.equals(Build.MANUFACTURER, ignoreCase = true)) {
manufacturer += " " + brand[0].uppercaseChar() + brand.substring(1)
}
val model = Build.MODEL ?: ""
if (model.isNotEmpty()) {
manufacturer += " $model "
}
manufacturer
} catch (_: Exception) {
"Unknown Device"
}
}
private fun getManagerVersion(context: Context): Pair<String, Long> { private fun getManagerVersion(context: Context): Pair<String, Long> {
return try { return try {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo) val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo)
val versionName = packageInfo.versionName.orSafe("Unknown") val versionName = packageInfo.versionName ?: "Unknown"
Pair(versionName, versionCode) Pair(versionName, versionCode)
} catch (e: Exception) { } catch (_: Exception) {
Log.w(TAG, "Error getting manager version", e)
Pair("Unknown", 0L) Pair("Unknown", 0L)
} }
} }
data class Tuple5<T1, T2, T3, T4, T5>(
val first: T1,
val second: T2,
val third: T3,
val fourth: T4,
val fifth: T5
)
override fun onCleared() {
super.onCleared()
loadingJobs.forEach { it.cancel() }
loadingJobs.clear()
}
} }