feat: unify email/SMS settings with Bark style (no total toggle)
- Remove emailForwardingEnabled / smsFallbackEnabled total toggles - Email/SMS SectionCards now use headerAction + CompactStopOnFirstSwitch (matching Bark and ntfy) - Sender logic: email/sms participation controlled solely by per-item enabled checkboxes (empty list = channel inactive) - version 1.8.4 -> 1.8.5 (code 21 -> 22) 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"
|
applicationId = "com.a2i.forwarder"
|
||||||
minSdk = 34
|
minSdk = 34
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 21
|
versionCode = 22
|
||||||
versionName = "1.8.4"
|
versionName = "1.8.5"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ object EmailSender {
|
|||||||
/** 如果电邮转发已启用且收件人非空,异步发邮件(fire-and-forget,不阻塞主流程)。 */
|
/** 如果电邮转发已启用且收件人非空,异步发邮件(fire-and-forget,不阻塞主流程)。 */
|
||||||
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.emailForwardingEnabled.value) return
|
|
||||||
val configs = s.emailServers.value.filter { it.enabled && it.to.isNotBlank() && it.host.isNotBlank() }
|
val configs = s.emailServers.value.filter { it.enabled && it.to.isNotBlank() && it.host.isNotBlank() }
|
||||||
if (configs.isEmpty()) return
|
if (configs.isEmpty()) return
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class SmsFallbackForwarder(private val context: Context) {
|
|||||||
/** 尝试用短信转发(遍历所有启用的目标号,按 stopOnFirst 决定发通一条止还是全发)。 */
|
/** 尝试用短信转发(遍历所有启用的目标号,按 stopOnFirst 决定发通一条止还是全发)。 */
|
||||||
fun forward(d: Decision) {
|
fun forward(d: Decision) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (!settings.smsFallbackEnabled.value) return@launch
|
|
||||||
if (settings.smsSuspended.value) {
|
if (settings.smsSuspended.value) {
|
||||||
log(d, "failed", "短信已挂起(余额不足)")
|
log(d, "failed", "短信已挂起(余额不足)")
|
||||||
return@launch
|
return@launch
|
||||||
|
|||||||
@@ -226,68 +226,78 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
|
|||||||
|
|
||||||
// ===== 电邮 =====
|
// ===== 电邮 =====
|
||||||
item {
|
item {
|
||||||
SectionCard("电邮转发", subtitle = "SMTPS 直发邮件") {
|
SectionCard(
|
||||||
SwitchRow(title = "启用电邮转发", subtitle = if (emailEnabled) "已开启" else "已关闭", checked = emailEnabled) { v -> app.appScope.launch { app.settings.setEmailForwarding(v) } }
|
"电邮转发",
|
||||||
if (emailEnabled) {
|
subtitle = "勾选的邮箱参与转发;开关切换容错/广播",
|
||||||
SwitchRow(title = "发通一条就停", subtitle = if (emailStopFirst) "容错:首个成功止" else "广播:每个都发", checked = emailStopFirst) { v -> app.appScope.launch { app.settings.setEmailStopOnFirst(v) } }
|
headerAction = {
|
||||||
if (emailServers.isNotEmpty()) {
|
CompactStopOnFirstSwitch(
|
||||||
Spacer(Modifier.size(8.dp))
|
checked = emailStopFirst,
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
onChange = { v -> app.appScope.launch { app.settings.setEmailStopOnFirst(v) } },
|
||||||
emailServers.forEachIndexed { i, s ->
|
)
|
||||||
ConfigRow(
|
},
|
||||||
title = s.name.ifBlank { s.to },
|
) {
|
||||||
subtitle = "${s.host}:${s.port} · ${s.user} → ${s.to}",
|
if (emailServers.isNotEmpty()) {
|
||||||
enabled = s.enabled, isFirst = i == 0, isLast = i == emailServers.lastIndex,
|
Spacer(Modifier.size(8.dp))
|
||||||
onToggle = { v -> app.appScope.launch { app.settings.setEmailEnabled(s.id, v) } },
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
onEdit = { editingEmail = s }, onDelete = { app.appScope.launch { app.settings.deleteEmail(s.id) } },
|
emailServers.forEachIndexed { i, s ->
|
||||||
onMoveUp = { app.appScope.launch { app.settings.moveEmail(s.id, true) } },
|
ConfigRow(
|
||||||
onMoveDown = { app.appScope.launch { app.settings.moveEmail(s.id, false) } },
|
title = s.name.ifBlank { s.to },
|
||||||
onTest = { app.appScope.launch { EmailSender.send(s.host, s.port, s.user, s.password, s.from, s.to, "a2i测试", "电邮配置测试") } },
|
subtitle = "${s.host}:${s.port} · ${s.user} → ${s.to}",
|
||||||
)
|
enabled = s.enabled, 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) } },
|
||||||
|
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测试", "电邮配置测试") } },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddButton { showAddEmail = true }
|
|
||||||
}
|
}
|
||||||
|
AddButton { showAddEmail = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 短信兜底 =====
|
// ===== 短信兜底 =====
|
||||||
item {
|
item {
|
||||||
SectionCard("断网短信兜底", subtitle = "无网络时把验证码/来电用短信发出去") {
|
SectionCard(
|
||||||
SwitchRow(title = "启用短信兜底", subtitle = if (smsFallback) "已开启" else "已关闭", checked = smsFallback) { v -> app.appScope.launch { app.settings.setSmsFallback(v) } }
|
"断网短信兜底",
|
||||||
if (smsFallback) {
|
subtitle = "勾选的目标号参与转发;开关切换容错/广播",
|
||||||
SwitchRow(title = "发通一条就停", subtitle = if (smsStopFirst) "容错:首个成功止" else "广播:每个都发", checked = smsStopFirst) { v -> app.appScope.launch { app.settings.setSmsStopOnFirst(v) } }
|
headerAction = {
|
||||||
if (!hasSmsPerm) {
|
CompactStopOnFirstSwitch(
|
||||||
Spacer(Modifier.size(4.dp))
|
checked = smsStopFirst,
|
||||||
OutlinedButton(onClick = { smsPermLauncher.launch(android.Manifest.permission.SEND_SMS) }, modifier = Modifier.fillMaxWidth()) { Text("授予短信权限") }
|
onChange = { v -> app.appScope.launch { app.settings.setSmsStopOnFirst(v) } },
|
||||||
}
|
)
|
||||||
if (smsTargets.isNotEmpty()) {
|
},
|
||||||
Spacer(Modifier.size(8.dp))
|
) {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
if (!hasSmsPerm) {
|
||||||
smsTargets.forEachIndexed { i, s ->
|
Spacer(Modifier.size(4.dp))
|
||||||
ConfigRow(
|
OutlinedButton(onClick = { smsPermLauncher.launch(android.Manifest.permission.SEND_SMS) }, modifier = Modifier.fillMaxWidth()) { Text("授予短信权限") }
|
||||||
title = s.name.ifBlank { s.number },
|
}
|
||||||
subtitle = s.number,
|
if (smsTargets.isNotEmpty()) {
|
||||||
enabled = s.enabled, isFirst = i == 0, isLast = i == smsTargets.lastIndex,
|
Spacer(Modifier.size(8.dp))
|
||||||
onToggle = { v -> app.appScope.launch { app.settings.setSmsTargetEnabled(s.id, v) } },
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
onEdit = { editingSms = s }, onDelete = { app.appScope.launch { app.settings.deleteSmsTarget(s.id) } },
|
smsTargets.forEachIndexed { i, s ->
|
||||||
onMoveUp = { app.appScope.launch { app.settings.moveSmsTarget(s.id, true) } },
|
ConfigRow(
|
||||||
onMoveDown = { app.appScope.launch { app.settings.moveSmsTarget(s.id, false) } },
|
title = s.name.ifBlank { s.number },
|
||||||
onTest = null,
|
subtitle = s.number,
|
||||||
)
|
enabled = s.enabled, isFirst = i == 0, isLast = i == smsTargets.lastIndex,
|
||||||
}
|
onToggle = { v -> app.appScope.launch { app.settings.setSmsTargetEnabled(s.id, v) } },
|
||||||
|
onEdit = { editingSms = s }, onDelete = { app.appScope.launch { app.settings.deleteSmsTarget(s.id) } },
|
||||||
|
onMoveUp = { app.appScope.launch { app.settings.moveSmsTarget(s.id, true) } },
|
||||||
|
onMoveDown = { app.appScope.launch { app.settings.moveSmsTarget(s.id, false) } },
|
||||||
|
onTest = null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddButton { showAddSms = true }
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
val balanceText = when {
|
|
||||||
smsSuspended -> "已挂起(余额不足)"
|
|
||||||
smsBalance < 0 -> "余额未知"
|
|
||||||
else -> "剩余约 $smsBalance 条"
|
|
||||||
}
|
|
||||||
Text("运营商:${detectedCarrier.displayName} · 余额:$balanceText · 手动额度:$smsManualQuota", style = MaterialTheme.typography.bodySmall, color = if (smsSuspended) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant)
|
|
||||||
}
|
}
|
||||||
|
AddButton { showAddSms = true }
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
val balanceText = when {
|
||||||
|
smsSuspended -> "已挂起(余额不足)"
|
||||||
|
smsBalance < 0 -> "余额未知"
|
||||||
|
else -> "剩余约 $smsBalance 条"
|
||||||
|
}
|
||||||
|
Text("运营商:${detectedCarrier.displayName} · 余额:$balanceText · 手动额度:$smsManualQuota", style = MaterialTheme.typography.bodySmall, color = if (smsSuspended) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user