Repository navigation
feat(messenger): host surfaces for group invite links - #1869
Merged
Merged
Conversation
bmc08gt
force-pushed
the
feat/group-invites-host
branch
from
October 28, 2026 16:38
c1decea to
2f0e8d1
Compare
bmc08gt
force-pushed
the
feat/group-invites-host
branch
from
October 28, 2026 17:32
2f0e8d1 to
6c6073b
Compare
bmc08gt
changed the base branch from
code/cash
to
feat/group-invites-key-bundle
October 28, 2026 17:32
bmc08gt
force-pushed
the
feat/group-invites-host
branch
6 times, most recently
from
October 29, 2026 00:18
4f04b04 to
1098f77
Compare
HostInvites holds a chat's invites with each link rebuilt from its secret_envelope, so the invite sheet and Manage Invites agree on which link is Current: the newest ACTIVE invite the viewer made that this device can open. Create keeps one secret per attempt until the server answers, so a retry after a dropped response resends the same token. An OK that comes back non-ACTIVE drops the secret and tries once more; a refused expires_at retries once at the next shorter preset. DENIED maps to NotAvailable and is not retried (CreateInvite is staff-only for now).
The local store drops can_invite, so a chat read from disk always says the viewer can't invite. HostInvites keeps the value GetChat last returned for each chat it checked, and a DENIED from GetChatInvites or RevokeInvite clears it. Logout clears it with the invites.
A private group's invite link carries its secret in the fragment, so Shareable.GroupInvite no longer prints the url.
Adds HostInvites.currentOrCreate, which returns the current link or creates one with the default 7-day, no-limit settings.
A destructive choice, such as Revoke Link, passes the error text colour.
Adds the host's three-way invite sheet, the invite variant of the share-to-chats sheet (create on first tap, Recent Chats limited to private groups and encrypted DMs), Manage Invites with its row sheet, revoke and new-link sheets, and the Invite Links menu item. Join Requests' empty-state button reads Share Invite Link for a viewer who can invite.
Covers create on first tap, each CreateInvite error, the current-link choice, revoke DENIED, the undecryptable row and the encrypted-only Recent Chats. Adds a preview for every frame state and a Robolectric test that renders them to PNGs.
The invite variant read Share Invite Link while the join variant read Invite People. Both now use title_invitePeople. The send button already reads Invite on both variants and is unchanged.
Who can join offered No limit, 1, 10 and 50, which is the Figma frame. The spec lists 1, 5, 10, 25 and No limit; the chips now follow it, and No limit stays the default. Also drops title_shareInviteLink, unused since the title change.
The spec (group-invites.md, Surfaces 1 step 4) retries INVALID_EXPIRES_AT once at the next shorter preset and only then shows the generic error. Restores the behaviour db579ca removed.
…lear load, attemptCreate and revoke no longer turn a cancelled caller into a failure result. currentOrCreate re-checks the current link under the create lock so two waiting callers make one link. clear() bumps an epoch, so an in-flight load or create for the old account cannot write into the next one's state, and the pending-secret map is concurrent. Adds HostInvites tests for unopenable envelopes, reuse, duplicate ids, clear between retries, serialized creates, load and revoke failures, the lost-access truth table and cancellation.
Adds setSensitiveText, which sets EXTRA_IS_SENSITIVE on the clip so Android hides the link from the clipboard preview. setText is unchanged for other callers.
The sheet now opens in the creating state and creates at once when the host has no current link; a specific invite from Manage Invites never creates. Share, Copy and Send tiles stay disabled until the link is ready. Too Many Active Links offers Manage Invites and Cancel, errors close the sheet afterwards, and a lost invite permission closes it as Manage Invites does. Manage Invites shows a spinner on first load and a Retry on failure, inactive rows show status and dates only, an unknown status reads as unavailable, and the use-limit chips are radio buttons at least 48dp tall.
…age Invites InviteLinkViewModel: creating to ready, each error mapping, sensitive clipboard copy, send, re-open, join-link and Manage Invites offers, denied at open. ManageInvitesViewModel: each create result, double-tap guard, revoke failure, retry. Also restrictToEncrypted, the profile menu truth table, no card tile on an invite link, and teardown clearing HostInvites.
…scene The Invite Link row sheet on Manage Invites was a raw Material3 ModalBottomSheet, which added a drag handle and top spacing the other choice sheets (Mute Chat, Paid Reactions) do not have. Make it a ChatStep.InviteLinkActions marked WrapContentSheet so it renders on the same scene. The step carries only the invite id; the sheet looks the invite up in the host's loaded invites. It returns Share or Revoke to Manage Invites, which opens the share sheet or runs the existing revoke confirm.
The Time to Pay sheet's preset chips slide one white highlight to the chosen chip and fade it out for a custom date. New Invite Link needs the same row, so the chips move to ui:components as SlidingChipRow, with the same colours, padding, caption style and spring. The chips now carry the radio-button role in a selectable group, where they were buttons with a selected state. Nothing on screen changes.
Matches iOS's SlidingChipPicker: chips weight(1f) across the full width, 6dp apart, with 6dp side padding and centred labels. The row no longer scrolls. Time To Pay's presets change with it. A label too wide for its chip shrinks by up to 20%, like iOS's minimumScaleFactor(0.8), then wraps at the space. Every chip takes the height of the tallest. TextAutoSize can't do this alone: with two lines allowed it wraps before it shrinks, so the row measures each label and picks the size. At font scale 2, "Weeks" at 80% is wider than a fifth of the row, so Time To Pay's "2 Weeks" and "4 Weeks" break inside the word; in a 320dp row "1 Week" does too. They stay whole on screen.
New Invite Link on Manage Invites was a raw Material3 ModalBottomSheet, so it showed a drag handle and a left-aligned title with no close button, unlike Mute Chat and the invite link row sheet. Make it ChatStep.NewInviteLink, a WrapContentSheet with the same AppBarWithTitle header (centred title, Close). It returns the chosen expiry and use limit as a NewInviteLinkChoice, which holds preset names only, and closes. Manage Invites creates the link from the result; the New Link button (and the empty-state button) show a loading state while ManageInvitesViewModel.creating is set. Error handling is unchanged. The Created effect is gone, since there is no sheet left to close.
Only the chip row: the sheet is a ModalBottomSheet, whose window the view-drawing harness doesn't capture. At font scale 2 each chip shows only its number, so 1 Day and 1 Week read the same.
bmc08gt
force-pushed
the
feat/group-invites-host
branch
from
October 29, 2026 04:05
1098f77 to
b1f9b3e
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.
Adds the host-side surfaces for private-group invite links.
can_invite, built on the existing option-sheet style. Members withoutcan_invitekeep today's sheet. The Join Requests button reads "Share Invite Link" for hosts./chat/invite#s=…pill, an "Expires … · N joined" line, Share Link and Copy Link, Recent Chats limited to private groups and E2EE DMs, and the footnote. The join variant is unchanged. Nothing is created when the sheet opens; the first Share or Copy tap creates a 7-day, no-limit link, reusing one secret across retries. DENIED shows 1e, TOO_MANY_INVITES shows 1c, anything else a generic "Couldn't Create Link".GetChatInvitesto the end, splits Active and Inactive, hides inactive invites older than 30 days, marks the current link, and shows "Can't share from this device" with Revoke only when a secret envelope won't open. The row sheet offers Share Link and Revoke Link; new links take the 1 day/7 days/30 days/Never and No limit/1/10/50 presets as drawn.toString. Orchestration lives inHostInvitesand small screen ViewModels, notChatViewModel.Stacks on #1868 (the key bundle and the Room
can_invitecolumn); this PR's base is that branch, so the diff shows only the host side.