feat(mobile): Material iconography, icon tiles and a rebuilt dashboard - #141
Merged
Conversation
The app read as a wall of text with a coloured header. Four changes. Material icon set. The existing ad-hoc geometries are replaced with Material Design's own 24dp path data, and the set grows from nine icons to twenty-three - one per system category, one per payment-source type, plus the interface icons. Transcribed as path data rather than shipped as the MaterialIcons font, and that is a real trade worth stating: a Path takes a theme-bound Fill, where a font glyph is a Label whose colour has to be set at every call site. It also keeps the app free of a binary font asset and its licence file. The shapes are the same either way. (The font could not be fetched here regardless - this machine has no outbound TLS - so shipping it would have meant hand-waving a download step.) Icon tiles. A filled rounded square carrying one glyph, wherever a row identifies something: categories, payment sources, the dashboard breakdown. The wash behind the glyph is the item's own colour at 20% and the glyph is that colour at full strength, so contrast holds in both themes without a second palette. Square, not circular, on purpose - the subscription list already uses circles for provider logos, and an avatar and a category marker must not look like the same kind of thing. This replaces the 10px dot that used to sit beside category names, where hue was the only thing separating one row from the next. A colour cannot say "Fitness"; a dumbbell can. Dashboard. The three secondary figures moved out of the hero, where they were three more lines of white text competing with the headline, into their own tiles with an icon and accent each. The hero keeps the weekly figure and gains a currency badge in translucent white rather than a second brand colour. Colour. Success and three tile accents added as theme-bound token pairs, and the Android colorPrimaryDark/colorAccent moved off the near-black they were set to. Every new colour is an AppThemeBinding pair - a single-theme colour is unreadable text the moment the phone is in the other one. Category and payment icons deliberately fall back rather than guess: a dinner plate against "Pet insurance" reads as a bug where a neutral marker reads as "no icon for this". A test holds the icon and colour maps against each other, so a category cannot end up with a hue and no glyph. Not visually verified. The Android TFM needs a JDK this machine does not have, so the XAML was compiled through the Windows TFM - same source generator - and the 185 mobile tests pass. Someone should look at it on a device. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Amounts printed the three-letter code next to the number - "USD 15.99", "INR 1,699.50". A symbol is what people read money as, and on a lock-screen notification "₹1,699.50" lands where "1,699.50 INR" has to be parsed. Resolved from the code carried on each amount, not from CultureInfo. The device culture describes the phone, not the money: on a handset set to en-US, NumberFormat.CurrencySymbol renders a rupee-billed subscription with a dollar sign. The app is multi-currency by design - a dollar subscription sits in the same list as a rupee one, and the dashboard converts across them - so hardcoding ₹ would have been wrong in exactly the places that matter. The table is deliberately not exhaustive. A code earns a symbol only when that symbol identifies it on sight; anything ambiguous keeps its letters, because "kr" against SEK, NOK and DKK, or ₨ against PKR and LKR, tells the reader less than the code does. The dollar family is disambiguated the way CLDR does it - US$ bare, everyone else prefixed - since a lone $ beside a converted total is the one ambiguity worth spending characters on. Unknown codes fall through unchanged, so the worst case is what the app printed before. Entry fields on the detail and settings screens still take ISO codes: that is the stored value and what the API validates. Also aligns the burn-rate fallback with the schema. users.preferred_currency has defaulted to INR since DefaultPreferredCurrencyInr, while DashboardController still fell back to USD. It only fires if the row vanished mid-request, but two defaults disagreeing is the kind of thing that surfaces years later as one account inexplicably totalling in dollars. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g a code Settings asked for a three-letter code in a free-text Entry, which means knowing the code, and a typo only surfaced as a validation error after Save. It is now a picker showing symbol, code and name together - "₹ INR — Indian Rupee" - while the stored value stays the ISO code the API expects. The list is derived from the runtime's own region data, the same source SubVora.Application.CurrencyCodes validates against on the server, so the picker cannot offer something the API would reject. Sorted ordinally by code: stable, and independent of the device language, since a picker that reorders itself because the phone's locale changed would be its own bug report. INR remains the default, from users.preferred_currency - this only changes how the value is chosen, not what it starts as. Two cases worth calling out: - A stored code the runtime does not know - saved by another client, or since retired - is inserted into the list at its sorted position and selected. It would otherwise leave the picker on nothing, and any later Save would quietly rewrite the currency the user actually had. A test covers it, and caught the first attempt resolving the selection against the global list rather than the bound one. - A profile that fails to load leaves the selection empty rather than defaulting to INR. Showing a currency the user never chose, on a screen whose Save button is one tap away, would overwrite whatever is really stored. PreferredCurrency stays the source of truth and the two properties update each other, guarded so the handlers do not recurse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e button Add, edit, delete and profile-save all failed offline with "You appear to be offline." - true, and useless on a write. It leaves the reader to guess whether the change was stored locally and will sync later. It will not: the SQLite mirror is refreshed from successful GETs only and there is no write queue, so the change is discarded. The message now says that. ApiErrorMapper gains ToWriteFailureMessage, used only on write paths. Reads keep the old wording, because nothing was lost on a read - there is just nothing new to show. Login and register keep it too: a failed sign-in is not a discarded edit. IConnectivityService was registered in MauiProgram and had no consumers at all. The four write-capable view models now take it, expose IsOffline, and gate their button through CanSubmit, which folds in IsBusy so a second tap cannot land while the first write is running. Each screen also carries a short notice while offline, so the disabled button has a reason next to it. Connectivity is read when the screen loads and again after a failed write, rather than by subscribing to the service's event: these view models are transient while IConnectivityService is a singleton, so a subscription would outlive the screen it belongs to. Worth being precise about what this does and does not catch. IsConnected means "this phone has a network", not "the server is reachable" - the case that started this was wifi up and the local API down, which still reads as online and keeps the button enabled. The message is what carries the truth there, and a test pins exactly that scenario. This is deliberately not offline write support. Queuing changes needs an outbox, replay on reconnect, and conflict handling against the version token added in #138 - a queued edit can hit the same stale-version 409. Until that exists, the honest behaviour is to fail loudly rather than look like it saved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Visual pass on the mobile app. Branched from current
main(a20b3e0), so #140 is included.1. Material icons
Replaced the ad-hoc geometries with Material Design's own 24dp path data, and grew the set from 9 icons to 23 — one per system category, one per payment-source type, plus interface icons.
Path data rather than the MaterialIcons font, which is a real trade worth your call:
Pathtakes a theme-boundFill. A font glyph is aLabelwhose colour has to be set at every call site — the existing theme is built entirely onAppThemeBindingpairs, and the font would have opted every icon out of that.Also: this machine has no outbound TLS (revocation check fails), so I couldn't fetch the TTF — shipping it would have meant leaving you a manual download step. Say the word if you'd rather have the real font and I'll wire it up with the file committed.
2. Icon tiles — the "bold square" ask
A filled rounded square (42px, 13px radius) carrying one glyph, used wherever a row identifies something: categories, payment sources, dashboard breakdown rows.
The wash behind the glyph is the item's own colour at 20% alpha; the glyph is that colour at full strength. Contrast holds in both themes without a second palette.
Square, not circular, deliberately — the subscription list already uses circles for provider logos. An avatar and a category marker shouldn't look like the same kind of thing.
This replaces the 10px dot beside category names, where hue was the only thing separating one row from the next. A colour can't say "Fitness"; a dumbbell can.
3. Dashboard
The three secondary figures moved out of the hero — where they were three more lines of white text competing with the headline — into their own tiles, each with an icon and its own accent. The hero keeps the weekly figure and gains a currency badge in translucent white rather than a second brand colour.
4. Colour
Success plus three tile accents added as theme-bound token pairs; Android's
colorPrimaryDark/colorAccentmoved off the near-black they were set to. Every new colour is anAppThemeBindingpair — a single-theme colour becomes unreadable the moment the phone flips theme, which on Android is one swipe away.Behaviour kept deliberately
Icons fall back rather than guess. A dinner plate against "Pet insurance" reads as a bug; a neutral marker reads as "no icon for this". User-created categories always hit the fallback, since nothing can infer an icon from an arbitrary name.
Testing
185 mobile tests pass (19 new). The new tests cover the mappings, case-insensitivity, fallbacks, and one invariant worth having: category icons and category colours must cover the same set, so a category can't end up with a hue and a generic glyph.
The Android TFM needs a JDK this machine doesn't have (
XA5300), so the XAML was compiled through the Windows TFM — same XAML source generator — and the tests pass. Nobody has actually looked at it on a phone."Beautiful" is your call, not mine. Please run it on a device before merging, and tell me what to adjust — tile size, corner radius, accent hues, spacing are all one-line changes in
Theme.xaml.Not touched
SubscriptionListPageandSubscriptionDetailPagestill use their existing treatment (circular provider logos, chips). They're the next candidates if you like this direction, but they're the screens with the most working detail and I didn't want to churn them speculatively in the same pass.