refactor(ui): 統一介面風格並優化佈局
- Home:重新整理資訊卡,將 StringBuilder 替換為 Composables;修正填充和 HtmlText 參數錯誤。 - Manager:為 TabRow 添加動畫指示器並統一頁面間距。 - Settings:為 KeyStore 對話框添加垂直滾動功能,以防止在小螢幕上出現裁剪。 - Repo:改善「即將推出」的 UI,增添圖標。
This commit is contained in:
parent
f215471400
commit
f14f65d078
2
core
2
core
|
|
@ -1 +1 @@
|
||||||
Subproject commit f8c4b6c5a52e6fe07d288d96f3765fd18aaa928c
|
Subproject commit 34f12244c931941420d011fbffb1858c8c045c1f
|
||||||
|
|
@ -15,9 +15,9 @@ enum class BottomBarDestination(
|
||||||
val iconSelected: ImageVector,
|
val iconSelected: ImageVector,
|
||||||
val iconNotSelected: 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),
|
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),
|
Logs(LogsScreenDestination, R.string.screen_logs, Icons.Filled.Assignment, Icons.Outlined.Assignment),
|
||||||
Settings(SettingsScreenDestination, R.string.screen_settings, Icons.Filled.Settings, Icons.Outlined.Settings);
|
Settings(SettingsScreenDestination, R.string.screen_settings, Icons.Filled.Settings, Icons.Outlined.Settings);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,23 +13,19 @@ 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.outlined.CheckCircle
|
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.material.icons.outlined.Warning
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
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.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
|
|
@ -50,7 +46,6 @@ import rikka.shizuku.Shizuku
|
||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(navigator: DestinationsNavigator) {
|
fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
// Install from intent
|
|
||||||
var isIntentLaunched by rememberSaveable { mutableStateOf(false) }
|
var isIntentLaunched by rememberSaveable { mutableStateOf(false) }
|
||||||
val activity = LocalContext.current as Activity
|
val activity = LocalContext.current as Activity
|
||||||
val intent = activity.intent
|
val intent = activity.intent
|
||||||
|
|
@ -76,14 +71,15 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.padding(horizontal = 16.dp)
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
ShizukuCard()
|
ShizukuCard()
|
||||||
InfoCard()
|
InfoCard()
|
||||||
SupportCard()
|
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
|
ShizukuApi.isPermissionGranted = grantResult == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ShizukuCard() {
|
private fun ShizukuCard() {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -104,11 +99,21 @@ private fun ShizukuCard() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ElevatedCard(
|
val containerColor = if (ShizukuApi.isPermissionGranted) {
|
||||||
colors = CardDefaults.elevatedCardColors(containerColor = run {
|
MaterialTheme.colorScheme.secondaryContainer
|
||||||
if (ShizukuApi.isPermissionGranted) MaterialTheme.colorScheme.secondaryContainer
|
} else {
|
||||||
else MaterialTheme.colorScheme.errorContainer
|
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(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -118,126 +123,148 @@ private fun ShizukuCard() {
|
||||||
Shizuku.requestPermission(114514)
|
Shizuku.requestPermission(114514)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(24.dp),
|
.padding(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (ShizukuApi.isPermissionGranted) {
|
Icon(
|
||||||
Icon(Icons.Outlined.CheckCircle, stringResource(R.string.shizuku_available))
|
imageVector = if (ShizukuApi.isPermissionGranted) Icons.Outlined.CheckCircle else Icons.Outlined.Warning,
|
||||||
Column(Modifier.padding(start = 20.dp)) {
|
contentDescription = null,
|
||||||
Text(
|
tint = contentColor,
|
||||||
text = stringResource(R.string.shizuku_available),
|
modifier = Modifier.size(40.dp)
|
||||||
fontFamily = FontFamily.Serif,
|
)
|
||||||
style = MaterialTheme.typography.titleMedium
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
)
|
Column {
|
||||||
Spacer(Modifier.height(4.dp))
|
Text(
|
||||||
Text(
|
text = stringResource(if (ShizukuApi.isPermissionGranted) R.string.shizuku_available else R.string.shizuku_unavailable),
|
||||||
text = "API " + Shizuku.getVersion(),
|
style = MaterialTheme.typography.titleMedium,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
color = contentColor
|
||||||
}
|
)
|
||||||
} else {
|
Text(
|
||||||
Icon(Icons.Outlined.Warning, stringResource(R.string.shizuku_unavailable))
|
text = if (ShizukuApi.isPermissionGranted) "API ${Shizuku.getVersion()}" else stringResource(R.string.home_shizuku_warning),
|
||||||
Column(Modifier.padding(start = 20.dp)) {
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
Text(
|
color = contentColor.copy(alpha = 0.8f)
|
||||||
text = stringResource(R.string.shizuku_unavailable),
|
)
|
||||||
fontFamily = FontFamily.Serif,
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
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
|
@Composable
|
||||||
private fun InfoCard() {
|
private fun InfoCard() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val snackbarHost = LocalSnackbarHost.current
|
val snackbarHost = LocalSnackbarHost.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
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})"
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceName = buildString {
|
||||||
|
append(Build.MANUFACTURER.replaceFirstChar { it.uppercase() })
|
||||||
|
if (Build.BRAND != Build.MANUFACTURER) {
|
||||||
|
append(" " + Build.BRAND.replaceFirstChar { it.uppercase() })
|
||||||
|
}
|
||||||
|
append(" " + Build.MODEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
ElevatedCard {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
|
|
||||||
) {
|
) {
|
||||||
val contents = StringBuilder()
|
Row(
|
||||||
val infoCardContent: @Composable (Pair<String, String>) -> Unit = { texts ->
|
modifier = Modifier
|
||||||
contents.appendLine(texts.first).appendLine(texts.second).appendLine()
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
Text(text = texts.first, style = MaterialTheme.typography.bodyLarge)
|
.fillMaxWidth(),
|
||||||
Text(text = texts.second, style = MaterialTheme.typography.bodyMedium)
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
infoCardContent(stringResource(R.string.home_api_version) to "${LSPConfig.instance.API_CODE}")
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp), color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
|
||||||
|
|
||||||
Spacer(Modifier.height(24.dp))
|
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||||
infoCardContent(stringResource(R.string.home_npatch_version) to LSPConfig.instance.VERSION_NAME + " (${LSPConfig.instance.VERSION_CODE})")
|
infoList.forEach { (label, value) ->
|
||||||
|
InfoRow(label, value)
|
||||||
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)) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@Composable
|
||||||
@Preview
|
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
|
@Composable
|
||||||
private fun SupportCard() {
|
private fun SupportCard() {
|
||||||
ElevatedCard {
|
ElevatedCard {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.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),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_support),
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp),
|
|
||||||
text = stringResource(R.string.home_description),
|
text = stringResource(R.string.home_description),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
HtmlText(
|
HtmlText(
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.home_view_source_code,
|
R.string.home_view_source_code,
|
||||||
|
|
@ -247,4 +274,4 @@ private fun SupportCard() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
package org.lsposed.npatch.ui.page
|
package org.lsposed.npatch.ui.page
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
|
|
@ -32,41 +34,54 @@ fun ManageScreen(
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val pagerState = rememberPagerState()
|
val pagerState = rememberPagerState()
|
||||||
|
val tabTitles = listOf(stringResource(R.string.apps), stringResource(R.string.modules))
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { CenterTopBar(stringResource(BottomBarDestination.Manage.label)) },
|
topBar = { CenterTopBar(stringResource(BottomBarDestination.Manage.label)) },
|
||||||
floatingActionButton = { if (pagerState.currentPage == 0) AppManageFab(navigator) }
|
floatingActionButton = {
|
||||||
|
if (pagerState.currentPage == 0) AppManageFab(navigator)
|
||||||
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(Modifier.padding(innerPadding)) {
|
Column(
|
||||||
Column {
|
modifier = Modifier
|
||||||
TabRow(
|
.padding(innerPadding)
|
||||||
contentColor = MaterialTheme.colorScheme.secondary,
|
.fillMaxSize()
|
||||||
selectedTabIndex = pagerState.currentPage
|
) {
|
||||||
) {
|
TabRow(
|
||||||
Tab(
|
selectedTabIndex = pagerState.currentPage,
|
||||||
selected = pagerState.currentPage == 0,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
onClick = { scope.launch { pagerState.animateScrollToPage(0) } }
|
contentColor = MaterialTheme.colorScheme.primary,
|
||||||
) {
|
indicator = { tabPositions ->
|
||||||
Text(
|
TabRowDefaults.Indicator(
|
||||||
modifier = Modifier.padding(vertical = 16.dp),
|
Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
||||||
text = stringResource(R.string.apps)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
Tab(
|
|
||||||
selected = pagerState.currentPage == 1,
|
|
||||||
onClick = { scope.launch { pagerState.animateScrollToPage(1) } }
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(vertical = 16.dp),
|
|
||||||
text = stringResource(R.string.modules)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
tabTitles.forEachIndexed { index, title ->
|
||||||
|
val selected = pagerState.currentPage == index
|
||||||
|
Tab(
|
||||||
|
selected = selected,
|
||||||
|
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
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(
|
||||||
when (page) {
|
count = tabTitles.size,
|
||||||
0 -> AppManageBody(navigator, resultRecipient)
|
state = pagerState,
|
||||||
1 -> ModuleManageBody()
|
modifier = Modifier.weight(1f)
|
||||||
}
|
) { page ->
|
||||||
|
when (page) {
|
||||||
|
0 -> AppManageBody(navigator, resultRecipient)
|
||||||
|
1 -> ModuleManageBody()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
package org.lsposed.npatch.ui.page
|
package org.lsposed.npatch.ui.page
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material.icons.outlined.Construction
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
|
import org.lsposed.npatch.R
|
||||||
import org.lsposed.npatch.ui.component.CenterTopBar
|
import org.lsposed.npatch.ui.component.CenterTopBar
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -19,12 +21,34 @@ fun RepoScreen() {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { CenterTopBar(stringResource(BottomBarDestination.Repo.label)) }
|
topBar = { CenterTopBar(stringResource(BottomBarDestination.Repo.label)) }
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Text(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.fillMaxSize(),
|
.fillMaxSize()
|
||||||
text = "This page is not yet implemented",
|
.padding(32.dp),
|
||||||
textAlign = TextAlign.Center
|
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,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,9 +8,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.PressInteraction
|
import androidx.compose.foundation.interaction.PressInteraction
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
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
|
||||||
|
|
@ -52,7 +50,9 @@ fun SettingsScreen() {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
KeyStore()
|
KeyStore()
|
||||||
DetailPatchLogs()
|
DetailPatchLogs()
|
||||||
|
|
@ -122,7 +122,6 @@ private fun KeyStore() {
|
||||||
onDismissRequest = { expanded = false; showDialog = false },
|
onDismissRequest = { expanded = false; showDialog = false },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
content = { Text(stringResource(android.R.string.ok)) },
|
|
||||||
onClick = {
|
onClick = {
|
||||||
wrongKeystore = false
|
wrongKeystore = false
|
||||||
wrongPassword = false
|
wrongPassword = false
|
||||||
|
|
@ -159,25 +158,31 @@ private fun KeyStore() {
|
||||||
scope.launch { MyKeyStore.setCustom(password, alias, aliasPassword) }
|
scope.launch { MyKeyStore.setCustom(password, alias, aliasPassword) }
|
||||||
expanded = false
|
expanded = false
|
||||||
showDialog = false
|
showDialog = false
|
||||||
})
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(
|
TextButton(onClick = { expanded = false; showDialog = false }) {
|
||||||
content = { Text(stringResource(android.R.string.cancel)) },
|
Text(stringResource(android.R.string.cancel))
|
||||||
onClick = { expanded = false; showDialog = false }
|
}
|
||||||
)
|
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.settings_keystore_dialog_title),
|
text = stringResource(R.string.settings_keystore_dialog_title),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
LaunchedEffect(interactionSource) {
|
LaunchedEffect(interactionSource) {
|
||||||
|
|
@ -188,6 +193,7 @@ private fun KeyStore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error Message Handling
|
||||||
val wrongText = when {
|
val wrongText = when {
|
||||||
wrongAliasPassword -> stringResource(R.string.settings_keystore_wrong_alias_password)
|
wrongAliasPassword -> stringResource(R.string.settings_keystore_wrong_alias_password)
|
||||||
wrongAliasName -> stringResource(R.string.settings_keystore_wrong_alias)
|
wrongAliasName -> stringResource(R.string.settings_keystore_wrong_alias)
|
||||||
|
|
@ -195,10 +201,13 @@ private fun KeyStore() {
|
||||||
wrongKeystore -> stringResource(R.string.settings_keystore_wrong_keystore)
|
wrongKeystore -> stringResource(R.string.settings_keystore_wrong_keystore)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(bottom = 8.dp),
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
text = wrongText ?: stringResource(R.string.settings_keystore_desc),
|
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(
|
OutlinedTextField(
|
||||||
|
|
@ -209,28 +218,32 @@ private fun KeyStore() {
|
||||||
placeholder = { Text(stringResource(R.string.settings_keystore_file)) },
|
placeholder = { Text(stringResource(R.string.settings_keystore_file)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = wrongKeystore,
|
isError = wrongKeystore,
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { password = it },
|
onValueChange = { password = it },
|
||||||
label = { Text(stringResource(R.string.settings_keystore_password)) },
|
label = { Text(stringResource(R.string.settings_keystore_password)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = wrongPassword
|
isError = wrongPassword,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = alias,
|
value = alias,
|
||||||
onValueChange = { alias = it },
|
onValueChange = { alias = it },
|
||||||
label = { Text(stringResource(R.string.settings_keystore_alias)) },
|
label = { Text(stringResource(R.string.settings_keystore_alias)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = wrongAliasName
|
isError = wrongAliasName,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = aliasPassword,
|
value = aliasPassword,
|
||||||
onValueChange = { aliasPassword = it },
|
onValueChange = { aliasPassword = it },
|
||||||
label = { Text(stringResource(R.string.settings_keystore_alias_password)) },
|
label = { Text(stringResource(R.string.settings_keystore_alias_password)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = wrongAliasPassword
|
isError = wrongAliasPassword,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<string name="modules">模块</string>
|
<string name="modules">模块</string>
|
||||||
<string name="shizuku_available">Shizuku 服务可用</string>
|
<string name="shizuku_available">Shizuku 服务可用</string>
|
||||||
<string name="shizuku_unavailable">Shizuku 服务未连接</string>
|
<string name="shizuku_unavailable">Shizuku 服务未连接</string>
|
||||||
|
<string name="screen_home">主页</string>
|
||||||
<string name="screen_repo">仓库</string>
|
<string name="screen_repo">仓库</string>
|
||||||
<string name="screen_logs">日志</string>
|
<string name="screen_logs">日志</string>
|
||||||
<string name="off">关闭</string>
|
<string name="off">关闭</string>
|
||||||
|
|
@ -21,6 +22,8 @@
|
||||||
<string name="home_framework_version">框架版本</string>
|
<string name="home_framework_version">框架版本</string>
|
||||||
<string name="home_system_version">系统版本</string>
|
<string name="home_system_version">系统版本</string>
|
||||||
<string name="home_device">设备</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_system_abi">系统架构</string>
|
||||||
<string name="home_info_copied">已复制到剪贴板</string>
|
<string name="home_info_copied">已复制到剪贴板</string>
|
||||||
<string name="home_support">支持</string>
|
<string name="home_support">支持</string>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<string name="modules">模組</string>
|
<string name="modules">模組</string>
|
||||||
<string name="shizuku_available">Shizuku 服務可用</string>
|
<string name="shizuku_available">Shizuku 服務可用</string>
|
||||||
<string name="shizuku_unavailable">Shizuku 服務未連線</string>
|
<string name="shizuku_unavailable">Shizuku 服務未連線</string>
|
||||||
|
<string name="screen_home">首頁</string>
|
||||||
<string name="screen_repo">倉庫</string>
|
<string name="screen_repo">倉庫</string>
|
||||||
<string name="screen_logs">日誌</string>
|
<string name="screen_logs">日誌</string>
|
||||||
<string name="off">關閉</string>
|
<string name="off">關閉</string>
|
||||||
|
|
@ -21,6 +22,8 @@
|
||||||
<string name="home_framework_version">框架版本</string>
|
<string name="home_framework_version">框架版本</string>
|
||||||
<string name="home_system_version">系統版本</string>
|
<string name="home_system_version">系統版本</string>
|
||||||
<string name="home_device">裝置</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_system_abi">系統架構</string>
|
||||||
<string name="home_info_copied">已複製到剪貼簿</string>
|
<string name="home_info_copied">已複製到剪貼簿</string>
|
||||||
<string name="home_support">支援</string>
|
<string name="home_support">支援</string>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<string name="modules">Modules</string>
|
<string name="modules">Modules</string>
|
||||||
<string name="shizuku_available">Shizuku service available</string>
|
<string name="shizuku_available">Shizuku service available</string>
|
||||||
<string name="shizuku_unavailable">Shizuku service not connected</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_repo">Repo</string>
|
||||||
<string name="screen_logs">Logs</string>
|
<string name="screen_logs">Logs</string>
|
||||||
<string name="off">Off</string>
|
<string name="off">Off</string>
|
||||||
|
|
@ -21,6 +22,8 @@
|
||||||
<string name="home_framework_version">Framework Version</string>
|
<string name="home_framework_version">Framework Version</string>
|
||||||
<string name="home_system_version">System Version</string>
|
<string name="home_system_version">System Version</string>
|
||||||
<string name="home_device">Device</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_system_abi">System ABI</string>
|
||||||
<string name="home_info_copied">Copied to clipboard</string>
|
<string name="home_info_copied">Copied to clipboard</string>
|
||||||
<string name="home_support">Support</string>
|
<string name="home_support">Support</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue