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.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer 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.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role 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.ui.unit.dp
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SdStorage import androidx.compose.material.icons.filled.SdStorage
import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Security
import androidx.core.net.toUri
import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.compose.dropUnlessResumed
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
@@ -69,7 +65,10 @@ import com.sukisu.ultra.R
import com.sukisu.ultra.getKernelVersion import com.sukisu.ultra.getKernelVersion
import com.sukisu.ultra.ui.component.ChooseKmiDialog import com.sukisu.ultra.ui.component.ChooseKmiDialog
import com.sukisu.ultra.ui.component.rememberConfirmDialog 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.component.SlotSelectionDialog
import com.sukisu.ultra.ui.kernelFlash.rememberAnyKernel3State
import com.sukisu.ultra.ui.util.LkmSelection import com.sukisu.ultra.ui.util.LkmSelection
import com.sukisu.ultra.ui.util.getAvailablePartitions import com.sukisu.ultra.ui.util.getAvailablePartitions
import com.sukisu.ultra.ui.util.getCurrentKmi 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.Back
import top.yukonga.miuix.kmp.icon.icons.useful.Edit import top.yukonga.miuix.kmp.icon.icons.useful.Edit
import top.yukonga.miuix.kmp.icon.icons.useful.Move 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
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
import top.yukonga.miuix.kmp.utils.getWindowSize import top.yukonga.miuix.kmp.utils.getWindowSize
@@ -109,12 +103,6 @@ import top.yukonga.miuix.kmp.utils.scrollEndHaptic
* @author weishu * @author weishu
* @date 2024/3/12. * @date 2024/3/12.
*/ */
enum class KpmPatchOption {
FOLLOW_KERNEL,
PATCH_KPM,
UNDO_PATCH_KPM
}
@Composable @Composable
@Destination<RootGraph> @Destination<RootGraph>
fun InstallScreen( fun InstallScreen(
@@ -122,19 +110,15 @@ fun InstallScreen(
preselectedKernelUri: String? = null preselectedKernelUri: String? = null
) { ) {
val context = LocalContext.current val context = LocalContext.current
var installMethod by remember { val installMethodState = remember {
mutableStateOf<InstallMethod?>(null) mutableStateOf<InstallMethod?>(null)
} }
var installMethod by installMethodState
var lkmSelection by remember { var lkmSelection by remember {
mutableStateOf<LkmSelection>(LkmSelection.KmiNone) 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 kernelVersion = getKernelVersion()
val isGKI = kernelVersion.isGKI() val isGKI = kernelVersion.isGKI()
val isAbDevice = produceState(initialValue = false) { val isAbDevice = produceState(initialValue = false) {
@@ -145,28 +129,14 @@ fun InstallScreen(
var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) } var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) }
var hasCustomSelected by remember { mutableStateOf(false) } var hasCustomSelected by remember { mutableStateOf(false) }
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary) val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
// AnyKernel3 刷写状态
// 处理预选的内核文件 val anyKernel3State = rememberAnyKernel3State(
LaunchedEffect(preselectedKernelUri) { installMethodState = installMethodState,
preselectedKernelUri?.let { uriString -> preselectedKernelUri = preselectedKernelUri,
try { horizonKernelSummary = horizonKernelSummary,
val preselectedUri = uriString.toUri() isAbDevice = isAbDevice
val horizonMethod = InstallMethod.HorizonKernel( )
uri = preselectedUri, val kpmPatchOption = anyKernel3State.kpmPatchOption
summary = horizonKernelSummary
)
installMethod = horizonMethod
tempKernelUri = preselectedUri
if (isAbDevice) {
showSlotSelectionDialog = true
} else {
showKpmPatchDialog = true
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
val onInstall = { val onInstall = {
installMethod?.let { method -> installMethod?.let { method ->
@@ -203,33 +173,24 @@ fun InstallScreen(
} }
// 槽位选择对话框 // 槽位选择对话框
if (showSlotSelectionDialog && isAbDevice) { if (anyKernel3State.showSlotSelectionDialog && isAbDevice) {
SlotSelectionDialog( SlotSelectionDialog(
show = true, show = true,
onDismiss = { showSlotSelectionDialog = false }, onDismiss = { anyKernel3State.onDismissSlotDialog() },
onSlotSelected = { slot -> onSlotSelected = { slot ->
showSlotSelectionDialog = false anyKernel3State.onSlotSelected(slot)
val horizonMethod = InstallMethod.HorizonKernel(
uri = tempKernelUri,
slot = slot,
summary = horizonKernelSummary
)
installMethod = horizonMethod
// 槽位选择后,显示 KPM 补丁选择对话框
showKpmPatchDialog = true
} }
) )
} }
// KPM补丁选择对话框 // KPM补丁选择对话框
if (showKpmPatchDialog) { if (anyKernel3State.showKpmPatchDialog) {
KpmPatchSelectionDialog( KpmPatchSelectionDialog(
show = true, show = true,
currentOption = kpmPatchOption, currentOption = anyKernel3State.kpmPatchOption,
onDismiss = { showKpmPatchDialog = false }, onDismiss = { anyKernel3State.onDismissPatchDialog() },
onOptionSelected = { option -> onOptionSelected = { option ->
kpmPatchOption = option anyKernel3State.onOptionSelected(option)
showKpmPatchDialog = false
} }
) )
} }
@@ -318,13 +279,7 @@ fun InstallScreen(
SelectInstallMethod( SelectInstallMethod(
onSelected = { method -> onSelected = { method ->
if (method is InstallMethod.HorizonKernel && method.uri != null) { if (method is InstallMethod.HorizonKernel && method.uri != null) {
if (isAbDevice) { anyKernel3State.onHorizonKernelSelected(method)
tempKernelUri = method.uri
showSlotSelectionDialog = true
} else {
installMethod = method
showKpmPatchDialog = true
}
} else { } else {
installMethod = method installMethod = method
} }
@@ -377,7 +332,7 @@ fun InstallScreen(
) )
} }
} }
// LKM 上传选项(仅 GKI // LKM 上传(仅 GKI
if (isGKI) { if (isGKI) {
Card( Card(
modifier = Modifier modifier = Modifier
@@ -405,7 +360,7 @@ fun InstallScreen(
} }
} }
// AnyKernel3 相关信息显示 // AnyKernel3 刷写
(installMethod as? InstallMethod.HorizonKernel)?.let { method -> (installMethod as? InstallMethod.HorizonKernel)?.let { method ->
if (method.slot != null) { if (method.slot != null) {
Card( Card(
@@ -449,7 +404,7 @@ fun InstallScreen(
leftAction = { leftAction = {
Icon( Icon(
Icons.Filled.Security, Icons.Filled.Security,
tint = if (kpmPatchOption == KpmPatchOption.PATCH_KPM) tint = if (kpmPatchOption == KpmPatchOption.PATCH_KPM)
colorScheme.primary colorScheme.primary
else else
colorScheme.secondary, 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 { private fun isKoFile(context: Context, uri: Uri): Boolean {
val seg = uri.lastPathSegment ?: "" val seg = uri.lastPathSegment ?: ""
if (seg.endsWith(".ko", ignoreCase = true)) return true if (seg.endsWith(".ko", ignoreCase = true)) return true
@@ -787,4 +627,4 @@ private fun isKoFile(context: Context, uri: Uri): Boolean {
} catch (_: Throwable) { } catch (_: Throwable) {
false false
} }
} }