Skip to content

fix(wallet): unify tab loading and stop the tutorial from showing for existing accounts - #1270

Merged
bmc08gt merged 1 commit into
code/cashfrom
fix/wallet-tab-loading
Aug 20, 2026
Merged

fix(wallet): unify tab loading and stop the tutorial from showing for existing accounts#1270
bmc08gt merged 1 commit into
code/cashfrom
fix/wallet-tab-loading

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Two reported defects on the wallet tab, one root cause: the tab renders from a local cache that starts empty on every fresh login, and had no way to tell "this account has no activity" from "we haven't looked yet."

1. New-user tutorial shown to an existing account

MessageDataSource.hasEverAddedMoney() and ChatMessageDataSource.hasEverTipped() were non-suspend flow factories that read the per-user DB — and, for hasEverTipped, userManager.accountIdonce at subscription time. The per-user Room DB is created at login, after singletons have built their flow graphs, so a subscriber that started first (the wallet tab composing while a soft login is still in flight) latched onto a constant false for the rest of the session. That pinned the onboarding checklist to "incomplete", so the tutorial stayed on screen and the 2×2 action grid (gated on hasAddedMoney) never rendered.

Both now resolve through FlipcashDatabase.observeInstance() — the pattern MessageDataSource.observeRecent() two functions below already used, with a comment naming this exact hazard.

Fixing the latch isn't sufficient on its own: the Room flows legitimately emit false from a genuinely-empty cache before the network sync lands, so the tutorial would still flash. ActivityFeedCoordinator now publishes a FeedSyncState (Unknown / Synced / Unavailable) maintained by fetchSinceLatest, which every refresh path funnels through. It resets to Unknown when API access is lost, so the next account waits for its own fetch rather than inheriting the previous session's verdict. Unavailable exists so an offline device stops waiting instead of spinning forever — the 60s poller upgrades it when it succeeds.

WalletViewModel.State.onboardingItems is now nullable (null = unknown), isNewUserTutorialComplete treats unknown as complete, and isAwaitingActivity covers the window before the feed has been reconciled. Cached rows short-circuit the wait: if there's already activity to draw, there's nothing to mistake for a new account.

2. Two loading states instead of one

BalanceHeader staged itself on balance == null while the body below it independently decided, from a still-empty cache, that this was a brand-new account and drew the tutorial — so the tab assembled in pieces. WalletScreenContent now gates the entire tab behind a single centred CodeCircularProgressIndicator until tokens have hydrated and the activity side has settled. BalanceHeader's own spinner is consequently unreachable on this screen; v1's BalanceScreen still uses it.

Gating

v2-only in effect. All UI changes are in WalletScreenContent / WalletViewModel, reached only via AppScreenContent's if (isNewUi) branch. BalanceScreenContent (v1) never reads onboardingItems, and BalanceHeader is untouched. The two data-source fixes are shared, but they're pure correctness fixes to flows whose only consumer is the v2 checklist.

Testing

  • New WalletLoadingStateTest — 9 pure-state cases covering the wait window, both exits from it (Synced and Unavailable), the cached-rows short-circuit, and the tutorial-visibility rules.
  • :apps:flipcash:app:assembleDebug and :apps:flipcash:features:balance:testDebugUnitTest both pass.
  • Verified on the Pixel 10 emulator: one centred spinner, then the full tab at once — no header-spinner-over-populated-body, no tutorial flash.

… existing accounts

Two defects, one root cause: the wallet tab reads a local cache that starts
empty on every fresh login, and had no way to tell "this account has no
activity" from "we haven't looked yet".

- MessageDataSource.hasEverAddedMoney() and ChatMessageDataSource.hasEverTipped()
  read the per-user DB (and, for the latter, the account id) once at subscription
  time. The DB is created at login, after singletons build their flow graphs, so a
  subscriber that started first latched onto a constant `false` for the whole
  session -- pinning the onboarding checklist to "incomplete" and leaving the
  new-user tutorial on screen for an established account. Both now resolve through
  FlipcashDatabase.observeInstance(), matching what observeRecent() already did.

- ActivityFeedCoordinator publishes a FeedSyncState so callers can distinguish an
  empty account from an unsynced cache. It resets to Unknown when API access is
  lost, so the next account waits for its own fetch. Unavailable exists so an
  offline device stops waiting instead of spinning forever; the 60s poller
  upgrades it.

- WalletViewModel.State.onboardingItems is nullable ("unknown"), and
  isAwaitingActivity covers the window before the feed has been reconciled. Local
  rows short-circuit the wait.

- WalletScreenContent gates the whole tab behind one CodeCircularProgressIndicator
  until tokens have hydrated and the activity side has settled. BalanceHeader's own
  spinner is consequently unreachable here; v1's BalanceScreen still uses it.

v2-only: BalanceScreenContent never reads onboardingItems and BalanceHeader is
untouched, so the v1 path is unaffected.
@github-actions github-actions Bot added the type: fix Bug fix label Aug 20, 2026
@bmc08gt
bmc08gt merged commit 12b1e27 into code/cash Aug 20, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the fix/wallet-tab-loading branch August 20, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant