From a0d93d7f6df17cee36fd8aa35def072c91125f82 Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Mon, 9 Feb 2026 21:33:28 +0900 Subject: [PATCH] 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 --- lib/color_themes.dart | 64 ------ lib/gps_service.dart | 11 +- lib/main.dart | 318 ++++++++++++++--------------- lib/services/gps_data_manager.dart | 128 +++++------- lib/speed_units.dart | 20 -- 5 files changed, 209 insertions(+), 332 deletions(-) delete mode 100644 lib/color_themes.dart delete mode 100644 lib/speed_units.dart diff --git a/lib/color_themes.dart b/lib/color_themes.dart deleted file mode 100644 index 26e680e..0000000 --- a/lib/color_themes.dart +++ /dev/null @@ -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 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; - } -} \ No newline at end of file diff --git a/lib/gps_service.dart b/lib/gps_service.dart index e818c8d..9b481c4 100644 --- a/lib/gps_service.dart +++ b/lib/gps_service.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'package:geolocator/geolocator.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'config/gps_constants.dart'; class GpsService { static double? _lastValidHeading; - + static Stream get positionStream { return Geolocator.getPositionStream( locationSettings: const LocationSettings( @@ -13,18 +14,18 @@ class GpsService { ), ); } - + static Future requestPermissions() async { final permission = await Permission.locationWhenInUse.request(); return permission.isGranted; } - + static Future isLocationServiceEnabled() async { return await Geolocator.isLocationServiceEnabled(); } - + static double getCachedHeading(double heading) { - if (heading >= 0 && heading <= 360) { + if (heading >= GpsConfig.VALID_HEADING_MIN && heading <= GpsConfig.VALID_HEADING_MAX) { _lastValidHeading = heading; return heading; } diff --git a/lib/main.dart b/lib/main.dart index 1a7987b..e9eed26 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,20 +5,16 @@ import 'dart:async'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:bg_launcher/bg_launcher.dart'; // For bringing app to foreground -import 'speed_units.dart'; -import 'color_themes.dart'; +import 'config/speed_unit.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/logger.dart'; 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() { runApp(const SpeedoApp()); } @@ -26,7 +22,7 @@ void main() { // Entry point for overlay window @pragma("vm:entry-point") void overlayMain() { - customDebugPrint('[Overlay] 🚀 overlayMain() called'); + Logger.info('overlayMain() called', 'Overlay'); runApp(const MaterialApp( debugShowCheckedModeBanner: false, home: OverlaySpeedometer(), @@ -85,11 +81,11 @@ class _SpeedometerScreenState extends State with WidgetsBindi Map _getOverlaySize() { final window = WidgetsBinding.instance.platformDispatcher.views.first; final physicalSize = window.physicalSize; - + // Use actual physical pixels for overlay sizing - final overlayWidth = (physicalSize.width * 0.45).round(); // 45% of actual screen width - final overlayHeight = (overlayWidth * 0.6).round(); - + final overlayWidth = (physicalSize.width * OverlayConfig.WIDTH_PERCENTAGE).round(); + final overlayHeight = (overlayWidth * OverlayConfig.ASPECT_RATIO).round(); + return {'width': overlayWidth, 'height': overlayHeight}; } @@ -125,22 +121,22 @@ class _SpeedometerScreenState extends State with WidgetsBindi _tapCloseRequested = true; // Reusing the flag for long press // Set a timer to reset this flag if overlay doesn't close soon _tapCloseTimer?.cancel(); - _tapCloseTimer = Timer(const Duration(milliseconds: 500), () { + _tapCloseTimer = Timer(TimingConfig.TAP_CLOSE_DELAY, () { _tapCloseRequested = false; }); break; // 'bringToFront' action handler removed per NOTES.txt (tap to bring front functionality disabled) default: - customDebugPrint('[Main] ⚠️ Unknown overlay action: "$action"'); + Logger.warn('Unknown overlay action: "$action"', 'Main'); } } } catch (e, stackTrace) { - customDebugPrint('[Main] ❌ Error processing overlay message: $e'); - customDebugPrint('[Main] 📚 Stack trace: $stackTrace'); + Logger.error('Error processing overlay message: $e', 'Main'); + Logger.error('Stack trace: $stackTrace', 'Main'); } }, onError: (error) { - customDebugPrint('[Main] ❌ Overlay listener error: $error'); + Logger.error('Overlay listener error: $error', 'Main'); }, ); } @@ -154,7 +150,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi // Cancel any existing timer first _overlayStatusCheckTimer?.cancel(); - _overlayStatusCheckTimer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async { + _overlayStatusCheckTimer = Timer.periodic(TimingConfig.OVERLAY_STATUS_CHECK_INTERVAL, (timer) async { if (_isOverlayActive) { try { final isActive = await FlutterOverlayWindow.isActive(); @@ -163,14 +159,14 @@ class _SpeedometerScreenState extends State with WidgetsBindi _handleOverlayClose(); } } catch (e) { - customDebugPrint('[Main] ❌ Overlay status check failed: $e'); + Logger.error('Overlay status check failed: $e', 'Main'); } } else { // If overlay is not supposed to be active, cancel the timer timer.cancel(); } }); - customDebugPrint('[Main] 🔍 Started overlay status monitoring'); + Logger.debug('Started overlay status monitoring', 'Main'); } void _handleOverlayClose({bool bringToForeground = false}) { @@ -196,7 +192,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi void _startBackgroundHeartbeat() { // 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) { // Minimal activity to prevent hibernation if (mounted) { @@ -231,8 +227,8 @@ class _SpeedometerScreenState extends State with WidgetsBindi }); } } 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 try { if (mounted) { @@ -241,7 +237,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi }); } } 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 with WidgetsBindi // Double-check wake lock status for debugging if (!isEnabled) { - customDebugPrint('[Main] ⚠️ Wake lock not properly enabled, retrying...'); + Logger.warn('Wake lock not properly enabled, retrying...', 'Main'); await WakelockPlus.enable(); } } catch (e) { - customDebugPrint('[Main] ❌ Wake lock failed: $e'); + Logger.error('Wake lock failed: $e', 'Main'); // Wake lock not supported on this platform, continue normally } } @@ -272,7 +268,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi _overlayStatusCheckTimer?.cancel(); _tapCloseTimer?.cancel(); 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(); } @@ -316,66 +312,66 @@ class _SpeedometerScreenState extends State with WidgetsBindi // Note: This requires platform-specific implementation which we'd need to add via method channel // For now, we'll rely on other persistence mechanisms } catch (e) { - customDebugPrint('[Main] ⚠️ Battery optimization exemption not available: $e'); + Logger.warn('Battery optimization exemption not available: $e', 'Main'); } } - + Future _ensureOverlayPermission() async { try { // Check if overlay permission is granted (required for bg_launcher to work properly) final isGranted = await FlutterOverlayWindow.isPermissionGranted(); - + if (!isGranted) { await FlutterOverlayWindow.requestPermission(); } } catch (e) { - customDebugPrint('[Main] ❌ Error checking overlay permission: $e'); + Logger.error('Error checking overlay permission: $e', 'Main'); } } Future _initializeGpsManager() async { - print('[Main] 🚀 Starting GPS manager initialization...'); + Logger.info('Starting GPS manager initialization...', 'Main'); try { // Initialize the GPS manager - print('[Main] 📡 Calling GpsDataManager.instance.initialize()...'); + Logger.debug('Calling GpsDataManager.instance.initialize()...', 'Main'); await GpsDataManager.instance.initialize(); - print('[Main] ✅ GPS manager initialization completed'); - + Logger.info('GPS manager initialization completed', 'Main'); + // 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( _onGpsDataUpdate, onError: (error) { - print('[Main] ❌ GPS data stream error: $error'); + Logger.error('GPS data stream error: $error', 'Main'); setState(() => _errorMessage = 'GPS manager error: $error'); }, ); - print('[Main] ✅ GPS data stream subscription active'); - - customDebugPrint('[Main] 🛰️ GPS manager initialized and subscribed'); + Logger.info('GPS data stream subscription active', 'Main'); + + Logger.info('GPS manager initialized and subscribed', 'Main'); } catch (e) { - print('[Main] ❌ GPS manager initialization exception: $e'); + Logger.error('GPS manager initialization exception: $e', 'Main'); 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) { - 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(() { _currentGpsData = gpsData; _errorMessage = ''; }); - - print('[Main] ✅ State updated with GPS data'); + + Logger.debug('State updated with GPS data', 'Main'); 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 if (_isOverlayActive) { _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 with WidgetsBindi void _pushDataToOverlay() { // Only push data if overlay is actually active (listener check was causing race conditions) 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; } - - 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 final speedText = GpsDataManager.instance.getFormattedSpeed(_currentUnit); final unitText = _currentUnit.label.toString(); final headingText = _currentGpsData.displayHeading; - - customDebugPrint('[Main] 📤 SENDING to overlay:'); - customDebugPrint(' speedText: "$speedText"'); - customDebugPrint(' headingText: "$headingText"'); - customDebugPrint(' direction: ${_currentGpsData.heading.toStringAsFixed(1)}°'); - customDebugPrint(' unit: $unitText, theme: $_currentThemeIndex'); - - FlutterOverlayWindow.shareData({ - 'action': 'updateDisplay', - 'speedText': speedText, - 'unitText': unitText, - 'headingText': headingText, - 'heading': _currentGpsData.heading, - 'unitIndex': _currentUnit.index, - 'themeIndex': _currentThemeIndex, - }); + + Logger.debug('SENDING to overlay:', 'Main'); + Logger.debug(' speedText: "$speedText"', 'Main'); + Logger.debug(' headingText: "$headingText"', 'Main'); + Logger.debug(' direction: ${_currentGpsData.heading.toStringAsFixed(1)}°', 'Main'); + Logger.debug(' unit: $unitText, theme: $_currentThemeIndex', 'Main'); + + FlutterOverlayWindow.shareData(OverlayMessage.updateDisplay( + speedText: speedText, + unitText: unitText, + headingText: headingText, + heading: _currentGpsData.heading, + unitIndex: _currentUnit.index, + themeIndex: _currentThemeIndex, + ).toMap()); } void _cycleUnit() { @@ -437,17 +432,18 @@ class _SpeedometerScreenState extends State with WidgetsBindi // Send initial display data WITH size info (only needed on creation) final speedText = GpsDataManager.instance.getFormattedSpeed(_currentUnit); final headingText = _currentGpsData.displayHeading; - - await FlutterOverlayWindow.shareData({ - 'action': 'updateDisplay', - 'speedText': speedText, - 'headingText': headingText, - 'heading': _currentGpsData.heading, - 'unitIndex': _currentUnit.index, - 'themeIndex': _currentThemeIndex, - 'overlayWidth': overlaySize['width']!.toDouble(), - 'overlayHeight': overlaySize['height']!.toDouble(), - }); + final unitText = _currentUnit.label.toString(); + + await FlutterOverlayWindow.shareData(OverlayMessage.updateDisplay( + speedText: speedText, + unitText: unitText, + headingText: headingText, + heading: _currentGpsData.heading, + unitIndex: _currentUnit.index, + themeIndex: _currentThemeIndex, + overlayWidth: overlaySize['width']!.toDouble(), + overlayHeight: overlaySize['height']!.toDouble(), + ).toMap()); // Show the overlay with proportional sizing based on system resolution await FlutterOverlayWindow.showOverlay( @@ -462,11 +458,11 @@ class _SpeedometerScreenState extends State with WidgetsBindi 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 _isOverlayActive = true; - customDebugPrint('[Main] ✅ _isOverlayActive set to: $_isOverlayActive'); + Logger.debug('_isOverlayActive set to: $_isOverlayActive', 'Main'); // Start listening to overlay messages after state is set _startListeningToOverlayMessages(); @@ -483,16 +479,16 @@ class _SpeedometerScreenState extends State with WidgetsBindi Future _closeFloatingWindow() async { if (!_isOverlayActive) { - customDebugPrint('[Main] ⚠️ No overlay to close'); + Logger.warn('No overlay to close', 'Main'); return; } - + try { 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 } catch (e) { - customDebugPrint('[Main] ❌ Error closing overlay: $e'); + Logger.error('Error closing overlay: $e', 'Main'); } } @@ -647,7 +643,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi child: GestureDetector( onTap: () { HapticFeedback.lightImpact(); - customDebugPrint('[Main] 🖱️ Navigation area tapped - overlay active: $_isOverlayActive'); + Logger.debug('Navigation area tapped - overlay active: $_isOverlayActive', 'Main'); if (_isOverlayActive) { _closeFloatingWindow(); } else { @@ -655,7 +651,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi } }, onLongPress: () { - customDebugPrint('[Main] 🖱️ Navigation area long-pressed - overlay active: $_isOverlayActive'); + Logger.debug('Navigation area long-pressed - overlay active: $_isOverlayActive', 'Main'); _closeFloatingWindow(); }, child: Container( @@ -774,7 +770,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi child: GestureDetector( onTap: () { HapticFeedback.lightImpact(); - customDebugPrint('[Main] 🖱️ Navigation area tapped (landscape) - overlay active: $_isOverlayActive'); + Logger.debug('Navigation area tapped (landscape) - overlay active: $_isOverlayActive', 'Main'); if (_isOverlayActive) { _closeFloatingWindow(); } else { @@ -782,7 +778,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi } }, onLongPress: () { - customDebugPrint('[Main] 🖱️ Navigation area long-pressed (landscape) - overlay active: $_isOverlayActive'); + Logger.debug('Navigation area long-pressed (landscape) - overlay active: $_isOverlayActive', 'Main'); _closeFloatingWindow(); }, child: Container( @@ -856,14 +852,14 @@ class _OverlaySpeedometerState extends State { @override void initState() { super.initState(); - customDebugPrint('[Overlay] 🟢 CREATED - Floating window initialized (${DateTime.now().millisecondsSinceEpoch})'); + Logger.info('CREATED - Floating window initialized (${DateTime.now().millisecondsSinceEpoch})', 'Overlay'); _getSystemScreenSize(); _listenToMainAppMessages(); } @override void dispose() { - customDebugPrint('[Overlay] 🔴 DISPOSING - Overlay widget disposed (${DateTime.now().millisecondsSinceEpoch})'); + Logger.info('DISPOSING - Overlay widget disposed (${DateTime.now().millisecondsSinceEpoch})', 'Overlay'); super.dispose(); } @@ -875,53 +871,53 @@ class _OverlaySpeedometerState extends State { final devicePixelRatio = window.devicePixelRatio; final systemSize = physicalSize / devicePixelRatio; - customDebugPrint('[Overlay] 🔍 Screen detection:'); - customDebugPrint(' physicalSize: ${physicalSize.width.round()}x${physicalSize.height.round()}'); - customDebugPrint(' devicePixelRatio: $devicePixelRatio'); - customDebugPrint(' calculated systemSize: ${systemSize.width.round()}x${systemSize.height.round()}'); - + Logger.debug('Screen detection:', 'Overlay'); + Logger.debug(' physicalSize: ${physicalSize.width.round()}x${physicalSize.height.round()}', 'Overlay'); + Logger.debug(' devicePixelRatio: $devicePixelRatio', 'Overlay'); + Logger.debug(' calculated systemSize: ${systemSize.width.round()}x${systemSize.height.round()}', 'Overlay'); + setState(() { _systemScreenWidth = systemSize.width; }); - - customDebugPrint('[Overlay] ✅ _systemScreenWidth set to: ${_systemScreenWidth.round()}'); + + Logger.debug('_systemScreenWidth set to: ${_systemScreenWidth.round()}', 'Overlay'); } catch (e) { - customDebugPrint('[Overlay] ❌ Error getting system screen size: $e'); + Logger.error('Error getting system screen size: $e', 'Overlay'); // Fallback to reasonable default setState(() { - _systemScreenWidth = 400; + _systemScreenWidth = OverlayConfig.DEFAULT_SCREEN_WIDTH; }); } } void _listenToMainAppMessages() { FlutterOverlayWindow.overlayListener.listen((data) { - if (data is Map && data['action'] == 'updateDisplay') { - setState(() { - _speedText = data['speedText'] ?? '--'; - _unitText = data['unitText']; - _headingText = data['headingText'] ?? 'N/A'; - _heading = data['heading']?.toDouble() ?? -1.0; - _currentThemeIndex = data['themeIndex'] ?? 0; - // Update screen size and overlay dimensions if provided by main app - if (data.containsKey('systemScreenWidth')) { - _systemScreenWidth = data['systemScreenWidth']?.toDouble() ?? _systemScreenWidth; - } - if (data.containsKey('overlayWidth')) { - _overlayWidth = data['overlayWidth']?.toDouble() ?? _overlayWidth; - } - if (data.containsKey('overlayHeight')) { - _overlayHeight = data['overlayHeight']?.toDouble() ?? _overlayHeight; - } - }); - - customDebugPrint('[Overlay] 📡 UPDATE received:'); - customDebugPrint(' speedText: "$_speedText"'); - customDebugPrint(' headingText: "$_headingText"'); - customDebugPrint(' direction: ${_heading.toStringAsFixed(1)}°'); - customDebugPrint(' themeIndex: $_currentThemeIndex'); - customDebugPrint(' systemScreenWidth: ${_systemScreenWidth.round()}'); - customDebugPrint(' overlaySize from main: ${_overlayWidth.round()}x${_overlayHeight.round()}'); + if (data is Map) { + final message = OverlayMessage.fromMap(data); + if (message.action == 'updateDisplay') { + setState(() { + _speedText = message.speedText ?? '--'; + _unitText = message.unitText ?? ''; + _headingText = message.headingText ?? 'N/A'; + _heading = message.heading ?? -1.0; + _currentThemeIndex = message.themeIndex ?? 0; + // Update overlay dimensions if provided by main app + if (message.overlayWidth != null) { + _overlayWidth = message.overlayWidth!; + } + if (message.overlayHeight != null) { + _overlayHeight = message.overlayHeight!; + } + }); + + Logger.debug('UPDATE received:', 'Overlay'); + Logger.debug(' speedText: "$_speedText"', 'Overlay'); + Logger.debug(' headingText: "$_headingText"', 'Overlay'); + Logger.debug(' direction: ${_heading.toStringAsFixed(1)}°', 'Overlay'); + Logger.debug(' themeIndex: $_currentThemeIndex', 'Overlay'); + Logger.debug(' systemScreenWidth: ${_systemScreenWidth.round()}', 'Overlay'); + Logger.debug(' overlaySize from main: ${_overlayWidth.round()}x${_overlayHeight.round()}', 'Overlay'); + } } }); } @@ -933,10 +929,10 @@ class _OverlaySpeedometerState extends State { final currentTheme = ColorThemes.getTheme(_currentThemeIndex); // Use overlay dimensions provided by main app (not calculated locally) - final fontSize = (_overlayWidth * 0.2); // Font size proportional to actual overlay width - - customDebugPrint('[Overlay] 🔨 Building overlay widget ($buildTimestamp)'); - customDebugPrint('[Overlay] 📐 Window size: ${_overlayWidth.round()}x${_overlayHeight.round()}, fontSize: ${fontSize.round()}'); + final fontSize = (_overlayWidth * OverlayConfig.FONT_SIZE_RATIO); + + Logger.debug('Building overlay widget ($buildTimestamp)', 'Overlay'); + Logger.debug('Window size: ${_overlayWidth.round()}x${_overlayHeight.round()}, fontSize: ${fontSize.round()}', 'Overlay'); return Material( color: Colors.transparent, @@ -946,10 +942,10 @@ class _OverlaySpeedometerState extends State { height: double.infinity, child: Container( decoration: BoxDecoration( - color: currentTheme.background.withValues(alpha: 0.85), + color: currentTheme.background.withValues(alpha: OverlayConfig.BACKGROUND_OPACITY), borderRadius: BorderRadius.circular(12), border: Border.all( - color: currentTheme.speedText.withValues(alpha: 0.3), + color: currentTheme.speedText.withValues(alpha: OverlayConfig.BORDER_OPACITY), width: 1 ), ), @@ -989,7 +985,7 @@ class _OverlaySpeedometerState extends State { child: Text( _unitText, style: TextStyle( - fontSize: fontSize * 0.6, + fontSize: fontSize * OverlayConfig.UNIT_FONT_RATIO, fontWeight: FontWeight.w300, color: currentTheme.unitText, fontFamily: 'DIN1451Alt', @@ -1018,7 +1014,7 @@ class _OverlaySpeedometerState extends State { fit: BoxFit.contain, child: Icon( Icons.navigation, - size: fontSize * 0.72, + size: fontSize * OverlayConfig.ICON_SIZE_RATIO, color: currentTheme.headingText, ), ), @@ -1031,7 +1027,7 @@ class _OverlaySpeedometerState extends State { child: Text( _headingText, style: TextStyle( - fontSize: fontSize * 0.4, + fontSize: fontSize * OverlayConfig.HEADING_FONT_RATIO, color: currentTheme.headingText, fontFamily: 'DIN1451Alt', ), @@ -1049,43 +1045,41 @@ class _OverlaySpeedometerState extends State { child: GestureDetector( behavior: HitTestBehavior.translucent, // Ensure gestures are captured onPanStart: (details) { - customDebugPrint('[Overlay] 🖐️ Pan start detected - gestures are working!'); + Logger.debug('Pan start detected - gestures are working!', 'Overlay'); }, onLongPress: () async { 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 { // 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) - customDebugPrint('[Overlay] 📤 Sending bring-to-front close signal...'); - FlutterOverlayWindow.shareData({ - 'action': 'longPressClose', - }).then((_) { - customDebugPrint('[Overlay] ✅ Long press close signal sent successfully'); + Logger.debug('Sending bring-to-front close signal...', 'Overlay'); + FlutterOverlayWindow.shareData(OverlayMessage.longPressClose().toMap()).then((_) { + Logger.debug('Long press close signal sent successfully', 'Overlay'); }).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 - await Future.delayed(const Duration(milliseconds: 50)); - + await Future.delayed(TimingConfig.LONG_PRESS_SIGNAL_DELAY); + // Close the overlay - customDebugPrint('[Overlay] 🔴 Attempting to close overlay...'); + Logger.info('Attempting to close overlay...', 'Overlay'); await FlutterOverlayWindow.closeOverlay(); - customDebugPrint('[Overlay] ✅ Overlay closed successfully via long press'); - + Logger.info('Overlay closed successfully via long press', 'Overlay'); + } catch (e, stackTrace) { - customDebugPrint('[Overlay] ❌ Long press close failed: $e'); - customDebugPrint('[Overlay] 📚 Stack trace: $stackTrace'); + Logger.error('Long press close failed: $e', 'Overlay'); + Logger.error('Stack trace: $stackTrace', 'Overlay'); // Fallback: try to close anyway try { - customDebugPrint('[Overlay] 🔄 Attempting fallback close...'); + Logger.debug('Attempting fallback close...', 'Overlay'); await FlutterOverlayWindow.closeOverlay(); - customDebugPrint('[Overlay] ✅ Fallback close succeeded'); + Logger.info('Fallback close succeeded', 'Overlay'); } catch (fallbackError) { - customDebugPrint('[Overlay] ❌ Fallback close also failed: $fallbackError'); + Logger.error('Fallback close also failed: $fallbackError', 'Overlay'); } } }, diff --git a/lib/services/gps_data_manager.dart b/lib/services/gps_data_manager.dart index 6a82947..2b8c1ce 100644 --- a/lib/services/gps_data_manager.dart +++ b/lib/services/gps_data_manager.dart @@ -1,43 +1,10 @@ import 'dart:async'; import 'package:geolocator/geolocator.dart'; import '../gps_service.dart'; -import '../speed_units.dart'; - -class ProcessedGpsData { - final double speed; // Raw speed in m/s - 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, - ); - } -} +import '../config/speed_unit.dart'; +import '../config/gps_constants.dart'; +import '../models/processed_gps_data.dart'; +import 'logger.dart'; class GpsDataManager { static GpsDataManager? _instance; @@ -58,7 +25,6 @@ class GpsDataManager { isHeadingValid: false, ); - static const Duration _staleDataThreshold = Duration(seconds: 4); Timer? _staleDataTimer; // Public stream for UI components to subscribe to @@ -71,17 +37,17 @@ class GpsDataManager { Future initialize() async { if (_isInitialized) { - print('[GpsDataManager] ⚠️ Already initialized, skipping...'); + Logger.warn('Already initialized, skipping...', 'GpsDataManager'); return; } - - print('[GpsDataManager] 🚀 Starting GPS manager initialization...'); - + + Logger.info('Starting GPS manager initialization...', 'GpsDataManager'); + // Check GPS permissions and services first final serviceEnabled = await GpsService.isLocationServiceEnabled(); - print('[GpsDataManager] 📍 Location service enabled: $serviceEnabled'); + Logger.debug('Location service enabled: $serviceEnabled', 'GpsDataManager'); if (!serviceEnabled) { - print('[GpsDataManager] ❌ GPS service disabled'); + Logger.error('GPS service disabled', 'GpsDataManager'); _updateData(_currentData.copyWith( displaySpeed: 'GPS OFF', displayHeading: 'GPS OFF' @@ -91,9 +57,9 @@ class GpsDataManager { } final permissionGranted = await GpsService.requestPermissions(); - print('[GpsDataManager] 🔐 GPS permission granted: $permissionGranted'); + Logger.debug('GPS permission granted: $permissionGranted', 'GpsDataManager'); if (!permissionGranted) { - print('[GpsDataManager] ❌ GPS permission denied'); + Logger.error('GPS permission denied', 'GpsDataManager'); _updateData(_currentData.copyWith( displaySpeed: 'NO PERM', displayHeading: 'NO PERM' @@ -103,47 +69,47 @@ class GpsDataManager { } // Create GPS stream subscription - print('[GpsDataManager] 📡 Creating GPS stream subscription...'); - + Logger.debug('Creating GPS stream subscription...', 'GpsDataManager'); + 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 final initialPosition = await Geolocator.getCurrentPosition( - locationSettings: const LocationSettings( + locationSettings: LocationSettings( 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); - + // 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( (Position position) { - print('[GpsDataManager] 📥 GPS callback triggered'); + Logger.debug('GPS callback triggered', 'GpsDataManager'); _onPositionUpdate(position); }, onError: (error) { - print('[GpsDataManager] ❌ GPS stream error callback: $error'); + Logger.error('GPS stream error callback: $error', 'GpsDataManager'); _onGpsError(error); }, onDone: () { - print('[GpsDataManager] ⚠️ GPS stream done callback - stream ended'); + Logger.warn('GPS stream done callback - stream ended', 'GpsDataManager'); }, cancelOnError: false, ); - - print('[GpsDataManager] ✅ GPS stream subscription created'); - print('[GpsDataManager] 🔍 Subscription details: ${_gpsSubscription.runtimeType}'); - + + Logger.info('GPS stream subscription created', 'GpsDataManager'); + Logger.debug('Subscription details: ${_gpsSubscription.runtimeType}', 'GpsDataManager'); + _isInitialized = true; - + } catch (e, stackTrace) { - print('[GpsDataManager] ❌ GPS subscription creation failed: $e'); - print('[GpsDataManager] 📚 Stack trace: $stackTrace'); + Logger.error('GPS subscription creation failed: $e', 'GpsDataManager'); + Logger.error('Stack trace: $stackTrace', 'GpsDataManager'); _updateData(_currentData.copyWith( displaySpeed: 'GPS ERR', displayHeading: 'GPS ERR' @@ -151,12 +117,12 @@ class GpsDataManager { _isInitialized = true; // Mark as initialized even if failed return; } - - print('[GpsDataManager] ✅ GPS manager initialized successfully'); + + Logger.info('GPS manager initialized successfully', 'GpsDataManager'); } 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(); @@ -165,18 +131,18 @@ class GpsDataManager { final double speed = speedValid ? rawSpeed : 0.0; 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; // 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 displayHeading = headingValid ? GpsService.formatHeading(heading) : _currentData.displayHeading; - print('[GpsDataManager] 🔄 Processed: displaySpeed="$displaySpeed", displayHeading="$displayHeading"'); + Logger.debug('Processed: displaySpeed="$displaySpeed", displayHeading="$displayHeading"', 'GpsDataManager'); final processedData = ProcessedGpsData( speed: speed, @@ -205,7 +171,7 @@ class GpsDataManager { void _scheduleStaleDataTimer() { _staleDataTimer?.cancel(); - _staleDataTimer = Timer(_staleDataThreshold, _handleStaleDataTimeout); + _staleDataTimer = Timer(GpsConfig.STALE_DATA_THRESHOLD, _handleStaleDataTimeout); } void _handleStaleDataTimeout() { @@ -215,10 +181,10 @@ class GpsDataManager { 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 : '--'; - 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( displaySpeed: '--', @@ -232,10 +198,10 @@ class GpsDataManager { } void _updateData(ProcessedGpsData newData) { - print('[GpsDataManager] 📤 Broadcasting data: ${newData.displaySpeed} ${newData.displayHeading}'); + Logger.debug('Broadcasting data: ${newData.displaySpeed} ${newData.displayHeading}', 'GpsDataManager'); _currentData = newData; _dataController.add(newData); - print('[GpsDataManager] ✅ Data broadcast complete'); + Logger.debug('Data broadcast complete', 'GpsDataManager'); } // Format speed for specific unit (used by UI) @@ -247,17 +213,17 @@ class GpsDataManager { } void dispose() { - print('[GpsDataManager] 🛑 Disposing GPS manager...'); + Logger.info('Disposing GPS manager...', 'GpsDataManager'); if (_gpsSubscription != null) { - print('[GpsDataManager] 🛑 Cancelling GPS subscription...'); + Logger.debug('Cancelling GPS subscription...', 'GpsDataManager'); _gpsSubscription?.cancel(); _gpsSubscription = null; } - print('[GpsDataManager] 🛑 Closing data controller...'); + Logger.debug('Closing data controller...', 'GpsDataManager'); _staleDataTimer?.cancel(); _staleDataTimer = null; _dataController.close(); _isInitialized = false; - print('[GpsDataManager] 🛑 GPS manager disposed'); + Logger.info('GPS manager disposed', 'GpsDataManager'); } } diff --git a/lib/speed_units.dart b/lib/speed_units.dart deleted file mode 100644 index 914b731..0000000 --- a/lib/speed_units.dart +++ /dev/null @@ -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]; - } -} \ No newline at end of file