diff --git a/TODO.md b/TODO.md index 81e1e19..0f79d17 100644 --- a/TODO.md +++ b/TODO.md @@ -25,19 +25,36 @@ ## 🔄 REMAINING -**Overlay Interaction Issues:** -- [ ] Fix overlay tap to bring main app to front - currently non-functional -- [ ] Fix overlay long press close functionality - currently non-functional +**Overlay Interaction Issues (Partially Working):** +- [x] Fixed overlay unit display syntax and layout issues +- [x] Implemented overlay status monitoring with FlutterOverlayWindow.isActive() +- [x] Fixed duplicate dispose() method causing widget lifecycle issues +- [x] Removed tap-to-close to prevent accidental closure from dragging +- [x] Enhanced debugging for overlay creation/disposal tracking +- [ ] **Long press close on second+ overlays non-functional** - gestures work on first overlay only +- [x] Tap brings main app to foreground (when bidirectional communication works) +- [ ] Bidirectional overlay communication unreliable (shareData() hangs or fails silently) + +**Root Cause Analysis:** +- HapticFeedback.* calls hang indefinitely in overlay context (platform services unavailable) +- FlutterOverlayWindow.shareData() from overlay to main app is fundamentally broken +- Overlay status monitoring via .isActive() works reliably +- Gesture detection works on first overlay but fails on subsequent overlays + +**Technical Approach:** +- Fire-and-forget communication pattern implemented +- Non-blocking overlay interactions with fallback mechanisms +- Comprehensive debug logging for troubleshooting **Critical Issues:** -- [ ] Fix Android APK installation - both debug and release builds marked as invalid by Android +- ✅ Fix Android APK installation - both debug and release builds marked as invalid by Android - [ ] Update packages to latest compatible versions **Future Features:** - [ ] Desktop widget -- [ ] Background color customization +- [ ] Background color customization - [ ] User-defined layout customization -## 🎯 STATUS: v2.2.2 Background Persistence Enhanced +## 🎯 STATUS: v2.2.3-wip Overlay Interactions Partially Fixed -Core floating window functionality and data flow working well. Overlay displays updates smoothly with 2Hz cached GPS data. Background persistence significantly improved. Overlay gesture interactions need attention but main UI fully functional. \ No newline at end of file +Overlay functionality **somewhat working**: first overlay responds to gestures, subsequent overlays display correctly but gesture detection fails. Bidirectional communication between overlay and main app proven unreliable. Status monitoring approach implemented as workaround. Main speedometer fully functional. \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 4e291e9..2e47399 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index cda48e7..7a3a6b5 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index d000e0c..301bc37 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 81e9e1d..65d6171 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 5e2dca6..118f662 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/icon/speedo.png b/icon/speedo.png index 8cc072a..701fa41 100644 Binary files a/icon/speedo.png and b/icon/speedo.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 2ea599f..8142363 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 3121ff1..717a09f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 755a9e4..d717e51 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 7f37211..bd030a8 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 7969bd1..2a94d9d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 4126a91..908815e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 5825a37..0c3438d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 755a9e4..d717e51 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 0f57ddc..42d5624 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 73c2010..7eb2f31 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 0dda2b4..085ffaf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index 195f501..fc7fe21 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 2486b51..78c38a1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 7680ee4..dc01195 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 73c2010..7eb2f31 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 91e3b4b..717a69b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 4e291e9..2e47399 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index 81e9e1d..65d6171 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 1e3fcae..f56b850 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 10c4c63..2079118 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 536c73c..ac33307 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/color_themes.dart b/lib/color_themes.dart index bd50989..26e680e 100644 --- a/lib/color_themes.dart +++ b/lib/color_themes.dart @@ -49,7 +49,7 @@ class ColorThemes { 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, 255, 157, 65), + headingText: Color.fromARGB(255, 221, 118, 0), unitText: Color.fromARGB(255, 98, 124, 118), ), ]; diff --git a/lib/main.dart b/lib/main.dart index 6d66817..fc640f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,6 +76,8 @@ class _SpeedometerScreenState extends State with WidgetsBindi String _errorMessage = ''; bool _isInBackground = false; bool _isOverlayActive = false; + bool _tapCloseRequested = false; + Timer? _tapCloseTimer; // Single source of truth for overlay sizing Map _getOverlaySize() { @@ -100,6 +102,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi Timer? _backgroundHeartbeatTimer; Timer? _overlayDataPushTimer; + Timer? _overlayStatusCheckTimer; @override void initState() { @@ -121,23 +124,49 @@ class _SpeedometerScreenState extends State with WidgetsBindi } customDebugPrint('[Main] 🎧 Starting overlay message listener'); - _overlaySubscription = FlutterOverlayWindow.overlayListener.listen((data) { - customDebugPrint('[Main] 📡 Received message from overlay: $data'); - if (data is Map) { - switch (data['action']) { - case 'overlayClosed': - customDebugPrint('[Main] ✅ Overlay close notification received'); - _handleOverlayClose(); - break; - case 'bringToFront': - customDebugPrint('[Main] 🚀 Bring to front request received'); - _bringAppToFront(); - break; - default: - customDebugPrint('[Main] ⚠️ Unknown overlay action: ${data['action']}'); + _overlaySubscription = FlutterOverlayWindow.overlayListener.listen( + (data) { + customDebugPrint('[Main] 📡 RAW message from overlay: $data (type: ${data.runtimeType})'); + try { + if (data is Map) { + final action = data['action']; + customDebugPrint('[Main] 🎯 Processing overlay action: "$action"'); + switch (action) { + case 'overlayClosed': + customDebugPrint('[Main] ✅ Processing overlay close notification'); + _handleOverlayClose(); + customDebugPrint('[Main] ✅ Overlay close processing completed'); + break; + case 'longPressClose': + customDebugPrint('[Main] 🔴 Long press close signal received - will bring to foreground on close'); + _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), () { + customDebugPrint('[Main] ⏰ Long press close timer expired - resetting flag'); + _tapCloseRequested = false; + }); + break; + case 'bringToFront': + customDebugPrint('[Main] 🚀 Processing bring to front request from overlay'); + _bringAppToFront(); + customDebugPrint('[Main] 🚀 Bring to front processing completed'); + break; + default: + customDebugPrint('[Main] ⚠️ Unknown overlay action: "$action"'); + } + } else { + customDebugPrint('[Main] ⚠️ Message is not a Map: $data'); + } + } catch (e, stackTrace) { + customDebugPrint('[Main] ❌ Error processing overlay message: $e'); + customDebugPrint('[Main] 📚 Stack trace: $stackTrace'); } - } - }); + }, + onError: (error) { + customDebugPrint('[Main] ❌ Overlay listener error: $error'); + }, + ); } void _stopListeningToOverlayMessages() { @@ -146,11 +175,55 @@ class _SpeedometerScreenState extends State with WidgetsBindi _overlaySubscription = null; } - void _handleOverlayClose() { + void _startOverlayStatusCheck() { + // Cancel any existing timer first + _overlayStatusCheckTimer?.cancel(); + + _overlayStatusCheckTimer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async { + if (_isOverlayActive) { + try { + final isActive = await FlutterOverlayWindow.isActive(); + if (!isActive) { + customDebugPrint('[Main] 🔍 Overlay status check: overlay is no longer active'); + timer.cancel(); // Cancel the timer before handling close + _handleOverlayClose(); + } + } catch (e) { + customDebugPrint('[Main] ❌ Overlay status check failed: $e'); + } + } else { + // If overlay is not supposed to be active, cancel the timer + customDebugPrint('[Main] 🔍 Overlay not active - stopping status check'); + timer.cancel(); + } + }); + customDebugPrint('[Main] 🔍 Started overlay status monitoring'); + } + + void _handleOverlayClose({bool bringToForeground = false}) { + // Check if this was a long-press close based on recent signal + final shouldBringToFront = bringToForeground || _tapCloseRequested; + + customDebugPrint('[Main] 🔄 Handling overlay close - bringToForeground: $bringToForeground, longPressRequested: $_tapCloseRequested'); + + // Cancel timers and reset flag + _tapCloseTimer?.cancel(); + _overlayStatusCheckTimer?.cancel(); + _tapCloseRequested = false; + setState(() { _isOverlayActive = false; }); _stopListeningToOverlayMessages(); // Clean up listener when overlay closes + + // Bring app to foreground on long press close, otherwise quiet close + if (shouldBringToFront) { + customDebugPrint('[Main] 🚀 Bringing app to foreground after long press close'); + _bringAppToFront(); + } else { + customDebugPrint('[Main] 🤫 Overlay closed without bring-to-front request'); + } + customDebugPrint('[Main] ✅ Overlay close handling completed'); } @@ -181,20 +254,34 @@ class _SpeedometerScreenState extends State with WidgetsBindi }); } - void _bringAppToFront() { + void _bringAppToFront() async { + customDebugPrint('[Main] 🚀 _bringAppToFront() called - attempting to bring app to foreground'); try { // Use proper Android method to bring app to foreground BgLauncher.bringAppToForeground(); - customDebugPrint('[Main] ✅ App brought to front via BgLauncher'); + customDebugPrint('[Main] ✅ App successfully brought to front via BgLauncher'); + // Update app state to reflect foreground status if (mounted) { - setState(() {}); + setState(() { + _isInBackground = false; + }); + customDebugPrint('[Main] 🔄 App state updated - _isInBackground set to false'); } } catch (e) { - customDebugPrint('[Main] ❌ Failed to bring app to front: $e'); - // Fallback to simple state update (original method) - if (mounted) { - setState(() {}); + customDebugPrint('[Main] ❌ BgLauncher failed to bring app to front: $e'); + + // Enhanced fallback: try alternative approach + try { + customDebugPrint('[Main] 🔄 Attempting fallback method...'); + if (mounted) { + setState(() { + _isInBackground = false; + }); + } + customDebugPrint('[Main] ⚠️ Using fallback - state updated but may not bring to actual foreground'); + } catch (fallbackError) { + customDebugPrint('[Main] ❌ Fallback method also failed: $fallbackError'); } } } @@ -224,6 +311,8 @@ class _SpeedometerScreenState extends State with WidgetsBindi _overlaySubscription?.cancel(); _backgroundHeartbeatTimer?.cancel(); _overlayDataPushTimer?.cancel(); + _overlayStatusCheckTimer?.cancel(); + _tapCloseTimer?.cancel(); WakelockPlus.disable(); customDebugPrint('[Main] 🛑 Main app disposed - all subscriptions and timers canceled'); super.dispose(); @@ -415,6 +504,7 @@ class _SpeedometerScreenState extends State with WidgetsBindi // Send complete display data to overlay including screen size final displaySpeed = _currentUnit.convert(_speed); + final unitText = _currentUnit.label.toString(); final speedText = displaySpeed < 1.0 ? '--' : displaySpeed.toInt().toString(); final headingText = GpsService.formatHeading(_heading); @@ -422,11 +512,12 @@ class _SpeedometerScreenState extends State with WidgetsBindi customDebugPrint(' speedText: "$speedText"'); customDebugPrint(' headingText: "$headingText"'); customDebugPrint(' direction: ${_heading.toStringAsFixed(1)}°'); - customDebugPrint(' unit: ${_currentUnit.label}, theme: $_currentThemeIndex'); + customDebugPrint(' unit: $unitText, theme: $_currentThemeIndex'); FlutterOverlayWindow.shareData({ 'action': 'updateDisplay', 'speedText': speedText, + 'unitText': unitText, 'headingText': headingText, 'heading': _heading, 'unitIndex': _currentUnit.index, @@ -493,6 +584,9 @@ class _SpeedometerScreenState extends State with WidgetsBindi // Start listening to overlay messages after state is set _startListeningToOverlayMessages(); + // Start periodic overlay status check since message-based detection is unreliable + _startOverlayStatusCheck(); + // Force UI update if (mounted) setState(() {}); } catch (e) { @@ -863,6 +957,7 @@ class OverlaySpeedometer extends StatefulWidget { class _OverlaySpeedometerState extends State { String _speedText = '--'; + String _unitText = 'km/h'; String _headingText = 'N/A'; double _heading = -1.0; int _currentThemeIndex = 0; @@ -873,10 +968,16 @@ class _OverlaySpeedometerState extends State { @override void initState() { super.initState(); - customDebugPrint('[Overlay] 🟢 CREATED - Floating window initialized'); + customDebugPrint('[Overlay] 🟢 CREATED - Floating window initialized (${DateTime.now().millisecondsSinceEpoch})'); _getSystemScreenSize(); _listenToMainAppMessages(); } + + @override + void dispose() { + customDebugPrint('[Overlay] 🔴 DISPOSING - Overlay widget disposed (${DateTime.now().millisecondsSinceEpoch})'); + super.dispose(); + } void _getSystemScreenSize() { try { @@ -910,6 +1011,7 @@ class _OverlaySpeedometerState extends State { 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; @@ -936,20 +1038,16 @@ class _OverlaySpeedometerState extends State { }); } - @override - void dispose() { - customDebugPrint('[Overlay] 🔴 DESTROYED - Floating window disposed'); - // No GPS subscription to cancel - overlay is display-only - super.dispose(); - } @override Widget build(BuildContext context) { + final buildTimestamp = DateTime.now().millisecondsSinceEpoch; 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()}'); return Material( @@ -976,19 +1074,42 @@ class _OverlaySpeedometerState extends State { Expanded( flex: 1, child: Container( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(6), child: Center( - child: FittedBox( - fit: BoxFit.contain, - child: Text( - (_speedText == "--" ? "0" : _speedText), - style: TextStyle( - fontSize: fontSize, - fontWeight: FontWeight.w300, - color: currentTheme.speedText, - fontFamily: 'DIN1451Alt', + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + flex: 3, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + (_speedText == "--" ? "0" : _speedText), + style: TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.w300, + color: currentTheme.speedText, + fontFamily: 'DIN1451Alt', + ), + ), + ), ), - ), + Flexible( + flex: 1, + child: FittedBox( + fit: BoxFit.contain, + child: Text( + _unitText, + style: TextStyle( + fontSize: fontSize * 0.6, + fontWeight: FontWeight.w300, + color: currentTheme.unitText, + fontFamily: 'DIN1451Alt', + ), + ), + ), + ), + ], ), ), ), @@ -1038,40 +1159,80 @@ class _OverlaySpeedometerState extends State { // Transparent full-overlay gesture detection layer Positioned.fill( child: GestureDetector( + behavior: HitTestBehavior.translucent, // Ensure gestures are captured + onPanStart: (details) { + customDebugPrint('[Overlay] 🖐️ Pan start detected - gestures are working!'); + }, onLongPress: () async { - customDebugPrint('[Overlay] 🔴 Long press detected - closing overlay'); - // Add haptic feedback for long press + final timestamp = DateTime.now().millisecondsSinceEpoch; + customDebugPrint('[Overlay] 🔴 Long press detected - closing overlay and bringing main app to front ($timestamp)'); try { - await HapticFeedback.mediumImpact(); - customDebugPrint('[Overlay] 📳 Haptic feedback triggered'); - } catch (e) { - customDebugPrint('[Overlay] ❌ Haptic feedback failed: $e'); - } - // Notify main app before closing - try { - // can we base this on handleOverlayClose() or _closeFloatingWindow()? - await FlutterOverlayWindow.shareData({ - 'action': 'overlayClosed', + // Skip haptic feedback in overlay - platform services not available + customDebugPrint('[Overlay] 📳 Skipping haptic feedback (overlay context limitation)'); + + // 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'); + }).catchError((error) { + customDebugPrint('[Overlay] ❌ Long press close signal failed (expected): $error'); }); - customDebugPrint('[Overlay] 📤 Close notification sent'); - } catch (e) { - customDebugPrint('[Overlay] ❌ Close notification failed: $e'); + + // Small delay to give signal a chance + await Future.delayed(const Duration(milliseconds: 50)); + + // Close the overlay + customDebugPrint('[Overlay] 🔴 Attempting to close overlay...'); + await FlutterOverlayWindow.closeOverlay(); + customDebugPrint('[Overlay] ✅ Overlay closed successfully via long press'); + + } catch (e, stackTrace) { + customDebugPrint('[Overlay] ❌ Long press close failed: $e'); + customDebugPrint('[Overlay] 📚 Stack trace: $stackTrace'); + // Fallback: try to close anyway + try { + customDebugPrint('[Overlay] 🔄 Attempting fallback close...'); + await FlutterOverlayWindow.closeOverlay(); + customDebugPrint('[Overlay] ✅ Fallback close succeeded'); + } catch (fallbackError) { + customDebugPrint('[Overlay] ❌ Fallback close also failed: $fallbackError'); + } } - FlutterOverlayWindow.closeOverlay(); }, onTap: () async { - customDebugPrint('[Overlay] 👆 Tap detected - bringing main app to front'); + final timestamp = DateTime.now().millisecondsSinceEpoch; + customDebugPrint('[Overlay] 👆 Tap detected - bringing main app to foreground ($timestamp)'); try { - await FlutterOverlayWindow.shareData({ + // Try to signal main app to come to foreground (non-blocking) + customDebugPrint('[Overlay] 📤 Attempting to signal bring-to-front...'); + FlutterOverlayWindow.shareData({ 'action': 'bringToFront', + }).catchError((error) { + customDebugPrint('[Overlay] ❌ Bring-to-front signal failed (expected): $error'); }); - customDebugPrint('[Overlay] 📤 Bring-to-front message sent'); - } catch (e) { - customDebugPrint('[Overlay] ❌ Bring-to-front message failed: $e'); + + customDebugPrint('[Overlay] ✅ Tap gesture completed (overlay stays open)'); + + } catch (e, stackTrace) { + customDebugPrint('[Overlay] ❌ Tap gesture failed: $e'); + customDebugPrint('[Overlay] 📚 Stack trace: $stackTrace'); } }, child: Container( + width: double.infinity, + height: double.infinity, color: Colors.transparent, + child: Center( + child: Text( + 'GESTURE', + style: TextStyle( + color: Colors.transparent, + fontSize: 1, + ), + ), + ), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 2edbfb5..47ccff7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,4 +45,5 @@ flutter: flutter_launcher_icons: android: true ios: true - image_path: "icon/speedo.png" \ No newline at end of file + image_path: "icon/speedo.png" + adaptive_icon_foreground: "icon/speedo.png" \ No newline at end of file