refactor(ui): 統一介面風格並優化佈局

- Home:重新整理資訊卡,將 StringBuilder 替換為 Composables;修正填充和 HtmlText 參數錯誤。
- Manager:為 TabRow 添加動畫指示器並統一頁面間距。
- Settings:為 KeyStore 對話框添加垂直滾動功能,以防止在小螢幕上出現裁剪。
- Repo:改善「即將推出」的 UI,增添圖標。
This commit is contained in:
NkBe 2025-12-11 22:38:14 +08:00
parent f215471400
commit f14f65d078
No known key found for this signature in database
GPG Key ID: 525137026FF031DF
9 changed files with 257 additions and 169 deletions

2
core

@ -1 +1 @@
Subproject commit f8c4b6c5a52e6fe07d288d96f3765fd18aaa928c
Subproject commit 34f12244c931941420d011fbffb1858c8c045c1f

View File

@ -15,9 +15,9 @@ enum class BottomBarDestination(
val iconSelected: ImageVector,
val iconNotSelected: ImageVector
) {
Repo(RepoScreenDestination, R.string.screen_repo, Icons.Filled.GetApp, Icons.Outlined.GetApp),
Home(HomeScreenDestination, R.string.screen_home, Icons.Filled.Home, Icons.Outlined.Home),
Manage(ManageScreenDestination, R.string.screen_manage, Icons.Filled.Dashboard, Icons.Outlined.Dashboard),
Home(HomeScreenDestination, R.string.app_name, Icons.Filled.Home, Icons.Outlined.Home),
Repo(RepoScreenDestination, R.string.screen_repo, Icons.Filled.GetApp, Icons.Outlined.GetApp),
Logs(LogsScreenDestination, R.string.screen_logs, Icons.Filled.Assignment, Icons.Outlined.Assignment),
Settings(SettingsScreenDestination, R.string.screen_settings, Icons.Filled.Settings, Icons.Outlined.Settings);
}

View File

