From d1aa6c8beb3ffbb8c80a59a46881b239691c91f3 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:39:42 +0800 Subject: [PATCH] manager: improve local LKM selection Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com> Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> --- .../com/sukisu/ultra/ui/screen/Install.kt | 105 ++++++++++++------ .../src/main/res/values-zh-rCN/strings.xml | 4 +- manager/app/src/main/res/values/strings.xml | 2 + 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt index 603a7aac..7d45c2b5 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Install.kt @@ -1,8 +1,10 @@ package com.sukisu.ultra.ui.screen import android.app.Activity +import android.content.Context import android.content.Intent import android.net.Uri +import android.provider.OpenableColumns import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -211,7 +213,17 @@ fun InstallScreen( ) { if (it.resultCode == Activity.RESULT_OK) { it.data?.data?.let { uri -> - lkmSelection = LkmSelection.LkmUri(uri) + val isKo = isKoFile(context, uri) + if (isKo) { + lkmSelection = LkmSelection.LkmUri(uri) + } else { + lkmSelection = LkmSelection.KmiNone + Toast.makeText( + context, + context.getString(R.string.install_only_support_ko_file), + Toast.LENGTH_SHORT + ).show() + } } } } @@ -228,7 +240,6 @@ fun InstallScreen( topBar = { TopBar( onBack = { navigator.popBackStack() }, - onLkmUpload = onLkmUpload, scrollBehavior = scrollBehavior ) }, @@ -268,32 +279,39 @@ fun InstallScreen( .fillMaxWidth() .padding(16.dp) ) { - (lkmSelection as? LkmSelection.LkmUri)?.let { - ElevatedCard( - colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant), - elevation = getCardElevation(), + ElevatedCard( + colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant), + elevation = getCardElevation(), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp), + ) { + ListItem( + headlineContent = { + Text(stringResource(id = R.string.install_upload_lkm_file)) + }, + supportingContent = { + (lkmSelection as? LkmSelection.LkmUri)?.let { + Text( + stringResource( + id = R.string.selected_lkm, + it.uri.lastPathSegment ?: "(file)" + ) + ) + } + }, + leadingContent = { + Icon( + Icons.AutoMirrored.Filled.Input, + contentDescription = null + ) + }, 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( - id = R.string.selected_lkm, - it.uri.lastPathSegment ?: "(file)" - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(16.dp) - ) - } + .clickable { onLkmUpload() } + ) } - - (installMethod as? InstallMethod.HorizonKernel)?.let { method -> + (installMethod as? InstallMethod.HorizonKernel)?.let { method -> if (method.slot != null) { ElevatedCard( colors = getCardColors(MaterialTheme.colorScheme.surfaceVariant), @@ -952,7 +970,6 @@ fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle { @Composable private fun TopBar( onBack: () -> Unit = {}, - onLkmUpload: () -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior? = null ) { val colorScheme = MaterialTheme.colorScheme @@ -985,21 +1002,35 @@ private fun TopBar( windowInsets = WindowInsets.safeDrawing.only( WindowInsetsSides.Top + WindowInsetsSides.Horizontal ), - actions = { - IconButton( - modifier = Modifier.padding(end = 16.dp), - onClick = onLkmUpload - ) { - Icon( - Icons.AutoMirrored.Filled.Input, - contentDescription = null - ) - } - }, scrollBehavior = scrollBehavior ) } +private fun isKoFile(context: Context, uri: Uri): Boolean { + val seg = uri.lastPathSegment ?: "" + if (seg.endsWith(".ko", ignoreCase = true)) return true + + return try { + context.contentResolver.query( + uri, + arrayOf(OpenableColumns.DISPLAY_NAME), + null, + null, + null + )?.use { cursor -> + val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (idx != -1 && cursor.moveToFirst()) { + val name = cursor.getString(idx) + name?.endsWith(".ko", ignoreCase = true) == true + } else { + false + } + } ?: false + } catch (_: Throwable) { + false + } +} + @Preview @Composable fun SelectInstallPreview() { diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index 108901ca..a7e36e67 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -110,6 +110,8 @@ 安装到未使用的槽位(OTA 后) 将在重启后强制切换到另一个槽位!\n注意只能在 OTA 更新完成后的重启之前使用。\n确认? 下一步 + 使用本地 LKM 文件 + 仅支持选择 .ko 文件 建议选择 %1$s 分区镜像 (实验性的) 选择 KMI @@ -123,7 +125,7 @@ 刷写中 刷写完成 刷写失败 - 选择的 LKM:%s + 已选择的 LKM:%s 保存日志 日志已保存 SuS SU 模式: diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 4664ff11..0bce4b1c 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -112,6 +112,8 @@ Install to inactive slot (After OTA) Your device will be **FORCED** to boot to the current inactive slot after a reboot!\nOnly use this option after OTA is done.\nContinue? Next + Use local LKM file + Only .ko files are supported %1$s partition image is recommended (Unstable) Select KMI