manger: update flash style
add instructions to make it easier to understand
This commit is contained in:
@@ -3,28 +3,40 @@ package com.sukisu.ultra.ui.screen
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Error
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material.icons.filled.Save
|
import androidx.compose.material.icons.filled.Save
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.key
|
import androidx.compose.ui.input.key.key
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||||
|
import com.ramcosta.composedestinations.generated.destinations.ModuleScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -38,6 +50,7 @@ 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,
|
||||||
@@ -47,14 +60,47 @@ enum class FlashingStatus {
|
|||||||
|
|
||||||
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
|
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
|
||||||
|
|
||||||
|
// 添加模块安装状态跟踪
|
||||||
|
data class ModuleInstallStatus(
|
||||||
|
val totalModules: Int = 0,
|
||||||
|
val currentModule: Int = 0,
|
||||||
|
val currentModuleName: String = "",
|
||||||
|
val failedModules: MutableList<String> = mutableListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
private var moduleInstallStatus = mutableStateOf(ModuleInstallStatus())
|
||||||
|
|
||||||
fun setFlashingStatus(status: FlashingStatus) {
|
fun setFlashingStatus(status: FlashingStatus) {
|
||||||
currentFlashingStatus.value = status
|
currentFlashingStatus.value = status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateModuleInstallStatus(
|
||||||
|
totalModules: Int? = null,
|
||||||
|
currentModule: Int? = null,
|
||||||
|
currentModuleName: String? = null,
|
||||||
|
failedModule: String? = null
|
||||||
|
) {
|
||||||
|
val current = moduleInstallStatus.value
|
||||||
|
moduleInstallStatus.value = current.copy(
|
||||||
|
totalModules = totalModules ?: current.totalModules,
|
||||||
|
currentModule = currentModule ?: current.currentModule,
|
||||||
|
currentModuleName = currentModuleName ?: current.currentModuleName
|
||||||
|
)
|
||||||
|
|
||||||
|
if (failedModule != null) {
|
||||||
|
val updatedFailedModules = current.failedModules.toMutableList()
|
||||||
|
updatedFailedModules.add(failedModule)
|
||||||
|
moduleInstallStatus.value = moduleInstallStatus.value.copy(
|
||||||
|
failedModules = updatedFailedModules
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||||
|
val context = LocalContext.current
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
var tempText: String
|
var tempText: String
|
||||||
val logContent = rememberSaveable { StringBuilder() }
|
val logContent = rememberSaveable { StringBuilder() }
|
||||||
@@ -65,16 +111,56 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
|
||||||
|
val errorCodeString = stringResource(R.string.error_code)
|
||||||
|
val checkLogString = stringResource(R.string.check_log)
|
||||||
|
val logSavedString = stringResource(R.string.log_saved)
|
||||||
|
val installingModuleString = stringResource(R.string.installing_module)
|
||||||
|
|
||||||
|
// 当前模块安装状态
|
||||||
|
val currentStatus = moduleInstallStatus.value
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
LaunchedEffect(flashIt) {
|
||||||
|
if (flashIt is FlashIt.FlashModules && flashIt.currentIndex == 0) {
|
||||||
|
moduleInstallStatus.value = ModuleInstallStatus(
|
||||||
|
totalModules = flashIt.uris.size,
|
||||||
|
currentModule = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (text.isNotEmpty()) {
|
if (text.isNotEmpty()) {
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
setFlashingStatus(FlashingStatus.FLASHING)
|
setFlashingStatus(FlashingStatus.FLASHING)
|
||||||
flashIt(flashIt, onFinish = { showReboot, code ->
|
|
||||||
|
if (flashIt is FlashIt.FlashModules) {
|
||||||
|
try {
|
||||||
|
val currentUri = flashIt.uris[flashIt.currentIndex]
|
||||||
|
val moduleName = getModuleNameFromUri(context, currentUri)
|
||||||
|
updateModuleInstallStatus(
|
||||||
|
currentModuleName = moduleName
|
||||||
|
)
|
||||||
|
text = installingModuleString.format(flashIt.currentIndex + 1, flashIt.uris.size, moduleName)
|
||||||
|
logContent.append(text).append("\n")
|
||||||
|
} catch (_: Exception) {
|
||||||
|
text = installingModuleString.format(flashIt.currentIndex + 1, flashIt.uris.size, "Module")
|
||||||
|
logContent.append(text).append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flashIt(context, flashIt, onFinish = { showReboot, code ->
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
text += "Error: exit code = $code.\nPlease save and check the log.\n"
|
text += "$errorCodeString $code.\n$checkLogString\n"
|
||||||
setFlashingStatus(FlashingStatus.FAILED)
|
setFlashingStatus(FlashingStatus.FAILED)
|
||||||
|
|
||||||
|
if (flashIt is FlashIt.FlashModules) {
|
||||||
|
updateModuleInstallStatus(
|
||||||
|
failedModule = moduleInstallStatus.value.currentModuleName
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setFlashingStatus(FlashingStatus.SUCCESS)
|
setFlashingStatus(FlashingStatus.SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -106,12 +192,24 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler(enabled = true) {
|
||||||
|
if (currentFlashingStatus.value != FlashingStatus.FLASHING) {
|
||||||
|
navigator.navigate(ModuleScreenDestination) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(
|
TopBar(
|
||||||
currentFlashingStatus.value,
|
currentFlashingStatus.value,
|
||||||
onBack = dropUnlessResumed {
|
currentStatus,
|
||||||
navigator.popBackStack()
|
navigator = navigator,
|
||||||
|
onBack = {
|
||||||
|
if (currentFlashingStatus.value != FlashingStatus.FLASHING) {
|
||||||
|
navigator.navigate(ModuleScreenDestination) {
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onSave = {
|
onSave = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -122,7 +220,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
"KernelSU_install_log_${date}.log"
|
"KernelSU_install_log_${date}.log"
|
||||||
)
|
)
|
||||||
file.writeText(logContent.toString())
|
file.writeText(logContent.toString())
|
||||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
snackBarHost.showSnackbar(logSavedString.format(file.absolutePath))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
@@ -130,8 +228,6 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (showFloatAction) {
|
if (showFloatAction) {
|
||||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
|
||||||
val reboot = stringResource(id = R.string.reboot)
|
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -140,38 +236,287 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = { Icon(Icons.Filled.Refresh, reboot) },
|
icon = {
|
||||||
text = { Text(text = reboot) },
|
Icon(
|
||||||
containerColor = cardColor.copy(alpha = 1f),
|
Icons.Filled.Refresh,
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
contentDescription = stringResource(id = R.string.reboot)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(id = R.string.reboot))
|
||||||
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
expanded = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackBarHost) },
|
snackbarHost = { SnackbarHost(hostState = snackBarHost) },
|
||||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
|
containerColor = MaterialTheme.colorScheme.background
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
KeyEventBlocker {
|
KeyEventBlocker {
|
||||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(1f)
|
.fillMaxSize(1f)
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
.verticalScroll(scrollState),
|
) {
|
||||||
|
if (flashIt is FlashIt.FlashModules) {
|
||||||
|
ModuleInstallProgressBar(
|
||||||
|
currentIndex = flashIt.currentIndex + 1,
|
||||||
|
totalCount = flashIt.uris.size,
|
||||||
|
currentModuleName = currentStatus.currentModuleName,
|
||||||
|
status = currentFlashingStatus.value,
|
||||||
|
failedModules = currentStatus.failedModules
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.verticalScroll(scrollState)
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(text) {
|
LaunchedEffect(text) {
|
||||||
scrollState.animateScrollTo(scrollState.maxValue)
|
scrollState.animateScrollTo(scrollState.maxValue)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
text = text,
|
text = text,
|
||||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示模块安装进度条和状态
|
||||||
|
@Composable
|
||||||
|
fun ModuleInstallProgressBar(
|
||||||
|
currentIndex: Int,
|
||||||
|
totalCount: Int,
|
||||||
|
currentModuleName: String,
|
||||||
|
status: FlashingStatus,
|
||||||
|
failedModules: List<String>
|
||||||
|
) {
|
||||||
|
val progressColor = when(status) {
|
||||||
|
FlashingStatus.FLASHING -> MaterialTheme.colorScheme.primary
|
||||||
|
FlashingStatus.SUCCESS -> MaterialTheme.colorScheme.tertiary
|
||||||
|
FlashingStatus.FAILED -> MaterialTheme.colorScheme.error
|
||||||
|
}
|
||||||
|
|
||||||
|
val progress = animateFloatAsState(
|
||||||
|
targetValue = currentIndex.toFloat() / totalCount.toFloat(),
|
||||||
|
label = "InstallProgress"
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
// 模块名称和进度
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (currentModuleName.isNotEmpty()) currentModuleName else stringResource(R.string.module),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "$currentIndex/$totalCount",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// 进度条
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { progress.value },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(8.dp),
|
||||||
|
color = progressColor,
|
||||||
|
trackColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// 失败模块列表
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = failedModules.isNotEmpty(),
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Error,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.module_failed_count, failedModules.size),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
// 失败模块列表
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.3f),
|
||||||
|
shape = MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
failedModules.forEach { moduleName ->
|
||||||
|
Text(
|
||||||
|
text = "• $moduleName",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onErrorContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TopBar(
|
||||||
|
status: FlashingStatus,
|
||||||
|
moduleStatus: ModuleInstallStatus = ModuleInstallStatus(),
|
||||||
|
navigator: DestinationsNavigator,
|
||||||
|
onBack: () -> Unit = {},
|
||||||
|
onSave: () -> Unit = {},
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
|
) {
|
||||||
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
val cardAlpha = CardConfig.cardAlpha
|
||||||
|
|
||||||
|
val statusColor = when(status) {
|
||||||
|
FlashingStatus.FLASHING -> MaterialTheme.colorScheme.primary
|
||||||
|
FlashingStatus.SUCCESS -> MaterialTheme.colorScheme.tertiary
|
||||||
|
FlashingStatus.FAILED -> MaterialTheme.colorScheme.error
|
||||||
|
}
|
||||||
|
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
when (status) {
|
||||||
|
FlashingStatus.FLASHING -> R.string.flashing
|
||||||
|
FlashingStatus.SUCCESS -> R.string.flash_success
|
||||||
|
FlashingStatus.FAILED -> R.string.flash_failed
|
||||||
|
}
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = statusColor
|
||||||
|
)
|
||||||
|
|
||||||
|
if (moduleStatus.failedModules.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.module_failed_count, moduleStatus.failedModules.size),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
if (status != FlashingStatus.FLASHING) {
|
||||||
|
navigator.navigate(ModuleScreenDestination) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onSave) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Save,
|
||||||
|
contentDescription = stringResource(id = R.string.save_log),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
|
scrollBehavior = scrollBehavior
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
zipInputStream.close()
|
||||||
|
name
|
||||||
|
} catch (_: Exception) {
|
||||||
|
context.getString(R.string.unknown_module)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -184,6 +529,7 @@ sealed class FlashIt : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun flashIt(
|
fun flashIt(
|
||||||
|
context: android.content.Context,
|
||||||
flashIt: FlashIt,
|
flashIt: FlashIt,
|
||||||
onFinish: (Boolean, Int) -> Unit,
|
onFinish: (Boolean, Int) -> Unit,
|
||||||
onStdout: (String) -> Unit,
|
onStdout: (String) -> Unit,
|
||||||
@@ -206,7 +552,7 @@ fun flashIt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val currentUri = flashIt.uris[flashIt.currentIndex]
|
val currentUri = flashIt.uris[flashIt.currentIndex]
|
||||||
onStdout("Installing module ${flashIt.currentIndex + 1} of ${flashIt.uris.size}")
|
onStdout("\n")
|
||||||
|
|
||||||
flashModule(currentUri, onFinish, onStdout, onStderr)
|
flashModule(currentUri, onFinish, onStdout, onStderr)
|
||||||
}
|
}
|
||||||
@@ -215,51 +561,6 @@ fun flashIt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
status: FlashingStatus,
|
|
||||||
onBack: () -> Unit = {},
|
|
||||||
onSave: () -> Unit = {},
|
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
|
||||||
) {
|
|
||||||
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
val cardAlpha = CardConfig.cardAlpha
|
|
||||||
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
when (status) {
|
|
||||||
FlashingStatus.FLASHING -> R.string.flashing
|
|
||||||
FlashingStatus.SUCCESS -> R.string.flash_success
|
|
||||||
FlashingStatus.FAILED -> R.string.flash_failed
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(
|
|
||||||
onClick = onBack
|
|
||||||
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
|
||||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
|
||||||
),
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = onSave) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Save,
|
|
||||||
contentDescription = "Localized description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
|
||||||
scrollBehavior = scrollBehavior
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun FlashScreenPreview() {
|
fun FlashScreenPreview() {
|
||||||
|
|||||||
@@ -357,4 +357,8 @@
|
|||||||
<string name="selected">已选择</string>
|
<string name="selected">已选择</string>
|
||||||
<string name="select">选择</string>
|
<string name="select">选择</string>
|
||||||
<string name="profile_umount_modules_disable">禁用自定义卸载模块</string>
|
<string name="profile_umount_modules_disable">禁用自定义卸载模块</string>
|
||||||
|
<string name="error_code">错误代码</string>
|
||||||
|
<string name="check_log">请查看日志</string>
|
||||||
|
<string name="installing_module">正在安装模块 %1$d/%2$d</string>
|
||||||
|
<string name="module_failed_count">%d 个模块安装失败</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -361,4 +361,8 @@
|
|||||||
<string name="selected">Selected</string>
|
<string name="selected">Selected</string>
|
||||||
<string name="select">option</string>
|
<string name="select">option</string>
|
||||||
<string name="profile_umount_modules_disable">Disable custom uninstallation module</string>
|
<string name="profile_umount_modules_disable">Disable custom uninstallation module</string>
|
||||||
|
<string name="error_code">error code</string>
|
||||||
|
<string name="check_log">Please check the log</string>
|
||||||
|
<string name="installing_module">Module being installed %1$d/%2$d</string>
|
||||||
|
<string name="module_failed_count">%d Failed to install a new module</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user