8 Commits
Author SHA1 Message Date
tyiuandClaude Sonnet 5 bb4602e8fa Resolve "EU" through platform locale data instead of hardcoding it
Verified directly (java.util.Locale, NSLocale, and Intl.DisplayNames
all tested outside the app) that every platform's locale data resolves
"EU" correctly on its own — "European Union" in en, "Union européenne"
in fr, "Europäische Union" in de, etc. — via CLDR, which defines "EU"
as a grouping in its own right despite it not being a real ISO 3166-1
country code. The hardcoded English fallback was based on an
assumption that was never actually verified.

Removes the special case from the shared code entirely. That required
two platform-side fixes to stop blocking "EU" from ever reaching the
platform lookup:
- JVM/Android: dropped the Locale.getISOCountries() membership guard
  (which excluded "EU") in favor of detecting an unresolvable code by
  getDisplayCountry() echoing it back unchanged.
- Web: jsRegionDisplayName() was hardcoding locale 'en' for
  Intl.DisplayNames — changed to undefined (the browser's own locale),
  matching what the other platforms already did. Without this fix,
  "EU" would resolve, but only ever in English on web.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
2026-09-10 13:21:24 +03:00
tyiuandClaude Sonnet 5 3ce3bab7c6 Format the "updated" timestamp as a locale-aware date/time
Replaces the raw "2026-09-10 10:03:54" with each platform's own
locale-formatted, no-seconds rendering (e.g. "Sep 10, 2026, 10:03 AM"
in en-US) — mirroring how amounts are already locale-formatted
elsewhere (NumberFormat.kt). Android uses the pre-java.time
Calendar/DateFormat APIs since minSdk 24 predates java.time (API 26)
and the project has no core library desugaring configured.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
2026-09-10 11:57:19 +03:00
tyiuandClaude Sonnet 5 e5b31f3750 Hide flag emoji on the web target, where they render as empty boxes
Compose for Web (js/wasmJs) draws everything through Skia onto a
<canvas> rather than through the browser's own text stack, so it has
no color-emoji font to fall back to — a flag's regional-indicator
codepoints render as empty tofu boxes instead of a flag. Reproduced
via a headless-Chrome screenshot of the running dev server.

Adds a per-platform supportsFlagEmoji() (true everywhere except
js/wasmJs) that currencyFlagEmoji() now checks first, so currency
rows on web just show the plain code/name with no flag rather than a
broken glyph.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
2026-09-10 09:49:12 +03:00
tyiuandClaude Sonnet 5 eafaeaa8d3 Match currency search against issuing country names too
Searching "Japan" now finds JPY, "Canada" finds CAD, etc. — matching
just the currency's own display name wasn't enough, since e.g.
"Canadian Dollar" doesn't contain "Canada" as a substring.

Adds a per-platform regionDisplayName(regionCode) (java.util.Locale
on JVM/Android, NSLocale on Apple, Intl.DisplayNames on web) and
reuses CurrencyFlag.kt's currency-to-country-code derivation (now
extracted as issuingCountryCodes) to look up the country/countries
behind a currency code. "EU" is hardcoded to "European Union" since
it's not a real ISO 3166-1 code platform locale data can resolve.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy
2026-09-10 08:36:12 +03:00
tyiuandClaude Sonnet 5 5587428ca7 Add locale-aware digit grouping to displayed amounts
Adds groupDigits(), a display-only formatter that inserts the current
locale's digit-grouping separators into an amount's integer part (e.g.
"1,000,000.5" in en-US vs "1.000.000,5" in de-DE, where the roles of ","
and "." are swapped, or hi-IN's irregular "12,34,567"). Delegates to each
platform's native locale APIs (java.text.NumberFormat/DecimalFormatSymbols,
NSNumberFormatter/NSLocale, Intl.NumberFormat with BigInt for arbitrary
precision) rather than reimplementing grouping rules, and never touches the
canonical '.'-decimal strings the rest of the app parses and stores.

Applied via a Compose VisualTransformation (BTC/Sats/fiat/manual-rate
fields, plus the read-only rate display) and, on the native SwiftUI screen,
by re-deriving the field's displayed text on every change. Also made input
parsing locale-aware: sanitizeDecimalInput now recognizes the locale's own
decimal separator (e.g. what a locale-aware decimal keypad sends), not just
"." — with a stricter rule on the SwiftUI side specifically, since its
single text buffer re-feeds the grouped display back through sanitize, so
a "." can be grouping noise there rather than a decimal point.

The exceeds-max-supply warning's "21,000,000 BTC" is no longer baked into
the localized string with hardcoded grouping; it's substituted in at
runtime via groupDigits so the link-detection text always matches exactly,
in every locale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QpjMKWGoiT5aJBzhxvXkwp
2026-09-09 07:37:46 +03:00
tyiuandClaude Sonnet 5 323ca43472 Exclude withdrawn currencies from the picker, keep precious metals
Currency.getAvailableCurrencies() (JVM/Android) and NSLocale.ISOCurrencyCodes
(iOS/macOS) return every ISO 4217 code the platform has ever known about —
withdrawn currencies like the Deutsche Mark and French Franc, and test/
placeholder codes (XTS, XXX), included. Measured on the JVM: 232 total
codes, only 156 still assigned to a country.

Filters the list down to currencies actually assigned to an ISO country
today, which drops the withdrawn ones without needing a maintained
exclusion list. Precious metals (XAU, XAG, XPD, XPT) are added back in
since they're still actively traded despite not being tied to any country.
Web is left unfiltered — there's no reliable country-to-currency API there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QpjMKWGoiT5aJBzhxvXkwp
2026-09-08 17:20:09 +03:00
tyiuandClaude Sonnet 5 14298d450e Persist exchange rates, selected currencies, and price source via SQLDelight
Adds a SQLDelight-backed AppDatabase (Android, Desktop, iOS/macOS) so the
converter survives restarts: the last-known rates per price source, the
user's selected fiat currencies (with order preserved), and the last-used
price source all reload before the first network fetch completes. Web isn't
a shipped platform and SQLDelight's driver there needs a worker plus a wasm
sqlite binary, so it gets a lighter localStorage-backed implementation of
the same store interfaces instead.

Android needs an app Context for its driver, wired via a new
SatsPriceApplication. iOS/macOS need libsqlite3 linked explicitly (Swift's
autolinking only covers Shared.framework itself, not its C library
dependencies), plus two extension-loading symbols marked optional since
macOS's system sqlite build omits them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QpjMKWGoiT5aJBzhxvXkwp
2026-09-08 09:34:21 +03:00
tyiu abf7cf2e83 Replace Skip implementation with Kotlin Multiplatform implementation 2026-09-02 22:11:32 +03:00