From 13b1aad4b845d81c204ac5b574dc4b33b742f1b0 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sat, 31 May 2025 17:39:24 +0800 Subject: [PATCH] manager: Optimizing Home Performance - Reorganize Home structure using MVVM architecture pattern to separate UI and data logic --- .../com/sukisu/ultra/flash/KernelFlash.kt | 5 + .../java/com/sukisu/ultra/ui/screen/Flash.kt | 4 + .../java/com/sukisu/ultra/ui/screen/Home.kt | 351 ++++++++---------- .../com/sukisu/ultra/ui/screen/Install.kt | 4 +- .../com/sukisu/ultra/ui/screen/KernelFlash.kt | 4 + .../java/com/sukisu/ultra/ui/screen/Module.kt | 5 +- .../sukisu/ultra/ui/screen/MoreSettings.kt | 4 + .../com/sukisu/ultra/ui/screen/Settings.kt | 6 +- .../com/sukisu/ultra/ui/screen/SuperUser.kt | 4 + .../sukisu/ultra/ui/util/SELinuxChecker.kt | 18 +- .../ultra/ui/viewmodel/HomeViewModel.kt | 272 ++++++++++++++ .../sukisu/ultra/ui/viewmodel/KpmViewModel.kt | 4 + .../ultra/ui/viewmodel/ModuleViewModel.kt | 4 + .../ultra/ui/viewmodel/SuperUserViewModel.kt | 5 + 14 files changed, 476 insertions(+), 214 deletions(-) create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt diff --git a/manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt b/manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt index 48be9668..f584e9ec 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt @@ -27,6 +27,11 @@ import java.io.File import java.io.FileOutputStream import java.io.IOException + +/** + * @author ShirkNeko + * @date 2025/5/31. + */ data class FlashState( val isFlashing: Boolean = false, val isCompleted: Boolean = false, diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt index 3bd9d90a..09d8ed90 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Flash.kt @@ -53,6 +53,10 @@ import java.io.File import java.text.SimpleDateFormat import java.util.* +/** + * @author ShirkNeko + * @date 2025/5/31. + */ enum class FlashingStatus { FLASHING, SUCCESS, diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt index 736b3054..5e9db3af 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt @@ -58,17 +58,16 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.composed @@ -83,16 +82,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.pm.PackageInfoCompat +import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.sukisu.ultra.KernelVersion import com.sukisu.ultra.Natives -import com.sukisu.ultra.Natives.isKPMEnabled import com.sukisu.ultra.R -import com.sukisu.ultra.getKernelVersion -import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ui.component.KsuIsValid import com.sukisu.ultra.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.theme.CardConfig @@ -102,65 +99,43 @@ import com.sukisu.ultra.ui.util.checkNewVersion import com.sukisu.ultra.ui.util.getKpmModuleCount import com.sukisu.ultra.ui.util.getKpmVersion import com.sukisu.ultra.ui.util.getModuleCount -import com.sukisu.ultra.ui.util.getSELinuxStatus -import com.sukisu.ultra.ui.util.getSuSFS -import com.sukisu.ultra.ui.util.getSuSFSFeatures -import com.sukisu.ultra.ui.util.getSuSFSVariant -import com.sukisu.ultra.ui.util.getSuSFSVersion import com.sukisu.ultra.ui.util.getSuperuserCount import com.sukisu.ultra.ui.util.module.LatestVersionInfo import com.sukisu.ultra.ui.util.reboot -import com.sukisu.ultra.ui.util.rootAvailable -import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode +import com.sukisu.ultra.ui.viewmodel.HomeViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.random.Random -@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) +/** + * @author ShirkNeko + * @date 2025/5/31. + */ +@OptIn(ExperimentalMaterial3Api::class) @Destination(start = true) @Composable fun HomeScreen(navigator: DestinationsNavigator) { val context = LocalContext.current - var isSimpleMode by rememberSaveable { mutableStateOf(false) } - var isHideVersion by rememberSaveable { mutableStateOf(false) } - var isHideOtherInfo by rememberSaveable { mutableStateOf(false) } - var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) } - var isHideLinkCard by rememberSaveable { mutableStateOf(false) } - var showKpmInfo by rememberSaveable { mutableStateOf(true) } + val viewModel = viewModel() + val coroutineScope = rememberCoroutineScope() - // 从 SharedPreferences 加载简洁模式状态 LaunchedEffect(Unit) { - isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_simple_mode", false) - - isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_hide_version", false) - - isHideOtherInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_hide_other_info", false) - - isHideSusfsStatus = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_hide_susfs_status", false) - - isHideLinkCard = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_hide_link_card", false) - - showKpmInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("show_kpm_info", true) + // 初始化加载用户设置 + viewModel.loadUserSettings(context) + // 初始化数据 + viewModel.initializeData() + // 检查更新 + viewModel.checkForUpdates(context) } - val kernelVersion = getKernelVersion() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollState = rememberScrollState() - val debounceTime = 100L - var lastScrollTime by remember { mutableLongStateOf(0L) } Scaffold( topBar = { TopBar( - kernelVersion, + kernelVersion = viewModel.systemStatus.kernelVersion, onInstallClick = { navigator.navigate(InstallScreenDestination) }, scrollBehavior = scrollBehavior ) @@ -169,71 +144,72 @@ fun HomeScreen(navigator: DestinationsNavigator) { WindowInsetsSides.Top + WindowInsetsSides.Horizontal ) ) { innerPadding -> - Column( - modifier = Modifier - .padding(innerPadding) - .disableOverscroll() - .nestedScroll(scrollBehavior.nestedScrollConnection) - .verticalScroll(scrollState) - .padding(top = 12.dp) - .padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - val isManager = Natives.becomeManager(ksuApp.packageName) - val ksuVersion = if (isManager) Natives.version else null - val lkmMode = ksuVersion?.let { - if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null - } - - StatusCard(kernelVersion, ksuVersion, lkmMode) { - navigator.navigate(InstallScreenDestination) - } - - if (isManager && Natives.requireNewKernel()) { - WarningCard( - stringResource(id = R.string.require_kernel_version).format( - ksuVersion, Natives.MINIMAL_SUPPORTED_KERNEL - ) - ) - } - - if (ksuVersion != null && !rootAvailable()) { - WarningCard( - stringResource(id = R.string.grant_root_failed) - ) - } - - val checkUpdate = - LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("check_update", true) - if (checkUpdate) { - UpdateCard() - } - - InfoCard() - - if (!isSimpleMode) { - if (!isHideLinkCard) { - ContributionCard() - DonateCard() - LearnMoreCard() + PullToRefreshBox( + onRefresh = { + coroutineScope.launch { + viewModel.refreshAllData(context) } - } - Spacer(Modifier.height(16.dp)) - } - } + }, + isRefreshing = viewModel.isRefreshing + ) { + Column( + modifier = Modifier + .padding(innerPadding) + .disableOverscroll() + .nestedScroll(scrollBehavior.nestedScrollConnection) + .verticalScroll(scrollState) + .padding(top = 12.dp) + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + StatusCard( + systemStatus = viewModel.systemStatus, + onClickInstall = { + navigator.navigate(InstallScreenDestination) + } + ) - LaunchedEffect(scrollState) { - snapshotFlow { scrollState.isScrollInProgress } - .debounce(debounceTime) - .collect { isScrolling -> - if (isScrolling) { - val currentTime = System.currentTimeMillis() - if (currentTime - lastScrollTime > debounceTime) { - lastScrollTime = currentTime + if (viewModel.systemStatus.requireNewKernel) { + WarningCard( + stringResource(id = R.string.require_kernel_version).format( + viewModel.systemStatus.ksuVersion, + Natives.MINIMAL_SUPPORTED_KERNEL + ) + ) + } + + if (viewModel.systemStatus.ksuVersion != null && !viewModel.systemStatus.isRootAvailable) { + WarningCard( + stringResource(id = R.string.grant_root_failed) + ) + } + + val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + .getBoolean("check_update", true) + if (checkUpdate) { + UpdateCard() + } + + InfoCard( + systemInfo = viewModel.systemInfo, + isSimpleMode = viewModel.isSimpleMode, + isHideVersion = viewModel.isHideVersion, + isHideOtherInfo = viewModel.isHideOtherInfo, + isHideSusfsStatus = viewModel.isHideSusfsStatus, + showKpmInfo = viewModel.showKpmInfo, + lkmMode = viewModel.systemStatus.lkmMode, + ) + + if (!viewModel.isSimpleMode) { + if (!viewModel.isHideLinkCard) { + ContributionCard() + DonateCard() + LearnMoreCard() } } + Spacer(Modifier.height(16.dp)) } + } } } @@ -357,9 +333,7 @@ private fun TopBar( @Composable private fun StatusCard( - kernelVersion: KernelVersion, - ksuVersion: Int?, - lkmMode: Boolean?, + systemStatus: HomeViewModel.SystemStatus, onClickInstall: () -> Unit = {} ) { ElevatedCard( @@ -378,7 +352,7 @@ private fun StatusCard( modifier = Modifier .fillMaxWidth() .clickable { - if (rootAvailable() || kernelVersion.isGKI()) { + if (systemStatus.isRootAvailable || systemStatus.kernelVersion.isGKI()) { onClickInstall() } } @@ -386,12 +360,12 @@ private fun StatusCard( verticalAlignment = Alignment.CenterVertically ) { when { - ksuVersion != null -> { + systemStatus.ksuVersion != null -> { val workingModeText = when { - lkmMode == true -> "LKM" - lkmMode == null && kernelVersion.isGKI1() -> "GKI1.0" - lkmMode == false || kernelVersion.isGKI() -> "GKI2.0" + systemStatus.lkmMode == true -> "LKM" + systemStatus.lkmMode == null && systemStatus.kernelVersion.isGKI1() -> "GKI1.0" + systemStatus.lkmMode == false || systemStatus.kernelVersion.isGKI() -> "GKI2.0" else -> "N-GKI" } @@ -464,7 +438,7 @@ private fun StatusCard( if (!isHideVersion) { Spacer(Modifier.height(4.dp)) Text( - text = stringResource(R.string.home_working_version, ksuVersion), + text = stringResource(R.string.home_working_version, systemStatus.ksuVersion), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -498,7 +472,7 @@ private fun StatusCard( } } - kernelVersion.isGKI() -> { + systemStatus.kernelVersion.isGKI() -> { Icon( Icons.Outlined.Warning, contentDescription = stringResource(R.string.home_not_installed), @@ -717,14 +691,15 @@ fun DonateCard() { } @Composable -private fun InfoCard() { - val lkmMode = Natives.isLkmMode - val context = LocalContext.current - val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_simple_mode", false) - val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("show_kpm_info", true) - +private fun InfoCard( + systemInfo: HomeViewModel.SystemInfo, + isSimpleMode: Boolean, + isHideVersion: Boolean, + isHideOtherInfo: Boolean, + isHideSusfsStatus: Boolean, + showKpmInfo: Boolean, + lkmMode: Boolean? +) { ElevatedCard( colors = getCardColors(MaterialTheme.colorScheme.surfaceContainer), elevation = CardDefaults.cardElevation(defaultElevation = cardElevation), @@ -741,17 +716,13 @@ private fun InfoCard() { modifier = Modifier .fillMaxWidth() .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp), - ) withContext@{ - val contents = StringBuilder() - val uname = Os.uname() - + ) { @Composable fun InfoCardItem( label: String, content: String, icon: ImageVector = Icons.Default.Info ) { - contents.appendLine(label).appendLine(content).appendLine() Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -787,55 +758,49 @@ private fun InfoCard() { InfoCardItem( stringResource(R.string.home_kernel), - uname.release, + systemInfo.kernelRelease, icon = Icons.Default.Memory, ) if (!isSimpleMode) { - val androidVersion = Build.VERSION.RELEASE InfoCardItem( stringResource(R.string.home_android_version), - androidVersion, + systemInfo.androidVersion, icon = Icons.Default.Android, ) } - val deviceModel = getDeviceModel() InfoCardItem( stringResource(R.string.home_device_model), - deviceModel, + systemInfo.deviceModel, icon = Icons.Default.PhoneAndroid, ) - val managerVersion = getManagerVersion(context) InfoCardItem( stringResource(R.string.home_manager_version), - "${managerVersion.first} (${managerVersion.second})", + "${systemInfo.managerVersion.first} (${systemInfo.managerVersion.second})", icon = Icons.Default.SettingsSuggest, ) InfoCardItem( stringResource(R.string.home_selinux_status), - getSELinuxStatus(), + systemInfo.seLinuxStatus, icon = Icons.Default.Security, ) if (!isSimpleMode) { if (lkmMode != true) { - val kpmVersion = getKpmVersion() - val isKpmConfigured = checkKPMEnabled() - // 根据showKpmInfo决定是否显示KPM信息 if (showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) { - val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) { - val statusText = if (isKpmConfigured) { + val displayVersion = if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) { + val statusText = if (Natives.isKPMEnabled()) { stringResource(R.string.kernel_patched) } else { stringResource(R.string.kernel_not_enabled) } "${stringResource(R.string.not_supported)} ($statusText)" } else { - "${stringResource(R.string.supported)} ($kpmVersion)" + "${stringResource(R.string.supported)} (${systemInfo.kpmVersion})" } InfoCardItem( @@ -847,22 +812,16 @@ private fun InfoCard() { } } - val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) - .getBoolean("is_hide_susfs_status", false) - if ((!isSimpleMode) && (!isHideSusfsStatus)) { - val suSFS = getSuSFS() - if (suSFS == "Supported") { - val suSFSVersion = getSuSFSVersion() - if (suSFSVersion.isNotEmpty()) { - val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU" + if (systemInfo.suSFSStatus == "Supported") { + if (systemInfo.suSFSVersion.isNotEmpty()) { + val isSUS_SU = systemInfo.suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU" val infoText = buildString { - append(suSFSVersion) - append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})") + append(systemInfo.suSFSVersion) + append(if (isSUS_SU) " (${systemInfo.suSFSVariant})" else " (${stringResource(R.string.manual_hook)})") if (isSUS_SU) { - val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" } - if (susSUMode.isNotEmpty()) { - append(" ${stringResource(R.string.sus_su_mode)} $susSUMode") + if (systemInfo.susSUMode.isNotEmpty()) { + append(" ${stringResource(R.string.sus_su_mode)} ${systemInfo.susSUMode}") } } } @@ -889,10 +848,45 @@ fun getManagerVersion(context: Context): Pair { @Composable private fun StatusCardPreview() { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - StatusCard(KernelVersion(5, 10, 101), 1, null) - StatusCard(KernelVersion(5, 10, 101), 20000, true) - StatusCard(KernelVersion(5, 10, 101), null, true) - StatusCard(KernelVersion(4, 10, 101), null, false) + StatusCard( + HomeViewModel.SystemStatus( + isManager = true, + ksuVersion = 1, + lkmMode = null, + kernelVersion = KernelVersion(5, 10, 101), + isRootAvailable = true + ) + ) + + StatusCard( + HomeViewModel.SystemStatus( + isManager = true, + ksuVersion = 20000, + lkmMode = true, + kernelVersion = KernelVersion(5, 10, 101), + isRootAvailable = true + ) + ) + + StatusCard( + HomeViewModel.SystemStatus( + isManager = false, + ksuVersion = null, + lkmMode = true, + kernelVersion = KernelVersion(5, 10, 101), + isRootAvailable = false + ) + ) + + StatusCard( + HomeViewModel.SystemStatus( + isManager = false, + ksuVersion = null, + lkmMode = false, + kernelVersion = KernelVersion(4, 10, 101), + isRootAvailable = false + ) + ) } } @@ -908,47 +902,6 @@ private fun WarningCardPreview() { } } -private object DeviceModelCache { - @Volatile - private var cachedModel: String? = null - - fun getDeviceModel(): String { - return cachedModel ?: synchronized(this) { - cachedModel ?: try { - 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 - ) - var result = Build.DEVICE - for (key in marketNameKeys) { - val marketName = getMethod.invoke(null, key, "") as String - if (marketName.isNotEmpty()) { - result = marketName - break - } - } - result - } catch (_: Exception) { - Build.DEVICE - }.also { cachedModel = it } - } - } -} -// 获取设备型号 -@SuppressLint("PrivateApi") -private fun getDeviceModel(): String { - return DeviceModelCache.getDeviceModel() -} - -// 检查KPM是否存在 -private fun checkKPMEnabled(): Boolean { - return isKPMEnabled() -} - @SuppressLint("UnnecessaryComposedModifier") fun Modifier.disableOverscroll(): Modifier = composed { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt index c7965ac5..3590a4a8 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt @@ -94,8 +94,8 @@ import com.sukisu.ultra.ui.util.rootAvailable import com.sukisu.ultra.getKernelVersion /** - * @author weishu - * @date 2024/3/12. + * @author ShirkNeko + * @date 2025/5/31. */ @OptIn(ExperimentalMaterial3Api::class) @Destination diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/KernelFlash.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/KernelFlash.kt index a8fc770a..5d6701cb 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/KernelFlash.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/KernelFlash.kt @@ -43,6 +43,10 @@ import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.key import com.sukisu.ultra.ui.theme.CardConfig +/** + * @author ShirkNeko + * @date 2025/5/31. + */ private object KernelFlashStateHolder { var currentState: HorizonKernelState? = null var currentUri: Uri? = null diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt index f909323c..e10e980b 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt @@ -75,7 +75,10 @@ import androidx.core.net.toUri import com.dergoogler.mmrl.platform.model.ModuleConfig import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig - +/** + * @author ShirkNeko + * @date 2025/5/31. + */ @OptIn(ExperimentalMaterial3Api::class) @Destination @Composable diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt index 3ccb6fbe..1b0c146d 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/MoreSettings.kt @@ -84,6 +84,10 @@ import kotlinx.coroutines.launch import java.util.Locale import kotlin.math.roundToInt +/** + * @author ShirkNeko + * @date 2025/5/31. + */ fun saveCardConfig(context: Context) { CardConfig.save(context) } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 847169a8..96348087 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -64,9 +64,11 @@ import com.sukisu.ultra.ui.util.getBugreportFile import java.time.LocalDateTime import java.time.format.DateTimeFormatter import com.sukisu.ultra.ui.component.KsuIsValid -import com.dergoogler.mmrl.platform.Platform - +/** + * @author ShirkNeko + * @date 2025/5/31. + */ @OptIn(ExperimentalMaterial3Api::class) @Destination @Composable diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt index 455f1ee3..cd81b393 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/SuperUser.kt @@ -50,6 +50,10 @@ import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItemDefaults +/** + * @author ShirkNeko + * @date 2025/5/31. + */ @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Destination @Composable diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SELinuxChecker.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SELinuxChecker.kt index cb5e1eef..dff51722 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SELinuxChecker.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SELinuxChecker.kt @@ -1,12 +1,10 @@ package com.sukisu.ultra.ui.util -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource +import android.content.Context import com.topjohnwu.superuser.Shell import com.sukisu.ultra.R -@Composable -fun getSELinuxStatus(): String { +fun getSELinuxStatus(context: Context): String { val shell = Shell.Builder.create().build("sh") val list = ArrayList() @@ -18,16 +16,16 @@ fun getSELinuxStatus(): String { return if (result.isSuccess) { when (output) { - "Enforcing" -> stringResource(R.string.selinux_status_enforcing) - "Permissive" -> stringResource(R.string.selinux_status_permissive) - "Disabled" -> stringResource(R.string.selinux_status_disabled) - else -> stringResource(R.string.selinux_status_unknown) + "Enforcing" -> context.getString(R.string.selinux_status_enforcing) + "Permissive" -> context.getString(R.string.selinux_status_permissive) + "Disabled" -> context.getString(R.string.selinux_status_disabled) + else -> context.getString(R.string.selinux_status_unknown) } } else { if (output.contains("Permission denied")) { - stringResource(R.string.selinux_status_enforcing) + context.getString(R.string.selinux_status_enforcing) } else { - stringResource(R.string.selinux_status_unknown) + context.getString(R.string.selinux_status_unknown) } } } \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt new file mode 100644 index 00000000..190aa8f3 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/HomeViewModel.kt @@ -0,0 +1,272 @@ +package com.sukisu.ultra.ui.viewmodel + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.os.SystemClock +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.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sukisu.ultra.KernelVersion +import com.sukisu.ultra.Natives +import com.sukisu.ultra.getKernelVersion +import com.sukisu.ultra.ksuApp +import com.sukisu.ultra.ui.util.checkNewVersion +import com.sukisu.ultra.ui.util.getKpmModuleCount +import com.sukisu.ultra.ui.util.getKpmVersion +import com.sukisu.ultra.ui.util.getModuleCount +import com.sukisu.ultra.ui.util.getSELinuxStatus +import com.sukisu.ultra.ui.util.getSuSFS +import com.sukisu.ultra.ui.util.getSuSFSFeatures +import com.sukisu.ultra.ui.util.getSuSFSVariant +import com.sukisu.ultra.ui.util.getSuSFSVersion +import com.sukisu.ultra.ui.util.getSuperuserCount +import com.sukisu.ultra.ui.util.module.LatestVersionInfo +import com.sukisu.ultra.ui.util.rootAvailable +import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import com.topjohnwu.superuser.internal.Utils.context + +/** + * @author ShirkNeko + * @date 2025/5/31. + */ +class HomeViewModel : ViewModel() { + companion object { + private const val TAG = "HomeViewModel" + } + + // 系统状态 + data class SystemStatus( + val isManager: Boolean = false, + val ksuVersion: Int? = null, + val lkmMode: Boolean? = null, + val kernelVersion: KernelVersion = getKernelVersion(), + val isRootAvailable: Boolean = false, + val isKpmConfigured: Boolean = false, + val requireNewKernel: Boolean = false + ) + + // 系统信息 + data class SystemInfo( + val kernelRelease: String = "", + val androidVersion: String = "", + val deviceModel: String = "", + val managerVersion: Pair = Pair("", 0L), + val seLinuxStatus: String = "", + val kpmVersion: String = "", + val suSFSStatus: String = "", + val suSFSVersion: String = "", + val suSFSVariant: String = "", + val suSFSFeatures: String = "", + val susSUMode: String = "", + val superuserCount: Int = 0, + val moduleCount: Int = 0, + val kpmModuleCount: Int = 0 + ) + + // UI状态 + var isRefreshing by mutableStateOf(false) + private set + + // 系统状态信息 + var systemStatus by mutableStateOf(SystemStatus()) + private set + + // 系统详细信息 + var systemInfo by mutableStateOf(SystemInfo()) + private set + + // 更新信息 + var latestVersionInfo by mutableStateOf(LatestVersionInfo()) + private set + + // 用户设置 + var isSimpleMode by mutableStateOf(false) + private set + var isHideVersion by mutableStateOf(false) + private set + var isHideOtherInfo by mutableStateOf(false) + private set + var isHideSusfsStatus by mutableStateOf(false) + private set + var isHideLinkCard by mutableStateOf(false) + private set + var showKpmInfo by mutableStateOf(true) + private set + + // 加载用户设置 + fun loadUserSettings(context: Context) { + viewModelScope.launch(Dispatchers.IO) { + val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + isSimpleMode = prefs.getBoolean("is_simple_mode", false) + isHideVersion = prefs.getBoolean("is_hide_version", false) + isHideOtherInfo = prefs.getBoolean("is_hide_other_info", false) + isHideSusfsStatus = prefs.getBoolean("is_hide_susfs_status", false) + isHideLinkCard = prefs.getBoolean("is_hide_link_card", false) + showKpmInfo = prefs.getBoolean("show_kpm_info", true) + } + } + + // 初始化数据 + fun initializeData() { + viewModelScope.launch { + fetchSystemStatus() + fetchSystemInfo() + } + } + + // 检查更新 + fun checkForUpdates(context: Context) { + viewModelScope.launch(Dispatchers.IO) { + try { + val checkUpdate = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + .getBoolean("check_update", true) + + if (checkUpdate) { + val start = SystemClock.elapsedRealtime() + latestVersionInfo = checkNewVersion() + Log.i(TAG, "Update check completed in ${SystemClock.elapsedRealtime() - start}ms") + } + } catch (e: Exception) { + Log.e(TAG, "Error checking for updates", e) + } + } + } + + // 刷新所有数据 + fun refreshAllData(context: Context) { + isRefreshing = true + viewModelScope.launch { + try { + fetchSystemStatus() + fetchSystemInfo() + checkForUpdates(context) + } finally { + isRefreshing = false + } + } + } + + // 获取系统状态 + private suspend fun fetchSystemStatus() { + withContext(Dispatchers.IO) { + try { + val kernelVersion = getKernelVersion() + val isManager = Natives.becomeManager(ksuApp.packageName) + val ksuVersion = if (isManager) Natives.version else null + val lkmMode = ksuVersion?.let { + if (it >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && kernelVersion.isGKI()) Natives.isLkmMode else null + } + + systemStatus = SystemStatus( + isManager = isManager, + ksuVersion = ksuVersion, + lkmMode = lkmMode, + kernelVersion = kernelVersion, + isRootAvailable = rootAvailable(), + isKpmConfigured = Natives.isKPMEnabled(), + requireNewKernel = isManager && Natives.requireNewKernel() + ) + } catch (e: Exception) { + Log.e(TAG, "Error fetching system status", e) + } + } + } + + // 获取系统信息 + @SuppressLint("RestrictedApi") + private suspend fun fetchSystemInfo() { + withContext(Dispatchers.IO) { + try { + val uname = Os.uname() + val kpmVersion = getKpmVersion() + val suSFS = getSuSFS() + var suSFSVersion = "" + var suSFSVariant = "" + var suSFSFeatures = "" + var susSUMode = "" + + if (suSFS == "Supported") { + suSFSVersion = getSuSFSVersion() + if (suSFSVersion.isNotEmpty()) { + suSFSVariant = getSuSFSVariant() + suSFSFeatures = getSuSFSFeatures() + val isSUS_SU = suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU" + if (isSUS_SU) { + susSUMode = try { + susfsSUS_SU_Mode().toString() + } catch (_: Exception) { + "" + } + } + } + } + + systemInfo = SystemInfo( + kernelRelease = uname.release, + androidVersion = Build.VERSION.RELEASE, + deviceModel = getDeviceModel(), + managerVersion = getManagerVersion(ksuApp.applicationContext), + seLinuxStatus = getSELinuxStatus(context), + kpmVersion = kpmVersion, + suSFSStatus = suSFS, + suSFSVersion = suSFSVersion, + suSFSVariant = suSFSVariant, + suSFSFeatures = suSFSFeatures, + susSUMode = susSUMode, + superuserCount = getSuperuserCount(), + moduleCount = getModuleCount(), + kpmModuleCount = getKpmModuleCount() + ) + } catch (e: Exception) { + Log.e(TAG, "Error fetching system info", e) + } + } + } + + // 获取设备型号 + @SuppressLint("PrivateApi") + private fun getDeviceModel(): String { + return try { + 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 + ) + var result = Build.DEVICE + for (key in marketNameKeys) { + val marketName = getMethod.invoke(null, key, "") as String + if (marketName.isNotEmpty()) { + result = marketName + break + } + } + result + } catch (e: Exception) { + Log.e(TAG, "Error getting device model", e) + Build.DEVICE + } + } + + // 获取管理器版本 + private fun getManagerVersion(context: Context): Pair { + return try { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)!! + val versionCode = androidx.core.content.pm.PackageInfoCompat.getLongVersionCode(packageInfo) + Pair(packageInfo.versionName!!, versionCode) + } catch (e: Exception) { + Log.e(TAG, "Error getting manager version", e) + Pair("", 0L) + } + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/KpmViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/KpmViewModel.kt index 579edd4b..4d2948a1 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/KpmViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/KpmViewModel.kt @@ -11,6 +11,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import com.sukisu.ultra.ui.util.* +/** + * @author ShirkNeko + * @date 2025/5/31. + */ class KpmViewModel : ViewModel() { var moduleList by mutableStateOf(emptyList()) private set diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt index a44af86c..3107f780 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt @@ -21,6 +21,10 @@ import java.text.Collator import java.util.Locale import java.util.concurrent.TimeUnit +/** + * @author ShirkNeko + * @date 2025/5/31. + */ class ModuleViewModel : ViewModel() { companion object { diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt index d4c3d7f7..ddc19793 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/SuperUserViewModel.kt @@ -25,6 +25,11 @@ import com.sukisu.ultra.ui.webui.getInstalledPackagesAll import kotlinx.coroutines.delay import kotlinx.coroutines.withTimeoutOrNull + +/** + * @author ShirkNeko + * @date 2025/5/31. + */ class SuperUserViewModel : ViewModel() { val isPlatformAlive get() = Platform.isAlive companion object {