mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-07-30 19:46:17 +00:00
fix: draw the "added you to a channel" prompt as a note row
The invite prompt was a floating Material card with a surfaceVariant fill, sitting above the Notifications feed and Messages > New Requests as its own visual language — nothing else on either surface looks like that. Render it through NoteComposeLayout instead, the same layout every note and notification card uses: the actor becomes the row's author (picture, name and time in the usual header), "Added to <channel>" plus the host relay become the row's content, and the three choices take the reactions slot. So the prompt now reads like the reply and mention notifications it sits next to, and a divider closes each row the way the feed does. The channel name is now read through observeChannel, the metadata-only observer the channel rows use, so a stranger's add resolves to a name instead of a raw group id. It does not open the channel's message subscription — holding that back until the viewer answers is the whole point of the prompt. Also relabel "Show" to "Add to Messages". acceptChannelInvite is defined as `= addRelayGroupToMessages(channel)`, literally the call behind the channel top bar's "Add to Messages" item, so one action had two words for it. Reusing the existing string drops channel_invite_accept and channel_invite_body (the actor and the question both live in the row now). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGB1z4VcNfDw5cMt9V5NCG
This commit is contained in:
+1
-1
@@ -139,7 +139,7 @@ fun MessagesPager(
|
||||
// same state holder, so the two surfaces cannot disagree.
|
||||
headerContent =
|
||||
if (tabs[page].resource == R.string.new_requests) {
|
||||
{ ChannelInvitesSection(accountViewModel) }
|
||||
{ ChannelInvitesSection(accountViewModel, nav) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
|
||||
+97
-31
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -35,15 +35,32 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzChannelInvite
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||
import com.vitorpamplona.amethyst.ui.layouts.NoteComposeLayout
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayBlankAuthor
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.UserNameRowHeight
|
||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
|
||||
|
||||
@@ -60,6 +77,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
|
||||
@Composable
|
||||
fun ChannelInvitesSection(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val invites by accountViewModel.feedStates.channelInvites.flow
|
||||
@@ -69,48 +87,94 @@ fun ChannelInvitesSection(
|
||||
|
||||
Column(modifier) {
|
||||
invites.forEach { invite ->
|
||||
ChannelInviteCard(invite, accountViewModel)
|
||||
ChannelInviteCard(invite, accountViewModel, nav)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One pending add, drawn as a feed row instead of a floating Material card: the actor is the row's
|
||||
* author — picture, name and time in the usual note header — and "added you to X" is the row's content,
|
||||
* so the prompt reads like the reply/mention notifications it sits next to. The three choices take the
|
||||
* reactions slot, which spans the full width and therefore fits "Add to Messages" without wrapping.
|
||||
*/
|
||||
@Composable
|
||||
fun ChannelInviteCard(
|
||||
invite: BuzzChannelInvite,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val channel = remember(invite.channelId, invite.relay) { LocalCache.getOrCreateRelayGroupChannel(GroupId(invite.channelId, invite.relay)) }
|
||||
val baseChannel =
|
||||
remember(invite.channelId, invite.relay) {
|
||||
LocalCache.getOrCreateRelayGroupChannel(GroupId(invite.channelId, invite.relay))
|
||||
}
|
||||
|
||||
// Metadata only — the same observer the channel rows use. It resolves the name a stranger's add
|
||||
// arrives without, and never opens the channel's message subscription (which is the whole point of
|
||||
// holding the invite back until the viewer answers).
|
||||
val channelState by observeChannel(baseChannel, accountViewModel)
|
||||
val channel = channelState?.channel as? RelayGroupChannel ?: baseChannel
|
||||
|
||||
val actorUser = remember(invite.actor) { invite.actor?.let { LocalCache.getOrCreateUser(it) } }
|
||||
val actorName = actorUser?.let { observeUserName(it, accountViewModel).value }
|
||||
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
) {
|
||||
Column(Modifier.padding(12.dp)) {
|
||||
Text(
|
||||
text = stringRes(R.string.channel_invite_title, channel.toBestDisplayName()),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Text(
|
||||
// A pending invite is by definition unanswered, so it always carries the new-item wash rather than
|
||||
// fading with a last-read marker: it is a standing question, not a dated event.
|
||||
val backgroundColor =
|
||||
MaterialTheme.colorScheme.newItemBackgroundColor
|
||||
.compositeOver(MaterialTheme.colorScheme.background)
|
||||
|
||||
NoteComposeLayout(
|
||||
modifier = Modifier.drawBehind { drawRect(backgroundColor) }.fillMaxWidth(),
|
||||
authorPicture = {
|
||||
Box(Size55Modifier, contentAlignment = Alignment.BottomEnd) {
|
||||
if (actorUser != null) {
|
||||
UserPicture(actorUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
||||
} else {
|
||||
DisplayBlankAuthor(Size55dp, accountViewModel = accountViewModel)
|
||||
}
|
||||
}
|
||||
},
|
||||
firstRow = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(Size5dp),
|
||||
modifier = UserNameRowHeight,
|
||||
) {
|
||||
// Who did it matters: the relay reports a self-join with the same event, so naming the
|
||||
// actor is what tells "I joined this" apart from "a stranger put me here".
|
||||
text =
|
||||
stringRes(
|
||||
R.string.channel_invite_body,
|
||||
actorName ?: stringRes(R.string.channel_invite_unknown_actor),
|
||||
invite.relay.displayUrl(),
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
if (actorUser != null) {
|
||||
UsernameDisplay(actorUser, Modifier.weight(1f), accountViewModel = accountViewModel)
|
||||
} else {
|
||||
Text(
|
||||
text = stringRes(R.string.channel_invite_unknown_actor),
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
||||
TimeAgo(invite.createdAt)
|
||||
}
|
||||
},
|
||||
secondRow = {},
|
||||
noteContent = {
|
||||
Text(text = stringRes(R.string.channel_invite_title, channel.toBestDisplayName()))
|
||||
|
||||
Text(
|
||||
text = invite.relay.displayUrl(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
reactionsRow = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp),
|
||||
) {
|
||||
// Leave is separate from Ignore on purpose: Ignore is a local display choice that leaves
|
||||
// you in the roster, Leave is the kind-9022 that actually removes you from the channel.
|
||||
@@ -120,10 +184,12 @@ fun ChannelInviteCard(
|
||||
TextButton(onClick = { accountViewModel.dismissChannelInvite(invite.channelId) }) {
|
||||
Text(stringRes(R.string.channel_invite_ignore))
|
||||
}
|
||||
// Accepting *is* `addRelayGroupToMessages`, the same call behind the channel top bar's
|
||||
// "Add to Messages", so it carries that label rather than a second word for one action.
|
||||
TextButton(onClick = { accountViewModel.acceptChannelInvite(channel) }) {
|
||||
Text(stringRes(R.string.channel_invite_accept), fontWeight = FontWeight.Bold)
|
||||
Text(stringRes(R.string.add_to_messages), fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -246,7 +246,7 @@ internal fun SingleNotificationsBody(
|
||||
ObserveInboxRelayListAndDisplayIfNotFound(accountViewModel, nav)
|
||||
// "X added you to #channel" prompts sit above the feed rather than inside it: they are a
|
||||
// standing decision, not a dated event, so they must not scroll away into history.
|
||||
ChannelInvitesSection(accountViewModel)
|
||||
ChannelInvitesSection(accountViewModel, nav)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2563,9 +2563,7 @@
|
||||
<string name="relay_group_browse_popular">Oblíbené relaye</string>
|
||||
<string name="relay_group_no_messages_yet">Zatím žádné zprávy</string>
|
||||
<string name="channel_invite_title">Přidáno do %1$s</string>
|
||||
<string name="channel_invite_body">%1$s vás přidal do tohoto kanálu na %2$s. Zobrazit ho ve Zprávách?</string>
|
||||
<string name="channel_invite_unknown_actor">Někdo</string>
|
||||
<string name="channel_invite_accept">Zobrazit</string>
|
||||
<string name="channel_invite_ignore">Ignorovat</string>
|
||||
<string name="channel_invite_leave">Opustit</string>
|
||||
<string name="relay_group_join_to_post">Připojte se k této skupině pro odesílání zpráv.</string>
|
||||
|
||||
@@ -2472,9 +2472,7 @@
|
||||
<string name="relay_group_browse_popular">Beliebte Relays</string>
|
||||
<string name="relay_group_no_messages_yet">Noch keine Nachrichten</string>
|
||||
<string name="channel_invite_title">Zu %1$s hinzugefügt</string>
|
||||
<string name="channel_invite_body">%1$s hat dich auf %2$s zu diesem Kanal hinzugefügt. In Nachrichten anzeigen?</string>
|
||||
<string name="channel_invite_unknown_actor">Jemand</string>
|
||||
<string name="channel_invite_accept">Anzeigen</string>
|
||||
<string name="channel_invite_ignore">Ignorieren</string>
|
||||
<string name="channel_invite_leave">Verlassen</string>
|
||||
<string name="relay_group_join_to_post">Tritt dieser Gruppe bei, um Nachrichten zu senden.</string>
|
||||
|
||||
@@ -2473,9 +2473,7 @@
|
||||
<string name="relay_group_browse_popular">लोकप्रिय पुनःप्रसारक</string>
|
||||
<string name="relay_group_no_messages_yet">कोई सन्देश नहीं अब तक</string>
|
||||
<string name="channel_invite_title">%1$s में जोडा गया</string>
|
||||
<string name="channel_invite_body">%1$s ने आपको इस प्रणाली में जोडा %2$s पर। क्या इसे सन्देशों में दिखाया जाए।</string>
|
||||
<string name="channel_invite_unknown_actor">कोई व्यक्ति</string>
|
||||
<string name="channel_invite_accept">दिखाएँ</string>
|
||||
<string name="channel_invite_ignore">उपेक्षा करें</string>
|
||||
<string name="channel_invite_leave">छोडें</string>
|
||||
<string name="relay_group_join_to_post">इस समूह से जुडें सन्देशों को भेजने के लिए।</string>
|
||||
|
||||
@@ -2467,9 +2467,7 @@
|
||||
<string name="relay_group_browse_popular">Populaire relays</string>
|
||||
<string name="relay_group_no_messages_yet">Nog geen berichten</string>
|
||||
<string name="channel_invite_title">Toegevoegd aan %1$s</string>
|
||||
<string name="channel_invite_body">%1$s heeft je toegevoegd aan dit kanaal op %2$s. In Berichten tonen?</string>
|
||||
<string name="channel_invite_unknown_actor">Iemand</string>
|
||||
<string name="channel_invite_accept">Tonen</string>
|
||||
<string name="channel_invite_ignore">Negeren</string>
|
||||
<string name="channel_invite_leave">Verlaten</string>
|
||||
<string name="relay_group_join_to_post">Neem deel aan deze groep om berichten te sturen.</string>
|
||||
|
||||
@@ -2565,9 +2565,7 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest
|
||||
<string name="relay_group_browse_popular">Popularne transmitery</string>
|
||||
<string name="relay_group_no_messages_yet">Brak wiadomości</string>
|
||||
<string name="channel_invite_title">Dodano do %1$s</string>
|
||||
<string name="channel_invite_body">%1$s dodał Cię do tego kanału na %2$s. Pokazać go w wiadomościach?</string>
|
||||
<string name="channel_invite_unknown_actor">Ktoś</string>
|
||||
<string name="channel_invite_accept">Pokaż</string>
|
||||
<string name="channel_invite_ignore">Ignoruj</string>
|
||||
<string name="channel_invite_leave">Wyjdź</string>
|
||||
<string name="relay_group_join_to_post">Dołącz do tej grupy, aby wysyłać wiadomości.</string>
|
||||
|
||||
@@ -2473,9 +2473,7 @@
|
||||
<string name="relay_group_browse_popular">Relays populares</string>
|
||||
<string name="relay_group_no_messages_yet">Nenhuma mensagem ainda</string>
|
||||
<string name="channel_invite_title">Adicionado a %1$s</string>
|
||||
<string name="channel_invite_body">%1$s adicionou você a este canal em %2$s. Mostrar em Mensagens?</string>
|
||||
<string name="channel_invite_unknown_actor">Alguém</string>
|
||||
<string name="channel_invite_accept">Mostrar</string>
|
||||
<string name="channel_invite_ignore">Ignorar</string>
|
||||
<string name="channel_invite_leave">Sair</string>
|
||||
<string name="relay_group_join_to_post">Entre neste grupo para enviar mensagens.</string>
|
||||
|
||||
@@ -2544,9 +2544,7 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem</string>
|
||||
<string name="relay_group_browse_popular">Popularni releji</string>
|
||||
<string name="relay_group_no_messages_yet">Ni sporočil</string>
|
||||
<string name="channel_invite_title">Dodano v: %1$s</string>
|
||||
<string name="channel_invite_body">%1$s vas je dodal v ta kanal na %2$s. Želite to prikazati med sporočili?</string>
|
||||
<string name="channel_invite_unknown_actor">Nekdo</string>
|
||||
<string name="channel_invite_accept">Prikaži</string>
|
||||
<string name="channel_invite_ignore">Prezri</string>
|
||||
<string name="channel_invite_leave">Zapusti</string>
|
||||
<string name="relay_group_join_to_post">Pridružite se tej skupini, da boste lahko pošiljali sporočila.</string>
|
||||
|
||||
@@ -2475,9 +2475,7 @@
|
||||
<string name="relay_group_browse_popular">Populära reläer</string>
|
||||
<string name="relay_group_no_messages_yet">Inga meddelanden ännu</string>
|
||||
<string name="channel_invite_title">Tillagd i %1$s</string>
|
||||
<string name="channel_invite_body">%1$s lade till dig i den här kanalen på %2$s. Visa den i Meddelanden?</string>
|
||||
<string name="channel_invite_unknown_actor">Någon</string>
|
||||
<string name="channel_invite_accept">Visa</string>
|
||||
<string name="channel_invite_ignore">Ignorera</string>
|
||||
<string name="channel_invite_leave">Lämna</string>
|
||||
<string name="relay_group_join_to_post">Gå med i den här gruppen för att skicka meddelanden.</string>
|
||||
|
||||
@@ -2702,9 +2702,7 @@
|
||||
<string name="relay_group_browse_popular">Popular relays</string>
|
||||
<string name="relay_group_no_messages_yet">No messages yet</string>
|
||||
<string name="channel_invite_title">Added to %1$s</string>
|
||||
<string name="channel_invite_body">%1$s added you to this channel on %2$s. Show it in Messages?</string>
|
||||
<string name="channel_invite_unknown_actor">Someone</string>
|
||||
<string name="channel_invite_accept">Show</string>
|
||||
<string name="channel_invite_ignore">Ignore</string>
|
||||
<string name="channel_invite_leave">Leave</string>
|
||||
<string name="relay_group_join_to_post">Join this group to send messages.</string>
|
||||
|
||||
Reference in New Issue
Block a user