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.os.Environment
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.Save
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.unit.dp
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
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.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -38,6 +50,7 @@ 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,
|
||||
@@ -47,14 +60,47 @@ enum class FlashingStatus {
|
||||
|
||||
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) {
|
||||
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)
|
||||
@Composable
|
||||
@Destination<RootGraph>
|
||||
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
val context = LocalContext.current
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
var tempText: String
|
||||
val logContent = rememberSaveable { StringBuilder() }
|
||||
@@ -65,16 +111,56 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
val scrollState = rememberScrollState()
|
||||
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) {
|
||||
if (text.isNotEmpty()) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
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) {
|
||||
text += "Error: exit code = $code.\nPlease save and check the log.\n"
|
||||
text += "$errorCodeString $code.\n$checkLogString\n"
|
||||
setFlashingStatus(FlashingStatus.FAILED)
|
||||
|
||||
if (flashIt is FlashIt.FlashModules) {
|
||||
updateModuleInstallStatus(
|
||||
failedModule = moduleInstallStatus.value.currentModuleName
|
||||
)
|
||||
}
|
||||
} else {
|
||||
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(
|
||||
topBar = {
|
||||
TopBar(
|
||||
currentFlashingStatus.value,
|
||||
onBack = dropUnlessResumed {
|
||||
navigator.popBackStack()
|
||||
currentStatus,
|
||||
navigator = navigator,
|
||||
onBack = {
|
||||
if (currentFlashingStatus.value != FlashingStatus.FLASHING) {
|
||||
navigator.navigate(ModuleScreenDestination) {
|
||||
}
|
||||
}
|
||||
},
|
||||
onSave = {
|
||||
scope.launch {
|
||||
@@ -122,7 +220,7 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
"KernelSU_install_log_${date}.log"
|
||||
)
|
||||
file.writeText(logContent.toString())
|
||||
snackBarHost.showSnackbar("Log saved to ${file.absolutePath}")
|
||||
snackBarHost.showSnackbar(logSavedString.format(file.absolutePath))
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
@@ -130,8 +228,6 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (showFloatAction) {
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val reboot = stringResource(id = R.string.reboot)
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
@@ -140,38 +236,287 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
|
||||
}
|
||||
}
|
||||
},
|
||||
icon = { Icon(Icons.Filled.Refresh, reboot) },
|
||||
text = { Text(text = reboot) },
|
||||
containerColor = cardColor.copy(alpha = 1f),
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.Refresh,
|
||||
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) },
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
) { innerPadding ->
|
||||
KeyEventBlocker {
|
||||
it.key == Key.VolumeDown || it.key == Key.VolumeUp
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(1f)
|
||||
.padding(innerPadding)
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.verticalScroll(scrollState),
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) {
|
||||
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) {
|
||||
scrollState.animateScrollTo(scrollState.maxValue)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = text,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
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
|
||||
@@ -184,6 +529,7 @@ sealed class FlashIt : Parcelable {
|
||||
}
|
||||
|
||||
fun flashIt(
|
||||
context: android.content.Context,
|
||||
flashIt: FlashIt,
|
||||
onFinish: (Boolean, Int) -> Unit,
|
||||
onStdout: (String) -> Unit,
|
||||
@@ -206,7 +552,7 @@ fun flashIt(
|
||||
}
|
||||
|
||||
val currentUri = flashIt.uris[flashIt.currentIndex]
|
||||
onStdout("Installing module ${flashIt.currentIndex + 1} of ${flashIt.uris.size}")
|
||||
onStdout("\n")
|
||||
|
||||
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
|
||||
@Composable
|
||||
fun FlashScreenPreview() {
|
||||
|
||||
@@ -357,4 +357,8 @@
|
||||
<string name="selected">已选择</string>
|
||||
<string name="select">选择</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>
|
||||
|
||||
@@ -361,4 +361,8 @@
|
||||
<string name="selected">Selected</string>
|
||||
<string name="select">option</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>
|
||||
|
||||
Reference in New Issue
Block a user