mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
feat(buzz): move per-row channel actions into the opened screen's top bar
The community list rows (channels, forums, DMs) each carried a 3-dot overflow — Pin/Unpin and Add/Remove-from-Messages. Those are gone; each row is now a clean tap-to-open target and its actions live in the top-bar overflow of the screen it opens: - Channels & DMs open RelayGroupChatScreen (RelayGroupTopBar): add Pin/Unpin (Buzz, non-DM) alongside the existing Messages toggle. A DM has no kind-10009 entry, so its Messages toggle drives the DM-specific hide/unhide (kind-41012 / re-open) read from the per-viewer 30622 snapshot; the DM overflow is always shown so that action stays reachable. - Forums open RelayGroupThreadsScreen: give its top bar an overflow with Pin/Unpin and Add/Remove-from-Messages. Shared BuzzPinDropdownItem / RelayGroupMessagesDropdownItem keep the two bars consistent. AccountViewModel gains hideBuzzDm/unhideBuzzDm wrappers. The community-list top-bar overflow (Add people, Invite, Agent Console, Add all) is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
This commit is contained in:
+16
@@ -1708,6 +1708,22 @@ class AccountViewModel(
|
||||
*/
|
||||
fun acceptChannelInvite(channel: RelayGroupChannel) = addRelayGroupToMessages(channel)
|
||||
|
||||
/**
|
||||
* Hide a Buzz DM from Messages (kind-41012). DM-specific — a DM has no kind-10009 entry; the relay
|
||||
* republishes my per-viewer 30622 hidden snapshot, dropping it from the inbox until I re-open it.
|
||||
*/
|
||||
fun hideBuzzDm(channel: RelayGroupChannel) = launchSigner { account.hideBuzzDm(channel) }
|
||||
|
||||
/**
|
||||
* Bring a hidden Buzz DM back to Messages: Buzz has no "unhide", so re-open the conversation with
|
||||
* the same [participants] (a kind-41010 resolving to the same canonical channel), which drops it
|
||||
* from the 30622 hidden snapshot.
|
||||
*/
|
||||
fun unhideBuzzDm(
|
||||
relay: NormalizedRelayUrl,
|
||||
participants: List<HexKey>,
|
||||
) = launchSigner { account.openBuzzDm(relay, participants) }
|
||||
|
||||
/**
|
||||
* Keep the channel off Messages without touching membership. Local and reversible — I stay in the
|
||||
* roster and can still open and post; [leaveChannelInvite] is the one that actually removes me.
|
||||
|
||||
+4
-90
@@ -32,16 +32,11 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
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.draw.clip
|
||||
@@ -83,9 +78,9 @@ private const val CARD_WARMUP_LIMIT = 10
|
||||
* kind-44100), rendered like the Concord server view — a colored monogram, the channel name with a
|
||||
* recent-posters facepile, a preview of the last message (author + snippet, or the Buzz activity
|
||||
* summary for system/diff/job rows), the relative time of that message, and an unread-count badge.
|
||||
* Tapping the card opens the channel ([onOpen]); the trailing overflow (3-dot) menu holds the
|
||||
* per-channel actions — Pin/Unpin and the Add/Remove-from-Messages toggle ([isAdded] says which half
|
||||
* to show, and it must come from the live kind-10009 list) — so the row stays clean.
|
||||
* Tapping the card opens the channel ([onOpen]); the row itself is a clean tap-to-open target — its
|
||||
* per-channel actions (Pin/Unpin, Add/Remove-from-Messages) live in the opened channel's/forum's
|
||||
* top-bar overflow, not on the row. A pinned channel still shows a pin marker here ([isStarred]).
|
||||
*
|
||||
* Reused by the relay group-list screen where Buzz membership discovery is folded in.
|
||||
*
|
||||
@@ -98,13 +93,9 @@ private const val CARD_WARMUP_LIMIT = 10
|
||||
@Composable
|
||||
fun BuzzImportRow(
|
||||
groupId: GroupId,
|
||||
isAdded: Boolean,
|
||||
onAdd: () -> Unit,
|
||||
onRemove: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
onOpen: (() -> Unit)? = null,
|
||||
isStarred: Boolean = false,
|
||||
onToggleStar: (() -> Unit)? = null,
|
||||
showActivityPreview: Boolean = true,
|
||||
) {
|
||||
val account = accountViewModel.account
|
||||
@@ -166,11 +157,7 @@ fun BuzzImportRow(
|
||||
faceAuthors = faceAuthors,
|
||||
unread = unread,
|
||||
hasUnread = hasUnread,
|
||||
isAdded = isAdded,
|
||||
isStarred = isStarred,
|
||||
onToggleStar = onToggleStar,
|
||||
onAdd = onAdd,
|
||||
onRemove = onRemove,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
@@ -195,15 +182,11 @@ private fun BuzzImportRowContent(
|
||||
faceAuthors: List<String>,
|
||||
unread: Int,
|
||||
hasUnread: Boolean,
|
||||
isAdded: Boolean,
|
||||
isStarred: Boolean,
|
||||
onToggleStar: (() -> Unit)?,
|
||||
onAdd: () -> Unit,
|
||||
onRemove: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 12.dp, top = 10.dp, bottom = 10.dp, end = 4.dp),
|
||||
modifier = Modifier.padding(start = 12.dp, top = 10.dp, bottom = 10.dp, end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
BuzzImportAvatar(name = name, seed = seed)
|
||||
@@ -254,13 +237,6 @@ private fun BuzzImportRowContent(
|
||||
ConcordUnreadBadge(unread)
|
||||
}
|
||||
}
|
||||
BuzzChannelRowMenu(
|
||||
isAdded = isAdded,
|
||||
onAdd = onAdd,
|
||||
onRemove = onRemove,
|
||||
isStarred = isStarred,
|
||||
onToggleStar = onToggleStar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,68 +284,6 @@ private fun BuzzChannelPreviewLine(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The per-channel overflow (3-dot) menu: Pin/Unpin and Add-to-my-list. Moved off the row itself so a
|
||||
* channel card reads as a clean Concord-style row, with its actions one tap behind the kebab.
|
||||
*/
|
||||
@Composable
|
||||
private fun BuzzChannelRowMenu(
|
||||
isAdded: Boolean,
|
||||
onAdd: () -> Unit,
|
||||
onRemove: () -> Unit,
|
||||
isStarred: Boolean,
|
||||
onToggleStar: (() -> Unit)?,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.MoreVert,
|
||||
contentDescription = stringRes(R.string.more_options),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
if (onToggleStar != null) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.PushPin,
|
||||
contentDescription = null,
|
||||
tint = if (isStarred) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
text = { Text(stringRes(if (isStarred) R.string.buzz_unpin else R.string.buzz_pin)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
onToggleStar()
|
||||
},
|
||||
)
|
||||
}
|
||||
// A toggle, not a one-way "Added" badge: a channel already on the kind-10009 list offers
|
||||
// the way back off it. Neither half touches the relay roster, so the channel stays in this
|
||||
// list (and readable) either way — only whether it shows on Messages changes.
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
symbol = if (isAdded) MaterialSymbols.VisibilityOff else MaterialSymbols.Add,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
text = { Text(stringRes(if (isAdded) R.string.remove_from_messages else R.string.add_to_messages)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
if (isAdded) onRemove() else onAdd()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A round monogram whose color is derived deterministically from the channel id. */
|
||||
@Composable
|
||||
private fun BuzzImportAvatar(
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.layout.size
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.commons.model.buzz.BuzzChannelStars
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
|
||||
|
||||
/**
|
||||
* Pin/Unpin a Buzz channel (a device-local favorite — [BuzzChannelStars]). Moved off the per-channel
|
||||
* list row into the opened channel's/forum's top-bar overflow, so the list row stays a clean
|
||||
* tap-to-open target. Reads the live starred set so the label + icon reflect the current state.
|
||||
*/
|
||||
@Composable
|
||||
fun BuzzPinDropdownItem(
|
||||
groupId: GroupId,
|
||||
closeMenu: () -> Unit,
|
||||
) {
|
||||
val starred by BuzzChannelStars.flow.collectAsStateWithLifecycle()
|
||||
val isStarred = groupId.id in starred
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.PushPin,
|
||||
contentDescription = null,
|
||||
tint = if (isStarred) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
text = { Text(stringRes(if (isStarred) R.string.buzz_unpin else R.string.buzz_pin)) },
|
||||
onClick = {
|
||||
closeMenu()
|
||||
BuzzChannelStars.toggle(groupId.id)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/Remove this relay-group [channel] from my kind-10009 list (whether it shows in Messages). A
|
||||
* reversible toggle that never touches my relay membership — same split as "Leave" — so it stays
|
||||
* readable either way. Reads the live kind-10009 list so it flips as the change lands. Moved off the
|
||||
* per-channel list row into the opened screen's top-bar overflow.
|
||||
*/
|
||||
@Composable
|
||||
fun RelayGroupMessagesDropdownItem(
|
||||
channel: RelayGroupChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
closeMenu: () -> Unit,
|
||||
) {
|
||||
val joinedGroupIds by accountViewModel.account.relayGroupList.liveRelayGroupIds
|
||||
.collectAsStateWithLifecycle()
|
||||
val onMyList = channel.groupId in joinedGroupIds
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
symbol = if (onMyList) MaterialSymbols.VisibilityOff else MaterialSymbols.Add,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
text = { Text(stringRes(if (onMyList) R.string.remove_from_messages else R.string.add_to_messages)) },
|
||||
onClick = {
|
||||
closeMenu()
|
||||
if (onMyList) {
|
||||
accountViewModel.removeRelayGroupFromMessages(channel)
|
||||
} else {
|
||||
accountViewModel.addRelayGroupToMessages(channel)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+7
-48
@@ -36,8 +36,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -463,13 +461,9 @@ fun RelayGroupChannelListScreen(
|
||||
RowHairline(index)
|
||||
BuzzImportRow(
|
||||
groupId = groupId,
|
||||
isAdded = groupId.id in buzzAdded,
|
||||
onAdd = { buzzVm.add(groupId) },
|
||||
onRemove = { buzzVm.remove(groupId) },
|
||||
accountViewModel = accountViewModel,
|
||||
onOpen = { nav.nav(Route.RelayGroup(groupId.id, relay.url)) },
|
||||
isStarred = groupId.id in starred,
|
||||
onToggleStar = { BuzzChannelStars.toggle(groupId.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -501,15 +495,11 @@ fun RelayGroupChannelListScreen(
|
||||
RowHairline(index)
|
||||
BuzzImportRow(
|
||||
groupId = groupId,
|
||||
isAdded = groupId.id in buzzAdded,
|
||||
onAdd = { buzzVm.add(groupId) },
|
||||
onRemove = { buzzVm.remove(groupId) },
|
||||
accountViewModel = accountViewModel,
|
||||
// A forum channel's primary content is its threads (kind-45001 posts), not a
|
||||
// kind-9 chat, so open the forum/threads view directly instead of the chat.
|
||||
onOpen = { nav.nav(Route.RelayGroupThreads(groupId.id, relay.url)) },
|
||||
isStarred = groupId.id in starred,
|
||||
onToggleStar = { BuzzChannelStars.toggle(groupId.id) },
|
||||
// Forum posts live in a separate thread store, not the chat notes the
|
||||
// activity preview reads — so don't warm a kind-9 sub that returns nothing.
|
||||
showActivityPreview = false,
|
||||
@@ -548,7 +538,6 @@ fun RelayGroupChannelListScreen(
|
||||
row = row,
|
||||
myPubkey = myPubkey,
|
||||
isHidden = false,
|
||||
onToggleMessages = { dmVm.removeFromMessages(row) },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
) {
|
||||
@@ -580,7 +569,6 @@ fun RelayGroupChannelListScreen(
|
||||
row = row,
|
||||
myPubkey = myPubkey,
|
||||
isHidden = true,
|
||||
onToggleMessages = { dmVm.addToMessages(row) },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
) {
|
||||
@@ -721,25 +709,23 @@ private fun SectionAddButton(
|
||||
|
||||
/**
|
||||
* One inline Direct-Message conversation row inside the community view: the counterpart's avatar +
|
||||
* name (or a "+N" cluster label for a group DM), a preview of the last message, a compact
|
||||
* last-activity time, and an overflow holding the Add/Remove-from-Messages toggle. The channel's
|
||||
* recent content is warmed while the row is visible so the preview fills in ahead of a tap. Tapping
|
||||
* opens the DM as its relay-group chat.
|
||||
* name (or a "+N" cluster label for a group DM), a preview of the last message, and a compact
|
||||
* last-activity time. The channel's recent content is warmed while the row is visible so the preview
|
||||
* fills in ahead of a tap. Tapping opens the DM as its relay-group chat; the Add/Remove-from-Messages
|
||||
* (hide/unhide) action lives in that chat screen's top-bar overflow, not on this row.
|
||||
*
|
||||
* [isHidden] renders the row faded and flips the overflow to "Add to Messages" — a hidden DM is a
|
||||
* live conversation the viewer merely parked, so it stays openable and reversible.
|
||||
* [isHidden] renders the row faded — a hidden DM is a live conversation the viewer merely parked, so
|
||||
* it stays openable and reversible from the opened conversation.
|
||||
*/
|
||||
@Composable
|
||||
private fun BuzzDmInlineRow(
|
||||
row: BuzzDmListViewModel.DmRow,
|
||||
myPubkey: HexKey,
|
||||
isHidden: Boolean,
|
||||
onToggleMessages: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
var menuOpen by remember { mutableStateOf(false) }
|
||||
val others = row.others.ifEmpty { listOf(myPubkey) }
|
||||
val leadHex = others.first()
|
||||
val leadUser = remember(leadHex) { LocalCache.getOrCreateUser(leadHex) }
|
||||
@@ -768,7 +754,7 @@ private fun BuzzDmInlineRow(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(start = 16.dp, end = 4.dp, top = 10.dp, bottom = 10.dp)
|
||||
.padding(start = 16.dp, end = 16.dp, top = 10.dp, bottom = 10.dp)
|
||||
.alpha(if (isHidden) 0.55f else 1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
@@ -797,33 +783,6 @@ private fun BuzzDmInlineRow(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Box {
|
||||
IconButton(onClick = { menuOpen = true }) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.MoreVert,
|
||||
contentDescription = stringRes(R.string.more_options),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
symbol = if (isHidden) MaterialSymbols.Add else MaterialSymbols.VisibilityOff,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
},
|
||||
text = { Text(stringRes(if (isHidden) R.string.add_to_messages else R.string.remove_from_messages)) },
|
||||
onClick = {
|
||||
menuOpen = false
|
||||
onToggleMessages()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
@@ -34,15 +34,19 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -166,6 +170,25 @@ private fun RelayGroupThreads(
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
// The forum's per-item actions, moved off the community-list row into this screen's
|
||||
// top-bar overflow: Pin/Unpin and the Add/Remove-from-Messages toggle. Buzz-only,
|
||||
// which every forum channel is.
|
||||
if (isBuzz) {
|
||||
var menuOpen by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { menuOpen = true }) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.MoreVert,
|
||||
contentDescription = stringRes(R.string.more_options),
|
||||
modifier = Modifier.size(22.dp),
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
||||
BuzzPinDropdownItem(channel.groupId) { menuOpen = false }
|
||||
RelayGroupMessagesDropdownItem(channel, accountViewModel) { menuOpen = false }
|
||||
}
|
||||
}
|
||||
},
|
||||
popBack = nav::popBack,
|
||||
)
|
||||
},
|
||||
|
||||
+36
-1
@@ -53,6 +53,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.commons.model.buzz.BuzzDmRegistry
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzWorkspaceStates
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
@@ -117,6 +118,9 @@ fun RelayGroupTopBar(
|
||||
// My kind-10009 list, live: drives the Add/Remove-from-Messages toggle in the overflow below.
|
||||
val joinedGroupIds by accountViewModel.account.relayGroupList.liveRelayGroupIds
|
||||
.collectAsStateWithLifecycle()
|
||||
// A Buzz DM is hidden via its own per-viewer 30622 snapshot, not the kind-10009 list, so the
|
||||
// Messages toggle branches on this for DMs.
|
||||
val hiddenDms by BuzzDmRegistry.hidden.collectAsStateWithLifecycle()
|
||||
// Read once here (nav.canPop() is @Composable) so the post-action navigation can pop from a menu
|
||||
// callback — leaving a group shouldn't strand the user on the screen of a group they left.
|
||||
val canPop = nav.canPop()
|
||||
@@ -237,7 +241,9 @@ fun RelayGroupTopBar(
|
||||
// Buzz `t=stream` channel it is always empty (forum posts live in `t=forum` channels, which
|
||||
// the relay's channel list already surfaces in their own section), so it read as a broken
|
||||
// feature on every chat. Demoted to the overflow, where the frequency of use actually is.
|
||||
if (!isDm || naddr != null || showMembershipActions) {
|
||||
// A Buzz DM always gets the overflow too — its hide/unhide (below) is the DM row's old
|
||||
// action, and it must be reachable even where the membership actions aren't offered.
|
||||
if (!isDm || naddr != null || showMembershipActions || (isDm && isBuzzRelay)) {
|
||||
IconButton(onClick = { menuOpen = true }) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.MoreVert,
|
||||
@@ -246,6 +252,33 @@ fun RelayGroupTopBar(
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
||||
// Pin/Unpin moved here off the community-list row. A local favorite, so it's offered
|
||||
// for any Buzz channel/forum regardless of membership; DMs are never pinned.
|
||||
if (isBuzzRelay && !isDm) {
|
||||
BuzzPinDropdownItem(channel.groupId) { menuOpen = false }
|
||||
}
|
||||
// A DM's Add/Remove-from-Messages, moved off the DM list row. It rides the per-viewer
|
||||
// 30622 hide snapshot (kind-41012 hide / re-open), not the kind-10009 list, and is
|
||||
// shown regardless of the membership gate below.
|
||||
if (isBuzzRelay && isDm) {
|
||||
val dmHidden = channel.groupId.id in (hiddenDms[myPubkey] ?: emptySet())
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringRes(if (dmHidden) R.string.add_to_messages else R.string.remove_from_messages)) },
|
||||
onClick = {
|
||||
menuOpen = false
|
||||
if (dmHidden) {
|
||||
val participants =
|
||||
channel.event
|
||||
?.buzzParticipants()
|
||||
?.filter { it != myPubkey }
|
||||
.orEmpty()
|
||||
accountViewModel.unhideBuzzDm(channel.groupId.relayUrl, participants.ifEmpty { listOf(myPubkey) })
|
||||
} else {
|
||||
accountViewModel.hideBuzzDm(channel)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
if (!isDm) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringRes(R.string.relay_group_threads_title)) },
|
||||
@@ -308,6 +341,8 @@ fun RelayGroupTopBar(
|
||||
// Two distinct actions, never conflated: the Messages toggle adds/drops the group
|
||||
// on my kind-10009 list but keeps my relay membership either way; "Leave" sends
|
||||
// the kind-9022 that actually removes me. Same split as the channel-invite card.
|
||||
// (A DM's Messages toggle is the hide/unhide item above — it rides a different
|
||||
// mechanism and must show even when these membership actions don't.)
|
||||
//
|
||||
// Reads the live kind-10009 list rather than assuming the group is on it: this
|
||||
// bar also opens for channels reached from the workspace browse (a Buzz relay
|
||||
|
||||
Reference in New Issue
Block a user