Commit Graph

10 Commits

Author SHA1 Message Date
ghost
9ea8261919 feat(mobile): migrate all hardcoded UI strings to gen_l10n ARB (Phase 1 + 2) (#2384)
* feat(mobile): add Flutter i18n foundation (gen_l10n Phase 1)

- Wire flutter_localizations + gen_l10n (pubspec.yaml + l10n.yaml)
- Bump intl ^0.18.1 → ^0.20.2 for Flutter 3.32 pin
- Add lib/l10n/app_en.arb with 9 seed keys (appTitle, 3 common*, 4 chatSuggestion*)
- Generate AppLocalizations via flutter gen-l10n
- Replace MaterialApp title: with onGenerateTitle + localizationsDelegates + supportedLocales
- Migrate suggested_questions.dart from const list to BuildContext function
- Update chat_conversation_screen.dart call site (one-line change at pre-designed seam)

* feat(mobile): migrate all hardcoded UI strings to gen_l10n ARB (Phase 2)

Extends the i18n foundation from Phase 1 to cover every remaining
hardcoded user-facing string across the mobile app.

Files changed (21 source files + ARB/generated):
- lib/l10n/app_en.arb: 226 keys (+~80 new keys covering all screens)
- lib/screens/dashboard_screen.dart
- lib/screens/calendar_screen.dart
- lib/screens/transactions_list_screen.dart
- lib/screens/backend_config_screen.dart
- lib/screens/recent_transactions_screen.dart
- lib/screens/biometric_lock_screen.dart
- lib/screens/chat_conversation_screen.dart
- lib/screens/chat_list_screen.dart
- lib/screens/log_viewer_screen.dart
- lib/screens/login_screen.dart
- lib/screens/main_navigation_screen.dart
- lib/screens/more_screen.dart
- lib/screens/settings_screen.dart
- lib/screens/sso_onboarding_screen.dart
- lib/screens/transaction_edit_screen.dart
- lib/screens/transaction_form_screen.dart
- lib/widgets/account_detail_header.dart
- lib/widgets/category_filter.dart
- lib/widgets/connectivity_banner.dart
- lib/widgets/currency_filter.dart
- lib/widgets/custom_proxy_headers_editor.dart

Notable patterns:
- ICU plural for connectivity pending-sync count
- Parameterised keys for greeting (firstName), update dialog
  (version), cash chip (amount), delete confirmation (name),
  show-N menu (count), proxy-headers count, multi-delete count
- AppLocalizations captured before async gaps; dialog builders
  re-obtain from their own BuildContext
- const removed only from Text/InputDecoration/SnackBar/Row
  widgets that directly reference localisation getters; sibling
  const widgets preserved

116/116 unit tests pass; flutter analyze --no-fatal-infos: 0 errors.

* feat(mobile): migrate the remaining secondary UI strings to gen_l10n ARB

Completes the i18n migration by covering the lower-traffic strings the
earlier passes left behind — secondary dialogs, snackbars, settings
rows, validators, helper texts, and tooltips across 11 files.

- settings_screen.dart: reset/delete-account dialogs, clear-data and
  proxy-header snackbars, list-tile titles/subtitles, theme-segment
  tooltips, biometric prompt reason, debug-logs semantics label
- transaction_form_screen.dart: amount validators, session/success/
  failure/generic-error snackbars, More/Less toggle, date/name/category
  fields + helper texts, create button
- transaction_edit_screen.dart: session/update snackbars, validators,
  category/merchant/tag fallbacks, synced-only notice
- login_screen.dart: API-key dialog, demo/sign-up TextSpan, MFA info +
  validator, divider, server-URL heading, API-key + backend tooltip
- chat_list_screen.dart: multi-delete plural, result snackbars, error
  heading, relative-time fallback
- chat_conversation_screen.dart: edit-title dialog, refresh tooltip,
  load-error heading
- connectivity_banner.dart: sign-in / sync-success / sync-failure /
  auth-failure snackbars (also resolves the const removals cleanly)
- main_navigation_screen.dart: enable-AI dialog + failure snackbar
- log_viewer_screen.dart: clear-logs confirmation dialog
- biometric_lock_screen.dart, transactions_list_screen.dart: snackbar
  and edit tooltip

Adds 98 ARB keys (now 324 total), reusing common* and existing
screen keys where text matched. Interpolations use String/int
placeholders; multi-delete uses an ICU plural. AppLocalizations is
captured before async gaps and re-obtained inside dialog builders;
const removed only where a localisation getter is introduced, with
const restored on still-const siblings.

116/116 unit tests pass; flutter analyze --no-fatal-infos: 0 errors
(6 pre-existing infos unchanged). No remaining hardcoded user-facing
strings (verified by grep; only a doc-comment example remains).

* fix(mobile): address i18n review feedback and prune dead ARB keys

Resolves the still-valid CodeRabbit/Codex review findings on the i18n
migration; the majority were already handled by earlier commits.

- pubspec: switch intl to `any` so flutter_localizations selects the
  SDK-pinned version (0.19.x on <3.32, 0.20.x on 3.32+). Pinning
  ^0.20.2 conflicted with the declared `flutter: '>=3.27.0'` floor and
  would break `flutter pub get` on pre-3.32 SDKs.
- sso_onboarding: localize "Signed in as {email}" / "Google account
  verified" / the link- and create-form notes; fix the first/last-name
  validators that wrongly returned the "Email is required" message.
- backend_config: localize _testConnection / _saveAndContinue /
  _validateUrl messages and the headers helper text (validator now
  takes AppLocalizations; async methods capture it before awaits).
- recent_transactions: localize "Unknown Account"; locale-aware
  timestamp.
- account_detail_header: localize the unavailable-details message via a
  render-time flag (avoids touching inherited widgets in the
  initState-driven load path); treat empty/whitespace securityName as
  missing for the holding-name fallback.
- dashboard: wrap syncing/refreshing snackbar text in Expanded to avoid
  overflow with long translations; use the distinct dashboardSyncError
  key on the exception path.
- chat_list: localize relative-time labels (plural keys) and use a
  locale-aware fallback date; calendar: locale-aware displayed dates and
  weekday letters (map-key formats left fixed; Sunday-anchored grid
  preserved).
- connectivity_banner: capture ScaffoldMessenger before the async gap
  (removes use_build_context_synchronously); log_viewer: const Duration.
- Prune 55 unused ARB keys (never-wired scaffolding + superseded
  duplicates). ARB now 298 keys, 0 unused.

116/116 tests pass; flutter analyze --no-fatal-infos: 0 errors, only 3
pre-existing infos in the untouched intro_screen_web.dart.

* fix(mobile): localize the log viewer empty-state string

The 'No logs yet' empty state was the last hardcoded user-facing string
(the logViewerEmpty key had been pruned before this string was migrated).
Re-add the key and route the empty state through AppLocalizations.

Closes the final CodeRabbit i18n-coverage thread. 0 analyze errors,
116 tests pass, ARB at 299 keys with 0 unused.

* fix(mobile): address i18n review feedback

- sso_onboarding: the "Accept Invitation" tab and submit buttons showed
  the Terms-of-Service string (ssoOnboardingAcceptTerms) when a household
  invitation was pending. Add ssoOnboardingAcceptInvitation and use it in
  both spots; drop the now-unused ToS key (no ToS checkbox exists).
- transaction_form: restore the required-field indicator on the amount
  label ("Amount *") that the migration dropped.
- settings: localize the "a newer version" fallback used in the update
  dialog when the store version is unknown.
- ARB plurals: use the CLDR `one{}` category instead of the exact-match
  `=1{}` so future non-English locales pluralize correctly. English output
  is unchanged.

ARB at 300 keys, 0 unused. 116 tests pass; flutter analyze: no new issues.

* fix(mobile): reconcile i18n with design-system primitives after merge

The merge of main into this branch pulled in the design-system primitive
migrations (SureCard/SureChip/SureSegmentedControl/SureTextField/
SureListGroup), which collided with the i18n string migration and left
several files broken (compile errors / un-localized text).

Re-apply the i18n migration on top of the design-system versions:
- more_screen: SureListGroup/SureListRow rows now use l.more* keys
  (was a mangled mix of _buildMenuItem + SureListGroup).
- transaction_form_screen: SureSegmentedControl segments + amount/date/
  name/category labels, helpers, validators, and snackbars localized;
  add transactionFormAmountHelper ("Required") for the amount helper text.
- custom_proxy_headers_editor: SureTextField label/hint params localized.
- currency_filter: the "All" chip is a SureChip again (the merge had left
  it as a Material FilterChip) and uses l.commonAll.
- currency_filter_test / custom_proxy_headers_editor_test: add the
  localization delegates the widgets now require.

ARB at 301 keys, 0 unused. 165 tests pass; flutter analyze: 0 errors.

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-06-21 11:53:12 +02:00
ghost
69f8a5b95f feat(mobile): add SureSegmentedControl primitive and migrate the transaction type selector (#2382)
* feat(mobile): add SureSegmentedControl primitive and migrate the transaction type selector

Adds SureSegmentedControl<T>, the segmented-control primitive completing the
mobile form-control set after SureTextField and SureChip: an inset track of
equal-width segments where the selected segment is a raised surface (container
fill + the subtle DS shadow) and unselected segments are flat textSecondary
labels, resolving from the active SureColors palette. Each segment is
keyboard/switch focusable (FocusableActionDetector, mirroring SureButton) with
button + selected semantics; the selected fill is brightness-aware because the
palette has no single raised-surface token that reads correctly in both modes.

Migrates the transaction-form Type selector off Material SegmentedButton as
proof (selection behavior preserved).

Part of #2235.

* fix(mobile): assert unique SureSegmentedControl segment values

Selection is value-based (segment.value == selected), so duplicate
segment values would render multiple segments as selected at once. Guard
against that with a debug assert in build() (a const constructor can't
host the non-constant uniqueness check). Addresses CodeRabbit review.
2026-06-20 21:57:45 +02:00
ghost
65c4487bfc feat(mobile): add SureChip primitive and migrate the currency filter (#2380)
* feat(mobile): add SureChip primitive and migrate the currency filter

Adds SureChip, the filter-chip primitive following SureTextField in the mobile
design-system sequence: a tokenized selectable pill resolving from the active
SureColors palette — a bordered transparent pill when unselected and a filled
neutral (buttonPrimary + inverse label) pill when selected — replacing the
Material FilterChip primaryContainer tint + blue primary border. Includes
selected-state semantics (announced as a button only when interactive), a 44px
min tap target, and single-line ellipsised labels.

Migrates the currency filter off Material FilterChip as proof (selection logic
preserved). The segmented control will follow as a separate PR.

Part of #2235.

* fix(mobile): normalize stale codes in currency filter All detection

Length-based All detection could misfire when selectedCurrencies holds
stale codes no longer in availableCurrencies. Intersect with the
available set before comparing, and require containsAll before emitting
the empty (All) selection. Addresses CodeRabbit review.
2026-06-20 21:56:58 +02:00
ghost
61d56a8a3c feat(mobile): add SureTextField primitive and migrate the proxy-headers editor (#2378)
Adds SureTextField, the form-control primitive following SureButton in the
mobile design-system sequence: a TextFormField wrapper that builds a complete,
brightness-aware InputDecoration from the active SureColors palette (filled
bg-container, borderSecondary hairline -> borderPrimary on focus, destructive
error border, textSubdued placeholder, radiusLg corners) with a DS-style label
rendered above the field and associated to it for screen readers (Material
labelText parity via ExcludeSemantics + Semantics(label:)).

Migrates the custom proxy-headers editor off raw TextFormField as proof.
Filter chips and a segmented control will follow as separate PRs.

Part of #2235.
2026-06-20 21:43:41 +02:00
ghost
305c11d4a6 feat(mobile): add SureListGroup/SureListRow primitives and migrate the More menu (#2376)
* feat(mobile): add SureListGroup/SureListRow primitives and migrate the More menu

Adds the grouped inset-list primitives that follow SureCard in the mobile
design-system sequence: SureListGroup (tokenized container — bg-container +
borderSecondary hairline + radiusLg corners + shadowXs, with clipped inset
dividers and an optional section header) and SureListRow (leading / title /
subtitle / trailing slots, a DS chevron disclosure affordance, and a
destructive variant). Both resolve from the active SureColors palette, so
they're brightness-aware and stay in lockstep with sure.tokens.json.

Migrates the More menu off raw Material ListTile/Divider (which fell back to
colorScheme defaults) as proof. Adds the lucide chevron-right asset and
SureIcons.chevronRight for the row disclosure indicator.

Part of #2235.

* fix(mobile): restore button semantics on SureListRow + harden grouped list

Adversarial + CodeRabbit review follow-ups on the SureListGroup primitives:

- a11y: a tappable SureListRow lost the button/enabled semantics the migrated
  Material ListTile exposed (a bare InkWell emits only a tap action). Wrap the
  tappable content in MergeSemantics + Semantics(button) so screen readers
  announce each row as a single button again. Adds a semantics regression test.
- SureListGroup collapses to SizedBox.shrink() for an empty children list
  instead of painting a 2px bordered/shadowed box.
- More menu: restore the leading icon-badge tint to textPrimary (the migration
  had silently dropped it to the lower-contrast textSecondary).
- Tests: extend divider / title / subtitle / destructive assertions to dark mode
  (CodeRabbit nitpick), plus the new semantics + empty-group cases.
2026-06-20 21:43:29 +02:00
ghost
56000d7834 feat(mobile): add SureCard primitive and migrate account cards (#2370)
* feat(mobile): add SureCard primitive and migrate account cards

SureCard mirrors the web card chrome — `bg-container` + a hairline border +
rounded corners + the subtle DS shadow (`shadowXs`, from the scale shipped in
#2349) — resolved from the active SureColors palette so it stays in lockstep with
`sure.tokens.json` in light and dark. An optional `onTap` gives a flat ink
response clipped to the card radius.

Migrates AccountCard off the Material `Card` to `SureCard`.

Refs #2235.

* fix(coinstats): deterministic wallet batch order in bulk fetches

The bulk balance/transaction fetches built the "blockchain:address" param in
linked-account query order, which isn't stable across runs — so the batched
param (and its mocked expectations) varied seed to seed, intermittently failing
CoinstatsItem::ImporterTest and red-flagging unrelated PRs. Sort the wallets
before joining so the batch order is deterministic, and align the test
expectations to the sorted order.

* fix(mobile): tokenize swipe-reveal radius + cover SureCard dark theme

Address PR review:
- The account-card swipe-reveal background still used a hardcoded radius (12)
  that mismatched SureCard's radiusLg (10) once the card was migrated — tokenize
  it so the reveal corners line up during the swipe.
- Parameterize the SureCard chrome test over light + dark, since the card is
  brightness-aware (catches palette regressions in either mode).

* test(mobile): assert SureCard onTap ink is clipped to the card radius

Address review nit: the onTap test claimed the ink is clipped to the card but
only checked the InkWell exists. Now it asserts the InkWell's borderRadius equals
SureTokens.radiusLg (tester.widget already enforces a single InkWell).
2026-06-20 21:43:20 +02:00
ghost
94d5ad2151 fix(mobile): neutral Sure tokens for FAB/badge/avatar surfaces + themed logo (#2366)
On screens not yet redesigned with SureColors (the Chats list, etc.), Material's
primary/secondary *container* roles were unset, so they fell back to defaults
that read as blue on the FAB, unread-badge, and avatar surfaces.

- Pin `primaryContainer`/`secondaryContainer` to a neutral Sure surface
  (`surfaceInset` + `textPrimary`), and add a `FloatingActionButtonThemeData` so
  FABs use Sure's neutral primary action color (`buttonPrimary`). `primary` /
  `secondary` stay `link` / `info`, so existing link/accent callers are unchanged.
- Add a `SureLogo` widget that renders `logomark.svg` with the wordmark's
  `currentColor` strokes tinted to the theme's secondary text color, and route
  every logomark consumer (nav bar, login) through it — so the mark stays legible
  in dark mode (the hardcoded grey was too dim) and no caller renders the strokes
  as the flutter_svg default (black). The green brand mark keeps its fill.

Refs #2235.
2026-06-16 10:03:28 +02:00
ghost
4eca3b5b6b feat(mobile): add SureIcon (Lucide) primitive and migrate dashboard icons (#2346)
Introduce a SureIcon design-system primitive that renders bundled Lucide SVGs via flutter_svg (already a dependency — no new dep), mirroring the web `icon` helper / DS::FilledIcon: tokenized size (SureIconSize xs–2xl = 12–32), color inherited from the ambient IconTheme by default, and accessibility that distinguishes decorative icons (excluded from the semantics tree) from meaningful ones (semanticLabel). A SureIcons registry keeps call sites typo-safe and limited to bundled assets.

Migrate the dashboard surface off Material Icons.* onto SureIcon — 24 call sites across net_worth_card, account_card, and dashboard_screen (account-type glyphs, asset/liability trend icons, sync/empty/error states, refresh, collapsible section headers, expand chevrons). Bundle the 17 Lucide SVGs the surface needs under assets/icons/lucide/ with the ISC license. The swipe Undo control and the rest of the app's icons follow in later slices.

Part of #2235.

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-06-16 09:49:14 +02:00
ghost
7b6b74874f feat(mobile): add SureButton primitive and migrate login buttons (#2358)
Introduce a custom (non-Material) SureButton mirroring the web DS::Button: variants (primary, secondary, destructive, outline, ghost), sizes (sm/md/lg ≈ 28/36/48), optional leading widget, fullWidth, loading, and disabled. Colors resolve from the SureColors palette and the shape from radius tokens (radiusMd/radiusLg); flat custom press feedback (no Material ripple). Dimensions mirror the web DS::Buttonish::SIZES (the web composes these from Tailwind's standard scale rather than brand tokens, so they're not in sure.tokens.json).

Migrate the three main-view login buttons onto it — Sign In (primary, lg, loading), Sign in with Google (outline + SVG leading), and API-Key Login (ghost). The two buttons inside the API-key modal dialog are left for a follow-up.

Part of #2235.
2026-06-16 08:10:44 +02:00
ghost
e38632632c feat(mobile): standardize money typography and semantic amount color (#2331)
* feat(mobile): standardize money typography and semantic amount color

Add a brightness-aware SureColors theme extension and a MoneyText/SureMoney
primitive (semantic success/destructive/subdued tokens + tabular figures for
column-aligned digits), then migrate the transaction lists and balance cards
off raw Colors.green/red/grey.

Step 2 of the mobile design-system sequence (#2235), after #2237's theme
foundation. Primitive-first: screens consume shared tokens/typography.

* fix: review feedback — brightness-aware token fallback, de-flake Setting tests, Pipelock localhost FP

- SureColors.of falls back to the palette matching the active brightness (not
  always light) when the extension is missing, so dark surfaces stay correct.
- Clear the rails-settings-cached cache before each test; its in-memory cache
  survives the per-test transaction rollback, leaking Setting.* across tests and
  flaking Settings::HostingsControllerTest (stale empty string vs nil).
  Full unit suite: 4952 runs, 0 failures.
- Suppress the localhost test-DB DATABASE_URL false positive with line-level
  `# pipelock:ignore` in ci.yml + llm-evals.yml instead of excluding whole files,
  so those workflows stay scanned for real secrets.
2026-06-14 21:37:30 +02:00