Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9f0632c
perf: map
whes1015 Aug 12, 2026
7235f53
perf: lower sky animation cost on hidden tabs and low-end devices
whes1015 Aug 12, 2026
35b0348
perf: stop repainting the frozen sky on every scroll tick
whes1015 Aug 12, 2026
86d29fc
perf: pause native map render when its tab is hidden
whes1015 Aug 12, 2026
80b0869
feat: storage scan, breakdown, and bounded caches for the debug page
whes1015 Aug 12, 2026
f803e81
perf: cut wind overlay raster cost, allocation, and hidden-tab burn
whes1015 Aug 12, 2026
89cbf30
fix: stop hidden-tab animations that never paused
whes1015 Aug 12, 2026
9df4747
feat: yellow county outline for the typhoon satellite underlay
whes1015 Aug 12, 2026
18f550c
fix: keep admin borders above timeline rasters, 國界 on by default
whes1015 Aug 12, 2026
d80392e
feat: drop the 7-day cache expiry for a 350 MB oldest-first budget
whes1015 Aug 12, 2026
76abce4
ci: rerun build
whes1015 Aug 12, 2026
5c30c0b
feat: drop the iOS disk URL cache for a single SQLite authority
whes1015 Aug 12, 2026
6cd83a0
feat: wipe debug kernel snapshots from tmp on release launch
whes1015 Aug 12, 2026
360b614
fix: widen the wind overlay's speed-bucket counter past 255
whes1015 Aug 13, 2026
cf2fa2f
refactor: house the notification sounds and standardise their encoding
whes1015 Aug 13, 2026
8b09f81
chore: upgrade Flutter to 3.47 and refresh dependencies
whes1015 Aug 13, 2026
d15617a
fix: don't clear the township on the monitor's first status refresh
whes1015 Aug 13, 2026
f5e8f77
feat: draw the report map's intensity markers in code, drop the PNGs
whes1015 Aug 13, 2026
326b3d3
build: ship arm64-only release APKs and ignore android build output
whes1015 Aug 13, 2026
060e952
fix: stop double-counting the ETag cache in the storage pie
whes1015 Aug 13, 2026
779a368
feat: query travel times from a per-event interpolated source
whes1015 Aug 13, 2026
053f14a
chore: slim the bundled assets
whes1015 Aug 13, 2026
ab972a6
perf: parallelise launch and offload asset decoding to isolates
whes1015 Aug 13, 2026
c5946f8
docs: refresh the docs against the current codebase
whes1015 Aug 13, 2026
4639879
perf: trim per-tick map overhead across all layers
whes1015 Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
EOF

- name: Build Release APK
run: flutter build apk --release --target-platform android-arm64,android-x64
run: flutter build apk --release

- name: Upload Artifacts
uses: actions/upload-artifact@v4
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
/android/build/
/android/app/build/

# Signing
android/key.properties
Expand Down
31 changes: 20 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,38 @@ concerns live in exactly one place.
```
lib/
├── main.dart # entry point → bootstrap()
├── bootstrap.dart # init platform services (Firebase, DI) then runApp
├── bootstrap.dart # init platform services (Firebase, prefs, SNTP, DI) then runApp
├── app/ # app shell: wiring, not features
│ ├── app.dart # root MaterialApp.router + providers
│ ├── router/ # go_router route table (central AppRoutes registry)
│ ├── shell/ # bottom-nav shell (StatefulShellRoute)
│ └── theme/ # Material 3 theming + design tokens (spacing/radius/)
│ └── theme/ # Material 3 tokens (spacing/radius/motion/colour/glass)
├── core/ # cross-cutting, feature-agnostic building blocks
│ ├── di/ # SharedDeps assembly + coreProviders (provider wiring)
│ ├── error/ # Result + Failure hierarchy
│ ├── geo/ # geometry helpers (point-in-polygon)
│ ├── geo/ # location services, township directory/boundaries
│ ├── logging/ # the Log facade
│ ├── models/ # shared value types (LatLng, …)
│ ├── network/ # Dio ApiClient, region selection, error→Failure
│ ├── network/ # ApiClient + ApiTier + ApiPaths, region failover,
│ │ # SSE, ETag cache, meteor delta decode
│ ├── notifications/ # FCM/APNs + awesome channels, tap routing seam
│ ├── platform/ # native-first device_info / compass
│ ├── realtime/ # polling spine (channel/state/clock/staleness)
│ └── settings/ # app-wide persisted/ephemeral state (provider)
│ ├── platform/ # native-first device_info / compass / battery / tier
│ ├── realtime/ # polling spine (channel/state/clock/staleness/SSE)
│ ├── settings/ # typed Prefs facade + persisted/ephemeral controllers
│ ├── storage/ # SQLite stores (ETag cache, network usage)
│ └── weather/ # weather-condition / icon mapping
├── features/ # one folder per feature, each self-contained
│ │ # changelog · disaster_map · earthquake · events ·
│ │ # home · location · log · map · more · notification ·
│ │ # onboarding · settings · sponsor · typhoon · weather
│ └── <feature>/
│ ├── data/ # datasources, repository impls, JSON→model mapping
│ ├── domain/ # @freezed entities, repository interfaces (pure Dart)
│ └── presentation/ # pages/, widgets/, controllers (state)
└── shared/ # reused across ≥2 features
├── map/ # the reusable MapLibre surface (layers, timeline)
├── map/ # the reusable MapLibre surface (BaseMap, layers, timeline)
├── navigation/ # AppRoutes (route names/paths)
├── seismic/ # intensity/report colour scales
└── widgets/ # AsyncView/RealtimeView, region bar, …
```

Expand Down Expand Up @@ -70,6 +78,7 @@ lib/
## Native config

Platform integration (Firebase, APNs critical-alerts, signing, permissions) is
restored per-need in `android/` and `ios/`. Firebase reads native
`google-services.json` / `GoogleService-Info.plist`; `Firebase.initializeApp()`
in `bootstrap.dart` needs no generated options file.
restored per-need in `android/` and `ios/`. Firebase initializes from the
generated `lib/firebase_options.dart` (`DefaultFirebaseOptions`) — not the
native plists — so init never depends on a bundle resource; Android also keeps
`google-services.json` for the GMS plugin.
67 changes: 38 additions & 29 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Guidance for working in this repository. See `ARCHITECTURE.md` for the folder
structure, `api.md` for the API/region map, and `DESIGN.md` for the design
system (colours, spacing, radius, motion, typography, shared components).

DPIP is a Taiwan disaster-prevention app, mid-rewrite on the `rewrite` branch
(clean Flutter 3.44 baseline, feature-first architecture).
DPIP is a Taiwan disaster-prevention app, mid-rewrite (clean Flutter 3.47
baseline, feature-first architecture).

## Toolchain

Expand Down Expand Up @@ -58,14 +58,20 @@ DPIP is a Taiwan disaster-prevention app, mid-rewrite on the `rewrite` branch
(`AppSpacing` / `AppRadius` / `AppMotion`), `ColorScheme` roles, and shared
components (`shared/widgets/`). Never hardcode spacing, radius, duration, or
colour where a token/role exists. Full reference: `DESIGN.md`.
- **State management:** `provider`. App-wide services are provided in
`app/app.dart`; feature state lives in the feature's `presentation`.
- **Networking:** never call hosts directly — use the region-aware API surface
(`api/redundant_api.dart`, `api/exclusive_api.dart`, `api/external_api.dart`).
No DNS-balanced bare hosts. See `api.md`. `ApiClient` fails over to the next
region **only** on transient/server faults (connection drop, timeout, 5xx) and
logs each failover; a 4xx or a cancellation throws immediately (it would recur
on every region). Pass a `CancelToken` to abort a superseded request. Fatal and
- **State management:** `provider`. `bootstrap()` assembles the shared
infrastructure into a `SharedDeps` (`core/di/shared_deps.dart`) and hands it
to each feature's `*Providers(deps)` aggregate (wired through
`core/di/core_providers.dart`); feature state lives in the feature's
`presentation`.
- **Networking:** never call hosts directly — use the region-aware `ApiClient`
(`core/network/api_client.dart`) with an `ApiTier` from
`core/network/api_region.dart` (LB vs Core, exclusive vs multi-active) and
path constants from `core/network/api_paths.dart`. No DNS-balanced bare
hosts. See `api.md`. `ApiClient` fails over to the next region **only** on
transient/server faults (connection drop, timeout, 5xx) and logs each
failover; a 4xx or a cancellation throws immediately (it would recur on every
region). Pass a `CancelToken` to abort a superseded request. SSE streams go
through `ApiClient.openStream` + `core/network/sse_client.dart`. Fatal and
handled errors forward to an optional `CrashSink` set on `Log` (Crashlytics
wire-up point).
- **Data & errors (contract):** models are `@freezed` value types with generated
Expand Down Expand Up @@ -127,9 +133,8 @@ DPIP is a Taiwan disaster-prevention app, mid-rewrite on the `rewrite` branch
key; `app/` maps it to an `AppRoutes` tab). **External, not code:** upload an
APNs auth key to the Firebase console for iOS; push only works on a physical
device; permission is requested after the first frame for now (move to
onboarding); backend token registration (`/v2/location`) needs the not-yet-
ported location feature — the token is stored (`NotificationService.token`)
meanwhile.
onboarding). The push token registers through the location feature
(`/v2/location`, `DeviceLocationReporter` in `core/geo/`) on meaningful moves.
- **Native-first:** prefer platform channels / built-ins over third-party
plugins where practical (e.g. `core/platform/` device_info, compass).
- **Icons:** use Flutter's built-in Material `Icons` only — no third-party icon
Expand All @@ -140,18 +145,20 @@ DPIP is a Taiwan disaster-prevention app, mid-rewrite on the `rewrite` branch
- **Localization (i18n):** every user-facing string goes through
`AppLocalizations` (`AppLocalizations.of(context).<key>`) — never hardcode
display text. ARB sources live in `lib/l10n/` (`app_en.arb` is the template,
`app_zh.arb` is Traditional Chinese, the Taiwan default; `zh_Hant_HK` /
`zh_Hans` cover HK/Simplified); generated code is in `lib/l10n/gen/`. Each ARB
**self-describes** with a `languageName` key (the locale's own name), and the
language picker (`shared/widgets/language_picker.dart`) is built from
`app_zh.arb` is Traditional Chinese, the Taiwan default; `zh_TW`,
`zh_Hant_HK` / `zh_Hans` cover HK/Simplified, plus ja/ko/th/vi/fil/id);
generated code is in `lib/l10n/gen/`. Each ARB **self-describes** with a
`languageName` key (the locale's own name), and the language picker
(`shared/widgets/language_picker.dart`) is built from
`AppLocalizations.supportedLocales` + that key — never a hardcoded list. So a
language is added by just dropping in `app_<locale>.arb` (with `languageName`);
the home/fallback locale is the one constant in `core/settings/locale_config.dart`.
Enforced by `tool/check_l10n.sh` (a CI gate, no packages): ARB key-parity with
the template + no hardcoded CJK/kana/Hangul/Thai string literals in
`features/*/presentation/**` or `shared/widgets/**`. A genuinely non-display or
throwaway literal is exempted with `// l10n-ignore: <reason>` (that line/the one
above) or `l10n-ignore-file` in a file's header doc. Config in `l10n.yaml`.
the home/fallback locale is the one constant in
`core/settings/locale_config.dart`. Enforced by `tool/check_l10n.sh` (a CI
gate, no packages): ARB key-parity with the template + no hardcoded
CJK/kana/Hangul/Thai string literals in `features/*/presentation/**` or
`shared/widgets/**`. A genuinely non-display or throwaway literal is exempted
with `// l10n-ignore: <reason>` (that line/the one above) or
`l10n-ignore-file` in a file's header doc. Config in `l10n.yaml`.
- **Persistence keys (contract):** all `SharedPreferences` access goes through
the typed `Prefs` facade (`core/settings/prefs.dart`), keyed by a `PrefKey<T>`
from the `PreferenceKeys` registry (`core/settings/preference_keys.dart`) —
Expand All @@ -170,12 +177,14 @@ DPIP is a Taiwan disaster-prevention app, mid-rewrite on the `rewrite` branch
`.github/workflows/ci.yml` runs on every push/PR and must stay green. It uses
the mise-pinned toolchain and runs, in order: the layering gate
(`tool/check_layering.sh`), the localization gate (`tool/check_l10n.sh`), the
prefs gate (`tool/check_prefs.sh`), `dart format --set-exit-if-changed`, a
codegen-drift check (`build_runner` + `git diff --exit-code` — committed
`*.g.dart` / `*.freezed.dart` must match a fresh build), `flutter analyze`, and
`flutter test`. The three bash gates need only bash + python3 (no toolchain), so
they fail fast. Run these locally before pushing. Safety-critical seismic math
is pinned by golden tests (`test/features/earthquake/eew_estimator_test.dart`);
prefs gate (`tool/check_prefs.sh`), the lockfile gate (`tool/check_pubspec_lock.sh`),
`dart format --set-exit-if-changed`, a codegen-drift check (`build_runner` +
`git diff --exit-code` — committed `*.g.dart` / `*.freezed.dart` must match a
fresh build), `flutter analyze`, and `flutter test`. The four bash gates need
only bash + python3 (no toolchain), so they fail fast. `android.yml` /
`ios.yml` build release artifacts, and `review.yml` adds an automated PR
review. Run these locally before pushing. Safety-critical seismic math is
pinned by golden tests (`test/features/earthquake/eew_estimator_test.dart`);
if you change the EEW estimator, update those goldens deliberately.

## Commits
Expand Down
27 changes: 19 additions & 8 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ logging and localization — is covered at the end.
outline `#a9b4bc` / town `#6A6B72`. Light: bg `#E0E0E0` / fill `#ADADAD` /
outline `#6B6B6B` / town `#9A9A9A`.
- Literal colours are also allowed where no theme role applies: shader
fallback/mood colours (`weather_sky.frag` / `weather_sky_background.dart`) and
fallback/mood colours (`weather_sky_background.dart`, `_fallbackColour`) and
the one sheet shadow.

## Spacing — `AppSpacing`
Expand Down Expand Up @@ -85,25 +85,36 @@ leading icon.
Prefer Material `elevation` / tonal surfaces. The home sheet's custom top-edge
shadow is the one bespoke shadow; add more only with a clear reason.

## Glass over the weather backdrop

Content layered over the weather sky tints through `lib/app/theme/app_glass.dart`
(`glassSurface` / `glassOnSurface` / `inkOverWeather` …), driven by a `reveal`
dial: at rest a translucent theme surface, once revealed a pane of the sky
itself (sky colour at 20 % alpha, HSL-lightness shifted by time of day — see
`skyCardTint`). Ink follows the sky, not the theme. Shared surfaces built from
it: `shared/widgets/frosted_surface.dart`, `sheet_surface.dart`.

## Shared components — `lib/shared/widgets/`

- `SectionHeader(title)` — the small primary-tinted header above a settings/menu
group. Use it for every section instead of re-styling a `Text`.

Map foundations live in `lib/shared/map/` (`BaseMap`, `map_style`,
`map_snapshot`); see `api.md` for the tile/radar endpoints.
Map foundations live in `lib/shared/map/` (`BaseMap` in `base_map.dart`,
`map_style.dart` with `MapColors`/`MapPalette`, `map_tile_cache.dart`); see
`api.md` for the tile/radar endpoints.

## Logging (infrastructure)

Always log through `Log` (`lib/core/logging/log.dart`):
`Log.debug / info / warning / error / handle`. **Never** `print` / `debugPrint`
(`avoid_print` fails analysis). In-app viewer: More → 實驗性功能 is above it; the
log page is backed by the same `Log` history.
(`avoid_print` fails analysis). In-app viewer: the **App 日誌** page under the
More tab (`LogPage`), backed by the same `Log` history.

## Localization (infrastructure)

Every user-facing string goes through `AppLocalizations`
(`AppLocalizations.of(context).<key>`). ARB sources in `lib/l10n/`
(`app_en.arb` template, `app_zh.arb` Traditional Chinese default); generated code
in `lib/l10n/gen/`. Add a language by dropping in `app_<locale>.arb`. Never
hardcode display text.
(`app_en.arb` template, `app_zh.arb` Traditional Chinese default, plus
`zh_TW` / `zh_Hant_HK` / `zh_Hans` and ja/ko/th/vi/fil/id); generated code
in `lib/l10n/gen/`. Add a language by dropping in `app_<locale>.arb` (with a
`languageName` key) — never hardcode display text.
Loading
Loading