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.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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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