Repository navigation
feat(messenger): redesign the group invite flow - #1575
Merged
Merged
Conversation
bmc08gt
force-pushed
the
feat/group-invite-sheet-redesign
branch
2 times, most recently
from
September 27, 2026 03:07
a2d84f0 to
b9edbeb
Compare
A text bubble takes its corners from bubbleShape(position, isFromSelf), with the small corner on the sender's side where it meets a neighbour. Link cards ignored that: LinkCardView used CodeTheme.shapes.medium, BareLinkCard passed no position, and groupsWith broke the run on any card row. An invite, a card and then the note sent with it, read as two unrelated messages. LinkCardView now takes a shape, and BareLinkCard hands it bubbleShape(position, isFromSelf). Each kind cuts its pieces from that outline: the cash voucher's top half and stub, the ghost stub's drawn path, TokenCard's bill, and the invite card's frame. groupsWith breaks a run only on a bare emoji now, since a card has an edge to square against. GroupInviteLinkCard becomes public and takes its button label, so the group's empty state can draw it too. The transcript card says "View" instead of "Start Chatting". LinkCardShapeTest renders each card kind at every position and checks its outline against bubbleShape; BubblePositionTest covers a card joining the run on either side.
An empty group showed ChatInfoCard with an "Invite People To Join" button under it. Node 10330:19164 replaces both with the group-invite card a DM transcript renders for the link, labelled "Invite People". MessageList draws GroupInviteLinkCard in the header slot when the group has no messages and the viewer has an invite URL. The CTA sends ChatAction.InviteToGroup, as the old button did. ChatSubject.Group builds the card from the chat's own record (title, picture, member count, rules), so it needs no link lookup. The card sits at 72% of the row, the frame's 290 of 402. Every other chat still gets ChatInfoCard.
The invite sheet only offered Share and Copy. Nodes 10329:12104 and 10330:19549 add a multi-select list of the viewer's 1:1 chats and a message bar, so an invite can go straight into existing conversations. GroupInviteSheet is titled "Invite People" and draws Share and Copy Invite Link with the profile screen's ProfileShortcut, now internal. "Recent Chats" lists ChatCoordinator.feed(CONTACT_DM, TIP_DM). The "Add a message" field and white Invite button appear once a chat is picked. GroupInviteViewModel keeps the selection in tap order. Invite sends the link to each picked chat, then the trimmed message as a second message if it isn't blank. Chats are sent to concurrently and one failing does not stop the others; a failed send leaves its usual retryable row in that transcript. A chat whose link failed does not get the message, so a retry cannot put the card after its note. Once every send answers, the sheet dismisses and pushes the chat picked first. GroupInviteViewModelTest covers the bar's visibility, the link-then- message order, a blank message, landing on the first-picked chat, and failure isolation.
On a Seeker, picking a chat in the invite sheet showed the message bar mostly below the screen edge, and the Copy tile read "Copy Invite L…". InviteToGroup was a WrapContentSheet. A wrap-content body is measured at its full height and then clipped, so a long chat list pushed the bar under the gesture area. As a full sheet the body is bounded: the list scrolls and the bar stays above it. ProfileShortcut fixed its width at staticGrid.x20, too narrow for "Copy Invite Link" on one line. It now takes that as a minimum, so the profile's one-word labels sit where they did and the longer label widens its tile.
The invite sheet's message bar drew TextInput's own rounded, bordered field inside the pill, with the hint flush against that field's edge. Node 10329:11963 has one 14dp pill: a bare "Add a message" hint inset 16dp at 40% white, and a 34dp white Invite button inset 8dp. The field now takes transparent background and border colours and the button's height, so the hint and the button label share a baseline, and the pill carries the node's padding and corner radius.
…poser
The Recent Chats list followed the live DM feed, so a chat receiving a
message while the sheet was open jumped to the top and moved rows under
the viewer's finger. The view model now reads the feed once when the
sheet opens.
Rows get the dividers from node 10330:19585, inset to start at the name,
and the header is ruled off below its label.
The message bar is now the chat screen's ChatInput. A new
ChatInputSubmit.Action shows a labelled button ("Invite") whether or not
anything is typed, since the link goes out with or without a message.
The list scrolls under the composer and blurs progressively behind it,
as in node 10330:19709.
The sheet only listed 1:1 chats, so a group link could not be passed to another group without leaving the sheet to share it. The feed now includes GROUP chats, mixed with DMs in the feed's own order. The group being invited to is left out of its own list: the sheet names it through GroupInviteViewModel.inviteTo, which also stops it from being picked. Group rows already take their title and picture from toConversationReference.
The card's padding and gaps came from the theme's inset and grid, which grow on larger phones, so the card drew taller than iOS's GroupCardView. It now uses iOS's fixed values: 12dp side and bottom padding, 13dp above the title and 11dp above each requirement line.
… list This ports the iOS sheet (code-payments/code-ios-app#873, node 10330:19387). The sheet no longer pins a header: the title bar, with a glass close button, and the message composer float over one list that scrolls under both. - "Recent Chats" is an ordinary list row with a divider under it. - Share and Copy are their own tiles per node 10330:19400: two equal 95dp tiles at 5% white with wrapping labels. Copy turns into "Copied" with a check. ProfileShortcut goes back to private and its fixed width. - The composer appears once a chat is picked and slides up on a spring rather than fading, since a fading glass field shows the list through it. It has no outline, and ChatInputSubmit.Action gains a busy flag that swaps the Invite label for a spinner while sending. - Both edges fade to the sheet's surface colour, the same scrim the Chats list draws under its title bar, instead of blurring. The bottom fade is drawn whether or not the composer is up, so it holds still while the composer slides through it.
ChatInput clips to its rounded shape and then applies the caller's modifier, so the padding the sheet passed in put the rounded clip on the padded box and left the blur square inside it. The square corners showed past the pill. The inset now sits on a wrapper. The pill also drew ChatInput's 68% fill over the haze tint, which made it opaque. It now takes one fill, node 10329:11963's 46% #1E1E1E, over the blur. ChatInput gains a containerColor parameter for callers that draw their own glass; its default is the old fill, so the chat screen is unchanged.
…'s top A fling that reached the top of Recent Chats carried on into the bottom sheet and dismissed it. The list now uses sheetResignmentBehavior, as the other scrolling sheets do, so leftover momentum stops at the top. A fresh drag down from the top still closes the sheet.
The sheet read CONTACT_DM alongside TIP_DM and GROUP. Contact DMs have their own inbox, so a person with both kinds of DM appeared twice, and contact rows without a name showed blank. The sheet now reads chatListFeed(), the same TIP_DM and GROUP set the Chats screen shows.
The invite sheet closed after sending but never opened the chat picked first. It closed itself and then dispatched the navigation through the conversation's event collector, which is only composed while the transcript is on screen. Opened from the group's profile, nothing collected the event, so it was dropped. InviteToGroup now returns a GroupInviteResult with that chat's id, and openGroupInvite() navigates from the result callback of whichever screen opened the sheet: the transcript or the group's profile. The keyboard is hidden before the sheet returns so the screen behind doesn't shift.
The invite card only replaced the group's info card while the transcript was empty, so a group whose only member had posted anything fell back to the old header. iOS decides on who is in the chat instead: the card heads the transcript while the viewer is a member and nobody else has joined (ConversationScreen.showsGroupInvite). State.showsGroupInviteCard now carries that rule and MessageList reads it. Once someone joins, the header goes back to the info card and the invite stays on the group's profile.
Link cards and the solo group's head card came out about 30dp narrower than iOS's on the same screen. iOS takes 78% of the transcript's full width for both (ChatViewController.maxBubbleWidthFraction). Android took 78% of the row after its side insets for link cards, and 72% for the head card. transcriptCardWidth() adds the insets back before applying the fraction, and both the card rows in ContentBubble and the head card in MessageList use it. Text and cash bubbles keep their existing widths.
bmc08gt
force-pushed
the
feat/group-invite-sheet-redesign
branch
from
September 28, 2026 02:38
12c0308 to
afaa5fa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the group invite flow redesign (Figma section node 10417:20555) in three commits.
Link cards follow the bubble corner rules
Link cards drew with a fixed
CodeTheme.shapes.medium, andgroupsWithbroke the run on any card row, so an invite (a card, then the note sent with it) read as two unrelated messages.LinkCardViewtakes ashape.BareLinkCardpassesbubbleShape(position, isFromSelf), so a card squares its corners against its neighbours like a text bubble does.TokenCard's bill (newshapeparameter), and the invite card's frame.groupsWithnow breaks a run only on a bare emoji.LinkCardShapeTestrenders each card kind at Solo, First, Middle and Last, from both senders, and checks the outline againstbubbleShape.BubblePositionTestcovers a card joining a run on either side.Empty group card (node 10330:19164)
An empty group showed
ChatInfoCardwith an "Invite People To Join" button under it. It now showsGroupInviteLinkCardin the header slot, at 72% of the row width, with an "Invite People" CTA that opens the invite sheet.GroupInviteLinkCardtakes its CTA label as a parameter; in a DM transcript it says "View" (was "Start Chatting") and opens the group. The empty-state card is built from the chat's own record, so it needs no link lookup.Invite sheet (nodes 10329:12104, 10330:19549)
ProfileShortcut(nowinternal).ChatCoordinator.feed(CONTACT_DM, TIP_DM)as a multi-select.GroupInviteViewModelkeeps the selection in tap order. Invite sends the link to each chat, then the trimmed message as a separate message when it isn't blank. Chats send concurrently, and one failing doesn't stop the others. A failed send leaves the usual retryable row in that chat's transcript. A chat whose link failed doesn't get the message, so a retry can't put the card after its note. Once every send has answered, the sheet dismisses and opens the chat picked first.GroupInviteViewModelTestcovers the bar's visibility, link-then-message order, a blank message, landing on the first-picked chat, and failure isolation.