From 4fe60162e1675031d3ec88f9ec5d5230877f7f84 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 02:20:09 +0000 Subject: [PATCH 1/3] feat: smaller subtitle font in Messages screen rows The message-preview second line on every Messages-screen row (channels, groups, and DMs) rendered at the ambient bodyLarge (16sp), matching the bold title above it. Drop it to bodyMedium (14sp) so the title and the muted preview read as two tiers instead of one block of same-size text. Covers both renderers all rows funnel through: ChannelName (public chats, ephemeral/geohash chats, Marmot/NIP-29 groups, Concord) and LastMessagePreview (NIP-17/NIP-04 DMs), including the "event not found" fallback line. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KQ9Cz2QjLMvemzVyJS1f5V --- .../ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index ad9a878212..b37d890c34 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -886,7 +886,7 @@ private fun RowScope.LastMessagePreview( color = MaterialTheme.colorScheme.grayText, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = LocalTextStyle.current.copy(textDirection = TextDirection.Content), + style = MaterialTheme.typography.bodyMedium.copy(textDirection = TextDirection.Content), modifier = Modifier.weight(1f), ) } @@ -965,7 +965,7 @@ fun ChannelName( color = MaterialTheme.colorScheme.grayText, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = LocalTextStyle.current.copy(textDirection = TextDirection.Content), + style = MaterialTheme.typography.bodyMedium.copy(textDirection = TextDirection.Content), modifier = Modifier.weight(1f), ) } else { @@ -974,6 +974,7 @@ fun ChannelName( color = MaterialTheme.colorScheme.grayText, maxLines = 1, overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium.copy(textDirection = TextDirection.Content), modifier = Modifier.weight(1f), ) } From 039beb0684afb2b3a69abd098f2cff6066b32754 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 02:29:34 +0000 Subject: [PATCH 2/3] fix: mark all room types read in Messages "mark as read" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit markAllChatNotesAsRead only enumerated public chats (IsInPublicChatChannel) and DMs (ChatroomKeyable), so every newer room type fell through the when() with no branch: NIP-29 relay groups, Concord communities, Marmot groups, geohash chat, and ephemeral relay chat. Their unread dots on the Messages screen could only be cleared by opening each room — "mark all as read" left them lit. The collapsed per-server rows (RelayGroupServerRoomNote, ConcordServerRoomNote) were skipped too. Extract markRoomNoteAsRead(account, note), mirroring ChatroomEntry's type dispatch so each row's last-read route is resolved the same way its unread dot reads it: synthetic grouped rows first (fanning out to every joined group on the relay / every channel in the community), then gatherer-attached channels (Marmot, NIP-29, Concord, geohash), then the h-tag group fallback, then the raw event type (public chat incl. ChannelCreateEvent, ephemeral, DM, and drafts wrapping those). markAllChatNotesAsRead now just maps the visible notes through it. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KQ9Cz2QjLMvemzVyJS1f5V --- .../ui/screen/loggedIn/AccountViewModel.kt | 28 +--- .../chats/rooms/MarkChatRoomsAsRead.kt | 149 ++++++++++++++++++ 2 files changed, 154 insertions(+), 23 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/MarkChatRoomsAsRead.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index c8845a6468..76e7c134de 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -103,6 +103,7 @@ import com.vitorpamplona.amethyst.ui.screen.UiSettingsState import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconChange import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconUpload import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconUploader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.markRoomNoteAsRead import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NOTIFICATION_LAST_READ_KEY import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync @@ -144,7 +145,6 @@ import com.vitorpamplona.quartz.nip05DnsIdentifiers.INip05Client import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey -import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable import com.vitorpamplona.quartz.nip17Dm.base.NIP17Group import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent @@ -159,7 +159,6 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent -import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel import com.vitorpamplona.quartz.nip29RelayGroups.GroupId import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect @@ -2135,27 +2134,10 @@ class AccountViewModel( fun markAllChatNotesAsRead(notes: List) { viewModelScope.launch(Dispatchers.IO) { - for (note in notes) { - val noteEvent = note.event - when { - noteEvent is IsInPublicChatChannel -> { - account.markAsRead("Channel/${noteEvent.channelId()}", noteEvent.createdAt) - } - - noteEvent is ChatroomKeyable -> { - account.markAsRead(privateChatLastReadRoute(noteEvent.chatroomKey(account.signer.pubKey)), noteEvent.createdAt) - } - - noteEvent is DraftWrapEvent -> { - val innerEvent = account.draftsDecryptionCache.preCachedDraft(noteEvent) - if (innerEvent is IsInPublicChatChannel) { - account.markAsRead("Channel/${innerEvent.channelId()}", noteEvent.createdAt) - } else if (innerEvent is ChatroomKeyable) { - account.markAsRead(privateChatLastReadRoute(innerEvent.chatroomKey(account.signer.pubKey)), noteEvent.createdAt) - } - } - } - } + // markRoomNoteAsRead resolves each row's last-read route the same way ChatroomEntry does, + // so every room kind shown on the Messages screen — public chats, DMs, NIP-29 relay groups, + // Concord, Marmot, geohash, ephemeral, and the collapsed per-server rows — is covered. + notes.forEach { markRoomNoteAsRead(account, it) } markHiddenChatroomsAsRead() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/MarkChatRoomsAsRead.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/MarkChatRoomsAsRead.kt new file mode 100644 index 0000000000..d923113984 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/MarkChatRoomsAsRead.kt @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * 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.ui.screen.loggedIn.chats.rooms + +import com.vitorpamplona.amethyst.commons.model.concord.ConcordChannel +import com.vitorpamplona.amethyst.commons.model.geohashChat.GeohashChatChannel +import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom +import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.privateChatLastReadRoute +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.marmotGroupLastReadRoute +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.concordChannelLastReadRoute +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.relayGroupChannelLastReadRoute +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ConcordServerRoomNote +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.RelayGroupServerRoomNote +import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer +import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable +import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent +import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel +import com.vitorpamplona.quartz.nip29RelayGroups.GroupId +import com.vitorpamplona.quartz.nip29RelayGroups.groupId +import com.vitorpamplona.quartz.nip29RelayGroups.isGroupScoped +import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent + +/** + * Marks one Messages-list row as read by advancing the very same last-read route(s) the row's unread + * dot reads from. This MUST stay in lockstep with [ChatroomEntry]'s type dispatch: every row kind that + * can light an unread dot needs a branch here, or "mark all as read" silently skips it and its dot can + * only ever be cleared by opening the room. The resolution order mirrors [ChatroomEntry] exactly — + * synthetic grouped rows, then gatherer-attached channels, then the `h`-tag fallback, then the raw + * event type — so the route computed here is byte-for-byte the one each row badge collects. + * + * The two synthetic "grouped by server" rows collapse many rooms behind one aggregate dot, so they + * fan out: every joined group on the relay ([RelayGroupServerRoomNote]) / every channel in the + * community ([ConcordServerRoomNote]) is marked read up to the row's newest message. [Account.markAsRead] + * only ever advances a route, so over-reaching a quieter room to the aggregate cutoff never un-reads it. + */ +fun markRoomNoteAsRead( + account: Account, + note: Note, +) { + when (note) { + is RelayGroupServerRoomNote -> { + val cutoff = note.createdAt() ?: return + account.relayGroupList.liveRelayGroupList.value.forEach { tag -> + if (RelayUrlNormalizer.normalizeOrNull(tag.relayUrl) == note.relay) { + account.markAsRead(relayGroupChannelLastReadRoute(GroupId(tag.groupId, note.relay)), cutoff) + } + } + return + } + + is ConcordServerRoomNote -> { + val cutoff = note.createdAt() ?: return + account.concordSessions + .sessionFor(note.communityId) + ?.state + ?.value + ?.channels + ?.keys + ?.forEach { channelKey -> + account.markAsRead(concordChannelLastReadRoute(note.communityId, channelKey), cutoff) + } + return + } + } + + val createdAt = note.createdAt() ?: return + + // Gatherer-attached channels first, in the same priority order as ChatroomEntry. + note.inGatherers?.firstNotNullOfOrNull { it as? MarmotGroupChatroom }?.let { + account.markAsRead(marmotGroupLastReadRoute(it.nostrGroupId), createdAt) + return + } + note.inGatherers?.firstNotNullOfOrNull { it as? RelayGroupChannel }?.let { + account.markAsRead(relayGroupChannelLastReadRoute(it.groupId), createdAt) + return + } + note.inGatherers?.firstNotNullOfOrNull { it as? ConcordChannel }?.let { + account.markAsRead(concordChannelLastReadRoute(it.channelId.communityId, it.channelId.channelId), createdAt) + return + } + note.inGatherers?.firstNotNullOfOrNull { it as? GeohashChatChannel }?.let { + account.markAsRead("Geohash/${it.geohash}", createdAt) + return + } + + // A NIP-29 group message whose channel gatherer never attached: resolve the group from its `h` tag + // + provenance relay, exactly like ChatroomEntry's fallback row. + val groupScopedEvent = note.event?.takeIf { it.isGroupScoped() } + if (groupScopedEvent != null) { + val gid = groupScopedEvent.groupId() + val hostRelay = note.relays.firstOrNull() + if (gid != null && hostRelay != null) { + account.markAsRead(relayGroupChannelLastReadRoute(GroupId(gid, hostRelay)), createdAt) + return + } + } + + markEventRoomAsRead(account, note.event, createdAt) +} + +/** + * Marks the room of a plain (non-gathered) chat event read: public chats (NIP-28), ephemeral relay + * chats, and 1:1/group DMs (NIP-17/04), unwrapping a [DraftWrapEvent] to the same three cases. Drafts + * for group-scoped rooms are already caught by the gatherer checks above, so they don't recur here. + */ +private fun markEventRoomAsRead( + account: Account, + event: Event?, + createdAt: Long, +) { + when (event) { + is IsInPublicChatChannel -> event.channelId()?.let { account.markAsRead("Channel/$it", createdAt) } + is ChannelCreateEvent -> account.markAsRead("Channel/${event.id}", createdAt) + is EphemeralChatEvent -> event.roomId()?.let { account.markAsRead("Channel/${it.toKey()}", createdAt) } + is ChatroomKeyable -> account.markAsRead(privateChatLastReadRoute(event.chatroomKey(account.signer.pubKey)), createdAt) + is DraftWrapEvent -> { + when (val inner = account.draftsDecryptionCache.preCachedDraft(event)) { + is IsInPublicChatChannel -> inner.channelId()?.let { account.markAsRead("Channel/$it", createdAt) } + is ChannelCreateEvent -> account.markAsRead("Channel/${inner.id}", createdAt) + is ChatroomKeyable -> account.markAsRead(privateChatLastReadRoute(inner.chatroomKey(account.signer.pubKey)), createdAt) + else -> {} + } + } + else -> {} + } +} From cd221d5de120f8c6544d375ead4737ee2dfd0187 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 02:57:56 +0000 Subject: [PATCH 3/3] feat: cap Messages room-label chips at ~half the row, unify Concord chip color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type/label chips that sit beside a room name on the Messages screen — the NIP-28 "Public Chat" pill (HeaderPill), the NIP-29 relay-host chip (RelayNameChip), and the Concord community chip (ConcordCommunityPill) — could grow with a long relay URL or community name and crowd the room name out. Cap each at ChatLabelMaxWidth (140.dp, ~half a phone row) via widthIn(max); the room name stays weighted so it keeps whatever the capped chip doesn't take, and each chip's label truncates with a middle ellipsis (TextOverflow.MiddleEllipsis) so the informative head and tail both survive. RelayNameChip switches from a plain end ellipsis; ConcordCommunityPill drops its char-count truncation (maxChars) for width-based truncation. Also give the Concord chip the NIP-29 chip's highlighted look — secondaryContainer background / onSecondaryContainer content (a gray on the dark theme) — so both "which server/community does this room belong to" chips read the same. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01KQ9Cz2QjLMvemzVyJS1f5V --- .../concord/ConcordCommunityPill.kt | 30 +++++++++---------- .../chats/rooms/ChatroomHeaderCompose.kt | 7 +++-- .../vitorpamplona/amethyst/ui/theme/Shape.kt | 8 +++++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCommunityPill.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCommunityPill.kt index 4fa775cc6f..91e60aa4ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCommunityPill.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCommunityPill.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -36,27 +37,26 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols -import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.amethyst.ui.theme.ChatLabelMaxWidth /** - * A tappable chip naming the Concord community a message belongs to. Deliberately **muted** — the same - * faint wash the note-header markers use — because the community's logo is now the row avatar, so the - * name only needs to read as tappable metadata, not compete with it. (The NIP-29 relay-host chip stays - * highlighted; a relay group has no avatar of its own.) Shared by the Messages row and the Notifications - * feed so a Concord message reads the same wherever it surfaces; the name is hard-capped so a long title - * can't crowd the row. + * A tappable chip naming the Concord community a message belongs to. Wears the same highlighted wash as + * the NIP-29 relay-host chip ([secondaryContainer] — a gray on the dark theme) so every "which server / + * community does this room belong to" chip reads the same across the Messages screen. Shared by the + * Messages row and the Notifications feed so a Concord message reads the same wherever it surfaces; the + * width is capped at [ChatLabelMaxWidth] with a middle ellipsis so a long community name is truncated + * instead of crowding the room name out. */ @Composable fun ConcordCommunityPill( communityName: String, onClick: () -> Unit, - maxChars: Int = 20, ) { Surface( shape = RoundedCornerShape(6.dp), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.07f), - contentColor = MaterialTheme.colorScheme.placeholderText, - modifier = Modifier.clickable(onClick = onClick), + color = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.widthIn(max = ChatLabelMaxWidth).clickable(onClick = onClick), ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -66,15 +66,15 @@ fun ConcordCommunityPill( Icon( symbol = MaterialSymbols.Group, contentDescription = null, - tint = MaterialTheme.colorScheme.placeholderText, + tint = MaterialTheme.colorScheme.onSecondaryContainer, modifier = Modifier.size(11.dp), ) Text( - text = if (communityName.length > maxChars) communityName.take(maxChars).trimEnd() + "…" else communityName, + text = communityName, style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.placeholderText, + color = MaterialTheme.colorScheme.onSecondaryContainer, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.MiddleEllipsis, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index b37d890c34..05ab71f7d3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.Spacer 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.shape.RoundedCornerShape import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -102,6 +103,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ConcordServ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.RelayGroupServerRoomNote import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AccountPictureModifier +import com.vitorpamplona.amethyst.ui.theme.ChatLabelMaxWidth import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size55dp @@ -683,7 +685,7 @@ private fun RelayNameChip( Surface( shape = RoundedCornerShape(6.dp), color = MaterialTheme.colorScheme.secondaryContainer, - modifier = Modifier.clickable(onClick = onClick), + modifier = Modifier.widthIn(max = ChatLabelMaxWidth).clickable(onClick = onClick), ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -701,7 +703,7 @@ private fun RelayNameChip( style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSecondaryContainer, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.MiddleEllipsis, ) } } @@ -733,6 +735,7 @@ private fun ChannelTitleWithLabelInfo( HeaderPill( symbol = labelIcon, text = stringRes(id = label), + modifier = Modifier.widthIn(max = ChatLabelMaxWidth), ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index f7543a0a1a..ffca10a088 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -133,6 +133,14 @@ val Size100dp = 100.dp val Size110dp = 110.dp val Size165dp = 165.dp +/** + * Max width for a type/label chip sitting beside a room name on a Messages-list row (the NIP-28 + * "Public Chat" pill, the NIP-29 relay-host chip, the Concord community chip, ...). Roughly half a + * phone row so a long relay URL or community name is truncated (middle ellipsis) instead of crowding + * the room name out — the name is weighted and keeps whatever the capped chip doesn't take. + */ +val ChatLabelMaxWidth = 140.dp + val StdEndPadding = Modifier.padding(end = 10.dp) val HalfEndPadding = Modifier.padding(end = 5.dp) val HalfStartPadding = Modifier.padding(start = 5.dp)