|
|
@@ -6,12 +6,15 @@ import android.net.Uri
|
|
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
import androidx.compose.foundation.BorderStroke
|
|
|
|
import androidx.compose.foundation.BorderStroke
|
|
|
|
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
|
|
|
|
|
import androidx.compose.foundation.verticalScroll
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
|
|
|
import androidx.compose.foundation.layout.heightIn
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
@@ -23,6 +26,7 @@ import androidx.compose.material.icons.filled.Add
|
|
|
|
import androidx.compose.material.icons.filled.Delete
|
|
|
|
import androidx.compose.material.icons.filled.Delete
|
|
|
|
import androidx.compose.material.icons.filled.Edit
|
|
|
|
import androidx.compose.material.icons.filled.Edit
|
|
|
|
import androidx.compose.material.icons.filled.History
|
|
|
|
import androidx.compose.material.icons.filled.History
|
|
|
|
|
|
|
|
import androidx.compose.material.icons.filled.Info
|
|
|
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|
|
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
|
|
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
|
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
|
|
import androidx.compose.material.icons.filled.RestartAlt
|
|
|
|
import androidx.compose.material.icons.filled.RestartAlt
|
|
|
@@ -39,6 +43,7 @@ import androidx.compose.material3.MaterialTheme
|
|
|
|
import androidx.compose.material3.OutlinedButton
|
|
|
|
import androidx.compose.material3.OutlinedButton
|
|
|
|
import androidx.compose.material3.OutlinedTextField
|
|
|
|
import androidx.compose.material3.OutlinedTextField
|
|
|
|
import androidx.compose.material3.Surface
|
|
|
|
import androidx.compose.material3.Surface
|
|
|
|
|
|
|
|
import androidx.compose.material3.Switch
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
import androidx.compose.material3.TextButton
|
|
|
|
import androidx.compose.material3.TextButton
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
@@ -51,6 +56,7 @@ import androidx.compose.runtime.setValue
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
|
|
|
|
import androidx.compose.ui.platform.LocalUriHandler
|
|
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
|
|
import androidx.compose.ui.text.style.TextOverflow
|
|
|
|
import androidx.compose.ui.text.style.TextOverflow
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
@@ -74,6 +80,11 @@ import com.a2i.forwarder.ui.SwitchRow
|
|
|
|
import com.a2i.forwarder.ui.appVersionName
|
|
|
|
import com.a2i.forwarder.ui.appVersionName
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const val BARK_APP_STORE_URL = "https://apps.apple.com/app/bark-custom-notifications/id1403753865"
|
|
|
|
|
|
|
|
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_OFFICIAL_SERVER = "https://api.day.app"
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
@Composable
|
|
|
|
fun SettingsScreen(onOpenLog: () -> Unit) {
|
|
|
|
fun SettingsScreen(onOpenLog: () -> Unit) {
|
|
|
|
val context = LocalContext.current
|
|
|
|
val context = LocalContext.current
|
|
|
@@ -118,6 +129,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
|
|
|
|
|
|
|
|
|
|
|
// 弹窗状态
|
|
|
|
// 弹窗状态
|
|
|
|
var showAddBark by remember { mutableStateOf(false) }
|
|
|
|
var showAddBark 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 editingNtfy by remember { mutableStateOf<NtfyServer?>(null) }
|
|
|
|
var editingNtfy by remember { mutableStateOf<NtfyServer?>(null) }
|
|
|
@@ -134,13 +146,21 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
|
|
|
|
|
|
|
|
|
|
|
// ===== Bark 服务器 =====
|
|
|
|
// ===== Bark 服务器 =====
|
|
|
|
item {
|
|
|
|
item {
|
|
|
|
SectionCard("Bark 服务器", subtitle = "勾选的服务器参与转发;开关切换容错/广播") {
|
|
|
|
SectionCard(
|
|
|
|
SwitchRow(
|
|
|
|
"Bark 服务器",
|
|
|
|
title = "发通一条就停",
|
|
|
|
subtitle = "勾选的服务器参与转发;开关切换容错/广播",
|
|
|
|
subtitle = if (barkStopFirst) "容错模式:首个成功即止" else "广播模式:每个都发",
|
|
|
|
headerAction = {
|
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
|
|
|
IconButton(onClick = { showBarkHelp = true }) {
|
|
|
|
|
|
|
|
Icon(Icons.Filled.Info, "Bark 配置帮助")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
CompactStopOnFirstSwitch(
|
|
|
|
checked = barkStopFirst,
|
|
|
|
checked = barkStopFirst,
|
|
|
|
icon = Icons.Filled.Settings,
|
|
|
|
onChange = { v -> app.appScope.launch { app.settings.setBarkStopOnFirst(v) } },
|
|
|
|
) { v -> app.appScope.launch { app.settings.setBarkStopOnFirst(v) } }
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
) {
|
|
|
|
if (servers.isNotEmpty()) {
|
|
|
|
if (servers.isNotEmpty()) {
|
|
|
|
Spacer(Modifier.size(8.dp))
|
|
|
|
Spacer(Modifier.size(8.dp))
|
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
|
@@ -307,6 +327,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 弹窗
|
|
|
|
// 弹窗
|
|
|
|
|
|
|
|
if (showBarkHelp) BarkHelpDialog(onDismiss = { showBarkHelp = 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 })
|
|
|
@@ -326,6 +347,112 @@ private fun AddButton(onClick: () -> Unit) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
|
|
private fun CompactStopOnFirstSwitch(
|
|
|
|
|
|
|
|
checked: Boolean,
|
|
|
|
|
|
|
|
onChange: (Boolean) -> Unit,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
Row(
|
|
|
|
|
|
|
|
modifier = Modifier.padding(start = 4.dp),
|
|
|
|
|
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
"发通一条就停",
|
|
|
|
|
|
|
|
style = MaterialTheme.typography.labelMedium,
|
|
|
|
|
|
|
|
fontWeight = FontWeight.Medium,
|
|
|
|
|
|
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
|
|
|
maxLines = 1,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
Spacer(Modifier.width(6.dp))
|
|
|
|
|
|
|
|
Switch(checked = checked, onCheckedChange = onChange)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
|
|
private fun BarkHelpDialog(onDismiss: () -> Unit) {
|
|
|
|
|
|
|
|
val uriHandler = LocalUriHandler.current
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AlertDialog(
|
|
|
|
|
|
|
|
onDismissRequest = onDismiss,
|
|
|
|
|
|
|
|
icon = { Icon(Icons.Filled.Info, null) },
|
|
|
|
|
|
|
|
title = { Text("Bark 配置帮助") },
|
|
|
|
|
|
|
|
text = {
|
|
|
|
|
|
|
|
Column(
|
|
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
|
|
.heightIn(max = 440.dp)
|
|
|
|
|
|
|
|
.verticalScroll(rememberScrollState()),
|
|
|
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
"Bark 用来把 a2i 处理后的 Android 通知推送到 iPhone / iPad。第一次使用按下面顺序配置即可。",
|
|
|
|
|
|
|
|
style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
|
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BarkHelpStep("1", "在 iPhone / iPad 上安装 Bark,首次打开时允许通知权限。")
|
|
|
|
|
|
|
|
BarkHelpStep("2", "打开 Bark,复制首页显示的推送地址,通常类似 $BARK_OFFICIAL_SERVER/你的Key。")
|
|
|
|
|
|
|
|
BarkHelpStep("3", "回到 a2i,点击本区域底部的「添加」。服务器地址填 $BARK_OFFICIAL_SERVER,自建服务则填你的自建地址。")
|
|
|
|
|
|
|
|
BarkHelpStep("4", "Device Key 填推送地址最后一段 Key,不要把完整 URL 都填进去。")
|
|
|
|
|
|
|
|
BarkHelpStep("5", "保存后点服务器行里的发送按钮测试;iPhone 收到「a2i 测试」就说明 Bark 通道已通。")
|
|
|
|
|
|
|
|
BarkHelpStep("6", "最后回首页开启「通知转发」,并按提示授予系统通知监听权限。")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
"常用链接",
|
|
|
|
|
|
|
|
style = MaterialTheme.typography.titleSmall,
|
|
|
|
|
|
|
|
fontWeight = FontWeight.SemiBold,
|
|
|
|
|
|
|
|
modifier = Modifier.padding(top = 4.dp),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
BarkHelpLink("Bark App Store", BARK_APP_STORE_URL) { uriHandler.openUri(it) }
|
|
|
|
|
|
|
|
BarkHelpLink("Bark 官方文档", BARK_DOCS_URL) { uriHandler.openUri(it) }
|
|
|
|
|
|
|
|
BarkHelpLink("Bark GitHub", BARK_GITHUB_URL) { uriHandler.openUri(it) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
"提示:如果使用自建 Bark Server,a2i 里的服务器地址要换成自建地址,但 Device Key 仍然来自 Bark App。",
|
|
|
|
|
|
|
|
style = MaterialTheme.typography.bodySmall,
|
|
|
|
|
|
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
|
|
|
|
|
|
modifier = Modifier.padding(top = 2.dp),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
confirmButton = { TextButton(onClick = onDismiss) { Text("知道了") } },
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
|
|
private fun BarkHelpStep(
|
|
|
|
|
|
|
|
index: String,
|
|
|
|
|
|
|
|
text: String,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.Top) {
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
"$index.",
|
|
|
|
|
|
|
|
style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
|
|
|
fontWeight = FontWeight.SemiBold,
|
|
|
|
|
|
|
|
color = MaterialTheme.colorScheme.primary,
|
|
|
|
|
|
|
|
modifier = Modifier.width(24.dp),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
|
|
text,
|
|
|
|
|
|
|
|
style = MaterialTheme.typography.bodyMedium,
|
|
|
|
|
|
|
|
modifier = Modifier.weight(1f),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
|
|
private fun BarkHelpLink(
|
|
|
|
|
|
|
|
title: String,
|
|
|
|
|
|
|
|
url: String,
|
|
|
|
|
|
|
|
onOpen: (String) -> Unit,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
OutlinedButton(
|
|
|
|
|
|
|
|
onClick = { onOpen(url) },
|
|
|
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 通用配置行(ntfy/电邮/短信 共用,仿 ServerRow 简化版)。 */
|
|
|
|
/** 通用配置行(ntfy/电邮/短信 共用,仿 ServerRow 简化版)。 */
|
|
|
|
@Composable
|
|
|
|
@Composable
|
|
|
|
private fun ConfigRow(
|
|
|
|
private fun ConfigRow(
|
|
|
|