diff --git a/lib/main.dart b/lib/main.dart index 3d0d3cb..e469b3e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ void main() { // Entry point for overlay window @pragma("vm:entry-point") void overlayMain() { + print('[Overlay] 🚀 overlayMain() called'); runApp(const MaterialApp( debugShowCheckedModeBanner: false, home: OverlaySpeedometer(), @@ -58,6 +59,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi StreamSubscription? _positionSubscription; String _errorMessage = ''; bool _isInBackground = false; + bool _isOverlayActive = false; @override void initState() { @@ -149,49 +151,93 @@ class _SpeedometerScreenState extends State with WidgetsBindi _heading = position.heading; _errorMessage = ''; }); + + // Push display data to overlay if active + _pushDataToOverlay(); + } + + void _pushDataToOverlay() { + // Send complete display data to overlay including screen size + final displaySpeed = _currentUnit.convert(_speed); + final speedText = displaySpeed < 1.0 ? '--' : displaySpeed.toInt().toString(); + final headingText = GpsService.formatHeading(_heading); + + // Get system screen size to share with overlay + final window = WidgetsBinding.instance.platformDispatcher.views.first; + final systemSize = window.physicalSize / window.devicePixelRatio; + + print('[Main] 📤 SENDING to overlay:'); + print(' speedText: "$speedText"'); + print(' headingText: "$headingText"'); + print(' direction: ${_heading.toStringAsFixed(1)}°'); + print(' unit: ${_currentUnit.label}, theme: $_currentThemeIndex'); + print(' systemScreen: ${systemSize.width.round()}x${systemSize.height.round()}'); + + // Calculate overlay size the same way as when launching + final overlayWidth = (systemSize.width * 0.4).round(); + final overlayHeight = (overlayWidth * 0.5).round(); + + FlutterOverlayWindow.shareData({ + 'action': 'updateDisplay', + 'speedText': speedText, + 'headingText': headingText, + 'heading': _heading, + 'unitIndex': _currentUnit.index, + 'themeIndex': _currentThemeIndex, + 'systemScreenWidth': systemSize.width, + 'systemScreenHeight': systemSize.height, + 'overlayWidth': overlayWidth.clamp(280, 480).toDouble(), + 'overlayHeight': overlayHeight.clamp(140, 240).toDouble(), + }); } void _cycleUnit() { setState(() { _currentUnit = _currentUnit.next; }); - // Share unit change with overlay if active - FlutterOverlayWindow.shareData({ - 'action': 'updateSettings', - 'unitIndex': _currentUnit.index, - 'themeIndex': _currentThemeIndex, - }); + // Push updated display data to overlay + _pushDataToOverlay(); } void _cycleTheme() { setState(() { _currentThemeIndex = ColorThemes.getNextThemeIndex(_currentThemeIndex); }); - // Share theme change with overlay if active - FlutterOverlayWindow.shareData({ - 'action': 'updateSettings', - 'themeIndex': _currentThemeIndex, - 'unitIndex': _currentUnit.index, - }); + // Push updated display data to overlay + _pushDataToOverlay(); } Future _showFloatingWindow() async { try { - // Get screen dimensions for proportional sizing - final screenSize = MediaQuery.of(context).size; - final overlayWidth = (screenSize.width * 0.4).round(); // 40% of screen width + // Get actual system screen resolution for proportional sizing + final window = WidgetsBinding.instance.platformDispatcher.views.first; + final systemSize = window.physicalSize / window.devicePixelRatio; + final overlayWidth = (systemSize.width * 0.4).round(); // 40% of system screen width final overlayHeight = (overlayWidth * 0.5).round(); // 2:1 aspect ratio - // Share current unit and theme with overlay + // Debug info for development + // print('[Main] System resolution: ${systemSize.width.round()}x${systemSize.height.round()}'); + // print('[Main] Calculated overlay size: ${overlayWidth}x${overlayHeight}'); + + // Send initial display data to overlay including screen size + final displaySpeed = _currentUnit.convert(_speed); + final speedText = displaySpeed < 1.0 ? '--' : displaySpeed.toInt().toString(); + final headingText = GpsService.formatHeading(_heading); + await FlutterOverlayWindow.shareData({ - 'action': 'updateSettings', + 'action': 'updateDisplay', + 'speedText': speedText, + 'headingText': headingText, + 'heading': _heading, 'unitIndex': _currentUnit.index, 'themeIndex': _currentThemeIndex, - 'screenWidth': screenSize.width, - 'screenHeight': screenSize.height, + 'systemScreenWidth': systemSize.width, + 'systemScreenHeight': systemSize.height, + 'overlayWidth': overlayWidth.clamp(280, 480).toDouble(), + 'overlayHeight': overlayHeight.clamp(140, 240).toDouble(), }); - // Show the overlay with hybrid sizing - proportional but with reasonable limits + // Show the overlay with proportional sizing based on system resolution await FlutterOverlayWindow.showOverlay( enableDrag: true, overlayTitle: "GPS Speedometer", @@ -199,20 +245,34 @@ class _SpeedometerScreenState extends State with WidgetsBindi flag: OverlayFlag.defaultFlag, visibility: NotificationVisibility.visibilityPublic, positionGravity: PositionGravity.none, - // Use proportional sizing with minimum reasonable dimensions - width: overlayWidth.clamp(280, 400), - height: overlayHeight.clamp(140, 300), + // Use system-based proportional sizing with reasonable limits + width: overlayWidth.clamp(280, 480), + height: overlayHeight.clamp(140, 240), ); + + print('[Main] 🟢 OVERLAY LAUNCHED - Size: ${overlayWidth.clamp(280, 480)}x${overlayHeight.clamp(140, 240)}'); + setState(() { + _isOverlayActive = true; + }); } catch (e) { // Silent error handling - floating window issues shouldn't crash main app } } Future _closeFloatingWindow() async { + if (!_isOverlayActive) { + print('[Main] ⚠️ No overlay to close'); + return; + } + try { await FlutterOverlayWindow.closeOverlay(); + print('[Main] 🔴 OVERLAY CLOSED - Requested from main app'); + setState(() { + _isOverlayActive = false; + }); } catch (e) { - // Silent error handling - floating window issues shouldn't crash main app + print('[Main] ❌ Error closing overlay: $e'); } } @@ -354,8 +414,18 @@ class _SpeedometerScreenState extends State with WidgetsBindi Expanded( flex: 32, child: GestureDetector( - onTap: _showFloatingWindow, // Tap navigation icon to show floating window - onLongPress: _closeFloatingWindow, // Long press to close floating window + onTap: () { + print('[Main] 🖱️ Navigation area tapped - overlay active: $_isOverlayActive'); + if (_isOverlayActive) { + _closeFloatingWindow(); + } else { + _showFloatingWindow(); + } + }, + onLongPress: () { + print('[Main] 🖱️ Navigation area long-pressed - overlay active: $_isOverlayActive'); + _closeFloatingWindow(); + }, child: Container( width: double.infinity, color: Colors.transparent, @@ -427,7 +497,11 @@ class _SpeedometerScreenState extends State with WidgetsBindi ) else ...[ Flexible( - flex: 4, + flex: 1, + child: FittedBox(), + ), + Flexible( + flex: 20, child: GestureDetector( onTap: _cycleTheme, child: FittedBox( @@ -437,7 +511,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi ), ), Flexible( - flex: 1, + flex: 7, child: GestureDetector( onTap: _cycleUnit, child: FittedBox( @@ -453,6 +527,10 @@ class _SpeedometerScreenState extends State with WidgetsBindi ), ), ), + Flexible( + flex: 5, + child: FittedBox(), + ), ], ], ), @@ -462,8 +540,18 @@ class _SpeedometerScreenState extends State with WidgetsBindi Expanded( flex: 33, child: GestureDetector( - onTap: _showFloatingWindow, - onLongPress: _closeFloatingWindow, // Long press to close floating window + onTap: () { + print('[Main] 🖱️ Navigation area tapped (landscape) - overlay active: $_isOverlayActive'); + if (_isOverlayActive) { + _closeFloatingWindow(); + } else { + _showFloatingWindow(); + } + }, + onLongPress: () { + print('[Main] 🖱️ Navigation area long-pressed (landscape) - overlay active: $_isOverlayActive'); + _closeFloatingWindow(); + }, child: Container( height: double.infinity, color: Colors.transparent, @@ -517,83 +605,95 @@ class OverlaySpeedometer extends StatefulWidget { } class _OverlaySpeedometerState extends State { - double _speed = 0.0; + String _speedText = '--'; + String _headingText = 'N/A'; double _heading = -1.0; - SpeedUnit _currentUnit = SpeedUnit.kmh; int _currentThemeIndex = 0; - StreamSubscription? _positionSubscription; - double _screenWidth = 400; + double _systemScreenWidth = 400; + double _overlayWidth = 280; + double _overlayHeight = 140; @override void initState() { super.initState(); + print('[Overlay] 🟢 CREATED - Floating window initialized'); + _getSystemScreenSize(); _listenToMainAppMessages(); - _initializeGps(); + } + + void _getSystemScreenSize() { + try { + // Get actual system screen resolution, not just app window size + final window = WidgetsBinding.instance.platformDispatcher.views.first; + final physicalSize = window.physicalSize; + final devicePixelRatio = window.devicePixelRatio; + final systemSize = physicalSize / devicePixelRatio; + + print('[Overlay] 🔍 Screen detection:'); + print(' physicalSize: ${physicalSize.width.round()}x${physicalSize.height.round()}'); + print(' devicePixelRatio: $devicePixelRatio'); + print(' calculated systemSize: ${systemSize.width.round()}x${systemSize.height.round()}'); + + setState(() { + _systemScreenWidth = systemSize.width; + }); + + print('[Overlay] ✅ _systemScreenWidth set to: ${_systemScreenWidth.round()}'); + } catch (e) { + print('[Overlay] ❌ Error getting system screen size: $e'); + // Fallback to reasonable default + setState(() { + _systemScreenWidth = 400; + }); + } } void _listenToMainAppMessages() { FlutterOverlayWindow.overlayListener.listen((data) { - if (data is Map) { - if (data.containsKey('unitIndex')) { - setState(() { - _currentUnit = SpeedUnit.values[data['unitIndex'] ?? 0]; - }); - } - if (data.containsKey('themeIndex')) { - setState(() { - _currentThemeIndex = data['themeIndex'] ?? 0; - }); - } - if (data.containsKey('screenWidth')) { - setState(() { - _screenWidth = data['screenWidth']?.toDouble() ?? 400; - }); - } + if (data is Map && data['action'] == 'updateDisplay') { + setState(() { + _speedText = data['speedText'] ?? '--'; + _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; + } + }); + + print('[Overlay] 📡 UPDATE received:'); + print(' speedText: "$_speedText"'); + print(' headingText: "$_headingText"'); + print(' direction: ${_heading.toStringAsFixed(1)}°'); + print(' themeIndex: $_currentThemeIndex'); + print(' systemScreenWidth: ${_systemScreenWidth.round()}'); + print(' overlaySize from main: ${_overlayWidth.round()}x${_overlayHeight.round()}'); } }); } - Future _initializeGps() async { - try { - final serviceEnabled = await GpsService.isLocationServiceEnabled(); - if (!serviceEnabled) return; - - final permissionGranted = await GpsService.requestPermissions(); - if (!permissionGranted) return; - - _positionSubscription = GpsService.positionStream.listen( - (position) { - setState(() { - _speed = position.speed; - _heading = position.heading; - }); - }, - onError: (error) { - // Silent error handling for overlay - }, - ); - } catch (e) { - // Silent error handling for overlay - } - } - @override void dispose() { - _positionSubscription?.cancel(); + print('[Overlay] 🔴 DESTROYED - Floating window disposed'); + // No GPS subscription to cancel - overlay is display-only super.dispose(); } @override Widget build(BuildContext context) { final currentTheme = ColorThemes.getTheme(_currentThemeIndex); - final displaySpeed = _currentUnit.convert(_speed); - // Per NOTES.txt: Speed section shows only integral part (no decimal, no unit) - final speedText = displaySpeed < 1.0 ? '--' : displaySpeed.toInt().toString(); - // Calculate proportional dimensions - final overlayWidth = (_screenWidth * 0.4); - final overlayHeight = (overlayWidth * 0.5); - final fontSize = (overlayWidth * 0.2); // Font size proportional to width + // Use overlay dimensions provided by main app (not calculated locally) + final fontSize = (_overlayWidth * 0.2); // Font size proportional to actual overlay width + + print('[Overlay] 📐 Window size: ${_overlayWidth.round()}x${_overlayHeight.round()}, fontSize: ${fontSize.round()}'); return Material( color: Colors.transparent, @@ -603,21 +703,10 @@ class _OverlaySpeedometerState extends State { height: double.infinity, child: Container( decoration: BoxDecoration( - // Use proper color opacity without deprecated properties - color: Color.fromARGB( - ((currentTheme.background.a * 255.0).round() * 0.85).round(), - (currentTheme.background.r * 255.0).round(), - (currentTheme.background.g * 255.0).round(), - (currentTheme.background.b * 255.0).round(), - ), + color: currentTheme.background.withOpacity(0.85), borderRadius: BorderRadius.circular(12), border: Border.all( - color: Color.fromARGB( - ((currentTheme.speedText.a * 255.0).round() * 0.3).round(), - (currentTheme.speedText.r * 255.0).round(), - (currentTheme.speedText.g * 255.0).round(), - (currentTheme.speedText.b * 255.0).round(), - ), + color: currentTheme.speedText.withOpacity(0.3), width: 1 ), ), @@ -635,7 +724,7 @@ class _OverlaySpeedometerState extends State { child: FittedBox( fit: BoxFit.contain, child: Text( - speedText, + _speedText, style: TextStyle( fontSize: fontSize, fontWeight: FontWeight.w300, @@ -674,7 +763,7 @@ class _OverlaySpeedometerState extends State { child: FittedBox( fit: BoxFit.contain, child: Text( - GpsService.formatHeading(_heading), + _headingText, style: TextStyle( fontSize: fontSize * 0.4, color: currentTheme.headingText, @@ -696,21 +785,20 @@ class _OverlaySpeedometerState extends State { child: GestureDetector( onTap: () => FlutterOverlayWindow.closeOverlay(), child: Container( - width: overlayHeight * 0.2, - height: overlayHeight * 0.2, + width: _overlayHeight * 0.2, + height: _overlayHeight * 0.2, decoration: BoxDecoration( - // Avoid deprecated withOpacity - use Color.fromARGB instead - color: const Color.fromARGB(153, 244, 67, 54), // red with 60% opacity - borderRadius: BorderRadius.circular(overlayHeight * 0.1), + color: Colors.red.withOpacity(0.6), + borderRadius: BorderRadius.circular(_overlayHeight * 0.1), border: Border.all( - color: const Color.fromARGB(204, 255, 255, 255), // white with 80% opacity + color: Colors.white.withOpacity(0.8), width: 1 ), ), child: Icon( Icons.close, - color: const Color.fromARGB(229, 255, 255, 255), // white with 90% opacity - size: overlayHeight * 0.15, + color: Colors.white.withOpacity(0.9), + size: _overlayHeight * 0.15, ), ), ),