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 32df8b3bf..6bbd0af73 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 ad471ad32..a36027fe5 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 5af3e8215..1cc0947f3 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 050b829bd..901f7ca4a 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 {