Repository navigation
feat(chat): edit a group's name and icon - #1525
Merged
Merged
Conversation
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
changed the base branch from
feat/chat-roster-edit-rpcs
to
code/cash
September 22, 2026 15:00
bmc08gt
force-pushed
the
feat/edit-chat-ui
branch
from
September 22, 2026 15:00
e0479e2 to
425ab9a
Compare
…ver 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.
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.
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.
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.
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.
Most of the bottom bar's prompt descriptions end without one — 16 of the 20 prompt_description_* strings — so these two were the outliers.
…ther 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.
bmc08gt
added a commit
that referenced
this pull request
Sep 22, 2026
…tate (#1527) * feat(messenger): use the currency creator's glyphs on the edit rows The picture and name rows drew Icons.Outlined.Image and Icons.Outlined.TextFields — stock Material vectors in a list whose neighbouring rows all use drawables the app ships. They now use ic_currencycreator_icon and ic_currencycreator_name, the pair the currency creator's info screen puts against its own Icon and Name steps. Reached through com.flipcash.core.R as CoreR, the alias MyAccountMenuItems — the file this one mirrors — already uses to pull core drawables into a feature. * fix(messenger): state the consequence in the confirmation bodies Both bodies described what the other members would see — "Everyone in this group will see it as ..." — rather than what the button is about to do. They now read "This will change the group name/picture for everyone in it", which names the action and keeps the reach. Dropping the quoted title takes the name body's format argument with it. The field it quoted sits on screen behind the prompt. * chore: keep agent session state out of the repo .gitignore covers .claude/worktrees/, .claude/plans/ and settings.local.json but not .claude/state/, which is where an agent session writes the working note it reads back after a context compaction. The note was picked up as a new file and merged in #1525. Untracked here and the directory ignored alongside the other .claude paths that are already local-only. The file stays on disk.
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.
Stacked on #1524 — base is
feat/chat-roster-edit-rpcs, which carries theeditChat/canEditscaffolding this builds on. Review after that one; rebase ontocode/cashonce it merges.Entry point
The group profile gains a top-right overflow with a single row, Edit. It is drawn only when
ViewerState.Permissions.canEditsays so, and nothing at all is drawn otherwise — an overflow button that opens an empty menu is worse than no button.canEditis server-computed and passed in rather than derived locally. Membership, being the creator, and the chat's kind all look like they answer the question and none of them is the answer, so a local guess would offer an Edit whose Save comes backDENIED— which reads as the app being broken rather than as a permission.The overflow reuses the message long-press menu's shape from
ChatTopBar.Scope
Edit opens a list of what can be changed. Node
10187:110373shows four rows; this builds Icon and Name only. Membership Card, Description and Social Links have no contract support in flipcash2 0.11.0, so they are not scaffolded. Name is not in the node either and is styled to match the Icon row.The node titles the screen "Edit Community". It says "Edit Group" here, matching
hint_groupName,title_newPublicGroupand the rest of the repo's user-facing vocabulary.The rows use the existing
MenuList/ListItem, so this screen matches the profile menu one tap above it. That means the repo's settings-row metrics — 24dp icon, 17sp Demi — rather than the node's 26dp/20sp.ListItem's own doc comment records 17sp as a deliberate choice for settings rows. Worth a look if the node should win instead; it would mean a bespoke row.Partial updates
EditChatleaves a field unchanged when it is unset, which makes an over-filled request destructive rather than merely wasteful: renaming a group while also sending the picture field would rewrite the picture, and sending it as null would clear it.titleOnlyandpictureOnlyname that rule in one place. Built inline at each call site it held by inspection only, and anEditChatParameters(title = ..., picture = state.picture)written later — the shape most object updates take — would quietly overwrite the other field with whatever the screen happened to be holding.Title length
Checked in code points, not
String.length. protovalidate'smax_lencounts Unicode code points while Kotlin counts UTF-16 units, so a title of 64 emoji measures 128 in Kotlin. UsingString.lengthwould refuse titles the contract accepts, and only for non-BMP characters — the case least likely to be noticed. The bound applies to the trimmed title, and the trimmed title is what is sent.Picture
Upload runs through the existing
BlobStorageCoordinator, which returns only once the blob isREADY, so awaiting it is what orders the call correctly — aPENDINGblob would come backPICTURE_BLOB_NOT_ACCEPTED. No new uploader.EditGroupPictureViewModelmirrorsCreateGroupViewModel's pick-and-upload path, including its error wording.Echo
On
OKthe response metadata is persisted directly rather than refetched — the response already carries the chat as it now stands. ThetitleChanged/pictureChangedstream events carry absolute values (TitleChanged(newTitle),PictureChanged(newPicture)), not deltas, so the echo arriving afterwards writes the same value and is idempotent.Moderation wording
moderationDescriptionmoves out ofCreateGroupViewModelintocore/moderationunchanged, so an edit refuses a title in the same words creation does.CreateGroupViewModelnow calls the shared one;TITLE_MODERATEDkeeps the user on the edit screen with the field intact.Tests
ChatTitleTestcovers the 1..64 bound including the code-point case,PartialEditsTestcovers name-only sending no picture and vice versa, andGroupProfileOverflowTestcoverscanEditgating the row.