Skip to content

Commit 501674b

Browse files
committed
refactor: cleanup
1 parent c822bce commit 501674b

81 files changed

Lines changed: 2881 additions & 1703 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project
6+
7+
DPIP (Disaster Prevention Information Platform) — a Flutter app for Taiwan earthquake early warning and disaster information, integrating TREM-Net and CWA data.
8+
9+
## Commands
10+
11+
```bash
12+
# Install dependencies
13+
flutter pub get --no-example
14+
15+
# Format
16+
dart format .
17+
18+
# Lint
19+
dart analyze .
20+
21+
# Code generation (required after editing routes, @JsonSerializable, or @freezed models)
22+
dart run build_runner build
23+
24+
# Update translations
25+
bash tools/update_translations.sh
26+
27+
# Run
28+
flutter run
29+
30+
# Build
31+
flutter build apk --release
32+
flutter build ios --release
33+
```
34+
35+
There is no test suite.
36+
37+
## Architecture
38+
39+
**State management:** Provider (`ChangeNotifier`). Global providers are registered in `lib/core/providers.dart`:
40+
- `DpipDataModel` — earthquake/weather data
41+
- `SettingsLocationModel`, `SettingsMapModel`, `SettingsNotificationModel`, `SettingsUserInterfaceModel`
42+
43+
**Routing:** go_router with type-safe routes via `@TypedGoRoute`. Routes are defined in `router.dart` and code-generated into `router.g.dart`. Run `build_runner` after route changes.
44+
45+
**Feature modules** live under `lib/app/` (home, map, settings, changelog, debug, welcome). Each is self-contained with its own widgets subfolder.
46+
47+
**API layer** (`lib/api/`): Dio HTTP client with caching. Models use `@JsonSerializable` + `@freezed` — regenerate with `build_runner` after changes.
48+
49+
**Core services** (`lib/core/`): FCM, GPS, local notifications, EEW logic (`eew.dart`), compass, i18n, device info.
50+
51+
**Assets:** JSON data files are gzip-compressed (`*.json.gz`) and decompressed at runtime. GLSL shaders (`fog.frag`, `thunderstorm.frag`) are used for map effects.
52+
53+
## Key Conventions
54+
55+
- **Settings naming:** Notification settings use numbered prefixes — `(1.eew)`, `(2.earthquake)`, `(3.weather)`, `(4.tsunami)`, `(5.basic)` — to control display order.
56+
- **Linting:** Extends `package:lint/strict.yaml`. Line width 100, preserve trailing commas, prefer single quotes.
57+
- **Documentation:** Every public member must have a doc comment (`///`). Every file must have a top-level library doc comment (`/// ...` before any `library` or first declaration). Follow Effective Dart: document usage and behavior from the caller's perspective, not internal implementation. Use `[...]` for code references, avoid restating the name.
58+
- **Dot shorthand:** Always use Dart dot shorthand (e.g., `.value` instead of `EnumType.value`) wherever the type can be inferred from context.
59+
- **Widget extraction:** When a build method nests too deeply, extract the subtree into a private widget class (`_FooWidget`) in the same file rather than keeping it inline.
60+
- **Class member order** (top to bottom), within each group sort alphabetically `A→Z` then `a→z`:
61+
1. Class fields
62+
2. Primary constructor
63+
3. Named constructors
64+
4. Uninitialized variables / private fields
65+
5. Private methods
66+
6. Public methods
67+
7. Overriding methods — widgets follow lifecycle order: `initState``build``dispose`
68+
8. Static fields
69+
9. Static members
70+
- **Extensions:** Always prefer extension methods from `lib/utils/extensions/` over verbose equivalents. Avoid `.of(context)` calls — use `BuildContext` extensions instead:
71+
- `context.theme``Theme.of(context)`
72+
- `context.colors``Theme.of(context).colorScheme`
73+
- `context.texts``Theme.of(context).textTheme`
74+
- `context.dimension``MediaQuery.sizeOf(context)`
75+
- `context.padding``MediaQuery.paddingOf(context)`
76+
- `context.brightness``MediaQuery.platformBrightnessOf(context)`
77+
- `context.navigator``Navigator.of(context)`
78+
- `context.scaffoldMessenger``ScaffoldMessenger.of(context)`
79+
- `context.router``GoRouter.of(context)`
80+
- `context.popUntil(path)``GoRouter.of(context).popUntil(path)`
81+
- `context.bottomSheetConstraints` → Material 3 bottom sheet constraints
82+
- **Generated files** (`**.freezed.dart`, `**.g.dart`) are excluded from analysis — do not edit them manually.
83+
- **Localization:** Uses `i18n_extension`. Translations are managed via Crowdin; only zh-Hant is updated locally via the translation script.
84+
- **Maps:** MapLibre GL with multiple layer managers. Dynamic color (Material You) via `dynamic_color`.

analysis_options.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ linter:
1515
public_member_api_docs: warning
1616

1717
formatter:
18-
page_width: 80
18+
page_width: 100
1919
trailing_commas: preserve

lib/app.dart

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class _DpipAppState extends State<DpipApp> with WidgetsBindingObserver {
7878
WidgetsBinding.instance.addPostFrameCallback((_) {
7979
final ctx = router.routerDelegate.navigatorKey.currentContext;
8080
if (ctx != null && mounted) {
81-
ctx.go(WelcomePermissionPage.route);
81+
WelcomePermissionsRoute().go(context);
8282
}
8383
});
8484
}
@@ -95,13 +95,7 @@ class _DpipAppState extends State<DpipApp> with WidgetsBindingObserver {
9595

9696
switch (widget.initialShortcut) {
9797
case 'monitor':
98-
ctx.push(
99-
MapPage.route(
100-
options: MapPageOptions(
101-
initialLayers: {MapLayer.monitor},
102-
),
103-
),
104-
);
98+
MapRoute(layers: MapLayer.monitor.name).push(context);
10599
break;
106100
}
107101
}

lib/app/changelog/_widgets/update_card.dart

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1+
/// A card widget that highlights an available app update.
2+
library;
3+
14
import 'package:dpip/utils/extensions/build_context.dart';
25
import 'package:flutter/material.dart';
36

7+
/// Displays a summary card for an available update.
8+
///
9+
/// Shows [title], [description], and an optional [onViewDetails] callback.
10+
/// The card uses a gradient derived from the current theme's primary color.
411
class UpdateCard extends StatelessWidget {
12+
/// The update title (typically the version name).
513
final String title;
14+
15+
/// A brief description of what changed in this update.
616
final String description;
17+
18+
/// Called when the user taps to view full release details.
19+
///
20+
/// When `null`, no interactive affordance is shown.
721
final VoidCallback? onViewDetails;
822

23+
/// Creates an [UpdateCard] with the given [title] and [description].
924
const UpdateCard({
1025
super.key,
1126
required this.title,
@@ -17,27 +32,29 @@ class UpdateCard extends StatelessWidget {
1732
Widget build(BuildContext context) {
1833
return Card(
1934
elevation: 8,
20-
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
35+
shape: RoundedRectangleBorder(
36+
borderRadius: .circular(20),
37+
),
2138
child: Container(
2239
decoration: BoxDecoration(
23-
borderRadius: BorderRadius.circular(20),
40+
borderRadius: .circular(20),
2441
gradient: LinearGradient(
25-
begin: Alignment.topLeft,
26-
end: Alignment.bottomRight,
42+
begin: .topLeft,
43+
end: .bottomRight,
2744
colors: [
2845
context.theme.primaryColor.withValues(alpha: 0.1),
2946
context.theme.primaryColor.withValues(alpha: 0.3),
3047
],
3148
),
3249
),
3350
child: Padding(
34-
padding: const EdgeInsets.all(20.0),
51+
padding: const .all(20),
3552
child: Row(
36-
crossAxisAlignment: CrossAxisAlignment.start,
53+
crossAxisAlignment: .start,
3754
children: [
3855
Expanded(
3956
child: Column(
40-
crossAxisAlignment: CrossAxisAlignment.start,
57+
crossAxisAlignment: .start,
4158
children: [
4259
Row(
4360
children: [
@@ -49,8 +66,8 @@ class UpdateCard extends StatelessWidget {
4966
const SizedBox(width: 10),
5067
Text(
5168
title,
52-
style: context.theme.textTheme.titleLarge?.copyWith(
53-
fontWeight: FontWeight.bold,
69+
style: context.texts.titleLarge?.copyWith(
70+
fontWeight: .bold,
5471
color: context.theme.primaryColor,
5572
),
5673
),
@@ -59,8 +76,8 @@ class UpdateCard extends StatelessWidget {
5976
const SizedBox(height: 12),
6077
Text(
6178
description,
62-
style: context.theme.textTheme.bodyMedium?.copyWith(
63-
color: context.theme.textTheme.bodyMedium?.color
79+
style: context.texts.bodyMedium?.copyWith(
80+
color: context.texts.bodyMedium?.color
6481
?.withValues(alpha: 0.8),
6582
),
6683
),
@@ -73,9 +90,13 @@ class UpdateCard extends StatelessWidget {
7390
height: 80,
7491
decoration: BoxDecoration(
7592
color: Colors.amber.withValues(alpha: 0.2),
76-
shape: BoxShape.circle,
93+
shape: .circle,
94+
),
95+
child: const Icon(
96+
Icons.update,
97+
size: 48,
98+
color: Colors.amber,
7799
),
78-
child: const Icon(Icons.update, size: 48, color: Colors.amber),
79100
),
80101
],
81102
),

lib/app/changelog/page.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/// The changelog page, displaying a list of GitHub releases for the app.
2+
library;
3+
14
import 'dart:async';
25

36
import 'package:dpip/api/exptech.dart';
@@ -14,7 +17,12 @@ import 'package:m3e_collection/m3e_collection.dart';
1417
import 'package:material_symbols_icons/material_symbols_icons.dart';
1518
import 'package:option_result/result.dart';
1619

20+
/// Displays the full release changelog fetched from GitHub.
21+
///
22+
/// Renders each [GithubRelease] with a sticky header and Markdown body.
23+
/// Pull-to-refresh triggers a fresh network fetch.
1724
class ChangelogPage extends StatefulWidget {
25+
/// Creates a [ChangelogPage].
1826
const ChangelogPage({super.key});
1927

2028
@override
@@ -23,6 +31,8 @@ class ChangelogPage extends StatefulWidget {
2331

2432
class _ChangelogPageState extends State<ChangelogPage> {
2533
final _refreshIndicatorKey = GlobalKey<ExpressiveRefreshIndicatorState>();
34+
35+
/// The most recently fetched releases result, or `null` while loading.
2636
Result<List<GithubRelease>, String>? releases;
2737

2838
Future<void> _refresh() async {
@@ -130,13 +140,17 @@ class _ChangelogPageState extends State<ChangelogPage> {
130140
}
131141
}
132142

143+
/// A [SliverPersistentHeaderDelegate] that renders a sticky release header.
144+
///
145+
/// Shows the release icon, name, publish date, and a "current version" badge
146+
/// when the release matches the installed app version.
133147
class _ReleaseHeaderDelegate extends SliverPersistentHeaderDelegate {
148+
/// The release whose metadata is shown in this header.
134149
final GithubRelease release;
135150

151+
/// Creates a [_ReleaseHeaderDelegate] for the given [release].
136152
const _ReleaseHeaderDelegate(this.release);
137153

138-
static const height = kToolbarHeight + 32;
139-
140154
@override
141155
double get minExtent => height;
142156

@@ -209,4 +223,7 @@ class _ReleaseHeaderDelegate extends SliverPersistentHeaderDelegate {
209223

210224
@override
211225
bool shouldRebuild(covariant _ReleaseHeaderDelegate oldDelegate) => true;
226+
227+
/// Fixed height of the header in logical pixels.
228+
static const height = kToolbarHeight + 32;
212229
}

lib/app/debug/logs/page.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
/// The debug logs page, displaying in-app Talker log output.
2+
library;
3+
14
import 'package:dpip/core/i18n.dart';
25
import 'package:dpip/utils/extensions/build_context.dart';
36
import 'package:dpip/utils/log.dart';
47
import 'package:flutter/material.dart';
58
import 'package:talker_flutter/talker_flutter.dart';
69

10+
/// Renders the full Talker log screen for in-app debugging.
11+
///
12+
/// Theming is derived from the current [BuildContext] color scheme so the
13+
/// screen respects light/dark mode automatically.
714
class AppDebugLogsPage extends StatelessWidget {
15+
/// Creates an [AppDebugLogsPage].
816
const AppDebugLogsPage({super.key});
917

10-
static const route = '/debug/logs';
11-
1218
@override
1319
Widget build(BuildContext context) {
1420
return TalkerScreen(

lib/app/home/_models/home_location.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1+
/// Model for temporarily overriding the home screen location.
2+
library;
3+
14
import 'package:flutter/material.dart';
25

6+
/// Holds an optional temporary location code for the home screen.
7+
///
8+
/// When [temporaryCode] is non-null, the home page uses it instead of the
9+
/// user's persisted location setting. Call [setTemporaryCode] to update and
10+
/// notify listeners.
311
class HomeLocationModel extends ChangeNotifier {
412
String? _temporaryCode;
513

14+
/// The current temporary location code, or `null` when none is set.
615
String? get temporaryCode => _temporaryCode;
716

17+
/// Updates [temporaryCode] to [code] and notifies listeners.
18+
///
19+
/// Does nothing if [code] equals the current value.
820
void setTemporaryCode(String? code) {
921
if (_temporaryCode == code) return;
1022
_temporaryCode = code;

0 commit comments

Comments
 (0)