Skip to content

feat(notifications): notification status page in Settings - #1874

Merged
bmc08gt merged 26 commits into
code/cashfrom
feat/notification-status
Oct 29, 2026
Merged

bmc08gt merged 26 commits into
code/cashfrom
feat/notification-status

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

When a user says they aren't getting notifications, there's nothing on the phone that tells them, or us, which part is broken: the permission, a blocked channel, battery optimization, or push registration with Flipcash. This adds a status page at Settings › Advanced › Notifications that runs those checks, offers a fix for each one that fails, asks Flipcash to send a test notification, and shares a plain-text report for support.

Spec: code-payments/flipcash-client-orchestrator#73 (Android and Both items). Figma page 12952:56; S5 is the Android frame.

Checks and registration

  • New features/notification-status module. The status model is a pure function from a device snapshot plus the last registration record to rows and a summary, with no Android types. The device read is the only Android-facing part.
  • Android checks a1–a7: permission, per-channel and group blocks, other categories, battery optimization, background restriction plus standby bucket, and Play services.
  • PushRegistrar in authentication records each AddToken attempt per account, with the time of the last ok, and clears the record on logout. Login, token refresh and the status page all go through it. If the page closes mid-attempt, or the timeout fires before a token is fetched, nothing is saved.
  • AddToken has no deadline and waits for ready, so in airplane mode it never returns. The registrar applies its own 10 s timeout at every call site and classifies timeouts, UNAVAILABLE and IO failures as "Couldn't reach Flipcash" rather than "Registration failed". That row is a Warning reading "Last confirmed " when an earlier ok is on record, and a Fail otherwise.
  • FirebasePushTokenProvider.deleteToken() now waits for the delete to finish, so Try again can't get the old token back.
  • All six channels and both groups are created at app start instead of on first post, so a channel a user has never received can still be checked.

Page

  • Rows start as Checking and change in place with a crossfade, so nothing jumps while checks run. A finished Fail or Warning sets the summary even while registration is still checking, and the summary's "Checked just now" line appears once the first run finishes.
  • A failing row puts the failure in its title and the explanation in the detail line. The all-pass fold and the rows hidden when the permission is off both animate.
  • A refresh icon in the top bar ("Check Again") re-runs every check, including a fresh AddToken. It's disabled while a run is in progress. Rows keep their titles and fix pills during the run; the pills are disabled until the row's result is in. The folded "N checks passed" row keeps its title and only its mark shows Checking. The summary card keeps the previous run's summary (e.g. "1 thing to fix") until the new run finishes, then animates to the new one. The same holds for Try Again on the registration row and the re-run after Turn On is granted; "Checking…" shows only on the first run after the page opens.
  • Coming back to the foreground re-reads only the phone's settings. Registration keeps its last result, so a folded page doesn't unfold and refold; it runs again only on open and on Check Again.
  • Each row reserves two lines of detail, Checking included, so a result that wraps doesn't push the rows below it.
  • At a font scale of 1.5 or more (the threshold UserLinkCard uses), the footer buttons scroll with the page instead of staying pinned.
  • Send Test Notification calls PushController.sendTestPush() with a 10 s timeout and shows loading while it's in flight. The server sends "Test Notification" with an empty payload, which NotificationService posts on the general channel (fcm_fallback_notification_channel, shown as "Misc.") with no navigation. The button is disabled while notifications or that channel are off, with a line under it saying which to turn on.
  • When the in-app Turn On is granted, every check runs again, registration included.
  • On success the button becomes Send Again and the floating toast host shows "Test notification sent" with a check icon. A timeout or transport failure shows "Couldn't reach Flipcash"; any other failure shows "Couldn't send a test notification". The footer says Flipcash sends the test to every phone signed in to this account.
  • Share Report's registration line describes this page's latest run. A run that saved nothing reads <status> (<reason>), e.g. fail (noToken), rather than an older record from login; a run still going reads checking. A Last ok: <time> line (or never) follows it.
  • Scroll edges use TopScrollEdge/BottomScrollEdge over haze, matching Settings. Group headers use SectionHeader in title case ("On This Phone", "With Flipcash"), as are the buttons.

