mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-30 15:48:36 +00:00
AccountViewModel imported no Compose UI; it was pinned to commonsUI only because commons had no headless way to tell the user something, so every message was a compose-resources string. - commons.notices: UserNotice is a sealed catalog of what headless code tells the user (SignerNotice, InvoiceNotice, ZapNotice, ...), reported to a UserNoticeSink or worded through a UserNoticeResolver. commonsUI words every notice in notices/ui/UserNoticeText.kt with one exhaustive `when`; ToastManager is the sink and renders notices like any toast. - The payment helpers report notices instead of Res text and moved to commons: LightningInvoiceResolver, ZapPaymentHandler (its callers keep their (title, message, user) callbacks), V4VPaymentHandler and MeltProcessor. TopNavFilterState labels became a TopNavLabel enum, and NotificationSummaryState publishes plain chart data that commonsUI turns into the Vico model. - BaseAccountViewModel (commons) is the whole ViewModel minus its toast manager and the call/nest bridges, which stay in commonsUI's thin AccountViewModel subclass, so the screens' call sites are unchanged. 28 helpers it needed moved with it (relay filter assemblers, feed states, URI routing, the LRU caches), and 9 more ViewModels now take it from commons (Bolt12 offers, payment targets, Blossom blobs, lists, geohash chat, Cashu wallet, top-up and wizard). One behavior change: an LNURL server that answers with an error status is reported as "service unavailable" with its message, instead of being re-wrapped as "could not resolve". Covered by new LightningInvoiceResolver tests; ResUserNoticeResolverTest checks the wording resolves. Plan: commons/plans/2026-10-09-headless-account-viewmodel.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019F7k9DtPmTVJCPRTBZpJHe
93 lines
4.6 KiB
Markdown
93 lines
4.6 KiB
Markdown
# Headless AccountViewModel and typed user notices
|
|
|
|
Status: shipped (steps 1-3); follow-ups below
|
|
|
|
## Problem
|
|
|
|
`AccountViewModel` (3.2k lines) lives in `commonsUI`, so every ViewModel that
|
|
takes it is stuck there too, out of reach of `cli`. It imports no Compose UI. What
|
|
pins it is that `commons` has no headless way to say "tell the user this": every
|
|
user-facing message is a compose-resources `StringResource`.
|
|
|
|
- 46 `Res` strings in `AccountViewModel` itself (PoW publish failures, Concord
|
|
kicks and pin failures, signer errors, Bolt12, nutzap, Cashu redemption).
|
|
- The payment helpers it calls build their error text the same way
|
|
(`loadStringRes(Res.string.x, args)` then a `(title, message)` callback):
|
|
`ZapPaymentHandler`, `V4VPaymentHandler`, `LightningInvoiceResolver`,
|
|
`MeltProcessor`, plus `HttpStatusMessages` and `NwcResponseMessages`.
|
|
- ~34 headless helpers sit in `commonsUI` only because their users did
|
|
(relay filter assemblers, `AccountFeedContentStates`, `UriToRoute`, `CachedState`).
|
|
|
|
## Design
|
|
|
|
### Typed notices (`commons.notices`)
|
|
|
|
- `sealed interface UserNotice`: a typed fact about what happened
|
|
(`SignerNotice.ReadOnly`, `InvoiceNotice.CallbackNotFound(user)`, …), never text.
|
|
All subtypes live in `commons.notices` (a sealed hierarchy must share a
|
|
package), which doubles as the catalog of everything the app tells the user
|
|
from headless code.
|
|
- `fun interface UserNoticeSink { fun notify(notice: UserNotice) }`: where
|
|
headless code reports a notice (a toast in the GUI apps).
|
|
- `fun interface UserNoticeResolver { suspend fun resolve(notice): ResolvedNotice }`:
|
|
for headless code whose API already hands text to a caller (the payment
|
|
helpers' `onError(title, message, user)` callbacks keep their signatures, so the
|
|
zap buttons and dialogs don't change).
|
|
|
|
`commonsUI` implements both: one exhaustive `when` maps each notice to its
|
|
`Res` title/message (`commons.notices.ui`), `ToastManager` implements
|
|
`UserNoticeSink`, and `DisplayErrorMessages` renders a notice toast in
|
|
composition like any resource toast. Adding a notice without a rendering does
|
|
not compile. `cli` can supply its own resolver.
|
|
|
|
Rejected: a generic `MessageKey` that `commonsUI` maps to `Res`. It only moves
|
|
string keys around instead of naming what failed, and loses the exhaustiveness.
|
|
|
|
### Split the ViewModel
|
|
|
|
- `commons`: `open class BaseAccountViewModel` holds the whole headless body. It
|
|
takes a `UserNoticeSink` and a `UserNoticeResolver` and never sees `Res`.
|
|
- `commonsUI`: `class AccountViewModel : BaseAccountViewModel` adds only what
|
|
needs the UI module: its `ToastManager` (passed down as the sink), the Res
|
|
resolver, and the `CallSessionBridge` registration (Android's call activity
|
|
reads the UI type back out). Every member the UI calls is inherited, so the
|
|
hundreds of `accountViewModel.x` call sites don't change.
|
|
- ViewModels that need `launchSigner`/notices take `BaseAccountViewModel`;
|
|
ones that only need the account take `Account`.
|
|
|
|
## Steps
|
|
|
|
1. Move the ViewModels that only read `accountViewModel.account` (switch them to
|
|
`Account`).
|
|
2. Add `commons.notices`; convert the payment helpers and move them to `commons`.
|
|
3. Extract `BaseAccountViewModel` with its headless helpers; move the ViewModels it
|
|
unblocks.
|
|
|
|
## Outcome
|
|
|
|
- Step 1: 7 ViewModels switched to `Account` and moved.
|
|
- Step 2: `commons.notices` added; `LightningInvoiceResolver`, `ZapPaymentHandler`,
|
|
`V4VPaymentHandler` and `MeltProcessor` report notices and moved to `commons`.
|
|
`TopNavFilterState` labels became a `TopNavLabel` enum; `NotificationSummaryState`
|
|
publishes plain chart data and `commonsUI` builds the Vico model.
|
|
- Step 3: `BaseAccountViewModel` in `commons` with 28 helpers (relay filter
|
|
assemblers, feed states, URI routing, caches); 9 more ViewModels moved onto it.
|
|
|
|
One behavior change: an LNURL server that answers with an error status is now
|
|
reported as "service unavailable" directly, instead of being re-wrapped as "could
|
|
not resolve" with the first message inside.
|
|
|
|
## Follow-ups
|
|
|
|
Still in `commonsUI`, by blocker:
|
|
|
|
- `toastManager`/`Res` in the ViewModel itself: podcast and music composers.
|
|
- `CountFilter`/`RelayCountResult` carry `StringResource` labels: the relay-list
|
|
ViewModels (`BasicRelaySetupInfoModel` and its 7 subclasses).
|
|
- UI-module helpers: `uploadToDefaultServer*` (emoji packs, new user metadata),
|
|
`UserSuggestionState` (list display), `ChannelNewMessageViewModel`/`LevelFeedViewModel`
|
|
(Compose UI state), `TransactionFilter`, `distanceUnit`, `EditNestSheet`, and the
|
|
Blossom/Nests server lists.
|
|
|
|
`ToastManager`'s ~150 existing `Res` call sites stay: UI code may keep using `Res`.
|