Repository navigation
Conversation
The viewer's state on a chat (mute, permissions, moderator silence) and the clock a timed silence is read against now sit in a ViewerSlice on ChatViewModel.State, with their three events under ViewerEvent, a pure reducer over the slice, and the three effect chains (metadata stream, silence timer, lift edge) in DefaultViewerScope.bind. ChatViewModel implements ViewerScope by delegation and routes every ViewerEvent to the slice reducer in one branch. This is the template for splitting the rest of ChatViewModel. The scope takes only what it reads from outside itself (the chat id, as a flow) and can dispatch only its own events, so ViewerScopeTest binds it to a test scope with a fake coordinator and no view model. Getters that read only the slice (silence, canPin) move onto it; root State keeps forwarders for them and for viewerState, so screen code is unchanged. canSpeak, isReadOnlySpeaker and cashCardTap also read membership, so they stay on State. Behaviour is unchanged: the effect chains are moved as they were, and the reducer still reads the wall clock.
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.
Moves the viewer's state on a chat (mute, permissions, moderator silence) out of
ChatViewModelinto its own scope. This is the template for splitting up the rest of the view model; no other scope moves in this PR.The shape
The rules this sets for the scopes that follow:
ViewerScopeTestbinds the scope to a test scope with a fake coordinator and no view model.State.canSpeak,isReadOnlySpeakerandcashCardTapall read membership or the speaker gate as well, so they stay.Statekeeps forwarders forviewerState,silenceandcanPin, so the 8 screen files that read them are unchanged.ChatViewModel.Event, and the root reducer has one branch per scope.The constructor takes
DefaultViewerScope, notViewerScope:bindis not on the interface, sobydoesn't expose it, and injecting the concrete class needs no@Bindsmodule.For the headless contract in the cross-platform behaviour-test spec, a scope maps onto one child node:
ViewerSliceis the node'sstateandViewerEventis its event set. All three viewer events are internal (the metadata stream, the timer, the lift edge), so this node'sactionslist would be empty. The public-action marker therefore has to separate events a viewer can send from internal messages.This scope doesn't exercise two parts of the target: it has no extra
MutableStateFlowto fold, andViewerScopehas no members yet, sobyforwards nothing. Pins exercises both, which is why it's next in the order below.The effect chains are moved unchanged; only their source flow is new. The reducer still reads
Clock.System.now(), asreduceChatOpendid.Mechanical updates:
ChatFlowScreenandChatSilenceLiftedToastTestnameViewerEvent.*.ChatSilenceStateTestand three title-bar/screenshot tests buildState(viewer = ViewerSlice(...)). The #1924 fixture andChatPinnedRowsTestpassDefaultViewerScope(chatCoordinator).Inventory
Line numbers are approximate and refer to
ChatViewModel.kton this branch.reduceChatOpenpins,pinBarIndex,waitingPinreducePinspinVersionHeard: latch, not rendered. Moves into the scope's implementation, not the slice.reduceTypingwrongKey,isEncryptedreduceChatOpenquickReactionInputs,quickReactionStripreduceReactionsreactionOverlay: cache, read synchronously by the toggle. Keep.reduceJumpReturn,reduceUnreadgatedLandingMessage: raw input to a derived bubble. Keep.groupAccess,speakerBlock*,rule*,profileStanding)reduceChatOpenreduceChatOpensubject,chatType,isAnonymous,chatNotFound,resolveState)reduceChatOpensenderProfiles(cache read by 6 scopes) andopenedChatId(re-entry guard). Keep both at view-model level.sendProgress,groupSend,token,limits,cashSymbol)reduceSend,reduceChatOpenamountStyleFlow,maxAmountFlow,amountDelegate,sendBack,claimReplyTargets: delegate wiring and latches. Keep.reduceMessageAction,reduceSenddraftFlushObserverand the mention picker's refresh latch. Keep.stagedPhotos,uploadStatesreduceComposerPhotoscaptureFiles(cache),pendingCaptures(latch). Keep.composerLinkreduceComposerPhotos,reduceSend_previewsReady: one-way gate on the first draw, exposed as its ownStateFlow. Fold into the slice.selection,reactionStripOnly,confirmingDeletereduceMessageActionchatId,separatorConfig,messagePolicyreduceChatOpenpagingScope,quotedMessages/quotedGeneration: infrastructure and a per-page cache. Keep.The moderation files from #1933 (
GroupModerationEvents.kt,MuteEvents.kt,GroupRestrictionWrites.kt) serveMuteChatViewModeland the moderation view models, notChatViewModel. The moderation-shaped scope insideChatViewModelis the group gate.Cross-scope reads
State:canSpeak(gate, viewer),isReadOnlySpeakerandcashCardTap(identity, membership, gate, viewer),showsGatePanel,readsFromOutside,obscuresTranscript,messageLinkLanding,showsJoinRequestsAnchor,groupSendCandidates,composerPhotos/canSendComposer,acceptsMedia.bind(chatId = ...)does here. A scope that needs several fields from one other scope gets a narrow read-only interface instead of the whole root state.selectionas a side effect:PinMessage,UnpinMessage,ToggleReaction,ReplyToMessage,EditMessage. If one event reaches both scopes' reducers, each writes only its own slice.quickReactionStripis stored but always equalsreactionStripFor(selection); making it a getter removes the reactions/selection cross-write.OnGroupResolved: it resets the gate by comparing against the previous subject.SendMessage: its reducer writesreturnStackandunreadBoundary, and its effect reads photos, the link preview and the reply.OnChatOpenedhandler (2394-2487) touches six scopes.Conflict map
The branches named when this was planned (
feat/chat-send-back,feat/chat-media-save-stack-edit,fix/request-pay-no-balance,feat/standard-cash-bubbles,perf/parallel-async,feat/group-send-per-person) are all merged; their remote branches are squash-merge leftovers. What still editsChatViewModel.kt:feat/chat-polls(local worktree, no PR)State.poll, 3 events, message actionsNone of them touch viewer state or silence. Every one adds a constructor parameter, so each later extraction that adds a scope parameter to the constructor will conflict there, trivially.
Proposed order
pinVersionHeardinto the scope's implementation, and givesPinsScopereal members (pinPreview,pinQuote,pinnedRows) forbyto forward.quickReactionStripturned into a getter first.reduceChatOpenis split across these three, so it dissolves here;OnGroupResolvedis the hard case.previewsReady), then transcript.Also noticed, not changed here:
ViewerSlice.reducereadsClock.System.now(). A serializable, replayable slice needs that instant to come in on the event; this changes the event shape and the tests, so it's a separate change.ChatViewModelcollectsobserveMetadata(chatId)five times, andrefreshViewerStatestill runs fromopenTranscript's stored-chat branch.waitingPinis set onPinCapReachedand never cleared in this module;separatorConfigis never written.