diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt index fcd6310c2b..1555ab0bc6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt @@ -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 }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/ChannelInvitesSection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/ChannelInvitesSection.kt index caf6d6c1bb..68c1ab9b9e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/ChannelInvitesSection.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/ChannelInvitesSection.kt @@ -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) } } - } - } + }, + ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt index 627984e45c..d2882fe543 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationScreen.kt @@ -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) }, ) } diff --git a/amethyst/src/main/res/values-cs/strings.xml b/amethyst/src/main/res/values-cs/strings.xml index 9f2709970e..21594a1f84 100644 --- a/amethyst/src/main/res/values-cs/strings.xml +++ b/amethyst/src/main/res/values-cs/strings.xml @@ -2563,9 +2563,7 @@ Oblíbené relaye Zatím žádné zprávy Přidáno do %1$s - %1$s vás přidal do tohoto kanálu na %2$s. Zobrazit ho ve Zprávách? Někdo - Zobrazit Ignorovat Opustit Připojte se k této skupině pro odesílání zpráv. diff --git a/amethyst/src/main/res/values-de-rDE/strings.xml b/amethyst/src/main/res/values-de-rDE/strings.xml index 7f06367211..4435dd9629 100644 --- a/amethyst/src/main/res/values-de-rDE/strings.xml +++ b/amethyst/src/main/res/values-de-rDE/strings.xml @@ -2472,9 +2472,7 @@ Beliebte Relays Noch keine Nachrichten Zu %1$s hinzugefügt - %1$s hat dich auf %2$s zu diesem Kanal hinzugefügt. In Nachrichten anzeigen? Jemand - Anzeigen Ignorieren Verlassen Tritt dieser Gruppe bei, um Nachrichten zu senden. diff --git a/amethyst/src/main/res/values-hi-rIN/strings.xml b/amethyst/src/main/res/values-hi-rIN/strings.xml index 43264dc3d3..d6a4b6334f 100644 --- a/amethyst/src/main/res/values-hi-rIN/strings.xml +++ b/amethyst/src/main/res/values-hi-rIN/strings.xml @@ -2473,9 +2473,7 @@ लोकप्रिय पुनःप्रसारक कोई सन्देश नहीं अब तक %1$s में जोडा गया - %1$s ने आपको इस प्रणाली में जोडा %2$s पर। क्या इसे सन्देशों में दिखाया जाए। कोई व्यक्ति - दिखाएँ उपेक्षा करें छोडें इस समूह से जुडें सन्देशों को भेजने के लिए। diff --git a/amethyst/src/main/res/values-nl-rNL/strings.xml b/amethyst/src/main/res/values-nl-rNL/strings.xml index 389eae2ccd..22a28884e1 100644 --- a/amethyst/src/main/res/values-nl-rNL/strings.xml +++ b/amethyst/src/main/res/values-nl-rNL/strings.xml @@ -2467,9 +2467,7 @@ Populaire relays Nog geen berichten Toegevoegd aan %1$s - %1$s heeft je toegevoegd aan dit kanaal op %2$s. In Berichten tonen? Iemand - Tonen Negeren Verlaten Neem deel aan deze groep om berichten te sturen. diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml index 50ee2469df..98950f5e02 100644 --- a/amethyst/src/main/res/values-pl-rPL/strings.xml +++ b/amethyst/src/main/res/values-pl-rPL/strings.xml @@ -2565,9 +2565,7 @@ Zaplanowane posty z innych kont nie zostaną opublikowane, dopóki to konto jest Popularne transmitery Brak wiadomości Dodano do %1$s - %1$s dodał Cię do tego kanału na %2$s. Pokazać go w wiadomościach? Ktoś - Pokaż Ignoruj Wyjdź Dołącz do tej grupy, aby wysyłać wiadomości. diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index c2c6fa8353..26efddbc9d 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -2473,9 +2473,7 @@ Relays populares Nenhuma mensagem ainda Adicionado a %1$s - %1$s adicionou você a este canal em %2$s. Mostrar em Mensagens? Alguém - Mostrar Ignorar Sair Entre neste grupo para enviar mensagens. diff --git a/amethyst/src/main/res/values-sl-rSI/strings.xml b/amethyst/src/main/res/values-sl-rSI/strings.xml index 5265d4ba5a..23c0485bed 100644 --- a/amethyst/src/main/res/values-sl-rSI/strings.xml +++ b/amethyst/src/main/res/values-sl-rSI/strings.xml @@ -2544,9 +2544,7 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem Popularni releji Ni sporočil Dodano v: %1$s - %1$s vas je dodal v ta kanal na %2$s. Želite to prikazati med sporočili? Nekdo - Prikaži Prezri Zapusti Pridružite se tej skupini, da boste lahko pošiljali sporočila. diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml index 6f5ee3cbd0..0adb501ce0 100644 --- a/amethyst/src/main/res/values-sv-rSE/strings.xml +++ b/amethyst/src/main/res/values-sv-rSE/strings.xml @@ -2475,9 +2475,7 @@ Populära reläer Inga meddelanden ännu Tillagd i %1$s - %1$s lade till dig i den här kanalen på %2$s. Visa den i Meddelanden? Någon - Visa Ignorera Lämna Gå med i den här gruppen för att skicka meddelanden. diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 53b93fb4a1..d5cf87e6a4 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -2702,9 +2702,7 @@ Popular relays No messages yet Added to %1$s - %1$s added you to this channel on %2$s. Show it in Messages? Someone - Show Ignore Leave Join this group to send messages.