Skip to content

feat(messenger): rebuild another user's profile with a pinned start-chatting action - #1692

Merged
bmc08gt merged 12 commits into
feat/profile-refresh-own-profilefrom
feat/profile-refresh-other-profile
Oct 7, 2026
Merged

bmc08gt merged 12 commits into
feat/profile-refresh-own-profilefrom
feat/profile-refresh-other-profile

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Third slice of the profile refresh, matching code-payments/code-ios-app#987: another user's profile is rebuilt on the shared header from #1691 and owns its own chat, block, fee and payment state. Stacked on #1691.

Screen (features/messenger, Figma node 10990:494)

  • Cover, avatar, name, handle, bio and the Minimum to Chat / Date Joined card come from the shared ProfileHeader and ProfileStatsCard.
  • The action row holds Blocked and Muted chips beside Share. Share opens ProfileShareSheet without the profile card row.
  • The ⋯ menu has Mute Notifications, Report and Block or Unblock, styled like the chat screen's overflow menu. It is hidden on your own profile.
  • The Message and Send Cash shortcuts are gone.
  • One pinned button reads "Send $X to Start Chatting", "Open Chat" or "Unblock", with the encryption note above it when the DM is encrypted. Content scrolls under it, and nothing is pinned until the full profile has loaded.
  • The pinned area is ProfilePinnedActionBar in shared/common-ui/.../profile, so the group profile can pin its own button. It takes an optional slot above the button and an optional secondary text action below it.
  • Toasts on this screen sit above the pinned button. FloatingToastHostState gains a per-screen bottom clearance for that (ToastBottomClearance), taking the largest clearance any open screen asks for. The bar registers its own height while it is shown.

State (ChatProfileViewModel)

  • A DM exists when the generated tip chat id has members. Blocked comes from the new BlocklistCoordinator.observeIsBlocked, and blocked wins over DM-exists because a block hides the DM without removing its members.
  • blockUser now writes the local blocklist row, so the chip and the button change without waiting for a sync.
  • The fee is startChattingFee. When it is unknown the button opens the DM with Send Cash, as before; slice 5 replaces that path when the paid-DM gate leaves the chat.
  • The view model is shared across one chat flow. Opening a different person resets everything about the last one; opening the same person again keeps the loaded profile on screen and refreshes it in the background.

Paying to start a chat

  • The fee sheet (StartChattingSheet) opens inside the profile rather than as a route, so the push to the new DM happens while the profile is still on top.
  • The balance, add-money, discover-currencies and send-limit checks moved from ChatViewModel into StartChattingPayer, which both screens use for their alerts. ChatViewModel's own fee payment still sends directly and goes away in slice 5.
  • The sheet stays up and can't be dismissed (swipe, scrim or back) from the slide until the new DM's members arrive; then it closes and the DM opens. If they haven't arrived after 10s, as on iOS, the sheet closes and the profile stays, with the button showing "Open Chat" and a spinner until they do.
  • A person who has been paid cannot be paid again while that wait runs. A failed payment unlocks the sheet, shows the error, and can be retried.
  • ChatInitPaymentSheet now takes a Flow<Unit> for send completion instead of ChatViewModel's event flow, so the profile can use it.

Tests

  • ProfileActionsTest covers the pinned-action and menu decisions, DefaultStartChattingPayerTest the balance, send-limit and no-account branches, and ChatProfileViewModelTest repeat opens, person switches, double payment and payment failures.
  • BlockedUserDaoTest runs observeIsBlocked against Room.
  • The shortcut tests are removed with the shortcuts, and the screenshot tests of the old header are removed; those had no recorded goldens.

@bmc08gt bmc08gt self-assigned this Oct 6, 2026
@github-actions github-actions Bot added type: feature New functionality area: ui Compose UI, theme, components, resources and removed type: feature New functionality labels Oct 6, 2026
@bmc08gt
bmc08gt force-pushed the feat/profile-refresh-other-profile branch from 1f4e178 to 3d432f5 Compare October 6, 2026 22:51
@bmc08gt
bmc08gt added this pull request to stack #1696 October 7, 2026 01:28
@bmc08gt
bmc08gt force-pushed the feat/profile-refresh-other-profile branch from 4514f75 to ef4e127 Compare October 7, 2026 01:51
@bmc08gt
bmc08gt force-pushed the feat/profile-refresh-other-profile branch 2 times, most recently from 63c93af to 6a2ed9e Compare October 7, 2026 02:40
bmc08gt added 12 commits October 6, 2026 22:58
Adds BlockedUserDao.observeIsBlocked and exposes it through
BlocklistCoordinator. blockUser now writes the local row so the flow
flips on block as well as unblock.
…yment state

ChatProfileViewModel now tracks the tip DM, block and mute state, the
start-chatting fee, the E2EE flag and the pinned action, and can pay the
fee to start a chat. The give-cash gate and the fee payment move out of
ChatViewModel into StartChattingPayer so the chat and the profile share
one copy. ChatInitPaymentSheet takes a sendComplete flow instead of the
chat's event flow.
Another person's profile now uses the shared ProfileHeader, stats card and
status chips. Mute, Report and Block move behind a frosted top-bar menu, and
the Message and Send Cash shortcuts give way to one pinned button: Start
Chatting, Open Chat or Unblock, with the E2EE footer above it.

The start-chatting fee sheet is shown from the screen. Share offers Share
Profile and Copy Link only.

Screens can ask the root toast host for bottom clearance
(ToastBottomClearance), so a toast rests above a pinned button instead of
under it. joinedLabel moves to common-ui for both profile screens.
…state straight

Paying the chat fee waited on the DM inside the event collector, so every other event stalled behind it for up to ten seconds, and a queued tap could pay the same person twice because the sheet's show and dismiss reset the only guard. The pay and the wait now run in their own coroutine, and a per-recipient paid marker (paidRecipients) that the sheet cannot clear gates payment and turns the pinned action into a loading Open Chat until the DM appears.

The flow-shared view model is reused across people, so opening a different person now resets the join date, block, mute, encryption, DM, fee and sheet state, and ends the previous person's DM subscriptions. Opening the same person again keeps the settled profile while it refetches in the background, and a fetch that lands after the screen moved on is dropped.

Also adds DefaultStartChattingPayer tests, lets the fee sheet animate out, and notes on ChatViewModel's duplicate pay path that it goes with the paid-DM gate.
Moves the other-user profile's pinned bottom area (optional line above, filled button, optional text action, nav-bar inset, height measurement, toast clearance) into ProfilePinnedActionBar so the group profile can reuse it.
The fee sheet now stays up, showing its success, until the DM's members arrive, and cannot be dismissed by swipe, scrim, back or DismissPaymentSheet while a payment is out. When the members land it hides and the chat opens. If they never do within 10s the sheet goes and the profile stays, as on iOS. A failed payment makes it dismissable again.
…ext colour

Report and Block used the error colour; menus show destructive rows in
errorText. The menu dropped below the ⋯ button; it now opens over it.
Each row gets the glyph iOS uses for it: a slashed bell for Mute, a speech bubble
for Report, a no-entry sign for Block and a check circle for Unblock.
Report and Block tint the icon in the error text colour with the label;
Mute shows the plain bell once the chat is muted, since the row then
unmutes.
iOS ProfileMenuItem.mute is always "Mute Notifications" with
bell.slash, and the sheet it opens handles unmuting. The Android row
flipped to "Unmute Notifications" and a plain bell once the chat was
muted.
- The Muted chip reads "Muted until <time>" for a timed mute and goes when it lapses, through the
  chat screen's rememberMutedLabel.
- Status chips sit 8 apart; the row keeps its 12 before Share.
- The ⋯ menu puts a divider where the red rows start.
- The pinned bar pads 12/8 with 8 between the footer and the button (10 on the 5dp grid).
@bmc08gt
bmc08gt force-pushed the feat/profile-refresh-other-profile branch from 6a2ed9e to 1e4ba6b Compare October 7, 2026 03:04
@bmc08gt
bmc08gt merged commit 23b9081 into code/cash Oct 7, 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