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.startDataRefreshCoroutine(lifecycleScope)
|
||||||
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
|
DataRefreshUtils.startSettingsMonitorCoroutine(lifecycleScope, this, settingsStateFlow)
|
||||||
@@ -228,7 +220,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
superUserViewModel.fetchAppList()
|
superUserViewModel.fetchAppList()
|
||||||
homeViewModel.initializeData()
|
|
||||||
DataRefreshUtils.refreshData(lifecycleScope)
|
DataRefreshUtils.refreshData(lifecycleScope)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ fun AddTryUmountDialog(
|
|||||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) },
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = umountModeExpanded) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.menuAnchor(MenuAnchorType.PrimaryEditable, true),
|
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true),
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(8.dp)
|
||||||
)
|
)
|
||||||
ExposedDropdownMenu(
|
ExposedDropdownMenu(
|
||||||
|
|||||||
@@ -394,7 +394,6 @@ fun TryUmountContent(
|
|||||||
tryUmounts: Set<String>,
|
tryUmounts: Set<String>,
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
onAddUmount: () -> Unit,
|
onAddUmount: () -> Unit,
|
||||||
onRunUmount: () -> Unit,
|
|
||||||
onRemoveUmount: (String) -> Unit,
|
onRemoveUmount: (String) -> Unit,
|
||||||
onEditUmount: ((String) -> Unit)? = null,
|
onEditUmount: ((String) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fun TemplateConfig(
|
|||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.menuAnchor(MenuAnchorType.PrimaryNotEditable)
|
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
label = { Text(stringResource(R.string.profile_template)) },
|
label = { Text(stringResource(R.string.profile_template)) },
|
||||||
|
|||||||
@@ -57,13 +57,14 @@ import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
|||||||
import com.sukisu.ultra.ui.util.reboot
|
import com.sukisu.ultra.ui.util.reboot
|
||||||
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
import com.sukisu.ultra.ui.viewmodel.HomeViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/5/31.
|
* @date 2025/9/29.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
@Destination<RootGraph>(start = true)
|
@Destination<RootGraph>(start = true)
|
||||||
@@ -74,15 +75,12 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(key1 = navigator) {
|
LaunchedEffect(key1 = navigator) {
|
||||||
coroutineScope.launch {
|
|
||||||
viewModel.refreshAllData(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.loadUserSettings(context)
|
viewModel.loadUserSettings(context)
|
||||||
viewModel.initializeData()
|
coroutineScope.launch {
|
||||||
viewModel.checkForUpdates(context)
|
viewModel.loadCoreData()
|
||||||
|
delay(100)
|
||||||
|
viewModel.loadExtendedData(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
@@ -92,27 +90,18 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
navigator = navigator
|
navigator = navigator,
|
||||||
|
isDataLoaded = viewModel.isCoreDataLoaded
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(
|
contentWindowInsets = WindowInsets.safeDrawing.only(
|
||||||
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
||||||
)
|
)
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
val pullRefreshState = rememberPullRefreshState(
|
|
||||||
refreshing = false,
|
|
||||||
onRefresh = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
viewModel.refreshAllData(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.pullRefresh(pullRefreshState)
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -121,50 +110,69 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
StatusCard(
|
// 状态卡片
|
||||||
systemStatus = viewModel.systemStatus,
|
if (viewModel.isCoreDataLoaded) {
|
||||||
onClickInstall = {
|
StatusCard(
|
||||||
navigator.navigate(InstallScreenDestination)
|
systemStatus = viewModel.systemStatus,
|
||||||
}
|
onClickInstall = {
|
||||||
)
|
navigator.navigate(InstallScreenDestination)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (viewModel.systemStatus.requireNewKernel) {
|
// 警告信息
|
||||||
WarningCard(
|
if (viewModel.systemStatus.requireNewKernel) {
|
||||||
stringResource(id = R.string.require_kernel_version).format(
|
WarningCard(
|
||||||
Natives.getSimpleVersionFull(),
|
stringResource(id = R.string.require_kernel_version).format(
|
||||||
Natives.MINIMAL_SUPPORTED_KERNEL_FULL
|
Natives.getSimpleVersionFull(),
|
||||||
|
Natives.MINIMAL_SUPPORTED_KERNEL_FULL
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable) {
|
||||||
|
WarningCard(
|
||||||
|
stringResource(id = R.string.grant_root_failed)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新检查
|
||||||
|
if (viewModel.isExtendedDataLoaded) {
|
||||||
|
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("check_update", true)
|
||||||
|
if (checkUpdate) {
|
||||||
|
UpdateCard()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 信息卡片
|
||||||
|
InfoCard(
|
||||||
|
systemInfo = viewModel.systemInfo,
|
||||||
|
isSimpleMode = viewModel.isSimpleMode,
|
||||||
|
isHideSusfsStatus = viewModel.isHideSusfsStatus,
|
||||||
|
isHideZygiskImplement = viewModel.isHideZygiskImplement,
|
||||||
|
showKpmInfo = viewModel.showKpmInfo,
|
||||||
|
lkmMode = viewModel.systemStatus.lkmMode,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable) {
|
// 链接卡片
|
||||||
WarningCard(
|
if (!viewModel.isSimpleMode && !viewModel.isHideLinkCard) {
|
||||||
stringResource(id = R.string.grant_root_failed)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
.getBoolean("check_update", true)
|
|
||||||
if (checkUpdate) {
|
|
||||||
UpdateCard()
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoCard(
|
|
||||||
systemInfo = viewModel.systemInfo,
|
|
||||||
isSimpleMode = viewModel.isSimpleMode,
|
|
||||||
isHideSusfsStatus = viewModel.isHideSusfsStatus,
|
|
||||||
isHideZygiskImplement = viewModel.isHideZygiskImplement,
|
|
||||||
showKpmInfo = viewModel.showKpmInfo,
|
|
||||||
lkmMode = viewModel.systemStatus.lkmMode,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!viewModel.isSimpleMode) {
|
|
||||||
if (!viewModel.isHideLinkCard) {
|
|
||||||
ContributionCard()
|
ContributionCard()
|
||||||
DonateCard()
|
DonateCard()
|
||||||
LearnMoreCard()
|
LearnMoreCard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!viewModel.isExtendedDataLoaded) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(24.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +239,8 @@ fun RebootDropdownItem(@StringRes id: Int, reason: String = "") {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TopBar(
|
private fun TopBar(
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
navigator: DestinationsNavigator
|
navigator: DestinationsNavigator,
|
||||||
|
isDataLoaded: Boolean = false
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
@@ -253,44 +262,46 @@ private fun TopBar(
|
|||||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
),
|
),
|
||||||
actions = {
|
actions = {
|
||||||
// SuSFS 配置按钮
|
if (isDataLoaded) {
|
||||||
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
|
// SuSFS 配置按钮
|
||||||
IconButton(onClick = {
|
if (getSuSFS() == "Supported" && SuSFSManager.isBinaryAvailable(context)) {
|
||||||
navigator.navigate(SuSFSConfigScreenDestination)
|
IconButton(onClick = {
|
||||||
}) {
|
navigator.navigate(SuSFSConfigScreenDestination)
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Tune,
|
|
||||||
contentDescription = stringResource(R.string.susfs_config_setting_title)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重启按钮
|
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
|
||||||
KsuIsValid {
|
|
||||||
IconButton(onClick = {
|
|
||||||
showDropdown = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.PowerSettingsNew,
|
|
||||||
contentDescription = stringResource(id = R.string.reboot)
|
|
||||||
)
|
|
||||||
|
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
|
||||||
showDropdown = false
|
|
||||||
}) {
|
}) {
|
||||||
RebootDropdownItem(id = R.string.reboot)
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Tune,
|
||||||
|
contentDescription = stringResource(R.string.susfs_config_setting_title)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val pm =
|
// 重启按钮
|
||||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
@Suppress("DEPRECATION")
|
KsuIsValid {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
IconButton(onClick = {
|
||||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
showDropdown = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.PowerSettingsNew,
|
||||||
|
contentDescription = stringResource(id = R.string.reboot)
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(expanded = showDropdown, onDismissRequest = {
|
||||||
|
showDropdown = false
|
||||||
|
}) {
|
||||||
|
RebootDropdownItem(id = R.string.reboot)
|
||||||
|
|
||||||
|
val pm =
|
||||||
|
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||||
|
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||||
|
}
|
||||||
|
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||||
|
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||||
|
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||||
|
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||||
}
|
}
|
||||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
|
||||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -731,7 +742,7 @@ private fun InfoCard(
|
|||||||
systemInfo.seLinuxStatus,
|
systemInfo.seLinuxStatus,
|
||||||
icon = Icons.Default.Security,
|
icon = Icons.Default.Security,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isHideZygiskImplement && !isSimpleMode && systemInfo.zygiskImplement != "None") {
|
if (!isHideZygiskImplement && !isSimpleMode && systemInfo.zygiskImplement != "None") {
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_zygisk_implement),
|
stringResource(R.string.home_zygisk_implement),
|
||||||
@@ -741,7 +752,6 @@ private fun InfoCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isSimpleMode) {
|
if (!isSimpleMode) {
|
||||||
// 根据showKpmInfo决定是否显示KPM信息
|
|
||||||
if (lkmMode != true && !showKpmInfo) {
|
if (lkmMode != true && !showKpmInfo) {
|
||||||
val displayVersion =
|
val displayVersion =
|
||||||
if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {
|
if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ import kotlinx.coroutines.withContext
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
// 菜单项数据类
|
|
||||||
data class ModuleBottomSheetMenuItem(
|
data class ModuleBottomSheetMenuItem(
|
||||||
val icon: ImageVector,
|
val icon: ImageVector,
|
||||||
val titleRes: Int,
|
val titleRes: Int,
|
||||||
@@ -93,7 +92,7 @@ data class ModuleBottomSheetMenuItem(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/5/31.
|
* @date 2025/9/29.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("ResourceType", "AutoboxingStateCreation")
|
@SuppressLint("ResourceType", "AutoboxingStateCreation")
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -102,24 +101,21 @@ data class ModuleBottomSheetMenuItem(
|
|||||||
fun ModuleScreen(navigator: DestinationsNavigator) {
|
fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||||
val viewModel = viewModel<ModuleViewModel>()
|
val viewModel = viewModel<ModuleViewModel>()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val prefs = context.getSharedPreferences("settings",MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val confirmDialog = rememberConfirmDialog()
|
val confirmDialog = rememberConfirmDialog()
|
||||||
var lastClickTime by remember { mutableStateOf(0L) }
|
var lastClickTime by remember { mutableStateOf(0L) }
|
||||||
|
|
||||||
// 签名验证弹窗状态
|
|
||||||
var showSignatureDialog by remember { mutableStateOf(false) }
|
var showSignatureDialog by remember { mutableStateOf(false) }
|
||||||
var signatureDialogMessage by remember { mutableStateOf("") }
|
var signatureDialogMessage by remember { mutableStateOf("") }
|
||||||
var isForceVerificationFailed by remember { mutableStateOf(false) }
|
var isForceVerificationFailed by remember { mutableStateOf(false) }
|
||||||
var pendingInstallAction by remember { mutableStateOf<(() -> Unit)?>(null) }
|
var pendingInstallAction by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||||
|
|
||||||
// 初始化缓存系统
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.initializeCache(context)
|
viewModel.initializeCache(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BottomSheet状态
|
|
||||||
val bottomSheetState = rememberModalBottomSheetState(
|
val bottomSheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
)
|
)
|
||||||
@@ -280,7 +276,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
|
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
|
||||||
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
|
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
|
||||||
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
|
if (viewModel.moduleList.isEmpty() || viewModel.isNeedRefresh) {
|
||||||
viewModel.sortEnabledFirst = prefs.getBoolean("module_sort_enabled_first", false)
|
viewModel.sortEnabledFirst = prefs.getBoolean("module_sort_enabled_first", false)
|
||||||
@@ -291,7 +286,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
val isSafeMode = Natives.isSafeMode
|
val isSafeMode = Natives.isSafeMode
|
||||||
val hasMagisk = hasMagisk()
|
val hasMagisk = hasMagisk()
|
||||||
|
|
||||||
val hideInstallButton = isSafeMode || hasMagisk
|
val hideInstallButton = isSafeMode || hasMagisk
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
@@ -300,7 +294,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
) { viewModel.fetchModuleList() }
|
) { viewModel.fetchModuleList() }
|
||||||
|
|
||||||
// BottomSheet菜单项
|
|
||||||
val bottomSheetMenuItems = remember {
|
val bottomSheetMenuItems = remember {
|
||||||
listOf(
|
listOf(
|
||||||
ModuleBottomSheetMenuItem(
|
ModuleBottomSheetMenuItem(
|
||||||
@@ -478,7 +471,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BottomSheet
|
|
||||||
if (showBottomSheet) {
|
if (showBottomSheet) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
@@ -620,7 +612,6 @@ private fun ModuleBottomSheetContent(
|
|||||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 排序选项
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 24.dp),
|
modifier = Modifier.padding(horizontal = 24.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
@@ -682,7 +673,6 @@ private fun ModuleBottomSheetContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) {
|
private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) {
|
||||||
// 添加交互状态
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val isPressed by interactionSource.collectIsPressedAsState()
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
@@ -810,7 +800,6 @@ private fun ModuleList(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// changelog is not empty, show it and wait for confirm
|
|
||||||
val confirmResult = confirmDialog.awaitConfirm(
|
val confirmResult = confirmDialog.awaitConfirm(
|
||||||
changelogText,
|
changelogText,
|
||||||
content = changelog,
|
content = changelog,
|
||||||
@@ -901,6 +890,7 @@ private fun ModuleList(
|
|||||||
reboot()
|
reboot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
modifier = boxModifier,
|
modifier = boxModifier,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
@@ -1003,7 +993,6 @@ private fun ModuleList(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// fix last item shadow incomplete in LazyColumn
|
|
||||||
Spacer(Modifier.height(1.dp))
|
Spacer(Modifier.height(1.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1011,7 +1000,6 @@ private fun ModuleList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
DownloadListener(context, onInstallModule)
|
DownloadListener(context, onInstallModule)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1044,7 +1032,6 @@ fun ModuleItem(
|
|||||||
val indication = LocalIndication.current
|
val indication = LocalIndication.current
|
||||||
val viewModel = viewModel<ModuleViewModel>()
|
val viewModel = viewModel<ModuleViewModel>()
|
||||||
|
|
||||||
// 使用缓存系统获取模块大小
|
|
||||||
val sizeStr = remember(module.dirId) {
|
val sizeStr = remember(module.dirId) {
|
||||||
viewModel.getModuleSize(module.dirId)
|
viewModel.getModuleSize(module.dirId)
|
||||||
}
|
}
|
||||||
@@ -1152,10 +1139,8 @@ fun ModuleItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = { },
|
||||||
},
|
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
// 长按复制updateJson地址
|
|
||||||
val clipData = ClipData.newPlainText(
|
val clipData = ClipData.newPlainText(
|
||||||
"Update JSON URL",
|
"Update JSON URL",
|
||||||
module.updateJson
|
module.updateJson
|
||||||
@@ -1163,7 +1148,6 @@ fun ModuleItem(
|
|||||||
clipboardManager.setPrimaryClip(clipData)
|
clipboardManager.setPrimaryClip(clipData)
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
|
||||||
// 显示复制成功的提示
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.module_update_json_copied),
|
context.getString(R.string.module_update_json_copied),
|
||||||
@@ -1202,8 +1186,8 @@ fun ModuleItem(
|
|||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
textDecoration = textDecoration,
|
textDecoration = textDecoration,
|
||||||
)
|
)
|
||||||
if (!isHideTagRow) {
|
|
||||||
|
|
||||||
|
if (!isHideTagRow) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
// 文件夹名称和大小标签
|
// 文件夹名称和大小标签
|
||||||
Row(
|
Row(
|
||||||
@@ -1276,8 +1260,7 @@ fun ModuleItem(
|
|||||||
onClick = { onClick(module) },
|
onClick = { onClick(module) },
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
contentPadding = ButtonDefaults.TextButtonContentPadding,
|
||||||
|
) {
|
||||||
) {
|
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
imageVector = Icons.AutoMirrored.Outlined.Wysiwyg,
|
imageVector = Icons.AutoMirrored.Outlined.Wysiwyg,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import java.time.format.DateTimeFormatter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/5/31.
|
* @date 2025/9/29.
|
||||||
*/
|
*/
|
||||||
private val SPACING_SMALL = 3.dp
|
private val SPACING_SMALL = 3.dp
|
||||||
private val SPACING_MEDIUM = 8.dp
|
private val SPACING_MEDIUM = 8.dp
|
||||||
@@ -77,7 +77,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
// containerColor = MaterialTheme.colorScheme.surfaceBright,
|
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(scrollBehavior = scrollBehavior)
|
TopBar(scrollBehavior = scrollBehavior)
|
||||||
},
|
},
|
||||||
@@ -792,7 +791,6 @@ enum class UninstallType(val title: Int, val message: Int, val icon: ImageVector
|
|||||||
fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
|
||||||
return rememberCustomDialog { dismiss ->
|
return rememberCustomDialog { dismiss ->
|
||||||
val options = listOf(
|
val options = listOf(
|
||||||
// UninstallType.TEMPORARY,
|
|
||||||
UninstallType.PERMANENT,
|
UninstallType.PERMANENT,
|
||||||
UninstallType.RESTORE_STOCK_IMAGE
|
UninstallType.RESTORE_STOCK_IMAGE
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ fun SuSFSConfigScreen(
|
|||||||
var showAddAppPathDialog by remember { mutableStateOf(false) }
|
var showAddAppPathDialog by remember { mutableStateOf(false) }
|
||||||
var showAddMountDialog by remember { mutableStateOf(false) }
|
var showAddMountDialog by remember { mutableStateOf(false) }
|
||||||
var showAddUmountDialog by remember { mutableStateOf(false) }
|
var showAddUmountDialog by remember { mutableStateOf(false) }
|
||||||
var showRunUmountDialog by remember { mutableStateOf(false) }
|
|
||||||
var showAddKstatStaticallyDialog by remember { mutableStateOf(false) }
|
var showAddKstatStaticallyDialog by remember { mutableStateOf(false) }
|
||||||
var showAddKstatDialog by remember { mutableStateOf(false) }
|
var showAddKstatDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -1090,12 +1089,12 @@ fun SuSFSConfigScreen(
|
|||||||
.padding(horizontal = 12.dp)
|
.padding(horizontal = 12.dp)
|
||||||
) {
|
) {
|
||||||
// 标签页
|
// 标签页
|
||||||
ScrollableTabRow(
|
PrimaryScrollableTabRow(
|
||||||
selectedTabIndex = allTabs.indexOf(selectedTab),
|
selectedTabIndex = allTabs.indexOf(selectedTab),
|
||||||
edgePadding = 0.dp,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
contentColor = MaterialTheme.colorScheme.onSurface
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
edgePadding = 0.dp
|
||||||
) {
|
) {
|
||||||
allTabs.forEach { tab ->
|
allTabs.forEach { tab ->
|
||||||
Tab(
|
Tab(
|
||||||
@@ -1262,7 +1261,6 @@ fun SuSFSConfigScreen(
|
|||||||
tryUmounts = tryUmounts,
|
tryUmounts = tryUmounts,
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
onAddUmount = { showAddUmountDialog = true },
|
onAddUmount = { showAddUmountDialog = true },
|
||||||
onRunUmount = { showRunUmountDialog = true },
|
|
||||||
onRemoveUmount = { umountEntry ->
|
onRemoveUmount = { umountEntry ->
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -1467,7 +1465,7 @@ private fun BasicSettingsContent(
|
|||||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = scriptLocationExpanded) },
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = scriptLocationExpanded) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.menuAnchor(MenuAnchorType.PrimaryEditable, true),
|
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true),
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
enabled = !isLoading
|
enabled = !isLoading
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -67,16 +67,12 @@ enum class AppPriority(val value: Int) {
|
|||||||
DEFAULT(3) // 默认应用
|
DEFAULT(3) // 默认应用
|
||||||
}
|
}
|
||||||
|
|
||||||
// 菜单项数据类
|
|
||||||
data class BottomSheetMenuItem(
|
data class BottomSheetMenuItem(
|
||||||
val icon: ImageVector,
|
val icon: ImageVector,
|
||||||
val titleRes: Int,
|
val titleRes: Int,
|
||||||
val onClick: () -> Unit
|
val onClick: () -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取应用的优先级
|
|
||||||
*/
|
|
||||||
private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority {
|
private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority {
|
||||||
return when {
|
return when {
|
||||||
app.allowSu -> AppPriority.ROOT
|
app.allowSu -> AppPriority.ROOT
|
||||||
@@ -85,9 +81,6 @@ private fun getAppPriority(app: SuperUserViewModel.AppInfo): AppPriority {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取多选模式的主按钮图标
|
|
||||||
*/
|
|
||||||
private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector {
|
private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector {
|
||||||
return if (isExpanded) {
|
return if (isExpanded) {
|
||||||
Icons.Filled.Close
|
Icons.Filled.Close
|
||||||
@@ -96,9 +89,6 @@ private fun getMultiSelectMainIcon(isExpanded: Boolean): ImageVector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单选模式的主按钮图标
|
|
||||||
*/
|
|
||||||
private fun getSingleSelectMainIcon(isExpanded: Boolean): ImageVector {
|
private fun getSingleSelectMainIcon(isExpanded: Boolean): ImageVector {
|
||||||
return if (isExpanded) {
|
return if (isExpanded) {
|
||||||
Icons.Filled.Close
|
Icons.Filled.Close
|
||||||
@@ -122,17 +112,14 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
// 使用ViewModel中的状态,这些状态现在都会从SharedPreferences中加载并自动保存
|
|
||||||
val selectedCategory = viewModel.selectedCategory
|
val selectedCategory = viewModel.selectedCategory
|
||||||
val currentSortType = viewModel.currentSortType
|
val currentSortType = viewModel.currentSortType
|
||||||
|
|
||||||
// BottomSheet状态
|
|
||||||
val bottomSheetState = rememberModalBottomSheetState(
|
val bottomSheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
)
|
)
|
||||||
var showBottomSheet by remember { mutableStateOf(false) }
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// 添加备份和还原启动器
|
|
||||||
val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState)
|
val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState)
|
||||||
val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState)
|
val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState)
|
||||||
|
|
||||||
@@ -145,8 +132,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
LaunchedEffect(viewModel.search) {
|
LaunchedEffect(viewModel.search) {
|
||||||
if (viewModel.search.isEmpty()) {
|
if (viewModel.search.isEmpty()) {
|
||||||
// 取消自动滚动到顶部的行为
|
// Optional: scroll to top when clearing search
|
||||||
// listState.scrollToItem(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +220,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
apps
|
apps
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算应用数量
|
|
||||||
val appCounts = remember(viewModel.appList, viewModel.showSystemApps) {
|
val appCounts = remember(viewModel.appList, viewModel.showSystemApps) {
|
||||||
mapOf(
|
mapOf(
|
||||||
AppCategory.ALL to viewModel.appList.size,
|
AppCategory.ALL to viewModel.appList.size,
|
||||||
@@ -244,7 +229,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BottomSheet菜单项
|
|
||||||
val bottomSheetMenuItems = remember(viewModel.showSystemApps) {
|
val bottomSheetMenuItems = remember(viewModel.showSystemApps) {
|
||||||
listOf(
|
listOf(
|
||||||
BottomSheetMenuItem(
|
BottomSheetMenuItem(
|
||||||
@@ -299,7 +283,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录FAB展开状态用于图标动画
|
|
||||||
var isFabExpanded by remember { mutableStateOf(false) }
|
var isFabExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -311,7 +294,7 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.superuser))
|
Text(stringResource(R.string.superuser))
|
||||||
// 显示当前分类和应用数量
|
|
||||||
if (selectedCategory != AppCategory.ALL) {
|
if (selectedCategory != AppCategory.ALL) {
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
@@ -413,7 +396,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
buttonSpacing = 72.dp,
|
buttonSpacing = 72.dp,
|
||||||
animationDurationMs = 300,
|
animationDurationMs = 300,
|
||||||
staggerDelayMs = 50,
|
staggerDelayMs = 50,
|
||||||
// 根据模式选择不同的图标
|
|
||||||
mainButtonIcon = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
|
mainButtonIcon = if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
|
||||||
getMultiSelectMainIcon(isFabExpanded)
|
getMultiSelectMainIcon(isFabExpanded)
|
||||||
} else {
|
} else {
|
||||||
@@ -458,7 +440,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当没有应用显示时显示加载动画或空状态
|
|
||||||
if (filteredAndSortedApps.isEmpty()) {
|
if (filteredAndSortedApps.isEmpty()) {
|
||||||
item {
|
item {
|
||||||
Box(
|
Box(
|
||||||
@@ -467,7 +448,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
.height(400.dp),
|
.height(400.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
// 根据加载状态显示不同内容
|
|
||||||
if ((viewModel.isRefreshing || viewModel.appList.isEmpty()) && viewModel.search.isEmpty()) {
|
if ((viewModel.isRefreshing || viewModel.appList.isEmpty()) && viewModel.search.isEmpty()) {
|
||||||
LoadingAnimation(
|
LoadingAnimation(
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -484,7 +464,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BottomSheet
|
|
||||||
if (showBottomSheet) {
|
if (showBottomSheet) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
@@ -594,7 +573,6 @@ private fun BottomSheetContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用分类选项
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp))
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 24.dp))
|
||||||
|
|
||||||
@@ -702,7 +680,6 @@ private fun CategoryChip(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用数量
|
|
||||||
Text(
|
Text(
|
||||||
text = "$appCount apps",
|
text = "$appCount apps",
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
@@ -718,7 +695,6 @@ private fun CategoryChip(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomSheetMenuItemView(menuItem: BottomSheetMenuItem) {
|
private fun BottomSheetMenuItemView(menuItem: BottomSheetMenuItem) {
|
||||||
// 添加交互状态
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val isPressed by interactionSource.collectIsPressedAsState()
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
@@ -897,9 +873,6 @@ fun LabelText(label: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载动画组件
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingAnimation(
|
private fun LoadingAnimation(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -907,7 +880,6 @@ private fun LoadingAnimation(
|
|||||||
) {
|
) {
|
||||||
val infiniteTransition = rememberInfiniteTransition(label = "loading")
|
val infiniteTransition = rememberInfiniteTransition(label = "loading")
|
||||||
|
|
||||||
// 透明度动画
|
|
||||||
val alpha by infiniteTransition.animateFloat(
|
val alpha by infiniteTransition.animateFloat(
|
||||||
initialValue = 0.3f,
|
initialValue = 0.3f,
|
||||||
targetValue = 1f,
|
targetValue = 1f,
|
||||||
@@ -928,7 +900,6 @@ private fun LoadingAnimation(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
// 进度指示器
|
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(200.dp)
|
.width(200.dp)
|
||||||
@@ -940,9 +911,6 @@ private fun LoadingAnimation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 空状态组件
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
@SuppressLint("ModifierParameter")
|
@SuppressLint("ModifierParameter")
|
||||||
private fun EmptyState(
|
private fun EmptyState(
|
||||||
|
|||||||
@@ -4,16 +4,11 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.content.edit
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.dergoogler.mmrl.platform.Platform.Companion.context
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonSyntaxException
|
|
||||||
import com.sukisu.ultra.KernelVersion
|
import com.sukisu.ultra.KernelVersion
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.getKernelVersion
|
import com.sukisu.ultra.getKernelVersion
|
||||||
@@ -21,20 +16,12 @@ import com.sukisu.ultra.ksuApp
|
|||||||
import com.sukisu.ultra.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
companion object {
|
|
||||||
private const val TAG = "HomeViewModel"
|
|
||||||
private const val PREFS_NAME = "home_cache"
|
|
||||||
private const val KEY_SYSTEM_STATUS = "system_status"
|
|
||||||
private const val KEY_SYSTEM_INFO = "system_info"
|
|
||||||
private const val KEY_VERSION_INFO = "version_info"
|
|
||||||
private const val KEY_LAST_UPDATE = "last_update_time"
|
|
||||||
private const val KEY_ERROR_COUNT = "error_count"
|
|
||||||
private const val MAX_ERROR_COUNT = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统状态
|
// 系统状态
|
||||||
data class SystemStatus(
|
data class SystemStatus(
|
||||||
@@ -69,9 +56,7 @@ class HomeViewModel : ViewModel() {
|
|||||||
val zygiskImplement: String = ""
|
val zygiskImplement: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
private val gson = Gson()
|
// 状态变量
|
||||||
private val prefs by lazy { ksuApp.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) }
|
|
||||||
|
|
||||||
var systemStatus by mutableStateOf(SystemStatus())
|
var systemStatus by mutableStateOf(SystemStatus())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -98,199 +83,52 @@ class HomeViewModel : ViewModel() {
|
|||||||
var showKpmInfo by mutableStateOf(false)
|
var showKpmInfo by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private fun clearAllCache() {
|
var isCoreDataLoaded by mutableStateOf(false)
|
||||||
try {
|
private set
|
||||||
prefs.edit { clear() }
|
var isExtendedDataLoaded by mutableStateOf(false)
|
||||||
Log.i(TAG, "All cache cleared successfully")
|
private set
|
||||||
} catch (e: Exception) {
|
var isRefreshing by mutableStateOf(false)
|
||||||
Log.e(TAG, "Error clearing cache", e)
|
private set
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetToDefaults() {
|
private var loadingJobs = mutableListOf<Job>()
|
||||||
systemStatus = SystemStatus()
|
|
||||||
systemInfo = SystemInfo()
|
|
||||||
latestVersionInfo = LatestVersionInfo()
|
|
||||||
isSimpleMode = false
|
|
||||||
isKernelSimpleMode = false
|
|
||||||
isHideVersion = false
|
|
||||||
isHideOtherInfo = false
|
|
||||||
isHideSusfsStatus = false
|
|
||||||
isHideZygiskImplement = false
|
|
||||||
isHideLinkCard = false
|
|
||||||
showKpmInfo = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleError(error: Exception, operation: String) {
|
|
||||||
Log.e(TAG, "Error in $operation", error)
|
|
||||||
|
|
||||||
val errorCount = prefs.getInt(KEY_ERROR_COUNT, 0)
|
|
||||||
val newErrorCount = errorCount + 1
|
|
||||||
|
|
||||||
if (newErrorCount >= MAX_ERROR_COUNT) {
|
|
||||||
Log.w(TAG, "Too many errors ($newErrorCount), clearing cache and resetting")
|
|
||||||
clearAllCache()
|
|
||||||
resetToDefaults()
|
|
||||||
} else {
|
|
||||||
prefs.edit {
|
|
||||||
putInt(KEY_ERROR_COUNT, newErrorCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String?.orSafe(default: String = ""): String {
|
|
||||||
return if (this.isNullOrBlank()) default else this
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T, R> Pair<T?, R?>?.orSafe(default: Pair<T, R>): Pair<T, R> {
|
|
||||||
return if (this?.first == null || this.second == null) default else Pair(this.first!!, this.second!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadUserSettings(context: Context) {
|
fun loadUserSettings(context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false)
|
||||||
isSimpleMode = settingsPrefs.getBoolean("is_simple_mode", false)
|
isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false)
|
||||||
isKernelSimpleMode = settingsPrefs.getBoolean("is_kernel_simple_mode", false)
|
isHideVersion = settingsPrefs.getBoolean("is_hide_version", false)
|
||||||
isHideVersion = settingsPrefs.getBoolean("is_hide_version", false)
|
isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false)
|
||||||
isHideOtherInfo = settingsPrefs.getBoolean("is_hide_other_info", false)
|
isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false)
|
||||||
isHideSusfsStatus = settingsPrefs.getBoolean("is_hide_susfs_status", false)
|
isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false)
|
||||||
isHideLinkCard = settingsPrefs.getBoolean("is_hide_link_card", false)
|
isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false)
|
||||||
isHideZygiskImplement = settingsPrefs.getBoolean("is_hide_zygisk_Implement", false)
|
showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false)
|
||||||
showKpmInfo = settingsPrefs.getBoolean("show_kpm_info", false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
handleError(e, "loadUserSettings")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initializeData() {
|
fun loadCoreData() {
|
||||||
viewModelScope.launch {
|
if (isCoreDataLoaded) return
|
||||||
try {
|
|
||||||
loadCachedData()
|
|
||||||
// 成功加载后重置错误计数
|
|
||||||
prefs.edit {
|
|
||||||
putInt(KEY_ERROR_COUNT, 0)
|
|
||||||
}
|
|
||||||
} catch(e: Exception) {
|
|
||||||
handleError(e, "initializeData")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadCachedData() {
|
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
|
||||||
prefs.getString(KEY_SYSTEM_STATUS, null)?.let { statusJson ->
|
|
||||||
try {
|
|
||||||
val cachedStatus = gson.fromJson(statusJson, SystemStatus::class.java)
|
|
||||||
if (cachedStatus != null) {
|
|
||||||
systemStatus = cachedStatus
|
|
||||||
}
|
|
||||||
} catch (e: JsonSyntaxException) {
|
|
||||||
Log.w(TAG, "Invalid system status JSON, using defaults", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.getString(KEY_SYSTEM_INFO, null)?.let { infoJson ->
|
|
||||||
try {
|
|
||||||
val cachedInfo = gson.fromJson(infoJson, SystemInfo::class.java)
|
|
||||||
if (cachedInfo != null) {
|
|
||||||
systemInfo = cachedInfo
|
|
||||||
}
|
|
||||||
} catch (e: JsonSyntaxException) {
|
|
||||||
Log.w(TAG, "Invalid system info JSON, using defaults", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.getString(KEY_VERSION_INFO, null)?.let { versionJson ->
|
|
||||||
try {
|
|
||||||
val cachedVersion = gson.fromJson(versionJson, LatestVersionInfo::class.java)
|
|
||||||
if (cachedVersion != null) {
|
|
||||||
latestVersionInfo = cachedVersion
|
|
||||||
}
|
|
||||||
} catch (e: JsonSyntaxException) {
|
|
||||||
Log.w(TAG, "Invalid version info JSON, using defaults", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error loading cached data", e)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun fetchAndSaveData() {
|
|
||||||
try {
|
|
||||||
fetchSystemStatus()
|
|
||||||
fetchSystemInfo()
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
prefs.edit {
|
|
||||||
putString(KEY_SYSTEM_STATUS, gson.toJson(systemStatus))
|
|
||||||
putString(KEY_SYSTEM_INFO, gson.toJson(systemInfo))
|
|
||||||
putString(KEY_VERSION_INFO, gson.toJson(latestVersionInfo))
|
|
||||||
putLong(KEY_LAST_UPDATE, System.currentTimeMillis())
|
|
||||||
putInt(KEY_ERROR_COUNT, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
handleError(e, "fetchAndSaveData")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkForUpdates(context: Context) {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
|
||||||
val checkUpdate = settingsPrefs.getBoolean("check_update", true)
|
|
||||||
|
|
||||||
if (checkUpdate) {
|
|
||||||
val newVersionInfo = checkNewVersion()
|
|
||||||
latestVersionInfo = newVersionInfo
|
|
||||||
prefs.edit {
|
|
||||||
putString(KEY_VERSION_INFO, gson.toJson(newVersionInfo))
|
|
||||||
putLong(KEY_LAST_UPDATE, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
handleError(e, "checkForUpdates")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshAllData(context: Context) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
fetchAndSaveData()
|
|
||||||
checkForUpdates(context)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
handleError(e, "refreshAllData")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun fetchSystemStatus() {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
try {
|
try {
|
||||||
val kernelVersion = getKernelVersion()
|
val kernelVersion = getKernelVersion()
|
||||||
val isManager = try {
|
val isManager = try {
|
||||||
Natives.becomeManager(ksuApp.packageName.orSafe("com.sukisu.ultra"))
|
Natives.becomeManager(ksuApp.packageName ?: "com.sukisu.ultra")
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to become manager", e)
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val ksuVersion = if (isManager) {
|
val ksuVersion = if (isManager) {
|
||||||
try {
|
try {
|
||||||
Natives.version
|
Natives.version
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to get KSU version", e)
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
val fullVersion = try {
|
val fullVersion = try {
|
||||||
Natives.getFullVersion().orSafe("Unknown")
|
Natives.getFullVersion()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to get full version", e)
|
|
||||||
"Unknown"
|
"Unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,8 +147,7 @@ class HomeViewModel : ViewModel() {
|
|||||||
} else {
|
} else {
|
||||||
fullVersion
|
fullVersion
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to process full version", e)
|
|
||||||
fullVersion
|
fullVersion
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -322,30 +159,26 @@ class HomeViewModel : ViewModel() {
|
|||||||
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) {
|
if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) {
|
||||||
Natives.isLkmMode
|
Natives.isLkmMode
|
||||||
} else null
|
} else null
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to get LKM mode", e)
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isRootAvailable = try {
|
val isRootAvailable = try {
|
||||||
rootAvailable()
|
rootAvailable()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to check root availability", e)
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val isKpmConfigured = try {
|
val isKpmConfigured = try {
|
||||||
Natives.isKPMEnabled()
|
Natives.isKPMEnabled()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to check KPM status", e)
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
val requireNewKernel = try {
|
val requireNewKernel = try {
|
||||||
isManager && Natives.requireNewKernel()
|
isManager && Natives.requireNewKernel()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to check kernel requirement", e)
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,198 +192,255 @@ class HomeViewModel : ViewModel() {
|
|||||||
isKpmConfigured = isKpmConfigured,
|
isKpmConfigured = isKpmConfigured,
|
||||||
requireNewKernel = requireNewKernel
|
requireNewKernel = requireNewKernel
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error fetching system status", e)
|
isCoreDataLoaded = true
|
||||||
throw e
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadingJobs.add(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
fun loadExtendedData(context: Context) {
|
||||||
private suspend fun fetchSystemInfo() {
|
if (isExtendedDataLoaded) return
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
|
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val uname = try {
|
// 分批加载
|
||||||
Os.uname()
|
delay(50)
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get uname", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val kpmVersion = try {
|
val basicInfo = loadBasicSystemInfo(context)
|
||||||
getKpmVersion().orSafe("Unknown")
|
systemInfo = systemInfo.copy(
|
||||||
} catch (e: Exception) {
|
kernelRelease = basicInfo.first,
|
||||||
Log.w(TAG, "Failed to get kpm version", e)
|
androidVersion = basicInfo.second,
|
||||||
"Unknown"
|
deviceModel = basicInfo.third,
|
||||||
}
|
managerVersion = basicInfo.fourth,
|
||||||
|
seLinuxStatus = basicInfo.fifth
|
||||||
val suSFS = try {
|
|
||||||
getSuSFS().orSafe("Unknown")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get SuSFS", e)
|
|
||||||
"Unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
var suSFSVersion = ""
|
|
||||||
var suSFSVariant = ""
|
|
||||||
var suSFSFeatures = ""
|
|
||||||
var susSUMode = ""
|
|
||||||
|
|
||||||
if (suSFS == "Supported") {
|
|
||||||
suSFSVersion = try {
|
|
||||||
getSuSFSVersion().orSafe("")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get SuSFS version", e)
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suSFSVersion.isNotEmpty()) {
|
|
||||||
suSFSVariant = try {
|
|
||||||
getSuSFSVariant().orSafe("")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get SuSFS variant", e)
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
suSFSFeatures = try {
|
|
||||||
getSuSFSFeatures().orSafe("")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get SuSFS features", e)
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
val isSUS_SU = suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
|
|
||||||
if (isSUS_SU) {
|
|
||||||
susSUMode = try {
|
|
||||||
susfsSUS_SU_Mode()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get SUS SU mode", e)
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取动态管理器状态和管理器列表
|
|
||||||
val dynamicSignConfig = try {
|
|
||||||
Natives.getDynamicManager()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get dynamic manager config", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val isDynamicSignEnabled = try {
|
|
||||||
dynamicSignConfig?.isValid() == true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to check dynamic manager validity", e)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
val managersList = if (isDynamicSignEnabled) {
|
|
||||||
try {
|
|
||||||
Natives.getManagersList()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get managers list", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceModel = try {
|
|
||||||
getDeviceModel().orSafe("Unknown")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get device model", e)
|
|
||||||
"Unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
val managerVersion = try {
|
|
||||||
getManagerVersion(ksuApp.applicationContext).orSafe(Pair("Unknown", 0L))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get manager version", e)
|
|
||||||
Pair("Unknown", 0L)
|
|
||||||
}
|
|
||||||
|
|
||||||
val seLinuxStatus = try {
|
|
||||||
getSELinuxStatus(context).orSafe("Unknown")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get SELinux status", e)
|
|
||||||
"Unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
val superuserCount = try {
|
|
||||||
getSuperuserCount()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get superuser count", e)
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
val moduleCount = try {
|
|
||||||
getModuleCount()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get module count", e)
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
val kpmModuleCount = try {
|
|
||||||
getKpmModuleCount()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get kpm module count", e)
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
val zygiskImplement = try {
|
|
||||||
getZygiskImplement().orSafe("None")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "Failed to get Zygisk implement", e)
|
|
||||||
"None"
|
|
||||||
}
|
|
||||||
|
|
||||||
systemInfo = SystemInfo(
|
|
||||||
kernelRelease = uname?.release.orSafe("Unknown"),
|
|
||||||
androidVersion = Build.VERSION.RELEASE.orSafe("Unknown"),
|
|
||||||
deviceModel = deviceModel,
|
|
||||||
managerVersion = managerVersion,
|
|
||||||
seLinuxStatus = seLinuxStatus,
|
|
||||||
kpmVersion = kpmVersion,
|
|
||||||
suSFSStatus = suSFS,
|
|
||||||
suSFSVersion = suSFSVersion,
|
|
||||||
suSFSVariant = suSFSVariant,
|
|
||||||
suSFSFeatures = suSFSFeatures,
|
|
||||||
susSUMode = susSUMode,
|
|
||||||
superuserCount = superuserCount,
|
|
||||||
moduleCount = moduleCount,
|
|
||||||
kpmModuleCount = kpmModuleCount,
|
|
||||||
managersList = managersList,
|
|
||||||
isDynamicSignEnabled = isDynamicSignEnabled,
|
|
||||||
zygiskImplement = zygiskImplement
|
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Error fetching system info", e)
|
delay(100)
|
||||||
throw e
|
|
||||||
|
// 加载模块信息
|
||||||
|
if (!isSimpleMode) {
|
||||||
|
val moduleInfo = loadModuleInfo()
|
||||||
|
systemInfo = systemInfo.copy(
|
||||||
|
kpmVersion = moduleInfo.first,
|
||||||
|
superuserCount = moduleInfo.second,
|
||||||
|
moduleCount = moduleInfo.third,
|
||||||
|
kpmModuleCount = moduleInfo.fourth,
|
||||||
|
zygiskImplement = moduleInfo.fifth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
// 加载SuSFS信息
|
||||||
|
if (!isHideSusfsStatus) {
|
||||||
|
val suSFSInfo = loadSuSFSInfo()
|
||||||
|
systemInfo = systemInfo.copy(
|
||||||
|
suSFSStatus = suSFSInfo.first,
|
||||||
|
suSFSVersion = suSFSInfo.second,
|
||||||
|
suSFSVariant = suSFSInfo.third,
|
||||||
|
suSFSFeatures = suSFSInfo.fourth,
|
||||||
|
susSUMode = suSFSInfo.fifth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(100)
|
||||||
|
|
||||||
|
// 加载管理器列表
|
||||||
|
val managerInfo = loadManagerInfo()
|
||||||
|
systemInfo = systemInfo.copy(
|
||||||
|
managersList = managerInfo.first,
|
||||||
|
isDynamicSignEnabled = managerInfo.second
|
||||||
|
)
|
||||||
|
|
||||||
|
isExtendedDataLoaded = true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// 静默处理错误
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadingJobs.add(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshData(context: Context) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
isRefreshing = true
|
||||||
|
|
||||||
|
// 取消正在进行的加载任务
|
||||||
|
loadingJobs.forEach { it.cancel() }
|
||||||
|
loadingJobs.clear()
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
isCoreDataLoaded = false
|
||||||
|
isExtendedDataLoaded = false
|
||||||
|
|
||||||
|
// 重新加载
|
||||||
|
loadCoreData()
|
||||||
|
delay(100)
|
||||||
|
loadExtendedData(context)
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
val settingsPrefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val checkUpdate = settingsPrefs.getBoolean("check_update", true)
|
||||||
|
if (checkUpdate) {
|
||||||
|
try {
|
||||||
|
val newVersionInfo = withContext(Dispatchers.IO) {
|
||||||
|
checkNewVersion()
|
||||||
|
}
|
||||||
|
latestVersionInfo = newVersionInfo
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDeviceInfo(): String {
|
private suspend fun loadBasicSystemInfo(context: Context): Tuple5<String, String, String, Pair<String, Long>, String> {
|
||||||
return try {
|
return withContext(Dispatchers.IO) {
|
||||||
var manufacturer = Build.MANUFACTURER.orSafe("Unknown")
|
val uname = try {
|
||||||
manufacturer = manufacturer[0].uppercaseChar().toString() + manufacturer.substring(1)
|
Os.uname()
|
||||||
|
} catch (_: Exception) {
|
||||||
val brand = Build.BRAND.orSafe("")
|
null
|
||||||
if (brand.isNotEmpty() && !brand.equals(Build.MANUFACTURER, ignoreCase = true)) {
|
|
||||||
manufacturer += " " + brand[0].uppercaseChar() + brand.substring(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val model = Build.MODEL.orSafe("")
|
val deviceModel = try {
|
||||||
if (model.isNotEmpty()) {
|
getDeviceModel()
|
||||||
manufacturer += " $model "
|
} catch (_: Exception) {
|
||||||
|
"Unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
manufacturer
|
val managerVersion = try {
|
||||||
} catch (e: Exception) {
|
getManagerVersion(context)
|
||||||
Log.w(TAG, "Failed to get device info", e)
|
} catch (_: Exception) {
|
||||||
"Unknown Device"
|
Pair("Unknown", 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
val seLinuxStatus = try {
|
||||||
|
getSELinuxStatus(ksuApp.applicationContext)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple5(
|
||||||
|
uname?.release ?: "Unknown",
|
||||||
|
Build.VERSION.RELEASE ?: "Unknown",
|
||||||
|
deviceModel,
|
||||||
|
managerVersion,
|
||||||
|
seLinuxStatus
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadModuleInfo(): Tuple5<String, Int, Int, Int, String> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val kpmVersion = try {
|
||||||
|
getKpmVersion()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
val superuserCount = try {
|
||||||
|
getSuperuserCount()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleCount = try {
|
||||||
|
getModuleCount()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
val kpmModuleCount = try {
|
||||||
|
getKpmModuleCount()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
val zygiskImplement = try {
|
||||||
|
getZygiskImplement()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"None"
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple5(kpmVersion, superuserCount, moduleCount, kpmModuleCount, zygiskImplement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadSuSFSInfo(): Tuple5<String, String, String, String, String> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val suSFS = try {
|
||||||
|
getSuSFS()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suSFS != "Supported") {
|
||||||
|
return@withContext Tuple5(suSFS, "", "", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
val suSFSVersion = try {
|
||||||
|
getSuSFSVersion()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suSFSVersion.isEmpty()) {
|
||||||
|
return@withContext Tuple5(suSFS, "", "", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
val suSFSVariant = try {
|
||||||
|
getSuSFSVariant()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val suSFSFeatures = try {
|
||||||
|
getSuSFSFeatures()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val susSUMode = if (suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU") {
|
||||||
|
try {
|
||||||
|
susfsSUS_SU_Mode()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple5(suSFS, suSFSVersion, suSFSVariant, suSFSFeatures, susSUMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadManagerInfo(): Pair<Natives.ManagersList?, Boolean> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val dynamicSignConfig = try {
|
||||||
|
Natives.getDynamicManager()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDynamicSignEnabled = try {
|
||||||
|
dynamicSignConfig?.isValid() == true
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
val managersList = if (isDynamicSignEnabled) {
|
||||||
|
try {
|
||||||
|
Natives.getManagersList()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair(managersList, isDynamicSignEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,10 +450,10 @@ class HomeViewModel : ViewModel() {
|
|||||||
val systemProperties = Class.forName("android.os.SystemProperties")
|
val systemProperties = Class.forName("android.os.SystemProperties")
|
||||||
val getMethod = systemProperties.getMethod("get", String::class.java, String::class.java)
|
val getMethod = systemProperties.getMethod("get", String::class.java, String::class.java)
|
||||||
val marketNameKeys = listOf(
|
val marketNameKeys = listOf(
|
||||||
"ro.product.marketname", // Xiaomi
|
"ro.product.marketname",
|
||||||
"ro.vendor.oplus.market.name", // Oppo, OnePlus, Realme
|
"ro.vendor.oplus.market.name",
|
||||||
"ro.vivo.market.name", // Vivo
|
"ro.vivo.market.name",
|
||||||
"ro.config.marketing_name" // Huawei
|
"ro.config.marketing_name"
|
||||||
)
|
)
|
||||||
var result = getDeviceInfo()
|
var result = getDeviceInfo()
|
||||||
for (key in marketNameKeys) {
|
for (key in marketNameKeys) {
|
||||||
@@ -573,26 +463,60 @@ class HomeViewModel : ViewModel() {
|
|||||||
result = marketName
|
result = marketName
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Failed to get market name for key: $key", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
} catch (e: Exception) {
|
} catch (
|
||||||
Log.w(TAG, "Error getting device model", e)
|
|
||||||
|
_: Exception) {
|
||||||
getDeviceInfo()
|
getDeviceInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getDeviceInfo(): String {
|
||||||
|
return try {
|
||||||
|
var manufacturer = Build.MANUFACTURER ?: "Unknown"
|
||||||
|
manufacturer = manufacturer[0].uppercaseChar().toString() + manufacturer.substring(1)
|
||||||
|
|
||||||
|
val brand = Build.BRAND ?: ""
|
||||||
|
if (brand.isNotEmpty() && !brand.equals(Build.MANUFACTURER, ignoreCase = true)) {
|
||||||
|
manufacturer += " " + brand[0].uppercaseChar() + brand.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val model = Build.MODEL ?: ""
|
||||||
|
if (model.isNotEmpty()) {
|
||||||
|
manufacturer += " $model "
|
||||||
|
}
|
||||||
|
|
||||||
|
manufacturer
|
||||||
|
} catch (_: Exception) {
|
||||||
|
"Unknown Device"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getManagerVersion(context: Context): Pair<String, Long> {
|
private fun getManagerVersion(context: Context): Pair<String, Long> {
|
||||||
return try {
|
return try {
|
||||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo)
|
val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo)
|
||||||
val versionName = packageInfo.versionName.orSafe("Unknown")
|
val versionName = packageInfo.versionName ?: "Unknown"
|
||||||
Pair(versionName, versionCode)
|
Pair(versionName, versionCode)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.w(TAG, "Error getting manager version", e)
|
|
||||||
Pair("Unknown", 0L)
|
Pair("Unknown", 0L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Tuple5<T1, T2, T3, T4, T5>(
|
||||||
|
val first: T1,
|
||||||
|
val second: T2,
|
||||||
|
val third: T3,
|
||||||
|
val fourth: T4,
|
||||||
|
val fifth: T5
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
loadingJobs.forEach { it.cancel() }
|
||||||
|
loadingJobs.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user