manager: Enhance and simplify module name capture

- Add the use of incoming module name to load the corresponding installation list when the file cannot be retrieved, using utf-8 encoding and formatting characters by default.
This commit is contained in:
ShirkNeko
2025-05-20 22:23:45 +08:00
parent 83bd4e9642
commit b2ae20b796
3 changed files with 157 additions and 88 deletions

View File

@@ -50,7 +50,6 @@ import com.sukisu.ultra.ui.theme.CardConfig
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.zip.ZipInputStream
enum class FlashingStatus { enum class FlashingStatus {
FLASHING, FLASHING,
@@ -493,26 +492,13 @@ private fun TopBar(
suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): String { suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): String {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri)) if (uri == Uri.EMPTY) {
var entry = zipInputStream.nextEntry return@withContext context.getString(R.string.unknown_module)
var name = context.getString(R.string.unknown_module)
while (entry != null) {
if (entry.name == "module.prop") {
val reader = java.io.BufferedReader(java.io.InputStreamReader(zipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line.substringAfter("=")
break
} }
if (!ModuleUtils.isUriAccessible(context, uri)) {
return@withContext context.getString(R.string.unknown_module)
} }
break ModuleUtils.extractModuleName(context, uri)
}
entry = zipInputStream.nextEntry
}
zipInputStream.close()
name
} catch (_: Exception) { } catch (_: Exception) {
context.getString(R.string.unknown_module) context.getString(R.string.unknown_module)
} }

View File

@@ -4,6 +4,7 @@ import android.app.Activity.*
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -64,10 +65,7 @@ import okhttp3.OkHttpClient
import com.sukisu.ultra.ui.util.ModuleModify import com.sukisu.ultra.ui.util.ModuleModify
import com.sukisu.ultra.ui.theme.getCardColors import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipInputStream
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
@@ -97,38 +95,21 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
scope.launch { scope.launch {
val clipData = data.clipData val clipData = data.clipData
if (clipData != null) { if (clipData != null) {
// 处理多选结果
val selectedModules = mutableListOf<Uri>() val selectedModules = mutableListOf<Uri>()
val selectedModuleNames = mutableMapOf<Uri, String>() val selectedModuleNames = mutableMapOf<Uri, String>()
suspend fun processUri(uri: Uri) { fun processUri(uri: Uri) {
val moduleName = withContext(Dispatchers.IO) {
try { try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri)) if (!ModuleUtils.isUriAccessible(context, uri)) {
var entry = zipInputStream.nextEntry return
var name = context.getString(R.string.unknown_module)
while (entry != null) {
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line.substringAfter("=")
break
}
}
break
}
entry = zipInputStream.nextEntry
}
name
} catch (e: Exception) {
context.getString(R.string.unknown_module)
}
} }
ModuleUtils.takePersistableUriPermission(context, uri)
val moduleName = ModuleUtils.extractModuleName(context, uri)
selectedModules.add(uri) selectedModules.add(uri)
selectedModuleNames[uri] = moduleName selectedModuleNames[uri] = moduleName
} catch (e: Exception) {
Log.e("ModuleScreen", "Error while processing URI: $uri, Error: ${e.message}")
}
} }
for (i in 0 until clipData.itemCount) { for (i in 0 until clipData.itemCount) {
@@ -136,7 +117,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
processUri(uri) processUri(uri)
} }
// 显示确认对话框 if (selectedModules.isEmpty()) {
snackBarHost.showSnackbar("Unable to access selected module files")
return@launch
}
val modulesList = selectedModuleNames.values.joinToString("\n", "") val modulesList = selectedModuleNames.values.joinToString("\n", "")
val confirmResult = confirmDialog.awaitConfirm( val confirmResult = confirmDialog.awaitConfirm(
title = context.getString(R.string.module_install), title = context.getString(R.string.module_install),
@@ -146,38 +131,27 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
) )
if (confirmResult == ConfirmResult.Confirmed) { if (confirmResult == ConfirmResult.Confirmed) {
try {
// 批量安装模块 // 批量安装模块
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules))) navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules)))
viewModel.markNeedRefresh() viewModel.markNeedRefresh()
} catch (e: Exception) {
Log.e("ModuleScreen", "Error navigating to FlashScreen: ${e.message}")
snackBarHost.showSnackbar("Error while installing module: ${e.message}")
}
} }
} else { } else {
// 单个文件安装逻辑
val uri = data.data ?: return@launch val uri = data.data ?: return@launch
val moduleName = withContext(Dispatchers.IO) { // 单个安装模块
try { try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri)) if (!ModuleUtils.isUriAccessible(context, uri)) {
var entry = zipInputStream.nextEntry snackBarHost.showSnackbar("Unable to access selected module files")
var name = context.getString(R.string.unknown_module) return@launch
}
while (entry != null) { ModuleUtils.takePersistableUriPermission(context, uri)
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream)) val moduleName = ModuleUtils.extractModuleName(context, uri)
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line.substringAfter("=")
break
}
}
break
}
entry = zipInputStream.nextEntry
}
name
} catch (e: Exception) {
context.getString(R.string.unknown_module)
}
}
val confirmResult = confirmDialog.awaitConfirm( val confirmResult = confirmDialog.awaitConfirm(
title = context.getString(R.string.module_install), title = context.getString(R.string.module_install),
@@ -190,6 +164,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri))) navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
viewModel.markNeedRefresh() viewModel.markNeedRefresh()
} }
} catch (e: Exception) {
Log.e("ModuleScreen", "Error processing a single URI: $uri, Error: ${e.message}")
snackBarHost.showSnackbar("Error processing module file: ${e.message}")
}
} }
} }
} }

View File

@@ -0,0 +1,105 @@
package com.sukisu.ultra.ui.util
import android.content.Context
import android.content.Intent
import android.net.Uri
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import java.util.zip.ZipInputStream
import com.sukisu.ultra.R
import android.util.Log
import java.io.IOException
object ModuleUtils {
private const val TAG = "ModuleUtils"
fun extractModuleName(context: Context, uri: Uri): String {
if (uri == Uri.EMPTY) {
Log.e(TAG, "The supplied URI is empty")
return context.getString(R.string.unknown_module)
}
return try {
Log.d(TAG, "Start extracting module names from URIs: $uri")
// 从URI路径中提取文件名
val fileName = uri.lastPathSegment?.let { path ->
val lastSlash = path.lastIndexOf('/')
if (lastSlash != -1 && lastSlash < path.length - 1) {
path.substring(lastSlash + 1)
} else {
path
}
}?.removeSuffix(".zip") ?: context.getString(R.string.unknown_module)
var formattedFileName = fileName.replace(Regex("[^a-zA-Z0-9\\s\\-_.()\\u4e00-\\u9fa5]"), "").trim()
var moduleName = formattedFileName
try {
// 打开ZIP文件输入流
val inputStream = context.contentResolver.openInputStream(uri)
if (inputStream == null) {
Log.e(TAG, "Unable to get input stream from URI: $uri")
return formattedFileName
}
val zipInputStream = ZipInputStream(inputStream)
var entry = zipInputStream.nextEntry
// 遍历ZIP文件中的条目查找module.prop文件
while (entry != null) {
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream, StandardCharsets.UTF_8))
var line: String?
var nameFound = false
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
moduleName = line.substringAfter("=")
moduleName = moduleName.replace(Regex("[^a-zA-Z0-9\\s\\-_.()\\u4e00-\\u9fa5]"), "").trim()
nameFound = true
break
}
}
break
}
entry = zipInputStream.nextEntry
}
zipInputStream.close()
Log.d(TAG, "Successfully extracted module name: $moduleName")
moduleName
} catch (e: IOException) {
Log.e(TAG, "Error reading ZIP file: ${e.message}")
formattedFileName
}
} catch (e: Exception) {
Log.e(TAG, "Exception when extracting module name: ${e.message}")
context.getString(R.string.unknown_module)
}
}
// 验证URI是否有效并可访问
fun isUriAccessible(context: Context, uri: Uri): Boolean {
if (uri == Uri.EMPTY) return false
return try {
val inputStream = context.contentResolver.openInputStream(uri)
inputStream?.close()
inputStream != null
} catch (e: Exception) {
Log.e(TAG, "The URI is inaccessible: $uri, Error: ${e.message}")
false
}
}
// 获取URI的持久权限
fun takePersistableUriPermission(context: Context, uri: Uri) {
try {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)
Log.d(TAG, "Persistent permissions for URIs have been obtained: $uri")
} catch (e: Exception) {
Log.e(TAG, "Unable to get persistent permissions on URIs: $uri, Error: ${e.message}")
}
}
}