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:
@@ -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 |
Reference in New Issue
Block a user