Step 5-2: Simplify and separate the main logic for flashing anykernel3

This commit is contained in:
ShirkNeko
2025-11-21 01:48:33 +08:00
parent e3ef521de5
commit c6b184793e
2 changed files with 277 additions and 185 deletions

View File

@@ -0,0 +1,252 @@
package com.sukisu.ultra.ui.kernelFlash
import android.net.Uri
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Security
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.screen.InstallMethod
import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextButton
import top.yukonga.miuix.kmp.extra.SuperArrow
import top.yukonga.miuix.kmp.extra.SuperDialog
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
enum class KpmPatchOption {
FOLLOW_KERNEL,
PATCH_KPM,
UNDO_PATCH_KPM
}
@Stable
data class AnyKernel3State(
val kpmPatchOption: KpmPatchOption,
val showSlotSelectionDialog: Boolean,
val showKpmPatchDialog: Boolean,
val onHorizonKernelSelected: (InstallMethod.HorizonKernel) -> Unit,
val onSlotSelected: (String) -> Unit,
val onDismissSlotDialog: () -> Unit,
val onOptionSelected: (KpmPatchOption) -> Unit,
val onDismissPatchDialog: () -> Unit
)
@Composable
fun rememberAnyKernel3State(
installMethodState: MutableState<InstallMethod?>,
preselectedKernelUri: String?,
horizonKernelSummary: String,
isAbDevice: Boolean
): AnyKernel3State {
var kpmPatchOption by remember { mutableStateOf(KpmPatchOption.FOLLOW_KERNEL) }
var showSlotSelectionDialog by remember { mutableStateOf(false) }
var showKpmPatchDialog by remember { mutableStateOf(false) }
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
val onHorizonKernelSelected: (InstallMethod.HorizonKernel) -> Unit = { method ->
val uri = method.uri
if (uri != null) {
if (isAbDevice) {
tempKernelUri = uri
showSlotSelectionDialog = true
} else {
installMethodState.value = method
showKpmPatchDialog = true
}
}
}
val onSlotSelected: (String) -> Unit = { slot ->
val uri = tempKernelUri
if (uri != null) {
installMethodState.value = InstallMethod.HorizonKernel(
uri = uri,
slot = slot,
summary = horizonKernelSummary
)
tempKernelUri = null
showSlotSelectionDialog = false
showKpmPatchDialog = true
}
}
val onDismissSlotDialog = {
showSlotSelectionDialog = false
tempKernelUri = null
}
val onOptionSelected: (KpmPatchOption) -> Unit = { option ->
kpmPatchOption = option
showKpmPatchDialog = false
}
val onDismissPatchDialog = {
showKpmPatchDialog = false
}
LaunchedEffect(preselectedKernelUri, isAbDevice, horizonKernelSummary) {
preselectedKernelUri?.let { uriString ->
runCatching { uriString.toUri() }
.getOrNull()
?.let { preselectedUri ->
val method = InstallMethod.HorizonKernel(
uri = preselectedUri,
summary = horizonKernelSummary
)
if (isAbDevice) {
tempKernelUri = preselectedUri
showSlotSelectionDialog = true
} else {
installMethodState.value = method
showKpmPatchDialog = true
}
}
}
}
return AnyKernel3State(
kpmPatchOption = kpmPatchOption,
showSlotSelectionDialog = showSlotSelectionDialog,
showKpmPatchDialog = showKpmPatchDialog,
onHorizonKernelSelected = onHorizonKernelSelected,
onSlotSelected = onSlotSelected,
onDismissSlotDialog = onDismissSlotDialog,
onOptionSelected = onOptionSelected,
onDismissPatchDialog = onDismissPatchDialog
)
}
@Composable
fun KpmPatchSelectionDialog(
show: Boolean,
currentOption: KpmPatchOption,
onDismiss: () -> Unit,
onOptionSelected: (KpmPatchOption) -> Unit
) {
var selectedOption by remember { mutableStateOf(currentOption) }
val showDialog = remember { mutableStateOf(show) }
LaunchedEffect(show) {
showDialog.value = show
if (show) {
selectedOption = currentOption
}
}
SuperDialog(
show = showDialog,
insideMargin = DpSize(0.dp, 0.dp),
onDismissRequest = {
showDialog.value = false
onDismiss()
},
content = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp)
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp),
text = stringResource(id = R.string.kpm_patch_options),
fontSize = MiuixTheme.textStyles.title4.fontSize,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
color = colorScheme.onSurface
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 8.dp),
text = stringResource(id = R.string.kpm_patch_description),
fontSize = MiuixTheme.textStyles.body2.fontSize,
color = colorScheme.onSurfaceVariantSummary,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
val options = listOf(
KpmPatchOption.FOLLOW_KERNEL to stringResource(R.string.kpm_follow_kernel_file),
KpmPatchOption.PATCH_KPM to stringResource(R.string.enable_kpm_patch),
KpmPatchOption.UNDO_PATCH_KPM to stringResource(R.string.enable_kpm_undo_patch)
)
options.forEach { (option, title) ->
SuperArrow(
title = title,
onClick = {
selectedOption = option
},
leftAction = {
Icon(
imageVector = Icons.Filled.Security,
contentDescription = null,
tint = if (selectedOption == option) {
colorScheme.primary
} else {
colorScheme.onSurfaceVariantSummary
}
)
},
insideMargin = PaddingValues(horizontal = 24.dp, vertical = 12.dp)
)
}
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
TextButton(
text = stringResource(android.R.string.cancel),
onClick = {
showDialog.value = false
onDismiss()
},
modifier = Modifier.weight(1f)
)
TextButton(
text = stringResource(android.R.string.ok),
onClick = {
onOptionSelected(selectedOption)
showDialog.value = false
onDismiss()
},
modifier = Modifier.weight(1f)
)
}
}
}
)
}

View File

@@ -14,7 +14,6 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -47,13 +46,10 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SdStorage
import androidx.compose.material.icons.filled.Security
import androidx.core.net.toUri
import androidx.lifecycle.compose.dropUnlessResumed
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
@@ -69,7 +65,10 @@ import com.sukisu.ultra.R
import com.sukisu.ultra.getKernelVersion
import com.sukisu.ultra.ui.component.ChooseKmiDialog
import com.sukisu.ultra.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.kernelFlash.KpmPatchOption
import com.sukisu.ultra.ui.kernelFlash.KpmPatchSelectionDialog
import com.sukisu.ultra.ui.kernelFlash.component.SlotSelectionDialog
import com.sukisu.ultra.ui.kernelFlash.rememberAnyKernel3State
import com.sukisu.ultra.ui.util.LkmSelection
import com.sukisu.ultra.ui.util.getAvailablePartitions
import com.sukisu.ultra.ui.util.getCurrentKmi
@@ -94,11 +93,6 @@ import top.yukonga.miuix.kmp.icon.MiuixIcons
import top.yukonga.miuix.kmp.icon.icons.useful.Back
import top.yukonga.miuix.kmp.icon.icons.useful.Edit
import top.yukonga.miuix.kmp.icon.icons.useful.Move
import top.yukonga.miuix.kmp.extra.SuperDialog
import top.yukonga.miuix.kmp.basic.TextButton
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.unit.DpSize
import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
import top.yukonga.miuix.kmp.utils.getWindowSize
@@ -109,12 +103,6 @@ import top.yukonga.miuix.kmp.utils.scrollEndHaptic
* @author weishu
* @date 2024/3/12.
*/
enum class KpmPatchOption {
FOLLOW_KERNEL,
PATCH_KPM,
UNDO_PATCH_KPM
}
@Composable
@Destination<RootGraph>
fun InstallScreen(
@@ -122,19 +110,15 @@ fun InstallScreen(
preselectedKernelUri: String? = null
) {
val context = LocalContext.current
var installMethod by remember {
val installMethodState = remember {
mutableStateOf<InstallMethod?>(null)
}
var installMethod by installMethodState
var lkmSelection by remember {
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
}
var kpmPatchOption by remember { mutableStateOf(KpmPatchOption.FOLLOW_KERNEL) }
var showSlotSelectionDialog by remember { mutableStateOf(false) }
var showKpmPatchDialog by remember { mutableStateOf(false) }
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
val kernelVersion = getKernelVersion()
val isGKI = kernelVersion.isGKI()
val isAbDevice = produceState(initialValue = false) {
@@ -145,28 +129,14 @@ fun InstallScreen(
var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) }
var hasCustomSelected by remember { mutableStateOf(false) }
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
// 处理预选的内核文件
LaunchedEffect(preselectedKernelUri) {
preselectedKernelUri?.let { uriString ->
try {
val preselectedUri = uriString.toUri()
val horizonMethod = InstallMethod.HorizonKernel(
uri = preselectedUri,
summary = horizonKernelSummary
)
installMethod = horizonMethod
tempKernelUri = preselectedUri
if (isAbDevice) {
showSlotSelectionDialog = true
} else {
showKpmPatchDialog = true
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// AnyKernel3 刷写状态
val anyKernel3State = rememberAnyKernel3State(
installMethodState = installMethodState,
preselectedKernelUri = preselectedKernelUri,
horizonKernelSummary = horizonKernelSummary,
isAbDevice = isAbDevice
)
val kpmPatchOption = anyKernel3State.kpmPatchOption
val onInstall = {
installMethod?.let { method ->
@@ -203,33 +173,24 @@ fun InstallScreen(
}
// 槽位选择对话框
if (showSlotSelectionDialog && isAbDevice) {
if (anyKernel3State.showSlotSelectionDialog && isAbDevice) {
SlotSelectionDialog(
show = true,
onDismiss = { showSlotSelectionDialog = false },
onDismiss = { anyKernel3State.onDismissSlotDialog() },
onSlotSelected = { slot ->
showSlotSelectionDialog = false
val horizonMethod = InstallMethod.HorizonKernel(
uri = tempKernelUri,
slot = slot,
summary = horizonKernelSummary
)
installMethod = horizonMethod
// 槽位选择后,显示 KPM 补丁选择对话框
showKpmPatchDialog = true
anyKernel3State.onSlotSelected(slot)
}
)
}
// KPM补丁选择对话框
if (showKpmPatchDialog) {
if (anyKernel3State.showKpmPatchDialog) {
KpmPatchSelectionDialog(
show = true,
currentOption = kpmPatchOption,
onDismiss = { showKpmPatchDialog = false },
currentOption = anyKernel3State.kpmPatchOption,
onDismiss = { anyKernel3State.onDismissPatchDialog() },
onOptionSelected = { option ->
kpmPatchOption = option
showKpmPatchDialog = false
anyKernel3State.onOptionSelected(option)
}
)
}
@@ -318,13 +279,7 @@ fun InstallScreen(
SelectInstallMethod(
onSelected = { method ->
if (method is InstallMethod.HorizonKernel && method.uri != null) {
if (isAbDevice) {
tempKernelUri = method.uri
showSlotSelectionDialog = true
} else {
installMethod = method
showKpmPatchDialog = true
}
anyKernel3State.onHorizonKernelSelected(method)
} else {
installMethod = method
}
@@ -377,7 +332,7 @@ fun InstallScreen(
)
}
}
// LKM 上传选项(仅 GKI
// LKM 上传(仅 GKI
if (isGKI) {
Card(
modifier = Modifier
@@ -405,7 +360,7 @@ fun InstallScreen(
}
}
// AnyKernel3 相关信息显示
// AnyKernel3 刷写
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
if (method.slot != null) {
Card(
@@ -449,7 +404,7 @@ fun InstallScreen(
leftAction = {
Icon(
Icons.Filled.Security,
tint = if (kpmPatchOption == KpmPatchOption.PATCH_KPM)
tint = if (kpmPatchOption == KpmPatchOption.PATCH_KPM)
colorScheme.primary
else
colorScheme.secondary,
@@ -649,121 +604,6 @@ private fun TopBar(
)
}
@Composable
private fun KpmPatchSelectionDialog(
show: Boolean,
currentOption: KpmPatchOption,
onDismiss: () -> Unit,
onOptionSelected: (KpmPatchOption) -> Unit
) {
var selectedOption by remember { mutableStateOf(currentOption) }
val showDialog = remember { mutableStateOf(show) }
LaunchedEffect(show) {
showDialog.value = show
if (show) {
selectedOption = currentOption
}
}
SuperDialog(
show = showDialog,
insideMargin = DpSize(0.dp, 0.dp),
onDismissRequest = {
showDialog.value = false
onDismiss()
},
content = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp)
) {
// 标题
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp),
text = stringResource(id = R.string.kpm_patch_options),
fontSize = MiuixTheme.textStyles.title4.fontSize,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
color = colorScheme.onSurface
)
// 描述
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 8.dp),
text = stringResource(id = R.string.kpm_patch_description),
fontSize = MiuixTheme.textStyles.body2.fontSize,
color = colorScheme.onSurfaceVariantSummary,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
// 选项列表
val options = listOf(
KpmPatchOption.FOLLOW_KERNEL to stringResource(R.string.kpm_follow_kernel_file),
KpmPatchOption.PATCH_KPM to stringResource(R.string.enable_kpm_patch),
KpmPatchOption.UNDO_PATCH_KPM to stringResource(R.string.enable_kpm_undo_patch)
)
options.forEach { (option, title) ->
SuperArrow(
title = title,
onClick = {
selectedOption = option
},
leftAction = {
Icon(
imageVector = Icons.Filled.Security,
contentDescription = null,
tint = if (selectedOption == option) {
colorScheme.primary
} else {
colorScheme.onSurfaceVariantSummary
}
)
},
insideMargin = PaddingValues(horizontal = 24.dp, vertical = 12.dp)
)
}
Spacer(modifier = Modifier.height(12.dp))
// 按钮行
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
TextButton(
text = stringResource(android.R.string.cancel),
onClick = {
showDialog.value = false
onDismiss()
},
modifier = Modifier.weight(1f)
)
TextButton(
text = stringResource(android.R.string.ok),
onClick = {
onOptionSelected(selectedOption)
showDialog.value = false
onDismiss()
},
modifier = Modifier.weight(1f)
)
}
}
}
)
}
private fun isKoFile(context: Context, uri: Uri): Boolean {
val seg = uri.lastPathSegment ?: ""
if (seg.endsWith(".ko", ignoreCase = true)) return true
@@ -787,4 +627,4 @@ private fun isKoFile(context: Context, uri: Uri): Boolean {
} catch (_: Throwable) {
false
}
}
}