feat: multi-server failover forwarding + reorder/enable
Bark servers now form an ordered failover chain: forwarding tries each enabled server in list order, stopping at the first success. - model: BarkServer.enabled (defaults true; legacy JSON compatible) - core/BarkSender.pushToFirst: ordered try-until-success helper, shared by NotifyListenerService / PhoneCallMonitor / RetryWorker - BarkClient.push uses msg.copy(deviceKey=server.deviceKey) so each server pushes its own key - SettingsStore: drop currentServer/setCurrent; add moveServer + setServerEnabled; NotificationProcessor no longer sources deviceKey - UI: ServerRow RadioButton -> Checkbox + per-row test/move-up/move-down; remove top "send test push" button; HomeScreen shows enabled count - version 1.5.1 -> 1.6.0 (code 9 -> 10); CLAUDE.md synced Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
||||
applicationId = "com.a2i.forwarder"
|
||||
minSdk = 34
|
||||
targetSdk = 36
|
||||
versionCode = 9
|
||||
versionName = "1.5.1"
|
||||
versionCode = 10
|
||||
versionName = "1.6.0"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
||||
@@ -36,7 +36,7 @@ class BarkClient(private val server: BarkServer) {
|
||||
* HTTP 4xx/5xx 是服务端拒绝,重试无意义,直接抛出。
|
||||
*/
|
||||
private suspend fun doPushWithRetry(msg: BarkMessage) {
|
||||
val body = json.encodeToString(msg).toRequestBody(JSON)
|
||||
val body = json.encodeToString(msg.copy(deviceKey = server.deviceKey)).toRequestBody(JSON)
|
||||
val url = server.server.trim().removeSuffix("/") + "/push"
|
||||
val req = Request.Builder().url(url).post(body).build()
|
||||
var lastError: IOException? = null
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.a2i.forwarder.core
|
||||
|
||||
import com.a2i.forwarder.model.BarkMessage
|
||||
import com.a2i.forwarder.model.BarkServer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* 多服务器容错推送:按列表顺序依次尝试,首个成功即止。
|
||||
* 全失败 → 返回最后一个错误;空列表 → "无可用服务器"。
|
||||
*/
|
||||
object BarkSender {
|
||||
suspend fun pushToFirst(servers: List<BarkServer>, msg: BarkMessage): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
require(servers.any()) { "无可用服务器" }
|
||||
var lastErr: Throwable? = null
|
||||
for (s in servers) {
|
||||
val r = BarkClient(s).push(msg)
|
||||
if (r.isSuccess) return@runCatching
|
||||
lastErr = r.exceptionOrNull()
|
||||
}
|
||||
throw lastErr ?: error("推送失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ class NotificationProcessor(private val context: Context) {
|
||||
val level = if (important) "timeSensitive" else "active"
|
||||
|
||||
val msg = BarkMessage(
|
||||
deviceKey = settings.currentServer.value?.deviceKey.orEmpty(),
|
||||
deviceKey = "",
|
||||
title = msgTitle.take(100),
|
||||
body = content.ifBlank { title }.take(500),
|
||||
level = level,
|
||||
|
||||
@@ -70,8 +70,6 @@ class PhoneCallMonitor(private val context: Context) {
|
||||
val app = A2iApp.instance
|
||||
val settings = app.settings
|
||||
if (!settings.forwardingEnabled.value) return
|
||||
val server = settings.currentServer.value ?: return
|
||||
if (server.deviceKey.isBlank()) return
|
||||
|
||||
when {
|
||||
// IDLE → RINGING:来电
|
||||
@@ -82,14 +80,14 @@ class PhoneCallMonitor(private val context: Context) {
|
||||
if (now - lastRingTime > 3000) {
|
||||
lastRingTime = now
|
||||
val msg = BarkMessage(
|
||||
deviceKey = server.deviceKey,
|
||||
deviceKey = "",
|
||||
title = "来电",
|
||||
body = "有电话进来",
|
||||
level = "timeSensitive",
|
||||
group = "phone_call",
|
||||
isArchive = "1",
|
||||
)
|
||||
pushToServer(server, msg, app, "来电")
|
||||
pushToServers(msg, app, "来电")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +100,14 @@ class PhoneCallMonitor(private val context: Context) {
|
||||
from == TelephonyManager.CALL_STATE_RINGING && to == TelephonyManager.CALL_STATE_IDLE -> {
|
||||
if (!offhook && ringing) {
|
||||
val msg = BarkMessage(
|
||||
deviceKey = server.deviceKey,
|
||||
deviceKey = "",
|
||||
title = "未接来电",
|
||||
body = "未接来电",
|
||||
level = "timeSensitive",
|
||||
group = "phone_call",
|
||||
isArchive = "1",
|
||||
)
|
||||
pushToServer(server, msg, app, "未接来电")
|
||||
pushToServers(msg, app, "未接来电")
|
||||
}
|
||||
ringing = false
|
||||
offhook = false
|
||||
@@ -123,14 +121,11 @@ class PhoneCallMonitor(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun pushToServer(
|
||||
server: com.a2i.forwarder.model.BarkServer,
|
||||
msg: BarkMessage,
|
||||
app: A2iApp,
|
||||
logBody: String,
|
||||
) {
|
||||
private fun pushToServers(msg: BarkMessage, app: A2iApp, logBody: String) {
|
||||
scope.launch {
|
||||
val result = BarkClient(server).push(msg)
|
||||
val servers = app.settings.servers.value.filter { it.enabled && it.deviceKey.isNotBlank() }
|
||||
if (servers.isEmpty()) return@launch
|
||||
val result = BarkSender.pushToFirst(servers, msg)
|
||||
val now = System.currentTimeMillis()
|
||||
if (result.isSuccess) {
|
||||
app.logStore.addLog(
|
||||
@@ -142,7 +137,7 @@ class PhoneCallMonitor(private val context: Context) {
|
||||
ForwardLog(now, "phone_call", "电话", msg.title, logBody, "failed", reason)
|
||||
)
|
||||
app.logStore.addPending(
|
||||
PendingPush(UUID.randomUUID().toString(), now, server.id, json.encodeToString(msg))
|
||||
PendingPush(UUID.randomUUID().toString(), now, servers.first().id, json.encodeToString(msg))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,11 @@ class RetryWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
|
||||
|
||||
var allOk = true
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val servers = settings.servers.value.filter { it.enabled && it.deviceKey.isNotBlank() }
|
||||
for (p in list) {
|
||||
val server = settings.servers.value.firstOrNull { it.id == p.serverId }
|
||||
?: settings.currentServer.value
|
||||
?: continue
|
||||
if (server.deviceKey.isBlank()) { allOk = false; continue }
|
||||
if (servers.isEmpty()) { allOk = false; break }
|
||||
val msg = runCatching { json.decodeFromString<com.a2i.forwarder.model.BarkMessage>(p.messageJson) }.getOrNull() ?: continue
|
||||
val r = BarkClient(server).push(msg)
|
||||
val r = BarkSender.pushToFirst(servers, msg)
|
||||
if (r.isSuccess) store.removePending(p.id) else allOk = false
|
||||
}
|
||||
return if (allOk) Result.success() else Result.retry()
|
||||
|
||||
@@ -9,6 +9,7 @@ data class BarkServer(
|
||||
val name: String,
|
||||
val server: String,
|
||||
val deviceKey: String,
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
||||
const val OFFICIAL_BARK_SERVER = "https://api.day.app"
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.a2i.forwarder.service
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import com.a2i.forwarder.A2iApp
|
||||
import com.a2i.forwarder.core.BarkClient
|
||||
import com.a2i.forwarder.core.BarkSender
|
||||
import com.a2i.forwarder.core.ForwardLog
|
||||
import com.a2i.forwarder.core.NotificationProcessor
|
||||
import com.a2i.forwarder.core.PendingPush
|
||||
@@ -58,20 +58,20 @@ class NotifyListenerService : NotificationListenerService() {
|
||||
return
|
||||
}
|
||||
|
||||
val server = app.settings.currentServer.value
|
||||
if (server == null || server.deviceKey.isBlank()) {
|
||||
app.logStore.addLog(ForwardLog(now, sbn.packageName, d.appLabel, msg.title, msg.body, "failed", "未配置 Bark key"))
|
||||
val servers = app.settings.servers.value.filter { it.enabled && it.deviceKey.isNotBlank() }
|
||||
if (servers.isEmpty()) {
|
||||
app.logStore.addLog(ForwardLog(now, sbn.packageName, d.appLabel, msg.title, msg.body, "failed", "未配置可用 Bark 服务器"))
|
||||
return
|
||||
}
|
||||
|
||||
val result = BarkClient(server).push(msg)
|
||||
val result = BarkSender.pushToFirst(servers, msg)
|
||||
if (result.isSuccess) {
|
||||
app.logStore.addLog(ForwardLog(now, sbn.packageName, d.appLabel, msg.title, msg.body, "sent", ""))
|
||||
} else {
|
||||
val reason = result.exceptionOrNull()?.message ?: "错误"
|
||||
app.logStore.addLog(ForwardLog(now, sbn.packageName, d.appLabel, msg.title, msg.body, "failed", reason))
|
||||
app.logStore.addPending(
|
||||
PendingPush(UUID.randomUUID().toString(), now, server.id, json.encodeToString(msg))
|
||||
PendingPush(UUID.randomUUID().toString(), now, servers.first().id, json.encodeToString(msg))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class SettingsStore(
|
||||
|
||||
private object K {
|
||||
val SERVERS = stringPreferencesKey("servers")
|
||||
val CURRENT = stringPreferencesKey("current")
|
||||
val ICON_PREFIX = stringPreferencesKey("icon_prefix")
|
||||
val FWD = booleanPreferencesKey("forwarding")
|
||||
val CODE = booleanPreferencesKey("code")
|
||||
@@ -60,9 +59,6 @@ class SettingsStore(
|
||||
private val _servers = MutableStateFlow(defaultServers)
|
||||
val servers = _servers.asStateFlow()
|
||||
|
||||
private val _currentServerId = MutableStateFlow(ID_OFFICIAL)
|
||||
val currentServer = MutableStateFlow<BarkServer?>(defaultServers.first())
|
||||
|
||||
val iconPrefix = MutableStateFlow(DEFAULT_ICON_PREFIX)
|
||||
val forwardingEnabled = MutableStateFlow(true)
|
||||
val codeEnabled = MutableStateFlow(true)
|
||||
@@ -92,8 +88,6 @@ class SettingsStore(
|
||||
?.let { runCatching { json.decodeFromString<List<BarkServer>>(it) }.getOrNull() }
|
||||
?: defaultServers
|
||||
_servers.value = list
|
||||
_currentServerId.value = p[K.CURRENT] ?: ID_OFFICIAL
|
||||
currentServer.value = list.firstOrNull { it.id == _currentServerId.value } ?: list.firstOrNull()
|
||||
iconPrefix.value = p[K.ICON_PREFIX] ?: DEFAULT_ICON_PREFIX
|
||||
forwardingEnabled.value = p[K.FWD] ?: true
|
||||
codeEnabled.value = p[K.CODE] ?: true
|
||||
@@ -125,15 +119,11 @@ class SettingsStore(
|
||||
private suspend fun saveServers(list: List<BarkServer>) {
|
||||
edit { it[K.SERVERS] = json.encodeToString(list) }
|
||||
_servers.value = list
|
||||
refreshCurrent()
|
||||
}
|
||||
|
||||
suspend fun addServer(name: String, server: String, key: String): BarkServer {
|
||||
val s = BarkServer(name = name.trim(), server = server.trim().removeSuffix("/"), deviceKey = key.trim())
|
||||
saveServers(_servers.value + s)
|
||||
if (_servers.value.count { it.id == s.id } == 1 && currentServer.value?.deviceKey.isNullOrBlank()) {
|
||||
setCurrent(s.id)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -142,19 +132,21 @@ class SettingsStore(
|
||||
suspend fun deleteServer(id: String) {
|
||||
val list = _servers.value.filterNot { it.id == id }
|
||||
saveServers(list)
|
||||
if (_currentServerId.value == id) {
|
||||
setCurrent(if (list.isEmpty()) "" else list.first().id)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setCurrent(id: String) {
|
||||
edit { it[K.CURRENT] = id }
|
||||
_currentServerId.value = id
|
||||
refreshCurrent()
|
||||
/** 上移/下移服务器(改变转发顺序) */
|
||||
suspend fun moveServer(id: String, up: Boolean) {
|
||||
val list = _servers.value.toMutableList()
|
||||
val idx = list.indexOfFirst { it.id == id }
|
||||
if (idx < 0) return
|
||||
val target = if (up) idx - 1 else idx + 1
|
||||
if (target < 0 || target >= list.size) return
|
||||
java.util.Collections.swap(list, idx, target)
|
||||
saveServers(list)
|
||||
}
|
||||
|
||||
private fun refreshCurrent() {
|
||||
currentServer.value = _servers.value.firstOrNull { it.id == _currentServerId.value } ?: _servers.value.firstOrNull()
|
||||
suspend fun setServerEnabled(id: String, enabled: Boolean) {
|
||||
saveServers(_servers.value.map { if (it.id == id) it.copy(enabled = enabled) else it })
|
||||
}
|
||||
|
||||
suspend fun setIconPrefix(v: String) { edit { it[K.ICON_PREFIX] = v }; iconPrefix.value = v }
|
||||
|
||||
@@ -62,7 +62,7 @@ fun HomeScreen(onOpenLog: () -> Unit, onOpenSettings: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val app = A2iApp.instance
|
||||
val forwarding by app.settings.forwardingEnabled.collectAsState()
|
||||
val current by app.settings.currentServer.collectAsState()
|
||||
val servers by app.settings.servers.collectAsState()
|
||||
val sent by app.logStore.sentCount.collectAsState()
|
||||
val filtered by app.logStore.filteredCount.collectAsState()
|
||||
val failed by app.logStore.failedCount.collectAsState()
|
||||
@@ -180,21 +180,14 @@ fun HomeScreen(onOpenLog: () -> Unit, onOpenSettings: () -> Unit) {
|
||||
) { v ->
|
||||
app.appScope.launch { app.settings.setForwarding(v) }
|
||||
}
|
||||
val enabledServers = servers.filter { it.enabled && it.deviceKey.isNotBlank() }
|
||||
ClickRow(
|
||||
title = current?.name?.ifBlank { "未命名服务器" } ?: "未配置 Bark 服务器",
|
||||
subtitle = current?.server ?: "进入设置添加官方或自建 Bark 服务",
|
||||
title = if (enabledServers.isEmpty()) "未启用 Bark 服务器" else "Bark 服务器(${enabledServers.size} 个启用)",
|
||||
subtitle = enabledServers.firstOrNull()?.server ?: "进入设置勾选要转发的服务器",
|
||||
icon = Icons.Filled.Settings,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
onClick = onOpenSettings,
|
||||
)
|
||||
if (current?.deviceKey.isNullOrBlank()) {
|
||||
Text(
|
||||
"当前服务器未填写 device key",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(start = 50.dp),
|
||||
)
|
||||
}
|
||||
ClickRow(
|
||||
title = "查看转发日志",
|
||||
subtitle = "最近 300 条处理记录",
|
||||
|
||||
@@ -24,12 +24,15 @@ import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.RestartAlt
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.SystemUpdateAlt
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -72,14 +75,11 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val app = A2iApp.instance
|
||||
val servers by app.settings.servers.collectAsState()
|
||||
val current by app.settings.currentServer.collectAsState()
|
||||
val iconPrefix by app.settings.iconPrefix.collectAsState()
|
||||
val prefs = remember { context.getSharedPreferences("a2i_ui", Context.MODE_PRIVATE) }
|
||||
var treeUri by remember { mutableStateOf<Uri?>(prefs.getString("icon_tree", null)?.let { runCatching { Uri.parse(it) }.getOrNull() }) }
|
||||
var exporting by remember { mutableStateOf(false) }
|
||||
var exportResult by remember { mutableStateOf<String?>(null) }
|
||||
var testing by remember { mutableStateOf(false) }
|
||||
var testMsg by remember { mutableStateOf<String?>(null) }
|
||||
var showAdd by remember { mutableStateOf(false) }
|
||||
var editing by remember { mutableStateOf<BarkServer?>(null) }
|
||||
|
||||
@@ -126,18 +126,21 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
||||
item { ScreenTitle("设置", "管理 Bark 服务、图标资源和维护操作") }
|
||||
|
||||
item {
|
||||
SectionCard("Bark 服务器", subtitle = "官方服务和自建服务可以并存,当前服务器用于所有推送") {
|
||||
SectionCard("Bark 服务器", subtitle = "勾选的服务器按列表顺序依次尝试转发,首个成功即止") {
|
||||
if (servers.isEmpty()) {
|
||||
Text("暂无服务器,请先添加。", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
} else {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
servers.forEach { s ->
|
||||
servers.forEachIndexed { index, s ->
|
||||
ServerRow(
|
||||
server = s,
|
||||
selected = s.id == current?.id,
|
||||
onSelect = { app.appScope.launch { app.settings.setCurrent(s.id) } },
|
||||
isFirst = index == 0,
|
||||
isLast = index == servers.lastIndex,
|
||||
onToggle = { v -> app.appScope.launch { app.settings.setServerEnabled(s.id, v) } },
|
||||
onEdit = { editing = s },
|
||||
onDelete = { app.appScope.launch { app.settings.deleteServer(s.id) } },
|
||||
onMoveUp = { app.appScope.launch { app.settings.moveServer(s.id, up = true) } },
|
||||
onMoveDown = { app.appScope.launch { app.settings.moveServer(s.id, up = false) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -150,33 +153,6 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("添加服务器")
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
app.appScope.launch {
|
||||
testing = true
|
||||
testMsg = testPush(app)
|
||||
testing = false
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
enabled = !testing,
|
||||
) {
|
||||
if (testing) {
|
||||
CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp)
|
||||
} else {
|
||||
Icon(Icons.AutoMirrored.Filled.Send, null, modifier = Modifier.size(18.dp))
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("发送测试推送")
|
||||
}
|
||||
testMsg?.let {
|
||||
Text(
|
||||
it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (it.startsWith("已发送")) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,8 +343,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
||||
onDismiss = { showAdd = false },
|
||||
onConfirm = { n, s, k ->
|
||||
app.appScope.launch {
|
||||
val ns = app.settings.addServer(n, s, k)
|
||||
app.settings.setCurrent(ns.id)
|
||||
app.settings.addServer(n, s, k)
|
||||
}
|
||||
showAdd = false
|
||||
},
|
||||
@@ -389,41 +364,66 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
||||
@Composable
|
||||
private fun ServerRow(
|
||||
server: BarkServer,
|
||||
selected: Boolean,
|
||||
onSelect: () -> Unit,
|
||||
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 (selected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.55f) else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.36f),
|
||||
border = BorderStroke(1.dp, if (selected) MaterialTheme.colorScheme.primary.copy(alpha = 0.32f) else MaterialTheme.colorScheme.outlineVariant),
|
||||
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),
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth().padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = selected, onClick = onSelect)
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.padding(8.dp)) {
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = server.enabled, onCheckedChange = onToggle)
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
server.name.ifBlank { "未命名" },
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (!server.enabled) StatusBadge("停用", MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
Text(
|
||||
server.name.ifBlank { "未命名" },
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
"${server.server} · key: ${if (server.deviceKey.isBlank()) "未填" else "已填"}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (selected) StatusBadge("当前", MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { if (!testing) app.appScope.launch { testing = true; testMsg = testPush(app, server); testing = false } },
|
||||
enabled = !testing,
|
||||
) {
|
||||
if (testing) CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp)
|
||||
else Icon(Icons.AutoMirrored.Filled.Send, "测试")
|
||||
}
|
||||
IconButton(onClick = onMoveUp, enabled = !isFirst) { Icon(Icons.Filled.KeyboardArrowUp, "上移") }
|
||||
IconButton(onClick = onMoveDown, enabled = !isLast) { Icon(Icons.Filled.KeyboardArrowDown, "下移") }
|
||||
IconButton(onClick = onEdit) { Icon(Icons.Filled.Edit, "编辑") }
|
||||
IconButton(onClick = onDelete) { Icon(Icons.Filled.Delete, "删除") }
|
||||
}
|
||||
testMsg?.let {
|
||||
Text(
|
||||
"${server.server} · key: ${if (server.deviceKey.isBlank()) "未填" else "已填"}",
|
||||
it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (it.startsWith("已发送")) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(start = 48.dp, top = 2.dp),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onEdit) { Icon(Icons.Filled.Edit, "编辑") }
|
||||
IconButton(onClick = onDelete) { Icon(Icons.Filled.Delete, "删除") }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,15 +455,14 @@ private fun ServerDialog(
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun testPush(app: A2iApp): String {
|
||||
val s = app.settings.currentServer.value ?: return "未选择服务器"
|
||||
if (s.deviceKey.isBlank()) return "未填写 device key"
|
||||
private suspend fun testPush(app: A2iApp, server: BarkServer): String {
|
||||
if (server.deviceKey.isBlank()) return "未填写 device key"
|
||||
val msg = BarkMessage(
|
||||
deviceKey = s.deviceKey,
|
||||
deviceKey = server.deviceKey,
|
||||
title = "a2i 测试",
|
||||
body = "如果你在 iOS 看到这条推送,说明配置成功",
|
||||
level = "active",
|
||||
)
|
||||
val r = BarkClient(s).push(msg)
|
||||
val r = BarkClient(server).push(msg)
|
||||
return if (r.isSuccess) "已发送,请查看 iOS 端" else "发送失败:${r.exceptionOrNull()?.message}"
|
||||
}
|
||||
Reference in New Issue
Block a user