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.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()

View File

@@ -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(

View File

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

View File

@@ -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)) },

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.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")) {

View File

@@ -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),

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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(

View File

@@ -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()
}
}