Skip to content

refactor(chat): drive the reaction sheets from ChatViewModel state - #1598

Merged
bmc08gt merged 5 commits into
code/cashfrom
refactor/chat-reactions-mvi
Sep 28, 2026
Merged

bmc08gt merged 5 commits into
code/cashfrom
refactor/chat-reactions-mvi

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

The reactions work added public methods on ChatViewModel that the screen called directly (reactorsRows, reactorsLoading, reactorsHasMore, loadMoreReactors, syncReactors, reactionPills, reactorDisplay, reactorParticipant, emojiPickerSections and quickReactionStripFor). The screen collected their flows itself, and the quick strip was computed in the screen and then passed back in on ToggleMessageSelection. This gives the reactors sheet and the emoji picker their own view models, and moves the quick strip onto ChatViewModel State.

  • Reactors sheet: ReactorsViewModel is scoped to the ChatStep.Reactors entry, which now carries the chat id. It follows the message's live pills (falling back to its stored reactions), owns the ReactorsPrefetchCache, resolves names and avatars, and pages on LoadMore. Tapping a row pushes the profile from the sheet. ChatViewModel has no reactors state or events left, and OpenReactors no longer carries pills.
  • Emoji picker: EmojiPickerViewModel is scoped to the picker entry. It owns the search TextFieldState, debounces it, and loads sections with mapLatest, so a load the user has typed past is dropped. ChatViewModel only sees the ToggleReaction a pick sends.
  • Quick strip: message rows now carry selfReactions, and the strip's other inputs load into State.quickReactionInputs, so ToggleMessageSelection builds the strip in the reducer from state alone.

ChatReactionsReducerTest has 6 reducer tests for the strip, ReactorsReducerTest 4 for the reactors sheet, ReactorsViewModelTest checks that a reaction added under the open sheet adds its row and that a reactor named later fills in, EmojiPickerReducerTest 2 for the picker, and EmojiPickerViewModelTest checks that a search the user typed past is dropped when it finishes late.

memberParticipant is still a public method the screen calls from MessengerScreen. It isn't reaction-specific, so it's left for a separate change.

@bmc08gt bmc08gt self-assigned this Sep 28, 2026
@github-actions github-actions Bot added the type: refactor Code restructuring, no behavior change label Sep 28, 2026
@bmc08gt
bmc08gt changed the base branch from fix/reactors-sheet-refresh to code/cash September 28, 2026 18:18
The reactors sheet, emoji picker and quick-reaction strip read the view
model through public methods that returned flows or values, bypassing
State and Event. The screen collected those flows itself, and the quick
strip was built in the screen and passed back into the selection event.

Each surface now has a slice of State: `reactors` (pills, rows with
names and avatars resolved, loading, hasMore), `emojiPicker` (query,
sections, loaded) and `quickReactionInputs`. Opening, paging, searching
and dismissing go through Events, and a reactor tap sends
OpenReactorProfile, answered by the ShowReactorProfile event. Message
rows carry the viewer's own reactions, so selecting a bubble builds the
strip in the same reduction as the selection. Picker sections for a
query the user has already typed past are dropped.

OpenReactors carries the long-pressed pills, so the sheet has a title
before the live reaction data arrives.
The picker's search and sections don't belong to the chat, so they move
off ChatViewModel into EmojiPickerViewModel, scoped to the picker's nav
entry. It owns the search field's TextFieldState, debounces it and loads
sections with mapLatest, which drops a load the reader has typed past.
The sheet reads the field directly, so there is no separate query in
state and no open or dismiss events to pair up. Choosing an emoji still
dispatches ChatViewModel.Event.ToggleReaction.
EmojiPickerViewModelTest holds each section load open, finishes the
"dog" search before the earlier "cat" one, and checks the late "cat"
results don't replace "dog". Swapping mapLatest for flatMapMerge makes
it fail with [cat face].

Robolectric's Paint reports no emoji glyphs, which empties every
section, so the view model takes a GlyphProbe through an internal
constructor. The @Inject constructor still passes PaintGlyphProbe.
@bmc08gt
bmc08gt force-pushed the refactor/chat-reactions-mvi branch from e626f23 to fa494b8 Compare September 28, 2026 19:00
The reactors sheet's rows, paging and pills lived on ChatViewModel,
which fed them to the sheet through OpenReactors, LoadMoreReactors,
ReactorsDismissed and a ShowReactorProfile round trip. ReactorsViewModel
is scoped to the ChatStep.Reactors entry instead: it opens on the chat
and message from the nav key, follows that message's live pills, owns
the ReactorsPrefetchCache, and resolves names and avatars. The sheet
pushes a reactor's profile itself.

ChatStep.Reactors now carries the chat id, and OpenReactors drops the
pills it carried for the old sheet title.
One test adds a reaction under the open sheet and checks the new reactor's row appears. The other checks an unnamed reactor's profile is requested once and the row picks up the name when it lands.
@bmc08gt
bmc08gt merged commit 93a13ee into code/cash Sep 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant