From cc35a9b29aad31f12c26f01fdfb798391b699cf4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 5 May 2026 02:28:26 +0000 Subject: [PATCH] feat(amethyst): modernize Settings screen UI Group rows into Material 3 cards by section, add tonal icon containers and trailing chevrons, upgrade section header typography, and visually mark the danger zone with error colors. --- .../loggedIn/settings/AllSettingsScreen.kt | 519 ++++++++++-------- 1 file changed, 298 insertions(+), 221 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index 2c12332730..a6cec1a382 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -21,17 +21,24 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings import android.widget.Toast +import androidx.compose.foundation.background 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.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -46,13 +53,14 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue 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.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol @@ -86,12 +94,12 @@ fun AllSettingsScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val tint = MaterialTheme.colorScheme.onBackground val context = LocalContext.current val scope = rememberCoroutineScope() var showResetMarmotDialog by remember { mutableStateOf(false) } var isResettingMarmot by remember { mutableStateOf(false) } val scrollState = rememberScrollState() + val hasPrivateKey = accountViewModel.account.settings.keyPair.privKey != null Scaffold( topBar = { @@ -107,188 +115,177 @@ fun AllSettingsScreen( } }, ) { padding -> - Column(Modifier.padding(padding).verticalScroll(scrollState)) { - SettingsSectionHeader(R.string.account_settings) - SettingsNavigationRow( - title = R.string.relay_setup, - iconPainter = R.drawable.relays, - iconPainterRef = 4, - tint = tint, - onClick = { nav.nav(Route.EditRelays) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.event_sync_title, - icon = MaterialSymbols.Sync, - tint = tint, - onClick = { nav.nav(Route.EventSync) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.route_import_follows, - icon = MaterialSymbols.GroupAdd, - tint = tint, - onClick = { nav.nav(Route.ImportFollowsSelectUser) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.media_servers, - icon = MaterialSymbols.CloudUpload, - tint = tint, - onClick = { nav.nav(Route.EditMediaServers) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.nests_servers_title, - icon = MaterialSymbols.CloudUpload, - tint = tint, - onClick = { nav.nav(Route.EditNestsServers) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.profile_badges_title, - icon = MaterialSymbols.MilitaryTech, - tint = tint, - onClick = { nav.nav(Route.ProfileBadges) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.favorite_dvms_title, - icon = MaterialSymbols.AutoAwesome, - tint = tint, - onClick = { nav.nav(Route.EditFavoriteAlgoFeeds) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.reactions, - icon = MaterialSymbols.FavoriteBorder, - tint = tint, - onClick = { nav.nav(Route.UpdateReactionType) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.video_player_settings, - icon = MaterialSymbols.VideoSettings, - tint = tint, - onClick = { nav.nav(Route.VideoPlayerSettings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.zaps, - icon = MaterialSymbols.Bolt, - tint = tint, - onClick = { nav.nav(Route.UpdateZapAmount()) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.payment_targets, - icon = MaterialSymbols.Payment, - tint = tint, - onClick = { nav.nav(Route.EditPaymentTargets) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.security_filters, - icon = MaterialSymbols.Security, - tint = tint, - onClick = { nav.nav(Route.SecurityFilters) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.call_settings, - icon = MaterialSymbols.Phone, - tint = tint, - onClick = { nav.nav(Route.CallSettings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.translations, - icon = MaterialSymbols.Translate, - tint = tint, - onClick = { nav.nav(Route.UserSettings) }, - ) - HorizontalDivider(thickness = 4.dp) - SettingsSectionHeader(R.string.app_settings) - SettingsNavigationRow( - title = R.string.privacy_options, - iconPainter = R.drawable.ic_tor, - iconPainterRef = 1, - tint = tint, - onClick = { nav.nav(Route.PrivacyOptions) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.ots_explorer_settings, - icon = MaterialSymbols.Search, - tint = tint, - onClick = { nav.nav(Route.OtsSettings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.namecoin_settings, - icon = MaterialSymbols.Security, - tint = tint, - onClick = { nav.nav(Route.NamecoinSettings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.ui_preferences, - icon = MaterialSymbols.Settings, - tint = tint, - onClick = { nav.nav(Route.Settings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.reactions_settings, - icon = MaterialSymbols.ThumbUp, - tint = tint, - onClick = { nav.nav(Route.ReactionsSettings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.bottom_bar_settings, - icon = MaterialSymbols.Dashboard, - tint = tint, - onClick = { nav.nav(Route.BottomBarSettings) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.home_tabs_settings, - icon = MaterialSymbols.Home, - tint = tint, - onClick = { nav.nav(Route.HomeTabsSettings) }, - ) - HorizontalDivider(thickness = 4.dp) - SettingsSectionHeader(R.string.danger_zone) - accountViewModel.account.settings.keyPair.privKey?.let { - SettingsNavigationRow( - title = R.string.backup_keys, - icon = MaterialSymbols.Key, - tint = tint, - onClick = { nav.nav(Route.AccountBackup) }, + Column( + modifier = + Modifier + .padding(padding) + .verticalScroll(scrollState) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + SettingsSection(R.string.account_settings) { + SettingsItem( + title = R.string.relay_setup, + iconPainter = R.drawable.relays, + iconPainterRef = 4, + onClick = { nav.nav(Route.EditRelays) }, ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.request_to_vanish, - icon = MaterialSymbols.DeleteForever, - tint = tint, - onClick = { nav.nav(Route.RequestToVanish) }, + SettingsDivider() + SettingsItem( + title = R.string.event_sync_title, + icon = MaterialSymbols.Sync, + onClick = { nav.nav(Route.EventSync) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.route_import_follows, + icon = MaterialSymbols.GroupAdd, + onClick = { nav.nav(Route.ImportFollowsSelectUser) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.media_servers, + icon = MaterialSymbols.CloudUpload, + onClick = { nav.nav(Route.EditMediaServers) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.nests_servers_title, + icon = MaterialSymbols.CloudUpload, + onClick = { nav.nav(Route.EditNestsServers) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.profile_badges_title, + icon = MaterialSymbols.MilitaryTech, + onClick = { nav.nav(Route.ProfileBadges) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.favorite_dvms_title, + icon = MaterialSymbols.AutoAwesome, + onClick = { nav.nav(Route.EditFavoriteAlgoFeeds) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.reactions, + icon = MaterialSymbols.FavoriteBorder, + onClick = { nav.nav(Route.UpdateReactionType) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.video_player_settings, + icon = MaterialSymbols.VideoSettings, + onClick = { nav.nav(Route.VideoPlayerSettings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.zaps, + icon = MaterialSymbols.Bolt, + onClick = { nav.nav(Route.UpdateZapAmount()) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.payment_targets, + icon = MaterialSymbols.Payment, + onClick = { nav.nav(Route.EditPaymentTargets) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.security_filters, + icon = MaterialSymbols.Security, + onClick = { nav.nav(Route.SecurityFilters) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.call_settings, + icon = MaterialSymbols.Phone, + onClick = { nav.nav(Route.CallSettings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.translations, + icon = MaterialSymbols.Translate, + onClick = { nav.nav(Route.UserSettings) }, + ) + } + + SettingsSection(R.string.app_settings) { + SettingsItem( + title = R.string.privacy_options, + iconPainter = R.drawable.ic_tor, + iconPainterRef = 1, + onClick = { nav.nav(Route.PrivacyOptions) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.ots_explorer_settings, + icon = MaterialSymbols.Search, + onClick = { nav.nav(Route.OtsSettings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.namecoin_settings, + icon = MaterialSymbols.Security, + onClick = { nav.nav(Route.NamecoinSettings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.ui_preferences, + icon = MaterialSymbols.Settings, + onClick = { nav.nav(Route.Settings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.reactions_settings, + icon = MaterialSymbols.ThumbUp, + onClick = { nav.nav(Route.ReactionsSettings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.bottom_bar_settings, + icon = MaterialSymbols.Dashboard, + onClick = { nav.nav(Route.BottomBarSettings) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.home_tabs_settings, + icon = MaterialSymbols.Home, + onClick = { nav.nav(Route.HomeTabsSettings) }, + ) + } + + SettingsSection(R.string.danger_zone, isDanger = true) { + if (hasPrivateKey) { + SettingsItem( + title = R.string.backup_keys, + icon = MaterialSymbols.Key, + isDanger = true, + onClick = { nav.nav(Route.AccountBackup) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.request_to_vanish, + icon = MaterialSymbols.DeleteForever, + isDanger = true, + onClick = { nav.nav(Route.RequestToVanish) }, + ) + SettingsDivider() + } + SettingsItem( + title = R.string.vanish_history, + icon = MaterialSymbols.History, + isDanger = true, + onClick = { nav.nav(Route.VanishEvents) }, + ) + SettingsDivider() + SettingsItem( + title = R.string.reset_marmot_state, + icon = MaterialSymbols.DeleteSweep, + isDanger = true, + onClick = { if (!isResettingMarmot) showResetMarmotDialog = true }, ) - HorizontalDivider() } - SettingsNavigationRow( - title = R.string.vanish_history, - icon = MaterialSymbols.History, - tint = tint, - onClick = { nav.nav(Route.VanishEvents) }, - ) - HorizontalDivider() - SettingsNavigationRow( - title = R.string.reset_marmot_state, - icon = MaterialSymbols.DeleteSweep, - tint = tint, - onClick = { if (!isResettingMarmot) showResetMarmotDialog = true }, - ) } } @@ -364,71 +361,151 @@ private fun ResetMarmotStateDialog( } @Composable -private fun SettingsSectionHeader(title: Int) { - Text( - text = stringRes(title), - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 16.dp, bottom = 4.dp), +private fun SettingsSection( + title: Int, + isDanger: Boolean = false, + content: @Composable ColumnScope.() -> Unit, +) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + text = stringRes(title), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = + if (isDanger) { + MaterialTheme.colorScheme.error + } else { + MaterialTheme.colorScheme.primary + }, + modifier = Modifier.padding(horizontal = 4.dp), + ) + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainerLow, + ), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), + ) { + Column(content = content) + } + } +} + +@Composable +private fun SettingsDivider() { + HorizontalDivider( + modifier = Modifier.padding(start = 68.dp), + thickness = 0.5.dp, + color = MaterialTheme.colorScheme.outlineVariant, ) } @Composable -private fun SettingsNavigationRow( +private fun SettingsItem( title: Int, icon: MaterialSymbol, - tint: Color, + isDanger: Boolean = false, onClick: () -> Unit, ) { - Row( - modifier = - Modifier - .fillMaxWidth() - .clickable(onClick = onClick) - .padding(vertical = 16.dp, horizontal = 24.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - symbol = icon, - contentDescription = stringRes(title), - modifier = Modifier.size(24.dp), - tint = tint, - ) - Text( - text = stringRes(title), - fontSize = 18.sp, - modifier = Modifier.padding(start = 16.dp), - ) - } + SettingsItemRow( + title = title, + isDanger = isDanger, + onClick = onClick, + leadingIcon = { tint -> + Icon( + symbol = icon, + contentDescription = stringRes(title), + modifier = Modifier.size(20.dp), + tint = tint, + ) + }, + ) } @Composable -private fun SettingsNavigationRow( +private fun SettingsItem( title: Int, iconPainter: Int, iconPainterRef: Int, - tint: Color, + isDanger: Boolean = false, onClick: () -> Unit, ) { + val painter: Painter = painterRes(iconPainter, iconPainterRef) + SettingsItemRow( + title = title, + isDanger = isDanger, + onClick = onClick, + leadingIcon = { tint -> + Icon( + painter = painter, + contentDescription = stringRes(title), + modifier = Modifier.size(20.dp), + tint = tint, + ) + }, + ) +} + +@Composable +private fun SettingsItemRow( + title: Int, + isDanger: Boolean, + onClick: () -> Unit, + leadingIcon: @Composable (tint: Color) -> Unit, +) { + val containerColor = + if (isDanger) { + MaterialTheme.colorScheme.errorContainer + } else { + MaterialTheme.colorScheme.primaryContainer + } + val iconTint = + if (isDanger) { + MaterialTheme.colorScheme.onErrorContainer + } else { + MaterialTheme.colorScheme.onPrimaryContainer + } + val textColor = + if (isDanger) { + MaterialTheme.colorScheme.error + } else { + MaterialTheme.colorScheme.onSurface + } + Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick) - .padding(vertical = 16.dp, horizontal = 24.dp), + .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, ) { - Icon( - painter = painterRes(iconPainter, iconPainterRef), - contentDescription = stringRes(title), - modifier = Modifier.size(24.dp), - tint = tint, - ) + Box( + modifier = + Modifier + .size(36.dp) + .clip(RoundedCornerShape(10.dp)) + .background(containerColor), + contentAlignment = Alignment.Center, + ) { + leadingIcon(iconTint) + } Text( text = stringRes(title), - fontSize = 18.sp, - modifier = Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.bodyLarge, + color = textColor, + modifier = + Modifier + .weight(1f) + .padding(start = 16.dp), + ) + Icon( + symbol = MaterialSymbols.ChevronRight, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } }