docs(01-foundation): research phase domain
This commit is contained in:
550
.planning/phases/01-foundation/01-RESEARCH.md
Normal file
550
.planning/phases/01-foundation/01-RESEARCH.md
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
# Phase 1: Foundation - Research
|
||||||
|
|
||||||
|
**Researched:** 2026-03-15
|
||||||
|
**Domain:** Flutter app scaffolding, Drift SQLite, Riverpod 3 state management, Material 3 theming, ARB localization
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Phase 1 establishes the foundational architecture for a greenfield Flutter app targeting Android. The core technologies are well-documented and stable: Flutter 3.41.2 (current stable), Riverpod 3.3 with code generation, Drift 2.32 for SQLite persistence, and Flutter's built-in gen_l10n tooling for ARB-based localization. All of these are mature, actively maintained, and have clear official documentation.
|
||||||
|
|
||||||
|
The primary risk areas are: (1) Riverpod 3's new `analysis_server_plugin`-based lint setup replacing the older `custom_lint` approach, which may have IDE integration quirks, and (2) getting the Drift `make-migrations` workflow right from the start since retrofitting it later risks data loss. The developer is new to Drift, so the plan should include explicit verification steps for the migration workflow.
|
||||||
|
|
||||||
|
**Primary recommendation:** Scaffold with `flutter create`, add all dependencies in one pass, establish code generation (`build_runner`) and the Drift migration pipeline before writing any feature code. Use `go_router` with `StatefulShellRoute.indexedStack` for bottom navigation with preserved tab state.
|
||||||
|
|
||||||
|
<user_constraints>
|
||||||
|
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
### Locked Decisions
|
||||||
|
- **Color palette & theme:** Sage & stone -- muted sage green primary, warm stone/beige surfaces, slate blue accents. Color-shy: mostly neutral surfaces, color only on key interactive elements. Light mode: warm stone/beige surface tones. Dark mode: warm charcoal-brown backgrounds (not pure #121212 black). Use M3 color system with ColorScheme.fromSeed, tuning the seed and surface tints.
|
||||||
|
- **Navigation & tabs:** Thematic icons (checklist/clipboard for Home, door for Rooms, sliders for Settings). German labels from ARB files: "Ubersicht", "Raume", "Einstellungen". Default tab: Home. Active tab style: standard Material 3 behavior with sage green palette.
|
||||||
|
- **Placeholder screens:** Playful & light tone with emoji-friendly German text. Pattern: Material icon + playful message + action button. Home empty state guides user to Rooms tab. Rooms empty state encourages creating first room. All text from ARB files.
|
||||||
|
- **Settings screen:** Working theme switcher (System/Hell/Dunkel) + About section (app name, version, tagline). Grouped with section headers "Darstellung" and "Uber". Tagline: "Dein Haushalt, entspannt organisiert." Language setting hidden until v1.1.
|
||||||
|
|
||||||
|
### Claude's Discretion
|
||||||
|
- Theme picker widget style (segmented button, dropdown, or bottom sheet -- pick best M3 pattern)
|
||||||
|
- Exact icon choices for thematic tab icons (from Material Icons set)
|
||||||
|
- Loading skeleton and transition animations
|
||||||
|
- Exact spacing, typography scale, and component sizing
|
||||||
|
- Error state designs (database/initialization errors)
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
None -- discussion stayed within phase scope
|
||||||
|
|
||||||
|
</user_constraints>
|
||||||
|
|
||||||
|
<phase_requirements>
|
||||||
|
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|-----------------|
|
||||||
|
| FOUND-01 | App uses Drift for local SQLite storage with proper schema migration workflow | Drift 2.32 setup with `drift_flutter`, `make-migrations` command, `build.yaml` config, stepByStep migration strategy, schema versioning and test generation |
|
||||||
|
| FOUND-02 | App uses Riverpod 3 for state management with code generation | `flutter_riverpod` 3.3.x + `riverpod_annotation` 4.0.x + `riverpod_generator` 4.0.x with `@riverpod` annotation and `build_runner` |
|
||||||
|
| FOUND-03 | App uses localization infrastructure (ARB files + AppLocalizations) with German locale | `l10n.yaml` with `template-arb-file: app_de.arb`, `flutter_localizations` SDK dependency, `generate: true` in pubspec |
|
||||||
|
| FOUND-04 | Bottom navigation with tabs: Home (Daily Plan), Rooms, Settings | `go_router` 17.x with `StatefulShellRoute.indexedStack` for preserved tab navigation state, `NavigationBar` (M3 widget) |
|
||||||
|
| THEME-01 | App supports light and dark themes, following the system setting by default | `ThemeMode.system` default, `ColorScheme.fromSeed` with brightness variants, Riverpod provider for theme preference persistence |
|
||||||
|
| THEME-02 | App uses a calm Material 3 palette with muted greens, warm grays, and gentle blues | Sage green seed color with `DynamicSchemeVariant.tonalSpot`, surface color overrides via `.copyWith()` for warm stone/charcoal tones |
|
||||||
|
|
||||||
|
</phase_requirements>
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core
|
||||||
|
| Library | Version | Purpose | Why Standard |
|
||||||
|
|---------|---------|---------|--------------|
|
||||||
|
| flutter | 3.41.2 | UI framework | Current stable, required by Riverpod 3.3 |
|
||||||
|
| flutter_riverpod | ^3.3.0 | State management (Flutter bindings) | Community standard for new Flutter projects in 2026, compile-time safety |
|
||||||
|
| riverpod_annotation | ^4.0.2 | `@riverpod` annotation for code generation | Required for Riverpod 3 code generation workflow |
|
||||||
|
| drift | ^2.32.0 | Reactive SQLite ORM | Type-safe queries, migration tooling, compile-time validation |
|
||||||
|
| drift_flutter | ^0.3.0 | Flutter-specific database opener | Simplifies platform-specific SQLite setup |
|
||||||
|
| go_router | ^17.1.0 | Declarative routing | Flutter team maintained, `StatefulShellRoute` for bottom nav |
|
||||||
|
|
||||||
|
### Supporting
|
||||||
|
| Library | Version | Purpose | When to Use |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| riverpod_generator | ^4.0.3 | Code gen for providers (dev dep) | Always -- generates provider boilerplate from `@riverpod` annotations |
|
||||||
|
| drift_dev | ^2.32.0 | Code gen for Drift tables (dev dep) | Always -- generates typed database code and migration helpers |
|
||||||
|
| build_runner | ^2.11.1 | Runs code generators (dev dep) | Always -- orchestrates riverpod_generator and drift_dev |
|
||||||
|
| riverpod_lint | ^3.1.3 | Lint rules for Riverpod (dev dep) | Always -- catches `ref.watch` outside `build()` at analysis time |
|
||||||
|
| path_provider | ^2.1.5 | Platform-specific file paths | Used by drift_flutter for database file location |
|
||||||
|
| flutter_localizations | SDK | Material/Cupertino l10n delegates | Required for ARB-based localization |
|
||||||
|
| intl | any | Internationalization utilities | Required by gen_l10n for date/number formatting |
|
||||||
|
| shared_preferences | ^2.3.0 | Key-value persistence | Theme mode preference persistence across app restarts |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Instead of | Could Use | Tradeoff |
|
||||||
|
|------------|-----------|----------|
|
||||||
|
| go_router | Auto-route | go_router is Flutter team maintained, has first-class StatefulShellRoute support; auto_route adds more codegen |
|
||||||
|
| shared_preferences (theme) | Drift table | Overkill for a single enum value; shared_preferences is simpler for settings |
|
||||||
|
| ColorScheme.fromSeed | flex_seed_scheme | flex_seed_scheme offers multi-seed colors and more control; but fromSeed with .copyWith() is sufficient for this palette and avoids extra dependency |
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
```bash
|
||||||
|
flutter create household_keeper --org com.jlmak --platforms android
|
||||||
|
cd household_keeper
|
||||||
|
flutter pub add flutter_riverpod riverpod_annotation drift drift_flutter go_router path_provider shared_preferences
|
||||||
|
flutter pub add -d riverpod_generator drift_dev build_runner riverpod_lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `flutter_localizations` and `intl` are added manually to `pubspec.yaml` under the `flutter_localizations: sdk: flutter` pattern.
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Recommended Project Structure
|
||||||
|
```
|
||||||
|
lib/
|
||||||
|
├── app.dart # MaterialApp.router with theme, localization, ProviderScope
|
||||||
|
├── main.dart # Entry point, ProviderScope wrapper
|
||||||
|
├── core/
|
||||||
|
│ ├── database/
|
||||||
|
│ │ ├── database.dart # @DriftDatabase class, schema version, migration strategy
|
||||||
|
│ │ ├── database.g.dart # Generated Drift code
|
||||||
|
│ │ └── database.steps.dart # Generated migration steps
|
||||||
|
│ ├── router/
|
||||||
|
│ │ └── router.dart # GoRouter config with StatefulShellRoute
|
||||||
|
│ ├── theme/
|
||||||
|
│ │ ├── app_theme.dart # Light/dark ThemeData with ColorScheme.fromSeed
|
||||||
|
│ │ └── theme_provider.dart # Riverpod provider for ThemeMode
|
||||||
|
│ └── providers/
|
||||||
|
│ └── database_provider.dart # Riverpod provider exposing AppDatabase
|
||||||
|
├── features/
|
||||||
|
│ ├── home/
|
||||||
|
│ │ └── presentation/
|
||||||
|
│ │ └── home_screen.dart # Placeholder with empty state
|
||||||
|
│ ├── rooms/
|
||||||
|
│ │ └── presentation/
|
||||||
|
│ │ └── rooms_screen.dart # Placeholder with empty state
|
||||||
|
│ └── settings/
|
||||||
|
│ └── presentation/
|
||||||
|
│ └── settings_screen.dart # Theme switcher + About section
|
||||||
|
├── l10n/
|
||||||
|
│ └── app_de.arb # German strings (template file)
|
||||||
|
└── shell/
|
||||||
|
└── app_shell.dart # Scaffold with NavigationBar, receives StatefulNavigationShell
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 1: Riverpod Provider with Code Generation
|
||||||
|
**What:** Define providers using `@riverpod` annotation, let build_runner generate the boilerplate
|
||||||
|
**When to use:** All state management -- no manual provider declarations
|
||||||
|
**Example:**
|
||||||
|
```dart
|
||||||
|
// Source: https://riverpod.dev/docs/introduction/getting_started
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'theme_provider.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ThemeNotifier extends _$ThemeNotifier {
|
||||||
|
@override
|
||||||
|
ThemeMode build() {
|
||||||
|
// Read persisted preference, default to system
|
||||||
|
return ThemeMode.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setThemeMode(ThemeMode mode) {
|
||||||
|
state = mode;
|
||||||
|
// Persist to shared_preferences
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: StatefulShellRoute for Bottom Navigation
|
||||||
|
**What:** `go_router`'s `StatefulShellRoute.indexedStack` preserves each tab's navigation stack independently
|
||||||
|
**When to use:** Bottom navigation with independent tab histories
|
||||||
|
**Example:**
|
||||||
|
```dart
|
||||||
|
// Source: https://pub.dev/packages/go_router
|
||||||
|
final router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
StatefulShellRoute.indexedStack(
|
||||||
|
builder: (context, state, navigationShell) {
|
||||||
|
return AppShell(navigationShell: navigationShell);
|
||||||
|
},
|
||||||
|
branches: [
|
||||||
|
StatefulShellBranch(routes: [
|
||||||
|
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
|
||||||
|
]),
|
||||||
|
StatefulShellBranch(routes: [
|
||||||
|
GoRoute(path: '/rooms', builder: (context, state) => const RoomsScreen()),
|
||||||
|
]),
|
||||||
|
StatefulShellBranch(routes: [
|
||||||
|
GoRoute(path: '/settings', builder: (context, state) => const SettingsScreen()),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Drift Database with DAO Separation
|
||||||
|
**What:** Each logical domain gets its own DAO class annotated with `@DriftAccessor`
|
||||||
|
**When to use:** From Phase 1 onward -- even with an empty schema, establish the pattern
|
||||||
|
**Example:**
|
||||||
|
```dart
|
||||||
|
// Source: https://drift.simonbinder.eu/dart_api/daos/
|
||||||
|
@DriftDatabase(tables: [/* tables added in Phase 2 */], daos: [/* DAOs added in Phase 2 */])
|
||||||
|
class AppDatabase extends _$AppDatabase {
|
||||||
|
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
static QueryExecutor _openConnection() {
|
||||||
|
return driftDatabase(
|
||||||
|
name: 'household_keeper',
|
||||||
|
native: const DriftNativeOptions(
|
||||||
|
databaseDirectory: getApplicationSupportDirectory,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 4: ColorScheme.fromSeed with Surface Overrides
|
||||||
|
**What:** Generate a harmonious M3 color scheme from a sage green seed, then override surface colors for warmth
|
||||||
|
**When to use:** Theme definition
|
||||||
|
**Example:**
|
||||||
|
```dart
|
||||||
|
// Source: https://api.flutter.dev/flutter/material/ColorScheme/ColorScheme.fromSeed.html
|
||||||
|
ColorScheme _lightScheme() {
|
||||||
|
return ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFF7A9A6D), // Sage green
|
||||||
|
brightness: Brightness.light,
|
||||||
|
dynamicSchemeVariant: DynamicSchemeVariant.tonalSpot,
|
||||||
|
).copyWith(
|
||||||
|
surface: const Color(0xFFF5F0E8), // Warm stone
|
||||||
|
surfaceContainerLowest: const Color(0xFFFAF7F2),
|
||||||
|
surfaceContainerLow: const Color(0xFFF2EDE4),
|
||||||
|
surfaceContainer: const Color(0xFFEDE7DC),
|
||||||
|
surfaceContainerHigh: const Color(0xFFE7E0D5),
|
||||||
|
surfaceContainerHighest: const Color(0xFFE0D9CE),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorScheme _darkScheme() {
|
||||||
|
return ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFF7A9A6D), // Same sage green seed
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
dynamicSchemeVariant: DynamicSchemeVariant.tonalSpot,
|
||||||
|
).copyWith(
|
||||||
|
surface: const Color(0xFF2A2520), // Warm charcoal-brown (not #121212)
|
||||||
|
surfaceContainerLowest: const Color(0xFF1E1A16),
|
||||||
|
surfaceContainerLow: const Color(0xFF322D27),
|
||||||
|
surfaceContainer: const Color(0xFF3A342E),
|
||||||
|
surfaceContainerHigh: const Color(0xFF433D36),
|
||||||
|
surfaceContainerHighest: const Color(0xFF4D463F),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
- **Hardcoding strings in Dart:** All user-visible text MUST come from ARB files via `AppLocalizations.of(context)!.keyName`. Even placeholder screen text.
|
||||||
|
- **Manual provider declarations:** Always use `@riverpod` annotation + code generation. Never write `StateProvider`, `StateNotifierProvider`, or `ChangeNotifierProvider` -- these are legacy in Riverpod 3.
|
||||||
|
- **Skipping make-migrations on initial schema:** Run `dart run drift_dev make-migrations` immediately after creating the database class with `schemaVersion => 1`, BEFORE making any changes. This captures the baseline schema.
|
||||||
|
- **Using Navigator.push with go_router:** Use `context.go()` and `context.push()` from go_router. Never mix Navigator API with GoRouter.
|
||||||
|
- **Sharing ProviderContainer between tests:** Each test must get its own `ProviderContainer` or `ProviderScope`.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| Database migrations | Manual SQL ALTER statements | Drift `make-migrations` + `stepByStep` | Drift generates type-safe migration steps, auto-generates tests, catches schema drift at compile time |
|
||||||
|
| Provider boilerplate | Manual `StateNotifierProvider` / `Provider` declarations | `@riverpod` + `riverpod_generator` | Eliminates AutoDispose/Family variants, unified Ref, compile-time error detection |
|
||||||
|
| Color scheme harmony | Manual hex color picking for all 30+ ColorScheme roles | `ColorScheme.fromSeed()` + `.copyWith()` | Algorithm ensures contrast ratios, accessibility compliance, and tonal harmony |
|
||||||
|
| Route management | Manual Navigator.push/pop with IndexedStack | `go_router` StatefulShellRoute | Handles back button, deep links, tab state preservation, URL-based navigation |
|
||||||
|
| Localization code | Manual Map<String, String> lookups | `gen_l10n` with ARB files | Type-safe access via `AppLocalizations.of(context)!.key`, compile-time key validation |
|
||||||
|
| Theme mode persistence | Manual file I/O for settings | `shared_preferences` | Handles platform-specific key-value storage, async initialization, type safety |
|
||||||
|
|
||||||
|
**Key insight:** This phase is infrastructure-heavy. Every "hand-rolled" solution here would need to be replaced later when the real features arrive in Phases 2-4. Using the standard tooling from day 1 prevents a rewrite.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Forgetting to Run make-migrations Before First Schema Change
|
||||||
|
**What goes wrong:** You define tables, change them, bump schemaVersion to 2, but never captured schema version 1. Drift cannot generate the from1To2 migration step.
|
||||||
|
**Why it happens:** Developer creates tables and iterates on them during initial development before thinking about migrations.
|
||||||
|
**How to avoid:** Run `dart run drift_dev make-migrations` immediately after defining the initial database class with `schemaVersion => 1` and zero tables. This captures the empty schema as version 1. Then add tables and bump to version 2.
|
||||||
|
**Warning signs:** `make-migrations` errors about missing schema files in `drift_schemas/`.
|
||||||
|
|
||||||
|
### Pitfall 2: Riverpod Code Generation Not Running
|
||||||
|
**What goes wrong:** `.g.dart` files are missing or stale, causing compilation errors referencing `_$ClassName`.
|
||||||
|
**Why it happens:** Developer forgets to run `dart run build_runner build` or the watcher (`build_runner watch`) is not running.
|
||||||
|
**How to avoid:** Start development with `dart run build_runner watch -d` in a terminal. The `-d` flag deletes conflicting outputs. Add a note in the project README.
|
||||||
|
**Warning signs:** `Target of URI hasn't been generated` errors in the IDE.
|
||||||
|
|
||||||
|
### Pitfall 3: riverpod_lint Not Showing in IDE
|
||||||
|
**What goes wrong:** Lint rules like `ref.watch outside build` don't appear as analysis errors in VS Code / Android Studio.
|
||||||
|
**Why it happens:** Riverpod 3 uses `analysis_server_plugin` instead of `custom_lint`. The IDE may need a restart of the Dart analysis server, or the `analysis_options.yaml` is misconfigured.
|
||||||
|
**How to avoid:** Verify by running `dart analyze` from the terminal -- if lints appear there but not in the IDE, restart the analysis server. Ensure `analysis_options.yaml` has `plugins: riverpod_lint: ^3.1.3` (not the old `analyzer: plugins: - custom_lint` format).
|
||||||
|
**Warning signs:** No Riverpod-specific warnings anywhere in the project. Run `dart analyze` to verify.
|
||||||
|
|
||||||
|
### Pitfall 4: ColorScheme.fromSeed Ignoring Surface Overrides
|
||||||
|
**What goes wrong:** You pass `surface:` to `ColorScheme.fromSeed()` constructor but the surface color doesn't change.
|
||||||
|
**Why it happens:** Known Flutter issue -- some color role parameters in `fromSeed()` constructor may be ignored by the algorithm.
|
||||||
|
**How to avoid:** Always use `.copyWith()` AFTER `ColorScheme.fromSeed()` to override surface colors. This reliably works.
|
||||||
|
**Warning signs:** Surfaces appear as cool gray instead of warm stone/beige.
|
||||||
|
|
||||||
|
### Pitfall 5: ARB Template File Must Be the Source Language
|
||||||
|
**What goes wrong:** Code generation fails or produces incorrect AppLocalizations when template file doesn't match supported locale.
|
||||||
|
**Why it happens:** The `template-arb-file` in `l10n.yaml` must correspond to a supported locale.
|
||||||
|
**How to avoid:** Use `app_de.arb` as the template file since German is the only (and source) language. Set `template-arb-file: app_de.arb` in `l10n.yaml`.
|
||||||
|
**Warning signs:** `gen-l10n` errors about missing template or unsupported locale.
|
||||||
|
|
||||||
|
### Pitfall 6: ThemeMode.system as Default Without Persistence
|
||||||
|
**What goes wrong:** User selects "Hell" (light) in settings, restarts app, and it reverts to system theme.
|
||||||
|
**Why it happens:** ThemeMode is stored only in Riverpod state (memory), not persisted to disk.
|
||||||
|
**How to avoid:** Use `shared_preferences` to persist the ThemeMode enum value. On app start, read persisted value; default to `ThemeMode.system` if no value stored.
|
||||||
|
**Warning signs:** Theme selection doesn't survive app restart.
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
Verified patterns from official sources:
|
||||||
|
|
||||||
|
### l10n.yaml Configuration (German-only)
|
||||||
|
```yaml
|
||||||
|
# Source: https://docs.flutter.dev/ui/internationalization
|
||||||
|
arb-dir: lib/l10n
|
||||||
|
template-arb-file: app_de.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
nullable-getter: false
|
||||||
|
```
|
||||||
|
|
||||||
|
### pubspec.yaml Localization Dependencies
|
||||||
|
```yaml
|
||||||
|
# Source: https://docs.flutter.dev/ui/internationalization
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
intl: any
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
generate: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### MaterialApp.router Setup
|
||||||
|
```dart
|
||||||
|
// Source: https://docs.flutter.dev/ui/internationalization + https://riverpod.dev
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class App extends ConsumerWidget {
|
||||||
|
const App({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final themeMode = ref.watch(themeNotifierProvider);
|
||||||
|
|
||||||
|
return MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
|
theme: ThemeData(colorScheme: lightColorScheme, useMaterial3: true),
|
||||||
|
darkTheme: ThemeData(colorScheme: darkColorScheme, useMaterial3: true),
|
||||||
|
themeMode: themeMode,
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: const [Locale('de')],
|
||||||
|
locale: const Locale('de'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SegmentedButton Theme Switcher (Recommended for Claude's Discretion)
|
||||||
|
```dart
|
||||||
|
// Source: https://api.flutter.dev/flutter/material/SegmentedButton-class.html
|
||||||
|
// Recommendation: SegmentedButton is the best M3 pattern for 3-option single-select
|
||||||
|
SegmentedButton<ThemeMode>(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: ThemeMode.system,
|
||||||
|
label: Text(l10n.themeSystem), // "System"
|
||||||
|
icon: const Icon(Icons.settings_suggest_outlined),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: ThemeMode.light,
|
||||||
|
label: Text(l10n.themeLight), // "Hell"
|
||||||
|
icon: const Icon(Icons.light_mode_outlined),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: ThemeMode.dark,
|
||||||
|
label: Text(l10n.themeDark), // "Dunkel"
|
||||||
|
icon: const Icon(Icons.dark_mode_outlined),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {currentThemeMode},
|
||||||
|
onSelectionChanged: (selection) {
|
||||||
|
ref.read(themeNotifierProvider.notifier).setThemeMode(selection.first);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Drift build.yaml Configuration
|
||||||
|
```yaml
|
||||||
|
# Source: https://drift.simonbinder.eu/migrations/
|
||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
builders:
|
||||||
|
drift_dev:
|
||||||
|
options:
|
||||||
|
databases:
|
||||||
|
household_keeper: lib/core/database/database.dart
|
||||||
|
sql:
|
||||||
|
dialect: sqlite
|
||||||
|
options:
|
||||||
|
version: "3.38"
|
||||||
|
```
|
||||||
|
|
||||||
|
### analysis_options.yaml with riverpod_lint
|
||||||
|
```yaml
|
||||||
|
# Source: https://riverpod.dev/docs/introduction/getting_started
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
riverpod_lint: ^3.1.3
|
||||||
|
```
|
||||||
|
|
||||||
|
### AppShell with NavigationBar
|
||||||
|
```dart
|
||||||
|
// Source: https://codewithandrea.com/articles/flutter-bottom-navigation-bar-nested-routes-gorouter/
|
||||||
|
class AppShell extends StatelessWidget {
|
||||||
|
final StatefulNavigationShell navigationShell;
|
||||||
|
|
||||||
|
const AppShell({super.key, required this.navigationShell});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: navigationShell,
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
selectedIndex: navigationShell.currentIndex,
|
||||||
|
onDestinationSelected: (index) {
|
||||||
|
navigationShell.goBranch(
|
||||||
|
index,
|
||||||
|
initialLocation: index == navigationShell.currentIndex,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
destinations: [
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(Icons.checklist_outlined),
|
||||||
|
selectedIcon: const Icon(Icons.checklist),
|
||||||
|
label: l10n.tabHome, // "Ubersicht"
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(Icons.door_front_door_outlined),
|
||||||
|
selectedIcon: const Icon(Icons.door_front_door),
|
||||||
|
label: l10n.tabRooms, // "Raume"
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(Icons.tune_outlined),
|
||||||
|
selectedIcon: const Icon(Icons.tune),
|
||||||
|
label: l10n.tabSettings, // "Einstellungen"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| `StateNotifierProvider` / `ChangeNotifierProvider` | `@riverpod` annotation + code generation | Riverpod 3.0 (2025) | Legacy APIs still work but are deprecated; all new code should use code generation |
|
||||||
|
| `custom_lint` for riverpod_lint | `analysis_server_plugin` | Riverpod 3.0 (2025) | Setup via `plugins:` in analysis_options.yaml instead of `analyzer: plugins:` |
|
||||||
|
| `Ref<T>` with generic parameter | Unified `Ref` (no generic) | Riverpod 3.0 (2025) | Simplified API -- one Ref type for all providers |
|
||||||
|
| `useMaterial3: true` flag needed | Material 3 is default | Flutter 3.16+ (2023) | No need to explicitly enable M3 |
|
||||||
|
| `background` / `onBackground` color roles | `surface` / `onSurface` | Flutter 3.22+ (2024) | Old roles deprecated; use new surface container hierarchy |
|
||||||
|
| Manual drift schema dumps | `dart run drift_dev make-migrations` | Drift 2.x (2024) | Automated step-by-step migration file generation and test scaffolding |
|
||||||
|
|
||||||
|
**Deprecated/outdated:**
|
||||||
|
- `StateProvider`, `StateNotifierProvider`, `ChangeNotifierProvider`: Legacy in Riverpod 3 -- use `@riverpod` annotation
|
||||||
|
- `AutoDisposeNotifier` vs `Notifier` distinction: Unified in Riverpod 3 -- just use `Notifier`
|
||||||
|
- `ColorScheme.background` / `ColorScheme.onBackground`: Deprecated -- use `surface` / `onSurface`
|
||||||
|
- `ColorScheme.surfaceVariant`: Deprecated -- use `surfaceContainerHighest`
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. **Exact sage green hex value for seed color**
|
||||||
|
- What we know: The palette should evoke "a calm kitchen with natural materials" -- sage green primary, slate blue accents
|
||||||
|
- What's unclear: The exact hex value that produces the best M3 tonal palette when run through `fromSeed`
|
||||||
|
- Recommendation: Start with `Color(0xFF7A9A6D)` (muted sage) and iterate visually. The `dynamicSchemeVariant: DynamicSchemeVariant.tonalSpot` will automatically produce muted tones. Fine-tune surface overrides for warmth.
|
||||||
|
|
||||||
|
2. **Riverpod 3 package compatibility with Flutter stable channel**
|
||||||
|
- What we know: Riverpod docs specify `sdk: ^3.7.0` and `flutter: ">=3.0.0"`. Some reports mention beta channel needed for latest `json_serializable` compatibility.
|
||||||
|
- What's unclear: Whether `flutter_riverpod: ^3.3.0` works cleanly on Flutter 3.41.2 stable without issues
|
||||||
|
- Recommendation: Run `flutter pub get` early in the scaffolding wave. If version resolution fails, use `flutter_riverpod: ^3.1.0` as fallback.
|
||||||
|
|
||||||
|
3. **Drift make-migrations with an empty initial schema**
|
||||||
|
- What we know: The docs say to run `make-migrations` before making changes. In Phase 1, we may have zero tables (tables come in Phase 2).
|
||||||
|
- What's unclear: Whether `make-migrations` works with an empty database (no tables)
|
||||||
|
- Recommendation: Define the database class with `schemaVersion => 1` and at minimum one placeholder table or zero tables, then run `make-migrations`. If it fails on empty, add a placeholder `AppSettings` table with a single column.
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
### Test Framework
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Framework | flutter_test (bundled with Flutter SDK) |
|
||||||
|
| Config file | none -- see Wave 0 |
|
||||||
|
| Quick run command | `flutter test` |
|
||||||
|
| Full suite command | `flutter test --coverage` |
|
||||||
|
|
||||||
|
### Phase Requirements to Test Map
|
||||||
|
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||||
|
|--------|----------|-----------|-------------------|-------------|
|
||||||
|
| FOUND-01 | Drift database opens with schemaVersion 1, make-migrations runs | unit + smoke | `flutter test test/core/database/database_test.dart -x` | No -- Wave 0 |
|
||||||
|
| FOUND-02 | Riverpod providers generate correctly, riverpod_lint active | smoke | `dart analyze` | No -- Wave 0 |
|
||||||
|
| FOUND-03 | AppLocalizations.of(context) returns German strings, no hardcoded text | widget | `flutter test test/l10n/localization_test.dart -x` | No -- Wave 0 |
|
||||||
|
| FOUND-04 | Bottom nav shows 3 tabs, tapping switches content | widget | `flutter test test/shell/app_shell_test.dart -x` | No -- Wave 0 |
|
||||||
|
| THEME-01 | Light/dark themes switch correctly, system default works | widget | `flutter test test/core/theme/theme_test.dart -x` | No -- Wave 0 |
|
||||||
|
| THEME-02 | Color scheme uses sage green seed, surfaces are warm-toned | unit | `flutter test test/core/theme/color_scheme_test.dart -x` | No -- Wave 0 |
|
||||||
|
|
||||||
|
### Sampling Rate
|
||||||
|
- **Per task commit:** `flutter test`
|
||||||
|
- **Per wave merge:** `flutter test --coverage`
|
||||||
|
- **Phase gate:** Full suite green + `dart analyze` clean (zero riverpod_lint issues)
|
||||||
|
|
||||||
|
### Wave 0 Gaps
|
||||||
|
- [ ] `test/core/database/database_test.dart` -- covers FOUND-01 (database opens, schema version correct)
|
||||||
|
- [ ] `test/l10n/localization_test.dart` -- covers FOUND-03 (German strings load from ARB)
|
||||||
|
- [ ] `test/shell/app_shell_test.dart` -- covers FOUND-04 (navigation bar renders 3 tabs)
|
||||||
|
- [ ] `test/core/theme/theme_test.dart` -- covers THEME-01 (light/dark/system switching)
|
||||||
|
- [ ] `test/core/theme/color_scheme_test.dart` -- covers THEME-02 (sage green seed, warm surfaces)
|
||||||
|
- [ ] Drift migration test scaffolding via `dart run drift_dev make-migrations` (auto-generated)
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- [Riverpod official docs](https://riverpod.dev/docs/introduction/getting_started) - getting started, package versions, code generation setup, lint setup
|
||||||
|
- [Drift official docs](https://drift.simonbinder.eu/setup/) - setup, migration workflow, make-migrations, DAOs
|
||||||
|
- [Flutter official docs](https://docs.flutter.dev/ui/internationalization) - ARB localization setup, l10n.yaml
|
||||||
|
- [Flutter API reference](https://api.flutter.dev/flutter/material/ColorScheme/ColorScheme.fromSeed.html) - ColorScheme.fromSeed, DynamicSchemeVariant, SegmentedButton
|
||||||
|
- [pub.dev/packages/drift](https://pub.dev/packages/drift) - version 2.32.0 verified
|
||||||
|
- [pub.dev/packages/flutter_riverpod](https://pub.dev/packages/flutter_riverpod) - version 3.3.1 verified
|
||||||
|
- [pub.dev/packages/go_router](https://pub.dev/packages/go_router) - version 17.1.0 verified
|
||||||
|
- [pub.dev/packages/riverpod_lint](https://pub.dev/packages/riverpod_lint) - version 3.1.3, 14 lint rules documented
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- [Flutter 3.41 blog post](https://blog.flutter.dev/whats-new-in-flutter-3-41-302ec140e632) - Flutter 3.41.2 current stable confirmed
|
||||||
|
- [CodeWithAndrea go_router tutorial](https://codewithandrea.com/articles/flutter-bottom-navigation-bar-nested-routes-gorouter/) - StatefulShellRoute.indexedStack pattern
|
||||||
|
- [Drift migrations docs](https://drift.simonbinder.eu/migrations/) - make-migrations workflow, stepByStep pattern
|
||||||
|
- [DynamicSchemeVariant API](https://api.flutter.dev/flutter/material/DynamicSchemeVariant.html) - tonalSpot vs fidelity behavior
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence)
|
||||||
|
- Exact surface color hex values for warm stone/charcoal -- these are illustrative starting points, need visual iteration
|
||||||
|
- Riverpod 3.3 compatibility with Flutter stable -- most reports confirm it works, but a few mention beta channel needed
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH - all packages verified on pub.dev with current versions, official docs consulted
|
||||||
|
- Architecture: HIGH - patterns drawn from official go_router and Riverpod documentation, well-established in community
|
||||||
|
- Pitfalls: HIGH - documented issues verified across multiple sources (GitHub issues, official docs, community reports)
|
||||||
|
- Color palette: MEDIUM - hex values are starting points; M3 algorithm behavior verified but exact visual output requires iteration
|
||||||
|
- riverpod_lint setup: MEDIUM - new analysis_server_plugin approach is documented but IDE integration may have quirks
|
||||||
|
|
||||||
|
**Research date:** 2026-03-15
|
||||||
|
**Valid until:** 2026-04-15 (30 days -- stable ecosystem, no major releases expected before Flutter 3.44 in May)
|
||||||
Reference in New Issue
Block a user