From 3ed2377b3c1729ec2a23ff8c9774ffd292ad1443 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sun, 4 Oct 2026 18:31:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AE=BE=E7=BD=AE):=20=E3=80=8C=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E3=80=8D=E8=A1=8C=E5=8F=AF=E7=82=B9=E8=BF=9B=E3=80=8C?= =?UTF-8?q?=E5=85=B3=E4=BA=8E=E3=80=8D=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 设置页的「关于」分组里,「版本」此行此前点了没反应——它是全页唯一 没有 onClick 的行。现在它带 ChevronForward(与管理员入口行同一种「可 进子页」提示)并可点,进入新的「关于」页。 「关于」页只有四项:应用名(Cyrene)、版本、一句话简介、作者 (AskaEth),自己带 Scaffold + 返回按钮,观感与设置页一致。版本号复用 BuildConfig,不写死;页面里不放任何链接 / 邮箱 / 版权年份——这些信息 没有人提供过。 返回层级照项目既有的手写布尔状态 + 分层 BackHandler 模式(本项目无导航 库),新增 showAbout: - 关于 → 设置 → 聊天; - 设置页的处理器条件是 showSettings && !showAbout(与关于页互斥), 所以在关于页按返回只退一层,不会一步跳回聊天; - 抽屉 / 设置 / 管理页既有层级未动。 版本文本抽成纯函数 versionText(versionName, versionCode),设置页与关于页 共用一份格式,并由 VersionTextTest 钉住输出。 测试:485 → 487(新增 VersionTextTest 2 条),0 失败 / 0 错误; assembleDebug 通过。 --- .../top/yeij/cyrene/ui/chat/ChatHomeScreen.kt | 10 +- .../yeij/cyrene/ui/settings/AboutScreen.kt | 96 +++++++++++++++++++ .../yeij/cyrene/ui/settings/SettingsScreen.kt | 11 ++- .../cyrene/ui/settings/VersionTextTest.kt | 23 +++++ 4 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 app/src/main/kotlin/top/yeij/cyrene/ui/settings/AboutScreen.kt create mode 100644 app/src/test/kotlin/top/yeij/cyrene/ui/settings/VersionTextTest.kt diff --git a/app/src/main/kotlin/top/yeij/cyrene/ui/chat/ChatHomeScreen.kt b/app/src/main/kotlin/top/yeij/cyrene/ui/chat/ChatHomeScreen.kt index 8bd7cb4..c77a52d 100644 --- a/app/src/main/kotlin/top/yeij/cyrene/ui/chat/ChatHomeScreen.kt +++ b/app/src/main/kotlin/top/yeij/cyrene/ui/chat/ChatHomeScreen.kt @@ -15,6 +15,7 @@ import top.yeij.cyrene.ui.admin.AdminHomeScreen import top.yeij.cyrene.ui.common.MiuixDrawer import top.yeij.cyrene.ui.sessions.SessionDrawerContent import top.yeij.cyrene.ui.sessions.SessionsViewModel +import top.yeij.cyrene.ui.settings.AboutScreen import top.yeij.cyrene.ui.settings.SettingsScreen /** @@ -33,9 +34,13 @@ fun ChatHomeScreen( var drawerOpen by remember { mutableStateOf(false) } var showSettings by remember { mutableStateOf(false) } var showAdmin by remember { mutableStateOf(false) } + var showAbout by remember { mutableStateOf(false) } BackHandler(enabled = showAdmin) { showAdmin = false } - BackHandler(enabled = showSettings) { showSettings = false } + // 「关于」嵌在设置页里,所以两个条件必须**互斥**:在关于页时设置页的处理器关掉, + // 返回才只退一层(关于 → 设置),不会一步跳回聊天。 + BackHandler(enabled = showAbout) { showAbout = false } + BackHandler(enabled = showSettings && !showAbout) { showSettings = false } BackHandler(enabled = drawerOpen) { drawerOpen = false } when { @@ -44,10 +49,13 @@ fun ChatHomeScreen( onBack = { showAdmin = false }, ) + showAbout -> AboutScreen(onBack = { showAbout = false }) + showSettings -> SettingsScreen( container = container, onBack = { showSettings = false }, onLogout = onLogout, + onOpenAbout = { showAbout = true }, ) else -> MiuixDrawer( diff --git a/app/src/main/kotlin/top/yeij/cyrene/ui/settings/AboutScreen.kt b/app/src/main/kotlin/top/yeij/cyrene/ui/settings/AboutScreen.kt new file mode 100644 index 0000000..20ed0d1 --- /dev/null +++ b/app/src/main/kotlin/top/yeij/cyrene/ui/settings/AboutScreen.kt @@ -0,0 +1,96 @@ +package top.yeij.cyrene.ui.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import top.yeij.cyrene.BuildConfig +import top.yeij.cyrene.ui.common.PreferenceCard +import top.yukonga.miuix.kmp.basic.BasicComponent +import top.yukonga.miuix.kmp.basic.Icon +import top.yukonga.miuix.kmp.basic.IconButton +import top.yukonga.miuix.kmp.basic.Scaffold +import top.yukonga.miuix.kmp.basic.SmallTopAppBar +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.icon.MiuixIcons +import top.yukonga.miuix.kmp.icon.extended.ChevronBackward +import top.yukonga.miuix.kmp.theme.MiuixTheme + +/** + * 「关于」页:只有名称、版本、简介、作者这四项。 + * + * 这四项分别来自本端常量与 `BuildConfig`(由 `app/build.gradle.kts` 生成),**不写死版本号**; + * 页面里刻意不放任何链接 / 邮箱 / 版权年份——没有人提供过这些信息,编不得。 + * + * 与设置页同为聊天之上的独立层级:自己带 `Scaffold`,返回落到设置页(层级见 `ChatHomeScreen`)。 + */ +@Composable +fun AboutScreen(onBack: () -> Unit) { + Scaffold( + topBar = { + SmallTopAppBar( + title = "关于", + navigationIcon = { + IconButton(onClick = onBack) { + Icon(imageVector = MiuixIcons.ChevronBackward, contentDescription = "返回") + } + }, + ) + }, + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(Modifier.height(40.dp)) + Text("Cyrene", style = MiuixTheme.textStyles.title1) + Spacer(Modifier.height(4.dp)) + Text( + text = "Cyrene v2 后端的 Android 客户端", + style = MiuixTheme.textStyles.footnote1, + color = MiuixTheme.colorScheme.onSurfaceVariantSummary, + modifier = Modifier.padding(horizontal = 24.dp), + ) + + Spacer(Modifier.height(32.dp)) + AboutRow(title = "版本", value = versionText(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)) + Spacer(Modifier.height(8.dp)) + AboutRow(title = "作者", value = "AskaEth") + Spacer(Modifier.height(32.dp)) + } + } +} + +@Composable +private fun AboutRow(title: String, value: String) { + PreferenceCard { + BasicComponent( + title = title, + endActions = { + Text( + text = value, + style = MiuixTheme.textStyles.footnote1, + color = MiuixTheme.colorScheme.onSurfaceVariantSummary, + ) + }, + ) + } +} + +/** + * 版本号的显示文本(纯函数):设置页的「版本」行与关于页共用同一份输出,格式不会各写各的。 + * + * 值一律来自 `BuildConfig`(AGP 按 `build.gradle.kts` 的 `versionName` / `versionCode` 生成), + * 两处都不写死版本号,发版只需改构建文件。 + */ +internal fun versionText(versionName: String, versionCode: Int): String = "$versionName ($versionCode)" diff --git a/app/src/main/kotlin/top/yeij/cyrene/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/top/yeij/cyrene/ui/settings/SettingsScreen.kt index 09322f7..d755c78 100644 --- a/app/src/main/kotlin/top/yeij/cyrene/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/top/yeij/cyrene/ui/settings/SettingsScreen.kt @@ -33,6 +33,7 @@ import top.yukonga.miuix.kmp.basic.Text import top.yukonga.miuix.kmp.icon.MiuixIcons import top.yukonga.miuix.kmp.icon.extended.Back import top.yukonga.miuix.kmp.icon.extended.ChevronBackward +import top.yukonga.miuix.kmp.icon.extended.ChevronForward import top.yukonga.miuix.kmp.preference.SwitchPreference import top.yukonga.miuix.kmp.theme.MiuixTheme @@ -41,6 +42,7 @@ fun SettingsScreen( container: AppContainer, onBack: () -> Unit, onLogout: () -> Unit = {}, + onOpenAbout: () -> Unit = {}, ) { val prefs by container.appSettings.data.collectAsState(initial = AppPrefs()) val scope = rememberCoroutineScope() @@ -129,12 +131,19 @@ fun SettingsScreen( BasicComponent( title = "版本", summary = null, + onClick = onOpenAbout, endActions = { Text( - text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})", + text = versionText(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE), style = MiuixTheme.textStyles.footnote1, color = MiuixTheme.colorScheme.onSurfaceVariantSummary, ) + Icon( + imageVector = MiuixIcons.ChevronForward, + contentDescription = null, + modifier = Modifier.padding(start = 6.dp), + tint = MiuixTheme.colorScheme.onSurfaceVariantSummary, + ) }, ) } diff --git a/app/src/test/kotlin/top/yeij/cyrene/ui/settings/VersionTextTest.kt b/app/src/test/kotlin/top/yeij/cyrene/ui/settings/VersionTextTest.kt new file mode 100644 index 0000000..7513cbe --- /dev/null +++ b/app/src/test/kotlin/top/yeij/cyrene/ui/settings/VersionTextTest.kt @@ -0,0 +1,23 @@ +package top.yeij.cyrene.ui.settings + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * 版本文本的格式(纯函数):设置页的「版本」行与关于页共用同一份输出。 + * + * 这里钉的是**格式**,不是某个具体版本号——值来自 `BuildConfig`,发版只改构建文件。 + */ +class VersionTextTest { + + @Test + fun `version name is followed by the code in parentheses`() { + assertEquals("0.1.0 (1)", versionText("0.1.0", 1)) + } + + @Test + fun `both inputs are rendered verbatim, so the label tracks the build config`() { + assertEquals("2.3.4 (567)", versionText("2.3.4", 567)) + assertEquals("1.0.0-beta (42)", versionText("1.0.0-beta", 42)) + } +}