manager: install: add choose partition support
manager: fix KsuCli cmd userspace: reuse choose_boot_device - manager: simplify find boot image Co-authored-by: weishu <twsxtd@gmail.com> Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com> Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SuperDropdown(
|
||||
items: List<String>,
|
||||
selectedIndex: Int,
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
icon: ImageVector? = null,
|
||||
enabled: Boolean = true,
|
||||
showValue: Boolean = true,
|
||||
maxHeight: Dp? = 400.dp,
|
||||
colors: SuperDropdownColors = SuperDropdownDefaults.colors(),
|
||||
leftAction: (@Composable () -> Unit)? = null,
|
||||
onSelectedIndexChange: (Int) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
val selectedItemText = items.getOrNull(selectedIndex) ?: ""
|
||||
val itemsNotEmpty = items.isNotEmpty()
|
||||
val actualEnabled = enabled && itemsNotEmpty
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = actualEnabled) { showDialog = true }
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
if (leftAction != null) {
|
||||
leftAction()
|
||||
} else if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = if (actualEnabled) colors.iconColor else colors.disabledIconColor,
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = if (actualEnabled) colors.titleColor else colors.disabledTitleColor
|
||||
)
|
||||
|
||||
if (summary != null) {
|
||||
Spacer(modifier = Modifier.height(3.dp))
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (actualEnabled) colors.summaryColor else colors.disabledSummaryColor
|
||||
)
|
||||
}
|
||||
|
||||
if (showValue && itemsNotEmpty) {
|
||||
Spacer(modifier = Modifier.height(3.dp))
|
||||
Text(
|
||||
text = selectedItemText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (actualEnabled) colors.valueColor else colors.disabledValueColor,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
|
||||
contentDescription = null,
|
||||
tint = if (actualEnabled) colors.arrowColor else colors.disabledArrowColor,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (showDialog && itemsNotEmpty) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
title = {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
text = {
|
||||
val dialogMaxHeight = maxHeight ?: 400.dp
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = dialogMaxHeight),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
items(items.size) { index ->
|
||||
DropdownItem(
|
||||
text = items[index],
|
||||
isSelected = selectedIndex == index,
|
||||
colors = colors,
|
||||
onClick = {
|
||||
onSelectedIndexChange(index)
|
||||
showDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showDialog = false }) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
},
|
||||
containerColor = colors.dialogBackgroundColor,
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
tonalElevation = 4.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DropdownItem(
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
colors: SuperDropdownColors,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val backgroundColor = if (isSelected) {
|
||||
colors.selectedBackgroundColor
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
|
||||
val contentColor = if (isSelected) {
|
||||
colors.selectedContentColor
|
||||
} else {
|
||||
colors.contentColor
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(backgroundColor)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 12.dp, horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = isSelected,
|
||||
onClick = null,
|
||||
colors = RadioButtonDefaults.colors(
|
||||
selectedColor = colors.selectedContentColor,
|
||||
unselectedColor = colors.contentColor
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = contentColor,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = null,
|
||||
tint = colors.selectedContentColor,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class SuperDropdownColors(
|
||||
val titleColor: Color,
|
||||
val summaryColor: Color,
|
||||
val valueColor: Color,
|
||||
val iconColor: Color,
|
||||
val arrowColor: Color,
|
||||
val disabledTitleColor: Color,
|
||||
val disabledSummaryColor: Color,
|
||||
val disabledValueColor: Color,
|
||||
val disabledIconColor: Color,
|
||||
val disabledArrowColor: Color,
|
||||
val dialogBackgroundColor: Color,
|
||||
val contentColor: Color,
|
||||
val selectedContentColor: Color,
|
||||
val selectedBackgroundColor: Color
|
||||
)
|
||||
|
||||
object SuperDropdownDefaults {
|
||||
@Composable
|
||||
fun colors(
|
||||
titleColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
summaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
valueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
iconColor: Color = MaterialTheme.colorScheme.primary,
|
||||
arrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
disabledTitleColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f),
|
||||
disabledSummaryColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
disabledValueColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
disabledIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
disabledArrowColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.38f),
|
||||
dialogBackgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
selectedContentColor: Color = MaterialTheme.colorScheme.primary,
|
||||
selectedBackgroundColor: Color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f)
|
||||
): SuperDropdownColors {
|
||||
return SuperDropdownColors(
|
||||
titleColor = titleColor,
|
||||
summaryColor = summaryColor,
|
||||
valueColor = valueColor,
|
||||
iconColor = iconColor,
|
||||
arrowColor = arrowColor,
|
||||
disabledTitleColor = disabledTitleColor,
|
||||
disabledSummaryColor = disabledSummaryColor,
|
||||
disabledValueColor = disabledValueColor,
|
||||
disabledIconColor = disabledIconColor,
|
||||
disabledArrowColor = disabledArrowColor,
|
||||
dialogBackgroundColor = dialogBackgroundColor,
|
||||
contentColor = contentColor,
|
||||
selectedContentColor = selectedContentColor,
|
||||
selectedBackgroundColor = selectedBackgroundColor
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -707,7 +707,7 @@ suspend fun getModuleNameFromUri(context: Context, uri: Uri): String {
|
||||
|
||||
@Parcelize
|
||||
sealed class FlashIt : Parcelable {
|
||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt()
|
||||
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean, val partition: String? = null) : FlashIt()
|
||||
data class FlashModule(val uri: Uri) : FlashIt()
|
||||
data class FlashModules(val uris: List<Uri>, val currentIndex: Int = 0) : FlashIt()
|
||||
data class FlashModuleUpdate(val uri: Uri) : FlashIt() // 模块更新
|
||||
@@ -736,6 +736,7 @@ fun flashIt(
|
||||
flashIt.boot,
|
||||
flashIt.lkm,
|
||||
flashIt.ota,
|
||||
flashIt.partition,
|
||||
onFinish,
|
||||
onStdout,
|
||||
onStderr
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Input
|
||||
import androidx.compose.material.icons.filled.AutoFixHigh
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.FileUpload
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.*
|
||||
@@ -51,7 +52,7 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.getKernelVersion
|
||||
import com.sukisu.ultra.ui.component.DialogHandle
|
||||
import zako.zako.zako.zakoui.screen.kernelFlash.component.SlotSelectionDialog
|
||||
import com.sukisu.ultra.ui.component.SuperDropdown
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
@@ -60,6 +61,7 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import zako.zako.zako.zakoui.screen.kernelFlash.component.SlotSelectionDialog
|
||||
|
||||
/**
|
||||
* @author ShirkNeko
|
||||
@@ -87,9 +89,12 @@ fun InstallScreen(
|
||||
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 = isAbDevice()
|
||||
val isAbDevice = produceState(initialValue = false) {
|
||||
value = isAbDevice()
|
||||
}.value
|
||||
val summary = stringResource(R.string.horizon_kernel_summary)
|
||||
|
||||
// 处理预选的内核文件
|
||||
@@ -103,6 +108,7 @@ fun InstallScreen(
|
||||
)
|
||||
installMethod = horizonMethod
|
||||
tempKernelUri = preselectedUri
|
||||
|
||||
if (isAbDevice) {
|
||||
showSlotSelectionDialog = true
|
||||
} else {
|
||||
@@ -133,6 +139,10 @@ fun InstallScreen(
|
||||
)
|
||||
}
|
||||
|
||||
var partitionSelectionIndex by remember { mutableIntStateOf(0) }
|
||||
var partitionsState by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||
var hasCustomSelected by remember { mutableStateOf(false) }
|
||||
|
||||
val onInstall = {
|
||||
installMethod?.let { method ->
|
||||
when (method) {
|
||||
@@ -149,10 +159,13 @@ fun InstallScreen(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val isOta = method is InstallMethod.DirectInstallToInactiveSlot
|
||||
val partitionSelection = partitionsState.getOrNull(partitionSelectionIndex)
|
||||
val flashIt = FlashIt.FlashBoot(
|
||||
boot = if (method is InstallMethod.SelectFile) method.uri else null,
|
||||
lkm = lkmSelection,
|
||||
ota = method is InstallMethod.DirectInstallToInactiveSlot
|
||||
ota = isOta,
|
||||
partition = partitionSelection
|
||||
)
|
||||
navigator.navigate(FlashScreenDestination(flashIt))
|
||||
}
|
||||
@@ -173,6 +186,7 @@ fun InstallScreen(
|
||||
summary = summary
|
||||
)
|
||||
installMethod = horizonMethod
|
||||
|
||||
if (preselectedKernelUri != null) {
|
||||
showKpmPatchDialog = true
|
||||
}
|
||||
@@ -274,11 +288,72 @@ fun InstallScreen(
|
||||
selectedMethod = installMethod
|
||||
)
|
||||
|
||||
// 选择LKM直接安装分区
|
||||
AnimatedVisibility(
|
||||
visible = installMethod is InstallMethod.DirectInstall || installMethod is InstallMethod.DirectInstallToInactiveSlot,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||
elevation = getCardElevation(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
) {
|
||||
val isOta = installMethod is InstallMethod.DirectInstallToInactiveSlot
|
||||
val suffix = produceState(initialValue = "", isOta) {
|
||||
value = getSlotSuffix(isOta)
|
||||
}.value
|
||||
|
||||
val partitions = produceState(initialValue = emptyList()) {
|
||||
value = getAvailablePartitions()
|
||||
}.value
|
||||
|
||||
val defaultPartition = produceState(initialValue = "") {
|
||||
value = getDefaultPartition()
|
||||
}.value
|
||||
|
||||
partitionsState = partitions
|
||||
val displayPartitions = partitions.map { name ->
|
||||
if (defaultPartition == name) "$name (default)" else name
|
||||
}
|
||||
|
||||
val defaultIndex = partitions.indexOf(defaultPartition).takeIf { it >= 0 } ?: 0
|
||||
if (!hasCustomSelected) partitionSelectionIndex = defaultIndex
|
||||
|
||||
SuperDropdown(
|
||||
items = displayPartitions,
|
||||
selectedIndex = partitionSelectionIndex,
|
||||
title = "${stringResource(R.string.install_select_partition)} (${suffix})",
|
||||
onSelectedIndexChange = { index ->
|
||||
hasCustomSelected = true
|
||||
partitionSelectionIndex = index
|
||||
},
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Default.Edit,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// 使用本地的LKM文件
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||
elevation = getCardElevation(),
|
||||
@@ -311,7 +386,8 @@ fun InstallScreen(
|
||||
.clickable { onLkmUpload() }
|
||||
)
|
||||
}
|
||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||
|
||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||
if (method.slot != null) {
|
||||
ElevatedCard(
|
||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant),
|
||||
@@ -319,12 +395,6 @@ fun InstallScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.shadow(
|
||||
elevation = cardElevation,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
@@ -346,12 +416,6 @@ fun InstallScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.shadow(
|
||||
elevation = cardElevation,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
spotColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = when (kpmPatchOption) {
|
||||
@@ -496,15 +560,15 @@ private fun SelectInstallMethod(
|
||||
selectedMethod: InstallMethod? = null
|
||||
) {
|
||||
val rootAvailable = rootAvailable()
|
||||
val isAbDevice = isAbDevice()
|
||||
val isAbDevice = produceState(initialValue = false) {
|
||||
value = isAbDevice()
|
||||
}.value
|
||||
val defaultPartitionName = produceState(initialValue = "boot") {
|
||||
value = getDefaultPartition()
|
||||
}.value
|
||||
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
||||
val selectFileTip = stringResource(
|
||||
id = R.string.select_file_tip,
|
||||
if (isInitBoot()) {
|
||||
"init_boot / vendor_boot ${stringResource(R.string.select_file_tip_vendor)}"
|
||||
} else {
|
||||
"boot"
|
||||
}
|
||||
id = R.string.select_file_tip, defaultPartitionName
|
||||
)
|
||||
|
||||
val radioOptions = mutableListOf<InstallMethod>(
|
||||
@@ -601,7 +665,6 @@ private fun SelectInstallMethod(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp)
|
||||
.clip(MaterialTheme.shapes.large)
|
||||
) {
|
||||
MaterialTheme(
|
||||
colorScheme = MaterialTheme.colorScheme.copy(
|
||||
@@ -640,7 +703,7 @@ private fun SelectInstallMethod(
|
||||
bottom = 16.dp
|
||||
)
|
||||
) {
|
||||
radioOptions.take(3).forEach { option ->
|
||||
radioOptions.filter { it !is InstallMethod.HorizonKernel }.forEach { option ->
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
Surface(
|
||||
color = if (option.javaClass == selectedOption?.javaClass)
|
||||
@@ -708,7 +771,6 @@ private fun SelectInstallMethod(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
.clip(MaterialTheme.shapes.large)
|
||||
) {
|
||||
MaterialTheme(
|
||||
colorScheme = MaterialTheme.colorScheme.copy(
|
||||
|
||||
@@ -150,7 +150,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
SettingDropdown(
|
||||
SuperDropdown(
|
||||
icon = Icons.Rounded.EnhancedEncryption,
|
||||
title = stringResource(id = R.string.settings_enable_enhanced_security),
|
||||
summary = stringResource(id = R.string.settings_enable_enhanced_security_summary),
|
||||
@@ -193,7 +193,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
SettingDropdown(
|
||||
SuperDropdown(
|
||||
icon = Icons.Rounded.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_disable_su),
|
||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||
@@ -236,7 +236,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
}
|
||||
SettingDropdown(
|
||||
SuperDropdown(
|
||||
icon = Icons.Rounded.RemoveCircle,
|
||||
title = stringResource(id = R.string.settings_disable_kernel_umount),
|
||||
summary = stringResource(id = R.string.settings_disable_kernel_umount_summary),
|
||||
@@ -1077,103 +1077,3 @@ private fun UidScannerSection(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingDropdown(
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
summary: String,
|
||||
items: List<String>,
|
||||
selectedIndex: Int,
|
||||
leftAction: (@Composable () -> Unit)? = null,
|
||||
onSelectedIndexChange: (Int) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
val selectedItemText = items.getOrNull(selectedIndex) ?: ""
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showDialog = true }
|
||||
.padding(horizontal = SPACING_LARGE, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
if (leftAction != null) {
|
||||
leftAction()
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(end = SPACING_LARGE)
|
||||
.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||
Text(
|
||||
text = selectedItemText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
title = { Text(text = title) },
|
||||
text = {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
items(items.size) { index ->
|
||||
val item = items[index]
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onSelectedIndexChange(index)
|
||||
showDialog = false
|
||||
}
|
||||
.padding(vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedIndex == index,
|
||||
onClick = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(text = item)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showDialog = false }) {
|
||||
Text(text = stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1573,7 +1573,9 @@ private fun BasicSettingsContent(
|
||||
onEnableAvcLogSpoofingChange: (Boolean) -> Unit
|
||||
) {
|
||||
var scriptLocationExpanded by remember { mutableStateOf(false) }
|
||||
val isAbDevice = isAbDevice()
|
||||
val isAbDevice = produceState(initialValue = false) {
|
||||
value = isAbDevice()
|
||||
}.value
|
||||
val isSusVersion159 = isSusVersion159()
|
||||
|
||||
Column(
|
||||
@@ -2063,7 +2065,9 @@ private fun SlotInfoDialog(
|
||||
onUseUname: (String) -> Unit,
|
||||
onUseBuildTime: (String) -> Unit
|
||||
) {
|
||||
val isAbDevice = isAbDevice()
|
||||
val isAbDevice = produceState(initialValue = false) {
|
||||
value = isAbDevice()
|
||||
}.value
|
||||
|
||||
if (showDialog && isAbDevice) {
|
||||
AlertDialog(
|
||||
|
||||
@@ -255,6 +255,7 @@ fun installBoot(
|
||||
bootUri: Uri?,
|
||||
lkm: LkmSelection,
|
||||
ota: Boolean,
|
||||
partition: String?,
|
||||
onFinish: (Boolean, Int) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
onStderr: (String) -> Unit,
|
||||
@@ -314,6 +315,10 @@ fun installBoot(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
cmd += " -o $downloadsDir"
|
||||
|
||||
partition?.let { part ->
|
||||
cmd += " --partition $part"
|
||||
}
|
||||
|
||||
val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr)
|
||||
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
|
||||
|
||||
@@ -344,18 +349,6 @@ fun rootAvailable(): Boolean {
|
||||
return shell.isRoot
|
||||
}
|
||||
|
||||
fun isAbDevice(): Boolean {
|
||||
val shell = getRootShell()
|
||||
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
|
||||
}
|
||||
|
||||
fun isInitBoot(): Boolean {
|
||||
val shell = getRootShell();
|
||||
if (shell.isRoot) {
|
||||
return SuFile("/dev/block/by-name/init_boot").exists() || SuFile("/dev/block/by-name/init_boot_a").exists()
|
||||
}
|
||||
return !Os.uname().release.contains("android12-")
|
||||
}
|
||||
|
||||
suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
@@ -365,7 +358,36 @@ suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
|
||||
|
||||
suspend fun getSupportedKmis(): List<String> = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
val cmd = "boot-info supported-kmi"
|
||||
val cmd = "boot-info supported-kmis"
|
||||
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
||||
out.filter { it.isNotBlank() }.map { it.trim() }
|
||||
}
|
||||
|
||||
suspend fun isAbDevice(): Boolean = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
val cmd = "boot-info is-ab-device"
|
||||
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim().toBoolean()
|
||||
}
|
||||
|
||||
suspend fun getDefaultPartition(): String = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
val cmd = "boot-info default-partition"
|
||||
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
|
||||
}
|
||||
|
||||
suspend fun getSlotSuffix(ota: Boolean): String = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
val cmd = if (ota) {
|
||||
"boot-info slot-suffix --ota"
|
||||
} else {
|
||||
"boot-info slot-suffix"
|
||||
}
|
||||
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd").trim()
|
||||
}
|
||||
|
||||
suspend fun getAvailablePartitions(): List<String> = withContext(Dispatchers.IO) {
|
||||
val shell = getRootShell()
|
||||
val cmd = "boot-info available-partitions"
|
||||
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
|
||||
out.filter { it.isNotBlank() }.map { it.trim() }
|
||||
}
|
||||
|
||||
@@ -108,8 +108,9 @@
|
||||
<string name="direct_install">直接安装(推荐)</string>
|
||||
<string name="select_file">选择一个需要修补的镜像</string>
|
||||
<string name="install_inactive_slot">安装到未使用的槽位(OTA 后)</string>
|
||||
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认?</string>
|
||||
<string name="install_inactive_slot_warning">将在重启后强制切换到另一个槽位!注意只能在 OTA 更新完成后的重启之前使用。</string>
|
||||
<string name="install_next">下一步</string>
|
||||
<string name="install_select_partition">选择分区</string>
|
||||
<string name="install_upload_lkm_file">使用本地 LKM 文件</string>
|
||||
<string name="install_only_support_ko_file">仅支持选择 .ko 文件</string>
|
||||
<string name="select_file_tip">建议选择 %1$s 分区镜像</string>
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
<string name="install_inactive_slot">Install to inactive slot (After OTA)</string>
|
||||
<string name="install_inactive_slot_warning">Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue?</string>
|
||||
<string name="install_next">Next</string>
|
||||
<string name="install_select_partition">Select partition</string>
|
||||
<string name="install_upload_lkm_file">Use local LKM file</string>
|
||||
<string name="install_only_support_ko_file">Only .ko files are supported</string>
|
||||
<string name="select_file_tip">%1$s partition image is recommended</string>
|
||||
|
||||
Reference in New Issue
Block a user