feat(messages): Push (repost) and Quote (#20) - #110
Merged
Merged
Conversation
Adds the two ways to re-share a message that the web has and Android
lacked, both posting `pushedMessageId` on POST /api/messages:
- Push (repost) posts immediately with no content at all — the one case
the API allows an absent `content` ("required unless pushing with no
comment").
- Quote reuses the normal composer with the original attached and sends
both the note and `pushedMessageId`.
The feed renders a push and a quote from the nested `pushedMessage` the
server already embeds, so the original needs no second fetch: it is
modelled on the DTO, the Room entity (JSON-converted, db v4) and the
domain type. A push shows a "<author> pushed" header with the original
in place of a body; a quote shows the author's note with the original
inset beneath it. Tapping the inset opens the original's own page.
The always-public rule is consumed from #18's
MessageVisibility.PUSH_OR_QUOTE rather than restated: the repository
posts it instead of the caller's selection whenever a `pushedMessageId`
is present, and the composer both hides the Private chip and shows the
"pushes and quotes are always public" banner before sending.
Push and Quote are withheld where the docs say a message cannot be
re-shared — your own message, a private one, or a re-share of a
re-share (a quote is also never scheduled, since the API rejects
`scheduledAt` with `pushedMessageId`) — and a server rejection is
surfaced with the server's own wording.
Tests: a push sends `pushedMessageId` with no content; a quote sends
both; a push is public even when private was asked for; the feed caches
and renders the embedded original for each; the visibility control
cannot select Private for a quote; a rejected push surfaces the server's
message.
Closes #20
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 #20. Part of epic #17.
What changed
pushedMessageIdwith nocontentfieldat all. That is the docs' one case where content may be absent ("required unless pushing with no
comment"); every other post still sends its content verbatim, so a media-only post is unaffected.
contentandpushedMessageId.pushedMessage— no second fetch — modelledthrough DTO → Room entity → domain. A push draws a
"<author> pushed"header with the original inplace of a body; a quote draws the author's note with the original inset below. Tapping the inset
opens the original, wired in both feed and detail.
Always-public is consumed, not restated
#18 landed
MessageVisibility.PUSH_OR_QUOTEas the single home for this rule. It is applied inDefaultMessagesRepository.createMessagewheneverpushedMessageIdis non-null, so it overridesthe caller's selection — the invariant cannot be bypassed by a call site. The composer reads the
same rule to hide the Private chip, lock Public, and show "Pushes and quotes are always public."
Edge cases live in one documented predicate
Message.canBePushed= not mine (help centre: re-shares "someone else's" message) andpublicly visible (API: "repost this public message ID") and not itself a re-share — no page
defines a push-of-a-push, so nesting was not invented. The card withholds both actions in those
cases, showing only the count when there is one, and the ViewModel guards again. A quote is never
scheduled, since
scheduledAtandpushedMessageIdare mutually exclusive. Server rejections(403/409/400) surface the server's own wording. Repeat taps cannot double-post the same repost (an
in-flight id set).
Verification
./gradlew :app:assembleDebug testDebugUnitTest→ BUILD SUCCESSFUL;:feature:messages178 tests,0 failures. New:
MessagePushTest(10),MessageConvertersTest(3), plus additions toMessageDtoMapperTest,DefaultMessagesRepositoryTest(wire-body assertions via MockWebServer),MessagesFeedViewModelTestandMessagesFeedScreenTest.Mutation-checked the two load-bearing repository rules: reverting "omit content on a push" and
"force public" makes exactly those two tests fail.
MessagesDatabasev3 → v4 with the existing destructive-migration fallback.Reviewer notes
covered on the JVM via the domain predicates and the DTO→cache→domain path.
setFeedinMessagesFeedScreenTestdeclaresmutableStateOfwithoutrememberinsidesetContent, so state mutations there may not surviverecomposition. The new composer tests seed state directly rather than depend on it.
pushCountis not optimistically bumped after a push — the server's count arrives on the nextrefresh, which keeps push and quote consistent with each other.