feat: brand logos for Bark/ntfy channel marks + icons for email/SMS

- Bark: official logo from Finb/Bark repo (ic_bark_logo.png)
- ntfy: official logo from binwiederhier/ntfy repo (ic_ntfy_logo.png)
- Meow: Material Pets icon (paw) — no official logo found
- Email: Material Email icon
- SMS: Material Sms icon
- ChannelMark now supports both ImageVector and Painter (PNG logo)
- SectionCard gains optional titleIcon parameter
- version 1.8.9 -> 1.8.10 (code 26 -> 27)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 13:23:05 +08:00
parent 22ac5a31ff
commit 191a04a68b
5 changed files with 54 additions and 23 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 = 26 versionCode = 27
versionName = "1.8.9" versionName = "1.8.10"
} }
signingConfigs { signingConfigs {
@@ -7,6 +7,7 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -68,6 +69,8 @@ fun SectionCard(
title: String? = null, title: String? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
subtitle: String? = null, subtitle: String? = null,
titleIcon: ImageVector? = null,
titleIconTint: Color = MaterialTheme.colorScheme.primary,
headerAction: (@Composable () -> Unit)? = null, headerAction: (@Composable () -> Unit)? = null,
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
) { ) {
@@ -81,12 +84,18 @@ fun SectionCard(
Column(Modifier.padding(16.dp)) { Column(Modifier.padding(16.dp)) {
if (title != null) { if (title != null) {
if (headerAction == null) { if (headerAction == null) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (titleIcon != null) {
Icon(titleIcon, null, tint = titleIconTint, modifier = Modifier.size(22.dp))
Spacer(Modifier.width(8.dp))
}
Text( Text(
title, title,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(bottom = if (subtitle == null) 10.dp else 2.dp), modifier = Modifier.padding(bottom = if (subtitle == null) 10.dp else 2.dp),
) )
}
if (subtitle != null) { if (subtitle != null) {
Text( Text(
subtitle, subtitle,
@@ -103,12 +112,18 @@ fun SectionCard(
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
) { ) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (titleIcon != null) {
Icon(titleIcon, null, tint = titleIconTint, modifier = Modifier.size(22.dp))
Spacer(Modifier.width(8.dp))
}
Text( Text(
title, title,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(bottom = if (subtitle == null) 0.dp else 2.dp), modifier = Modifier.padding(bottom = if (subtitle == null) 0.dp else 2.dp),
) )
}
if (subtitle != null) { if (subtitle != null) {
Text( Text(
subtitle, subtitle,
@@ -6,6 +6,7 @@ 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.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@@ -26,11 +27,15 @@ import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.Add 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.Email
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.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.Notifications
import androidx.compose.material.icons.filled.Pets
import androidx.compose.material.icons.filled.RestartAlt import androidx.compose.material.icons.filled.RestartAlt
import androidx.compose.material.icons.filled.Sms
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.SystemUpdateAlt import androidx.compose.material.icons.filled.SystemUpdateAlt
import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Update
@@ -57,12 +62,16 @@ 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
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
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.a2i.forwarder.A2iApp import com.a2i.forwarder.A2iApp
import com.a2i.forwarder.R
import com.a2i.forwarder.core.BarkClient import com.a2i.forwarder.core.BarkClient
import com.a2i.forwarder.core.EmailSender import com.a2i.forwarder.core.EmailSender
import com.a2i.forwarder.core.IconExporter import com.a2i.forwarder.core.IconExporter
@@ -167,6 +176,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
summary = "iPhone / iPad 推送目标", summary = "iPhone / iPad 推送目标",
mark = "B", mark = "B",
markColor = Color(0xFFFF9800), markColor = Color(0xFFFF9800),
markLogo = painterResource(R.drawable.ic_bark_logo),
platforms = listOf("iOS"), platforms = listOf("iOS"),
stopFirst = barkStopFirst, stopFirst = barkStopFirst,
onStopFirstChange = { v -> app.appScope.launch { app.settings.setBarkStopOnFirst(v) } }, onStopFirstChange = { v -> app.appScope.launch { app.settings.setBarkStopOnFirst(v) } },
@@ -199,6 +209,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
summary = "Android / iOS topic 推送", summary = "Android / iOS topic 推送",
mark = "n", mark = "n",
markColor = Color(0xFF43A047), markColor = Color(0xFF43A047),
markLogo = painterResource(R.drawable.ic_ntfy_logo),
platforms = listOf("Android", "iOS"), platforms = listOf("Android", "iOS"),
stopFirst = ntfyStopFirst, stopFirst = ntfyStopFirst,
onStopFirstChange = { v -> app.appScope.launch { app.settings.setNtfyStopOnFirst(v) } }, onStopFirstChange = { v -> app.appScope.launch { app.settings.setNtfyStopOnFirst(v) } },
@@ -234,6 +245,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
summary = "鸿蒙手机推送目标", summary = "鸿蒙手机推送目标",
mark = "M", mark = "M",
markColor = Color(0xFF7E57C2), markColor = Color(0xFF7E57C2),
markIcon = Icons.Filled.Pets,
platforms = listOf("HarmonyOS"), platforms = listOf("HarmonyOS"),
stopFirst = meowStopFirst, stopFirst = meowStopFirst,
onStopFirstChange = { v -> app.appScope.launch { app.settings.setMeowStopOnFirst(v) } }, onStopFirstChange = { v -> app.appScope.launch { app.settings.setMeowStopOnFirst(v) } },
@@ -269,6 +281,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
summary = "SMTP 邮箱推送目标", summary = "SMTP 邮箱推送目标",
mark = "@", mark = "@",
markColor = Color(0xFF1976D2), markColor = Color(0xFF1976D2),
markIcon = Icons.Filled.Email,
platforms = listOf("Email"), platforms = listOf("Email"),
stopFirst = emailStopFirst, stopFirst = emailStopFirst,
onStopFirstChange = { v -> app.appScope.launch { app.settings.setEmailStopOnFirst(v) } }, onStopFirstChange = { v -> app.appScope.launch { app.settings.setEmailStopOnFirst(v) } },
@@ -302,6 +315,7 @@ fun SettingsScreen(onOpenLog: () -> Unit) {
summary = "无网络时发送关键通知", summary = "无网络时发送关键通知",
mark = "S", mark = "S",
markColor = Color(0xFF00897B), markColor = Color(0xFF00897B),
markIcon = Icons.Filled.Sms,
platforms = listOf("SMS"), platforms = listOf("SMS"),
stopFirst = smsStopFirst, stopFirst = smsStopFirst,
onStopFirstChange = { v -> app.appScope.launch { app.settings.setSmsStopOnFirst(v) } }, onStopFirstChange = { v -> app.appScope.launch { app.settings.setSmsStopOnFirst(v) } },
@@ -421,6 +435,8 @@ private fun ChannelSectionHeader(
summary: String, summary: String,
mark: String, mark: String,
markColor: Color, markColor: Color,
markIcon: ImageVector? = null,
markLogo: Painter? = null,
platforms: List<String>, platforms: List<String>,
stopFirst: Boolean, stopFirst: Boolean,
onStopFirstChange: (Boolean) -> Unit, onStopFirstChange: (Boolean) -> Unit,
@@ -435,7 +451,7 @@ private fun ChannelSectionHeader(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
ChannelMark(mark = mark, tint = markColor) ChannelMark(mark = mark, tint = markColor, icon = markIcon, logoPainter = markLogo)
Spacer(Modifier.width(10.dp)) Spacer(Modifier.width(10.dp))
Row( Row(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@@ -492,7 +508,7 @@ private fun ChannelSectionHeader(
} }
@Composable @Composable
private fun ChannelMark(mark: String, tint: Color) { private fun ChannelMark(mark: String, tint: Color, icon: ImageVector? = null, logoPainter: Painter? = null) {
Surface( Surface(
modifier = Modifier.size(36.dp), modifier = Modifier.size(36.dp),
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
@@ -504,13 +520,13 @@ private fun ChannelMark(mark: String, tint: Color) {
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( if (logoPainter != null) {
mark, Image(logoPainter, null, modifier = Modifier.size(24.dp))
style = MaterialTheme.typography.titleSmall, } else if (icon != null) {
fontWeight = FontWeight.Bold, Icon(icon, null, tint = tint, modifier = Modifier.size(20.dp))
color = tint, } else {
maxLines = 1, Text(mark, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, color = tint, maxLines = 1)
) }
} }
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB