From c9507050441d647d111750ec840f1eda5bfd21b6 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:17:19 +0800 Subject: [PATCH] Manager: fmt - Optimized homepage refresh logic and removed the caching mechanism --- .../java/com/sukisu/ultra/ui/MainActivity.kt | 9 - .../ultra/ui/component/SuSFSConfigDialogs.kt | 2 +- .../ultra/ui/component/SuSFSConfigTabs.kt | 1 - .../ui/component/profile/TemplateConfig.kt | 2 +- .../java/com/sukisu/ultra/ui/screen/Home.kt | 196 ++--- .../java/com/sukisu/ultra/ui/screen/Module.kt | 29 +- .../com/sukisu/ultra/ui/screen/Settings.kt | 4 +- .../com/sukisu/ultra/ui/screen/SuSFSConfig.kt | 10 +- .../com/sukisu/ultra/ui/screen/SuperUser.kt | 36 +- .../ultra/ui/viewmodel/HomeViewModel.kt | 698 ++++++++---------- 10 files changed, 429 insertions(+), 558 deletions(-) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt index ca0b9958..b6ce2aed 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/MainActivity.kt @@ -187,14 +187,6 @@ class MainActivity : ComponentActivity() { } } - lifecycleScope.launch { - try { - homeViewModel.initializeData() - } catch (e: Exception) { - e.printStackTrace() - } - } - // 数据刷新协程 DataRefreshUtils.startDataRefreshCoroutine(lifecycleScope) DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow) @@ -228,7 +220,6 @@ class MainActivity : ComponentActivity() { lifecycleScope.launch { try { superUserViewModel.fetchAppList() - homeViewModel.initializeData() DataRefreshUtils.refreshData(lifecycleScope) } catch (e: Exception) { e.printStackTrace() diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt index f3fb7804..3282881a 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigDialogs.kt @@ -464,7 +464,7 @@ fun AddTryUmountDialog( trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) }, modifier = Modifier .fillMaxWidth() - .menuAnchor(MenuAnchorType.PrimaryEditable, true), + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true), shape = RoundedCornerShape(8.dp) ) ExposedDropdownMenu( diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt index 74469e3a..6e5092aa 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/SuSFSConfigTabs.kt @@ -394,7 +394,6 @@ fun TryUmountContent( tryUmounts: Set, isLoading: Boolean, onAddUmount: () -> Unit, - onRunUmount: () -> Unit, onRemoveUmount: (String) -> Unit, onEditUmount: ((String) -> Unit)? = null, ) { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/profile/TemplateConfig.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/profile/TemplateConfig.kt index 08c093a3..7af311b3 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/component/profile/TemplateConfig.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/profile/TemplateConfig.kt @@ -43,7 +43,7 @@ fun TemplateConfig( ) { OutlinedTextField( modifier = Modifier - .menuAnchor(MenuAnchorType.PrimaryNotEditable) + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) .fillMaxWidth(), readOnly = true, label = { Text(stringResource(R.string.profile_template)) }, diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt index 954044ee..fc13dfce 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt @@ -57,13 +57,14 @@ import com.sukisu.ultra.ui.util.module.LatestVersionInfo import com.sukisu.ultra.ui.util.reboot import com.sukisu.ultra.ui.viewmodel.HomeViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.random.Random /** * @author ShirkNeko - * @date 2025/5/31. + * @date 2025/9/29. */ @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Destination(start = true) @@ -74,15 +75,12 @@ fun HomeScreen(navigator: DestinationsNavigator) { val coroutineScope = rememberCoroutineScope() LaunchedEffect(key1 = navigator) { - coroutineScope.launch { - viewModel.refreshAllData(context) - } - } - - LaunchedEffect(Unit) { viewModel.loadUserSettings(context) - viewModel.initializeData() - viewModel.checkForUpdates(context) + coroutineScope.launch { + viewModel.loadCoreData() + delay(100) + viewModel.loadExtendedData(context) + } } val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) @@ -92,27 +90,18 @@ fun HomeScreen(navigator: DestinationsNavigator) { topBar = { TopBar( scrollBehavior = scrollBehavior, - navigator = navigator + navigator = navigator, + isDataLoaded = viewModel.isCoreDataLoaded ) }, contentWindowInsets = WindowInsets.safeDrawing.only( WindowInsetsSides.Top + WindowInsetsSides.Horizontal ) ) { innerPadding -> - val pullRefreshState = rememberPullRefreshState( - refreshing = false, - onRefresh = { - coroutineScope.launch { - viewModel.refreshAllData(context) - } - } - ) - Box( modifier = Modifier .padding(innerPadding) .fillMaxSize() - .pullRefresh(pullRefreshState) ) { Column( modifier = Modifier @@ -121,50 +110,69 @@ fun HomeScreen(navigator: DestinationsNavigator) { .padding(top = 12.dp, start = 16.dp, end = 16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - StatusCard( - systemStatus = viewModel.systemStatus, - onClickInstall = { - navigator.navigate(InstallScreenDestination) - } - ) + // 状态卡片 + if (viewModel.isCoreDataLoaded) { + StatusCard( + systemStatus = viewModel.systemStatus, + onClickInstall = { + navigator.navigate(InstallScreenDestination) + } + ) - if (viewModel.systemStatus.requireNewKernel) { - WarningCard( - stringResource(id = R.string.require_kernel_version).format( - Natives.getSimpleVersionFull(), - Natives.MINIMAL_SUPPORTED_KERNEL_FULL + // 警告信息 + if (viewModel.systemStatus.requireNewKernel) { + WarningCard( + stringResource(id = R.string.require_kernel_version).format( + 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( - 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) { + // 链接卡片 + if (!viewModel.isSimpleMode && !viewModel.isHideLinkCard) { ContributionCard() DonateCard() LearnMoreCard() } } + + if (!viewModel.isExtendedDataLoaded) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + Spacer(Modifier.height(16.dp)) } } @@ -231,7 +239,8 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") { @Composable private fun TopBar( scrollBehavior: TopAppBarScrollBehavior? = null, - navigator: DestinationsNavigator + navigator: DestinationsNavigator, + isDataLoaded: Boolean = false ) { val context = LocalContext.current val colorScheme = MaterialTheme.colorScheme @@ -253,44 +262,46 @@ private fun TopBar( scrolledContainerColor = cardColor.copy(alpha = cardAlpha) ), actions = { - // SuSFS 配置按钮 - if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) { - 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 + if (isDataLoaded) { + // SuSFS 配置按钮 + if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) { + IconButton(onClick = { + navigator.navigate(SuSFSConfigScreenDestination) }) { - 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? - @Suppress("DEPRECATION") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { - RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") + // 重启按钮 + 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) + + 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") } } } @@ -731,7 +742,7 @@ private fun InfoCard( systemInfo.seLinuxStatus, icon = Icons.Default.Security, ) - + if (!isHideZygiskImplement && !isSimpleMode && systemInfo.zygiskImplement != "None") { InfoCardItem( stringResource(R.string.home_zygisk_implement), @@ -741,7 +752,6 @@ private fun InfoCard( } if (!isSimpleMode) { - // 根据showKpmInfo决定是否显示KPM信息 if (lkmMode != true && !showKpmInfo) { val displayVersion = if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt index 3f4ceadf..294e8a84 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt @@ -84,7 +84,6 @@ import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit -// 菜单项数据类 data class ModuleBottomSheetMenuItem( val icon: ImageVector, val titleRes: Int, @@ -93,7 +92,7 @@ data class ModuleBottomSheetMenuItem( /** * @author ShirkNeko - * @date 2025/5/31. + * @date 2025/9/29. */ @SuppressLint("ResourceType", "AutoboxingStateCreation") @OptIn(ExperimentalMaterial3Api::class) @@ -102,24 +101,21 @@ data class ModuleBottomSheetMenuItem( fun ModuleScreen(navigator: DestinationsNavigator) { val viewModel = viewModel() val context = LocalContext.current - val prefs = context.getSharedPreferences("settings",MODE_PRIVATE) + val prefs = context.getSharedPreferences("settings", MODE_PRIVATE) val snackBarHost = LocalSnackbarHost.current val scope = rememberCoroutineScope() val confirmDialog = rememberConfirmDialog() var lastClickTime by remember { mutableStateOf(0L) } - // 签名验证弹窗状态 var showSignatureDialog by remember { mutableStateOf(false) } var signatureDialogMessage by remember { mutableStateOf("") } var isForceVerificationFailed by remember { mutableStateOf(false) } var pendingInstallAction by remember { mutableStateOf<(() -> Unit)?>(null) } - // 初始化缓存系统 LaunchedEffect(Unit) { viewModel.initializeCache(context) } - // BottomSheet状态 val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) @@ -280,7 +276,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) { val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost) val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost) - LaunchedEffect(Unit) { if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) { viewModel.sortEnabledFirst = prefs.getBoolean("module_sort_enabled_first", false) @@ -291,7 +286,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) { val isSafeMode = Natives.isSafeMode val hasMagisk = hasMagisk() - val hideInstallButton = isSafeMode || hasMagisk val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) @@ -300,7 +294,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) { contract = ActivityResultContracts.StartActivityForResult() ) { viewModel.fetchModuleList() } - // BottomSheet菜单项 val bottomSheetMenuItems = remember { listOf( ModuleBottomSheetMenuItem( @@ -478,7 +471,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) { } } - // BottomSheet if (showBottomSheet) { ModalBottomSheet( onDismissRequest = { @@ -620,7 +612,6 @@ private fun ModuleBottomSheetContent( modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) ) - // 排序选项 Column( modifier = Modifier.padding(horizontal = 24.dp), verticalArrangement = Arrangement.spacedBy(8.dp) @@ -682,7 +673,6 @@ private fun ModuleBottomSheetContent( @Composable private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) { - // 添加交互状态 val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() @@ -810,7 +800,6 @@ private fun ModuleList( return } - // changelog is not empty, show it and wait for confirm val confirmResult = confirmDialog.awaitConfirm( changelogText, content = changelog, @@ -901,6 +890,7 @@ private fun ModuleList( reboot() } } + PullToRefreshBox( modifier = boxModifier, onRefresh = { @@ -1003,7 +993,6 @@ private fun ModuleList( } ) - // fix last item shadow incomplete in LazyColumn Spacer(Modifier.height(1.dp)) } } @@ -1011,7 +1000,6 @@ private fun ModuleList( } DownloadListener(context, onInstallModule) - } } @@ -1044,7 +1032,6 @@ fun ModuleItem( val indication = LocalIndication.current val viewModel = viewModel() - // 使用缓存系统获取模块大小 val sizeStr = remember(module.dirId) { viewModel.getModuleSize(module.dirId) } @@ -1152,10 +1139,8 @@ fun ModuleItem( modifier = Modifier .fillMaxWidth() .combinedClickable( - onClick = { - }, + onClick = { }, onLongClick = { - // 长按复制updateJson地址 val clipData = ClipData.newPlainText( "Update JSON URL", module.updateJson @@ -1163,7 +1148,6 @@ fun ModuleItem( clipboardManager.setPrimaryClip(clipData) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - // 显示复制成功的提示 Toast.makeText( context, context.getString(R.string.module_update_json_copied), @@ -1202,8 +1186,8 @@ fun ModuleItem( maxLines = 4, textDecoration = textDecoration, ) - if (!isHideTagRow) { + if (!isHideTagRow) { Spacer(modifier = Modifier.height(12.dp)) // 文件夹名称和大小标签 Row( @@ -1276,8 +1260,7 @@ fun ModuleItem( onClick = { onClick(module) }, interactionSource = interactionSource, contentPadding = ButtonDefaults.TextButtonContentPadding, - - ) { + ) { Icon( modifier = Modifier.size(20.dp), imageVector = Icons.AutoMirrored.Outlined.Wysiwyg, diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 2581a1c8..1d640ea4 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -56,7 +56,7 @@ import java.time.format.DateTimeFormatter /** * @author ShirkNeko - * @date 2025/5/31. + * @date 2025/9/29. */ private val SPACING_SMALL = 3.dp private val SPACING_MEDIUM = 8.dp @@ -77,7 +77,6 @@ fun SettingScreen(navigator: DestinationsNavigator) { } Scaffold( - // containerColor = MaterialTheme.colorScheme.surfaceBright, topBar = { 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 { return rememberCustomDialog { dismiss -> val options = listOf( - // UninstallType.TEMPORARY, UninstallType.PERMANENT, UninstallType.RESTORE_STOCK_IMAGE ) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt index b16e738a..a06927b7 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuSFSConfig.kt @@ -118,7 +118,6 @@ fun SuSFSConfigScreen( var showAddAppPathDialog by remember { mutableStateOf(false) } var showAddMountDialog by remember { mutableStateOf(false) } var showAddUmountDialog by remember { mutableStateOf(false) } - var showRunUmountDialog by remember { mutableStateOf(false) } var showAddKstatStaticallyDialog by remember { mutableStateOf(false) } var showAddKstatDialog by remember { mutableStateOf(false) } @@ -1090,12 +1089,12 @@ fun SuSFSConfigScreen( .padding(horizontal = 12.dp) ) { // 标签页 - ScrollableTabRow( + PrimaryScrollableTabRow( selectedTabIndex = allTabs.indexOf(selectedTab), - edgePadding = 0.dp, modifier = Modifier.fillMaxWidth(), containerColor = MaterialTheme.colorScheme.surface, - contentColor = MaterialTheme.colorScheme.onSurface + contentColor = MaterialTheme.colorScheme.onSurface, + edgePadding = 0.dp ) { allTabs.forEach { tab -> Tab( @@ -1262,7 +1261,6 @@ fun SuSFSConfigScreen( tryUmounts = tryUmounts, isLoading = isLoading, onAddUmount = { showAddUmountDialog = true }, - onRunUmount = { showRunUmountDialog = true }, onRemoveUmount = { umountEntry -> coroutineScope.launch { isLoading = true @@ -1467,7 +1465,7 @@ private fun BasicSettingsContent( trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = scriptLocationExpanded) }, modifier = Modifier .fillMaxWidth() - .menuAnchor(MenuAnchorType.PrimaryEditable, true), + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true), shape = RoundedCornerShape(8.dp), enabled = !isLoading ) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt index e2434b74..8b700771 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt @@ -67,16 +67,12 @@ enum class AppPriority(val value: Int) { DEFAULT(3) // 默认应用 } -// 菜单项数据类 data class BottomSheetMenuItem( val icon: ImageVector, val titleRes: Int, val onClick: () -> Unit ) -/** - * 获取应用的优先级 - */ private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority { return when { app.allowSu -> AppPriority.ROOT @@ -85,9 +81,6 @@ private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority { } } -/** - * 获取多选模式的主按钮图标 - */ private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector { return if (isExpanded) { Icons.Filled.Close @@ -96,9 +89,6 @@ private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector { } } -/** - * 获取单选模式的主按钮图标 - */ private fun getSingleSelectMainIcon(isExpanded: Boolean): ImageVector { return if (isExpanded) { Icons.Filled.Close @@ -122,17 +112,14 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { val context = LocalContext.current val snackBarHostState = remember { SnackbarHostState() } - // 使用ViewModel中的状态,这些状态现在都会从SharedPreferences中加载并自动保存 val selectedCategory = viewModel.selectedCategory val currentSortType = viewModel.currentSortType - // BottomSheet状态 val bottomSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) var showBottomSheet by remember { mutableStateOf(false) } - // 添加备份和还原启动器 val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState) val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState) @@ -145,8 +132,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { LaunchedEffect(viewModel.search) { if (viewModel.search.isEmpty()) { - // 取消自动滚动到顶部的行为 - // listState.scrollToItem(0) + // Optional: scroll to top when clearing search } } @@ -234,7 +220,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { apps } - // 计算应用数量 val appCounts = remember(viewModel.appList, viewModel.showSystemApps) { mapOf( AppCategory.ALL to viewModel.appList.size, @@ -244,7 +229,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { ) } - // BottomSheet菜单项 val bottomSheetMenuItems = remember(viewModel.showSystemApps) { listOf( BottomSheetMenuItem( @@ -299,7 +283,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { ) } - // 记录FAB展开状态用于图标动画 var isFabExpanded by remember { mutableStateOf(false) } Scaffold( @@ -311,7 +294,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Text(stringResource(R.string.superuser)) - // 显示当前分类和应用数量 + if (selectedCategory != AppCategory.ALL) { Surface( shape = RoundedCornerShape(12.dp), @@ -413,7 +396,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { buttonSpacing = 72.dp, animationDurationMs = 300, staggerDelayMs = 50, - // 根据模式选择不同的图标 mainButtonIcon = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) { getMultiSelectMainIcon(isFabExpanded) } else { @@ -458,7 +440,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { ) } - // 当没有应用显示时显示加载动画或空状态 if (filteredAndSortedApps.isEmpty()) { item { Box( @@ -467,7 +448,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { .height(400.dp), contentAlignment = Alignment.Center ) { - // 根据加载状态显示不同内容 if ((viewModel.isRefreshing || viewModel.appList.isEmpty()) && viewModel.search.isEmpty()) { LoadingAnimation( isLoading = true @@ -484,7 +464,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) { } } - // BottomSheet if (showBottomSheet) { ModalBottomSheet( onDismissRequest = { @@ -594,7 +573,6 @@ private fun BottomSheetContent( } } - // 应用分类选项 Spacer(modifier = Modifier.height(24.dp)) HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp)) @@ -702,7 +680,6 @@ private fun CategoryChip( } } - // 应用数量 Text( text = "$appCount apps", style = MaterialTheme.typography.labelSmall, @@ -718,7 +695,6 @@ private fun CategoryChip( @Composable private fun BottomSheetMenuItemView(menuItem: BottomSheetMenuItem) { - // 添加交互状态 val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() @@ -897,9 +873,6 @@ fun LabelText(label: String) { } } -/** - * 加载动画组件 - */ @Composable private fun LoadingAnimation( modifier: Modifier = Modifier, @@ -907,7 +880,6 @@ private fun LoadingAnimation( ) { val infiniteTransition = rememberInfiniteTransition(label = "loading") - // 透明度动画 val alpha by infiniteTransition.animateFloat( initialValue = 0.3f, targetValue = 1f, @@ -928,7 +900,6 @@ private fun LoadingAnimation( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - // 进度指示器 LinearProgressIndicator( modifier = Modifier .width(200.dp) @@ -940,9 +911,6 @@ private fun LoadingAnimation( } } -/** - * 空状态组件 - */ @Composable @SuppressLint("ModifierParameter") private fun EmptyState( diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt index 838c2298..3628615a 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt @@ -4,16 +4,11 @@ import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.system.Os -import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.core.content.edit import androidx.lifecycle.ViewModel 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.Natives 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.module.LatestVersionInfo import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext 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( @@ -69,9 +56,7 @@ class HomeViewModel : ViewModel() { val zygiskImplement: String = "" ) - private val gson = Gson() - private val prefs by lazy { ksuApp.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) } - + // 状态变量 var systemStatus by mutableStateOf(SystemStatus()) private set @@ -98,199 +83,52 @@ class HomeViewModel : ViewModel() { var showKpmInfo by mutableStateOf(false) private set - private fun clearAllCache() { - try { - prefs.edit { clear() } - Log.i(TAG, "All cache cleared successfully") - } catch (e: Exception) { - Log.e(TAG, "Error clearing cache", e) - } - } + var isCoreDataLoaded by mutableStateOf(false) + private set + var isExtendedDataLoaded by mutableStateOf(false) + private set + var isRefreshing by mutableStateOf(false) + private set - private fun resetToDefaults() { - 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 Pair?.orSafe(default: Pair): Pair { - return if (this?.first == null || this.second == null) default else Pair(this.first!!, this.second!!) - } + private var loadingJobs = mutableListOf() fun loadUserSettings(context: Context) { viewModelScope.launch(Dispatchers.IO) { - try { - val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false) - isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false) - isHideVersion = settingsPrefs.getBoolean("is_hide_version", false) - isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false) - isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false) - isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false) - isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false) - showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false) - } catch (e: Exception) { - handleError(e, "loadUserSettings") - } + val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false) + isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false) + isHideVersion = settingsPrefs.getBoolean("is_hide_version", false) + isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false) + isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false) + isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false) + isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false) + showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false) } } - fun initializeData() { - viewModelScope.launch { - try { - loadCachedData() - // 成功加载后重置错误计数 - prefs.edit { - putInt(KEY_ERROR_COUNT, 0) - } - } catch(e: Exception) { - handleError(e, "initializeData") - } - } - } + fun loadCoreData() { + if (isCoreDataLoaded) return - private fun loadCachedData() { - 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) { + val job = viewModelScope.launch(Dispatchers.IO) { try { val kernelVersion = getKernelVersion() val isManager = try { - Natives.becomeManager(ksuApp.packageName.orSafe("com.sukisu.ultra")) - } catch (e: Exception) { - Log.w(TAG, "Failed to become manager", e) + Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra") + } catch (_: Exception) { false } val ksuVersion = if (isManager) { try { Natives.version - } catch (e: Exception) { - Log.w(TAG, "Failed to get KSU version", e) + } catch (_: Exception) { null } } else null val fullVersion = try { - Natives.getFullVersion().orSafe("Unknown") - } catch (e: Exception) { - Log.w(TAG, "Failed to get full version", e) + Natives.getFullVersion() + } catch (_: Exception) { "Unknown" } @@ -309,8 +147,7 @@ class HomeViewModel : ViewModel() { } else { fullVersion } - } catch (e: Exception) { - Log.w(TAG, "Failed to process full version", e) + } catch (_: Exception) { fullVersion } } else { @@ -322,30 +159,26 @@ class HomeViewModel : ViewModel() { if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) { Natives.isLkmMode } else null - } catch (e: Exception) { - Log.w(TAG, "Failed to get LKM mode", e) + } catch (_: Exception) { null } } val isRootAvailable = try { rootAvailable() - } catch (e: Exception) { - Log.w(TAG, "Failed to check root availability", e) + } catch (_: Exception) { false } val isKpmConfigured = try { Natives.isKPMEnabled() - } catch (e: Exception) { - Log.w(TAG, "Failed to check KPM status", e) + } catch (_: Exception) { false } val requireNewKernel = try { isManager && Natives.requireNewKernel() - } catch (e: Exception) { - Log.w(TAG, "Failed to check kernel requirement", e) + } catch (_: Exception) { false } @@ -359,198 +192,255 @@ class HomeViewModel : ViewModel() { isKpmConfigured = isKpmConfigured, requireNewKernel = requireNewKernel ) - } catch (e: Exception) { - Log.e(TAG, "Error fetching system status", e) - throw e + + isCoreDataLoaded = true + } catch (_: Exception) { } } + loadingJobs.add(job) } - @SuppressLint("RestrictedApi") - private suspend fun fetchSystemInfo() { - withContext(Dispatchers.IO) { + fun loadExtendedData(context: Context) { + if (isExtendedDataLoaded) return + + val job = viewModelScope.launch(Dispatchers.IO) { try { - val uname = try { - Os.uname() - } catch (e: Exception) { - Log.w(TAG, "Failed to get uname", e) - null - } + // 分批加载 + delay(50) - val kpmVersion = try { - getKpmVersion().orSafe("Unknown") - } catch (e: Exception) { - Log.w(TAG, "Failed to get kpm version", e) - "Unknown" - } - - 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 + val basicInfo = loadBasicSystemInfo(context) + systemInfo = systemInfo.copy( + kernelRelease = basicInfo.first, + androidVersion = basicInfo.second, + deviceModel = basicInfo.third, + managerVersion = basicInfo.fourth, + seLinuxStatus = basicInfo.fifth ) - } catch (e: Exception) { - Log.e(TAG, "Error fetching system info", e) - throw e + + delay(100) + + // 加载模块信息 + 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 { - return try { - var manufacturer = Build.MANUFACTURER.orSafe("Unknown") - manufacturer = manufacturer[0].uppercaseChar().toString() + manufacturer.substring(1) - - val brand = Build.BRAND.orSafe("") - if (brand.isNotEmpty() && !brand.equals(Build.MANUFACTURER, ignoreCase = true)) { - manufacturer += " " + brand[0].uppercaseChar() + brand.substring(1) + private suspend fun loadBasicSystemInfo(context: Context): Tuple5, String> { + return withContext(Dispatchers.IO) { + val uname = try { + Os.uname() + } catch (_: Exception) { + null } - val model = Build.MODEL.orSafe("") - if (model.isNotEmpty()) { - manufacturer += " $model " + val deviceModel = try { + getDeviceModel() + } catch (_: Exception) { + "Unknown" } - manufacturer - } catch (e: Exception) { - Log.w(TAG, "Failed to get device info", e) - "Unknown Device" + val managerVersion = try { + getManagerVersion(context) + } catch (_: Exception) { + 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 { + 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 { + 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 { + 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 getMethod = systemProperties.getMethod("get", String::class.java, String::class.java) val marketNameKeys = listOf( - "ro.product.marketname", // Xiaomi - "ro.vendor.oplus.market.name", // Oppo, OnePlus, Realme - "ro.vivo.market.name", // Vivo - "ro.config.marketing_name" // Huawei + "ro.product.marketname", + "ro.vendor.oplus.market.name", + "ro.vivo.market.name", + "ro.config.marketing_name" ) var result = getDeviceInfo() for (key in marketNameKeys) { @@ -573,26 +463,60 @@ class HomeViewModel : ViewModel() { result = marketName break } - } catch (e: Exception) { - Log.w(TAG, "Failed to get market name for key: $key", e) + } catch (_: Exception) { } } result - } catch (e: Exception) { - Log.w(TAG, "Error getting device model", e) + } catch ( + + _: Exception) { 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 { return try { val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo) - val versionName = packageInfo.versionName.orSafe("Unknown") + val versionName = packageInfo.versionName ?: "Unknown" Pair(versionName, versionCode) - } catch (e: Exception) { - Log.w(TAG, "Error getting manager version", e) + } catch (_: Exception) { Pair("Unknown", 0L) } } + + data class Tuple5( + 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() + } } \ No newline at end of file