Repository navigation
feat(chat): scaffold GetRoster and EditChat - #1524
Merged
Merged
Conversation
…h2 override Adds the service/repository/controller chain for chat.v1's two new RPCs, against the flipcash2-client-protocol composite build local.properties already points at: - GetRoster pages a chat's roster (result mapped to DENIED/NOT_FOUND, page data to a new RosterPage model). - EditChat edits a group's title/picture (result mapped to DENIED/NOT_FOUND/TITLE_MODERATED/PICTURE_BLOB_NOT_ACCEPTED, the last carrying flagged_category, following the StartChat error shape). Carries the model changes the proto brought with the RPCs: Member gains joinedAt/version (the per-member merge key GetRoster's paging relies on, greater version wins), ViewerState gains a Permissions.canEdit flag, and MetadataUpdate gains TitleChanged/PictureChanged cases, wired into EventStreamDelegate's existing exhaustive when-block. Persistence gets targeted updateTitle/updatePicture queries alongside the existing whole-row upsert, since ChatMetadataEntity already has the columns but no single-field update path for them. can_edit is mapped from the wire but not persisted: no column exists for it on ChatMetadataEntity, and adding one is a schema migration this pass doesn't attempt. UI gating on can_edit and the picture-upload flow EditChat's Picture.blob_id assumes are both left for follow-up work. RosterStateHolder still gates its cached roster on whole-roster version and repairs gaps via GetChat; it doesn't call GetRoster or apply its per-member merge rule yet, so a large group's paged roster can still lag behind what GetRoster's own staleness contract would let a client catch up to.
0.11.0 isn't published to Maven Central yet; it lands once the client repo cuts the release carrying GetRoster/EditChat. Locally the build resolves anyway, since local.properties' protoLocalRoot/protoLocalPackages override substitutes the composite build for any version string on `com.flipcash:flipcash2-client-protocol`. CI has no such override, so it can't resolve 0.11.0 until the real release exists - the PR goes up as a draft for that reason.
bmc08gt
marked this pull request as ready for review
September 21, 2026 22:38
bmc08gt
added a commit
that referenced
this pull request
Sep 22, 2026
Adds the Edit entry point and the two edits behind it, on top of the editChat/canEdit scaffolding in #1524. The profile's top-right overflow holds one row, Edit, and is drawn only when ViewerState.Permissions.canEdit says so. That field is server-computed and is passed in rather than derived: membership, being the creator, and the chat's kind all look like they answer the question, and a local guess would offer an Edit whose Save comes back DENIED. Edit opens a list of what can be changed. The design node shows four rows; only Icon and Name are built, because the other three have no contract support in flipcash2 0.11.0. EditChat leaves a field unchanged when it is unset, so each edit sends only its own field. titleOnly and pictureOnly name that rule in one place -- built inline at each call site it held by inspection, and an EditChatParameters(title = ..., picture = state.picture) written later, in the shape most object updates take, would overwrite the other field with whatever the screen was holding. Title length is checked in code points, not String.length. protovalidate's max_len counts code points, so 64 emoji measure 128 in Kotlin and a UTF-16 count would refuse titles the contract accepts. On OK the response metadata is persisted directly rather than refetched. The titleChanged/pictureChanged echo carries absolute values, so the stream applying it afterwards is idempotent. The moderation-category wording moves from CreateGroupViewModel to core/moderation so an edit refuses a title in the same words creation does.
bmc08gt
added a commit
that referenced
this pull request
Sep 22, 2026
* feat(chat): edit a group's name and icon Adds the Edit entry point and the two edits behind it, on top of the editChat/canEdit scaffolding in #1524. The profile's top-right overflow holds one row, Edit, and is drawn only when ViewerState.Permissions.canEdit says so. That field is server-computed and is passed in rather than derived: membership, being the creator, and the chat's kind all look like they answer the question, and a local guess would offer an Edit whose Save comes back DENIED. Edit opens a list of what can be changed. The design node shows four rows; only Icon and Name are built, because the other three have no contract support in flipcash2 0.11.0. EditChat leaves a field unchanged when it is unset, so each edit sends only its own field. titleOnly and pictureOnly name that rule in one place -- built inline at each call site it held by inspection, and an EditChatParameters(title = ..., picture = state.picture) written later, in the shape most object updates take, would overwrite the other field with whatever the screen was holding. Title length is checked in code points, not String.length. protovalidate's max_len counts code points, so 64 emoji measure 128 in Kotlin and a UTF-16 count would refuse titles the contract accepts. On OK the response metadata is persisted directly rather than refetched. The titleChanged/pictureChanged echo carries absolute values, so the stream applying it afterwards is idempotent. The moderation-category wording moves from CreateGroupViewModel to core/moderation so an edit refuses a title in the same words creation does. * fix(messenger): give the group profile overflow a leading icon and cover its anchor DropdownMenu lays out to a 112dp minimum width. "Edit" fills about 24dp of that at a 13dp inset, so the menu rendered as a mostly empty rounded rectangle with a word in one corner. MenuItem already carries an icon for this action; the menu just never drew it. Passing it as leadingIcon fills the leading space and gives the trailing space something to measure against, matching the rows on the screen behind the menu at the same 24dp. The menu also dropped clear of the overflow button, which pushed its first row toward the middle of the screen and left the button stranded above it. Chrome's toolbar overflow opens over its button instead. Offsetting by the anchor's measured height does the same here; AppBarDefaults.Overflow sizes its own circle, so the pull-up is read from the anchor rather than hardcoded. ChatTopBar's message menu still drops below its anchor. * feat(messenger): confirm a group name or photo change before sending it Save now raises a BottomBarManager prompt, and the prompt's action is what calls EditChat — the same shape UserProfileViewModel uses to confirm these two edits on the user's own profile. Both changes are visible to everyone in the group, which is the case for asking first. The gate is a view model event rather than screen state so a test can hold it: SaveClicked sets nothing in motion, and only the confirmation dispatches SubmitTitle/SubmitPicture. The name screen's keyboard Done routes through the same event and so re-reads canSubmit, which the Save button draws itself from but Done never consulted. Both view models now take their event dispatcher from DispatcherProvider. BaseViewModel otherwise publishes on Dispatchers.Default, which no test scheduler can drain, so Initialize was overwriting a draft the test had already typed. Titles alongside: the Icon row is Photo and its screen is Set Photo, Edit Group is centred to match the screens under it, and the name screen drops its title — the field is the screen, and the row that opened it said Name. * refactor(messenger): call the group's image a Picture, not a Photo It is what EditChatRequest.picture writes and what the rest of the app already calls it, so the design's "Icon" resolves to Picture rather than to a third word. Renames the row label, the screen title, the prompt and the menu item object together, so the string ids match their values. * fix(messenger): draw the edit confirmations neutral, not destructive showAlert paints the bar red, which reads as a warning about something being taken away. Both edits it guards are additive and reversible; what justifies asking at all is that the new name or picture goes to everyone in the group, and the prompts already say so. NameEntryViewModel and PhotoSelectionViewModel confirm the same two edits on the user's own profile with showMessage and keep showAlert for the failures. These now match on both counts. * fix(messenger): confirm group edits on the alert style, not the neutral one The account screens confirm these same two edits with showMessage, and matching them is what 6416369 changed. But those edits are the user's own profile. A group's name and picture change for everyone in the chat, and showAlert is the weight that carries. Reverts 6416369. * feat(messenger): head the group name edit with Set Name The screen had no title at all, so it opened on a bare field with nothing saying what the field was for. It now carries an inline heading the way NameEntryScreen does: no app bar title, a textLarge Text at 70% width above the field. That asymmetry with the picture screen, which keeps its app bar title, is the account flow's own split rather than an inconsistency — PhotoSelectionScreen titles its bar and NameEntryScreen heads its content. * chore(messenger): drop the trailing periods from the edit confirmations Most of the bottom bar's prompt descriptions end without one — 16 of the 20 prompt_description_* strings — so these two were the outliers. * fix(messenger): line up the picture screen's back button with every other one The bar was nested in the CodeScaffold that carries padding(horizontal = inset), so the back control started at inset + grid.x2 — 20dp further in than the same control on the name screen beside it. Hoisting the bar into a Column above the scaffold is how PhotoSelectionScreen, NameEntryScreen and EditGroupNameScreen all place theirs, and it leaves the centred title centred against the full screen width rather than the inset body.
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.
Scaffolds the service layer for the two RPCs added in
code-payments/flipcash2-client-protocol#17, and pins the release that carries them.
Blocked on that PR.
com.flipcash:flipcash2-client-protocol:0.11.0is not on Maven Centralyet, so CI will report
FAILEDon resolution until it is published. This stays a draft untilthen. The work here was developed and verified against the client repo's checkout through
protoLocalRoot, which is untracked and invisible to CI.What this adds
ChatApi/ChatServicegaingetRosterandeditChat, surfaced throughChatControllerandInternalChatRepository, with result-enum mapping inErrors.kt—DENIED/NOT_FOUNDfor thefirst, and
DENIED/NOT_FOUND/TITLE_MODERATED/PICTURE_BLOB_NOT_ACCEPTEDplus the trippedFlaggedCategoryfor the second. New domain modelsRosterPageandEditChatParameters.ChatMembercarries the newjoinedAtandversion;ViewerStatecarriescanEdit. Theevent-stream delegate and the metadata persistence layer handle the two new
MetadataUpdatecases,
TitleChangedandPictureChanged.Two things a caller has to know
A roster page is not a snapshot. A large group's page is served from an index that trails
membership writes, and the client cannot tell that case from a small group's whole-roster read.
Pages have to be merged against the event stream per member, by
ChatMember.version, greaterwinning.
RosterPagecarries what that needs, but no merge exists yet because nothing consumesgetRoster— whoever builds the roster UI has to write it rather than treating a page asauthoritative.
canEditis server-computed and not derivable. Gate the edit affordance on the flag alone,not on membership or chat kind.
Deliberately not included
No
canEdit-gated UI, and no picture-upload flow —editChattakes aBlobIdfor a blob alreadyuploaded and
READYvia BlobStorage. Both are feature work on top of this scaffolding.