From ff12b6abbe7b17609ae5ad367b2a86518d1162dd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Apr 2026 14:38:43 +0000 Subject: [PATCH] feat(desktop): Messages-style headers across every remaining screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rolled the compact header pattern out to the screens the earlier pass missed. Each one now lives on the same padding/typography rhythm as Messages: Row(fillMaxWidth, padding horizontal = 12, vertical = 8), titleMedium for labels, IconButton size = 32dp with 20dp icons tinted with colorScheme.primary. - ThreadScreen: back button + "Thread" title. Was headlineMedium with bottom-only padding; now compact row. - UserProfileScreen: back + "Profile" title on the left, Edit / Follow buttons kept on the right (they stay as text buttons — they represent destructive intent, not icon affordances). - ArticleReaderScreen: back + "Article" title; zoom % label still surfaces next to the title when != 100%. - ArticleEditorScreen: back IconButton + "Article" title (was a text "Back" OutlinedButton with no title). Save / Publish buttons kept on the right — same reasoning as UserProfileScreen. - ChessScreen: back (conditional) + "Chess" / "Live Game" title; refresh + New Game converted from Button → IconButton so the header reads at the same scale as the rest. - RelayDashboardScreen: had no header at all, just a PrimaryTabRow. Converted Monitor / Configure to FilterChip tabs-first (matching Feed / Reads) so the selected tab is the title. FeedHeader relocation: - Moved commons/ui/feed/FeedHeader.kt → desktopApp/ui/FeedHeader.kt. Only NotificationsScreen.kt referenced it, and dropping the import from that file was enough because it's now in the same package. - Removed the empty commons/ui/feed/ directory. https://claude.ai/code/session_01NufduPfZvYQVYwLkbCjCUo --- .../amethyst/desktop/chess/ChessScreen.kt | 45 +++++++++++-------- .../desktop/ui/ArticleEditorScreen.kt | 34 +++++++++++--- .../desktop/ui/ArticleReaderScreen.kt | 11 ++--- .../amethyst/desktop/ui}/FeedHeader.kt | 2 +- .../desktop/ui/NotificationsScreen.kt | 1 - .../amethyst/desktop/ui/ThreadScreen.kt | 11 ++--- .../amethyst/desktop/ui/UserProfileScreen.kt | 15 ++++--- .../desktop/ui/relay/RelayDashboardScreen.kt | 22 ++++++--- 8 files changed, 92 insertions(+), 49 deletions(-) rename {commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feed => desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui}/FeedHeader.kt (98%) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/chess/ChessScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/chess/ChessScreen.kt index 5e50bcf17..4f00c44cb 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/chess/ChessScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/chess/ChessScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -38,7 +39,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -179,42 +179,49 @@ fun ChessScreen( var showNewGameDialog by remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize()) { - // Header + // Header — Messages-style: compact row, titleMedium title, icon-only actions Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Row(verticalAlignment = Alignment.CenterVertically) { if (selectedGameId != null) { - IconButton(onClick = { viewModel.selectGame(null) }) { - Icon(MaterialSymbols.AutoMirrored.ArrowBack, "Back to list") + IconButton(onClick = { viewModel.selectGame(null) }, modifier = Modifier.size(32.dp)) { + Icon( + MaterialSymbols.AutoMirrored.ArrowBack, + contentDescription = "Back to list", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), + ) } Spacer(Modifier.width(8.dp)) } Text( if (selectedGameId != null) "Live Game" else "Chess", - style = MaterialTheme.typography.headlineMedium, + style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground, ) } if (selectedGameId == null) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - // Refresh button - IconButton(onClick = { viewModel.forceRefresh() }) { - Icon(MaterialSymbols.Refresh, "Refresh") + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = { viewModel.forceRefresh() }, modifier = Modifier.size(32.dp)) { + Icon( + MaterialSymbols.Refresh, + contentDescription = "Refresh", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), + ) } - - // New Game button if (!account.isReadOnly) { - Button(onClick = { showNewGameDialog = true }) { - Icon(MaterialSymbols.Add, "New Game") - Spacer(Modifier.width(8.dp)) - Text("New Game") + IconButton(onClick = { showNewGameDialog = true }, modifier = Modifier.size(32.dp)) { + Icon( + MaterialSymbols.Add, + contentDescription = "New Game", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), + ) } } } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt index 965b25821..6812d5226 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt @@ -29,12 +29,15 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text @@ -63,6 +66,8 @@ import com.vitorpamplona.amethyst.commons.compose.editor.MarkdownEditorState import com.vitorpamplona.amethyst.commons.compose.editor.MarkdownToolbar import com.vitorpamplona.amethyst.commons.compose.editor.MetadataPanel import com.vitorpamplona.amethyst.commons.compose.markdown.RenderMarkdown +import com.vitorpamplona.amethyst.commons.icons.symbols.Icon +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.nip23LongContent.LongFormPublishAction import com.vitorpamplona.amethyst.desktop.account.AccountState import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager @@ -227,24 +232,41 @@ fun ArticleEditorScreen( } }, ) { - // Top bar + // Header — Messages-style: back + titleMedium on left, actions on right Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - OutlinedButton(onClick = onBack) { - Text("Back") + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onBack, modifier = Modifier.size(32.dp)) { + Icon( + MaterialSymbols.AutoMirrored.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), + ) + } + Spacer(Modifier.width(8.dp)) + Text( + "Article", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onBackground, + ) } - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { saveMessage?.let { Text( it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.align(Alignment.CenterVertically), ) } + // Save/Publish stay as text buttons — they're primary destructive + // actions, not affordances you'd reduce to an icon. OutlinedButton(onClick = { saveDraft() }) { Text("Save") } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt index a63511ac4..1e8804efd 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt @@ -404,24 +404,25 @@ fun ArticleReaderScreen( } }, ) { - // Top bar: back + bookmark placeholder + // Header — Messages-style: compact row, titleMedium title Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, ) { Row(verticalAlignment = Alignment.CenterVertically) { - IconButton(onClick = onBack) { + IconButton(onClick = onBack, modifier = Modifier.size(32.dp)) { Icon( MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = "Back", - modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), ) } Spacer(Modifier.width(8.dp)) Text( "Article", - style = MaterialTheme.typography.headlineMedium, + style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground, ) if (zoomLevel != 1.0f) { diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feed/FeedHeader.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedHeader.kt similarity index 98% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feed/FeedHeader.kt rename to desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedHeader.kt index 23e2f8ccb..802d7eb12 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/feed/FeedHeader.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedHeader.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.commons.ui.feed +package com.vitorpamplona.amethyst.desktop.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NotificationsScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NotificationsScreen.kt index 6c19e5833..9cfd88660 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NotificationsScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NotificationsScreen.kt @@ -56,7 +56,6 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPa import com.vitorpamplona.amethyst.commons.state.EventCollectionState import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.components.LoadingState -import com.vitorpamplona.amethyst.commons.ui.feed.FeedHeader import com.vitorpamplona.amethyst.commons.util.toTimeAgo import com.vitorpamplona.amethyst.desktop.account.AccountState import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ThreadScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ThreadScreen.kt index 0c7489a76..0c3ec0322 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ThreadScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ThreadScreen.kt @@ -198,22 +198,23 @@ fun ThreadScreen( Box(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) { - // Header with back button + // Header — Messages-style: compact row with back + titleMedium Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { - IconButton(onClick = onBack) { + IconButton(onClick = onBack, modifier = Modifier.size(32.dp)) { Icon( MaterialSymbols.AutoMirrored.ArrowBack, contentDescription = "Back", - modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), ) } Spacer(Modifier.width(8.dp)) Text( "Thread", - style = MaterialTheme.typography.headlineMedium, + style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground, ) } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt index 41348b130..c961d1882 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt @@ -458,21 +458,26 @@ fun UserProfileScreen( ) } - // Header with back button + // Header — Messages-style: compact row, titleMedium title item(key = "header") { Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Row(verticalAlignment = Alignment.CenterVertically) { - IconButton(onClick = onBack) { - Icon(MaterialSymbols.AutoMirrored.ArrowBack, "Back") + IconButton(onClick = onBack, modifier = Modifier.size(32.dp)) { + Icon( + MaterialSymbols.AutoMirrored.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(20.dp), + ) } Spacer(Modifier.width(8.dp)) Text( "Profile", - style = MaterialTheme.typography.headlineMedium, + style = MaterialTheme.typography.titleMedium, ) } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/relay/RelayDashboardScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/relay/RelayDashboardScreen.kt index fdff5c1a7..f2d6163f4 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/relay/RelayDashboardScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/relay/RelayDashboardScreen.kt @@ -20,11 +20,13 @@ */ package com.vitorpamplona.amethyst.desktop.ui.relay +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.PrimaryTabRow -import androidx.compose.material3.Tab +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.FilterChip import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -34,7 +36,9 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.model.nip65RelayList.Nip65RelayListState import com.vitorpamplona.amethyst.desktop.model.DesktopAccountRelays import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager @@ -50,7 +54,6 @@ enum class DashboardTab( CONFIGURE("Configure"), } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RelayDashboardScreen( relayManager: DesktopRelayConnectionManager, @@ -79,12 +82,17 @@ fun RelayDashboardScreen( } Column(modifier = modifier.fillMaxSize()) { - PrimaryTabRow(selectedTabIndex = DashboardTab.entries.indexOf(selectedTab)) { + // Header — Messages-style tabs-first: selected chip acts as the screen title. + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { DashboardTab.entries.forEach { tab -> - Tab( + FilterChip( selected = selectedTab == tab, onClick = { selectedTab = tab }, - text = { Text(tab.label) }, + label = { Text(tab.label) }, ) } }