feat(messages): public/private visibility toggle in the composer (#18) - #91
Merged
Merged
Conversation
Android never sent `publiclyVisible` on `POST /api/messages`, so the server default silently decided whether a post was public or private and the user had no control and no indication of what they had posted. - Add `MessageVisibility` (PUBLIC/PRIVATE) as the domain vocabulary for the wire boolean, including the documented `PUSH_OR_QUOTE` invariant: a push or quote amplifies someone else's message and is therefore always public. The push/quote UI itself is out of scope (#20) — this is the single place that rule lives, for that work to consume. - Send `publiclyVisible` explicitly on every create, plumbed through `CreateMessageRequest` -> `MessagesRepository.createMessage` -> the composer. - Seed the composer from the account preference `defaultPubliclyVisible` on `GET /api/user`, read via the existing shared current-user endpoint; the field is added to the shared `UserDto`/`User` rather than a new fetch path. Reading it is best-effort: a failure leaves the composer on public. - Let the user override the default per message (Public / Private chips plus a hint spelling out what private means). The override is dropped when the sheet is dismissed and after a successful post. - Model `publiclyVisible` on the message DTO, entity (Room v2 -> v3, destructive fallback already configured) and domain model, and render an unmistakable lock + "Private" badge on the author's own private cards. Public messages are never badged, and another user's message is never labelled. Tests: the create request carries the flag both ways; the default comes from the account preference; a per-message override wins and is reset on dismiss/post; the DTO/entity round-trips the flag; `showsPrivateBadge` is true only for the author's own private message. Compose assertions for the card badge and the composer chips are added to the (emulator-only) androidTest suite. Verified with `./gradlew :app:assembleDebug testDebugUnitTest`: BUILD SUCCESSFUL, 705 unit tests, 0 failures. Closes #18
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.
Closes #18. Part of epic #17.
What changed
The composer now owns the public/private decision instead of letting the server default silently
decide.
publiclyVisibleis sent explicitly on every create, seeded from the account'sdefaultPubliclyVisiblepreference, overridable per message, and the resulting visibility isrendered on the author's own cards.
MessageVisibility(PUBLIC/PRIVATE)is the domain vocabulary for the wire boolean, soViewModel/UI call sites read unambiguously.
MessageVisibility.PUSH_OR_QUOTE = PUBLIC. No push/quote UI or request plumbing was built —that is Messages: Push (repost) and Quote (
pushedMessageId) #20; this just gives that work a single rule to consume.InterlinedListApi.getCurrentUser()),adding
defaultPubliclyVisibleto the existingUserDto/Userrather than creating a new fetchpath. Reading it is best-effort: a failure leaves the composer on public and does not raise a
feed-level error.
visibilityOverride ?: defaultVisibility, so a late-arriving preferencestill applies and an explicit user choice always wins. The override is dropped on dismiss and
after a successful post.
mine && !publiclyVisible— public messages are never badged, andanother user's message is never labelled.
Verification
./gradlew :app:assembleDebug testDebugUnitTest→ BUILD SUCCESSFUL, 705 unit tests repo-wide, 0failures (113 in
:feature:messages). Tests were written first and confirmed red beforeimplementing.
:feature:messages:compileDebugAndroidTestKotlincompiles clean.New/extended tests:
MessageVisibilityTest(wire mapping both ways, the push/quote invariant,showsPrivateBadgefor own-private / own-public / another user's message); repository testsasserting the body carries
"publiclyVisible":trueandfalse, thatgetDefaultVisibilityreadsthe preference and falls back to public when absent, and that visibility round-trips through the
Room cache; ViewModel tests for default-from-preference, error-free fallback, override wins, and
override cleared on dismiss/post; DTO mapper tests. Compose UI tests for the badge and composer
chips were added but not run (no emulator available to the agent).
Reviewer notes
MessagesFeedViewModelcreation, mirroring the existingloadLinkedNetworks()pattern. It is not persisted, so an offline compose opens on public.Persisting it belongs with the Settings epic (Settings & preferences parity — most of
PATCH /api/user/updateis unreachable from Android #31), which ownsdefaultPubliclyVisiblewrites.postReplydeliberately still omitspubliclyVisible, preserving existing behaviour — the fieldis nullable on
CreateMessageRequestfor exactly that reason.MessagesDatabasev2 → v3 with the existing destructive-migration fallback, so the local messagecache is dropped once on upgrade.