refactor(01-01): rewire all imports, replace logging, apply constants

- Update gps_data_manager.dart: import ProcessedGpsData from models/, use GpsConfig constants, replace all print() with Logger calls
- Update gps_service.dart: use GpsConfig.VALID_HEADING_MIN/MAX for heading validation
- Update main.dart: import from config/, models/, replace all customDebugPrint() and print() with Logger, use TimingConfig/OverlayConfig constants, convert overlay IPC to OverlayMessage typed class
- Delete customDebugPrint() function definition
- Delete lib/speed_units.dart and lib/color_themes.dart (moved to config/)
- Zero analyzer errors, zero bare print() calls remaining
This commit is contained in:
Mikkeli Matlock
2026-02-09 21:33:28 +09:00
parent 5122be4bc1
commit a0d93d7f6d
5 changed files with 209 additions and 332 deletions
-64
View File
@@ -1,64 +0,0 @@
import 'package:flutter/material.dart';
class ColorTheme {
final Color background;
final Color speedText;
final Color speedTextSub;
final Color headingText;
final Color unitText;
final String name;
const ColorTheme({
required this.background,
required this.speedText,
required this.speedTextSub,
required this.headingText,
required this.unitText,
required this.name,
});
}
class ColorThemes {
static const List<ColorTheme> themes = [
ColorTheme(
name: 'Yuzuki',
background: Color.fromARGB(255, 114, 85, 109),
speedText: Color.fromARGB(255, 246, 224, 255),
speedTextSub: Color.fromARGB(255, 245, 217, 245),
headingText: Color.fromARGB(255, 244, 201, 255),
unitText: Color.fromARGB(255, 244, 201, 255),
),
ColorTheme(
name: 'Yuuparo',
background: Color.fromARGB(255, 85, 99, 112),
speedText: Color.fromARGB(255, 255, 171, 16),
speedTextSub: Color.fromARGB(255, 245, 204, 116),
headingText: Color.fromARGB(255, 228, 235, 238),
unitText: Color.fromARGB(255, 141, 196, 184),
),
ColorTheme(
name: 'Yuzuki Light',
background: Color.fromARGB(255, 246, 224, 255),
speedText: Color.fromARGB(255, 114, 85, 109),
speedTextSub: Color.fromARGB(255, 161, 138, 170),
headingText: Color.fromARGB(255, 140, 94, 151),
unitText: Color.fromARGB(255, 114, 85, 109),
),
ColorTheme(
name: 'Yuuparo Light',
background: Color.fromARGB(255, 228, 235, 238),
speedText: Color.fromARGB(255, 85, 99, 112),
speedTextSub: Color.fromARGB(255, 100, 100, 131),
headingText: Color.fromARGB(255, 221, 118, 0),
unitText: Color.fromARGB(255, 98, 124, 118),
),
];
static ColorTheme getTheme(int index) {
return themes[index % themes.length];
}
static int getNextThemeIndex(int currentIndex) {
return (currentIndex + 1) % themes.length;
}
}
+6 -5
View File
@@ -1,10 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'config/gps_constants.dart';
class GpsService { class GpsService {
static double? _lastValidHeading; static double? _lastValidHeading;
static Stream<Position> get positionStream { static Stream<Position> get positionStream {
return Geolocator.getPositionStream( return Geolocator.getPositionStream(
locationSettings: const LocationSettings( locationSettings: const LocationSettings(
@@ -13,18 +14,18 @@ class GpsService {
), ),
); );
} }
static Future<bool> requestPermissions() async { static Future<bool> requestPermissions() async {
final permission = await Permission.locationWhenInUse.request(); final permission = await Permission.locationWhenInUse.request();
return permission.isGranted; return permission.isGranted;
} }
static Future<bool> isLocationServiceEnabled() async { static Future<bool> isLocationServiceEnabled() async {
return await Geolocator.isLocationServiceEnabled(); return await Geolocator.isLocationServiceEnabled();
} }
static double getCachedHeading(double heading) { static double getCachedHeading(double heading) {
if (heading >= 0 && heading <= 360) { if (heading >= GpsConfig.VALID_HEADING_MIN && heading <= GpsConfig.VALID_HEADING_MAX) {
_lastValidHeading = heading; _lastValidHeading = heading;
return heading; return heading;
} }
+156 -162
View File
@@ -5,20 +5,16 @@ import 'dart:async';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:bg_launcher/bg_launcher.dart'; // For bringing app to foreground import 'package:bg_launcher/bg_launcher.dart'; // For bringing app to foreground
import 'speed_units.dart'; import 'config/speed_unit.dart';
import 'color_themes.dart'; import 'config/color_themes.dart';
import 'config/timing_constants.dart';
import 'config/overlay_constants.dart';
import 'models/overlay_message.dart';
import 'models/processed_gps_data.dart';
import 'services/gps_data_manager.dart'; import 'services/gps_data_manager.dart';
import 'services/logger.dart';
import 'dart:math' as math; // Import for math.pi import 'dart:math' as math; // Import for math.pi
// Global debug configuration (removed - now handled by GPS manager)
// Debug helper - only prints in debug builds
void customDebugPrint(String message) {
if (kDebugMode) {
print(message);
}
}
void main() { void main() {
runApp(const SpeedoApp()); runApp(const SpeedoApp());
} }
@@ -26,7 +22,7 @@ void main() {
// Entry point for overlay window // Entry point for overlay window
@pragma("vm:entry-point") @pragma("vm:entry-point")
void overlayMain() { void overlayMain() {
customDebugPrint('[Overlay] 🚀 overlayMain() called'); Logger.info('overlayMain() called', 'Overlay');
runApp(const MaterialApp( runApp(const MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: OverlaySpeedometer(), home: OverlaySpeedometer(),
@@ -85,11 +81,11 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
Map<String, int> _getOverlaySize() { Map<String, int> _getOverlaySize() {
final window = WidgetsBinding.instance.platformDispatcher.views.first; final window = WidgetsBinding.instance.platformDispatcher.views.first;
final physicalSize = window.physicalSize; final physicalSize = window.physicalSize;
// Use actual physical pixels for overlay sizing // Use actual physical pixels for overlay sizing
final overlayWidth = (physicalSize.width * 0.45).round(); // 45% of actual screen width final overlayWidth = (physicalSize.width * OverlayConfig.WIDTH_PERCENTAGE).round();
final overlayHeight = (overlayWidth * 0.6).round(); final overlayHeight = (overlayWidth * OverlayConfig.ASPECT_RATIO).round();
return {'width': overlayWidth, 'height': overlayHeight}; return {'width': overlayWidth, 'height': overlayHeight};
} }
@@ -125,22 +121,22 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
_tapCloseRequested = true; // Reusing the flag for long press _tapCloseRequested = true; // Reusing the flag for long press
// Set a timer to reset this flag if overlay doesn't close soon // Set a timer to reset this flag if overlay doesn't close soon
_tapCloseTimer?.cancel(); _tapCloseTimer?.cancel();
_tapCloseTimer = Timer(const Duration(milliseconds: 500), () { _tapCloseTimer = Timer(TimingConfig.TAP_CLOSE_DELAY, () {
_tapCloseRequested = false; _tapCloseRequested = false;
}); });
break; break;
// 'bringToFront' action handler removed per NOTES.txt (tap to bring front functionality disabled) // 'bringToFront' action handler removed per NOTES.txt (tap to bring front functionality disabled)
default: default:
customDebugPrint('[Main] ⚠️ Unknown overlay action: "$action"'); Logger.warn('Unknown overlay action: "$action"', 'Main');
} }
} }
} catch (e, stackTrace) { } catch (e, stackTrace) {
customDebugPrint('[Main] ❌ Error processing overlay message: $e'); Logger.error('Error processing overlay message: $e', 'Main');
customDebugPrint('[Main] 📚 Stack trace: $stackTrace'); Logger.error('Stack trace: $stackTrace', 'Main');
} }
}, },
onError: (error) { onError: (error) {
customDebugPrint('[Main] ❌ Overlay listener error: $error'); Logger.error('Overlay listener error: $error', 'Main');
}, },
); );
} }
@@ -154,7 +150,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
// Cancel any existing timer first // Cancel any existing timer first
_overlayStatusCheckTimer?.cancel(); _overlayStatusCheckTimer?.cancel();
_overlayStatusCheckTimer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async { _overlayStatusCheckTimer = Timer.periodic(TimingConfig.OVERLAY_STATUS_CHECK_INTERVAL, (timer) async {
if (_isOverlayActive) { if (_isOverlayActive) {
try { try {
final isActive = await FlutterOverlayWindow.isActive(); final isActive = await FlutterOverlayWindow.isActive();
@@ -163,14 +159,14 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
_handleOverlayClose(); _handleOverlayClose();
} }
} catch (e) { } catch (e) {
customDebugPrint('[Main] ❌ Overlay status check failed: $e'); Logger.error('Overlay status check failed: $e', 'Main');
} }
} else { } else {
// If overlay is not supposed to be active, cancel the timer // If overlay is not supposed to be active, cancel the timer
timer.cancel(); timer.cancel();
} }
}); });
customDebugPrint('[Main] 🔍 Started overlay status monitoring'); Logger.debug('Started overlay status monitoring', 'Main');
} }
void _handleOverlayClose({bool bringToForeground = false}) { void _handleOverlayClose({bool bringToForeground = false}) {
@@ -196,7 +192,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
void _startBackgroundHeartbeat() { void _startBackgroundHeartbeat() {
// Aggressive heartbeat to keep main app process active for overlay communication // Aggressive heartbeat to keep main app process active for overlay communication
_backgroundHeartbeatTimer = Timer.periodic(const Duration(seconds: 5), (timer) { _backgroundHeartbeatTimer = Timer.periodic(TimingConfig.HEARTBEAT_INTERVAL, (timer) {
if (_isInBackground && _isOverlayActive) { if (_isInBackground && _isOverlayActive) {
// Minimal activity to prevent hibernation // Minimal activity to prevent hibernation
if (mounted) { if (mounted) {
@@ -231,8 +227,8 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
}); });
} }
} catch (e) { } catch (e) {
customDebugPrint('[Main] ❌ BgLauncher failed to bring app to front: $e'); Logger.error('BgLauncher failed to bring app to front: $e', 'Main');
// Enhanced fallback: try alternative approach // Enhanced fallback: try alternative approach
try { try {
if (mounted) { if (mounted) {
@@ -241,7 +237,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
}); });
} }
} catch (fallbackError) { } catch (fallbackError) {
customDebugPrint('[Main] ❌ Fallback method also failed: $fallbackError'); Logger.error('Fallback method also failed: $fallbackError', 'Main');
} }
} }
} }
@@ -254,11 +250,11 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
// Double-check wake lock status for debugging // Double-check wake lock status for debugging
if (!isEnabled) { if (!isEnabled) {
customDebugPrint('[Main] ⚠️ Wake lock not properly enabled, retrying...'); Logger.warn('Wake lock not properly enabled, retrying...', 'Main');
await WakelockPlus.enable(); await WakelockPlus.enable();
} }
} catch (e) { } catch (e) {
customDebugPrint('[Main] ❌ Wake lock failed: $e'); Logger.error('Wake lock failed: $e', 'Main');
// Wake lock not supported on this platform, continue normally // Wake lock not supported on this platform, continue normally
} }
} }
@@ -272,7 +268,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
_overlayStatusCheckTimer?.cancel(); _overlayStatusCheckTimer?.cancel();
_tapCloseTimer?.cancel(); _tapCloseTimer?.cancel();
WakelockPlus.disable(); WakelockPlus.disable();
customDebugPrint('[Main] 🛑 Main app disposed - all subscriptions and timers canceled'); Logger.info('Main app disposed - all subscriptions and timers canceled', 'Main');
super.dispose(); super.dispose();
} }
@@ -316,66 +312,66 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
// Note: This requires platform-specific implementation which we'd need to add via method channel // Note: This requires platform-specific implementation which we'd need to add via method channel
// For now, we'll rely on other persistence mechanisms // For now, we'll rely on other persistence mechanisms
} catch (e) { } catch (e) {
customDebugPrint('[Main] ⚠️ Battery optimization exemption not available: $e'); Logger.warn('Battery optimization exemption not available: $e', 'Main');
} }
} }
Future<void> _ensureOverlayPermission() async { Future<void> _ensureOverlayPermission() async {
try { try {
// Check if overlay permission is granted (required for bg_launcher to work properly) // Check if overlay permission is granted (required for bg_launcher to work properly)
final isGranted = await FlutterOverlayWindow.isPermissionGranted(); final isGranted = await FlutterOverlayWindow.isPermissionGranted();
if (!isGranted) { if (!isGranted) {
await FlutterOverlayWindow.requestPermission(); await FlutterOverlayWindow.requestPermission();
} }
} catch (e) { } catch (e) {
customDebugPrint('[Main] ❌ Error checking overlay permission: $e'); Logger.error('Error checking overlay permission: $e', 'Main');
} }
} }
Future<void> _initializeGpsManager() async { Future<void> _initializeGpsManager() async {
print('[Main] 🚀 Starting GPS manager initialization...'); Logger.info('Starting GPS manager initialization...', 'Main');
try { try {
// Initialize the GPS manager // Initialize the GPS manager
print('[Main] 📡 Calling GpsDataManager.instance.initialize()...'); Logger.debug('Calling GpsDataManager.instance.initialize()...', 'Main');
await GpsDataManager.instance.initialize(); await GpsDataManager.instance.initialize();
print('[Main] ✅ GPS manager initialization completed'); Logger.info('GPS manager initialization completed', 'Main');
// Subscribe to processed GPS data // Subscribe to processed GPS data
print('[Main] 🎧 Setting up data stream subscription...'); Logger.debug('Setting up data stream subscription...', 'Main');
_gpsDataSubscription = GpsDataManager.instance.dataStream.listen( _gpsDataSubscription = GpsDataManager.instance.dataStream.listen(
_onGpsDataUpdate, _onGpsDataUpdate,
onError: (error) { onError: (error) {
print('[Main] ❌ GPS data stream error: $error'); Logger.error('GPS data stream error: $error', 'Main');
setState(() => _errorMessage = 'GPS manager error: $error'); setState(() => _errorMessage = 'GPS manager error: $error');
}, },
); );
print('[Main] ✅ GPS data stream subscription active'); Logger.info('GPS data stream subscription active', 'Main');
customDebugPrint('[Main] 🛰️ GPS manager initialized and subscribed'); Logger.info('GPS manager initialized and subscribed', 'Main');
} catch (e) { } catch (e) {
print('[Main] ❌ GPS manager initialization exception: $e'); Logger.error('GPS manager initialization exception: $e', 'Main');
setState(() => _errorMessage = 'Failed to initialize GPS manager'); setState(() => _errorMessage = 'Failed to initialize GPS manager');
customDebugPrint('[Main] ❌ GPS manager initialization failed: $e'); Logger.error('GPS manager initialization failed: $e', 'Main');
} }
} }
void _onGpsDataUpdate(ProcessedGpsData gpsData) { void _onGpsDataUpdate(ProcessedGpsData gpsData) {
print('[Main] 📥 Received GPS data: ${gpsData.speed.toStringAsFixed(1)} m/s, ${gpsData.displayHeading}'); Logger.debug('Received GPS data: ${gpsData.speed.toStringAsFixed(1)} m/s, ${gpsData.displayHeading}', 'Main');
setState(() { setState(() {
_currentGpsData = gpsData; _currentGpsData = gpsData;
_errorMessage = ''; _errorMessage = '';
}); });
print('[Main] ✅ State updated with GPS data'); Logger.debug('State updated with GPS data', 'Main');
final convertedSpeed = _currentUnit.convert(gpsData.speed); final convertedSpeed = _currentUnit.convert(gpsData.speed);
customDebugPrint('[Main] 📡 Converted for display: ${convertedSpeed.toStringAsFixed(1)} ${_currentUnit.label}, ${gpsData.displayHeading}'); Logger.debug('Converted for display: ${convertedSpeed.toStringAsFixed(1)} ${_currentUnit.label}, ${gpsData.displayHeading}', 'Main');
// Always push display data to overlay if active - critical for background communication // Always push display data to overlay if active - critical for background communication
if (_isOverlayActive) { if (_isOverlayActive) {
_pushDataToOverlay(); _pushDataToOverlay();
customDebugPrint('[Main] 📤 Data pushed to overlay from GPS update'); Logger.debug('Data pushed to overlay from GPS update', 'Main');
} }
} }
@@ -383,32 +379,31 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
void _pushDataToOverlay() { void _pushDataToOverlay() {
// Only push data if overlay is actually active (listener check was causing race conditions) // Only push data if overlay is actually active (listener check was causing race conditions)
if (!_isOverlayActive) { if (!_isOverlayActive) {
customDebugPrint('[Main] 🚫 Skipping data push - no active overlay (active: $_isOverlayActive, subscription: ${_overlaySubscription != null})'); Logger.debug('Skipping data push - no active overlay (active: $_isOverlayActive, subscription: ${_overlaySubscription != null})', 'Main');
return; return;
} }
customDebugPrint('[Main] ✅ Overlay guard passed - pushing data (active: $_isOverlayActive, subscription: ${_overlaySubscription != null})'); Logger.debug('Overlay guard passed - pushing data (active: $_isOverlayActive, subscription: ${_overlaySubscription != null})', 'Main');
// Get formatted speed for current unit from GPS manager // Get formatted speed for current unit from GPS manager
final speedText = GpsDataManager.instance.getFormattedSpeed(_currentUnit); final speedText = GpsDataManager.instance.getFormattedSpeed(_currentUnit);
final unitText = _currentUnit.label.toString(); final unitText = _currentUnit.label.toString();
final headingText = _currentGpsData.displayHeading; final headingText = _currentGpsData.displayHeading;
customDebugPrint('[Main] 📤 SENDING to overlay:'); Logger.debug('SENDING to overlay:', 'Main');
customDebugPrint(' speedText: "$speedText"'); Logger.debug(' speedText: "$speedText"', 'Main');
customDebugPrint(' headingText: "$headingText"'); Logger.debug(' headingText: "$headingText"', 'Main');
customDebugPrint(' direction: ${_currentGpsData.heading.toStringAsFixed(1)}°'); Logger.debug(' direction: ${_currentGpsData.heading.toStringAsFixed(1)}°', 'Main');
customDebugPrint(' unit: $unitText, theme: $_currentThemeIndex'); Logger.debug(' unit: $unitText, theme: $_currentThemeIndex', 'Main');
FlutterOverlayWindow.shareData({ FlutterOverlayWindow.shareData(OverlayMessage.updateDisplay(
'action': 'updateDisplay', speedText: speedText,
'speedText': speedText, unitText: unitText,
'unitText': unitText, headingText: headingText,
'headingText': headingText, heading: _currentGpsData.heading,
'heading': _currentGpsData.heading, unitIndex: _currentUnit.index,
'unitIndex': _currentUnit.index, themeIndex: _currentThemeIndex,
'themeIndex': _currentThemeIndex, ).toMap());
});
} }
void _cycleUnit() { void _cycleUnit() {
@@ -437,17 +432,18 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
// Send initial display data WITH size info (only needed on creation) // Send initial display data WITH size info (only needed on creation)
final speedText = GpsDataManager.instance.getFormattedSpeed(_currentUnit); final speedText = GpsDataManager.instance.getFormattedSpeed(_currentUnit);
final headingText = _currentGpsData.displayHeading; final headingText = _currentGpsData.displayHeading;
final unitText = _currentUnit.label.toString();
await FlutterOverlayWindow.shareData({
'action': 'updateDisplay', await FlutterOverlayWindow.shareData(OverlayMessage.updateDisplay(
'speedText': speedText, speedText: speedText,
'headingText': headingText, unitText: unitText,
'heading': _currentGpsData.heading, headingText: headingText,
'unitIndex': _currentUnit.index, heading: _currentGpsData.heading,
'themeIndex': _currentThemeIndex, unitIndex: _currentUnit.index,
'overlayWidth': overlaySize['width']!.toDouble(), themeIndex: _currentThemeIndex,
'overlayHeight': overlaySize['height']!.toDouble(), overlayWidth: overlaySize['width']!.toDouble(),
}); overlayHeight: overlaySize['height']!.toDouble(),
).toMap());
// Show the overlay with proportional sizing based on system resolution // Show the overlay with proportional sizing based on system resolution
await FlutterOverlayWindow.showOverlay( await FlutterOverlayWindow.showOverlay(
@@ -462,11 +458,11 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
height: overlaySize['height']!, height: overlaySize['height']!,
); );
customDebugPrint('[Main] 🟢 OVERLAY LAUNCHED - Size: ${overlaySize['width']}x${overlaySize['height']}'); Logger.info('OVERLAY LAUNCHED - Size: ${overlaySize['width']}x${overlaySize['height']}', 'Main');
// Set overlay active immediately and synchronously // Set overlay active immediately and synchronously
_isOverlayActive = true; _isOverlayActive = true;
customDebugPrint('[Main] ✅ _isOverlayActive set to: $_isOverlayActive'); Logger.debug('_isOverlayActive set to: $_isOverlayActive', 'Main');
// Start listening to overlay messages after state is set // Start listening to overlay messages after state is set
_startListeningToOverlayMessages(); _startListeningToOverlayMessages();
@@ -483,16 +479,16 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
Future<void> _closeFloatingWindow() async { Future<void> _closeFloatingWindow() async {
if (!_isOverlayActive) { if (!_isOverlayActive) {
customDebugPrint('[Main] ⚠️ No overlay to close'); Logger.warn('No overlay to close', 'Main');
return; return;
} }
try { try {
await FlutterOverlayWindow.closeOverlay(); await FlutterOverlayWindow.closeOverlay();
customDebugPrint('[Main] 🔴 OVERLAY CLOSED - Requested from main app'); Logger.info('OVERLAY CLOSED - Requested from main app', 'Main');
_handleOverlayClose(); // This will set _isOverlayActive = false and stop listener _handleOverlayClose(); // This will set _isOverlayActive = false and stop listener
} catch (e) { } catch (e) {
customDebugPrint('[Main] ❌ Error closing overlay: $e'); Logger.error('Error closing overlay: $e', 'Main');
} }
} }
@@ -647,7 +643,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
customDebugPrint('[Main] 🖱️ Navigation area tapped - overlay active: $_isOverlayActive'); Logger.debug('Navigation area tapped - overlay active: $_isOverlayActive', 'Main');
if (_isOverlayActive) { if (_isOverlayActive) {
_closeFloatingWindow(); _closeFloatingWindow();
} else { } else {
@@ -655,7 +651,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
} }
}, },
onLongPress: () { onLongPress: () {
customDebugPrint('[Main] 🖱️ Navigation area long-pressed - overlay active: $_isOverlayActive'); Logger.debug('Navigation area long-pressed - overlay active: $_isOverlayActive', 'Main');
_closeFloatingWindow(); _closeFloatingWindow();
}, },
child: Container( child: Container(
@@ -774,7 +770,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
customDebugPrint('[Main] 🖱️ Navigation area tapped (landscape) - overlay active: $_isOverlayActive'); Logger.debug('Navigation area tapped (landscape) - overlay active: $_isOverlayActive', 'Main');
if (_isOverlayActive) { if (_isOverlayActive) {
_closeFloatingWindow(); _closeFloatingWindow();
} else { } else {
@@ -782,7 +778,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
} }
}, },
onLongPress: () { onLongPress: () {
customDebugPrint('[Main] 🖱️ Navigation area long-pressed (landscape) - overlay active: $_isOverlayActive'); Logger.debug('Navigation area long-pressed (landscape) - overlay active: $_isOverlayActive', 'Main');
_closeFloatingWindow(); _closeFloatingWindow();
}, },
child: Container( child: Container(
@@ -856,14 +852,14 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
customDebugPrint('[Overlay] 🟢 CREATED - Floating window initialized (${DateTime.now().millisecondsSinceEpoch})'); Logger.info('CREATED - Floating window initialized (${DateTime.now().millisecondsSinceEpoch})', 'Overlay');
_getSystemScreenSize(); _getSystemScreenSize();
_listenToMainAppMessages(); _listenToMainAppMessages();
} }
@override @override
void dispose() { void dispose() {
customDebugPrint('[Overlay] 🔴 DISPOSING - Overlay widget disposed (${DateTime.now().millisecondsSinceEpoch})'); Logger.info('DISPOSING - Overlay widget disposed (${DateTime.now().millisecondsSinceEpoch})', 'Overlay');
super.dispose(); super.dispose();
} }
@@ -875,53 +871,53 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
final devicePixelRatio = window.devicePixelRatio; final devicePixelRatio = window.devicePixelRatio;
final systemSize = physicalSize / devicePixelRatio; final systemSize = physicalSize / devicePixelRatio;
customDebugPrint('[Overlay] 🔍 Screen detection:'); Logger.debug('Screen detection:', 'Overlay');
customDebugPrint(' physicalSize: ${physicalSize.width.round()}x${physicalSize.height.round()}'); Logger.debug(' physicalSize: ${physicalSize.width.round()}x${physicalSize.height.round()}', 'Overlay');
customDebugPrint(' devicePixelRatio: $devicePixelRatio'); Logger.debug(' devicePixelRatio: $devicePixelRatio', 'Overlay');
customDebugPrint(' calculated systemSize: ${systemSize.width.round()}x${systemSize.height.round()}'); Logger.debug(' calculated systemSize: ${systemSize.width.round()}x${systemSize.height.round()}', 'Overlay');
setState(() { setState(() {
_systemScreenWidth = systemSize.width; _systemScreenWidth = systemSize.width;
}); });
customDebugPrint('[Overlay] ✅ _systemScreenWidth set to: ${_systemScreenWidth.round()}'); Logger.debug('_systemScreenWidth set to: ${_systemScreenWidth.round()}', 'Overlay');
} catch (e) { } catch (e) {
customDebugPrint('[Overlay] ❌ Error getting system screen size: $e'); Logger.error('Error getting system screen size: $e', 'Overlay');
// Fallback to reasonable default // Fallback to reasonable default
setState(() { setState(() {
_systemScreenWidth = 400; _systemScreenWidth = OverlayConfig.DEFAULT_SCREEN_WIDTH;
}); });
} }
} }
void _listenToMainAppMessages() { void _listenToMainAppMessages() {
FlutterOverlayWindow.overlayListener.listen((data) { FlutterOverlayWindow.overlayListener.listen((data) {
if (data is Map && data['action'] == 'updateDisplay') { if (data is Map) {
setState(() { final message = OverlayMessage.fromMap(data);
_speedText = data['speedText'] ?? '--'; if (message.action == 'updateDisplay') {
_unitText = data['unitText']; setState(() {
_headingText = data['headingText'] ?? 'N/A'; _speedText = message.speedText ?? '--';
_heading = data['heading']?.toDouble() ?? -1.0; _unitText = message.unitText ?? '';
_currentThemeIndex = data['themeIndex'] ?? 0; _headingText = message.headingText ?? 'N/A';
// Update screen size and overlay dimensions if provided by main app _heading = message.heading ?? -1.0;
if (data.containsKey('systemScreenWidth')) { _currentThemeIndex = message.themeIndex ?? 0;
_systemScreenWidth = data['systemScreenWidth']?.toDouble() ?? _systemScreenWidth; // Update overlay dimensions if provided by main app
} if (message.overlayWidth != null) {
if (data.containsKey('overlayWidth')) { _overlayWidth = message.overlayWidth!;
_overlayWidth = data['overlayWidth']?.toDouble() ?? _overlayWidth; }
} if (message.overlayHeight != null) {
if (data.containsKey('overlayHeight')) { _overlayHeight = message.overlayHeight!;
_overlayHeight = data['overlayHeight']?.toDouble() ?? _overlayHeight; }
} });
});
Logger.debug('UPDATE received:', 'Overlay');
customDebugPrint('[Overlay] 📡 UPDATE received:'); Logger.debug(' speedText: "$_speedText"', 'Overlay');
customDebugPrint(' speedText: "$_speedText"'); Logger.debug(' headingText: "$_headingText"', 'Overlay');
customDebugPrint(' headingText: "$_headingText"'); Logger.debug(' direction: ${_heading.toStringAsFixed(1)}°', 'Overlay');
customDebugPrint(' direction: ${_heading.toStringAsFixed(1)}°'); Logger.debug(' themeIndex: $_currentThemeIndex', 'Overlay');
customDebugPrint(' themeIndex: $_currentThemeIndex'); Logger.debug(' systemScreenWidth: ${_systemScreenWidth.round()}', 'Overlay');
customDebugPrint(' systemScreenWidth: ${_systemScreenWidth.round()}'); Logger.debug(' overlaySize from main: ${_overlayWidth.round()}x${_overlayHeight.round()}', 'Overlay');
customDebugPrint(' overlaySize from main: ${_overlayWidth.round()}x${_overlayHeight.round()}'); }
} }
}); });
} }
@@ -933,10 +929,10 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
final currentTheme = ColorThemes.getTheme(_currentThemeIndex); final currentTheme = ColorThemes.getTheme(_currentThemeIndex);
// Use overlay dimensions provided by main app (not calculated locally) // Use overlay dimensions provided by main app (not calculated locally)
final fontSize = (_overlayWidth * 0.2); // Font size proportional to actual overlay width final fontSize = (_overlayWidth * OverlayConfig.FONT_SIZE_RATIO);
customDebugPrint('[Overlay] 🔨 Building overlay widget ($buildTimestamp)'); Logger.debug('Building overlay widget ($buildTimestamp)', 'Overlay');
customDebugPrint('[Overlay] 📐 Window size: ${_overlayWidth.round()}x${_overlayHeight.round()}, fontSize: ${fontSize.round()}'); Logger.debug('Window size: ${_overlayWidth.round()}x${_overlayHeight.round()}, fontSize: ${fontSize.round()}', 'Overlay');
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
@@ -946,10 +942,10 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
height: double.infinity, height: double.infinity,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: currentTheme.background.withValues(alpha: 0.85), color: currentTheme.background.withValues(alpha: OverlayConfig.BACKGROUND_OPACITY),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: currentTheme.speedText.withValues(alpha: 0.3), color: currentTheme.speedText.withValues(alpha: OverlayConfig.BORDER_OPACITY),
width: 1 width: 1
), ),
), ),
@@ -989,7 +985,7 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
child: Text( child: Text(
_unitText, _unitText,
style: TextStyle( style: TextStyle(
fontSize: fontSize * 0.6, fontSize: fontSize * OverlayConfig.UNIT_FONT_RATIO,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
color: currentTheme.unitText, color: currentTheme.unitText,
fontFamily: 'DIN1451Alt', fontFamily: 'DIN1451Alt',
@@ -1018,7 +1014,7 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
fit: BoxFit.contain, fit: BoxFit.contain,
child: Icon( child: Icon(
Icons.navigation, Icons.navigation,
size: fontSize * 0.72, size: fontSize * OverlayConfig.ICON_SIZE_RATIO,
color: currentTheme.headingText, color: currentTheme.headingText,
), ),
), ),
@@ -1031,7 +1027,7 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
child: Text( child: Text(
_headingText, _headingText,
style: TextStyle( style: TextStyle(
fontSize: fontSize * 0.4, fontSize: fontSize * OverlayConfig.HEADING_FONT_RATIO,
color: currentTheme.headingText, color: currentTheme.headingText,
fontFamily: 'DIN1451Alt', fontFamily: 'DIN1451Alt',
), ),
@@ -1049,43 +1045,41 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, // Ensure gestures are captured behavior: HitTestBehavior.translucent, // Ensure gestures are captured
onPanStart: (details) { onPanStart: (details) {
customDebugPrint('[Overlay] 🖐️ Pan start detected - gestures are working!'); Logger.debug('Pan start detected - gestures are working!', 'Overlay');
}, },
onLongPress: () async { onLongPress: () async {
final timestamp = DateTime.now().millisecondsSinceEpoch; final timestamp = DateTime.now().millisecondsSinceEpoch;
customDebugPrint('[Overlay] 🔴 Long press detected - closing overlay and bringing main app to front ($timestamp)'); Logger.info('Long press detected - closing overlay and bringing main app to front ($timestamp)', 'Overlay');
try { try {
// Skip haptic feedback in overlay - platform services not available // Skip haptic feedback in overlay - platform services not available
customDebugPrint('[Overlay] 📳 Skipping haptic feedback (overlay context limitation)'); Logger.debug('Skipping haptic feedback (overlay context limitation)', 'Overlay');
// Signal main app to bring to foreground when overlay closes (non-blocking) // Signal main app to bring to foreground when overlay closes (non-blocking)
customDebugPrint('[Overlay] 📤 Sending bring-to-front close signal...'); Logger.debug('Sending bring-to-front close signal...', 'Overlay');
FlutterOverlayWindow.shareData({ FlutterOverlayWindow.shareData(OverlayMessage.longPressClose().toMap()).then((_) {
'action': 'longPressClose', Logger.debug('Long press close signal sent successfully', 'Overlay');
}).then((_) {
customDebugPrint('[Overlay] ✅ Long press close signal sent successfully');
}).catchError((error) { }).catchError((error) {
customDebugPrint('[Overlay] ❌ Long press close signal failed (expected): $error'); Logger.warn('Long press close signal failed (expected): $error', 'Overlay');
}); });
// Small delay to give signal a chance // Small delay to give signal a chance
await Future.delayed(const Duration(milliseconds: 50)); await Future.delayed(TimingConfig.LONG_PRESS_SIGNAL_DELAY);
// Close the overlay // Close the overlay
customDebugPrint('[Overlay] 🔴 Attempting to close overlay...'); Logger.info('Attempting to close overlay...', 'Overlay');
await FlutterOverlayWindow.closeOverlay(); await FlutterOverlayWindow.closeOverlay();
customDebugPrint('[Overlay] ✅ Overlay closed successfully via long press'); Logger.info('Overlay closed successfully via long press', 'Overlay');
} catch (e, stackTrace) { } catch (e, stackTrace) {
customDebugPrint('[Overlay] ❌ Long press close failed: $e'); Logger.error('Long press close failed: $e', 'Overlay');
customDebugPrint('[Overlay] 📚 Stack trace: $stackTrace'); Logger.error('Stack trace: $stackTrace', 'Overlay');
// Fallback: try to close anyway // Fallback: try to close anyway
try { try {
customDebugPrint('[Overlay] 🔄 Attempting fallback close...'); Logger.debug('Attempting fallback close...', 'Overlay');
await FlutterOverlayWindow.closeOverlay(); await FlutterOverlayWindow.closeOverlay();
customDebugPrint('[Overlay] ✅ Fallback close succeeded'); Logger.info('Fallback close succeeded', 'Overlay');
} catch (fallbackError) { } catch (fallbackError) {
customDebugPrint('[Overlay] ❌ Fallback close also failed: $fallbackError'); Logger.error('Fallback close also failed: $fallbackError', 'Overlay');
} }
} }
}, },
+47 -81
View File
@@ -1,43 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import '../gps_service.dart'; import '../gps_service.dart';
import '../speed_units.dart'; import '../config/speed_unit.dart';
import '../config/gps_constants.dart';
class ProcessedGpsData { import '../models/processed_gps_data.dart';
final double speed; // Raw speed in m/s import 'logger.dart';
final double heading; // Raw heading in degrees
final String displaySpeed; // Formatted speed text (with -- logic)
final String displayHeading; // Formatted heading text
final bool isSpeedValid; // True if from recent GPS
final bool isHeadingValid; // True if from recent GPS
const ProcessedGpsData({
required this.speed,
required this.heading,
required this.displaySpeed,
required this.displayHeading,
required this.isSpeedValid,
required this.isHeadingValid,
});
ProcessedGpsData copyWith({
double? speed,
double? heading,
String? displaySpeed,
String? displayHeading,
bool? isSpeedValid,
bool? isHeadingValid,
}) {
return ProcessedGpsData(
speed: speed ?? this.speed,
heading: heading ?? this.heading,
displaySpeed: displaySpeed ?? this.displaySpeed,
displayHeading: displayHeading ?? this.displayHeading,
isSpeedValid: isSpeedValid ?? this.isSpeedValid,
isHeadingValid: isHeadingValid ?? this.isHeadingValid,
);
}
}
class GpsDataManager { class GpsDataManager {
static GpsDataManager? _instance; static GpsDataManager? _instance;
@@ -58,7 +25,6 @@ class GpsDataManager {
isHeadingValid: false, isHeadingValid: false,
); );
static const Duration _staleDataThreshold = Duration(seconds: 4);
Timer? _staleDataTimer; Timer? _staleDataTimer;
// Public stream for UI components to subscribe to // Public stream for UI components to subscribe to
@@ -71,17 +37,17 @@ class GpsDataManager {
Future<void> initialize() async { Future<void> initialize() async {
if (_isInitialized) { if (_isInitialized) {
print('[GpsDataManager] ⚠️ Already initialized, skipping...'); Logger.warn('Already initialized, skipping...', 'GpsDataManager');
return; return;
} }
print('[GpsDataManager] 🚀 Starting GPS manager initialization...'); Logger.info('Starting GPS manager initialization...', 'GpsDataManager');
// Check GPS permissions and services first // Check GPS permissions and services first
final serviceEnabled = await GpsService.isLocationServiceEnabled(); final serviceEnabled = await GpsService.isLocationServiceEnabled();
print('[GpsDataManager] 📍 Location service enabled: $serviceEnabled'); Logger.debug('Location service enabled: $serviceEnabled', 'GpsDataManager');
if (!serviceEnabled) { if (!serviceEnabled) {
print('[GpsDataManager] ❌ GPS service disabled'); Logger.error('GPS service disabled', 'GpsDataManager');
_updateData(_currentData.copyWith( _updateData(_currentData.copyWith(
displaySpeed: 'GPS OFF', displaySpeed: 'GPS OFF',
displayHeading: 'GPS OFF' displayHeading: 'GPS OFF'
@@ -91,9 +57,9 @@ class GpsDataManager {
} }
final permissionGranted = await GpsService.requestPermissions(); final permissionGranted = await GpsService.requestPermissions();
print('[GpsDataManager] 🔐 GPS permission granted: $permissionGranted'); Logger.debug('GPS permission granted: $permissionGranted', 'GpsDataManager');
if (!permissionGranted) { if (!permissionGranted) {
print('[GpsDataManager] ❌ GPS permission denied'); Logger.error('GPS permission denied', 'GpsDataManager');
_updateData(_currentData.copyWith( _updateData(_currentData.copyWith(
displaySpeed: 'NO PERM', displaySpeed: 'NO PERM',
displayHeading: 'NO PERM' displayHeading: 'NO PERM'
@@ -103,47 +69,47 @@ class GpsDataManager {
} }
// Create GPS stream subscription // Create GPS stream subscription
print('[GpsDataManager] 📡 Creating GPS stream subscription...'); Logger.debug('Creating GPS stream subscription...', 'GpsDataManager');
try { try {
print('[GpsDataManager] 🔧 Getting initial GPS position with long timeout...'); Logger.debug('Getting initial GPS position with long timeout...', 'GpsDataManager');
// First, get initial position with long timeout for cold start // First, get initial position with long timeout for cold start
final initialPosition = await Geolocator.getCurrentPosition( final initialPosition = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings( locationSettings: LocationSettings(
accuracy: LocationAccuracy.bestForNavigation, accuracy: LocationAccuracy.bestForNavigation,
timeLimit: Duration(seconds: 20), // Long timeout for initial fix timeLimit: GpsConfig.INITIAL_FIX_TIMEOUT,
), ),
); );
print('[GpsDataManager] ✅ Initial GPS fix obtained'); Logger.info('Initial GPS fix obtained', 'GpsDataManager');
_onPositionUpdate(initialPosition); _onPositionUpdate(initialPosition);
// Then start position stream with shorter timeout for updates // Then start position stream with shorter timeout for updates
print('[GpsDataManager] 🔧 Starting GPS stream with short update timeout...'); Logger.debug('Starting GPS stream with short update timeout...', 'GpsDataManager');
_gpsSubscription = GpsService.positionStream.listen( _gpsSubscription = GpsService.positionStream.listen(
(Position position) { (Position position) {
print('[GpsDataManager] 📥 GPS callback triggered'); Logger.debug('GPS callback triggered', 'GpsDataManager');
_onPositionUpdate(position); _onPositionUpdate(position);
}, },
onError: (error) { onError: (error) {
print('[GpsDataManager] ❌ GPS stream error callback: $error'); Logger.error('GPS stream error callback: $error', 'GpsDataManager');
_onGpsError(error); _onGpsError(error);
}, },
onDone: () { onDone: () {
print('[GpsDataManager] ⚠️ GPS stream done callback - stream ended'); Logger.warn('GPS stream done callback - stream ended', 'GpsDataManager');
}, },
cancelOnError: false, cancelOnError: false,
); );
print('[GpsDataManager] ✅ GPS stream subscription created'); Logger.info('GPS stream subscription created', 'GpsDataManager');
print('[GpsDataManager] 🔍 Subscription details: ${_gpsSubscription.runtimeType}'); Logger.debug('Subscription details: ${_gpsSubscription.runtimeType}', 'GpsDataManager');
_isInitialized = true; _isInitialized = true;
} catch (e, stackTrace) { } catch (e, stackTrace) {
print('[GpsDataManager] ❌ GPS subscription creation failed: $e'); Logger.error('GPS subscription creation failed: $e', 'GpsDataManager');
print('[GpsDataManager] 📚 Stack trace: $stackTrace'); Logger.error('Stack trace: $stackTrace', 'GpsDataManager');
_updateData(_currentData.copyWith( _updateData(_currentData.copyWith(
displaySpeed: 'GPS ERR', displaySpeed: 'GPS ERR',
displayHeading: 'GPS ERR' displayHeading: 'GPS ERR'
@@ -151,12 +117,12 @@ class GpsDataManager {
_isInitialized = true; // Mark as initialized even if failed _isInitialized = true; // Mark as initialized even if failed
return; return;
} }
print('[GpsDataManager] ✅ GPS manager initialized successfully'); Logger.info('GPS manager initialized successfully', 'GpsDataManager');
} }
void _onPositionUpdate(Position position) { void _onPositionUpdate(Position position) {
print('[GpsDataManager] 📡 Raw GPS update: speed=${position.speed.toStringAsFixed(2)} m/s, heading=${position.heading.toStringAsFixed(1)}°'); Logger.debug('Raw GPS update: speed=${position.speed.toStringAsFixed(2)} m/s, heading=${position.heading.toStringAsFixed(1)}°', 'GpsDataManager');
_scheduleStaleDataTimer(); _scheduleStaleDataTimer();
@@ -165,18 +131,18 @@ class GpsDataManager {
final double speed = speedValid ? rawSpeed : 0.0; final double speed = speedValid ? rawSpeed : 0.0;
final rawHeading = position.heading; final rawHeading = position.heading;
final bool headingValid = rawHeading.isFinite && rawHeading >= 0 && rawHeading < 360; final bool headingValid = rawHeading.isFinite && rawHeading >= GpsConfig.VALID_HEADING_MIN && rawHeading < GpsConfig.VALID_HEADING_MAX;
final double heading = headingValid ? rawHeading : _currentData.heading; final double heading = headingValid ? rawHeading : _currentData.heading;
// Basic display logic - TEMPORARILY show all speeds for indoor testing // Basic display logic - TEMPORARILY show all speeds for indoor testing
// TODO: Re-enable low-speed logic later: (speed < 1.0 && (heading < 0.0 || heading >= 360.0)) ? '--' : ... // TODO: Re-enable low-speed logic later: (speed < GpsConfig.LOW_SPEED_THRESHOLD && (heading < GpsConfig.VALID_HEADING_MIN || heading >= GpsConfig.VALID_HEADING_MAX)) ? '--' : ...
final displaySpeed = speed.toStringAsFixed(1); final displaySpeed = speed.toStringAsFixed(1);
final displayHeading = headingValid final displayHeading = headingValid
? GpsService.formatHeading(heading) ? GpsService.formatHeading(heading)
: _currentData.displayHeading; : _currentData.displayHeading;
print('[GpsDataManager] 🔄 Processed: displaySpeed="$displaySpeed", displayHeading="$displayHeading"'); Logger.debug('Processed: displaySpeed="$displaySpeed", displayHeading="$displayHeading"', 'GpsDataManager');
final processedData = ProcessedGpsData( final processedData = ProcessedGpsData(
speed: speed, speed: speed,
@@ -205,7 +171,7 @@ class GpsDataManager {
void _scheduleStaleDataTimer() { void _scheduleStaleDataTimer() {
_staleDataTimer?.cancel(); _staleDataTimer?.cancel();
_staleDataTimer = Timer(_staleDataThreshold, _handleStaleDataTimeout); _staleDataTimer = Timer(GpsConfig.STALE_DATA_THRESHOLD, _handleStaleDataTimeout);
} }
void _handleStaleDataTimeout() { void _handleStaleDataTimeout() {
@@ -215,10 +181,10 @@ class GpsDataManager {
return; return;
} }
final headingStillValid = _currentData.isHeadingValid && _currentData.heading >= 0 && _currentData.heading < 360; final headingStillValid = _currentData.isHeadingValid && _currentData.heading >= GpsConfig.VALID_HEADING_MIN && _currentData.heading < GpsConfig.VALID_HEADING_MAX;
final displayHeading = headingStillValid ? _currentData.displayHeading : '--'; final displayHeading = headingStillValid ? _currentData.displayHeading : '--';
print('[GpsDataManager] ⏱️ No GPS updates within ${_staleDataThreshold.inSeconds}s - marking data as stale'); Logger.warn('No GPS updates within ${GpsConfig.STALE_DATA_THRESHOLD.inSeconds}s - marking data as stale', 'GpsDataManager');
final staleData = _currentData.copyWith( final staleData = _currentData.copyWith(
displaySpeed: '--', displaySpeed: '--',
@@ -232,10 +198,10 @@ class GpsDataManager {
} }
void _updateData(ProcessedGpsData newData) { void _updateData(ProcessedGpsData newData) {
print('[GpsDataManager] 📤 Broadcasting data: ${newData.displaySpeed} ${newData.displayHeading}'); Logger.debug('Broadcasting data: ${newData.displaySpeed} ${newData.displayHeading}', 'GpsDataManager');
_currentData = newData; _currentData = newData;
_dataController.add(newData); _dataController.add(newData);
print('[GpsDataManager] ✅ Data broadcast complete'); Logger.debug('Data broadcast complete', 'GpsDataManager');
} }
// Format speed for specific unit (used by UI) // Format speed for specific unit (used by UI)
@@ -247,17 +213,17 @@ class GpsDataManager {
} }
void dispose() { void dispose() {
print('[GpsDataManager] 🛑 Disposing GPS manager...'); Logger.info('Disposing GPS manager...', 'GpsDataManager');
if (_gpsSubscription != null) { if (_gpsSubscription != null) {
print('[GpsDataManager] 🛑 Cancelling GPS subscription...'); Logger.debug('Cancelling GPS subscription...', 'GpsDataManager');
_gpsSubscription?.cancel(); _gpsSubscription?.cancel();
_gpsSubscription = null; _gpsSubscription = null;
} }
print('[GpsDataManager] 🛑 Closing data controller...'); Logger.debug('Closing data controller...', 'GpsDataManager');
_staleDataTimer?.cancel(); _staleDataTimer?.cancel();
_staleDataTimer = null; _staleDataTimer = null;
_dataController.close(); _dataController.close();
_isInitialized = false; _isInitialized = false;
print('[GpsDataManager] 🛑 GPS manager disposed'); Logger.info('GPS manager disposed', 'GpsDataManager');
} }
} }
-20
View File
@@ -1,20 +0,0 @@
enum SpeedUnit {
kmh('km/h', 1.0),
mph('mph', 0.621371),
knots('kts', 0.539957);
const SpeedUnit(this.label, this.multiplier);
final String label;
final double multiplier;
double convert(double speedMps) {
return speedMps * 3.6 * multiplier;
}
SpeedUnit get next {
final values = SpeedUnit.values;
final currentIndex = values.indexOf(this);
return values[(currentIndex + 1) % values.length];
}
}