diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/common/theme/Theme.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/common/theme/Theme.kt index 0cde1871..d9dddbfc 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/common/theme/Theme.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/common/theme/Theme.kt @@ -52,10 +52,10 @@ private val md_theme_dark_tertiary = Color(0xFFA5CCDF) private val md_theme_dark_onTertiary = Color(0xFF073543) private val md_theme_dark_tertiaryContainer = Color(0xFF244C5B) private val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB) -private val md_theme_dark_error = Color(0xFFD32F2F) // Material Design Red 700 (consistent with light) -private val md_theme_dark_errorContainer = Color(0xFF93000A) -private val md_theme_dark_onError = Color.White -private val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +private val md_theme_dark_error = Color(0xFFFF8A8A) // Softened red — readable on dark backgrounds +private val md_theme_dark_errorContainer = Color(0xFF5C1A1A) // Dark muted red — visible card bg +private val md_theme_dark_onError = Color(0xFF2E0000) +private val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) // Warm light pink — always readable on dark red private val md_theme_dark_background = Color(0xFF353937) private val md_theme_dark_onBackground = Color.White private val md_theme_dark_surface = Color(0xFF353937) diff --git a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NotificationComponents.kt b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NotificationComponents.kt index adf760df..a5c2c918 100644 --- a/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NotificationComponents.kt +++ b/desktop-shared/src/main/kotlin/io/askimo/ui/shell/NotificationComponents.kt @@ -5,6 +5,7 @@ package io.askimo.ui.shell import androidx.compose.foundation.VerticalScrollbar +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -16,16 +17,14 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Notifications -import androidx.compose.material3.Badge -import androidx.compose.material3.BadgedBox import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.HorizontalDivider @@ -102,28 +101,31 @@ fun notificationIcon(onShowUpdateDetails: () -> Unit) { .size(32.dp) .pointerHoverIcon(PointerIcon.Hand), ) { - BadgedBox( - badge = { - if (unreadCount > 0) { - Badge( - modifier = Modifier - .widthIn(min = 20.dp) - .padding(horizontal = 4.dp), - ) { - Text( - text = unreadCount.toString(), - style = MaterialTheme.typography.labelSmall, - fontSize = 10.sp, - ) - } - } - }, + Icon( + Icons.Default.Notifications, + contentDescription = "Events", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(20.dp), + ) + } + + if (unreadCount > 0) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .align(Alignment.TopEnd) + .background( + color = MaterialTheme.colorScheme.error, + shape = RoundedCornerShape(50), + ) + .padding(horizontal = 4.dp, vertical = 2.dp), ) { - Icon( - Icons.Default.Notifications, - contentDescription = "Events", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp), + Text( + text = if (unreadCount > 9) "9+" else unreadCount.toString(), + style = MaterialTheme.typography.labelSmall, + fontSize = 9.sp, + color = MaterialTheme.colorScheme.onError, + maxLines = 1, ) } } @@ -334,9 +336,24 @@ fun notificationEventCard( else -> event::class.simpleName ?: "Unknown" } + val cardColors = if (isShellError) { + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + ) + } else { + AppComponents.surfaceVariantCardColors() + } + + val contentColor = if (isShellError) { + MaterialTheme.colorScheme.onErrorContainer + } else { + MaterialTheme.colorScheme.onSurface + } + Card( modifier = Modifier.fillMaxWidth(), - colors = AppComponents.surfaceVariantCardColors(), + colors = cardColors, ) { Column( modifier = Modifier @@ -353,10 +370,7 @@ fun notificationEventCard( text = eventName, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, - color = when { - isShellError -> MaterialTheme.colorScheme.error - else -> MaterialTheme.colorScheme.onSurface - }, + color = contentColor, ) TextButton( @@ -367,7 +381,7 @@ fun notificationEventCard( Text( text = stringResource("event.notification.clear"), style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurface, + color = contentColor, ) } } @@ -375,24 +389,19 @@ fun notificationEventCard( Text( text = formatInstantDisplay(event.timestamp), style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface, + color = contentColor.copy(alpha = 0.7f), ) SelectionContainer { Text( text = event.getDetails(), style = MaterialTheme.typography.bodySmall, - color = if (isShellError) { - MaterialTheme.colorScheme.error - } else { - MaterialTheme.colorScheme.onSurface - }, + color = contentColor, ) } // Expandable technical details for ShellErrorEvent — lets users copy & report if (isShellError) { - val shellError = event as ShellErrorEvent var showCause by remember { mutableStateOf(false) } TextButton( @@ -407,16 +416,16 @@ fun notificationEventCard( stringResource("event.shell.error.cause.show") }, style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurface, + color = contentColor, ) } if (showCause) { SelectionContainer { Text( - text = shellError.cause.stackTraceToString(), + text = event.cause.stackTraceToString(), style = MaterialTheme.typography.labelSmall.copy(fontFamily = FontFamily.Monospace), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + color = contentColor.copy(alpha = 0.85f), ) } } diff --git a/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt b/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt index 2ffa9be4..2cd206a0 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/shell/FooterBar.kt @@ -508,7 +508,7 @@ fun footerBar( TextButton( onClick = { runCatching { - Desktop.getDesktop().browse(URI("https://github.com/haiphucnguyen/askimo/issues")) + Desktop.getDesktop().browse(URI("https://askimo.chat/contact/")) } }, modifier = Modifier.pointerHoverIcon(PointerIcon.Hand), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39093b5d..1f038301 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ hikaricp = "7.0.2" exposed = "1.2.0" compose = "1.10.3" compose-material3 = "1.9.0" -compose-material-icons = "1.7.8" +compose-material-icons = "1.7.3" coil = "3.4.0" koin = "4.2.0" jlatexmath = "1.0.7"