feat: sync ntfy settings with bark
This commit is contained in:
@@ -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,29 +188,39 @@ 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 = {
|
||||||
if (ntfyServers.isNotEmpty()) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Spacer(Modifier.size(8.dp))
|
IconButton(onClick = { showNtfyHelp = true }) {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Icon(Icons.Filled.Info, "ntfy 配置帮助")
|
||||||
ntfyServers.forEachIndexed { i, s ->
|
}
|
||||||
ConfigRow(
|
CompactStopOnFirstSwitch(
|
||||||
title = s.name.ifBlank { s.topic },
|
checked = ntfyStopFirst,
|
||||||
subtitle = "${s.server} · topic: ${s.topic}${if (s.token.isNotBlank()) " · token已填" else ""}",
|
onChange = { v -> app.appScope.launch { app.settings.setNtfyStopOnFirst(v) } },
|
||||||
enabled = s.enabled, isFirst = i == 0, isLast = i == ntfyServers.lastIndex,
|
)
|
||||||
onToggle = { v -> app.appScope.launch { app.settings.setNtfyEnabled(s.id, v) } },
|
}
|
||||||
onEdit = { editingNtfy = s }, onDelete = { app.appScope.launch { app.settings.deleteNtfy(s.id) } },
|
},
|
||||||
onMoveUp = { app.appScope.launch { app.settings.moveNtfy(s.id, true) } },
|
) {
|
||||||
onMoveDown = { app.appScope.launch { app.settings.moveNtfy(s.id, false) } },
|
if (ntfyServers.isNotEmpty()) {
|
||||||
onTest = { app.appScope.launch { NtfySender.send(s.server, s.topic, s.token, "a2i测试", "ntfy配置测试") } },
|
Spacer(Modifier.size(8.dp))
|
||||||
)
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
}
|
ntfyServers.forEachIndexed { i, s ->
|
||||||
|
NtfyServerRow(
|
||||||
|
server = s,
|
||||||
|
isFirst = i == 0,
|
||||||
|
isLast = i == ntfyServers.lastIndex,
|
||||||
|
onToggle = { v -> app.appScope.launch { app.settings.setNtfyEnabled(s.id, v) } },
|
||||||
|
onEdit = { editingNtfy = s },
|
||||||
|
onDelete = { app.appScope.launch { app.settings.deleteNtfy(s.id) } },
|
||||||
|
onMoveUp = { app.appScope.launch { app.settings.moveNtfy(s.id, true) } },
|
||||||
|
onMoveDown = { app.appScope.launch { app.settings.moveNtfy(s.id, false) } },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddButton { showAddNtfy = true }
|
|
||||||
}
|
}
|
||||||
|
AddButton { showAddNtfy = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 APK,iPhone / iPad 可用 App Store。")
|
||||||
|
HelpStep("2", "打开 ntfy,添加订阅。服务器通常填 $NTFY_OFFICIAL_SERVER;Topic 建议用一串不容易猜到的随机名字,例如 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}"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user