Skip to content

feat(chat): float the message bar over a fade and retune chat motion - #1617

Merged
bmc08gt merged 6 commits into
code/cashfrom
feat/chat-bar-fade-motion
Sep 29, 2026
Merged

bmc08gt merged 6 commits into
code/cashfrom
feat/chat-bar-fade-motion

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Ports code-ios-app#907 and code-ios-app#908. The one piece of #908 not here is the typing dots moving into the transcript so the incoming bubble grows out of them; that is a separate change, because our dots are drawn in the message bar.

Message bar (#907)

  • The bar has no background of its own and sits on a curved fade that reaches 0.9 opacity.
  • Keyboard down: grid.x6 side margin, and the bar sinks grid.x2 into the navigation bar area. Keyboard up: normal margin, and the newest message sits grid.x2 closer to the bar.
  • Width, drop and transcript spacing all follow the keyboard's open fraction, so the bar resizes with the keyboard rather than after it. This replaces iOS's (0.22, 0.14) spring. The drop only happens once the keyboard has cleared the navigation bar, so the keyboard doesn't cover the bar on dismiss.

Motion and color values (#908)

  • insertion spring is now (0.27s, bounce 0.15). New rows start at 0.9 scale from the sender's bottom corner and rise 35% of their height plus the gap below them.
  • The receipt line fades out in 80ms while its row closes on a no-bounce 0.35s spring. The reaction reflow uses the same spring.
  • Bubble fills: outgoing 0.10 white, incoming 0.05.
  • Swipe-to-reply arrow starts at 0.35 scale.

$ button
The button stays "$" whenever a message field sits next to it. It was switching to "Send $" in contact DMs whenever the field was empty.

Transcript push
When the transcript is resting on the newest message, a new message scrolls it up by the new row's height on the insertion stiffness, so older rows ride up while the new row rises into its slot. Before, our own messages jumped in one frame and incoming ones used animateScrollToItem's curve. animateItem can't do this, because the move is a scroll and a scroll jump resets placement animations. The scroll has no bounce: the list can't go past its newest message, so an overshoot would be clipped.

Receipt handoff (#908)
The newest send holds the receipt on the row above until it is confirmed and 700ms have passed, then the line moves down in one step as the old one fades out. Before, the old line dropped at confirmation and the new one appeared 700ms later. A failed send still shows "Not Delivered" straight away. The rules are covered in ReceiptHandoffTest.

The new line grows on the same no-bounce spring the old one shrinks on, and the row gap animates on it too, so the two rows trade height and the transcript doesn't jump. A row composed with its line already showing (opening a chat, scrolling back) doesn't animate it.

Typing linger, Read/Delivered, receipt grouping (#908)

  • Typing dots stay 800ms after the other person stops; their message clears them straight away.
  • Read stays on the newest message they've read while a newer one shows Delivered.
  • A bubble carrying a status line keeps its round corners and the normal gap until the line moves on.

Ports code-ios-app#907 and the tuning values from code-ios-app#908.

The message bar drops its own background and sits on a fade that
reaches 0.9 opacity. With the keyboard down it narrows to a grid.x6
side margin and sinks grid.x2 into the navigation bar area; both follow
the keyboard's open fraction, so the bar resizes with the keyboard
instead of after it. The transcript moves grid.x2 closer to the bar
while the keyboard is up, on the same fraction.

New messages use iOS's retuned insertion spring (0.27s, bounce 0.15),
start at 0.9 scale from the sender's bottom corner, and rise 35% of
their height plus the gap below them. The receipt line fades out in
80ms while its row closes on a no-bounce 0.35s spring, which the
reaction reflow now shares. Bubble fills go to 0.10 (outgoing) and
0.05 (incoming) white, and the swipe-to-reply arrow starts at 0.35.

The $ button stays "$" whenever a message field sits next to it, so it
no longer flashes "Send $" in contact DMs as text comes and goes.
@bmc08gt bmc08gt self-assigned this Sep 29, 2026
@github-actions github-actions Bot added area: ui Compose UI, theme, components, resources type: feature New functionality labels Sep 29, 2026
A new message at index 0 used to land by requestScrollToItem for our own
messages, a one-frame jump, and by animateScrollToItem for incoming ones,
on its own curve. When the transcript is resting on the newest message,
it now scrolls by the new row's measured height on insertionPush, the
insertion stiffness without the bounce. The older rows ride up while the
new row rises into its slot, as on iOS.

animateItem can't do this: the move is a scroll, and a jump in scroll
position resets placement animations. The scroll ends with a snap to
index 0 if the row above resized during it.
The newest-message flow fires when the paged snapshot changes, before the
list lays it out, so the scroll position still described the old list:
firstVisibleItemIndex read 0 rather than 1, the push was skipped, and our
own messages fell through to requestScrollToItem's one-frame jump. Wait
for the next layout pass, with a 250ms guard, before reading it.
The previous message's receipt dropped when a send was confirmed and the
new one's line appeared 700ms later, so the line hopped. The newest send
now holds the receipt on the row above until it is confirmed and 700ms
have passed, then the line moves down as the old one leaves, matching
iOS #908. A failed send still shows its own line straight away.
…t grouping

- Typing dots stay 800ms after STOPPED, and the typist's own message
  clears them straight away.
- A row carries a status line if it is the newest confirmed send or the
  newest of mine they have read, so Read and Delivered can show together.
- A bubble carrying a line ends its run: round corners and the normal gap
  until the line moves on.
At the handoff the new line drew at full height for a frame before its
own expand started, and it grew on a different spring than the old line
shrank on, while the old row's gap snapped from x2 to x1. The transcript
jumped about 20px and snapped back.

The row's AnimatedVisibility now owns the entrance on the reflow spring
the exit uses, so one row grows as the other shrinks, and the row gap
animates on the same spring. ReceiptLabel loses its own entrance.
@bmc08gt
bmc08gt merged commit d2fe576 into code/cash Sep 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui Compose UI, theme, components, resources type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant