Merge pull request #3822 from vitorpamplona/claude/buzz-notification-relay-ebj12q

Extract RelayNameChip to shared component and add it to relay group message headers
This commit is contained in:
Vitor Pamplona
2026-07-29 21:09:43 -04:00
committed by GitHub
3 changed files with 104 additions and 46 deletions
@@ -49,13 +49,16 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayNameChip
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.buzz.workspace.buzzParticipants
import com.vitorpamplona.quartz.buzz.workspace.isBuzzDm
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nipC7Chats.ChatEvent
/**
@@ -100,7 +103,14 @@ fun RenderRelayGroupMessage(
}
}
/** A compact, tappable header naming the Buzz group (or DM participant) a message belongs to. */
/**
* A compact, tappable header naming the Buzz group (or DM participant) a message belongs to, plus a
* [RelayNameChip] naming its host relay — the same pairing the Messages row uses, and the NIP-29
* analog of the [com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordCommunityPill]
* a Concord message wears on these same cards. A group id is only unique within its host relay, so
* without the relay a notification from `#general` doesn't say *which* `#general` it came from. The
* chip taps through to the relay's channel list, while the name/avatar open the room itself.
*/
@Composable
fun RelayGroupChannelHeader(
channel: RelayGroupChannel,
@@ -129,16 +139,21 @@ fun RelayGroupChannelHeader(
)
if (dmOther != null) {
RelayGroupDmName(dmOther, channel, accountViewModel, Modifier.weight(1f))
RelayGroupDmName(dmOther, channel, accountViewModel, Modifier.weight(1f, fill = false))
} else {
Text(
text = channel.toBestDisplayName(),
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f),
modifier = Modifier.weight(1f, fill = false),
)
}
RelayNameChip(
label = channel.groupId.relayUrl.displayUrl(),
onClick = { nav.nav(Route.RelayGroupServer(channel.groupId.relayUrl.url)) },
)
}
}
@@ -0,0 +1,85 @@
/*
* 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.publicChannels.relayGroup
import androidx.compose.foundation.clickable
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
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.ChatLabelMaxWidth
/**
* A tappable chip naming the relay a NIP-29 / Buzz group lives on. A group id is only unique within
* its host relay, so the relay is part of the room's identity — the same `#general` can exist on two
* Buzz servers and only this chip tells them apart.
*
* Wears the same highlighted wash as [com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordCommunityPill]
* so every "which server / community does this room belong to" chip reads the same way. Unlike the
* muted note-header [com.vitorpamplona.amethyst.commons.ui.note.HeaderPill] (PoW/OTS/location
* markers), this one is a first-class navigation entry point, so it keeps the stronger
* `secondaryContainer` highlight. Shared by the Messages row and the Notifications feed so a Buzz
* message reads the same wherever it surfaces; the width is capped at [ChatLabelMaxWidth] with a
* middle ellipsis so a long host is truncated instead of crowding the channel name out.
*/
@Composable
fun RelayNameChip(
label: String,
onClick: () -> Unit,
) {
Surface(
shape = RoundedCornerShape(6.dp),
color = MaterialTheme.colorScheme.secondaryContainer,
modifier = Modifier.widthIn(max = ChatLabelMaxWidth).clickable(onClick = onClick),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(3.dp),
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
) {
Icon(
symbol = MaterialSymbols.Dns,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier.size(11.dp),
)
Text(
text = label,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSecondaryContainer,
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
)
}
}
}
@@ -20,22 +20,16 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
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
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -98,6 +92,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.concordCommunityHasUnreadFlow
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayNameChip
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.relayGroupChannelLastReadRoute
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.relayGroupServerHasUnreadFlow
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ConcordServerRoomNote
@@ -746,43 +741,6 @@ private fun ConcordServerRoomCompose(
)
}
/**
* A tappable chip naming the server/community a Messages row belongs to. Unlike the muted
* note-header [HeaderPill] (PoW/OTS/location markers), this one is a first-class navigation entry
* point, so it keeps the stronger `secondaryContainer` highlight.
*/
@Composable
private fun RelayNameChip(
label: String,
onClick: () -> Unit,
) {
Surface(
shape = RoundedCornerShape(6.dp),
color = MaterialTheme.colorScheme.secondaryContainer,
modifier = Modifier.widthIn(max = ChatLabelMaxWidth).clickable(onClick = onClick),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(3.dp),
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
) {
Icon(
symbol = MaterialSymbols.Dns,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier.size(11.dp),
)
Text(
text = label,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSecondaryContainer,
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
)
}
}
}
/**
* Renders a Messages row title as the channel name followed by a muted [HeaderPill] naming the room
* type (Public Chat, Marmot Group, ...). The pill mirrors the Concord community chip so every group