Repository navigation
feat(chat): pin messages in DMs and groups - #1898
Merged
Merged
Conversation
#1884 parsed ViewerState.Permissions.can_pin but never saved it, so every chat read it back as false. Add chat_metadata.can_pin (DB v51) and re-arm viewer_state_version on upgrade, as can_edit and can_invite did.
Pinned messages open through the same path as readMessage: the stored copy when there is one, else the wire copy opened for the chat's view mode. They are not written into the transcript. MessageCapability.Pin is granted from can_pin and refused on system messages, tombstones and payment requests.
bmc08gt
force-pushed
the
feat/chat-pins-ui
branch
3 times, most recently
from
October 30, 2026 19:11
b08b685 to
efee5e7
Compare
bmc08gt
force-pushed
the
feat/chat-pins-ui
branch
from
October 30, 2026 19:20
efee5e7 to
8405f18
Compare
…trip The pin bar draws the pinned message with the reply strip's quote, now shared from chat-ui as ChatQuoteContent, without the strip's 8dp text padding and with a 10dp rule gap, as iOS's PinBar does. Its edges line up with the back button and the top bar's pin button, and it slides down from the top as it fades in. With more than one pin, the author rule splits into segments stacked bottom up from the latest, the shown pin lit. A 34dp window holds two segments; past two, the track scrolls to keep the lit one in view. The bar's list button and the top-bar pin action use the list-and-pin glyph. The pinned list is a ChatStep.PinnedMessages sheet destination. It takes the chat id and, after a refused pin, the message waiting for a slot. Rows come from the shared ChatViewModel's pins, and a row tap returns PinnedMessageJump through navigateForResult. The title stays put while rows scroll under it, with a progressive blur at the top and bottom edges, and unpinned rows animate out. In a row, only the bubble and Unpin take a tap. The bubble claims the down on the Initial pass, so its own links and buttons never fire in the sheet, and it exposes one click action labelled "Jump to message". Unpin keeps a 48dp target laid out below the bubble. PinnedMessageRowTest asserts those two actions and drives real taps on the name, meta line and the bubble's link. Swiping the request strip up docks it into the pill. The top-bar pin button leaves the composition on the first collapsing frame, because AnimatedVisibility kept it laid out beside the pill's slot for three frames even with ExitTransition.None. The top-bar pin button sits in the app bar's action slot, so it is as far from the right edge as back is from the left (45px each on the Seeker), and it fades and scales in. The pinned list's header is the reactors sheet's: AppBarWithTitle with a centred title and AppBarDefaults.Close at the end, which pops the sheet in every state. The close is drawn at 32dp, so expandedTouchTarget grows its touch area to 48dp; PinnedMessagesSheetTest taps 23dp out from its centre in the normal, read-only and full lists.
bmc08gt
force-pushed
the
feat/chat-pins-ui
branch
from
October 30, 2026 19:30
8405f18 to
c547035
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.
Chat message pinning on Android, DMs and groups, built against the pinning design note (orchestrator #81) and the chat-pinning frames. Stacked on #1897, which stores
can_pin; without it Pin never shows.State (
:apps:flipcash:shared:chat)MessagingOperationsgainsgetPinnedMessages,pinMessageandunpinMessage. A pinned message comes from its stored transcript copy when there is one, else the wire copy is opened the wayreadMessageopens it, so E2EE pins decrypt on the same path. Pins are not written into the messages table, so the transcript stays contiguous.MessageCapability.Pinis granted fromcan_pinand never on system messages, tombstones, unconfirmed messages or payment requests. Requests already stay at the top through the strip and pill.Messenger (
:apps:flipcash:features:messenger)ChatPinsholds the lastGetPinnedMessagesread and the summary version it was read at. A newer storedPinSummary.version(advanced byPinsChangedor by the viewer's own pin/unpin) triggers a fresh read. A feed or list write withoutpin_summarynever clears pins.pinnedIdsbut are skipped by the bar, its "N of M" count and the list.chat-uiasChatQuoteContent. There's no title, pin icon or dismiss. With more than one pin the author rule splits into segments, stacked bottom up from the latest, and the shown pin's segment is lit. The window is 34dp and fits two 16dp segments with a 2dp gap; past two pins the track scrolls to keep the lit one in view (PinSegments, Figma Cycling variant node 13016:87). A tap jumps to the pin it shows and steps to the next older one. A jumpresolveJumpcan't reach shows a toast. TalkBack reads "Pinned message from : ".ic_list_bullet_pin, converted path for path fromlist-bullet-pin.svg(node 13104:84664).pinSlotputs pins in the top bar's trailing slot while the request strip is out, and back under the top bar once the strip rests as the pill. The strip and pill exist only in groups, so a DM always gets the bar.DeckHostState's existing collapse and settles at halfway. A downward drag still cycles requests (Android's strip cycles vertically, not horizontally as on iOS). The dock holds until a new owed request arrives, whichDeckRest.collapsed()already did. The top-bar pin button leaves the composition on the first collapsing frame: the pill takes the trailing slot at full width straight away, andAnimatedVisibilitywithExitTransition.Nonestill kept the button laid out for three frames, which truncated the title.ChatStep.PinnedMessages(chatId, pinAfterUnpin), aHalfSheeton the app'sModalBottomSheetSceneStrategylike Reactors. Its rows come from the flow-sharedChatViewModel's pins, not the route. A row tap returnsPinnedMessageJumpthroughnavigateForResultand the chat jumps; back, predictive back and a scrim tap return Canceled. It lists pins newest first. Each row draws the transcript'sContentBubblewith the sender's avatar and name, then "Pinned by … · date", and Unpin whencan_pin. OnTOO_MANY_PINSthe list opens with a header asking to unpin one, and the waiting message is pinned once an unpin succeeds. The copy doesn't state the cap. The title stays put while rows scroll under it, with a progressive blur at the top and bottom edges. In a row, only the bubble and Unpin take a tap: the bubble claims the press before its own links or card buttons see it and exposes one click action, "Jump to message"; Unpin keeps a 48dp target below the bubble.PinnedMessageRowTestasserts the two actions and drives real taps on the name, meta line and the bubble's link. The header is the reactors sheet's: a centred title withAppBarDefaults.Closeat the end, which pops the sheet in every state; its 32dp button gets a 48dp touch area throughexpandedTouchTarget, whichPinnedMessagesSheetTestchecks with taps 23dp from its centre.Tests
ChatPinsTest: version handling, a missing summary never clearing pins, tombstone skipping including stored-copy deletes, bar stepping, the C6/C6b/C7 slot rule, and when the list is done (last visible pin gone, or the waiting message pinned).DeckDockTest: dock drag, release past and short of halfway, and the button gone mid-drag.MessageCapabilityTest: the Pin gate, including system, tombstone and payment-request hides.getPinnedMessages; their relaxed coordinator mocks can't return aResult.Design vs app tokens
CodeThemetokens (dimens.grid,colors.divider, the chat glass material), not the Figma values, so the bar's height and padding follow the app's grid rather than the frame's exact points.Frames on the emulator (
flipcash_blockstore, screen recordings stepped frame by frame, off-centre taps)dimens.grid, not the frame's points