2 Commits

Author SHA1 Message Date
song a48ef78844 chore: bump to 1.8.4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:53:59 +08:00
song ff9673ed11 feat: sync ntfy settings with bark 2026-07-10 08:51:26 +08:00
4 changed files with 155 additions and 44 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ android {
applicationId = "com.a2i.forwarder" applicationId = "com.a2i.forwarder"
minSdk = 34 minSdk = 34
targetSdk = 36 targetSdk = 36
versionCode = 20 versionCode = 21
versionName = "1.8.3" versionName = "1.8.4"
} }
signingConfigs { signingConfigs {
@@ -89,7 +89,6 @@ object NtfySender {
fun trySend(app: A2iApp, title: String, body: String, scope: CoroutineScope) { fun trySend(app: A2iApp, title: String, body: String, scope: CoroutineScope) {
val s = app.settings val s = app.settings
if (!s.ntfyEnabled.value) return
val configs = s.ntfyServers.value.filter { it.enabled && it.topic.isNotBlank() } val configs = s.ntfyServers.value.filter { it.enabled && it.topic.isNotBlank() }
if (configs.isEmpty()) return if (configs.isEmpty()) return
scope.launch { scope.launch {
@@ -57,8 +57,6 @@ class SettingsStore(
// 电邮转发 // 电邮转发
val EMAIL_ENABLED = booleanPreferencesKey("email_enabled") val EMAIL_ENABLED = booleanPreferencesKey("email_enabled")
val EMAIL_SERVERS = stringPreferencesKey("email_servers") val EMAIL_SERVERS = stringPreferencesKey("email_servers")
// ntfy 转发
val NTFY_ENABLED = booleanPreferencesKey("ntfy_enabled")
val NTFY_SERVERS = stringPreferencesKey("ntfy_servers") val NTFY_SERVERS = stringPreferencesKey("ntfy_servers")
// 应用更新 // 应用更新
val UPDATE_CHECK_AUTO = booleanPreferencesKey("update_check_auto") val UPDATE_CHECK_AUTO = booleanPreferencesKey("update_check_auto")
@@ -117,9 +115,8 @@ class SettingsStore(
val smsSuspended = MutableStateFlow(false) val smsSuspended = MutableStateFlow(false)
val lastSmsSentTime = MutableStateFlow(0L) val lastSmsSentTime = MutableStateFlow(0L)
// 电邮/ntfy 总开关 // 电邮总开关
val emailForwardingEnabled = MutableStateFlow(false) val emailForwardingEnabled = MutableStateFlow(false)
val ntfyEnabled = MutableStateFlow(false)
// 应用更新 // 应用更新
val updateCheckEnabled = MutableStateFlow(true) val updateCheckEnabled = MutableStateFlow(true)
@@ -154,9 +151,8 @@ class SettingsStore(
smsBalance.value = p[K.SMS_BALANCE] ?: -1 smsBalance.value = p[K.SMS_BALANCE] ?: -1
smsSuspended.value = p[K.SMS_SUSPENDED] ?: false smsSuspended.value = p[K.SMS_SUSPENDED] ?: false
lastSmsSentTime.value = p[K.SMS_LAST_SENT] ?: 0L lastSmsSentTime.value = p[K.SMS_LAST_SENT] ?: 0L
// 电邮/ntfy 总开关 // 电邮总开关
emailForwardingEnabled.value = p[K.EMAIL_ENABLED] ?: false emailForwardingEnabled.value = p[K.EMAIL_ENABLED] ?: false
ntfyEnabled.value = p[K.NTFY_ENABLED] ?: false
// 应用更新 // 应用更新
updateCheckEnabled.value = p[K.UPDATE_CHECK_AUTO] ?: true updateCheckEnabled.value = p[K.UPDATE_CHECK_AUTO] ?: true
lastUpdateCheckTime.value = p[K.LAST_UPDATE_CHECK] ?: 0L lastUpdateCheckTime.value = p[K.LAST_UPDATE_CHECK] ?: 0L
@@ -325,7 +321,6 @@ class SettingsStore(
suspend fun setIgnoredVersion(v: String) { edit { it[K.IGNORED_VERSION] = v }; ignoredVersion.value = v } suspend fun setIgnoredVersion(v: String) { edit { it[K.IGNORED_VERSION] = v }; ignoredVersion.value = v }
suspend fun setEmailForwarding(v: Boolean) { edit { it[K.EMAIL_ENABLED] = v }; emailForwardingEnabled.value = v } suspend fun setEmailForwarding(v: Boolean) { edit { it[K.EMAIL_ENABLED] = v }; emailForwardingEnabled.value = v }
suspend fun setNtfyEnabled(v: Boolean) { edit { it[K.NTFY_ENABLED] = v }; ntfyEnabled.value = v }
suspend fun snapshot() = ds.data.first() suspend fun snapshot() = ds.data.first()
@@ -84,6 +84,13 @@ private const val BARK_APP_STORE_URL = "https://apps.apple.com/app/bark-custom-n
private const val BARK_DOCS_URL = "https://bark.day.app/#/" private const val BARK_DOCS_URL = "https://bark.day.app/#/"
private const val BARK_GITHUB_URL = "https://github.com/Finb/Bark" private const val BARK_GITHUB_URL = "https://github.com/Finb/Bark"
private const val BARK_OFFICIAL_SERVER = "https://api.day.app" private const val BARK_OFFICIAL_SERVER = "https://api.day.app"
private const val NTFY_OFFICIAL_SERVER = "https://ntfy.sh"
private const val NTFY_ANDROID_URL = "https://play.google.com/store/apps/details?id=io.heckel.ntfy"
private const val NTFY_IOS_URL = "https://apps.apple.com/us/app/ntfy/id1625396347"
private const val NTFY_FDROID_URL = "https://f-droid.org/en/packages/io.heckel.ntfy/"
private const val NTFY_DOCS_URL = "https://docs.ntfy.sh/subscribe/phone/"
private const val NTFY_PUBLISH_DOCS_URL = "https://docs.ntfy.sh/publish/"
private const val NTFY_GITHUB_URL = "https://github.com/binwiederhier/ntfy"
@Composable @Composable
fun SettingsScreen(onOpenLog: () -> Unit) { fun SettingsScreen(onOpenLog: () -> Unit) {
@@ -97,7 +104,6 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
val ntfyStopFirst by app.settings.ntfyStopOnFirst.collectAsState() val ntfyStopFirst by app.settings.ntfyStopOnFirst.collectAsState()
val emailStopFirst by app.settings.emailStopOnFirst.collectAsState() val emailStopFirst by app.settings.emailStopOnFirst.collectAsState()
val smsStopFirst by app.settings.smsStopOnFirst.collectAsState() val smsStopFirst by app.settings.smsStopOnFirst.collectAsState()
val ntfyEnabled by app.settings.ntfyEnabled.collectAsState()
val emailEnabled by app.settings.emailForwardingEnabled.collectAsState() val emailEnabled by app.settings.emailForwardingEnabled.collectAsState()
val smsFallback by app.settings.smsFallbackEnabled.collectAsState() val smsFallback by app.settings.smsFallbackEnabled.collectAsState()
val iconPrefix by app.settings.iconPrefix.collectAsState() val iconPrefix by app.settings.iconPrefix.collectAsState()
@@ -132,6 +138,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
var showBarkHelp by remember { mutableStateOf(false) } var showBarkHelp by remember { mutableStateOf(false) }
var editingBark by remember { mutableStateOf<BarkServer?>(null) } var editingBark by remember { mutableStateOf<BarkServer?>(null) }
var showAddNtfy by remember { mutableStateOf(false) } var showAddNtfy by remember { mutableStateOf(false) }
var showNtfyHelp by remember { mutableStateOf(false) }
var editingNtfy by remember { mutableStateOf<NtfyServer?>(null) } var editingNtfy by remember { mutableStateOf<NtfyServer?>(null) }
var showAddEmail by remember { mutableStateOf(false) } var showAddEmail by remember { mutableStateOf(false) }
var editingEmail by remember { mutableStateOf<EmailServer?>(null) } var editingEmail by remember { mutableStateOf<EmailServer?>(null) }
@@ -181,23 +188,34 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
// ===== ntfy ===== // ===== ntfy =====
item { item {
SectionCard("ntfy 转发", subtitle = "POST 推送到 ntfy 服务") { SectionCard(
SwitchRow(title = "启用 ntfy 转发", subtitle = if (ntfyEnabled) "已开启" else "已关闭", checked = ntfyEnabled) { v -> app.appScope.launch { app.settings.setNtfyEnabled(v) } } "ntfy 转发",
if (ntfyEnabled) { subtitle = "勾选的 topic 参与转发;开关切换容错/广播",
SwitchRow(title = "发通一条就停", subtitle = if (ntfyStopFirst) "容错:首个成功止" else "广播:每个都发", checked = ntfyStopFirst) { v -> app.appScope.launch { app.settings.setNtfyStopOnFirst(v) } } headerAction = {
Row(verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = { showNtfyHelp = true }) {
Icon(Icons.Filled.Info, "ntfy 配置帮助")
}
CompactStopOnFirstSwitch(
checked = ntfyStopFirst,
onChange = { v -> app.appScope.launch { app.settings.setNtfyStopOnFirst(v) } },
)
}
},
) {
if (ntfyServers.isNotEmpty()) { if (ntfyServers.isNotEmpty()) {
Spacer(Modifier.size(8.dp)) Spacer(Modifier.size(8.dp))
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
ntfyServers.forEachIndexed { i, s -> ntfyServers.forEachIndexed { i, s ->
ConfigRow( NtfyServerRow(
title = s.name.ifBlank { s.topic }, server = s,
subtitle = "${s.server} · topic: ${s.topic}${if (s.token.isNotBlank()) " · token已填" else ""}", isFirst = i == 0,
enabled = s.enabled, isFirst = i == 0, isLast = i == ntfyServers.lastIndex, isLast = i == ntfyServers.lastIndex,
onToggle = { v -> app.appScope.launch { app.settings.setNtfyEnabled(s.id, v) } }, onToggle = { v -> app.appScope.launch { app.settings.setNtfyEnabled(s.id, v) } },
onEdit = { editingNtfy = s }, onDelete = { app.appScope.launch { app.settings.deleteNtfy(s.id) } }, onEdit = { editingNtfy = s },
onDelete = { app.appScope.launch { app.settings.deleteNtfy(s.id) } },
onMoveUp = { app.appScope.launch { app.settings.moveNtfy(s.id, true) } }, onMoveUp = { app.appScope.launch { app.settings.moveNtfy(s.id, true) } },
onMoveDown = { app.appScope.launch { app.settings.moveNtfy(s.id, false) } }, onMoveDown = { app.appScope.launch { app.settings.moveNtfy(s.id, false) } },
onTest = { app.appScope.launch { NtfySender.send(s.server, s.topic, s.token, "a2i测试", "ntfy配置测试") } },
) )
} }
} }
@@ -205,7 +223,6 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
AddButton { showAddNtfy = true } AddButton { showAddNtfy = true }
} }
} }
}
// ===== 电邮 ===== // ===== 电邮 =====
item { item {
@@ -328,6 +345,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
// 弹窗 // 弹窗
if (showBarkHelp) BarkHelpDialog(onDismiss = { showBarkHelp = false }) if (showBarkHelp) BarkHelpDialog(onDismiss = { showBarkHelp = false })
if (showNtfyHelp) NtfyHelpDialog(onDismiss = { showNtfyHelp = false })
if (showAddBark) ServerDialog(onDismiss = { showAddBark = false }, onConfirm = { n, s, k -> app.appScope.launch { app.settings.addServer(n, s, k) }; showAddBark = false }) if (showAddBark) ServerDialog(onDismiss = { showAddBark = false }, onConfirm = { n, s, k -> app.appScope.launch { app.settings.addServer(n, s, k) }; showAddBark = false })
editingBark?.let { srv -> ServerDialog(initial = srv, onDismiss = { editingBark = null }, onConfirm = { n, s, k -> app.appScope.launch { app.settings.updateServer(srv.copy(name = n, server = s, deviceKey = k)) }; editingBark = null }) } editingBark?.let { srv -> ServerDialog(initial = srv, onDismiss = { editingBark = null }, onConfirm = { n, s, k -> app.appScope.launch { app.settings.updateServer(srv.copy(name = n, server = s, deviceKey = k)) }; editingBark = null }) }
if (showAddNtfy) NtfyConfigDialog(onDismiss = { showAddNtfy = false }, onConfirm = { n, sv, t, tk -> app.appScope.launch { app.settings.addNtfy(NtfyServer(name = n, server = sv, topic = t, token = tk)) }; showAddNtfy = false }) if (showAddNtfy) NtfyConfigDialog(onDismiss = { showAddNtfy = false }, onConfirm = { n, sv, t, tk -> app.appScope.launch { app.settings.addNtfy(NtfyServer(name = n, server = sv, topic = t, token = tk)) }; showAddNtfy = false })
@@ -389,12 +407,12 @@ private fun BarkHelpDialog(onDismiss: () -> Unit) {
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
) )
BarkHelpStep("1", "在 iPhone / iPad 上安装 Bark,首次打开时允许通知权限。") HelpStep("1", "在 iPhone / iPad 上安装 Bark,首次打开时允许通知权限。")
BarkHelpStep("2", "打开 Bark,复制首页显示的推送地址,通常类似 $BARK_OFFICIAL_SERVER/你的Key。") HelpStep("2", "打开 Bark,复制首页显示的推送地址,通常类似 $BARK_OFFICIAL_SERVER/你的Key。")
BarkHelpStep("3", "回到 a2i,点击本区域底部的「添加」。服务器地址填 $BARK_OFFICIAL_SERVER,自建服务则填你的自建地址。") HelpStep("3", "回到 a2i,点击本区域底部的「添加」。服务器地址填 $BARK_OFFICIAL_SERVER,自建服务则填你的自建地址。")
BarkHelpStep("4", "Device Key 填推送地址最后一段 Key,不要把完整 URL 都填进去。") HelpStep("4", "Device Key 填推送地址最后一段 Key,不要把完整 URL 都填进去。")
BarkHelpStep("5", "保存后点服务器行里的发送按钮测试;iPhone 收到「a2i 测试」就说明 Bark 通道已通。") HelpStep("5", "保存后点服务器行里的发送按钮测试;iPhone 收到「a2i 测试」就说明 Bark 通道已通。")
BarkHelpStep("6", "最后回首页开启「通知转发」,并按提示授予系统通知监听权限。") HelpStep("6", "最后回首页开启「通知转发」,并按提示授予系统通知监听权限。")
Text( Text(
"常用链接", "常用链接",
@@ -402,9 +420,9 @@ private fun BarkHelpDialog(onDismiss: () -> Unit) {
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 4.dp), modifier = Modifier.padding(top = 4.dp),
) )
BarkHelpLink("Bark App Store", BARK_APP_STORE_URL) { uriHandler.openUri(it) } HelpLink("Bark App Store", BARK_APP_STORE_URL) { uriHandler.openUri(it) }
BarkHelpLink("Bark 官方文档", BARK_DOCS_URL) { uriHandler.openUri(it) } HelpLink("Bark 官方文档", BARK_DOCS_URL) { uriHandler.openUri(it) }
BarkHelpLink("Bark GitHub", BARK_GITHUB_URL) { uriHandler.openUri(it) } HelpLink("Bark GitHub", BARK_GITHUB_URL) { uriHandler.openUri(it) }
Text( Text(
"提示:如果使用自建 Bark Server,a2i 里的服务器地址要换成自建地址,但 Device Key 仍然来自 Bark App。", "提示:如果使用自建 Bark Server,a2i 里的服务器地址要换成自建地址,但 Device Key 仍然来自 Bark App。",
@@ -419,7 +437,60 @@ private fun BarkHelpDialog(onDismiss: () -> Unit) {
} }
@Composable @Composable
private fun BarkHelpStep( private fun NtfyHelpDialog(onDismiss: () -> Unit) {
val uriHandler = LocalUriHandler.current
AlertDialog(
onDismissRequest = onDismiss,
icon = { Icon(Icons.Filled.Info, null) },
title = { Text("ntfy 配置帮助") },
text = {
Column(
modifier = Modifier
.heightIn(max = 440.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
Text(
"ntfy 用来把 a2i 处理后的通知推送到 Android、iOS 或网页版 ntfy。它不需要先建项目,只要接收端和 a2i 使用同一个 server + topic 即可。",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
HelpStep("1", "在接收端安装 ntfy。Android 可用 Google Play / F-Droid / GitHub APKiPhone / iPad 可用 App Store。")
HelpStep("2", "打开 ntfy,添加订阅。服务器通常填 $NTFY_OFFICIAL_SERVERTopic 建议用一串不容易猜到的随机名字,例如 a2i_xxxxxxxx。")
HelpStep("3", "回到 a2i,点击本区域底部的「添加」。服务器和 Topic 要与接收端订阅完全一致。")
HelpStep("4", "Token 是可选项。只有你在 ntfy 账号或自建服务里把 topic 设成需要鉴权时,才填写 access token。")
HelpStep("5", "保存后点 topic 行里的发送按钮测试;接收端收到「a2i 测试」就说明 ntfy 通道已通。")
HelpStep("6", "后续只要这一行左侧保持勾选,它就会参与转发;取消勾选就是停用这个 topic。")
Text(
"常用链接",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 4.dp),
)
HelpLink("ntfy Android · Google Play", NTFY_ANDROID_URL) { uriHandler.openUri(it) }
HelpLink("ntfy Android · F-Droid", NTFY_FDROID_URL) { uriHandler.openUri(it) }
HelpLink("ntfy iOS · App Store", NTFY_IOS_URL) { uriHandler.openUri(it) }
HelpLink("ntfy 手机端文档", NTFY_DOCS_URL) { uriHandler.openUri(it) }
HelpLink("ntfy 发布 API 文档", NTFY_PUBLISH_DOCS_URL) { uriHandler.openUri(it) }
HelpLink("ntfy GitHub", NTFY_GITHUB_URL) { uriHandler.openUri(it) }
Text(
"提示:公共 topic 谁知道名字谁就可能订阅或发送,所以 topic 名不要用手机号、姓名、邮箱等可猜信息。",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 2.dp),
)
}
},
confirmButton = { TextButton(onClick = onDismiss) { Text("知道了") } },
)
}
@Composable
private fun HelpStep(
index: String, index: String,
text: String, text: String,
) { ) {
@@ -440,7 +511,7 @@ private fun BarkHelpStep(
} }
@Composable @Composable
private fun BarkHelpLink( private fun HelpLink(
title: String, title: String,
url: String, url: String,
onOpen: (String) -> Unit, onOpen: (String) -> Unit,
@@ -485,7 +556,47 @@ private fun ConfigRow(
} }
} }
// ===== Bark 服务器 Row + Dialog(保留原有) ===== // ===== ntfy / Bark Row + Dialog =====
@Composable
private fun NtfyServerRow(
server: NtfyServer, 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.topic.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 = testNtfy(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.server, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 2, overflow = TextOverflow.Ellipsis)
Text("topic: " + server.topic.ifBlank { "未填" } + if (server.token.isNotBlank()) " · token已填" else "", 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)) }
}
}
}
@Composable @Composable
private fun ServerRow( private fun ServerRow(
server: BarkServer, isFirst: Boolean, isLast: Boolean, server: BarkServer, isFirst: Boolean, isLast: Boolean,
@@ -543,7 +654,7 @@ private fun ServerDialog(initial: BarkServer? = null, onDismiss: () -> Unit, onC
@Composable @Composable
private fun NtfyConfigDialog(initial: NtfyServer? = null, onDismiss: () -> Unit, onConfirm: (String, String, String, String) -> Unit) { private fun NtfyConfigDialog(initial: NtfyServer? = null, onDismiss: () -> Unit, onConfirm: (String, String, String, String) -> Unit) {
var name by remember { mutableStateOf(initial?.name ?: "") } var name by remember { mutableStateOf(initial?.name ?: "") }
var server by remember { mutableStateOf(initial?.server ?: "https://ntfy.sh") } var server by remember { mutableStateOf(initial?.server ?: NTFY_OFFICIAL_SERVER) }
var topic by remember { mutableStateOf(initial?.topic ?: "") } var topic by remember { mutableStateOf(initial?.topic ?: "") }
var token by remember { mutableStateOf(initial?.token ?: "") } var token by remember { mutableStateOf(initial?.token ?: "") }
ConfigDialogSkeleton(title = if (initial == null) "添加 ntfy" else "编辑 ntfy", onDismiss = onDismiss, onConfirm = { onConfirm(name.trim(), server.trim().removeSuffix("/"), topic.trim(), token.trim()) }) { ConfigDialogSkeleton(title = if (initial == null) "添加 ntfy" else "编辑 ntfy", onDismiss = onDismiss, onConfirm = { onConfirm(name.trim(), server.trim().removeSuffix("/"), topic.trim(), token.trim()) }) {
@@ -613,3 +724,9 @@ private suspend fun testPush(app: A2iApp, server: BarkServer): String {
val r = BarkClient(server).push(msg) val r = BarkClient(server).push(msg)
return if (r.isSuccess) "已发送,请查看 iOS 端" else "发送失败:${r.exceptionOrNull()?.message}" return if (r.isSuccess) "已发送,请查看 iOS 端" else "发送失败:${r.exceptionOrNull()?.message}"
} }
private suspend fun testNtfy(server: NtfyServer): String {
if (server.topic.isBlank()) return "未填写 topic"
val r = NtfySender.send(server.server, server.topic, server.token, "a2i 测试", "如果你在 ntfy 看到这条推送,说明配置成功")
return if (r.isSuccess) "已发送,请查看 ntfy 接收端" else "发送失败:${r.exceptionOrNull()?.message}"
}