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:
@@ -50,7 +50,6 @@ import com.sukisu.ultra.ui.theme.CardConfig
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
enum class FlashingStatus {
|
||||
FLASHING,
|
||||
@@ -493,26 +492,13 @@ private fun TopBar(
|
||||
suspend fun getModuleNameFromUri(context: android.content.Context, uri: Uri): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
|
||||
var entry = zipInputStream.nextEntry
|
||||
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
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
entry = zipInputStream.nextEntry
|
||||
if (uri == Uri.EMPTY) {
|
||||
return@withContext context.getString(R.string.unknown_module)
|
||||
}
|
||||
zipInputStream.close()
|
||||
name
|
||||
if (!ModuleUtils.isUriAccessible(context, uri)) {
|
||||
return@withContext context.getString(R.string.unknown_module)
|
||||
}
|
||||
ModuleUtils.extractModuleName(context, uri)
|
||||
} catch (_: Exception) {
|
||||
context.getString(R.string.unknown_module)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
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.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipInputStream
|
||||
import androidx.core.content.edit
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||
@@ -97,38 +95,21 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
scope.launch {
|
||||
val clipData = data.clipData
|
||||
if (clipData != null) {
|
||||
// 处理多选结果
|
||||
val selectedModules = mutableListOf<Uri>()
|
||||
val selectedModuleNames = mutableMapOf<Uri, String>()
|
||||
|
||||
suspend fun processUri(uri: Uri) {
|
||||
val moduleName = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
|
||||
var entry = zipInputStream.nextEntry
|
||||
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)
|
||||
fun processUri(uri: Uri) {
|
||||
try {
|
||||
if (!ModuleUtils.isUriAccessible(context, uri)) {
|
||||
return
|
||||
}
|
||||
ModuleUtils.takePersistableUriPermission(context, uri)
|
||||
val moduleName = ModuleUtils.extractModuleName(context, uri)
|
||||
selectedModules.add(uri)
|
||||
selectedModuleNames[uri] = moduleName
|
||||
} catch (e: Exception) {
|
||||
Log.e("ModuleScreen", "Error while processing URI: $uri, Error: ${e.message}")
|
||||
}
|
||||
selectedModules.add(uri)
|
||||
selectedModuleNames[uri] = moduleName
|
||||
}
|
||||
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
@@ -136,7 +117,11 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
processUri(uri)
|
||||
}
|
||||
|
||||
// 显示确认对话框
|
||||
if (selectedModules.isEmpty()) {
|
||||
snackBarHost.showSnackbar("Unable to access selected module files")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val modulesList = selectedModuleNames.values.joinToString("\n• ", "• ")
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.module_install),
|
||||
@@ -146,49 +131,42 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
|
||||
if (confirmResult == ConfirmResult.Confirmed) {
|
||||
// 批量安装模块
|
||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules)))
|
||||
viewModel.markNeedRefresh()
|
||||
}
|
||||
} else {
|
||||
// 单个文件安装逻辑
|
||||
val uri = data.data ?: return@launch
|
||||
val moduleName = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
|
||||
var entry = zipInputStream.nextEntry
|
||||
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
|
||||
// 批量安装模块
|
||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(selectedModules)))
|
||||
viewModel.markNeedRefresh()
|
||||
} catch (e: Exception) {
|
||||
context.getString(R.string.unknown_module)
|
||||
Log.e("ModuleScreen", "Error navigating to FlashScreen: ${e.message}")
|
||||
snackBarHost.showSnackbar("Error while installing module: ${e.message}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val uri = data.data ?: return@launch
|
||||
// 单个安装模块
|
||||
try {
|
||||
if (!ModuleUtils.isUriAccessible(context, uri)) {
|
||||
snackBarHost.showSnackbar("Unable to access selected module files")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.module_install),
|
||||
content = context.getString(R.string.module_install_confirm, moduleName),
|
||||
confirm = context.getString(R.string.install),
|
||||
dismiss = context.getString(R.string.cancel)
|
||||
)
|
||||
ModuleUtils.takePersistableUriPermission(context, uri)
|
||||
|
||||
if (confirmResult == ConfirmResult.Confirmed) {
|
||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
|
||||
viewModel.markNeedRefresh()
|
||||
val moduleName = ModuleUtils.extractModuleName(context, uri)
|
||||
|
||||
val confirmResult = confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.module_install),
|
||||
content = context.getString(R.string.module_install_confirm, moduleName),
|
||||
confirm = context.getString(R.string.install),
|
||||
dismiss = context.getString(R.string.cancel)
|
||||
)
|
||||
|
||||
if (confirmResult == ConfirmResult.Confirmed) {
|
||||
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user