- Stub OverlayViewModel reserves abstraction point
- Stub OverlayView shows 'Overlay deferred' placeholder
- Original OverlayProvider (274 lines) was too tangled:
mixed IPC, GPS forwarding, timers, foreground mgmt
- Will be reimplemented properly when overlay issues are resolved
- Create SpeedometerViewModel in ui/features/speedometer/
- Create thin SpeedometerView in ui/features/speedometer/
- Extract shared SpeedDisplay widget in ui/shared/
- Extract _CompassWidget from old speedometer screen
- Remove all business logic from UI layer
- Move constants from lib/config/ to lib/core/constants/
- Move color themes to lib/core/theme/
- Create LocationData domain model in data/models/
- Create lightweight GpsService in data/services/
- Create LocationRepository in data/repositories/ consolidating
GpsDataManager business logic (GPS stream, staleness, precision)
- Create shared SpeedDisplay widget in ui/shared/
- Defer overlay feature — stubs reserved in ui/features/overlay/
The project pinned Gradle 8.12 / AGP 8.9.1 / Kotlin 2.1.0, all three below the
hard error floors that Flutter 3.47.1 enforces in DependencyVersionChecker.kt
(8.14.0 / 8.11.1 / 2.2.20). Nothing here had rotted; the SDK moved.
Targets Flutter's own template defaults rather than the lowest combo that
clears the floors, because Flutter promotes its warn floor to the error floor
in the very next release -- clearing only the hard floor buys one release of
runway. `flutter-android-preflight` on the host re-derives all of this from
the installed SDK.
Also:
- kotlinOptions{} -> kotlin{compilerOptions{}}: the old DSL is a script
COMPILATION error under Kotlin >= 2.3, not a warning.
- Java/jvmTarget 11 -> 17, matching what the AndroidX artifacts now require.
- Drop android.enableJetifier (obsolete, removed outright in AGP 9); the
Flutter migrator adds builtInKotlin/newDsl=false in its place. Keep them:
builtInKotlin=true makes AGP 9.1.0 supply Kotlin 2.2.10, which is BELOW
Flutter's own 2.2.20 floor, so Flutter rejects its own recommendation.
- Force plugin subprojects to compileSdk 36 / minSdk 24. bg_launcher 0.1.0 is
abandoned at compileSdk 31 and the AndroidX artifacts it resolves to refuse
it. The override must be registered before evaluationDependsOn(":app"),
which evaluates subprojects eagerly. Dropping bg_launcher is the real fix.
Verified: flutter build apk --debug and --release, as both mikkeli and hermes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dKAqFo9Uj3mPZWvDzj3G8
- Remove _gracePeriodTimer field and all grace period logic
- Grace period management now handled by GpsDataManager
- Overlay provider wiring completed in Task 1 (SpeedometerScreen initState)
- GPS_GRACE_PERIOD constant remains in TimingConfig (deprecated, unused)
- Created SUMMARY.md documenting lifecycle-aware GPS engine implementation
- Updated STATE.md to Phase 4 Plan 1 of 2 complete (70% progress)
- Added 6 key decisions to STATE.md decisions table
- Updated performance metrics: 7 plans, 4.9 min average for Phase 4
- Updated session continuity and blockers sections
- Documented self-check results: all files and commits verified
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add WidgetsBindingObserver mixin for app lifecycle tracking
- Add overlay provider reference for background decision-making
- Add state tracking: current accuracy, first-fix status, foreground/GPS active state
- Fix default ProcessedGpsData to non-const with timestamp
- Register lifecycle observer in initialize(), remove in dispose()
- Set first-fix state and time after initial GPS fix
- Update position handler to include timestamp in ProcessedGpsData
- Implement precision switching with hysteresis (8/12 km/h thresholds)
- Gate precision switching until 5s after first fix
- Implement lifecycle handlers: paused (7s grace period) and resumed (restart GPS)
- Add grace period expiration with race condition protection (re-check overlay)
- Add GPS stop/restart methods preserving last known data
- Implement conditional wake lock: GPS active AND (overlay OR foreground)
- Cancel all timers in dispose() including background grace period
- Remove unnecessary flutter/foundation import (widgets provides ChangeNotifier)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add timestamp field to ProcessedGpsData with copyWith support
- Add precision switching thresholds: 3.33 m/s (12 km/h) up, 2.22 m/s (8 km/h) down
- Add PRECISION_SWITCH_FIRST_FIX_DELAY (5s) to prevent early switching
- Add BACKGROUND_GRACE_PERIOD (7s) for lifecycle-aware GPS management
- Mark GPS_GRACE_PERIOD as deprecated in favor of BACKGROUND_GRACE_PERIOD
- Change GpsService.positionStream to createPositionStream() with dynamic accuracy
- Keep deprecated positionStream getter for backward compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Task 1: Integrated OverlayService into OverlayProvider with error callbacks and GPS grace period
- Task 2: Added staleness detection to overlay screen and error surfacing to SpeedometerScreen
SUMMARY: .planning/phases/03-overlay-refactor/03-02-SUMMARY.md
Overlay screen staleness:
- Add _lastUpdateTime field to track GPS data freshness
- Add _stalenessCheckTimer for periodic staleness monitoring (1s interval)
- Update _lastUpdateTime from message.timestamp in _listenToMainAppMessages
- Start staleness monitoring in initState, cancel in dispose
- Calculate staleness opacity in build() (0.5 alpha after 3s)
- Apply staleness dimming to speed text, unit text, heading icon, and heading text
- Show "--" for speed after 10s of no data (setState in timer callback)
- Immediate snap-back when fresh data arrives (no AnimatedOpacity)
SpeedometerScreen error surfacing:
- Import permission_handler for openAppSettings
- Register error callback on OverlayProvider in initState postFrameCallback
- Implement _handleOverlayError to show snackbars for overlay errors
- Permission error: snackbar with "Settings" action button that opens app settings
- Creation failure: snackbar with error message (no action button)
- Communication degraded: snackbar with error message (no action button)
- Clean up error callback in dispose (try-catch for disposed provider)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add OverlayError enum (permission, creationFailed, communicationDegraded)
- Add OverlayService dependency via constructor injection
- Add onError callback field for error surfacing (set by SpeedometerScreen)
- Refactor showOverlay to check permission before showing overlay
- Refactor showOverlay to verify overlay creation via isActive check
- Only set _isOverlayActive after verification succeeds
- Replace all FlutterOverlayWindow calls with OverlayService methods
- Add GPS grace period timer (30s) in _handleOverlayClose
- Cancel grace period if overlay reopens within 30s
- Register OverlayService in MultiProvider before OverlayProvider
- Inject OverlayService into OverlayProvider constructor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2
- Task 1: Create OverlayService and add retry dependency
- Task 2: Add timestamp field to OverlayMessage model
SUMMARY: .planning/phases/03-overlay-refactor/03-01-SUMMARY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add timestamp field with auto-population via DateTime.now()
- Update all factory constructors to include timestamp
- Serialize timestamp as millisecondsSinceEpoch in toMap()
- Deserialize with fallback to DateTime.now() in fromMap()
- Add timestamp parameter to copyWith() method
- Remove const modifiers to allow DateTime.now() runtime values
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3: Overlay Refactor
- 2 plan(s) in 2 wave(s)
- Sequential execution (Plan 02 depends on Plan 01)
- Ready for execution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove all shared state variables (unit, theme, GPS data, overlay status)
- Keep only local widget state (_errorMessage, _isInBackground, timer)
- Replace GPS initialization with context.read<GpsDataManager>().initialize()
- Replace overlay show/close/toggle with OverlayProvider methods
- Replace theme/unit cycling with SettingsProvider.cycleTheme/Unit()
- Remove isOverlayMode parameter (not used, overlay runs in separate isolate)
- Wrap build with Selector<SettingsProvider, ColorTheme> for scaffold background
- Use Selector<GpsDataManager, double> for speed display (rebuilds only on speed change)
- Use Selector<SettingsProvider, String> for unit label (rebuilds only on unit change)
- Use Selector<GpsDataManager, (heading, displayHeading)> for compass (rebuilds only on heading change)
- Use context.read<> for event handlers (non-rebuilding access)
- Simplify dispose() - only cancel background heartbeat timer and disable wakelock
- Background heartbeat uses OverlayProvider.pushCurrentData() instead of inline logic
- setState remains ONLY for _errorMessage (purely local error UI state)
- Remove unused imports (speed_unit.dart, processed_gps_data.dart)
- Portrait and landscape layouts both use Provider pattern
Tasks completed: 3/3
- Task 1: Add provider dependencies and refactor GpsDataManager to ChangeNotifier
- Task 2: Create SettingsProvider for theme and unit state
- Task 3: Create OverlayProvider for overlay lifecycle and messaging
SUMMARY: .planning/phases/02-provider-migration/02-01-SUMMARY.md
- Created lib/providers/overlay_provider.dart
- Extends ChangeNotifier for reactive overlay state management
- Centralizes ALL overlay operations: show, close, toggle, status monitoring, messaging
- updateDependencies() called by ProxyProvider when GPS or Settings change
- Subscribes to GpsDataManager.dataStream on first update (persist subscription)
- Skips overlay sends when inactive (if-check, not unsubscribe)
- _isDisposed guards on all async-accessible methods
- showOverlay() sends initial data with overlay size info
- _pushDataToOverlay() sends GPS data with current theme/unit
- _startListeningToOverlayMessages() handles overlay→main IPC
- _startOverlayStatusCheck() polls overlay status at 1s interval
- _handleOverlayClose() manages cleanup and optional bring-to-front
- pushCurrentData() public method for background heartbeat usage
- dispose() cancels 4 subscriptions/timers, calls super.dispose() last
- Created lib/providers/settings_provider.dart
- Extends ChangeNotifier for reactive state management
- Constructor accepts pre-initialized SharedPreferences instance
- Loads theme index and speed unit synchronously from SharedPreferences
- cycleTheme() and cycleUnit() methods with haptic feedback
- Persists settings to SharedPreferences on every change
- Both cycle methods call notifyListeners() for reactive UI updates
- No notifyListeners() in constructor (runs before first build)
- Added provider ^6.1.2 and shared_preferences ^2.3.4 to pubspec.yaml
- Removed singleton pattern from GpsDataManager (deleted _instance and instance getter)
- Changed GpsDataManager._internal() to public GpsDataManager() constructor
- Extended ChangeNotifier to enable reactive state updates
- Added _isDisposed guard field to prevent disposed notifications
- Updated _updateData() to call notifyListeners() after stream emission
- Added _isDisposed guards to _onGpsError() and _handleStaleDataTimeout()
- Updated dispose() to set _isDisposed, call super.dispose() last, removed _isInitialized reset
Phase 1 fully executed and verified (9/9 must-haves).
Ready to resume with Phase 2 (Provider Migration).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create 01-01-SUMMARY.md: Comprehensive execution summary with 12.8 min duration
- Update STATE.md: Progress 50% (1/2 plans complete), velocity metrics, decisions table
- Document 5 key decisions made during execution
- Record zero deviations, one minor bug (missing import)
- Phase 1 ready to proceed with plan 01-02
- Update gps_data_manager.dart: import ProcessedGpsData from models/, use GpsConfig constants, replace all print() with Logger calls
- Update gps_service.dart: use GpsConfig.VALID_HEADING_MIN/MAX for heading validation
- Update main.dart: import from config/, models/, replace all customDebugPrint() and print() with Logger, use TimingConfig/OverlayConfig constants, convert overlay IPC to OverlayMessage typed class
- Delete customDebugPrint() function definition
- Delete lib/speed_units.dart and lib/color_themes.dart (moved to config/)
- Zero analyzer errors, zero bare print() calls remaining