Merge pull request #3601 from vitorpamplona/claude/amethyst-mobile-colors-kgdfhn

Make theme colors follow selected accent, not fixed purple
This commit is contained in:
Vitor Pamplona
2026-07-16 20:22:47 -04:00
committed by GitHub
37 changed files with 381 additions and 262 deletions
@@ -58,7 +58,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -78,6 +77,8 @@ import com.vitorpamplona.amethyst.service.pow.formatTimeLeft
import com.vitorpamplona.amethyst.service.pow.powKindLabelRes
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.warningColor
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
@@ -440,8 +441,8 @@ fun CompletedBroadcastContent(
modifier = Modifier.fillMaxWidth(),
) {
// Status colors (for icon tint only)
val successColor = Color(0xFF22C55E)
val warningColor = Color(0xFFF59E0B)
val successColor = MaterialTheme.colorScheme.allGoodColor
val warningColor = MaterialTheme.colorScheme.warningColor
val (statusIcon, iconTint) =
when (broadcast.status) {
@@ -530,8 +531,8 @@ fun MultipleCompletedBroadcastContent(
modifier = Modifier.fillMaxWidth(),
) {
// Status colors (for icon tint only)
val successColor = Color(0xFF22C55E)
val warningColor = Color(0xFFF59E0B)
val successColor = MaterialTheme.colorScheme.allGoodColor
val warningColor = MaterialTheme.colorScheme.warningColor
val totalRelayCount = broadcasts.sumOf { it.totalRelays }
val failedRelayCount = broadcasts.sumOf { it.failedRelays.size }
@@ -77,6 +77,8 @@ import com.vitorpamplona.amethyst.commons.service.broadcast.BroadcastStatus
import com.vitorpamplona.amethyst.commons.service.broadcast.RelayResult
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.warningColor
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
@@ -299,8 +301,8 @@ private fun BroadcastSection(
@Composable
private fun statusColor(status: BroadcastStatus): Color =
when (status) {
BroadcastStatus.SUCCESS -> Color(0xFF22C55E)
BroadcastStatus.PARTIAL -> Color(0xFFF59E0B)
BroadcastStatus.SUCCESS -> MaterialTheme.colorScheme.allGoodColor
BroadcastStatus.PARTIAL -> MaterialTheme.colorScheme.warningColor
BroadcastStatus.FAILED -> MaterialTheme.colorScheme.error
BroadcastStatus.IN_PROGRESS -> MaterialTheme.colorScheme.primary
}
@@ -312,8 +314,8 @@ private fun StatusIcon(
) {
val (icon, tint, shouldRotate) =
when (status) {
BroadcastStatus.SUCCESS -> Triple(MaterialSymbols.CheckCircle, Color(0xFF22C55E), false)
BroadcastStatus.PARTIAL -> Triple(MaterialSymbols.Error, Color(0xFFF59E0B), false)
BroadcastStatus.SUCCESS -> Triple(MaterialSymbols.CheckCircle, MaterialTheme.colorScheme.allGoodColor, false)
BroadcastStatus.PARTIAL -> Triple(MaterialSymbols.Error, MaterialTheme.colorScheme.warningColor, false)
BroadcastStatus.FAILED -> Triple(MaterialSymbols.Error, MaterialTheme.colorScheme.error, false)
BroadcastStatus.IN_PROGRESS -> Triple(MaterialSymbols.HourglassEmpty, MaterialTheme.colorScheme.primary, true)
}
@@ -342,9 +344,9 @@ private fun RelayResultRow(
onRetry: () -> Unit,
rotationAngle: Float = 0f,
) {
val successColor = Color(0xFF22C55E)
val successColor = MaterialTheme.colorScheme.allGoodColor
val errorColor = MaterialTheme.colorScheme.error
val warningColor = Color(0xFFF59E0B)
val warningColor = MaterialTheme.colorScheme.warningColor
Row(
modifier =
@@ -1254,7 +1254,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) {
painter = painterRes(R.drawable.original, 1),
contentDescription = stringRes(id = R.string.hash_verification_passed),
modifier = Size30Modifier,
tint = Color.Unspecified,
tint = MaterialTheme.colorScheme.primary,
)
}
} else {
@@ -1268,7 +1268,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean) {
symbol = MaterialSymbols.Report,
contentDescription = stringRes(id = R.string.hash_verification_failed),
modifier = Size30Modifier,
tint = Color.Red,
tint = MaterialTheme.colorScheme.error,
)
}
}
@@ -35,7 +35,6 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.hashtags.Amethyst
import com.vitorpamplona.amethyst.commons.hashtags.Cashu
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.commons.icons.Following
import com.vitorpamplona.amethyst.commons.icons.Like
import com.vitorpamplona.amethyst.commons.icons.Liked
import com.vitorpamplona.amethyst.commons.icons.Reply
@@ -43,6 +42,7 @@ import com.vitorpamplona.amethyst.commons.icons.Repost
import com.vitorpamplona.amethyst.commons.icons.Reposted
import com.vitorpamplona.amethyst.commons.icons.Search
import com.vitorpamplona.amethyst.commons.icons.Zap
import com.vitorpamplona.amethyst.commons.icons.following
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.ui.stringRes
@@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size19Modifier
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.amethyst.ui.theme.isLight
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.subtleButton
@@ -66,8 +67,15 @@ fun AmethystIcon(iconSize: Dp) {
@Composable
fun FollowingIcon(modifier: Modifier) {
// Deep, saturated accent shield + white figure, like the original #7F2EFF badge. Use the accent's
// deep tone (its light-theme primary) in both themes: dark-theme `primary` is a pale pastel, so
// the shield would read washed out — inversePrimary carries the deep tone in the dark scheme,
// while `primary` already is it in light.
val scheme = MaterialTheme.colorScheme
val shield = if (scheme.isLight) scheme.primary else scheme.inversePrimary
val icon = remember(shield) { following(shield, Color.White) }
Icon(
imageVector = Following,
imageVector = icon,
contentDescription = stringRes(id = R.string.following),
modifier = modifier,
tint = Color.Unspecified,
@@ -36,7 +36,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
@@ -481,7 +480,7 @@ fun RenderNIP05VerifiedSymbol(
painter = painterRes(R.drawable.nip_05, compositionSizeReference),
contentDescription = stringRes(id = R.string.nip05_verified),
modifier = modifier,
tint = Color.Unspecified,
tint = MaterialTheme.colorScheme.nip05,
)
}
@@ -38,7 +38,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -52,6 +51,8 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.warningColor
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.RelayDiscoveryEvent
@@ -215,8 +216,8 @@ private fun RttMetricChip(
) {
val color =
when {
ms < 200 -> Color(0xFF4CAF50)
ms < 500 -> Color(0xFFFFC107)
ms < 200 -> MaterialTheme.colorScheme.allGoodColor
ms < 500 -> MaterialTheme.colorScheme.warningColor
else -> MaterialTheme.colorScheme.error
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -46,7 +45,7 @@ fun NewArticleButton(nav: INav) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_long_form_post),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -29,7 +29,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.painterRes
@@ -63,7 +62,7 @@ fun NewBadgeButton(accountViewModel: AccountViewModel) {
painter = painterRes(R.drawable.ic_compose, 5),
contentDescription = stringRes(id = R.string.new_badge),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -37,7 +37,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -74,7 +73,7 @@ fun NewCalendarButton(nav: INav) {
// string is ambiguous out of context.
contentDescription = stringRes(R.string.calendar_fab_new_collection),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -93,7 +92,7 @@ fun NewCalendarButton(nav: INav) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(R.string.calendar_fab_new_event),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -116,7 +115,7 @@ fun NewCalendarButton(nav: INav) {
symbol = MaterialSymbols.Close,
contentDescription = stringRes(R.string.calendar_fab_toggle),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -130,7 +129,7 @@ fun NewCalendarButton(nav: INav) {
// Top FAB toggles the sub-FABs in/out — describe that, not the sub-action.
contentDescription = stringRes(R.string.calendar_fab_toggle),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -26,7 +26,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -54,7 +53,7 @@ fun ChannelFabColumn(nav: INav) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(R.string.messages_create_public_private_chat_description),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -48,7 +47,7 @@ fun NewChessGameButton(onClick: () -> Unit) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(R.string.new_chess_game),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.LoadNote
@@ -70,7 +69,7 @@ fun NewCommunityNoteButton(
painter = painterRes(R.drawable.ic_compose, 2),
contentDescription = stringRes(id = R.string.new_community_note),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -46,7 +45,7 @@ fun NewCommunityButton(nav: INav) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_community),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -48,7 +48,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
@@ -370,7 +369,7 @@ fun NewProductButton(
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_product),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -392,7 +391,7 @@ fun NewLongFormMarkdownButton(
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_long_form_post),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -53,7 +52,7 @@ fun NewGeoPostButton(
painter = painterRes(R.drawable.ic_compose, 1),
contentDescription = stringRes(id = R.string.new_community_note),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -53,7 +52,7 @@ fun NewHashtagPostButton(
painter = painterRes(R.drawable.ic_compose, 3),
contentDescription = stringRes(id = R.string.new_community_note),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -29,7 +29,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
@@ -66,7 +65,7 @@ fun NewNoteButton(nav: INav) {
painter = painterRes(R.drawable.ic_compose, 4),
contentDescription = stringRes(R.string.new_post),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -40,7 +40,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
@@ -130,7 +129,7 @@ fun NewLongVideoButton(
symbol = MaterialSymbols.Videocam,
contentDescription = stringRes(id = R.string.record_a_video),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -149,7 +148,7 @@ fun NewLongVideoButton(
symbol = MaterialSymbols.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -172,7 +171,7 @@ fun NewLongVideoButton(
symbol = MaterialSymbols.Close,
contentDescription = stringRes(id = R.string.new_long_video),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -185,7 +184,7 @@ fun NewLongVideoButton(
painter = painterRes(R.drawable.ic_compose, 5),
contentDescription = stringRes(id = R.string.new_long_video),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -52,7 +51,7 @@ fun NewMusicPlaylistFab(nav: INav) {
symbol = MaterialSymbols.AutoMirrored.PlaylistAdd,
contentDescription = stringRes(R.string.new_music_playlist),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -54,7 +53,7 @@ fun NewMusicTrackButton(
symbol = MaterialSymbols.MusicNote,
contentDescription = stringRes(R.string.new_music_track),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -40,7 +40,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
@@ -130,7 +129,7 @@ fun NewPictureButton(
symbol = MaterialSymbols.CameraAlt,
contentDescription = stringRes(id = R.string.take_a_picture),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -149,7 +148,7 @@ fun NewPictureButton(
symbol = MaterialSymbols.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -172,7 +171,7 @@ fun NewPictureButton(
symbol = MaterialSymbols.Close,
contentDescription = stringRes(id = R.string.new_picture),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -185,7 +184,7 @@ fun NewPictureButton(
painter = painterRes(R.drawable.ic_compose, 5),
contentDescription = stringRes(id = R.string.new_picture),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -26,7 +26,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
@@ -101,7 +100,7 @@ fun PodcastsScreen(
Icon(
symbol = MaterialSymbols.Mic,
contentDescription = stringRes(R.string.podcast_your_podcast),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -46,7 +45,7 @@ fun NewPollButton(nav: INav) {
painter = painterRes(R.drawable.ic_compose, 4),
contentDescription = stringRes(id = R.string.new_poll),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -52,7 +51,7 @@ fun NewProductButton(
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_product),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -46,7 +45,7 @@ fun NewPublicChatButton(nav: INav) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_public_chat),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -52,7 +51,7 @@ fun NewRelayNoteButton(
painter = painterRes(R.drawable.ic_compose, 3),
contentDescription = stringRes(id = R.string.new_community_note),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -107,8 +107,10 @@ import com.vitorpamplona.amethyst.ui.theme.SpacedBy10dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.bitcoinColor
import com.vitorpamplona.amethyst.ui.theme.redColorOnSecondSurface
import com.vitorpamplona.amethyst.ui.theme.warningColor
import com.vitorpamplona.quartz.kinds.KindNames
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.ErrorDebugMessage
@@ -1437,12 +1439,8 @@ private fun RttChip(
) {
val color =
when {
ms < 200 -> Color(0xFF4CAF50)
// green
ms < 500 -> Color(0xFFFFC107)
// amber
ms < 200 -> MaterialTheme.colorScheme.allGoodColor
ms < 500 -> MaterialTheme.colorScheme.warningColor
else -> MaterialTheme.colorScheme.error
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -21,15 +21,21 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@@ -40,14 +46,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.os.LocaleListCompat
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.model.AccentColorType
import com.vitorpamplona.amethyst.model.ConnectivityType
import com.vitorpamplona.amethyst.model.FeatureSetType
@@ -55,7 +66,6 @@ import com.vitorpamplona.amethyst.model.FontFamilyType
import com.vitorpamplona.amethyst.model.FontSizeType
import com.vitorpamplona.amethyst.model.ThemeType
import com.vitorpamplona.amethyst.model.UiSettingsFlow
import com.vitorpamplona.amethyst.model.parseAccentColorType
import com.vitorpamplona.amethyst.model.parseBooleanType
import com.vitorpamplona.amethyst.model.parseConnectivityType
import com.vitorpamplona.amethyst.model.parseFeatureSetType
@@ -72,6 +82,9 @@ import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.contentColorOnAccent
import com.vitorpamplona.amethyst.ui.theme.isLight
import com.vitorpamplona.amethyst.ui.theme.previewColor
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentListOf
@@ -226,25 +239,67 @@ fun ShowThemeChoice(sharedPrefs: UiSettingsFlow) {
@Composable
fun ShowAccentColorChoice(sharedPrefs: UiSettingsFlow) {
val accentOptions =
persistentListOf(
TitleExplainer(stringRes(AccentColorType.PURPLE.resourceId)),
TitleExplainer(stringRes(AccentColorType.BLUE.resourceId)),
TitleExplainer(stringRes(AccentColorType.GREEN.resourceId)),
TitleExplainer(stringRes(AccentColorType.ORANGE.resourceId)),
TitleExplainer(stringRes(AccentColorType.RED.resourceId)),
TitleExplainer(stringRes(AccentColorType.PINK.resourceId)),
)
val accent by sharedPrefs.accentColor.collectAsState()
val dark = !MaterialTheme.colorScheme.isLight
val accentIndex by sharedPrefs.accentColor.collectAsState()
SettingsRow(
R.string.accent_color,
R.string.accent_color_description,
accentOptions,
accentIndex.screenCode,
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
sharedPrefs.accentColor.tryEmit(parseAccentColorType(it))
Text(
text = stringRes(R.string.accent_color),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = stringRes(R.string.accent_color_description),
style = MaterialTheme.typography.bodySmall,
color = Color.Gray,
)
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
AccentColorType.entries.forEach { option ->
AccentColorSwatch(
color = option.previewColor(dark),
label = stringRes(option.resourceId),
selected = option == accent,
onClick = { sharedPrefs.accentColor.tryEmit(option) },
)
}
}
}
}
@Composable
private fun AccentColorSwatch(
color: Color,
label: String,
selected: Boolean,
onClick: () -> Unit,
) {
Box(
modifier =
Modifier
.size(36.dp)
.clip(CircleShape)
.background(color)
.then(
if (selected) {
Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
} else {
Modifier
},
).clickable(onClick = onClick)
.semantics { contentDescription = label },
contentAlignment = Alignment.Center,
) {
if (selected) {
Icon(
symbol = MaterialSymbols.Done,
contentDescription = null,
tint = contentColorOnAccent(color),
modifier = Modifier.size(20.dp),
)
}
}
}
@@ -48,7 +48,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
@@ -60,6 +59,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.model.nip03Timestamp.OtsSettings
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpBitcoinExplorer
/**
@@ -149,7 +149,7 @@ private fun SectionHeaderOts() {
Icon(
MaterialSymbols.Search,
contentDescription = null,
tint = Color(0xFFF7931A), // Bitcoin orange
tint = BitcoinOrange,
modifier = Modifier.size(22.dp),
)
Spacer(Modifier.width(10.dp))
@@ -194,11 +194,11 @@ private fun ActiveExplorerDisplay(
"CUSTOM",
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Bold,
color = Color(0xFFF7931A),
color = BitcoinOrange,
modifier =
Modifier
.background(
Color(0xFFF7931A).copy(alpha = 0.1f),
BitcoinOrange.copy(alpha = 0.1f),
RoundedCornerShape(4.dp),
).padding(horizontal = 6.dp, vertical = 2.dp),
)
@@ -73,6 +73,8 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.routeToMessage
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.warningColor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
@@ -447,9 +449,9 @@ private fun MemorySection(memory: MemorySnapshot?) {
if (memory == null) return
val heapColor =
when {
memory.heapFraction > 0.80f -> Color(0xFFE53935)
memory.heapFraction > 0.60f -> Color(0xFFFFA000)
else -> Color(0xFF43A047)
memory.heapFraction > 0.80f -> MaterialTheme.colorScheme.error
memory.heapFraction > 0.60f -> MaterialTheme.colorScheme.warningColor
else -> MaterialTheme.colorScheme.allGoodColor
}
SettingsSection(R.string.resource_usage_memory_section) {
BarRow(
@@ -40,7 +40,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
@@ -130,7 +129,7 @@ fun NewShortVideoButton(
symbol = MaterialSymbols.Videocam,
contentDescription = stringRes(id = R.string.record_a_video),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -149,7 +148,7 @@ fun NewShortVideoButton(
symbol = MaterialSymbols.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -172,7 +171,7 @@ fun NewShortVideoButton(
symbol = MaterialSymbols.Close,
contentDescription = stringRes(id = R.string.new_short_video),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -185,7 +184,7 @@ fun NewShortVideoButton(
painter = painterRes(R.drawable.ic_compose, 5),
contentDescription = stringRes(id = R.string.new_short_video),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -25,7 +25,6 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -53,7 +52,7 @@ fun NewUrlPostButton(
painter = painterRes(R.drawable.ic_compose, 1),
contentDescription = stringRes(id = R.string.new_community_note),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -40,7 +40,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
@@ -144,7 +143,7 @@ fun NewImageButton(
symbol = MaterialSymbols.CameraAlt,
contentDescription = stringRes(id = R.string.take_a_picture),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -163,7 +162,7 @@ fun NewImageButton(
symbol = MaterialSymbols.Videocam,
contentDescription = stringRes(id = R.string.record_a_video),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -182,7 +181,7 @@ fun NewImageButton(
symbol = MaterialSymbols.AddPhotoAlternate,
contentDescription = stringRes(id = R.string.upload_image),
modifier = Modifier.size(26.dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -207,7 +206,7 @@ fun NewImageButton(
symbol = MaterialSymbols.Close,
contentDescription = stringRes(id = R.string.new_short),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
@@ -220,7 +219,7 @@ fun NewImageButton(
painter = painterRes(R.drawable.ic_compose, 5),
contentDescription = stringRes(id = R.string.new_short),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,7 +24,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -46,7 +45,7 @@ fun NewWorkoutButton(nav: INav) {
symbol = MaterialSymbols.Add,
contentDescription = stringRes(id = R.string.new_workout),
modifier = Size26Modifier,
tint = Color.White,
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@@ -24,17 +24,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
val Primary50 = Color(red = 127, green = 103, blue = 190)
val Primary60 = Color(red = 154, green = 130, blue = 219)
val Primary70 = Color(red = 182, green = 157, blue = 248)
val Primary80 = Color(red = 208, green = 188, blue = 255)
val DEFAULT_PRIMARY = Color(red = 208, green = 188, blue = 255)
val LIGHT_PURPLE = Color(red = 187, green = 134, blue = 252)
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
// Accent palette options selected through Settings -> Accent Color.
@@ -56,17 +47,8 @@ val BitcoinDark = Color(0xFFF7931A)
val BitcoinLight = Color(0xFFB66605)
val Following = Color(0xFF03DAC5)
val FollowsFollow = Color.Yellow
val NIP05Verified = Color.Blue
val Nip05EmailColor = Color(0xFFb198ec)
val Nip05EmailColorDark = Color(0xFF765AA2)
val Nip05EmailColorLight = Color(0xFFa770f3)
val DarkerGreen = Color.Green.copy(alpha = 0.32f)
val LightRedColor = Color(0xFFC62828)
val LighterRedColor = Color(0xFFFF0E0E)
val RelayIconFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) })
@@ -46,6 +46,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -93,25 +95,126 @@ private fun accentSecondary(
dark: Boolean,
): Color = if (accent == AccentColorType.PURPLE) Teal200 else accentPrimary(accent, dark)
private fun darkColors(accent: AccentColorType) =
darkColorScheme(
primary = accentPrimary(accent, dark = true),
secondary = accentSecondary(accent, dark = true),
tertiary = accentSecondary(accent, dark = true),
background = Color.Black,
surface = Color.Black,
surfaceDim = Color.Black,
surfaceVariant = Color(red = 29, green = 26, blue = 34),
)
// Black or white — whichever has the higher WCAG contrast ratio against a solid accent fill
// (primary/secondary/tertiary). Runs once per scheme build (not on a render path), so the
// luminance() cost is irrelevant here. Picks black on the light pastel accents used in the dark
// theme and on the purple theme's bright teal secondary, white on the deep accents used in light.
private fun onAccent(color: Color): Color {
val luminance = color.luminance()
val contrastOnBlack = (luminance + 0.05f) / 0.05f
val contrastOnWhite = 1.05f / (luminance + 0.05f)
return if (contrastOnBlack >= contrastOnWhite) Color.Black else Color.White
}
private fun lightColors(accent: AccentColorType) =
lightColorScheme(
primary = accentPrimary(accent, dark = false),
secondary = accentSecondary(accent, dark = false),
tertiary = accentSecondary(accent, dark = false),
surfaceContainerHighest = Color(red = 236, green = 230, blue = 240),
surfaceVariant = Color(red = 250, green = 245, blue = 252),
// Fill for the Material3 "container" roles (primaryContainer, etc.). A moderately saturated tone,
// mirroring Material's baseline containers but recoloured to the accent, so filled shapes such as
// the settings icon boxes, tonal buttons and selected chips stay clearly visible (not a faint
// wash). Dark themes darken the light accent toward a mid tone; light themes lighten the deep
// accent toward a pale tint. Left unset, these roles fall back to Material's baseline violet.
private fun accentContainer(
accent: Color,
dark: Boolean,
): Color = if (dark) lerp(accent, Color.Black, 0.58f) else lerp(accent, Color.White, 0.85f)
// High-contrast content for an accentContainer: near-white in dark (so icons/labels read as white
// on the accent), deep accent in light.
private fun onAccentContainer(
accent: Color,
dark: Boolean,
): Color = if (dark) lerp(accent, Color.White, 0.85f) else lerp(accent, Color.Black, 0.40f)
// Representative colour for an accent option, used by the Settings accent-picker swatches — the
// same primary the theme would apply for the given light/dark mode, so the swatch previews the
// real result.
fun AccentColorType.previewColor(dark: Boolean): Color = accentPrimary(this, dark)
// Contrast colour (black or white) for content drawn on top of an accent swatch.
fun contentColorOnAccent(color: Color): Color = onAccent(color)
private fun darkColors(accent: AccentColorType): ColorScheme {
val primary = accentPrimary(accent, dark = true)
val secondary = accentSecondary(accent, dark = true)
return darkColorScheme(
primary = primary,
onPrimary = Color.White,
primaryContainer = accentContainer(primary, dark = true),
onPrimaryContainer = onAccentContainer(primary, dark = true),
secondary = secondary,
onSecondary = onAccent(secondary),
// Container roles derive from the accent (primary), not the teal secondary. Secondary/tertiary
// container surfaces (FilledTonalButton, tonal chips) were neutral before; deriving them from
// teal made them read as teal-on-teal, so they follow the accent hue instead.
secondaryContainer = accentContainer(primary, dark = true),
onSecondaryContainer = onAccentContainer(primary, dark = true),
tertiary = secondary,
onTertiary = onAccent(secondary),
tertiaryContainer = accentContainer(primary, dark = true),
onTertiaryContainer = onAccentContainer(primary, dark = true),
inversePrimary = accentPrimary(accent, dark = false),
surfaceTint = primary,
// Neutral (hue-free) base + surface ramp. Left unset, these fall back to Material's
// violet-tinted baseline greys, which read as a faint purple wash independent of the
// accent. The greys below keep Material's lightness so contrast is unchanged.
background = Color.Black,
onBackground = Color(0xFFE6E6E6),
surface = Color.Black,
onSurface = Color(0xFFE6E6E6),
surfaceVariant = Color(0xFF1E1E1E),
onSurfaceVariant = Color(0xFFCACACA),
surfaceDim = Color.Black,
surfaceBright = Color(0xFF383838),
surfaceContainerLowest = Color(0xFF141414),
surfaceContainerLow = Color(0xFF1A1A1A),
surfaceContainer = Color(0xFF252525),
surfaceContainerHigh = Color(0xFF2E2E2E),
surfaceContainerHighest = Color(0xFF383838),
outline = Color(0xFF909090),
outlineVariant = Color(0xFF454545),
)
}
private fun lightColors(accent: AccentColorType): ColorScheme {
val primary = accentPrimary(accent, dark = false)
val secondary = accentSecondary(accent, dark = false)
return lightColorScheme(
primary = primary,
onPrimary = Color.White,
primaryContainer = accentContainer(primary, dark = false),
onPrimaryContainer = onAccentContainer(primary, dark = false),
secondary = secondary,
onSecondary = onAccent(secondary),
// Container roles derive from the accent (primary), not the teal secondary. Secondary/tertiary
// container surfaces (FilledTonalButton, tonal chips) were neutral before; deriving them from
// teal made them read as teal-on-teal, so they follow the accent hue instead.
secondaryContainer = accentContainer(primary, dark = false),
onSecondaryContainer = onAccentContainer(primary, dark = false),
tertiary = secondary,
onTertiary = onAccent(secondary),
tertiaryContainer = accentContainer(primary, dark = false),
onTertiaryContainer = onAccentContainer(primary, dark = false),
inversePrimary = accentPrimary(accent, dark = true),
surfaceTint = primary,
// Neutral (hue-free) base + surface ramp. Left unset, these fall back to Material's
// violet-tinted baseline greys (e.g. surface #FEF7FF), which read as a faint purple wash
// independent of the accent. The greys below keep Material's lightness so contrast is
// unchanged.
background = Color(0xFFFDFDFD),
onBackground = Color(0xFF1C1C1C),
surface = Color(0xFFFDFDFD),
onSurface = Color(0xFF1C1C1C),
surfaceVariant = Color(0xFFFAFAFA),
onSurfaceVariant = Color(0xFF484848),
surfaceDim = Color(0xFFDEDEDE),
surfaceBright = Color(0xFFFDFDFD),
surfaceContainerLowest = Color(0xFFFFFFFF),
surfaceContainerLow = Color(0xFFF7F7F7),
surfaceContainer = Color(0xFFF2F2F2),
surfaceContainerHigh = Color(0xFFECECEC),
surfaceContainerHighest = Color(0xFFEBEBEB),
outline = Color(0xFF767676),
outlineVariant = Color(0xFFCACACA),
)
}
private val DarkColorPalette = darkColors(AccentColorType.PURPLE)
private val LightColorPalette = lightColors(AccentColorType.PURPLE)
@@ -223,21 +326,14 @@ val LightMaxWidthWithBackground =
.fillMaxWidth()
.background(LightColorPalette.background)
val DarkSelectedReactionBoxModifier =
// Geometry only — no color. The fill is applied live from the scheme's secondaryContainer in
// the selectedReactionBoxModifier getter so the selected-reaction highlight follows the accent
// instead of freezing to the purple palette captured at class load.
val SelectedReactionBoxOuterModifier =
Modifier
.padding(horizontal = 5.dp, vertical = 5.dp)
.size(Size40dp)
.clip(shape = SmallBorder)
.background(DarkColorPalette.secondaryContainer)
.padding(5.dp)
val LightSelectedReactionBoxModifier =
Modifier
.padding(horizontal = 5.dp, vertical = 5.dp)
.size(Size40dp)
.clip(shape = SmallBorder)
.background(LightColorPalette.secondaryContainer)
.padding(5.dp)
val DarkChannelNotePictureModifier =
Modifier
@@ -287,17 +383,13 @@ val lightLargeProfilePictureModifier =
.clip(shape = CircleShape)
.border(3.dp, LightColorPalette.onBackground, CircleShape)
val darkNewItemBubbleModifier =
// Geometry only — no color. The fill is applied live from the scheme's primary in the
// newItemBubbleModifier getter so the unread dot follows the accent (matching its sibling
// newItemBackgroundColor) instead of freezing to the purple palette captured at class load.
val NewItemBubbleShapeModifier =
Modifier
.size(10.dp)
.clip(shape = CircleShape)
.background(DarkColorPalette.primary)
val lightNewItemBubbleModifier =
Modifier
.size(10.dp)
.clip(shape = CircleShape)
.background(LightColorPalette.primary)
val darkBlackTagModifier =
Modifier
@@ -457,8 +549,9 @@ val ColorScheme.mediumImportanceLink: Color
val ColorScheme.placeholderText: Color
get() = if (isLight) LightPlaceholderText else DarkPlaceholderText
// NIP-05 identifiers were a fixed purple; follow the selected accent instead.
val ColorScheme.nip05: Color
get() = if (isLight) Nip05EmailColorLight else Nip05EmailColorDark
get() = primary
val ColorScheme.onBackgroundColorFilter: ColorFilter
get() = if (isLight) LightOnBackgroundColorFilter else DarkOnBackgroundColorFilter
@@ -552,7 +645,7 @@ val ColorScheme.relayIconModifier: Modifier
@Suppress("ModifierFactoryExtensionFunction")
val ColorScheme.selectedReactionBoxModifier: Modifier
get() = if (isLight) LightSelectedReactionBoxModifier else DarkSelectedReactionBoxModifier
get() = SelectedReactionBoxOuterModifier.background(secondaryContainer).padding(5.dp)
@Suppress("ModifierFactoryExtensionFunction")
val ColorScheme.largeProfilePictureModifier: Modifier
@@ -560,7 +653,7 @@ val ColorScheme.largeProfilePictureModifier: Modifier
@Suppress("ModifierFactoryExtensionFunction")
val ColorScheme.newItemBubbleModifier: Modifier
get() = if (isLight) lightNewItemBubbleModifier else darkNewItemBubbleModifier
get() = NewItemBubbleShapeModifier.background(primary)
@Suppress("ModifierFactoryExtensionFunction")
val ColorScheme.blackTagModifier: Modifier
@@ -35,94 +35,96 @@ import androidx.compose.ui.unit.dp
@Preview
@Composable
private fun VectorPreview() {
Image(Following, null)
Image(following(Color(0xFF7F2EFF), Color.White), null)
}
private var labelsFollowing: ImageVector? = null
public val Following: ImageVector
get() =
labelsFollowing ?: ImageVector
.Builder(
name = "Following",
defaultWidth = 30.dp,
defaultHeight = 30.dp,
viewportWidth = 30f,
viewportHeight = 30f,
).apply {
path(
fill = SolidColor(Color(0xFF7F2EFF)),
fillAlpha = 1.0f,
stroke = null,
strokeAlpha = 1.0f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(27.8f, 12.8f)
lineTo(27.8f, 12.8f)
curveTo(27.8f, 5f, 27.2f, 0f, 24.4f, 0f)
reflectiveCurveTo(15f, 0f, 15f, 0f)
reflectiveCurveTo(8.4f, 0f, 5.6f, 0f)
curveTo(2.8f, 0f, 2.2f, 5f, 2.2f, 12.8f)
verticalLineToRelative(0f)
lineToRelative(0f, 0f)
curveToRelative(0f, 0f, 0f, 0f, 0f, 0f)
curveTo(2.2f, 24.9f, 15f, 30f, 15f, 30f)
reflectiveCurveTo(27.8f, 24.9f, 27.8f, 12.8f)
curveTo(27.8f, 12.8f, 27.8f, 12.8f, 27.8f, 12.8f)
lineTo(27.8f, 12.8f)
close()
}
path(
fill = SolidColor(Color(0xFFFFFFFF)),
fillAlpha = 1.0f,
stroke = SolidColor(Color(0xFF7F2EFF)),
strokeAlpha = 1.0f,
strokeLineWidth = 0.5f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(15.1f, 24f)
lineToRelative(-0.5f, -0.2f)
curveToRelative(-0.2f, -0.1f, -4.5f, -2f, -6.8f, -6.4f)
curveToRelative(-0.3f, -0.6f, -0.1f, -1.4f, 0.6f, -1.7f)
curveToRelative(0.6f, -0.3f, 1.4f, -0.1f, 1.7f, 0.6f)
curveToRelative(1.4f, 2.8f, 3.9f, 4.4f, 5f, 4.9f)
curveToRelative(1.1f, -0.6f, 3.6f, -2.2f, 5f, -4.9f)
curveToRelative(0.3f, -0.6f, 1.1f, -0.9f, 1.7f, -0.6f)
curveToRelative(0.6f, 0.3f, 0.9f, 1.1f, 0.6f, 1.7f)
curveToRelative(-2.2f, 4.4f, -6.6f, 6.3f, -6.8f, 6.4f)
lineTo(15.1f, 24f)
close()
}
path(
fill = SolidColor(Color(0xFFFFFFFF)),
fillAlpha = 1.0f,
stroke = SolidColor(Color(0xFF7F2EFF)),
strokeAlpha = 1.0f,
strokeLineWidth = 0.5f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(15f, 15f)
curveToRelative(-2.9f, 0f, -5.2f, -2.3f, -5.2f, -5.2f)
reflectiveCurveToRelative(2.3f, -5.2f, 5.2f, -5.2f)
reflectiveCurveToRelative(5.2f, 2.3f, 5.2f, 5.2f)
reflectiveCurveTo(17.8f, 15f, 15f, 15f)
close()
moveTo(15f, 7.2f)
curveToRelative(-1.4f, 0f, -2.6f, 1.2f, -2.6f, 2.6f)
reflectiveCurveToRelative(1.2f, 2.6f, 2.6f, 2.6f)
reflectiveCurveToRelative(2.6f, -1.2f, 2.6f, -2.6f)
reflectiveCurveTo(16.4f, 7.2f, 15f, 7.2f)
close()
}
}.build()
.also { labelsFollowing = it }
// Two-tone follow badge. [shield] fills the outer shape and outlines the figure; [figure] fills the
// inner person. Callers pass the FilledTonalButton pair (secondaryContainer / onSecondaryContainer)
// so the badge tracks the accent and matches the tonal buttons. Build once and cache with remember().
public fun following(
shield: Color,
figure: Color,
): ImageVector =
ImageVector
.Builder(
name = "Following",
defaultWidth = 30.dp,
defaultHeight = 30.dp,
viewportWidth = 30f,
viewportHeight = 30f,
).apply {
path(
fill = SolidColor(shield),
fillAlpha = 1.0f,
stroke = null,
strokeAlpha = 1.0f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(27.8f, 12.8f)
lineTo(27.8f, 12.8f)
curveTo(27.8f, 5f, 27.2f, 0f, 24.4f, 0f)
reflectiveCurveTo(15f, 0f, 15f, 0f)
reflectiveCurveTo(8.4f, 0f, 5.6f, 0f)
curveTo(2.8f, 0f, 2.2f, 5f, 2.2f, 12.8f)
verticalLineToRelative(0f)
lineToRelative(0f, 0f)
curveToRelative(0f, 0f, 0f, 0f, 0f, 0f)
curveTo(2.2f, 24.9f, 15f, 30f, 15f, 30f)
reflectiveCurveTo(27.8f, 24.9f, 27.8f, 12.8f)
curveTo(27.8f, 12.8f, 27.8f, 12.8f, 27.8f, 12.8f)
lineTo(27.8f, 12.8f)
close()
}
path(
fill = SolidColor(figure),
fillAlpha = 1.0f,
stroke = SolidColor(shield),
strokeAlpha = 1.0f,
strokeLineWidth = 0.5f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(15.1f, 24f)
lineToRelative(-0.5f, -0.2f)
curveToRelative(-0.2f, -0.1f, -4.5f, -2f, -6.8f, -6.4f)
curveToRelative(-0.3f, -0.6f, -0.1f, -1.4f, 0.6f, -1.7f)
curveToRelative(0.6f, -0.3f, 1.4f, -0.1f, 1.7f, 0.6f)
curveToRelative(1.4f, 2.8f, 3.9f, 4.4f, 5f, 4.9f)
curveToRelative(1.1f, -0.6f, 3.6f, -2.2f, 5f, -4.9f)
curveToRelative(0.3f, -0.6f, 1.1f, -0.9f, 1.7f, -0.6f)
curveToRelative(0.6f, 0.3f, 0.9f, 1.1f, 0.6f, 1.7f)
curveToRelative(-2.2f, 4.4f, -6.6f, 6.3f, -6.8f, 6.4f)
lineTo(15.1f, 24f)
close()
}
path(
fill = SolidColor(figure),
fillAlpha = 1.0f,
stroke = SolidColor(shield),
strokeAlpha = 1.0f,
strokeLineWidth = 0.5f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero,
) {
moveTo(15f, 15f)
curveToRelative(-2.9f, 0f, -5.2f, -2.3f, -5.2f, -5.2f)
reflectiveCurveToRelative(2.3f, -5.2f, 5.2f, -5.2f)
reflectiveCurveToRelative(5.2f, 2.3f, 5.2f, 5.2f)
reflectiveCurveTo(17.8f, 15f, 15f, 15f)
close()
moveTo(15f, 7.2f)
curveToRelative(-1.4f, 0f, -2.6f, 1.2f, -2.6f, 2.6f)
reflectiveCurveToRelative(1.2f, 2.6f, 2.6f, 2.6f)
reflectiveCurveToRelative(2.6f, -1.2f, 2.6f, -2.6f)
reflectiveCurveTo(16.4f, 7.2f, 15f, 7.2f)
close()
}
}.build()