109 Commits

Author SHA1 Message Date
irene 411166ef5d feat: bump version to 2.3.0+2
Release 2.3.0 with 4Hz GPS update interval, CI workflow, and lint fixes
v2.3.0
2026-08-27 20:53:41 +09:00
irene b2f0015556 Merge branch 'fix/analysis-options' into master
CI / Analyze & Build (push) Successful in 3m5s
2026-08-27 13:41:24 +09:00
irene f9a33809a2 fix: disable sort_pub_dependencies lint rule 2026-08-27 13:41:06 +09:00
irene abd6d779ea fix: sort pubspec.yaml dependencies alphabetically 2026-08-27 13:37:56 +09:00
irene 92815c4682 fix: address remaining lint warnings 2026-08-27 13:36:57 +09:00
irene 8a3487f65e fix: suppress constant_identifier_names lint in analysis_options.yaml
SCREAMING_SNAKE_CASE is project convention for GPS constants.
2026-08-27 13:35:16 +09:00
irene 64b80eecc9 Merge branch 'ci/gps-speedo-workflow' into master
CI / Analyze & Build (push) Failing after 11s
2026-08-27 13:17:43 +09:00
irene 43f1e4ecb2 Merge branch 'feat/gps-update-interval' into master 2026-08-27 13:17:43 +09:00
irene 449e345e3e ci: add CI workflow (analyze + release APK)
- Modelled on iperf3-gui: analyze job on flutter-android runner.
- flutter analyze + flutter build apk --release.
- Uploads release APK as artifact.
2026-08-27 13:17:43 +09:00
irene c2bcd685cf feat: add 4Hz GPS update interval
- Switch LocationSettings to AndroidSettings with intervalDuration: 250ms.
- 250ms = 4Hz; higher rate is only achievable when GNSS is available (not indoors).
2026-08-27 13:17:15 +09:00
irene 54cec166b4 fix: add SettingsProvider to SpeedometerViewModel and fix ProviderNotFoundException
- 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
2026-08-23 23:26:03 +09:00
mikkeli fff81719b1 Merge pull request 'refactor: layered architecture (core/data/ui separation)' (#1) from refactor/flutter-architecture into master
Reviewed-on: #1
2026-08-23 11:25:54 +00:00
irene 248053270b refactor: complete layered architecture migration
Delete old flat files, wire new DI in main.dart:
- Removed: gps_service.dart, config/, models/, providers/overlay_provider.dart,
  screens/, services/gps_data_manager.dart, services/overlay_service.dart
- Kept: settings_provider.dart (thin, correct), logger.dart (shared util)
- main.dart now wires: LocationRepository → SpeedometerViewModel → SpeedometerView
- overlay_provider.dart deferred, stub in ui/features/overlay/
- flutter analyze: 30 info-level constant_identifier_names warnings (project convention),
  zero errors, zero warnings — builds clean

New structure:
  lib/
    core/constants/     — gps, overlay, timing, speed_unit
    core/theme/         — color_themes
    data/
      models/           — LocationData (immutable domain model)
      services/         — GpsService (stateless wrapper), OverlayService (IPC)
      repositories/     — LocationRepository (business logic, ChangeNotifier)
    providers/          — SettingsProvider (thin, unchanged)
    ui/
      features/
        speedometer/    — SpeedometerViewModel + SpeedometerView
        overlay/        — Stub ViewModel + View (deferred)
      shared/           — SpeedDisplay widget
    services/           — Logger (shared util)
2026-08-23 17:32:29 +09:00
irene 6bba05efc8 refactor(data): move OverlayService to data/services/
- 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
2026-08-23 17:27:42 +09:00
irene 1cc111bb76 feat(overlay): create stub ViewModel and View for deferred overlay feature
- 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
2026-08-23 17:21:45 +09:00
irene 3c96571325 refactor(ui): create speedometer UI layer with ViewModel and shared widgets
- 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
2026-08-23 17:21:09 +09:00
irene 44480eb969 refactor(data): create layered architecture with core/data/ui packages
- 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/
2026-08-23 17:16:43 +09:00
mikkeli 7a58483ba2 build(android): move to Flutter 3.47 template toolchain (Gradle 9.3.1 / AGP 9.1.0 / Kotlin 2.4.0)
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
2026-08-23 11:56:45 +09:00
irene a9844e9665 docs: add MIT license (Mikkeli Matlock, 2026) 2026-08-23 02:26:23 +09:00
mikkeli c889defa2e chore/fix: added DIN1451 font asset and removed gitignore item - reason: moving to self-hosted gitea so copyright is less of a concern here - long term, need to find proper licensed fonts or reactivate gitignore 2026-08-22 21:28:00 +09:00
Mikkeli Matlock b22872cf2e updates 2026-02-18 19:54:01 +09:00
Mikkeli Matlock 5422c73400 feat: merge GPS manager architecture restructure (v2.3.0)
Complete restructure of monolithic main.dart into modular MVVM
architecture. All 4 phases executed across 8 plans (~0.77 hrs total).

- Phase 1: Foundation — directory structure, models, Logger utility
- Phase 2: Provider migration — GpsDataManager, SettingsProvider, OverlayProvider
- Phase 3: Overlay refactor — OverlayService, staleness detection, error surfacing
- Phase 4: GPS optimization — adaptive precision, lifecycle management, wake locks

Key outcomes:
- SpeedometerScreen: 1075 lines → 493 lines (-54%)
- Zero setState for shared state (Provider/Consumer throughout)
- Speed-adaptive GPS precision (8/12 km/h hysteresis)
- Background GPS lifecycle awareness (7s grace period)
- Overlay staleness detection (3s dim, 10s dashes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 08:09:26 +09:00
Mikkeli Matlock 26cdf24874 docs(04): complete phase 4 verification and roadmap
Mark Phase 4 complete in ROADMAP.md, add VERIFICATION.md (6/6
automated checks passed), and update phase 02 continue-here artifact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 07:52:38 +09:00
Mikkeli Matlock 177e13a2f8 docs(04-02): complete UI integration plan
- Created 04-02-SUMMARY.md with execution details
- Updated STATE.md progress (Phase 4: 2/2 complete)
- Updated performance metrics (8 plans, 5.5 min avg)
- Added key decisions to accumulated context
- Marked Phase 4 as complete
2026-02-12 09:12:43 +09:00
Mikkeli Matlock 75d58c08ed chore(04-02): remove grace period placeholder from OverlayProvider
- 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)
2026-02-12 09:10:18 +09:00
Mikkeli Matlock 1dcfe60b4d feat(04-02): add staleness detection to main speed display
- Add staleness check timer (1s interval) with proper disposal
- Speed/heading dim at 3s, show dashes at 10s (matching overlay)
- Apply staleness opacity to both speed and heading displays
- Remove WakelockPlus management (now in GpsDataManager)
- Remove WidgetsBindingObserver (now in GpsDataManager)
- Simplify background heartbeat (removed _isInBackground check)
- Wire overlay provider into GpsDataManager via initState
- Add haptic feedback to theme/unit cycling
- Update _buildSpeedDisplay to accept opacity parameter
2026-02-12 09:09:10 +09:00
Mikkeli Matlock 8bc1e392a5 docs(04-01): complete core GPS engine plan
- 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>
2026-02-12 09:04:59 +09:00
Mikkeli Matlock 1618cfeb26 feat(04-01): implement lifecycle-aware GPS engine with precision switching
- 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>
2026-02-12 09:02:33 +09:00
Mikkeli Matlock c4d4032b6b feat(04-01): add timestamp to ProcessedGpsData and precision/lifecycle constants
- 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>
2026-02-12 09:00:10 +09:00
Mikkeli Matlock 40c74677be docs(04): create phase plan for GPS & power optimization
2 plans in 2 waves: core GPS engine (precision switching, lifecycle,
wake lock) then UI integration (main app staleness, screen cleanup,
provider wiring).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 08:50:46 +09:00
Mikkeli Matlock 1dd3511047 docs(04): complete phase research 2026-02-12 08:43:40 +09:00
Mikkeli Matlock 7fe6168106 docs(04): capture phase context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 00:39:35 +09:00
Mikkeli Matlock d0427219a2 docs(03): complete overlay refactor phase
Phase 3 verified: 9/9 must-haves, 6/6 OVRL requirements satisfied.
OverlayService wrapper, staleness detection, error surfacing all verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:35:15 +09:00
Mikkeli Matlock 9fb91f6e3f chore(03): update lockfile for retry dependency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:27:22 +09:00
Mikkeli Matlock 0976b76f44 docs(03-02): complete overlay integration plan
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
2026-02-11 19:26:26 +09:00
Mikkeli Matlock cc11a3d31a feat(03-02): add staleness detection to overlay screen and error surfacing to SpeedometerScreen
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>
2026-02-11 19:24:05 +09:00
Mikkeli Matlock 66bdd35995 feat(03-02): integrate OverlayService into OverlayProvider with error callbacks and GPS grace period
- 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>
2026-02-11 19:21:58 +09:00
Mikkeli Matlock acfbeb85a2 docs(03-01): complete overlay foundation plan
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>
2026-02-11 19:16:21 +09:00
Mikkeli Matlock 9e3decc91a feat(03-01): add timestamp field to OverlayMessage model
- 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>
2026-02-11 19:13:39 +09:00
Mikkeli Matlock 505219c858 feat(03-01): create OverlayService and add retry dependency
- Add retry package (^3.1.2) for robust platform operations
- Create OverlayService wrapper for all FlutterOverlayWindow calls
- Implement showOverlay/closeOverlay with 3-attempt retry logic
- Add shareData fire-and-forget with warn-level logging
- Add isActive, overlayListener, checkPermission helpers
- Define staleness constants: 3s dim, 10s dash, 0.5 opacity, 1s check
- Define GPS_GRACE_PERIOD constant (30 seconds)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:12:23 +09:00
Mikkeli Matlock c58183b4fb docs(03): create phase plan for overlay refactor
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>
2026-02-11 19:00:39 +09:00
Mikkeli Matlock 07d446d912 docs(03): research overlay communication and staleness patterns
Phase 3: Overlay Refactor
- Standard stack identified: flutter_overlay_window 0.5.0, retry package, permission_handler
- Architecture patterns documented: OverlayService wrapper, timestamp-based staleness, error surfacing
- Pitfalls catalogued: communication blackhole, permission race conditions, opacity performance
- User decisions integrated: 3s dim threshold, 10s dashes, no recovery animation, 30s grace period

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:54:38 +09:00
Mikkeli Matlock 9001a42196 docs(03): capture phase context
Phase 03: Overlay Refactor
- Implementation decisions documented
- Phase boundary established

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 23:14:45 +09:00
Mikkeli Matlock a27e6b7f3e docs(02): complete provider migration phase
- Phase 2 verified: 20/20 must-haves passed
- STATE-01 through STATE-07 marked Complete in REQUIREMENTS.md
- VERIFICATION.md confirms codebase matches all success criteria
- ROADMAP.md and STATE.md updated for phase completion
2026-02-10 22:28:42 +09:00
Mikkeli Matlock 66320e122b chore(02): update lockfile and generated plugin registrant
- pubspec.lock updated for provider and shared_preferences dependencies
- macOS plugin registrant regenerated by Flutter toolchain
2026-02-10 22:21:48 +09:00
Mikkeli Matlock 09c80011b8 docs(02-02): complete MultiProvider wiring and screen migration plan
Tasks completed: 2/2
- Task 1: Wire MultiProvider in main.dart with async SharedPreferences
- Task 2: Migrate SpeedometerScreen from setState to Provider

SUMMARY: .planning/phases/02-provider-migration/02-02-SUMMARY.md
2026-02-10 22:21:04 +09:00
Mikkeli Matlock 7d111ca202 feat(02-02): migrate SpeedometerScreen from setState to Provider
- 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
2026-02-10 22:18:34 +09:00
Mikkeli Matlock 9867fe8c46 feat(02-02): wire MultiProvider with pre-initialized SharedPreferences
- Make main() async with WidgetsFlutterBinding.ensureInitialized()
- Pre-initialize SharedPreferences before runApp() to eliminate flash of defaults
- Wire MultiProvider with 4 providers in dependency order:
  1. Provider.value(SharedPreferences) - pre-initialized dependency
  2. ChangeNotifierProvider(SettingsProvider) - theme + unit state
  3. ChangeNotifierProvider(GpsDataManager) - GPS data stream
  4. ChangeNotifierProxyProvider2(OverlayProvider) - depends on GPS + Settings
- Remove isOverlayMode parameter from SpeedoApp (unused, overlay runs in separate isolate)
- Keep overlayMain() unchanged (separate isolate entry point)
2026-02-10 22:15:31 +09:00
Mikkeli Matlock 3c5ce89ded docs(02-01): complete provider infrastructure plan
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
2026-02-10 22:11:49 +09:00
Mikkeli Matlock 6b8bba8686 feat(02-01): create OverlayProvider for overlay lifecycle and messaging
- 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
2026-02-10 22:05:39 +09:00