docs: map existing codebase
- STACK.md - Technologies and dependencies - ARCHITECTURE.md - System design and patterns - STRUCTURE.md - Directory layout - CONVENTIONS.md - Code style and patterns - TESTING.md - Test structure - INTEGRATIONS.md - External services - CONCERNS.md - Technical debt and issues
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** Layered Service Pattern with Reactive Data Flow
|
||||
|
||||
**Key Characteristics:**
|
||||
- **Singleton Service Layer** - Centralized GPS data management via `GpsDataManager`
|
||||
- **Reactive Streams** - Broadcast streams for reactive UI updates across main app and overlay
|
||||
- **Direct Communication** - Main app and floating window communicate via `FlutterOverlayWindow.shareData()`
|
||||
- **Stateful Widget Hierarchy** - Two independent stateful widgets (`SpeedometerScreen` and `OverlaySpeedometer`) consuming shared GPS data
|
||||
- **Widget-to-Widget Data Push** - Main app explicitly pushes formatted data to overlay instead of shared state management
|
||||
|
||||
## Layers
|
||||
|
||||
**GPS Hardware Layer:**
|
||||
- Purpose: Acquire raw GPS data from device sensors
|
||||
- Location: `lib/gps_service.dart`
|
||||
- Contains: GPS permission handling, position stream creation, heading calculations
|
||||
- Depends on: `geolocator`, `permission_handler` packages
|
||||
- Used by: `GpsDataManager`
|
||||
|
||||
**GPS Data Management Layer:**
|
||||
- Purpose: Process raw GPS data, validate signals, manage stale data timeouts, broadcast processed data
|
||||
- Location: `lib/services/gps_data_manager.dart`
|
||||
- Contains: `GpsDataManager` singleton, `ProcessedGpsData` model, stream subscription and timing logic
|
||||
- Depends on: `GpsService`, `SpeedUnit`
|
||||
- Used by: `SpeedometerScreen` and `OverlaySpeedometer`
|
||||
|
||||
**UI State & Rendering Layer:**
|
||||
- Purpose: Consume GPS data, manage theme/unit selection, render main app and overlay screens
|
||||
- Location: `lib/main.dart`
|
||||
- Contains: `SpeedometerScreen` (main app widget), `OverlaySpeedometer` (overlay widget), layout builders, gesture handlers
|
||||
- Depends on: `GpsDataManager`, `ColorThemes`, `SpeedUnit`, `WakelockPlus`, `FlutterOverlayWindow`, `BgLauncher`
|
||||
- Used by: Flutter framework
|
||||
|
||||
**Configuration/Utilities Layer:**
|
||||
- Purpose: Provide constants and helper functions for speed units, color themes
|
||||
- Location: `lib/speed_units.dart`, `lib/color_themes.dart`
|
||||
- Contains: `SpeedUnit` enum, `ColorTheme` classes, theme collection
|
||||
- Depends on: Flutter Material Design
|
||||
- Used by: All UI and data layers
|
||||
|
||||
## Data Flow
|
||||
|
||||
**GPS Data Collection & Broadcasting:**
|
||||
|
||||
1. `main.dart` calls `GpsDataManager.instance.initialize()` on app startup
|
||||
2. `GpsDataManager` requests permissions via `GpsService.requestPermissions()`
|
||||
3. `GpsDataManager` calls `Geolocator.getCurrentPosition()` with 20s timeout for initial fix
|
||||
4. Once initial position obtained, `GpsDataManager` subscribes to `GpsService.positionStream()` with 2s update timeout
|
||||
5. Raw `Position` updates trigger `GpsDataManager._onPositionUpdate()`
|
||||
6. `GpsDataManager` validates speed/heading, formats display strings, creates `ProcessedGpsData`
|
||||
7. `GpsDataManager` broadcasts via `_dataController.add(newData)` (broadcast stream)
|
||||
8. Both `SpeedometerScreen` and `OverlaySpeedometer` receive updates via stream subscriptions
|
||||
|
||||
**Main App Display Update:**
|
||||
|
||||
1. `SpeedometerScreen._onGpsDataUpdate()` receives `ProcessedGpsData` from stream
|
||||
2. Updates local state: `_currentGpsData`, `_currentUnit`, `_currentThemeIndex`
|
||||
3. Rebuilds UI with converted speed value and formatted heading
|
||||
4. If overlay is active, calls `_pushDataToOverlay()` to sync overlay display
|
||||
|
||||
**Overlay Activation & Communication:**
|
||||
|
||||
1. User taps compass area of main app
|
||||
2. `_showFloatingWindow()` executes: prepares display data, calls `FlutterOverlayWindow.shareData()`, launches overlay
|
||||
3. `FlutterOverlayWindow.showOverlay()` creates new process running `OverlaySpeedometer`
|
||||
4. Overlay's `_listenToMainAppMessages()` sets up listener for `overlayListener`
|
||||
5. Main app pushes updates via periodic `_pushDataToOverlay()` calls when overlay active
|
||||
6. Overlay applies updates to state and rebuilds display
|
||||
|
||||
**Background Persistence:**
|
||||
|
||||
1. App detects background transition via `didChangeAppLifecycleState()`
|
||||
2. Enables wake lock via `WakelockPlus.enable()`
|
||||
3. If overlay active, starts heartbeat timer every 5 seconds
|
||||
4. Heartbeat calls `_pushDataToOverlay()` to maintain communication with overlay process
|
||||
5. On resume, disables heartbeat
|
||||
|
||||
**State Management:**
|
||||
|
||||
- **No centralized Provider** - App uses direct state via `setState()` in main screen
|
||||
- **No local persistence** - GPS data is ephemeral (speed, heading, validity flags)
|
||||
- **Settings stored in SharedPreferences** - Would be implemented here if settings persistence added
|
||||
- **Overlay state isolated** - Overlay maintains its own independent state, receives push updates from main app
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
**ProcessedGpsData (Model):**
|
||||
- Purpose: Represents validated, formatted GPS information
|
||||
- Examples: `lib/services/gps_data_manager.dart` (lines 6-40)
|
||||
- Pattern: Immutable data class with `copyWith()` constructor for updates
|
||||
- Contains: `speed` (m/s), `heading` (degrees), display strings, validity flags
|
||||
|
||||
**GpsDataManager (Singleton Service):**
|
||||
- Purpose: Centralized GPS lifecycle and data processing
|
||||
- Examples: `lib/services/gps_data_manager.dart` (lines 42-263)
|
||||
- Pattern: Singleton (`_instance ??= GpsDataManager._internal()`) with public stream and synchronous getter
|
||||
- Manages: Permission handling, GPS subscription, stale data timeouts, format conversion
|
||||
|
||||
**SpeedUnit (Configuration Enum):**
|
||||
- Purpose: Speed unit definitions with conversion logic
|
||||
- Examples: `lib/speed_units.dart` (lines 1-20)
|
||||
- Pattern: Enum with associated data (label, multiplier) and methods (`convert()`, `next`)
|
||||
- Values: `kmh` (1.0x), `mph` (0.621371x), `knots` (0.539957x) all convert from m/s via 3.6 multiplier
|
||||
|
||||
**ColorTheme (Configuration):**
|
||||
- Purpose: Immutable theme definition for UI colors
|
||||
- Examples: `lib/color_themes.dart` (lines 3-19)
|
||||
- Pattern: Immutable data class with predefined theme collection
|
||||
- Used by: Both main app and overlay for consistent visual styling
|
||||
|
||||
**OverlayListener Pattern:**
|
||||
- Purpose: Async message passing from main app to overlay process
|
||||
- Method: `FlutterOverlayWindow.overlayListener.listen()` (broadcast stream from plugin)
|
||||
- Data format: Maps with action key and payload (speed, heading, theme index, etc.)
|
||||
|
||||
## Entry Points
|
||||
|
||||
**Main App Entry:**
|
||||
- Location: `lib/main.dart` (lines 22-24)
|
||||
- Triggers: `flutter run` or app launch
|
||||
- Responsibilities: Create `SpeedometerApp`, initialize MaterialApp theme, set home to `SpeedometerScreen`
|
||||
|
||||
**Overlay Entry:**
|
||||
- Location: `lib/main.dart` (lines 27-34: `overlayMain()`)
|
||||
- Triggers: `FlutterOverlayWindow.showOverlay()` with overlay file parameter
|
||||
- Responsibilities: Create isolated MaterialApp, set home to `OverlaySpeedometer`, no theme inheritance from main app
|
||||
- Annotation: `@pragma("vm:entry-point")` required for Flutter to recognize as valid entry point
|
||||
|
||||
**Speedometer Screen (Main):**
|
||||
- Location: `lib/main.dart` (lines 56-837: `SpeedometerScreen`, `_SpeedometerScreenState`)
|
||||
- Triggers: Set as home widget in MaterialApp
|
||||
- Responsibilities: Initialize GPS manager, subscribe to data stream, render portrait/landscape layouts, handle overlay lifecycle, manage background persistence
|
||||
|
||||
**Overlay Speedometer (Floating Window):**
|
||||
- Location: `lib/main.dart` (lines 839-1075: `OverlaySpeedometer`, `_OverlaySpeedometerState`)
|
||||
- Triggers: Launched via `FlutterOverlayWindow.showOverlay()`
|
||||
- Responsibilities: Listen for main app updates, render speed/heading display, handle close and long-press gestures, send close signal to main app
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Strategy:** Graceful degradation with user-visible status
|
||||
|
||||
**Patterns:**
|
||||
|
||||
- **GPS Service Disabled:** Set display text to "GPS OFF", mark validity flags false, continue app without crash
|
||||
- **Permissions Denied:** Set display to "NO PERM", mark validity flags false, allow user to retry via OS settings prompt
|
||||
- **GPS Timeout:** `ProcessedGpsData` copied with `isSpeedValid=false`, display text set to "--", heading may remain valid if recently received
|
||||
- **Stale Data:** Timer-based: after 4 seconds without update, set speed to "--" but keep heading if valid
|
||||
- **Overlay Errors:** Wrapped in try-catch in `_showFloatingWindow()` and `_closeFloatingWindow()`, silent failures prevent main app crash
|
||||
- **Stream Errors:** Caught in listener error handler, sets `_errorMessage` for display, allows user to see issue
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
**Logging:**
|
||||
- Direct `print()` statements with emoji prefix codes for identification
|
||||
- Debug-only printing via `customDebugPrint()` which checks `kDebugMode`
|
||||
- Format: `[Component] emoji Descriptive message`
|
||||
- Examples: `[GpsDataManager] 📡 Raw GPS update:`, `[Main] ❌ GPS stream error callback:`
|
||||
|
||||
**Validation:**
|
||||
- Speed validation: `position.speed.isFinite && position.speed >= 0`
|
||||
- Heading validation: `position.heading.isFinite && position.heading >= 0 && position.heading < 360`
|
||||
- Invalid data triggers fallback: speed becomes 0.0, heading keeps last valid value
|
||||
- Display text fallback: "--" used for invalid/stale speed data
|
||||
|
||||
**Permissions:**
|
||||
- Requested via `Permission.locationWhenInUse.request()` from `permission_handler`
|
||||
- Overlay overlay permissions checked via `FlutterOverlayWindow.isPermissionGranted()`
|
||||
- No permission flow UI; user must grant in Android system settings
|
||||
|
||||
**Background Lifecycle:**
|
||||
- Wake lock enabled on init via `WakelockPlus.enable()`
|
||||
- Lifecycle observer via `WidgetsBinding.instance.addObserver(this)`
|
||||
- Background heartbeat timer prevents process hibernation when overlay active
|
||||
- Multiple timer cleanup in dispose to prevent memory leaks
|
||||
|
||||
**Overlay Synchronization:**
|
||||
- Size calculated in main app: `(physicalSize.width * 0.45).round()` for width, `width * 0.6` for height
|
||||
- Size sent to overlay via `shareData()` on creation
|
||||
- Theme index and unit index sent on every update
|
||||
- Speed value re-converted for overlay to use (overlay doesn't have access to `_currentUnit`)
|
||||
|
||||
---
|
||||
|
||||
*Architecture analysis: 2026-02-09*
|
||||
@@ -0,0 +1,206 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## Tech Debt
|
||||
|
||||
**Excessive Debug Logging in Production Code:**
|
||||
- Issue: 27 `print()` calls in `lib/services/gps_data_manager.dart` and 10 `print()` calls in `lib/main.dart` for verbose initialization/lifecycle tracking. These print directly to console in release builds without conditional checking like the rest of the code.
|
||||
- Files: `lib/services/gps_data_manager.dart` (lines 74-261), `lib/main.dart` (lines 337-373)
|
||||
- Impact: Performance overhead, console spam in production APK, potential security information leakage in logs. Makes logs harder to parse since most code uses `customDebugPrint()` guard.
|
||||
- Fix approach: Replace all unconditional `print()` calls with `customDebugPrint()` to respect debug-only context. This is inconsistent with the established logging pattern in the codebase.
|
||||
|
||||
**Disabled Low-Speed Heading Display Logic:**
|
||||
- Issue: Low-speed validation for heading display is temporarily disabled (commented out TODO) to allow testing indoors. Speeds below 1 m/s with invalid heading should show '--' but currently show raw values.
|
||||
- Files: `lib/main.dart` (line 501), `lib/services/gps_data_manager.dart` (line 172)
|
||||
- Impact: GPS data invalid at rest is displayed to user, confusing accuracy metrics. May cause false user reports of heading tracking.
|
||||
- Fix approach: Re-enable the validation logic: `(speed < 1.0 && (heading < 0.0 || heading >= 360.0)) ? '--' : displaySpeed` after completing indoor testing phase. Document test completion criteria.
|
||||
|
||||
**Silent Error Suppression in Overlay Creation:**
|
||||
- Issue: Floating window `_showFloatingWindow()` has a catch block that silently suppresses all exceptions with no logging.
|
||||
- Files: `lib/main.dart` (line 479-481)
|
||||
- Impact: Overlay initialization failures go undetected. User sees no overlay and no error message. Makes debugging overlay issues impossible.
|
||||
- Fix approach: Add `customDebugPrint()` call in catch block to log overlay errors, even at debug level. Users should see informative error messages if overlay fails.
|
||||
|
||||
**Inconsistent Error Handling in Overlay Message Processing:**
|
||||
- Issue: Overlay listener's error handler (line 142-144) logs errors but doesn't inform UI or take corrective action. GPS data stream error handler (line 348-351 in main.dart) sets error message in UI state.
|
||||
- Files: `lib/main.dart` (lines 115-145, 348-351)
|
||||
- Impact: Overlay communication errors won't be visible to user, but GPS stream errors will. Inconsistent user experience for different error types.
|
||||
- Fix approach: Unify error handling: either surface all errors to UI via setState or suppress all non-critical errors consistently. Document which errors are recoverable vs. fatal.
|
||||
|
||||
## Known Bugs
|
||||
|
||||
**Overlay Long-Press Close Unreliable on Subsequent Overlays:**
|
||||
- Symptoms: Long-press to close overlay works on first overlay creation, but gesture detection fails on second and subsequent overlay launches. Overlay stays open after long press.
|
||||
- Files: `lib/main.dart` (lines 1054-1090 overlay gesture handling, lines 649-659 navigation tap detection)
|
||||
- Trigger: Launch overlay, close with long press (works). Launch overlay again, long press fails to close.
|
||||
- Workaround: Tap navigation area to close overlay instead (toggles overlay state based on `_isOverlayActive` flag).
|
||||
- Root cause documented in TODO.md: "Gesture detection works on first overlay but fails on subsequent overlays" - indicates system-level platform limitation.
|
||||
|
||||
**Bidirectional Overlay Communication Unreliable:**
|
||||
- Symptoms: `FlutterOverlayWindow.shareData()` calls from overlay to main app hang or fail silently. Long-press close signal from overlay may not reach main app.
|
||||
- Files: `lib/main.dart` (lines 1063-1069 overlay signal sending), `lib/services/gps_data_manager.dart` (N/A)
|
||||
- Trigger: Long-press overlay to send close signal back to main app.
|
||||
- Workaround: Use status polling instead of message-based detection (`_startOverlayStatusCheck()` at line 153).
|
||||
- Root cause documented in TODO.md: "FlutterOverlayWindow.shareData() from overlay to main app is fundamentally broken"
|
||||
|
||||
**HapticFeedback Calls Hang in Overlay Context:**
|
||||
- Symptoms: HapticFeedback.lightImpact() calls in overlay context cause indefinite hangs (commented out at line 1059).
|
||||
- Files: `lib/main.dart` (line 1059 intentionally skipped with comment)
|
||||
- Trigger: Any attempt to call `HapticFeedback.*` methods from overlay widget.
|
||||
- Workaround: Skip haptic feedback in overlay entirely. Only use on main app screen (lines 415, 424, 649, 657, 775, 783).
|
||||
- Root cause documented in TODO.md: "HapticFeedback.* calls hang indefinitely in overlay context (platform services unavailable)"
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Permission Request Not Validated in All Paths:**
|
||||
- Risk: `GpsService.requestPermissions()` may return false if permission denied by user, but app continues running with `displaySpeed: 'NO PERM'` marker. No explicit permission re-request flow or user guidance.
|
||||
- Files: `lib/services/gps_data_manager.dart` (lines 93-103), `lib/gps_service.dart` (lines 17-20)
|
||||
- Current mitigation: Display 'NO PERM' message to user indicating lack of permission.
|
||||
- Recommendations: Add UI button to re-request permission with explanation. Handle permission state changes via lifecycle callbacks if location service is toggled while app runs.
|
||||
|
||||
**Location Data Transmitted to Overlay Without Verification:**
|
||||
- Risk: GPS location data (coordinates implicit in speed/heading, potentially in logs) transmitted via `FlutterOverlayWindow.shareData()` which may be intercepted by other processes with SYSTEM_ALERT_WINDOW permission.
|
||||
- Files: `lib/main.dart` (lines 403-411, 441-450), `lib/services/gps_data_manager.dart` (N/A)
|
||||
- Current mitigation: Data sent via Flutter internal IPC, not external networking.
|
||||
- Recommendations: Verify overlay target identity before sharing sensitive location data. Consider encrypting location data for inter-process communication. Document data privacy model.
|
||||
|
||||
**No Certificate Pinning for Future API Integrations:**
|
||||
- Risk: Codebase currently has no external API calls, but structure provides no hook for certificate validation if APIs are added.
|
||||
- Files: N/A currently
|
||||
- Current mitigation: N/A
|
||||
- Recommendations: When adding external API calls, implement certificate pinning and SSL validation. Add secrets management (env vars not checked into git).
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
**1133-Line Main.dart File Creates Monolithic State Management:**
|
||||
- Problem: Single `_SpeedometerScreenState` manages GPS subscription, overlay lifecycle, theme cycling, unit cycling, haptic feedback, background heartbeat, overlay status polling, and gesture handling. Complex interdependencies make optimization difficult.
|
||||
- Files: `lib/main.dart` (entire file, especially lines 65-837)
|
||||
- Cause: All UI state and cross-cutting concerns collapsed into one widget state. No separation of concerns.
|
||||
- Improvement path: Extract overlay management to separate `OverlayController` class. Extract compass/navigation into `CompassWidget`. Extract speed display into `SpeedDisplay` widget. Reduces main file to ~400 lines. Would enable independent optimization of each subsystem.
|
||||
|
||||
**Background Heartbeat Timer Fires Every 5 Seconds Even When Not Needed:**
|
||||
- Problem: `_startBackgroundHeartbeat()` (line 197-220) runs on 5-second intervals for the entire app lifetime. Performs setState() even when overlay is inactive and app is in foreground (wasteful).
|
||||
- Files: `lib/main.dart` (lines 197-220)
|
||||
- Cause: Timer always runs without condition to stop when not needed. Only guards action inside timer callback, not timer creation.
|
||||
- Improvement path: Stop heartbeat timer when app resumes foreground and overlay is closed. Restart only when background + overlay active. Would save 5+ timer ticks per minute during normal use.
|
||||
|
||||
**Overlay Status Polling Runs Every 1 Second While Overlay Active:**
|
||||
- Problem: `_startOverlayStatusCheck()` (line 153-174) polls `FlutterOverlayWindow.isActive()` every 1 second. This is async call that may block on every poll.
|
||||
- Files: `lib/main.dart` (lines 153-174)
|
||||
- Cause: Workaround for unreliable message-based close detection. Polling is expensive fallback.
|
||||
- Improvement path: Once overlay message handling is fixed (known bug), remove polling entirely. Fallback to message-based detection which is event-driven rather than time-based.
|
||||
|
||||
**Overlay Size Calculation Runs on Every Build:**
|
||||
- Problem: `_getOverlaySize()` (line 85-94) recalculates overlay dimensions every time overlay is shown by querying `platformDispatcher.views.first.physicalSize`. View dimensions don't change frequently.
|
||||
- Files: `lib/main.dart` (lines 85-94, called at lines 435, 461)
|
||||
- Cause: Called from `_showFloatingWindow()` which is synchronous but not cached.
|
||||
- Improvement path: Cache overlay size on first calculation. Invalidate cache only on orientation change or platform dispatcher size change. Would save repeated calculations on repeated overlay launches.
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
**GpsDataManager Singleton State Not Thread-Safe:**
|
||||
- Files: `lib/services/gps_data_manager.dart` (lines 42-50)
|
||||
- Why fragile: `_instance` variable is lazily initialized without synchronization. If `GpsDataManager.instance` is called from multiple isolates or during hot-reload, could create multiple instances. `_dataController` broadcast stream is also not protected from concurrent additions.
|
||||
- Safe modification: Check if used from single isolate only (likely true in Flutter). Add comment documenting single-threaded assumption. For multi-threaded use, wrap initialization in `Mutex` or use `async` getter.
|
||||
- Test coverage: No unit tests. No integration tests for concurrent access patterns.
|
||||
|
||||
**Overlay Widget Assumes Shared Data Structure Without Validation:**
|
||||
- Files: `lib/main.dart` (lines 897-927, the `_OverlaySpeedometerState._listenToMainAppMessages()`)
|
||||
- Why fragile: Overlay widget assumes incoming map data has expected keys (`speedText`, `unitText`, etc.). Missing keys get null and default values. If main app changes data structure, overlay silently receives invalid data. No schema validation.
|
||||
- Safe modification: Add data validation function that checks for required keys before processing. Document expected message schema as Dart class/typedef. Type-safe would require moving message handling to common file.
|
||||
- Test coverage: No unit tests for message deserialization. No schema validation tests.
|
||||
|
||||
**GpsDataManager Display Logic Disabled Means Invalid Data State:**
|
||||
- Files: `lib/services/gps_data_manager.dart` (line 172, comment), `lib/main.dart` (line 501, comment)
|
||||
- Why fragile: With TODO-disabled validation, invalid GPS speeds can propagate to UI. Code assumes `displaySpeed == '--'` check at line 246 and 243, but that won't match if speed is invalid.
|
||||
- Safe modification: Create feature flag `const bool kEnableLowSpeedValidation = false;` and wrap logic. Then toggle to true when ready. This documents the temporary nature and makes re-enabling obvious.
|
||||
- Test coverage: No tests for low-speed heading suppression logic.
|
||||
|
||||
**Overlay Creation Success Not Verified:**
|
||||
- Files: `lib/main.dart` (line 453-478)
|
||||
- Why fragile: `FlutterOverlayWindow.showOverlay()` completes without error, but overlay may fail to display due to platform restrictions (permissions, Android version, overlay count limits). Code immediately sets `_isOverlayActive = true` (line 468) without waiting to confirm overlay actually appeared.
|
||||
- Safe modification: Add verification step after `showOverlay()` call. Wait for first GPS data update or first message from overlay to confirm it started. Set `_isOverlayActive = true` only after confirmation. Currently relying on `_startOverlayStatusCheck()` to detect if overlay is really active.
|
||||
- Test coverage: No tests. Manual testing on different Android versions needed.
|
||||
|
||||
## Scaling Limits
|
||||
|
||||
**Single ProcessedGpsData Stream for All Consumers:**
|
||||
- Current capacity: Single broadcast stream shared by main app + overlay messaging. GPS updates broadcast to all listeners every time, even if only main app needs update.
|
||||
- Limit: If multiple independent overlay instances tried to subscribe (not current behavior), stream backpressure could cause updates to queue. Currently not scalable to background service model.
|
||||
- Scaling path: Extract data provider to platform channel with multiple listener support. Or move to provider/GetX for proper multi-listener state management. Document why direct stream subscriptions chosen.
|
||||
|
||||
**Hardcoded Timeout Values for GPS and Stale Data:**
|
||||
- Current capacity: Initial GPS timeout 20s, ongoing stream timeout 2s, stale data threshold 4s. Values tuned for mobile use.
|
||||
- Limit: Cannot adjust timeouts without code change + rebuild. Fails on edge devices with poor GPS (rural areas, deep indoors). No adaptive timeout mechanism.
|
||||
- Scaling path: Make timeouts configurable via SharedPreferences settings screen. Allow user to dial in for their use case (rural vs. urban, indoor vs. outdoor).
|
||||
|
||||
## Dependencies at Risk
|
||||
|
||||
**flutter_overlay_window: Multiple Known Issues:**
|
||||
- Risk: Package has known limitations (shared data communication broken, gesture detection unreliable) documented in TODO.md and throughout code as hardcoded workarounds.
|
||||
- Impact: If package is not maintained or breaks in future Android versions, overlay feature will stop working. No fallback implementation.
|
||||
- Migration plan: Maintain fallback to non-overlay display mode (remove GPS data from overlay, user keeps main app in foreground). Or migrate to newer overlay library if available. Pin to specific version and test on each Flutter/Android release.
|
||||
|
||||
**geolocator & permission_handler: Version Pinning:**
|
||||
- Risk: `pubspec.yaml` pins geolocator to `^14.0.2` and permission_handler to `^12.0.1`. These are major versions so minor updates are allowed, but breaking changes possible. Project was previously on lower versions.
|
||||
- Impact: Flutter dependency resolution could pull incompatible minor version. Not currently a blocker but prevents easy updates.
|
||||
- Migration plan: After each Flutter/Android release, run `flutter pub upgrade --dry-run` to check available updates. Test on physical device before committing. Document breaking changes when they occur. Consider migrating to `geolocator: 14.x.x` style for more flexibility.
|
||||
|
||||
**bg_launcher: Minimal Package with Limited Maintenance:**
|
||||
- Risk: `bg_launcher` is a small utility package for bringing app to foreground. May not be maintained actively. Only has 1 public method (`bringAppToForeground()`).
|
||||
- Impact: If package breaks or is removed from pub.dev, overlay long-press close won't bring main app to foreground. Would require platform-specific implementation via method channels.
|
||||
- Migration plan: Document method channel approach in comments. If bg_launcher fails, implement native Android code to bring app to foreground using `startActivity()`. Keep fallback ready.
|
||||
|
||||
## Missing Critical Features
|
||||
|
||||
**No User-Facing Error Messages for Overlay Failures:**
|
||||
- Problem: If overlay fails to create, user gets no indication. Code catches exception (line 479-481) but prints nothing. User sees no overlay and no error, assumes app is broken.
|
||||
- Blocks: Users cannot diagnose why overlay won't appear. Cannot report meaningful feedback.
|
||||
- Recommendation: Display toast/snackbar if overlay creation fails: "Floating window permission denied. Check app permissions." Include specific error category in message.
|
||||
|
||||
**No Permission Re-Request Flow:**
|
||||
- Problem: If user denies GPS permission, app shows 'NO PERM' message. No button to retry or open settings to grant permission.
|
||||
- Blocks: User must manually go to Settings > App > Permissions to re-grant. App cannot recover from denied permission state.
|
||||
- Recommendation: Add "Request Permission" button when 'NO PERM' is displayed. Call `openAppSettings()` from permission_handler to open system settings if user wants to grant manually.
|
||||
|
||||
**No Connection to SharedPreferences (Mentioned in CLAUDE.md but Not Implemented):**
|
||||
- Problem: CLAUDE.md mentions "Persistence: SharedPreferences for settings" and states v2.1.0 had "Customizable speed units (km/h, mph) with persistent storage". Current code has no SharedPreferences integration.
|
||||
- Blocks: Unit selection resets on app restart. Theme selection resets on app restart. No way to save user preferences.
|
||||
- Recommendation: Implement SharedPreferences save/load for `_currentUnit` and `_currentThemeIndex`. Load from preferences in `initState()`. Save on each change.
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
**No Unit Tests for GpsDataManager:**
|
||||
- What's not tested: ProcessedGpsData copyWith(), data stream broadcasting, stale data timeout, error handling, speed formatting logic.
|
||||
- Files: `lib/services/gps_data_manager.dart` (entire file)
|
||||
- Risk: Changes to GPS data processing logic could break undetected. Stale data timeout edge cases not verified. Heading validation changes could silently fail.
|
||||
- Priority: High - GPS data is core to app functionality. Stale data timeout is time-sensitive logic prone to off-by-one errors.
|
||||
|
||||
**No Tests for Overlay Message Deserialization:**
|
||||
- What's not tested: Map data validation, missing key handling, type coercion, corrupt data recovery.
|
||||
- Files: `lib/main.dart` (lines 897-927)
|
||||
- Risk: If main app changes message format, overlay silently receives invalid data. Edge case where heading > 360 or speed is negative not tested.
|
||||
- Priority: Medium - affects overlay reliability but occurs in steady state, not initialization.
|
||||
|
||||
**No Integration Tests for Main/Overlay Communication:**
|
||||
- What's not tested: shareData() timing, message ordering, concurrent updates, cleanup on close.
|
||||
- Files: `lib/main.dart` (lines 403-411, 441-450 main→overlay; lines 1063-1069 overlay→main; lines 346-352 subscription setup)
|
||||
- Risk: Race conditions between overlay creation and first data push. Message loss if overlay closes while data is in flight. Zombie overlays not cleaned up.
|
||||
- Priority: High - known bugs in this area (unreliable bidirectional communication) need test-driven fixes.
|
||||
|
||||
**No Tests for Lifecycle State Management:**
|
||||
- What's not tested: App resume/pause/terminate → overlay state transitions, timer cleanup on dispose, subscription cleanup on reload.
|
||||
- Files: `lib/main.dart` (lines 267-277 dispose, lines 280-300 lifecycle callbacks, lines 96-98 timer initialization)
|
||||
- Risk: Memory leaks from uncanceled timers. Orphaned subscriptions. Overlays not closed on app exit.
|
||||
- Priority: High - affects app stability and battery life. Timer leak easily happens with hot-reload during development.
|
||||
|
||||
**No Manual Device Testing Documentation:**
|
||||
- What's not tested: App behavior on different Android versions (11, 12, 13, 14), different screen sizes (phones, tablets), different GPS conditions (outdoor, indoor, no signal).
|
||||
- Files: All files
|
||||
- Risk: Overlay feature tested only on developer's device. May fail silently on other devices. No way to onboard new testers or validate before release.
|
||||
- Priority: Medium - critical for APK release but not blocking development. TODO.md mentions "Requires physical Android device for GPS functionality" but gives no testing checklist.
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-02-09*
|
||||
@@ -0,0 +1,160 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- snake_case for file names: `gps_service.dart`, `color_themes.dart`, `speed_units.dart`
|
||||
- Underscore prefix for internal/private files: `gps_data_manager.dart` in `services/` directory
|
||||
|
||||
**Classes:**
|
||||
- PascalCase: `GpsService`, `ColorTheme`, `ColorThemes`, `GpsDataManager`, `ProcessedGpsData`
|
||||
- State classes use underscore prefix: `_SpeedometerScreenState`, `_OverlaySpeedometerState`
|
||||
- Widget classes standard PascalCase: `SpeedoApp`, `SpeedometerScreen`, `OverlaySpeedometer`
|
||||
|
||||
**Functions:**
|
||||
- camelCase for all functions and methods: `initialize()`, `requestPermissions()`, `getFormattedSpeed()`, `_onPositionUpdate()`, `_onGpsError()`
|
||||
- Private/internal functions use underscore prefix: `_updateData()`, `_scheduleStaleDataTimer()`, `_buildPortraitLayout()`
|
||||
- Single-purpose private methods are common: `_cycleUnit()`, `_cycleTheme()`, `_handleOverlayClose()`
|
||||
|
||||
**Variables:**
|
||||
- camelCase for instance and local variables: `_currentUnit`, `_currentThemeIndex`, `displaySpeed`, `speedText`
|
||||
- Underscore prefix for private fields: `_gpsSubscription`, `_currentData`, `_isOverlayActive`, `_errorMessage`
|
||||
- Boolean variables include descriptive names: `_isInitialized`, `_isSpeedValid`, `_isHeadingValid`, `_isInBackground`
|
||||
|
||||
**Types:**
|
||||
- PascalCase for enums: `SpeedUnit` with enum values in lowercase: `kmh`, `mph`, `knots`
|
||||
- Data classes: `ProcessedGpsData`, `ColorTheme` with named constructors using `const`
|
||||
|
||||
**Constants:**
|
||||
- UPPER_CASE with underscore prefix for private constants: `_staleDataThreshold`
|
||||
- camelCase for enum members: `kmh`, `mph`, `knots`, `themes`
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- 2-space indentation throughout all Dart files
|
||||
- Lines typically kept under 100 characters but pragmatically longer when needed (examples: line 671-672 in main.dart for complex expressions)
|
||||
- Single newline between method definitions
|
||||
- Double newline between class definitions
|
||||
|
||||
**Linting:**
|
||||
- Uses `flutter_lints: ^6.0.0` from pubspec.yaml
|
||||
- Configured via `analysis_options.yaml` with `include: package:flutter_lints/flutter.yaml`
|
||||
- Explicit `exclude` pattern for backup directories: `backup_v1.1/**`
|
||||
|
||||
**Language Features:**
|
||||
- Uses `const` constructors for immutable data classes: `const ProcessedGpsData(...)`, `const ColorTheme(...)`
|
||||
- Enum with properties: `SpeedUnit` enum includes label and multiplier
|
||||
- Named parameters with required keyword: `required this.speed`, `required this.label`
|
||||
- Null safety enabled by default (Dart 3.9+)
|
||||
|
||||
## Import Organization
|
||||
|
||||
**Order:**
|
||||
1. Dart SDK imports: `import 'dart:async'`, `import 'dart:math'`
|
||||
2. Flutter framework imports: `import 'package:flutter/material.dart'`, `import 'package:flutter/foundation.dart'`
|
||||
3. Third-party package imports: `import 'package:geolocator/geolocator.dart'`, `import 'package:permission_handler/permission_handler.dart'`
|
||||
4. Relative imports (local files): `import 'speed_units.dart'`, `import 'services/gps_data_manager.dart'`
|
||||
|
||||
**Path Aliases:**
|
||||
- No path aliases configured - uses explicit relative imports
|
||||
- Deep imports work from lib root: `import 'services/gps_data_manager.dart'` from main.dart, `import '../speed_units.dart'` from services/gps_data_manager.dart
|
||||
|
||||
**Import Annotations:**
|
||||
- Uses `as` for disambiguation: `import 'dart:math' as math;` to access `math.pi`
|
||||
- Comments inline with imports for clarity: `import 'package:flutter/foundation.dart'; // For kDebugMode`
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Patterns:**
|
||||
- Try-catch blocks with specific error handling: 15 try-catch blocks across codebase
|
||||
- Error messages propagated through `setState()`: `setState(() => _errorMessage = 'Failed to initialize GPS manager')`
|
||||
- Silent error handling in production code: catch blocks with `customDebugPrint()` only - see `_showFloatingWindow()` line 480
|
||||
- Stack trace logging: `catch (e, stackTrace)` pattern used consistently in GPS manager and main app
|
||||
- Error display in UI: `_errorMessage` variable checked in build methods and displayed when non-empty
|
||||
|
||||
**Error Message Patterns:**
|
||||
- Prefixed error states in GPS manager: `'GPS OFF'`, `'NO PERM'`, `'GPS ERR'` for UI display
|
||||
- Detailed debug messages for troubleshooting: `[GpsDataManager] ❌ GPS manager initialization exception: $e`
|
||||
|
||||
## Logging
|
||||
|
||||
**Framework:**
|
||||
- Uses `print()` for initialization and critical operations (GPS manager setup, main app lifecycle)
|
||||
- Uses custom `customDebugPrint()` helper for conditional debug output
|
||||
- Logging conditionally enabled in debug mode via `if (kDebugMode)` check
|
||||
|
||||
**Pattern - customDebugPrint():**
|
||||
```dart
|
||||
void customDebugPrint(String message) {
|
||||
if (kDebugMode) {
|
||||
print(message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Patterns:**
|
||||
- Heavy use of emoji prefixes for log levels: ✅ success, ❌ error, ⚠️ warning, 📡 data/status, 🚀 start, 🔴 end
|
||||
- Consistent tagging system: `[GpsDataManager]`, `[Main]`, `[Overlay]` for source identification
|
||||
- Log immediately on entry and exit: `print('[Main] 🚀 Starting GPS manager initialization...')` and `print('[Main] ✅ GPS manager initialization completed')`
|
||||
- Detailed state information in logs: `customDebugPrint('[Main] 📡 Received GPS data: ${gpsData.speed.toStringAsFixed(1)} m/s, ${gpsData.displayHeading}')`
|
||||
|
||||
## Comments
|
||||
|
||||
**When to Comment:**
|
||||
- Block comments explain WHY, not WHAT: `// Aggressive heartbeat to keep main app process active for overlay communication`
|
||||
- Inline comments for non-obvious logic: `// Use actual physical pixels for overlay sizing`
|
||||
- TODO comments mark future work: `// TODO: Re-enable: if (_currentGpsData.speed < 1.0...` (line 501)
|
||||
- Feature notes: `// Tap to bring main app to front functionality removed per NOTES.txt` (line 1092)
|
||||
|
||||
**JSDoc/TSDoc:**
|
||||
- Not used. Dart conventions not followed strictly for documentation.
|
||||
- Comments are inline and contextual, not formal doc comments.
|
||||
|
||||
## Function Design
|
||||
|
||||
**Size:**
|
||||
- Methods vary from 3 lines to 70+ lines
|
||||
- Widget build methods are large (100+ lines) as they contain layout logic
|
||||
- Helper methods tend to be 10-20 lines: `_cycleUnit()`, `_cycleTheme()`, `_formatHeading()`
|
||||
|
||||
**Parameters:**
|
||||
- Named parameters preferred over positional: `{required this.speed, required this.heading}`
|
||||
- Optional parameters with defaults: `void customDebugPrint(String message)`
|
||||
- Named parameters in method calls: `await FlutterOverlayWindow.showOverlay(enableDrag: true, overlayTitle: "Speedometer"...)`
|
||||
|
||||
**Return Values:**
|
||||
- Void for side-effect operations: `void _cycleUnit()`, `void _cycleTheme()`, `void _updateData()`
|
||||
- Stream returns for reactive data: `Stream<ProcessedGpsData> get dataStream => _dataController.stream`
|
||||
- Widget returns for UI methods: `Widget _buildSpeedDisplay(...)`, `Widget _buildPortraitLayout(...)`
|
||||
- Future returns for async operations: `Future<void> _enableWakelock()`, `Future<void> _initializeGpsManager()`
|
||||
- String returns for formatting: `String getFormattedSpeed(SpeedUnit unit)`, `String formatHeading(double heading)`
|
||||
|
||||
## Module Design
|
||||
|
||||
**Exports:**
|
||||
- No explicit pub exports used
|
||||
- Public API via static methods: `GpsService.isLocationServiceEnabled()`, `ColorThemes.getTheme()`
|
||||
- Singleton pattern for manager: `GpsDataManager.instance`
|
||||
|
||||
**Barrel Files:**
|
||||
- Not used - each module imported directly by name
|
||||
- Example: `import 'services/gps_data_manager.dart'` (not `import 'services/index.dart'`)
|
||||
|
||||
**Singleton Pattern:**
|
||||
- Used in `GpsDataManager`: lazy initialization with null coalescing
|
||||
```dart
|
||||
static GpsDataManager? _instance;
|
||||
static GpsDataManager get instance => _instance ??= GpsDataManager._internal();
|
||||
GpsDataManager._internal();
|
||||
```
|
||||
|
||||
**Service Classes:**
|
||||
- Stateless utility class: `GpsService` contains only static methods
|
||||
- Stateful manager: `GpsDataManager` contains mutable state and streams
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-02-09*
|
||||
@@ -0,0 +1,163 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**Location Services:**
|
||||
- Geolocator API (geolocator 14.0.2)
|
||||
- What it's used for: Real-time GPS positioning, speed, heading, and location tracking
|
||||
- SDK/Client: `package:geolocator` (Dart wrapper around native location APIs)
|
||||
- Implementation: `lib/gps_service.dart` provides stream-based position updates
|
||||
- Native integration: Android (geolocator_android 5.0.2), iOS (geolocator_apple 2.3.13), Linux, Web, Windows
|
||||
- Stream Configuration: LocationSettings with `LocationAccuracy.bestForNavigation` and `distanceFilter: 0` for continuous GPS updates
|
||||
|
||||
**System Overlay:**
|
||||
- Flutter Overlay Window (flutter_overlay_window 0.5.0)
|
||||
- What it's used for: Creates floating window overlay for displaying GPS speed while app is in background
|
||||
- Implementation: `lib/main.dart` with `overlayMain()` entry point
|
||||
- Service: Android service `flutter.overlay.window.flutter_overlay_window.OverlayService`
|
||||
- Requires: `SYSTEM_ALERT_WINDOW` permission and Android API 29+
|
||||
|
||||
**App Launcher:**
|
||||
- bg_launcher 0.1.0
|
||||
- What it's used for: Brings main app to foreground from background or overlay state
|
||||
- Implementation: Used in `lib/main.dart` for overlay interaction handling
|
||||
|
||||
## Data Storage
|
||||
|
||||
**Databases:**
|
||||
- None detected - Application uses client-side state only
|
||||
|
||||
**File Storage:**
|
||||
- SharedPreferences (Android native, not explicitly in pubspec.yaml but referenced in backup version history)
|
||||
- Location: Android internal app data directory
|
||||
- Used for: Persisting user settings (speed units, theme preferences)
|
||||
- Implementation: Handled at runtime through Provider/State management
|
||||
- Note: Direct SharedPreferences not found in current active code, settings managed through in-app state
|
||||
|
||||
**Caching:**
|
||||
- In-memory state management via Dart
|
||||
- GPS data cached in `lib/services/gps_data_manager.dart` ProcessedGpsData class
|
||||
- Heading caching in `lib/gps_service.dart` with `_lastValidHeading` static variable
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**Auth Provider:**
|
||||
- None - Application is fully offline and requires no user authentication
|
||||
- Permissions are handled via OS-level runtime permission requests
|
||||
- Implementation: `lib/gps_service.dart` with `requestPermissions()` using permission_handler
|
||||
|
||||
**Permission Management:**
|
||||
- permission_handler 12.0.1
|
||||
- Location permission request: `Permission.locationWhenInUse.request()`
|
||||
- Platforms: Android (13.0.1), iOS (9.4.7), Windows (0.2.1), HTML (0.1.3+5)
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- None detected - No external error tracking service integrated
|
||||
|
||||
**Logs:**
|
||||
- Console logging via Dart's `print()` function
|
||||
- Debug mode conditional logging: `if (kDebugMode)` check in `customDebugPrint()` function
|
||||
- Logging in: `lib/main.dart`, `lib/services/gps_data_manager.dart`
|
||||
- Format: Prefixed messages with emoji indicators (e.g., '[Overlay] 🚀', '[GpsDataManager] ⚠️')
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- Not configured - Deployment is manual
|
||||
- Target: Google Play Store (intended) or sideload APK distribution
|
||||
- Build output: Flutter generates APK files via `flutter build apk`
|
||||
|
||||
**CI Pipeline:**
|
||||
- None detected - No GitHub Actions, GitLab CI, or other CI/CD configuration present
|
||||
- Manual build process:
|
||||
- `flutter run` for development
|
||||
- `flutter build apk` for release builds
|
||||
- Signing configured in Gradle (currently uses debug keys for release, see TODO in `android/app/build.gradle.kts`)
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required env vars:**
|
||||
- None - Application requires no environment variables
|
||||
- All configuration is hardcoded or managed through app state at runtime
|
||||
|
||||
**Secrets location:**
|
||||
- None - No API keys, tokens, or secrets integrated
|
||||
- Android signing keys: Located in Android default keystore (debug only currently)
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- None - Application is fully self-contained with no webhook endpoints
|
||||
|
||||
**Outgoing:**
|
||||
- None - No external API calls or webhook notifications
|
||||
- All data remains local to device
|
||||
|
||||
## Native Platform Integrations
|
||||
|
||||
**Android:**
|
||||
- Geolocator Android Plugin (geolocator_android 5.0.2) - FusedLocationProviderClient integration
|
||||
- Permission Handler Android (permission_handler_android 13.0.1) - Runtime permission requests
|
||||
- Flutter Overlay Window - System alert window overlay
|
||||
- Waklock Plus Android integration - CPU/screen wake lock via WakeLock API
|
||||
- Configured permissions in AndroidManifest.xml:
|
||||
- `android.permission.ACCESS_FINE_LOCATION`
|
||||
- `android.permission.ACCESS_COARSE_LOCATION`
|
||||
- `android.permission.ACCESS_BACKGROUND_LOCATION`
|
||||
- `android.permission.SYSTEM_ALERT_WINDOW`
|
||||
- `android.permission.FOREGROUND_SERVICE`
|
||||
- `android.permission.FOREGROUND_SERVICE_SPECIAL_USE`
|
||||
- `android.permission.WAKE_LOCK`
|
||||
- `android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`
|
||||
|
||||
**iOS:**
|
||||
- Geolocator Apple Plugin (geolocator_apple 2.3.13) - CoreLocation integration
|
||||
- Permission Handler Apple (permission_handler_apple 9.4.7) - iOS runtime permission handling
|
||||
- Wakelock Plus (requires iOS implementation)
|
||||
- Note: iOS functionality is included but untested
|
||||
|
||||
## Data Flow & Synchronization
|
||||
|
||||
**GPS Data Flow:**
|
||||
1. Hardware GPS → `geolocator.getPositionStream()` streams Position objects
|
||||
2. Position objects captured in `lib/services/gps_data_manager.dart`
|
||||
3. Processed into ProcessedGpsData with speed, heading, and display strings
|
||||
4. Broadcast via `StreamController<ProcessedGpsData>` for UI consumption
|
||||
5. Optional: Sent to overlay window via flutter_overlay_window messaging
|
||||
|
||||
**Settings Synchronization:**
|
||||
- Current unit (km/h, mph, knots) synchronized between main app and overlay
|
||||
- Current theme synchronized between main app and overlay
|
||||
- Synchronization method: Direct state transmission (not persistent between app restarts)
|
||||
|
||||
## External Dependencies Summary
|
||||
|
||||
**Direct Dependencies:** 6 packages
|
||||
- geolocator 14.0.2
|
||||
- permission_handler 12.0.1
|
||||
- wakelock_plus 1.3.2 (recently updated to 1.3.2)
|
||||
- flutter_overlay_window 0.5.0
|
||||
- bg_launcher 0.1.0
|
||||
- flutter (SDK)
|
||||
|
||||
**Dev Dependencies:** 3 packages
|
||||
- flutter_test (SDK)
|
||||
- flutter_lints 6.0.0
|
||||
- flutter_launcher_icons 0.14.4
|
||||
|
||||
**Transitive Dependencies:** 40+ packages (most are Flutter/Dart core utilities)
|
||||
|
||||
**No External Cloud Services:**
|
||||
- No Firebase, Supabase, AWS, or other backend services
|
||||
- No REST API integrations
|
||||
- No database synchronization
|
||||
- No analytics or tracking services
|
||||
- No third-party payment or subscription services
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-02-09*
|
||||
@@ -0,0 +1,132 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- Dart 3.9.0+ - Flutter app logic, services, UI widgets
|
||||
- Kotlin - Android native bridge and plugin implementations
|
||||
- Swift - iOS native bridge (untested)
|
||||
|
||||
**Secondary:**
|
||||
- XML - Android manifest and resource files
|
||||
- YAML - Flutter configuration and package management
|
||||
- XML/Plist - iOS configuration
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- Flutter 3.24.0+ (SDK)
|
||||
- Android Runtime (API 21+, configured via Gradle)
|
||||
- iOS Runtime (optional, iOS 12.0+)
|
||||
|
||||
**Package Manager:**
|
||||
- Pub (Dart package manager)
|
||||
- Lockfile: `pubspec.lock` (present)
|
||||
- Direct dependencies resolved from `pub.dev`
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- Flutter 0.0.0 - UI framework and cross-platform mobile development
|
||||
- Material Design 3 - Flutter Material Design components with `useMaterial3: true`
|
||||
|
||||
**Testing:**
|
||||
- Flutter Test SDK - Unit and widget testing framework (included in Flutter SDK)
|
||||
- flutter_lints 6.0.0 - Linting rules for code quality
|
||||
|
||||
**Build/Dev:**
|
||||
- flutter_launcher_icons 0.14.4 - Icon generation for Android and iOS
|
||||
- Gradle (Kotlin DSL) - Android build system configured in `android/app/build.gradle.kts`
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- geolocator 14.0.2 - GPS positioning and real-time location streaming with heading/speed data
|
||||
- Platform-specific implementations: geolocator_android, geolocator_apple, geolocator_linux, geolocator_web, geolocator_windows
|
||||
- Transitive: geolocator_platform_interface 4.2.6
|
||||
- permission_handler 12.0.1 - Runtime permissions management (location, overlay window)
|
||||
- Platform-specific: permission_handler_android, permission_handler_apple, permission_handler_windows, permission_handler_html
|
||||
|
||||
**Infrastructure:**
|
||||
- wakelock_plus 1.3.2 - Keeps device screen/CPU awake during GPS tracking
|
||||
- Transitive: wakelock_plus_platform_interface 1.2.3
|
||||
- flutter_overlay_window 0.5.0 - System-level floating window overlay for GPS data display
|
||||
- Core overlay service: `flutter.overlay.window.flutter_overlay_window.OverlayService`
|
||||
- bg_launcher 0.1.0 - Brings app to foreground from background or overlay state
|
||||
|
||||
**Utilities:**
|
||||
- http 1.5.0 (transitive) - HTTP client library (included as dependency of other packages)
|
||||
- package_info_plus 8.3.1 (transitive) - App version and package info
|
||||
- uuid 4.5.1 (transitive) - UUID generation for identifiers
|
||||
- meta 1.16.0 (transitive) - Dart language annotations
|
||||
|
||||
**Development Utilities:**
|
||||
- archive 4.0.7 - For launcher icon generation
|
||||
- image 4.5.4 - Image processing for icons
|
||||
- json_annotation 4.9.0 - JSON serialization support
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- No `.env` files or environment variable configuration detected
|
||||
- Configuration is hardcoded in Dart code and managed through SharedPreferences during runtime
|
||||
- Key runtime settings:
|
||||
- Speed units (km/h, mph, knots) stored in app state
|
||||
- Theme selection (dark/light) managed in state
|
||||
- Location service permissions checked at runtime
|
||||
|
||||
**Build:**
|
||||
- Android build configuration: `android/app/build.gradle.kts`
|
||||
- Namespace: `com.novoyuuparosk.speedo`
|
||||
- Min SDK: Flutter default
|
||||
- Target SDK: Flutter default
|
||||
- Source/Target Compatibility: Java 11
|
||||
- JVM Target: Java 11 (Kotlin)
|
||||
- iOS build configuration: `ios/Runner/Info.plist`
|
||||
- Bundle ID: `$(PRODUCT_BUNDLE_IDENTIFIER)`
|
||||
- Supported orientations: Portrait, Landscape (Left/Right), Portrait Upside Down (iPad)
|
||||
|
||||
**Icon Configuration:**
|
||||
- flutter_launcher_icons configuration in pubspec.yaml
|
||||
- Source image: `icon/speedo.png`
|
||||
- Generates both Android and iOS icons with adaptive icon support
|
||||
- Adaptive foreground: `icon/speedo.png`
|
||||
|
||||
**Fonts:**
|
||||
- Custom font: DIN1451Alt
|
||||
- Asset file: `fonts/din1451alt.ttf`
|
||||
- Used for speedometer display typography
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Flutter SDK 3.24.0 or later
|
||||
- Dart 3.9.0 or later
|
||||
- Android development environment with Gradle
|
||||
- Kotlin compiler (for Android native code)
|
||||
- Physical Android device or emulator with GPS simulation (recommended: physical device for accurate GPS)
|
||||
|
||||
**Production:**
|
||||
- Android 5.0+ (API 21+) for primary platform
|
||||
- iOS 12.0+ (secondary/untested platform)
|
||||
- Foreground service capability (Android 12+)
|
||||
- System alert window permission (for overlay functionality)
|
||||
- Location services enabled on device
|
||||
- Screen wake lock capability for continuous monitoring
|
||||
|
||||
**Permissions & Capabilities:**
|
||||
- Runtime permissions:
|
||||
- `ACCESS_FINE_LOCATION` - High-precision GPS
|
||||
- `ACCESS_COARSE_LOCATION` - Network-based location fallback
|
||||
- `ACCESS_BACKGROUND_LOCATION` - Background location access (optional)
|
||||
- `SYSTEM_ALERT_WINDOW` - Floating overlay window
|
||||
- `FOREGROUND_SERVICE` - Background service requirement
|
||||
- `FOREGROUND_SERVICE_SPECIAL_USE` - Overlay service special use
|
||||
- `WAKE_LOCK` - Keep device awake
|
||||
- `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` - Battery optimization bypass
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-02-09*
|
||||
@@ -0,0 +1,191 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
gps_speedo/
|
||||
├── lib/ # Main Dart source code
|
||||
│ ├── main.dart # App entry points and UI widgets
|
||||
│ ├── gps_service.dart # GPS hardware abstraction
|
||||
│ ├── speed_units.dart # Speed unit enum and conversion
|
||||
│ ├── color_themes.dart # Theme data and definitions
|
||||
│ └── services/
|
||||
│ └── gps_data_manager.dart # GPS data processing singleton
|
||||
├── android/ # Android native code and config
|
||||
│ └── app/src/main/
|
||||
│ ├── AndroidManifest.xml # GPS and system alert window permissions
|
||||
│ └── kotlin/com/novoyuuparosk/speedo/ # Android entry point
|
||||
├── fonts/ # Custom fonts
|
||||
│ └── din1451alt.ttf # DIN 1451 Alt display font
|
||||
├── icon/ # App launcher icon
|
||||
│ └── speedo.png # Icon image for Android/iOS
|
||||
├── pubspec.yaml # Flutter dependencies and project config
|
||||
├── pubspec.lock # Dependency lock file
|
||||
├── analysis_options.yaml # Dart analyzer configuration
|
||||
├── test/ # Test files
|
||||
│ └── widget_test.dart # Widget testing scaffold
|
||||
└── .dart_tool/ # Dart/Flutter build artifacts (generated)
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
**`lib/`:**
|
||||
- Purpose: All application Dart source code
|
||||
- Contains: Widgets, services, models, configuration enums
|
||||
- Key files: `main.dart` is the central file containing 2 widget classes (44KB)
|
||||
|
||||
**`lib/services/`:**
|
||||
- Purpose: Business logic services isolated from UI
|
||||
- Contains: `GpsDataManager` singleton service
|
||||
- Key files: `gps_data_manager.dart` (264 lines of GPS processing logic)
|
||||
|
||||
**`android/`:**
|
||||
- Purpose: Android-specific configuration and permissions
|
||||
- Contains: Manifest with GPS, overlay, and location permissions; Kotlin entry point
|
||||
- Key files: `AndroidManifest.xml` defines FINE_LOCATION, COARSE_LOCATION, SYSTEM_ALERT_WINDOW, READ_PHONE_STATE
|
||||
|
||||
**`fonts/`:**
|
||||
- Purpose: Custom font assets
|
||||
- Contains: DIN 1451 Alt (single font file)
|
||||
- Key files: `din1451alt.ttf` referenced in pubspec.yaml
|
||||
|
||||
**`icon/`:**
|
||||
- Purpose: Launcher icon for Android and iOS
|
||||
- Contains: Single PNG image (speedo.png)
|
||||
- Key files: `speedo.png` (adaptive icon source)
|
||||
|
||||
## Key File Locations
|
||||
|
||||
**Entry Points:**
|
||||
|
||||
- `lib/main.dart` (lines 22-24): `void main()` - Standard Flutter app entry, runs `SpeedoApp()`
|
||||
- `lib/main.dart` (lines 27-34): `void overlayMain()` - Overlay process entry marked with `@pragma("vm:entry-point")`, runs `OverlaySpeedometer`
|
||||
- `android/app/src/main/AndroidManifest.xml`: Android app manifest with permissions and overlay metadata
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- `pubspec.yaml`: Flutter project manifest with dependency versions (geolocator 14.0.2, flutter_overlay_window 0.5.0, etc.)
|
||||
- `analysis_options.yaml`: Dart linter rules
|
||||
- `android/app/src/main/AndroidManifest.xml`: GPS and system permissions
|
||||
|
||||
**Core Logic:**
|
||||
|
||||
- `lib/services/gps_data_manager.dart`: GPS data singleton, stream processing, stale data timeout (4 seconds)
|
||||
- `lib/gps_service.dart`: GPS permission requests, position stream factory, heading formatting
|
||||
- `lib/main.dart` (lines 56-837): `SpeedometerScreen` - Main app UI, GPS subscription, overlay lifecycle
|
||||
- `lib/main.dart` (lines 839-1075): `OverlaySpeedometer` - Floating window UI, data listener
|
||||
|
||||
**UI & Data Models:**
|
||||
|
||||
- `lib/main.dart` (lines 499-548): `_buildSpeedDisplay()` - Speed display renderer with split decimal parts
|
||||
- `lib/main.dart` (lines 574-703): `_buildPortraitLayout()` - Portrait screen layout with speed/heading sections
|
||||
- `lib/main.dart` (lines 705-836): `_buildLandscapeLayout()` - Landscape screen layout with side-by-side sections
|
||||
- `lib/color_themes.dart` (lines 21-63): `ColorThemes` static list with 4 theme variants
|
||||
- `lib/speed_units.dart` (lines 1-20): `SpeedUnit` enum with km/h, mph, knots conversion factors
|
||||
- `lib/services/gps_data_manager.dart` (lines 6-40): `ProcessedGpsData` immutable model class
|
||||
|
||||
**Testing:**
|
||||
|
||||
- `test/widget_test.dart`: Widget test skeleton (minimal)
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
|
||||
- Snake_case: `gps_service.dart`, `gps_data_manager.dart`, `speed_units.dart`, `color_themes.dart`, `main.dart`
|
||||
- Exception: `main.dart` contains all entry points and main widgets
|
||||
|
||||
**Directories:**
|
||||
|
||||
- Lowercase: `lib/`, `services/`, `android/`, `fonts/`, `icon/`, `test/`
|
||||
- Java-style package paths: `android/app/src/main/kotlin/com/novoyuuparosk/speedo/`
|
||||
|
||||
**Classes:**
|
||||
|
||||
- PascalCase: `SpeedometerScreen`, `GpsDataManager`, `ProcessedGpsData`, `ColorTheme`, `ColorThemes`, `SpeedUnit`, `GpsService`, `OverlaySpeedometer`
|
||||
|
||||
**Functions/Methods:**
|
||||
|
||||
- camelCase: `initState()`, `dispose()`, `_showFloatingWindow()`, `_onGpsDataUpdate()`, `_pushDataToOverlay()`, `_cycleTheme()`, `_cycleUnit()`
|
||||
- Private prefix: Methods starting with `_` (e.g., `_initializeGpsManager()`, `_buildPortraitLayout()`)
|
||||
|
||||
**Variables:**
|
||||
|
||||
- camelCase for local/member: `_currentGpsData`, `_currentUnit`, `_currentThemeIndex`, `_isOverlayActive`, `speedText`, `displaySpeed`
|
||||
- Constant ALL_CAPS: `_staleDataThreshold` (const Duration), `const directions` array
|
||||
|
||||
**Enums:**
|
||||
|
||||
- PascalCase: `SpeedUnit`, with lowercase members: `SpeedUnit.kmh`, `SpeedUnit.mph`, `SpeedUnit.knots`
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New Feature (e.g., Speed History Tracking):**
|
||||
|
||||
- Primary code: Create new file in `lib/services/speed_history_manager.dart` for business logic
|
||||
- Singleton pattern: Follow `GpsDataManager` structure with stream broadcasting
|
||||
- UI integration: Add stream subscription in `_SpeedometerScreenState.initState()`
|
||||
- Display: Add widget in `_buildPortraitLayout()` or create separate widget method
|
||||
- Tests: Add to `test/widget_test.dart`
|
||||
|
||||
**New UI Component/Widget:**
|
||||
|
||||
- Implementation: Add widget class directly in `lib/main.dart` if small (<100 lines), or create separate file
|
||||
- Naming: Follow PascalCase class convention, e.g., `SpeedHistoryPanel`, `MetricsDisplay`
|
||||
- Props: Pass required data as constructor parameters, avoid global state
|
||||
- Tests: Add widget tests in `test/widget_test.dart`
|
||||
|
||||
**New Configuration/Enum:**
|
||||
|
||||
- Speed units variant: Add new enum member to `SpeedUnit` in `lib/speed_units.dart`, add multiplier
|
||||
- Color theme: Add new `ColorTheme` instance to `ColorThemes.themes` list in `lib/color_themes.dart`
|
||||
- GPS behavior constants: Add to `GpsDataManager` static fields (e.g., timeout durations)
|
||||
|
||||
**Utilities/Helpers:**
|
||||
|
||||
- Shared helpers: Create `lib/utils/` directory if adding 5+ utility functions
|
||||
- Single-use helpers: Keep in same file as consumer (e.g., `customDebugPrint()` in `main.dart`)
|
||||
- Formatting helpers: Add to relevant domain file (`GpsService` for GPS formatting, `SpeedUnit` for speed conversion)
|
||||
|
||||
**New Service:**
|
||||
|
||||
- Location: `lib/services/new_feature_manager.dart`
|
||||
- Pattern: Singleton with internal state, public stream for subscribers, async initialize method
|
||||
- Lifecycle: Call `initialize()` from `SpeedometerScreen.initState()`, add stream listener
|
||||
- Cleanup: Call `dispose()` in widget `dispose()` method
|
||||
|
||||
## Special Directories
|
||||
|
||||
**`lib/services/`:**
|
||||
- Purpose: Business logic services (no UI widgets)
|
||||
- Generated: No
|
||||
- Committed: Yes
|
||||
- Content: `GpsDataManager` (only service currently, designed for expansion)
|
||||
|
||||
**`android/`:**
|
||||
- Purpose: Android native configuration and permissions
|
||||
- Generated: No (manually configured, but gradle build outputs go to `build/`)
|
||||
- Committed: Yes
|
||||
- Key file: `AndroidManifest.xml` defines permissions needed for GPS and overlay functionality
|
||||
|
||||
**`build/`:**
|
||||
- Purpose: Flutter/Android build artifacts
|
||||
- Generated: Yes (auto-generated by `flutter build`)
|
||||
- Committed: No (in .gitignore)
|
||||
|
||||
**`.dart_tool/`:**
|
||||
- Purpose: Dart analyzer and build cache
|
||||
- Generated: Yes (auto-generated by Dart tooling)
|
||||
- Committed: No (in .gitignore)
|
||||
|
||||
**`test/`:**
|
||||
- Purpose: Automated tests
|
||||
- Generated: No (manually written)
|
||||
- Committed: Yes
|
||||
- Current state: Minimal scaffold only
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-02-09*
|
||||
@@ -0,0 +1,209 @@
|
||||
# Testing Patterns
|
||||
|
||||
**Analysis Date:** 2026-02-09
|
||||
|
||||
## Test Framework
|
||||
|
||||
**Runner:**
|
||||
- `flutter_test` (included with Flutter SDK)
|
||||
- Config: None detected (uses default Flutter test configuration)
|
||||
|
||||
**Assertion Library:**
|
||||
- Flutter's built-in matchers from `flutter_test` package
|
||||
- Uses `expect()` and `find.*()` for widget testing
|
||||
|
||||
**Run Commands:**
|
||||
```bash
|
||||
flutter test # Run all tests
|
||||
flutter test --watch # Watch mode (not explicitly configured but supported)
|
||||
flutter test --coverage # Coverage mode (not explicitly configured)
|
||||
flutter run test/widget_test.dart # Run specific test file
|
||||
```
|
||||
|
||||
## Test File Organization
|
||||
|
||||
**Location:**
|
||||
- Co-located in `test/` directory parallel to `lib/`
|
||||
- Single test file: `test/widget_test.dart`
|
||||
|
||||
**Naming:**
|
||||
- Pattern: `{purpose}_test.dart`
|
||||
- Example: `widget_test.dart`
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
test/
|
||||
└── widget_test.dart # Basic widget tests
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
**Suite Organization:**
|
||||
```dart
|
||||
void main() {
|
||||
testWidgets('GPS Speedometer app loads', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const SpeedoApp());
|
||||
|
||||
// Verify that the app loads with the title
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Patterns:**
|
||||
- Single `main()` function per test file containing one or more test cases
|
||||
- `testWidgets()` for widget tests (not `test()` for unit tests)
|
||||
- `async/await` for asynchronous operations
|
||||
- Setup: `tester.pumpWidget()` to build the widget tree
|
||||
- Teardown: None explicit - Flutter test framework cleans up automatically
|
||||
|
||||
## Mocking
|
||||
|
||||
**Framework:**
|
||||
- Not currently used in codebase
|
||||
- No mock packages found in `pubspec.yaml`
|
||||
- No mocking libraries imported in test files
|
||||
|
||||
**Patterns:**
|
||||
- No mocking patterns found in existing tests
|
||||
- Real widget testing approach taken: actual `SpeedoApp` widget instantiated
|
||||
|
||||
**What NOT to Mock (Current Approach):**
|
||||
- Flutter widgets and MaterialApp - built and tested directly
|
||||
- Widget state and lifecycle - managed by WidgetTester
|
||||
|
||||
## Fixtures and Factories
|
||||
|
||||
**Test Data:**
|
||||
- No fixtures defined
|
||||
- Hard-coded expectations: `expect(find.text('0'), findsOneWidget);`
|
||||
|
||||
**Location:**
|
||||
- N/A - not implemented in current codebase
|
||||
|
||||
## Coverage
|
||||
|
||||
**Requirements:**
|
||||
- Not enforced - no coverage configuration found in pubspec.yaml
|
||||
- No coverage thresholds set
|
||||
|
||||
**View Coverage:**
|
||||
```bash
|
||||
# Not explicitly documented, but standard Flutter command:
|
||||
flutter test --coverage
|
||||
# Generates lcov.info file with coverage data
|
||||
```
|
||||
|
||||
## Test Types
|
||||
|
||||
**Widget Tests:**
|
||||
- Primary test type in use
|
||||
- Scope: Full app widget tree integration
|
||||
- Location: `test/widget_test.dart`
|
||||
- Approach: Build `SpeedoApp` and verify basic functionality loads
|
||||
- Example from line 13-19:
|
||||
```dart
|
||||
testWidgets('GPS Speedometer app loads', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const SpeedoApp());
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
});
|
||||
```
|
||||
|
||||
**Unit Tests:**
|
||||
- Not currently implemented
|
||||
- Candidates for unit testing:
|
||||
- `SpeedUnit.convert()` method in `lib/speed_units.dart`
|
||||
- `GpsService` static methods: `getCompassDirection()`, `formatHeading()`
|
||||
- `ColorThemes.getTheme()`, `ColorThemes.getNextThemeIndex()`
|
||||
- `ProcessedGpsData.copyWith()` copy constructor
|
||||
|
||||
**Integration Tests:**
|
||||
- Not implemented
|
||||
- Candidates would include:
|
||||
- GPS initialization flow (`GpsDataManager.initialize()`)
|
||||
- Overlay window creation and communication
|
||||
- Theme/unit synchronization between main app and overlay
|
||||
|
||||
**E2E Tests:**
|
||||
- Not used - no Appium or similar automation framework detected
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Async Testing:**
|
||||
- Uses `async/await` pattern with `WidgetTester`
|
||||
- No explicit futures chaining or timeout handling in current tests
|
||||
- Could expand with patterns like:
|
||||
```dart
|
||||
// Not currently used but could be:
|
||||
await tester.pumpWidget(const SpeedoApp());
|
||||
await tester.pumpAndSettle(); // Wait for all animations
|
||||
expect(find.byType(SpeedometerScreen), findsOneWidget);
|
||||
```
|
||||
|
||||
**Error Testing:**
|
||||
- Not currently implemented
|
||||
- No error path testing for:
|
||||
- GPS permission denial handling
|
||||
- GPS service disabled scenarios
|
||||
- GPS stream errors and recovery
|
||||
|
||||
**Gesture Testing:**
|
||||
- Not currently tested, but WidgetTester supports:
|
||||
```dart
|
||||
// Example pattern (not in current tests):
|
||||
await tester.tap(find.byType(GestureDetector));
|
||||
await tester.pumpAndSettle();
|
||||
// Verify tap resulted in theme/unit change
|
||||
```
|
||||
|
||||
## Test Gaps
|
||||
|
||||
**Untested Components:**
|
||||
- `_SpeedometerScreenState` - Core app state management (lifecycle, theme cycling, unit cycling)
|
||||
- `_OverlaySpeedometerState` - Overlay widget state and message handling
|
||||
- `GpsDataManager` - GPS data processing, stale data handling, stream broadcasting
|
||||
- `GpsService` - Permission handling, location stream creation, heading formatting
|
||||
- All error paths - GPS errors, permission denial, service disabled
|
||||
- Gesture interactions - tap, long-press, pan gestures on both main and overlay
|
||||
- Overlay messaging - data synchronization between main app and overlay window
|
||||
- Theme switching - `_cycleTheme()` and related state updates
|
||||
- Unit conversion - `_cycleUnit()` and display formatting
|
||||
|
||||
**Coverage Assessment:**
|
||||
- Current test coverage: Minimal - single smoke test verifying app loads
|
||||
- Estimated actual coverage: <5% of codebase
|
||||
- High-risk untested areas:
|
||||
- GPS data processing and validation (critical for core functionality)
|
||||
- Overlay communication (complex async messaging)
|
||||
- State synchronization between main and overlay (prone to race conditions)
|
||||
|
||||
## Suggested Testing Roadmap
|
||||
|
||||
**Priority 1 - Core Functionality:**
|
||||
1. Unit tests for `SpeedUnit.convert()` with various speed values
|
||||
2. Unit tests for `GpsService.formatHeading()` with edge cases (0°, 360°, invalid values)
|
||||
3. Unit tests for `ProcessedGpsData.copyWith()` partial updates
|
||||
4. Integration tests for `GpsDataManager` initialization sequence
|
||||
|
||||
**Priority 2 - Error Handling:**
|
||||
1. GPS permission denial scenarios
|
||||
2. GPS service disabled scenarios
|
||||
3. GPS stream errors and timeout handling
|
||||
4. Stale data detection and state updates
|
||||
|
||||
**Priority 3 - UI/Interaction:**
|
||||
1. Widget tests for theme cycling with `_cycleTheme()`
|
||||
2. Widget tests for unit cycling with `_cycleUnit()`
|
||||
3. Gesture tests for tap/long-press on navigation area
|
||||
4. Overlay messaging and data synchronization tests
|
||||
|
||||
**Priority 4 - Edge Cases:**
|
||||
1. App lifecycle transitions (background/foreground)
|
||||
2. Overlay creation, display, and closure
|
||||
3. Concurrent operations (GPS updates + theme changes)
|
||||
4. Memory cleanup on dispose
|
||||
|
||||
---
|
||||
|
||||
*Testing analysis: 2026-02-09*
|
||||
Reference in New Issue
Block a user