ui: update text + email two-line row

- Replace "转发到 iOS Bark" with "多通道转发" across HomeScreen/FilterScreen
- Email config items now use EmailServerRow (two-line layout matching
  Bark/ntfy): name + test/edit/delete on row 1, host:port + recipient
  on row 2 with up/down buttons
- version 1.8.6 -> 1.8.7 (code 23 -> 24)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 09:31:20 +08:00
parent b32b759695
commit 8588da1f6b
4 changed files with 59 additions and 13 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ android {
applicationId = "com.a2i.forwarder"
minSdk = 34
targetSdk = 36
versionCode = 23
versionName = "1.8.6"
versionCode = 24
versionName = "1.8.7"
}
signingConfigs {
@@ -61,7 +61,7 @@ fun FilterScreen() {
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
item { ScreenTitle("过滤与优化", "减少噪音,保留真正需要同步到 iOS 的通知") }
item { ScreenTitle("过滤与优化", "减少噪音,保留真正需要转发的通知") }
item {
SectionCard("功能开关", subtitle = "规则按通知处理流水线顺序执行") {
@@ -178,7 +178,7 @@ fun HomeScreen(onOpenLog: () -> Unit, onOpenSettings: () -> Unit) {
Column(Modifier.weight(1f)) {
Text(if (granted) "通知监听已开启" else "需要授权通知监听", style = MaterialTheme.typography.titleMedium)
Text(
if (granted) "系统通知将按规则处理并推送到 Bark" else "授权后才能读取 Android 通知并转发",
if (granted) "系统通知将按规则处理并多通道转发" else "授权后才能读取 Android 通知并转发",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -202,7 +202,7 @@ fun HomeScreen(onOpenLog: () -> Unit, onOpenSettings: () -> Unit) {
SectionCard("运行状态", subtitle = "转发能力、目标服务器和日志入口") {
SwitchRow(
title = "通知转发",
subtitle = if (forwarding) "新通知会继续转发到 Bark" else "当前已暂停所有转发",
subtitle = if (forwarding) "新通知会继续多通道转发" else "当前已暂停所有转发",
checked = forwarding,
icon = Icons.Filled.PowerSettingsNew,
tint = if (forwarding) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
@@ -286,7 +286,7 @@ private fun HomeHero(
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) {
Text("a2i", style = MaterialTheme.typography.displaySmall, color = Color.White)
Text("安卓通知转发到 iOS Bark", style = MaterialTheme.typography.bodyMedium, color = Color.White.copy(alpha = 0.82f))
Text("安卓通知多通道转发", style = MaterialTheme.typography.bodyMedium, color = Color.White.copy(alpha = 0.82f))
}
StatusBadge(
text = if (granted && forwarding) "运行中" else "待处理",
@@ -295,7 +295,7 @@ private fun HomeHero(
}
Spacer(Modifier.size(14.dp))
Text(
if (granted && forwarding) "监听、过滤、提取验证码和推送链路已就绪。" else "完成授权并开启转发后,通知会按规则推送到 iOS",
if (granted && forwarding) "监听、过滤、提取验证码和推送链路已就绪。" else "完成授权并开启转发后,通知会按规则转发到各通道",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(alpha = 0.86f),
)
@@ -240,15 +240,15 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
Spacer(Modifier.size(8.dp))
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
emailServers.forEachIndexed { i, s ->
ConfigRow(
title = s.name.ifBlank { s.to },
subtitle = "${s.host}:${s.port} · ${s.user}${s.to}",
enabled = s.enabled, isFirst = i == 0, isLast = i == emailServers.lastIndex,
EmailServerRow(
server = s,
isFirst = i == 0,
isLast = i == emailServers.lastIndex,
onToggle = { v -> app.appScope.launch { app.settings.setEmailEnabled(s.id, v) } },
onEdit = { editingEmail = s }, onDelete = { app.appScope.launch { app.settings.deleteEmail(s.id) } },
onEdit = { editingEmail = s },
onDelete = { app.appScope.launch { app.settings.deleteEmail(s.id) } },
onMoveUp = { app.appScope.launch { app.settings.moveEmail(s.id, true) } },
onMoveDown = { app.appScope.launch { app.settings.moveEmail(s.id, false) } },
onTest = { app.appScope.launch { EmailSender.send(s.host, s.port, s.user, s.password, s.from, s.to, "a2i测试", "电邮配置测试") } },
)
}
}
@@ -647,6 +647,52 @@ private fun ServerRow(
}
}
@Composable
private fun EmailServerRow(
server: EmailServer, isFirst: Boolean, isLast: Boolean,
onToggle: (Boolean) -> Unit, onEdit: () -> Unit, onDelete: () -> Unit,
onMoveUp: () -> Unit, onMoveDown: () -> Unit,
) {
val app = A2iApp.instance
var testing by remember { mutableStateOf(false) }
var testMsg by remember { mutableStateOf<String?>(null) }
Surface(
modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(8.dp),
color = if (server.enabled) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.40f) else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.36f),
border = BorderStroke(1.dp, if (server.enabled) MaterialTheme.colorScheme.primary.copy(alpha = 0.30f) else MaterialTheme.colorScheme.outlineVariant),
) {
Column(Modifier.padding(start = 4.dp, end = 4.dp, top = 2.dp, bottom = 6.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = server.enabled, onCheckedChange = onToggle)
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
Text(server.name.ifBlank { server.to.ifBlank { "未命名" } }, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false))
if (!server.enabled) { Spacer(Modifier.width(8.dp)); StatusBadge("停用", MaterialTheme.colorScheme.onSurfaceVariant) }
}
IconButton(onClick = { if (!testing) app.appScope.launch { testing = true; testMsg = testEmail(server); testing = false } }, enabled = !testing) {
if (testing) CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp) else Icon(Icons.AutoMirrored.Filled.Send, "测试")
}
IconButton(onClick = onEdit) { Icon(Icons.Filled.Edit, "编辑") }
IconButton(onClick = onDelete) { Icon(Icons.Filled.Delete, "删除") }
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 2.dp)) {
Column(Modifier.weight(1f).padding(start = 36.dp)) {
Text("${server.host}:${server.port}", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(server.to.ifBlank { "收件人未填" }, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
IconButton(onClick = onMoveUp, enabled = !isFirst) { Icon(Icons.Filled.KeyboardArrowUp, "上移") }
IconButton(onClick = onMoveDown, enabled = !isLast) { Icon(Icons.Filled.KeyboardArrowDown, "下移") }
}
testMsg?.let { Text(it, style = MaterialTheme.typography.bodySmall, color = if (it.startsWith("已发送")) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.error, modifier = Modifier.padding(start = 48.dp, top = 2.dp)) }
}
}
}
private suspend fun testEmail(server: EmailServer): String {
if (server.host.isBlank() || server.to.isBlank()) return "SMTP 主机或收件人未填"
val r = EmailSender.send(server.host, server.port, server.user, server.password, server.from, server.to, "a2i 测试", "电邮配置测试")
return if (r.isSuccess) "已发送,请查看邮箱" else "发送失败:${r.exceptionOrNull()?.message}"
}
@Composable
private fun ServerDialog(initial: BarkServer? = null, onDismiss: () -> Unit, onConfirm: (String, String, String) -> Unit) {
var name by remember { mutableStateOf(initial?.name ?: "") }