Repository navigation
fix(chat): polish the reaction strip and add double-tap - #898
Merged
Merged
Conversation
The strip's fixed gradient mask faded both ends at rest, so the first emoji looked disabled before anything had scrolled. The fades now follow the scroll position: the leading one runs 20pt while the row can scroll back, and the trailing one runs 14pt from the middle of the "+" while it can scroll forward, with the row hidden past it. Both grow in over the first 20pt of travel and mirror in RTL. Emoji are centered on their ink bounds rather than the label's line box, the "+" is 40pt, and the end padding is inset + "+" + spacing (52pt, was 74pt). The strip always opens above the bubble. The lift hands UIKit a preview 55 + 16pt taller than the bubble, so UIKit keeps room for the strip on screen and places the menu clear of it; a bubble at the top of the transcript moves down instead of the strip flipping below. Double-tapping a text bubble that can take a reaction presents the strip alone, with no menu. Link cards, cash cards, quote panels and reaction pills keep their instant single tap.
This was referenced Sep 29, 2026
bmc08gt
added a commit
that referenced
this pull request
Sep 29, 2026
* fix(chat): keep the long-press menu off its neighbours Long-pressing any bubble slid the group's invite card at the top of the transcript down over its own reaction pills, and it stayed there until the next scroll. The menu froze the transcript's inset by switching `contentInsetAdjustmentBehavior` to `.never`, which changes the safe area UIKit hands down to the rows; the SwiftUI-hosted card under the nav bar re-laid itself out for it. The freeze now holds the bottom inset at its keyboard-up value and leaves the mode at `.always`. The shift predates #898: it has been there since the freeze landed, and only showed once pills sat under a hosted card. The strip now takes whichever side of the bubble the menu didn't. UIKit picks the menu's side, and a bubble near the bottom got the menu and strip both above it. The taller lift from #898 now applies only to bubbles too close to the top for the strip to fit above; elsewhere it left an empty band between the bubble and a menu placed above it. The lift's copy fades out as the row's bubble reappears. UIKit keeps the copy above the transcript for about 0.4s after it lands, so a bubble partly under the bar's soft edge stayed sharp until it went. * docs(plans): plan a custom long-press menu for chat UIKit's context menu can't slide only the pressed bubble to a spot with the strip above and the menu below. This records what the device investigation ruled out and the shape of an overlay that can.
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.
PR 1 of the chat polish brief (reaction strip). Android's side is code-payments/code-android-app#1605.
Reaction strip
CAGradientLayermask (8pt leading, 47pt trailing), which faded the first emoji at rest. The leading fade runs 20pt in from the edge, only while the row can scroll back. The trailing fade runs from the middle of the "+" to 14pt past it, only while the row can scroll forward, and the row is hidden from there to the strip's end. Both grow in over the first 20pt of scroll and mirror in RTL.Placement
The strip always opens above the bubble; the below branch in
presentReactionStripis gone. For a row that offers the strip,preview(for:raising:)returns a clear box 55 + 16pt taller than the bubble with a raised copy of the bubble at its bottom, and the preview'svisiblePathcovers that headroom. UIKit keeps the whole box on screen and places the menu clear of it, so a bubble at the top of the transcript moves down far enough for the strip.liftedBubbleFrame()finds where UIKit put the box, since the lift is drawn through a portal and the box's own frame stays at its origin.The group invite card now draws its own
Color.backgroundMainground. It used to borrow the transcript's, so its lifted copy went black over the menu's dim.Double-tap
Double-tapping any bubble with
offersReactionStrippresents the strip alone, with no context menu: text bubbles, cash cards, link cards, and the quote panel inside a reply. It usesUITapGestureRecognizer(numberOfTapsRequired: 2), and the double tap only receives touches on bubbles that can take it, so a tap elsewhere doesn't wait out the timeout. The strip-only path (beginStripLift/endStripLift) holds the transcript the same way the context menu does.On cards and quote panels, the single tap now waits out the double-tap timeout before it opens the cash detail, the link, or jumps to the quoted message. The cash card's tap moved off collection view selection onto its own recognizer, because a selection can't require another recognizer to fail. The group card's "View" button keeps its instant tap and takes no double tap; a double tap on the card around it presents the strip. Reaction pills keep their instant tap.
This matches Android's #1605, which changed A5's "link and cash cards keep their instant tap".
"+" frost and haptics
The emoji scroll on under the "+", which frosts them. Below iOS 26 that's a dark blur tinted toward the strip surface (#303030 at 0.6) under the white 0.18 fill; the iOS 26 glass button frosts on its own. A pill's long-press, which opens the reactors, plays a haptic. The strip's emoji and "+", and the pill row's "N more" and "+", do nothing on long-press and stay silent.
Constants
Identical to Android's #1605:
The six default emoji don't fit in 313pt: they need 8 + 6×40 + 5×4 + 4 + 40 + 8 = 320pt. At rest all six are fully opaque, but the sixth sits flush against the "+". The width is unchanged here, as on Android; this needs a design call.