From 0cb9c764691bd6e6995468ea404b60e4dd77a3c9 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 8 Oct 2026 20:54:34 -0400 Subject: [PATCH] fix(chat): open the group's profile from an invite card Tapping a group invite card in a transcript opened the group's chat, skipping the profile (cover, members, balance requirements) that the card previews. A resolved card now pushes the group's profile, the way a featured group does: the chat route opens on the profile, and Open Chat there pushes the transcript. A card that is still loading or unavailable keeps opening the chat by id. A link to the chat already on screen still does nothing, and the invite-followed analytics event is unchanged. GroupLinkLookup hands the public record it fetched to FeaturedGroupsStore, so the profile draws from it instead of sitting empty through its own GetChat. The group profile has no origin parameter on Android (it reads whether a chat is underneath), so no new origin value is needed. --- .../app/messenger/internal/link/GroupLinkLookup.kt | 9 +++++++++ .../messenger/internal/screens/MessengerScreen.kt | 12 +++++++++--- .../com/flipcash/shared/chat/models/ChatAction.kt | 5 ++++- .../com/flipcash/shared/chat/ui/MessageBubble.kt | 10 ++++++++-- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/link/GroupLinkLookup.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/link/GroupLinkLookup.kt index 32df8b3bf0..6bbd0af739 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/link/GroupLinkLookup.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/link/GroupLinkLookup.kt @@ -9,6 +9,7 @@ import com.flipcash.services.controllers.ChatController import com.flipcash.services.models.chat.ChatId import com.flipcash.services.models.chat.ChatType import com.flipcash.services.models.chat.ViewMode +import com.flipcash.shared.chat.FeaturedGroupsStore import com.flipcash.shared.chat.models.LinkCard import com.getcode.solana.keys.Mint import com.getcode.util.resources.ResourceHelper @@ -25,6 +26,9 @@ import javax.inject.Inject * Anything that is not a group -- a DM id pasted into a `/chat/` link, or a chat that has gone -- * fails the lookup, and the card draws its unavailable state. * + * A resolved record is also handed to [FeaturedGroupsStore], so the profile a tap opens has + * something to draw before its own fetch answers. + * * The requirement is stated the way the chat's own head card states it, including the reserve * being named by the amount alone ("$100", not "$100 of Dollars"). An unknown mint is not a * failure: the amount is still true, so it is stated without a token beside it. @@ -33,12 +37,17 @@ internal class GroupLinkLookup @Inject constructor( private val chatController: ChatController, private val tokenCoordinator: TokenCoordinator, private val resources: ResourceHelper, + private val featuredGroups: FeaturedGroupsStore, ) { suspend operator fun invoke(chatId: ChatId): Result = runCatching { val chat = chatController.getChat(chatId, ViewMode.REDACTED).getOrThrow() if (chat.type != ChatType.GROUP) throw NotAGroup(chatId) + // A tap opens the group's profile, which draws from this record at once rather than + // sitting empty through its own GetChat -- the same placeholder a featured group gets. + featuredGroups.remember(listOf(chat)) + val balance = chat.rules.balanceRequirement() val staffOnly = chat.rules.requiresStaff() val requirement = if (balance != null || staffOnly) { diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt index ad471ad32b..a36027fe51 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt @@ -190,9 +190,15 @@ internal fun MessengerScreen(viewModel: ChatViewModel) { keyboard.hideIfVisible { viewModel.dispatchEvent( ChatViewModel.Event.OpenScreen( - // Pushed, so Back returns to this chat. The pushed screen gates - // itself: a non-member sees the join or the buy there. - AppRoute.Messaging.Chat(ChatIdentifier.ByChatId(action.chatId)) + // Pushed, so Back returns to this chat. A resolved card opens + // the group's profile, as a featured group does: Open Chat there + // pushes the transcript, which gates itself for a non-member. + // Until the card resolves there is no record to show, so it opens + // the chat. + AppRoute.Messaging.Chat( + identifier = ChatIdentifier.ByChatId(action.chatId), + openOnProfile = action.resolved, + ) ) ) } diff --git a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/models/ChatAction.kt b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/models/ChatAction.kt index 5af3e8215b..1cc0947f3b 100644 --- a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/models/ChatAction.kt +++ b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/models/ChatAction.kt @@ -35,8 +35,11 @@ sealed interface ChatAction { /** * Opens the group a link card names, pushed over this chat so Back returns here. The pushed * screen gates itself; a card never joins or buys on the reader's behalf. + * + * [resolved] is whether the card has fetched the group's public record. A resolved card opens + * the group's profile, a card still loading or unavailable opens the chat itself. */ - data class OpenGroup(val chatId: ChatId) : ChatAction + data class OpenGroup(val chatId: ChatId, val resolved: Boolean) : ChatAction /** * Opens the person a link card names: their DM, pushed over this chat so Back returns here, or diff --git a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt index 050b829bde..901f7ca4ab 100644 --- a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt +++ b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt @@ -734,7 +734,8 @@ private fun BareLinkCard( * app, wrong from inside a chat, where it swaps the transcript for the wallet on the way to a * screen the reader asked for directly. There is no wallet card here for the detail to grow out of. * So it pushes, exactly as the cash bubble's own token tap does, and back returns to the message. - * A group invite pushes the group's conversation for the same reason. + * A group invite pushes the group's profile (its conversation until the card has resolved) for + * the same reason. */ @Composable private fun rememberLinkCardClick(): (LinkCard) -> Unit { @@ -749,7 +750,12 @@ private fun rememberLinkCardClick(): (LinkCard) -> Unit { is LinkCard.TokenInfo -> actionHandler(ChatAction.ViewToken(card.mint)) // Pushed over this chat rather than through the chat deep link, which replaces the // stack: Back has to return to the message that held the invite. - is LinkCard.GroupInvite -> actionHandler(ChatAction.OpenGroup(card.chatId)) + is LinkCard.GroupInvite -> actionHandler( + ChatAction.OpenGroup( + chatId = card.chatId, + resolved = card.state is LinkCard.GroupInvite.State.Resolved, + ) + ) // Only a resolved card takes a tap. Which screen it opens -- the person's DM, your own // tip card, or nothing for the person already on the other end -- is the chat's call. is LinkCard.User -> (card.state as? LinkCard.User.State.Resolved)?.let {