@ -13,23 +13,19 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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 com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph
@ -50,7 +46,6 @@ import rikka.shizuku.Shizuku
@Destination
@Composable
fun HomeScreen(navigator: DestinationsNavigator) {
// Install from intent
var isIntentLaunched by rememberSaveable { mutableStateOf(false) }
val activity = LocalContext.current as Activity
val intent = activity.intent
@ -76,14 +71,15 @@ fun HomeScreen(navigator: DestinationsNavigator) {
Column(
modifier = Modifier
.padding(innerPadding)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
ShizukuCard()
InfoCard()
SupportCard()
Spacer(Modifier)
Spacer(Modifier.height(16.dp))
}
}
}
@ -92,7 +88,6 @@ private val listener: (Int, Int) -> Unit = { _, grantResult ->
ShizukuApi.isPermissionGranted = grantResult == PackageManager.PERMISSION_GRANTED
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ShizukuCard() {
LaunchedEffect(Unit) {
@ -104,11 +99,21 @@ private fun ShizukuCard() {
}
}
ElevatedCard(
colors = CardDefaults.elevatedCardColors(containerColor = run {
if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.errorContainer
})
val containerColor = if (ShizukuApi.isPermissionGranted) {
MaterialTheme.colorScheme.secondaryContainer
} else {
MaterialTheme.colorScheme.errorContainer
}
val contentColor = if (ShizukuApi.isPermissionGranted) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.onErrorContainer
}
Card(
colors = CardDefaults.cardColors(containerColor = containerColor),
modifier = Modifier.clip(CardDefaults.shape)
) {
Row(
modifier = Modifier
@ -118,126 +123,148 @@ private fun ShizukuCard() {
Shizuku.requestPermission(114514)
}
}
.padding(24.dp),
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (ShizukuApi.isPermissionGranted) {
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.shizuku_available))
Column(Modifier.padding(start = 20.dp)) {
Text(
text = stringResource(R.string.shizuku_available),
fontFamily = FontFamily.Serif,
style = MaterialTheme.typography.titleMedium
Icon(
imageVector = if (ShizukuApi.isPermissionGranted) Icons.Outlined.CheckCircle else Icons.Outlined.Warning,
contentDescription = null,
tint = contentColor,
modifier = Modifier.size(40.dp)
)
Spacer(Modifier.height(4.dp))
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = "API " + Shizuku.getVersion(),
style = MaterialTheme.typography.bodyMedium
text = stringResource(if (ShizukuApi.isPermissionGranted) R.string.shizuku_available else R.string.shizuku_unavailable),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = contentColor
)
}
} else {
Icon(Icons.Outlined.Warning, stringResource(R.string.shizuku_unavailable))
Column(Modifier.padding(start = 20.dp)) {
Text(
text = stringResource(R.string.shizuku_unavailable),
fontFamily = FontFamily.Serif,
style = MaterialTheme.typography.titleMedium
text = if (ShizukuApi.isPermissionGranted) "API ${Shizuku.getVersion()}" else stringResource(R.string.home_shizuku_warning),
style = MaterialTheme.typography.bodyMedium,
color = contentColor.copy(alpha = 0.8f)
)
Spacer(Modifier.height(4.dp))
Text(
text = stringResource(R.string.home_shizuku_warning),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
private val apiVersion = if (Build.VERSION.PREVIEW_SDK_INT != 0) {
"${Build.VERSION.CODENAME} Preview (API ${Build.VERSION.PREVIEW_SDK_INT})"
} else {
"${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})"
}
private val device = buildString {
append(Build.MANUFACTURER[0].uppercaseChar().toString() + Build.MANUFACTURER.substring(1))
if (Build.BRAND != Build.MANUFACTURER) {
append(" " + Build.BRAND[0].uppercaseChar() + Build.BRAND.substring(1))
}
append(" " + Build.MODEL + " ")
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun InfoCard() {
val context = LocalContext.current
val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope()
ElevatedCard {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
) {
val contents = StringBuilder()
val infoCardContent: @Composable (Pair<String, String>) -> Unit = { texts ->
contents.appendLine(texts.first).appendLine(texts.second).appendLine()
Text(text = texts.first, style = MaterialTheme.typography.bodyLarge)
Text(text = texts.second, style = MaterialTheme.typography.bodyMedium)
val apiVersion = if (Build.VERSION.PREVIEW_SDK_INT != 0) {
"${Build.VERSION.CODENAME} Preview (API ${Build.VERSION.PREVIEW_SDK_INT})"
} else {
"${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})"
}
infoCardContent(stringResource(R.string.home_api_version) to "${LSPConfig.instance.API_CODE}")
val deviceName = buildString {
append(Build.MANUFACTURER.replaceFirstChar { it.uppercase() })
if (Build.BRAND != Build.MANUFACTURER) {
append(" " + Build.BRAND.replaceFirstChar { it.uppercase() })
}
append(" " + Build.MODEL)
}
Spacer(Modifier.height(24.dp))
infoCardContent(stringResource(R.string.home_npatch_version) to LSPConfig.instance.VERSION_NAME + " (${LSPConfig.instance.VERSION_CODE})")
Spacer(Modifier.height(24.dp))
infoCardContent(stringResource(R.string.home_framework_version) to LSPConfig.instance.CORE_VERSION_NAME + " (${LSPConfig.instance.CORE_VERSION_CODE})")
Spacer(Modifier.height(24.dp))
infoCardContent(stringResource(R.string.home_system_version) to apiVersion)
Spacer(Modifier.height(24.dp))
infoCardContent(stringResource(R.string.home_device) to device)
Spacer(Modifier.height(24.dp))
infoCardContent(stringResource(R.string.home_system_abi) to Build.SUPPORTED_ABIS[0])
val copiedMessage = stringResource(R.string.home_info_copied)
TextButton(
modifier = Modifier.align(Alignment.End),
onClick = {
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("NPatch", contents.toString()))
scope.launch { snackbarHost.showSnackbar(copiedMessage) }
},
content = { Text(stringResource(android.R.string.copy)) }
val infoList = listOf(
stringResource(R.string.home_api_version) to "${LSPConfig.instance.API_CODE}",
stringResource(R.string.home_npatch_version) to "${LSPConfig.instance.VERSION_NAME} (${LSPConfig.instance.VERSION_CODE})",
stringResource(R.string.home_framework_version) to "${LSPConfig.instance.CORE_VERSION_NAME} (${LSPConfig.instance.CORE_VERSION_CODE})",
stringResource(R.string.home_system_version) to apiVersion,
stringResource(R.string.home_device) to deviceName,
stringResource(R.string.home_system_abi) to Build.SUPPORTED_ABIS[0]
)
val copySuccessMessage = stringResource(R.string.home_info_copied)
ElevatedCard {
Column(
modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 12.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.home_device_info),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary
)
IconButton(onClick = {
val contentString = infoList.joinToString("\n") { "${it.first}: ${it.second}" }
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(ClipData.newPlainText("NPatch Info", contentString))
scope.launch { snackbarHost.showSnackbar(copySuccessMessage) }
}) {
Icon(Icons.Outlined.ContentCopy, contentDescription = "Copy")
}
}
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp), color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
Column(modifier = Modifier.padding(vertical = 8.dp)) {
infoList.forEach { (label, value) ->
InfoRow(label, value)
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
private fun InfoRow(label: String, value: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = label,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = value,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
)
}
}
@Composable
private fun SupportCard() {
ElevatedCard {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp)
.padding(16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Outlined.Info, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
Spacer(Modifier.width(8.dp))
Text(
text = stringResource(R.string.home_support),
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.titleMedium
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold
)
}
Spacer(Modifier.height(8.dp))
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = stringResource(R.string.home_description),
style = MaterialTheme.typography.bodyMedium
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(Modifier.height(12.dp))
HtmlText(
stringResource(
R.string.home_view_source_code,

View File

@ -1,13 +1,15 @@
package org.lsposed.npatch.ui.page
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
@ -32,37 +34,51 @@ fun ManageScreen(
) {
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState()
val tabTitles = listOf(stringResource(R.string.apps), stringResource(R.string.modules))
Scaffold(
topBar = { CenterTopBar(stringResource(BottomBarDestination.Manage.label)) },
floatingActionButton = { if (pagerState.currentPage == 0) AppManageFab(navigator) }
floatingActionButton = {
if (pagerState.currentPage == 0) AppManageFab(navigator)
}
) { innerPadding ->
Box(Modifier.padding(innerPadding)) {
Column {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
TabRow(
contentColor = MaterialTheme.colorScheme.secondary,
selectedTabIndex = pagerState.currentPage
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { scope.launch { pagerState.animateScrollToPage(0) } }
) {
Text(
modifier = Modifier.padding(vertical = 16.dp),
text = stringResource(R.string.apps)
selectedTabIndex = pagerState.currentPage,
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage])
)
}
Tab(
selected = pagerState.currentPage == 1,
onClick = { scope.launch { pagerState.animateScrollToPage(1) } }
) {
tabTitles.forEachIndexed { index, title ->
val selected = pagerState.currentPage == index
Tab(
selected = selected,
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
text = {
Text(
modifier = Modifier.padding(vertical = 16.dp),
text = stringResource(R.string.modules)
text = title,
style = MaterialTheme.typography.titleSmall,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
modifier = Modifier.padding(vertical = 12.dp)
)
},
)
}
}
HorizontalPager(count = 2, state = pagerState) { page ->
HorizontalPager(
count = tabTitles.size,
state = pagerState,
modifier = Modifier.weight(1f)
) { page ->
when (page) {
0 -> AppManageBody(navigator, resultRecipient)
1 -> ModuleManageBody()
@ -70,5 +86,4 @@ fun ManageScreen(
}
}
}
}
}

View File

@ -1,15 +1,17 @@
package org.lsposed.npatch.ui.page
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Construction
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.ramcosta.composedestinations.annotation.Destination
import org.lsposed.npatch.R
import org.lsposed.npatch.ui.component.CenterTopBar
@OptIn(ExperimentalMaterial3Api::class)
@ -19,12 +21,34 @@ fun RepoScreen() {
Scaffold(
topBar = { CenterTopBar(stringResource(BottomBarDestination.Repo.label)) }
) { innerPadding ->
Text(
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
.fillMaxSize()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Outlined.Construction,
contentDescription = null,
modifier = Modifier.size(72.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.coming_soon),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "This page is not yet implemented",
textAlign = TextAlign.Center
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}

View File

@ -8,9 +8,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@ -52,7 +50,9 @@ fun SettingsScreen() {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)
) {
KeyStore()
DetailPatchLogs()
@ -122,7 +122,6 @@ private fun KeyStore() {
onDismissRequest = { expanded = false; showDialog = false },
confirmButton = {
TextButton(
content = { Text(stringResource(android.R.string.ok)) },
onClick = {
wrongKeystore = false
wrongPassword = false
@ -159,25 +158,31 @@ private fun KeyStore() {
scope.launch { MyKeyStore.setCustom(password, alias, aliasPassword) }
expanded = false
showDialog = false
})
}
) {
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(
content = { Text(stringResource(android.R.string.cancel)) },
onClick = { expanded = false; showDialog = false }
)
TextButton(onClick = { expanded = false; showDialog = false }) {
Text(stringResource(android.R.string.cancel))
}
},
title = {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.settings_keystore_dialog_title),
textAlign = TextAlign.Center
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge
)
},
text = {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val interactionSource = remember { MutableInteractionSource() }
LaunchedEffect(interactionSource) {
@ -188,6 +193,7 @@ private fun KeyStore() {
}
}
// Error Message Handling
val wrongText = when {
wrongAliasPassword -> stringResource(R.string.settings_keystore_wrong_alias_password)
wrongAliasName -> stringResource(R.string.settings_keystore_wrong_alias)
@ -195,10 +201,13 @@ private fun KeyStore() {
wrongKeystore -> stringResource(R.string.settings_keystore_wrong_keystore)
else -> null
}
Text(
modifier = Modifier.padding(bottom = 8.dp),
text = wrongText ?: stringResource(R.string.settings_keystore_desc),
color = if (wrongText != null) MaterialTheme.colorScheme.error else Color.Unspecified
color = if (wrongText != null) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
OutlinedTextField(
@ -209,28 +218,32 @@ private fun KeyStore() {
placeholder = { Text(stringResource(R.string.settings_keystore_file)) },
singleLine = true,
isError = wrongKeystore,
interactionSource = interactionSource
interactionSource = interactionSource,
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text(stringResource(R.string.settings_keystore_password)) },
singleLine = true,
isError = wrongPassword
isError = wrongPassword,
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = alias,
onValueChange = { alias = it },
label = { Text(stringResource(R.string.settings_keystore_alias)) },
singleLine = true,
isError = wrongAliasName
isError = wrongAliasName,
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = aliasPassword,
onValueChange = { aliasPassword = it },
label = { Text(stringResource(R.string.settings_keystore_alias_password)) },
singleLine = true,
isError = wrongAliasPassword
isError = wrongAliasPassword,
modifier = Modifier.fillMaxWidth()
)
}
}

View File

@ -10,6 +10,7 @@
<string name="modules">模块</string>
<string name="shizuku_available">Shizuku 服务可用</string>
<string name="shizuku_unavailable">Shizuku 服务未连接</string>
<string name="screen_home">主页</string>
<string name="screen_repo">仓库</string>
<string name="screen_logs">日志</string>
<string name="off">关闭</string>
@ -21,6 +22,8 @@
<string name="home_framework_version">框架版本</string>
<string name="home_system_version">系统版本</string>
<string name="home_device">设备</string>
<string name="home_device_info">设备信息</string>
<string name="coming_soon">敬请期待</string>
<string name="home_system_abi">系统架构</string>
<string name="home_info_copied">已复制到剪贴板</string>
<string name="home_support">支持</string>

View File

@ -10,6 +10,7 @@
<string name="modules">模組</string>
<string name="shizuku_available">Shizuku 服務可用</string>
<string name="shizuku_unavailable">Shizuku 服務未連線</string>
<string name="screen_home">首頁</string>
<string name="screen_repo">倉庫</string>
<string name="screen_logs">日誌</string>
<string name="off">關閉</string>
@ -21,6 +22,8 @@
<string name="home_framework_version">框架版本</string>
<string name="home_system_version">系統版本</string>
<string name="home_device">裝置</string>
<string name="home_device_info">設備資訊</string>
<string name="coming_soon">敬請期待</string>
<string name="home_system_abi">系統架構</string>
<string name="home_info_copied">已複製到剪貼簿</string>
<string name="home_support">支援</string>

View File

@ -9,6 +9,7 @@
<string name="modules">Modules</string>
<string name="shizuku_available">Shizuku service available</string>
<string name="shizuku_unavailable">Shizuku service not connected</string>
<string name="screen_home">Home</string>
<string name="screen_repo">Repo</string>
<string name="screen_logs">Logs</string>
<string name="off">Off</string>
@ -21,6 +22,8 @@
<string name="home_framework_version">Framework Version</string>
<string name="home_system_version">System Version</string>
<string name="home_device">Device</string>
<string name="home_device_info">Equipment Information</string>
<string name="coming_soon">Stay tuned</string>
<string name="home_system_abi">System ABI</string>
<string name="home_info_copied">Copied to clipboard</string>
<string name="home_support">Support</string>