- Switch LocationSettings to AndroidSettings with intervalDuration: 250ms.
- 250ms = 4Hz; higher rate is only achievable when GNSS is available (not indoors).
- SpeedometerViewModel now requires both LocationRepository and SettingsProvider
- SpeedometerView uses SettingsProvider.currentTheme instead of non-existent Theme provider
- main.dart: SettingsProvider created before SpeedometerViewModel in provider hierarchy
- Restores proper DI wiring after layered architecture refactor
- OverlayService was the only overlay-related file that should stay
- It is a stateless platform wrapper — fits in data/services/
- Will be used by future OverlayProvider when overlay is reimplemented
- 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)