manager: Optimizing Home Performance

- Reorganize Home structure using MVVM architecture pattern to separate UI and data logic
This commit is contained in:
ShirkNeko
2025-05-31 17:39:24 +08:00
parent 916d956ce2
commit 13b1aad4b8
14 changed files with 476 additions and 214 deletions

View File

@@ -27,6 +27,11 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
data class FlashState( data class FlashState(
val isFlashing: Boolean = false, val isFlashing: Boolean = false,
val isCompleted: Boolean = false, val isCompleted: Boolean = false,

View File

@@ -53,6 +53,10 @@ import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
enum class FlashingStatus { enum class FlashingStatus {
FLASHING, FLASHING,
SUCCESS, SUCCESS,

View File

@@ -58,17 +58,16 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember 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.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination import com.ramcosta.composedestinations.generated.destinations.InstallScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.sukisu.ultra.KernelVersion import com.sukisu.ultra.KernelVersion
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.Natives.isKPMEnabled
import com.sukisu.ultra.R 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.KsuIsValid
import com.sukisu.ultra.ui.component.rememberConfirmDialog import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.theme.CardConfig 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.getKpmModuleCount
import com.sukisu.ultra.ui.util.getKpmVersion import com.sukisu.ultra.ui.util.getKpmVersion
import com.sukisu.ultra.ui.util.getModuleCount 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.getSuperuserCount
import com.sukisu.ultra.ui.util.module.LatestVersionInfo 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.util.rootAvailable import com.sukisu.ultra.ui.viewmodel.HomeViewModel
import com.sukisu.ultra.ui.util.susfsSUS_SU_Mode
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.random.Random import kotlin.random.Random
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) /**
* @author ShirkNeko
* @date 2025/5/31.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>(start = true) @Destination<RootGraph>(start = true)
@Composable @Composable
fun HomeScreen(navigator: DestinationsNavigator) { fun HomeScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current val context = LocalContext.current
var isSimpleMode by rememberSaveable { mutableStateOf(false) } val viewModel = viewModel<HomeViewModel>()
var isHideVersion by rememberSaveable { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope()
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
var isHideLinkCard by rememberSaveable { mutableStateOf(false) }
var showKpmInfo by rememberSaveable { mutableStateOf(true) }
// 从 SharedPreferences 加载简洁模式状态
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE) // 初始化加载用户设置
.getBoolean("is_simple_mode", false) viewModel.loadUserSettings(context)
// 初始化数据
isHideVersion = context.getSharedPreferences("settings", Context.MODE_PRIVATE) viewModel.initializeData()
.getBoolean("is_hide_version", false) // 检查更新
viewModel.checkForUpdates(context)
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)
} }
val kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val debounceTime = 100L
var lastScrollTime by remember { mutableLongStateOf(0L) }
Scaffold( Scaffold(
topBar = { topBar = {
TopBar( TopBar(
kernelVersion, kernelVersion = viewModel.systemStatus.kernelVersion,
onInstallClick = { navigator.navigate(InstallScreenDestination) }, onInstallClick = { navigator.navigate(InstallScreenDestination) },
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
@@ -169,71 +144,72 @@ fun HomeScreen(navigator: DestinationsNavigator) {
WindowInsetsSides.Top + WindowInsetsSides.Horizontal WindowInsetsSides.Top + WindowInsetsSides.Horizontal
) )
) { innerPadding -> ) { innerPadding ->
Column( PullToRefreshBox(
modifier = Modifier onRefresh = {
.padding(innerPadding) coroutineScope.launch {
.disableOverscroll() viewModel.refreshAllData(context)
.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()
} }
} },
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) { if (viewModel.systemStatus.requireNewKernel) {
snapshotFlow { scrollState.isScrollInProgress } WarningCard(
.debounce(debounceTime) stringResource(id = R.string.require_kernel_version).format(
.collect { isScrolling -> viewModel.systemStatus.ksuVersion,
if (isScrolling) { Natives.MINIMAL_SUPPORTED_KERNEL
val currentTime = System.currentTimeMillis() )
if (currentTime - lastScrollTime > debounceTime) { )
lastScrollTime = currentTime }
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 @Composable
private fun StatusCard( private fun StatusCard(
kernelVersion: KernelVersion, systemStatus: HomeViewModel.SystemStatus,
ksuVersion: Int?,
lkmMode: Boolean?,
onClickInstall: () -> Unit = {} onClickInstall: () -> Unit = {}
) { ) {
ElevatedCard( ElevatedCard(
@@ -378,7 +352,7 @@ private fun StatusCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
if (rootAvailable() || kernelVersion.isGKI()) { if (systemStatus.isRootAvailable || systemStatus.kernelVersion.isGKI()) {
onClickInstall() onClickInstall()
} }
} }
@@ -386,12 +360,12 @@ private fun StatusCard(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
when { when {
ksuVersion != null -> { systemStatus.ksuVersion != null -> {
val workingModeText = when { val workingModeText = when {
lkmMode == true -> "LKM" systemStatus.lkmMode == true -> "LKM"
lkmMode == null && kernelVersion.isGKI1() -> "GKI1.0" systemStatus.lkmMode == null && systemStatus.kernelVersion.isGKI1() -> "GKI1.0"
lkmMode == false || kernelVersion.isGKI() -> "GKI2.0" systemStatus.lkmMode == false || systemStatus.kernelVersion.isGKI() -> "GKI2.0"
else -> "N-GKI" else -> "N-GKI"
} }
@@ -464,7 +438,7 @@ private fun StatusCard(
if (!isHideVersion) { if (!isHideVersion) {
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
text = stringResource(R.string.home_working_version, ksuVersion), text = stringResource(R.string.home_working_version, systemStatus.ksuVersion),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -498,7 +472,7 @@ private fun StatusCard(
} }
} }
kernelVersion.isGKI() -> { systemStatus.kernelVersion.isGKI() -> {
Icon( Icon(
Icons.Outlined.Warning, Icons.Outlined.Warning,
contentDescription = stringResource(R.string.home_not_installed), contentDescription = stringResource(R.string.home_not_installed),
@@ -717,14 +691,15 @@ fun DonateCard() {
} }
@Composable @Composable
private fun InfoCard() { private fun InfoCard(
val lkmMode = Natives.isLkmMode systemInfo: HomeViewModel.SystemInfo,
val context = LocalContext.current isSimpleMode: Boolean,
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideVersion: Boolean,
.getBoolean("is_simple_mode", false) isHideOtherInfo: Boolean,
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) isHideSusfsStatus: Boolean,
.getBoolean("show_kpm_info", true) showKpmInfo: Boolean,
lkmMode: Boolean?
) {
ElevatedCard( ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainer), colors = getCardColors(MaterialTheme.colorScheme.surfaceContainer),
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation), elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
@@ -741,17 +716,13 @@ private fun InfoCard() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp), .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
) withContext@{ ) {
val contents = StringBuilder()
val uname = Os.uname()
@Composable @Composable
fun InfoCardItem( fun InfoCardItem(
label: String, label: String,
content: String, content: String,
icon: ImageVector = Icons.Default.Info icon: ImageVector = Icons.Default.Info
) { ) {
contents.appendLine(label).appendLine(content).appendLine()
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
@@ -787,55 +758,49 @@ private fun InfoCard() {
InfoCardItem( InfoCardItem(
stringResource(R.string.home_kernel), stringResource(R.string.home_kernel),
uname.release, systemInfo.kernelRelease,
icon = Icons.Default.Memory, icon = Icons.Default.Memory,
) )
if (!isSimpleMode) { if (!isSimpleMode) {
val androidVersion = Build.VERSION.RELEASE
InfoCardItem( InfoCardItem(
stringResource(R.string.home_android_version), stringResource(R.string.home_android_version),
androidVersion, systemInfo.androidVersion,
icon = Icons.Default.Android, icon = Icons.Default.Android,
) )
} }
val deviceModel = getDeviceModel()
InfoCardItem( InfoCardItem(
stringResource(R.string.home_device_model), stringResource(R.string.home_device_model),
deviceModel, systemInfo.deviceModel,
icon = Icons.Default.PhoneAndroid, icon = Icons.Default.PhoneAndroid,
) )
val managerVersion = getManagerVersion(context)
InfoCardItem( InfoCardItem(
stringResource(R.string.home_manager_version), stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})", "${systemInfo.managerVersion.first} (${systemInfo.managerVersion.second})",
icon = Icons.Default.SettingsSuggest, icon = Icons.Default.SettingsSuggest,
) )
InfoCardItem( InfoCardItem(
stringResource(R.string.home_selinux_status), stringResource(R.string.home_selinux_status),
getSELinuxStatus(), systemInfo.seLinuxStatus,
icon = Icons.Default.Security, icon = Icons.Default.Security,
) )
if (!isSimpleMode) { if (!isSimpleMode) {
if (lkmMode != true) { if (lkmMode != true) {
val kpmVersion = getKpmVersion()
val isKpmConfigured = checkKPMEnabled()
// 根据showKpmInfo决定是否显示KPM信息 // 根据showKpmInfo决定是否显示KPM信息
if (showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) { if (showKpmInfo && Natives.version >= Natives.MINIMAL_SUPPORTED_KPM) {
val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) { val displayVersion = if (systemInfo.kpmVersion.isEmpty() || systemInfo.kpmVersion.startsWith("Error")) {
val statusText = if (isKpmConfigured) { val statusText = if (Natives.isKPMEnabled()) {
stringResource(R.string.kernel_patched) stringResource(R.string.kernel_patched)
} else { } else {
stringResource(R.string.kernel_not_enabled) stringResource(R.string.kernel_not_enabled)
} }
"${stringResource(R.string.not_supported)} ($statusText)" "${stringResource(R.string.not_supported)} ($statusText)"
} else { } else {
"${stringResource(R.string.supported)} ($kpmVersion)" "${stringResource(R.string.supported)} (${systemInfo.kpmVersion})"
} }
InfoCardItem( 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)) { if ((!isSimpleMode) && (!isHideSusfsStatus)) {
val suSFS = getSuSFS() if (systemInfo.suSFSStatus == "Supported") {
if (suSFS == "Supported") { if (systemInfo.suSFSVersion.isNotEmpty()) {
val suSFSVersion = getSuSFSVersion() val isSUS_SU = systemInfo.suSFSFeatures == "CONFIG_KSU_SUSFS_SUS_SU"
if (suSFSVersion.isNotEmpty()) {
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val infoText = buildString { val infoText = buildString {
append(suSFSVersion) append(systemInfo.suSFSVersion)
append(if (isSUS_SU) " (${getSuSFSVariant()})" else " (${stringResource(R.string.manual_hook)})") append(if (isSUS_SU) " (${systemInfo.suSFSVariant})" else " (${stringResource(R.string.manual_hook)})")
if (isSUS_SU) { if (isSUS_SU) {
val susSUMode = try { susfsSUS_SU_Mode().toString() } catch (_: Exception) { "" } if (systemInfo.susSUMode.isNotEmpty()) {
if (susSUMode.isNotEmpty()) { append(" ${stringResource(R.string.sus_su_mode)} ${systemInfo.susSUMode}")
append(" ${stringResource(R.string.sus_su_mode)} $susSUMode")
} }
} }
} }
@@ -889,10 +848,45 @@ fun getManagerVersion(context: Context): Pair<String, Long> {
@Composable @Composable
private fun StatusCardPreview() { private fun StatusCardPreview() {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
StatusCard(KernelVersion(5, 10, 101), 1, null) StatusCard(
StatusCard(KernelVersion(5, 10, 101), 20000, true) HomeViewModel.SystemStatus(
StatusCard(KernelVersion(5, 10, 101), null, true) isManager = true,
StatusCard(KernelVersion(4, 10, 101), null, false) 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") @SuppressLint("UnnecessaryComposedModifier")
fun Modifier.disableOverscroll(): Modifier = composed { fun Modifier.disableOverscroll(): Modifier = composed {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

View File

@@ -94,8 +94,8 @@ import com.sukisu.ultra.ui.util.rootAvailable
import com.sukisu.ultra.getKernelVersion import com.sukisu.ultra.getKernelVersion
/** /**
* @author weishu * @author ShirkNeko
* @date 2024/3/12. * @date 2025/5/31.
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>

View File

@@ -43,6 +43,10 @@ import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
import com.sukisu.ultra.ui.theme.CardConfig import com.sukisu.ultra.ui.theme.CardConfig
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
private object KernelFlashStateHolder { private object KernelFlashStateHolder {
var currentState: HorizonKernelState? = null var currentState: HorizonKernelState? = null
var currentUri: Uri? = null var currentUri: Uri? = null

View File

@@ -75,7 +75,10 @@ import androidx.core.net.toUri
import com.dergoogler.mmrl.platform.model.ModuleConfig import com.dergoogler.mmrl.platform.model.ModuleConfig
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable

View File

@@ -84,6 +84,10 @@ import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
import kotlin.math.roundToInt import kotlin.math.roundToInt
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
fun saveCardConfig(context: Context) { fun saveCardConfig(context: Context) {
CardConfig.save(context) CardConfig.save(context)
} }

View File

@@ -64,9 +64,11 @@ import com.sukisu.ultra.ui.util.getBugreportFile
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import com.sukisu.ultra.ui.component.KsuIsValid import com.sukisu.ultra.ui.component.KsuIsValid
import com.dergoogler.mmrl.platform.Platform
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable

View File

@@ -50,6 +50,10 @@ import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import com.dergoogler.mmrl.ui.component.LabelItem import com.dergoogler.mmrl.ui.component.LabelItem
import com.dergoogler.mmrl.ui.component.LabelItemDefaults import com.dergoogler.mmrl.ui.component.LabelItemDefaults
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable

View File

@@ -1,12 +1,10 @@
package com.sukisu.ultra.ui.util package com.sukisu.ultra.ui.util
import androidx.compose.runtime.Composable import android.content.Context
import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.R import com.sukisu.ultra.R
@Composable fun getSELinuxStatus(context: Context): String {
fun getSELinuxStatus(): String {
val shell = Shell.Builder.create().build("sh") val shell = Shell.Builder.create().build("sh")
val list = ArrayList<String>() val list = ArrayList<String>()
@@ -18,16 +16,16 @@ fun getSELinuxStatus(): String {
return if (result.isSuccess) { return if (result.isSuccess) {
when (output) { when (output) {
"Enforcing" -> stringResource(R.string.selinux_status_enforcing) "Enforcing" -> context.getString(R.string.selinux_status_enforcing)
"Permissive" -> stringResource(R.string.selinux_status_permissive) "Permissive" -> context.getString(R.string.selinux_status_permissive)
"Disabled" -> stringResource(R.string.selinux_status_disabled) "Disabled" -> context.getString(R.string.selinux_status_disabled)
else -> stringResource(R.string.selinux_status_unknown) else -> context.getString(R.string.selinux_status_unknown)
} }
} else { } else {
if (output.contains("Permission denied")) { if (output.contains("Permission denied")) {
stringResource(R.string.selinux_status_enforcing) context.getString(R.string.selinux_status_enforcing)
} else { } else {
stringResource(R.string.selinux_status_unknown) context.getString(R.string.selinux_status_unknown)
} }
} }
} }

View File

@@ -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<String, Long> = 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<String, Long> {
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)
}
}
}

View File

@@ -11,6 +11,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.ui.util.* import com.sukisu.ultra.ui.util.*
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
class KpmViewModel : ViewModel() { class KpmViewModel : ViewModel() {
var moduleList by mutableStateOf(emptyList<ModuleInfo>()) var moduleList by mutableStateOf(emptyList<ModuleInfo>())
private set private set

View File

@@ -21,6 +21,10 @@ import java.text.Collator
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
class ModuleViewModel : ViewModel() { class ModuleViewModel : ViewModel() {
companion object { companion object {

View File

@@ -25,6 +25,11 @@ import com.sukisu.ultra.ui.webui.getInstalledPackagesAll
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
/**
* @author ShirkNeko
* @date 2025/5/31.
*/
class SuperUserViewModel : ViewModel() { class SuperUserViewModel : ViewModel() {
val isPlatformAlive get() = Platform.isAlive val isPlatformAlive get() = Platform.isAlive
companion object { companion object {