Repository navigation
feat(notifications): notification status page in Settings - #1153
Merged
Merged
Conversation
bmc08gt
force-pushed
the
feat/notification-status
branch
from
October 28, 2026 20:50
d7b0f29 to
c9eba4e
Compare
Settings › Advanced › Notifications checks the phone's notification settings and the push registration, offers a fix for each problem, posts a local test notification and shares a plain-text report. PushController now records every AddToken outcome per account instead of dropping it, keeps the last APNs registration error, and returns the full UNNotificationSettings from fetchStatus.
The page opened on a spinner, then inserted rows as each check
returned, so the list jumped while it loaded. It now lays out every
check as Checking from the first frame (`NotificationStatus.checkingPage`)
and animates each status, the hidden permission-off rows, and the
"N checks passed" fold in place. Rows have a fixed minimum height so a
detail line coming or going doesn't move its neighbours.
The buttons move into a bottom `scrollEdgeBar`, so the checks fade out
behind them, and the group headers drop their negative padding to sit
like SettingsScreen's.
A refresh action in the navigation bar ("Check again") re-reads the
phone's settings and runs a fresh `AddToken`. The rows keep their text
and show Checking until the run ends; the action is disabled while a
run is in progress. `NotificationStatusViewModelTests` covers both.
…er a minute The group headers read "ON THIS PHONE" / "WITH FLIPCASH" and the fix buttons "Turn on" / "Try again"; the spec's Copy table has them in title case. "Last confirmed" passed a sub-minute save straight to RelativeDateTimeFormatter, which reads "0 minutes ago"; it now reads "just now".
…er when large - Buttons are title case. The group headers use ListHeader, since SettingsSectionHeader uppercases its text. - "Send Test Notification" shows "Test notification sent" through the floating toast host instead of the green text under the button. - A return to the foreground re-reads only UNNotificationSettings. Registration keeps its last result, so a folded page no longer unfolds and refolds while AddToken runs again. - Each row reserves a title and two lines of detail, Checking included, so a result that wraps doesn't push the rows below it. - At accessibility text sizes the footer scrolls with the checks instead of staying pinned.
Tapping the test notification set the You stack to the status page alone, so Back went to the You tab's root. navigate(to:under:) now pushes parent destinations beneath the target, and the tap routes through Settings, as Android does.
…ility sizes
Beside a Try Again pill at AX5, "This phone isn't registered with Apple"
had a column a few letters wide and broke mid-word ("registe / red").
…nknown A timeout after a confirmed registration never became a Warning: the attempt saved `transport` before the view model read the record, so `saved.result == .ok` could not hold. The record now carries `lastRegistrationOkAt`, set only by an `ok` from any call site, and the view model reads it before the attempt starts. A failure with that time set is a Warning, "Last confirmed <relative time>". - One 10 s deadline covers the token delete, the token fetch and AddToken; the delete was unbounded and the fetch and AddToken each had their own 10 s. - A run that fetched no token saves nothing, including a failed Firebase fetch with an APNs token present. A run cancelled by the page closing saves nothing either. - The Unknown status and its copy are gone; closing the page mid-run drops the run. - Check Again after INVALID_PUSH_TOKEN deletes the local token first. - The report's token is the one sent in the latest attempt, or "none", and its registration line reads `<status> (<result>, <source>, <time>)`. - The summary's second line waits for every check to finish and stays through Check Again. - "N checks passed" stays as a toggle above the groups it opens, and shows Checking during Check Again.
…gister only on grant Try Again and the Turn On re-run evaluated with the registration Checking, which cleared the summary's second line until the run finished. The view model now passes whether a run has finished before, and the summary keeps "Checked just now" once one has. The in-app Turn On re-ran every check whatever the answer. It now re-reads the settings and runs Registration again only when permission is granted, since the phone can't register while notifications are off.
… line The registration line now describes the page's latest run only: "<status> (<result unless ok>, <source>, <time>)", "fail (noAPNsToken)" when the run saved nothing, and "checking" while it runs. The older record's "; last ok, ..." suffix is replaced by a separate "Last ok: <time>" line, or "Last ok: never". The header writes a zero offset as UTC+0, like UTC-4 elsewhere.
bmc08gt
force-pushed
the
feat/notification-status
branch
from
October 29, 2026 10:56
0916520 to
0e37fbe
Compare
The test button now calls SendTestPush with a 10 s deadline instead of posting a local notification, so a test that arrives shows the server can reach this phone. While the call runs the button shows its loading state. OK switches it to "Send Again" with a "Test notification sent" toast; a transport failure, cancellation or timeout toasts "Couldn't reach Flipcash", and any other failure "Couldn't send a test notification". The server's push carries an empty Payload. NotificationDelegate already presents a push it can't decode as a chat and routes nothing on tap, so the flipcash_test branches and the tap route to the status page go away. A presentation test pins the empty-Payload case.
Its only caller was the local test notification's tap route, removed in the previous commit.
During Check Again the summary card now holds the previous run's summary and animates to the new one when the run finishes; "Checking…" shows only on the page's first run. The folded row reads its Checking mark from the rows instead of the summary, so it still shows Checking during the re-run.
Try Again and the registration re-run after Turn On is granted now hold the summary the page showed before the tap until registration finishes, as Check Again already does. "Checking…" shows only on the page's first run. Turn On captures the summary before the permission request, because the settings re-read that follows would otherwise change it before registration starts.
…ellation withDeadline ran the call in a task group, which waits for every child before returning. SendTestPush runs behind a continuation that ignores cancellation, so a hung send waited for the 15 s gRPC deadline instead of the 10 s limit. The helper now races the call, the deadline and caller cancellation through one continuation and cancels whatever loses, so AddToken and the FCM token fetch get the same wall-clock limit. Timeouts still throw CancellationError, so the transport outcomes are unchanged. Also pins that a failed send after one that worked keeps Send Again, and corrects the screen doc: the test notification comes from Flipcash.
… sleep The previous stand-in looped on Task.sleep, which throws at once after cancellation, so it spun until its 2 s ran out. The deadline lock also takes checked state now that everything it holds is Sendable.
Row titles show on screen and in the copied report, and the spec now calls for title case there: Notifications Allowed, Sounds and Badges, Registered for Push. Problem titles and details stay sentence case.
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.
When a user says they aren't getting notifications, support has no way to see why. The iOS permission, the banner and Lock Screen settings, and whether this phone's token ever reached our servers are all invisible from the app, and
AddTokenfailures were dropped without a trace.This adds Settings › Advanced › Notifications, a page that runs those checks, says which one is failing, and links each failure to its fix. Spec: code-payments/flipcash-client-orchestrator#73. Design: Figma page 12952:56.
NotificationStatusin FlipcashCore is a pure function from a settings snapshot plus the last registration result to the page's rows and summary. No UIKit or UserNotifications types, so every spec test case it covers is a unit test (NotificationStatusTests).PushController.fetchStatusnow returns the fullUNNotificationSettingsinstead of onlyauthorizationStatus; its callers in onboarding and the swap completion read.authorizationStatusfrom it.NotificationRegistrationStore) and cleared on logout.AddTokenerrors from the Firebase and APNs token callbacks are recorded and reported instead of dropped, anddidFailToRegisterForRemoteNotificationssaves its error for the page.AddToken) under one 10 second deadline. An attempt that never got a token saves nothing; closing the page mid-run drops the attempt and saves nothing.lastRegistrationOkAt, set by anyokand cleared on logout. A failed attempt after an earlierokshows as a Warning ("Last confirmed …") instead of a Fail.INVALID_PUSH_TOKENresult deletes the local token first, as Try Again does.AddToken. Registration runs on open, on Check Again, on Try Again, and after the in-app Turn On when permission is granted (not on deny); returning from iOS Settings re-readsUNNotificationSettingsand leaves the registration row on its last result.SendTestPushwith a 10 second deadline, and the button shows its loading state while the call runs.OKswitches it to Send Again with a "Test notification sent" toast, and a failed send after that leaves it on Send Again. A transport failure, cancellation or timeout toasts "Couldn't reach Flipcash"; any other failure toasts "Couldn't send a test notification". The footer reads "Flipcash sends the test to every phone signed in to this account." The tap re-reads the permission first and sends nothing if it went away.Payload.NotificationDelegatealready presents a push with no chat payload as a foreground banner and routes nothing on tap, so the push needs no special case;NotificationPresentationTestspins the empty-Payloadcase. The earlier localflipcash_testnotification, its delegate branches, its tap route andAppRouter.navigate(to:under:)are gone.PushController.withDeadlinereturns at the deadline even when the call ignores cancellation. A task group would wait for every child, andSendTestPushruns behind a continuation that ignores cancellation, so a hung send would hold the button until the 15 s gRPC deadline.DeadlineRaceinstead races the call, the deadline and caller cancellation and cancels whichever loses. The same limit coversAddTokenand the Firebase token fetch and delete. A timeout still throwsCancellationError, so it maps to the same transport result as before.PushControllerDeadlineTestscovers a call that ignores cancellation, a caller cancelled mid-call, and a value or error that arrives before the deadline.pass (statusPage, <time>),fail (noAPNsToken),checking), followed by a separateLast ok: <time>line, orLast ok: never. A zero offset is writtenUTC+0.textWarningalready exists inColors.swift, so the warning rows reuse it rather than adding the token the spec asks for.