Theme

  • New warningText colour token, named to match successText/errorText (the spec calls it textWarning). Flipcash uses the existing Flipcash2ColorSpec.warning (#FFA953); the Code theme uses Warning.

Copy not in the spec

  • The spec gives a2 copy for chat only. Deposit, buy/sell, gain and general channel rows use the same " notifications are off" pattern; the strings are in apps/flipcash/core strings.xml.

@bmc08gt bmc08gt self-assigned this Oct 28, 2026
@github-actions github-actions Bot added type: feature New functionality area: auth Login, session, access keys, identity area: ui Compose UI, theme, components, resources area: network gRPC, connectivity, API, exchange rates area: scanner QR/Kikcode scanning, camera area: notifications Push notifications, in-app messaging area: build-system Gradle, convention plugins, build-logic area: deeplinks Deep link handling, URL routing, and link parsing and removed type: feature New functionality labels Oct 28, 2026
@bmc08gt
bmc08gt force-pushed the feat/notification-status branch from d655512 to fbd985c Compare October 28, 2026 20:42
@github-actions github-actions Bot added the type: feature New functionality label Oct 28, 2026
AuthManager.updateFcmToken and NotificationService.onNewToken dropped the
AddToken result, so nothing could say whether push registration worked.
Both now go through PushRegistrar, which saves the outcome (ok,
invalidPushToken, transport, unknown), source and time per account, and
logout clears it. FirebasePushTokenProvider.deleteToken now awaits the
task so a following getToken can't return the deleted token.
Channels were created on first post, so a category that never fired had
no channel the user could turn off and nothing for the status page to
check. FlipcashApp now creates all six channels and both groups at
startup; the lazy create stays as a backstop.

Adds TestNotificationPoster for the status page's local test: posted on
channel_chat after 3 s from its own scope, with a fixed id and a tap
target of app.flipcash.com/settings/notifications.
The status page's checks as a pure function: a settings snapshot and the
registration result in, ordered rows and a summary out, with no Android
types. A blocked channel is a Fail or a Warning by what the user misses;
a transport failure is a Warning only if the server last said ok. The
report builder prints each check with its raw value and only the last
six characters of the token.
The page drew nothing until the device read landed, then snapped the
expanded groups into the folded row once registration finished. Rows
now have stable keys and a Checking placeholder per row, status changes
crossfade in place, rows that come or go and the all-pass fold animate,
and the page scrolls under both bars with a progressive blur as
Settings does.
Spec Behaviour 7 now puts Check again in the top bar only, with no
pull-to-refresh, so the summary-card button and PullToRefreshBox go.
CircularIconButton gains an enabled flag so the action can be inert
while a run is in progress.

The status page Card painted a rounded background without clipping,
so the folded row's ripple filled square corners. It now clips before
any clickable.
Buttons and group headers are title case; the page no longer uppercases
its headers. A sent test shows "Test notification sent" in the floating
toast host instead of the green note under the button. Each row reserves
two lines of detail, and at a font scale of 1.5 or more the footer
buttons scroll with the page instead of staying pinned.
…text

At large font scales the actions scroll inside the hazeSource, so the
glass would sample its own ancestor.
iOS leads this toast with checkmark.circle.fill in the text colour. Pass the
existing ic_check_circle, drawn to match that symbol, through the toast's icon.
Login and refresh called the registrar with no timeout, so a hung AddToken
recorded nothing. The 10 s limit is now the registrar's default and covers
the token delete, the token fetch and AddToken for every call site.

The saved record gains lastOkAt. Only an ok sets it; any other result carries
the previous value forward, and logout clears it with the rest of the record.
Records written before this change still decode, with no lastOkAt.

A timed-out attempt now reports the token it sent instead of none.
…d report

- Summary: a finished Fail or Warning wins over a row still checking. The
  "Checked just now" line is hidden until the first run finishes.
- The transport Warning keys off lastRegistrationOkAt from the record read
  before the attempt, not the last saved result.
- Remove the Unknown status and its "The check didn't finish" string. A page
  closed mid-run saves nothing.
- A grant from the in-app Turn On re-reads the phone and registers again.
- Registration failures put the failure in the row title: no token, rejected
  token and "Registration failed".
- The test button is also disabled while channel_chat is off, with "Turn on
  chat notifications to send a test."
- Report: the registration line is "<status> (<result>, <source>, <time>)",
  the result left out when ok, names in camelCase. The token is the one the
  latest attempt sent, or none.
A timeout during deleteToken() or getToken() returned Transport and saved a
transport record, though AddToken was never called. Save only when a token
was sent.
…heck Again

The folded "N checks passed" row keeps its title; only its mark shows
Checking. A row checking again keeps its fix pill, disabled until the result
is in. The registration row keeps the Try Again from the page's last run.
…ok time

The registration line printed the saved record even when the latest run saved
nothing, so a run with no token showed an older login result. It now reads
<status> (<reason>) for such a run, e.g. fail (noToken), and checking while
the run is in flight. A Last ok line follows it.
@bmc08gt
bmc08gt force-pushed the feat/notification-status branch from 97fd484 to 3fcfcca Compare October 29, 2026 10:56
The test button now calls SendTestPush, so a notification that arrives
shows Flipcash's servers can reach the phone, not only that it can post.

The button loads and disables while the call runs, with a 10 s timeout.
OK reads "Send Again" and toasts "Test notification sent"; a transport
failure or the timeout toasts "Couldn't reach Flipcash"; anything else
toasts "Couldn't send a test notification".

The server's test push has an empty payload, so NotificationService
posts it on the default channel (system name "Misc.") with the launcher
as its tap target. The test button now gates on that channel instead of
chat.

Removes TestNotificationPoster, its strings, and the
/settings/notifications deeplink, which only the poster used.
Check Again turned every row to Checking, so the summary card flickered
to "Checking…" for the 0.6 s the run takes. The view model now keeps the
summary from the last finished run until the new run finishes, and the
card crossfades to the new one. "Checking…" shows only on the first run
after the page opens.
…n On

The summary getter from d587f39 keeps the last finished summary whenever
any check is running, so Try Again and the re-run after Turn On is granted
already held it. These tests pin that: neither run shows "Checking…" or an
in-between summary before the new result.
The disabled test button now reads "Turn on Misc. notifications in Android
settings to send a test." The channel can only be turned on from system
settings, and the old copy didn't say so.
A SendTestPush that ends in gRPC CANCELLED now shows "Couldn't reach
Flipcash", as the spec asks. CANCELLED is added only to the test-push
classification; AddToken registration keeps its old transport codes.
Coroutine cancellation still propagates, since foldWithSuppression
rethrows it before the error is built.

Tests pin the CANCELLED case, a cancelled send showing no result, a
failed send after a successful one keeping "Send Again", and a push with
no or undecodable payload opening the app without navigating.

Also corrects the channel comment for the blocked-channel footer.
…k channel Misc.

Passing-row titles and the report's check names are now title case, per the spec.
The blocked fallback channel row reads "Misc. notifications are off", the name
Android settings show for fcm_fallback_notification_channel.
…nnel

The server's test push has no payload, so it landed on
fcm_fallback_notification_channel, which is IMPORTANCE_DEFAULT and never
pops up as a heads-up. An app can't raise an existing channel's
importance, so the test gets its own channel, channel_test_push ("Test
Notifications", IMPORTANCE_HIGH), created at app start with the others.

The page sets a TestPushMark just before SendTestPush. While it is set,
NotificationService posts the next push with an empty payload on
channel_test_push and clears the mark. The mark also clears when the
send fails, or 30 s after it was set. Every other push is unchanged.

The test button's channel gate now reads channel_test_push. The channel
has no row and is left out of Other Categories; the report lists it as
the seventh channel. The fallback channel's description changes to
"Other updates from Flipcash".
@bmc08gt
bmc08gt merged commit 32d8963 into code/cash Oct 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: auth Login, session, access keys, identity area: build-system Gradle, convention plugins, build-logic area: deeplinks Deep link handling, URL routing, and link parsing area: network gRPC, connectivity, API, exchange rates area: notifications Push notifications, in-app messaging area: scanner QR/Kikcode scanning, camera area: ui Compose UI, theme, components, resources type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant