Skip to content

fix(chat): polish the reaction strip and add double-tap - #898

Merged
bmc08gt merged 1 commit into
mainfrom
fix/reaction-strip-polish
Sep 28, 2026
Merged

bmc08gt merged 1 commit into
mainfrom
fix/reaction-strip-polish

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

PR 1 of the chat polish brief (reaction strip). Android's side is code-payments/code-android-app#1605.

Reaction strip

  • Fades follow the scroll position instead of a fixed CAGradientLayer mask (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.
  • Centering: each emoji is drawn into an image cropped to its ink bounds and centered in its 40pt circle, so the glyph no longer sits on the label's line box. The "+" is 40pt (was 38).
  • End padding is inset + "+" + item spacing, 52pt (was 74).

Placement

The strip always opens above the bubble; the below branch in presentReactionStrip is 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's visiblePath covers 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.backgroundMain ground. 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 offersReactionStrip presents the strip alone, with no context menu: text bubbles, cash cards, link cards, and the quote panel inside a reply. It uses UITapGestureRecognizer(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:

Constant Value
Strip height 55pt
Max width 313pt
Item size 40pt
Item spacing 4pt
Inset 8pt
"+" size 40pt
Leading fade 20pt
Trailing fade 14pt, from the middle of the "+"
Gap to bubble 16pt

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.

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.
@bmc08gt bmc08gt self-assigned this Sep 28, 2026
@bmc08gt
bmc08gt merged commit 3647a72 into main Sep 28, 2026
1 check failed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant