feat(ui): show the earnings the app was already receiving - #47
Conversation
The whole earnings pipeline shipped and nothing displayed it. Verified before
touching anything:
grep formatPlatformEarnings app/src/ -> ONLY EarningsDisplayTest.kt
grep -c earnings ui/MainViewModel.kt -> 0
So the phone asked the server for earnings, the server answered, the reply was
parsed, stored, kept across offline blips and timestamped for staleness -- and
then no screen read any of it. That was the original complaint about this app.
WHAT IS NOW SHOWN
A card with the device total, the window it covers, how many platforms have no
reading yet, and a "may be out of date" badge; plus each app's own figure on its
card, with a note when the provider reports one balance for the whole account.
THE THREE RULES A DISPLAY CAN SILENTLY THROW AWAY, each now tested:
- a missing figure is NOT zero. null renders as "nothing read yet", never
$0.00, because a confident zero beside a running app claims the provider
paid nothing -- a measurement nobody took. A real 0.0 IS shown as $0.00.
- a kept figure is NOT a current one. The last value survives an offline phone
deliberately, so it is labelled when old.
- an account balance is NOT a device's earnings, and the card says so instead
of implying it.
Those rules live in EarningsPresentation, not inside the Composable, because
this module has no way to render a Composable in a test -- leaving them in the
UI is how they would go unverified, which is exactly how the pipeline came to be
built and never displayed.
PROBLEMS NOW SORT FIRST
The dashboard sorted by AppState.ordinal -- the enum's DECLARATION order, which
puts RUNNING first -- so apps that were fine took the top of the screen and
anything STOPPED was pushed underneath. On a two-column grid with eleven apps a
stopped earner can sit below the fold, which is the one thing the user opened
the app to find out. Ranking is now an explicit table, not an enum's ordinal, so
reordering the enum cannot silently rearrange the screen, and a test fails if a
new state has no rank. A secondary alphabetical key stops the grid reshuffling
under the user's thumb on every 30-second refresh.
AppStateResolutionTest used to COPY the production when-expression and said so:
"this is the exact same when-expression used in production". A copy cannot fail
when production changes, so it verified the copy. It calls AppPresentation now.
Nine negative controls fire, including reverting the sort to ordinal, treating
an unread total as zero, reporting a real 0.0 as unread, and dropping the
staleness label.
|
Warning Review limit reached
Next review available in: 52 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe dashboard now uses explicit app-state ranking and earnings presentation rules. It displays server earnings and per-app earnings with stale-data handling. Tests cover the presentation rules. A remote Gradle execution helper was added. ChangesDashboard presentation
Remote build support
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/test/java/com/cashpilot/android/AppStateResolutionTest.kt`:
- Around line 33-37: Update the obsolete dashboard-order test that currently
sorts by AppState.ordinal and expects RUNNING first. Replace the ordinal-based
sorting with AppPresentation.sortForDashboard, then assert the resulting
production order with STOPPED first.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ab99bdf0-ef0d-45f1-932a-5ee970a4db57
📒 Files selected for processing (10)
app/src/main/java/com/cashpilot/android/ui/AppPresentation.ktapp/src/main/java/com/cashpilot/android/ui/EarningsPresentation.ktapp/src/main/java/com/cashpilot/android/ui/MainViewModel.ktapp/src/main/java/com/cashpilot/android/ui/component/EarningsCard.ktapp/src/main/java/com/cashpilot/android/ui/screen/DashboardScreen.ktapp/src/main/res/values/strings.xmlapp/src/test/java/com/cashpilot/android/AppPresentationTest.ktapp/src/test/java/com/cashpilot/android/AppStateResolutionTest.ktapp/src/test/java/com/cashpilot/android/EarningsPresentationTest.ktscripts/remote-gradle.sh
CodeRabbit caught this, and it is the same defect class as the resolveState copy
in the same file -- I fixed that one and walked past this.
`build display list and sort by state ordinal` re-implemented the OLD sort on
its own local list:
}.sortedBy { it.state.ordinal }
and asserted RUNNING first. Because it sorted a list it built itself, it never
called the app, so it kept passing after production changed to put problems
first -- while documenting behaviour the dashboard no longer has. A future
reader would have taken it as the spec.
It now calls AppPresentation.sortForDashboard and asserts the real order,
including the alphabetical tiebreak within a rank.
Verified by control: reverting attentionRank so RUNNING leads again now fails
this test. Before the change, the same mutation left it green.
Closes
CashPilot-android-9nb, and the first half ofkv8.The app was receiving earnings and showing you none of them
Before touching anything:
The entire pipeline shipped and is careful —
Earnings/PlatformEarnings,recordEarnings,earningsToKeepfor offline blips,earningsAsOfso staleness is expressible,formatPlatformEarningsreturning an em-dash rather than$0.00. Every one of those was unreached from the UI. The phone asked, the server answered, the reply was parsed and stored — and no screen read it. That was your original complaint about this app.Now shown: device total, the window it covers, how many platforms have no reading yet, a "may be out of date" badge — and each app's own figure on its card, with a note when the provider reports one balance for the whole account.
Three rules a display can silently discard
Each is now a test, not a comment:
null→ "nothing read yet", never$0.00. A confident zero beside a running app claims the provider paid nothing — a measurement nobody took. A real0.0still shows as$0.00.These live in
EarningsPresentation, not inside the Composable. This module has no way to render a Composable in a test, so rules left in the UI go unverified — which is precisely how a complete pipeline came to be built and never displayed.Problems now sort first
The dashboard sorted by
AppState.ordinal— the enum's declaration order, which putsRUNNINGfirst. So apps that were fine occupied the top of the screen and anything STOPPED was pushed underneath them. On a two-column grid with eleven apps, a stopped earner can sit below the fold — the one thing you opened the app to find out.Ranking is now an explicit table rather than an ordinal, so reordering the enum cannot silently rearrange the screen, and a test fails if a new state is added without a rank. A secondary alphabetical key stops the grid reshuffling under your thumb on every 30-second refresh.
A test that could not fail
AppStateResolutionTestcopied the productionwhen-expression, and said so: "this is the exact same when-expression used in production". A copy cannot fail when production changes, so every case verified the copy. The logic moved toAppPresentation; both call it now.I can build this locally again
There is no working JVM on either Mac — the dev machine has no Java, and Temurin 17 on the mini crashes with
SIGBUS ... CodeHeap::allocateonjava -version, in interpreted mode, and with a reduced code cache, so it is the JVM itself.scripts/remote-gradle.shruns Gradle ineclipse-temurin:17-jdkon the build host instead: ~26s warm, versus a CI round trip. It is a faster pre-check, not a replacement for CI, and says so.Evidence
356 tests, 0 failures,
lintDebugclean. Nine negative controls fire:ordinal→ 4 fail0.0as unread → 3 failNot in this PR
The rest of
kv8— permissions as a blocking state rather than a dismissible banner, and QR pairing. Those are separate changes and this one was already the highest-value part.Summary by CodeRabbit