Manager: fmt
- Optimized homepage refresh logic and removed the caching mechanism
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -394,7 +394,6 @@ fun TryUmountContent(
|
||||
tryUmounts: Set<String>,
|
||||
isLoading: Boolean,
|
||||
onAddUmount: () -> Unit,
|
||||
onRunUmount: () -> Unit,
|
||||
onRemoveUmount: (String) -> Unit,
|
||||
onEditUmount: ((String) -> Unit)? = null,
|
||||
) {
|
||||
|
||||
@@ -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)) },
|
||||
|
||||
@@ -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<RootGraph>(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,6 +110,8 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 状态卡片
|
||||
if (viewModel.isCoreDataLoaded) {
|
||||
StatusCard(
|
||||
systemStatus = viewModel.systemStatus,
|
||||
onClickInstall = {
|
||||
@@ -128,6 +119,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
|
||||
// 警告信息
|
||||
if (viewModel.systemStatus.requireNewKernel) {
|
||||
WarningCard(
|
||||
stringResource(id = R.string.require_kernel_version).format(
|
||||
@@ -142,13 +134,17 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
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,
|
||||
@@ -158,13 +154,25 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
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,6 +262,7 @@ private fun TopBar(
|
||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||
),
|
||||
actions = {
|
||||
if (isDataLoaded) {
|
||||
// SuSFS 配置按钮
|
||||
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
|
||||
IconButton(onClick = {
|
||||
@@ -294,6 +304,7 @@ private fun TopBar(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
scrollBehavior = scrollBehavior
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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<ModuleViewModel>()
|
||||
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<ModuleViewModel>()
|
||||
|
||||
// 使用缓存系统获取模块大小
|
||||
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,7 +1260,6 @@ fun ModuleItem(
|
||||
onClick = { onClick(module) },
|
||||
interactionSource = interactionSource,
|
||||
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
||||
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,57 +83,17 @@ 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 <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!!)
|
||||
}
|
||||
private var loadingJobs = mutableListOf<Job>()
|
||||
|
||||
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)
|
||||
@@ -158,139 +103,32 @@ class HomeViewModel : ViewModel() {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initializeData() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
loadCachedData()
|
||||
// 成功加载后重置错误计数
|
||||
prefs.edit {
|
||||
putInt(KEY_ERROR_COUNT, 0)
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
handleError(e, "initializeData")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
fun loadCoreData() {
|
||||
if (isCoreDataLoaded) return
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun loadExtendedData(context: Context) {
|
||||
if (isExtendedDataLoaded) return
|
||||
|
||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
// 分批加载
|
||||
delay(50)
|
||||
|
||||
val basicInfo = loadBasicSystemInfo(context)
|
||||
systemInfo = systemInfo.copy(
|
||||
kernelRelease = basicInfo.first,
|
||||
androidVersion = basicInfo.second,
|
||||
deviceModel = basicInfo.third,
|
||||
managerVersion = basicInfo.fourth,
|
||||
seLinuxStatus = basicInfo.fifth
|
||||
)
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private suspend fun fetchSystemInfo() {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadBasicSystemInfo(context: Context): Tuple5<String, String, String, Pair<String, Long>, String> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val uname = try {
|
||||
Os.uname()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get uname", e)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
val deviceModel = try {
|
||||
getDeviceModel()
|
||||
} catch (_: Exception) {
|
||||
"Unknown"
|
||||
}
|
||||
|
||||
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<String, Int, Int, Int, String> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val kpmVersion = try {
|
||||
getKpmVersion().orSafe("Unknown")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get kpm version", e)
|
||||
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().orSafe("Unknown")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get SuSFS", e)
|
||||
getSuSFS()
|
||||
} catch (_: Exception) {
|
||||
"Unknown"
|
||||
}
|
||||
|
||||
var suSFSVersion = ""
|
||||
var suSFSVariant = ""
|
||||
var suSFSFeatures = ""
|
||||
var susSUMode = ""
|
||||
if (suSFS != "Supported") {
|
||||
return@withContext Tuple5(suSFS, "", "", "", "")
|
||||
}
|
||||
|
||||
if (suSFS == "Supported") {
|
||||
suSFSVersion = try {
|
||||
getSuSFSVersion().orSafe("")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get SuSFS version", e)
|
||||
val suSFSVersion = try {
|
||||
getSuSFSVersion()
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
|
||||
if (suSFSVersion.isNotEmpty()) {
|
||||
suSFSVariant = try {
|
||||
getSuSFSVariant().orSafe("")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get SuSFS variant", e)
|
||||
if (suSFSVersion.isEmpty()) {
|
||||
return@withContext Tuple5(suSFS, "", "", "", "")
|
||||
}
|
||||
|
||||
val suSFSVariant = try {
|
||||
getSuSFSVariant()
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
|
||||
suSFSFeatures = try {
|
||||
getSuSFSFeatures().orSafe("")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get SuSFS features", e)
|
||||
val suSFSFeatures = try {
|
||||
getSuSFSFeatures()
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
|
||||
val isSUS_SU = suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
|
||||
if (isSUS_SU) {
|
||||
susSUMode = try {
|
||||
val susSUMode = if (suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU") {
|
||||
try {
|
||||
susfsSUS_SU_Mode()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get SUS SU mode", e)
|
||||
} 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 (e: Exception) {
|
||||
Log.w(TAG, "Failed to get dynamic manager config", e)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
val isDynamicSignEnabled = try {
|
||||
dynamicSignConfig?.isValid() == true
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to check dynamic manager validity", e)
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
val managersList = if (isDynamicSignEnabled) {
|
||||
try {
|
||||
Natives.getManagersList()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get managers list", e)
|
||||
} catch (_: Exception) {
|
||||
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)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
val model = Build.MODEL.orSafe("")
|
||||
if (model.isNotEmpty()) {
|
||||
manufacturer += " $model "
|
||||
}
|
||||
|
||||
manufacturer
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to get device info", e)
|
||||
"Unknown Device"
|
||||
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<String, Long> {
|
||||
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<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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user