Unsuccessful fix on floating window interactions & new icons
@@ -25,19 +25,36 @@
|
|||||||
|
|
||||||
## 🔄 REMAINING
|
## 🔄 REMAINING
|
||||||
|
|
||||||
**Overlay Interaction Issues:**
|
**Overlay Interaction Issues (Partially Working):**
|
||||||
- [ ] Fix overlay tap to bring main app to front - currently non-functional
|
- [x] Fixed overlay unit display syntax and layout issues
|
||||||
- [ ] Fix overlay long press close functionality - currently non-functional
|
- [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:**
|
**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
|
- [ ] Update packages to latest compatible versions
|
||||||
|
|
||||||
**Future Features:**
|
**Future Features:**
|
||||||
- [ ] Desktop widget
|
- [ ] Desktop widget
|
||||||
- [ ] Background color customization
|
- [ ] Background color customization
|
||||||
- [ ] User-defined layout 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.
|
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.
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -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":"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"}}
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 6.4 KiB |
@@ -49,7 +49,7 @@ class ColorThemes {
|
|||||||
background: Color.fromARGB(255, 228, 235, 238),
|
background: Color.fromARGB(255, 228, 235, 238),
|
||||||
speedText: Color.fromARGB(255, 85, 99, 112),
|
speedText: Color.fromARGB(255, 85, 99, 112),
|
||||||
speedTextSub: Color.fromARGB(255, 100, 100, 131),
|
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),
|
unitText: Color.fromARGB(255, 98, 124, 118),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
String _errorMessage = '';
|
String _errorMessage = '';
|
||||||
bool _isInBackground = false;
|
bool _isInBackground = false;
|
||||||
bool _isOverlayActive = false;
|
bool _isOverlayActive = false;
|
||||||
|
bool _tapCloseRequested = false;
|
||||||
|
Timer? _tapCloseTimer;
|
||||||
|
|
||||||
// Single source of truth for overlay sizing
|
// Single source of truth for overlay sizing
|
||||||
Map<String, int> _getOverlaySize() {
|
Map<String, int> _getOverlaySize() {
|
||||||
@@ -100,6 +102,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
|
|
||||||
Timer? _backgroundHeartbeatTimer;
|
Timer? _backgroundHeartbeatTimer;
|
||||||
Timer? _overlayDataPushTimer;
|
Timer? _overlayDataPushTimer;
|
||||||
|
Timer? _overlayStatusCheckTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -121,23 +124,49 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
}
|
}
|
||||||
|
|
||||||
customDebugPrint('[Main] 🎧 Starting overlay message listener');
|
customDebugPrint('[Main] 🎧 Starting overlay message listener');
|
||||||
_overlaySubscription = FlutterOverlayWindow.overlayListener.listen((data) {
|
_overlaySubscription = FlutterOverlayWindow.overlayListener.listen(
|
||||||
customDebugPrint('[Main] 📡 Received message from overlay: $data');
|
(data) {
|
||||||
if (data is Map) {
|
customDebugPrint('[Main] 📡 RAW message from overlay: $data (type: ${data.runtimeType})');
|
||||||
switch (data['action']) {
|
try {
|
||||||
case 'overlayClosed':
|
if (data is Map) {
|
||||||
customDebugPrint('[Main] ✅ Overlay close notification received');
|
final action = data['action'];
|
||||||
_handleOverlayClose();
|
customDebugPrint('[Main] 🎯 Processing overlay action: "$action"');
|
||||||
break;
|
switch (action) {
|
||||||
case 'bringToFront':
|
case 'overlayClosed':
|
||||||
customDebugPrint('[Main] 🚀 Bring to front request received');
|
customDebugPrint('[Main] ✅ Processing overlay close notification');
|
||||||
_bringAppToFront();
|
_handleOverlayClose();
|
||||||
break;
|
customDebugPrint('[Main] ✅ Overlay close processing completed');
|
||||||
default:
|
break;
|
||||||
customDebugPrint('[Main] ⚠️ Unknown overlay action: ${data['action']}');
|
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() {
|
void _stopListeningToOverlayMessages() {
|
||||||
@@ -146,11 +175,55 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
_overlaySubscription = null;
|
_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(() {
|
setState(() {
|
||||||
_isOverlayActive = false;
|
_isOverlayActive = false;
|
||||||
});
|
});
|
||||||
_stopListeningToOverlayMessages(); // Clean up listener when overlay closes
|
_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<SpeedometerScreen> with WidgetsBindi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _bringAppToFront() {
|
void _bringAppToFront() async {
|
||||||
|
customDebugPrint('[Main] 🚀 _bringAppToFront() called - attempting to bring app to foreground');
|
||||||
try {
|
try {
|
||||||
// Use proper Android method to bring app to foreground
|
// Use proper Android method to bring app to foreground
|
||||||
BgLauncher.bringAppToForeground();
|
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) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {
|
||||||
|
_isInBackground = false;
|
||||||
|
});
|
||||||
|
customDebugPrint('[Main] 🔄 App state updated - _isInBackground set to false');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
customDebugPrint('[Main] ❌ Failed to bring app to front: $e');
|
customDebugPrint('[Main] ❌ BgLauncher failed to bring app to front: $e');
|
||||||
// Fallback to simple state update (original method)
|
|
||||||
if (mounted) {
|
// Enhanced fallback: try alternative approach
|
||||||
setState(() {});
|
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<SpeedometerScreen> with WidgetsBindi
|
|||||||
_overlaySubscription?.cancel();
|
_overlaySubscription?.cancel();
|
||||||
_backgroundHeartbeatTimer?.cancel();
|
_backgroundHeartbeatTimer?.cancel();
|
||||||
_overlayDataPushTimer?.cancel();
|
_overlayDataPushTimer?.cancel();
|
||||||
|
_overlayStatusCheckTimer?.cancel();
|
||||||
|
_tapCloseTimer?.cancel();
|
||||||
WakelockPlus.disable();
|
WakelockPlus.disable();
|
||||||
customDebugPrint('[Main] 🛑 Main app disposed - all subscriptions and timers canceled');
|
customDebugPrint('[Main] 🛑 Main app disposed - all subscriptions and timers canceled');
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -415,6 +504,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
|
|
||||||
// Send complete display data to overlay including screen size
|
// Send complete display data to overlay including screen size
|
||||||
final displaySpeed = _currentUnit.convert(_speed);
|
final displaySpeed = _currentUnit.convert(_speed);
|
||||||
|
final unitText = _currentUnit.label.toString();
|
||||||
final speedText = displaySpeed < 1.0 ? '--' : displaySpeed.toInt().toString();
|
final speedText = displaySpeed < 1.0 ? '--' : displaySpeed.toInt().toString();
|
||||||
final headingText = GpsService.formatHeading(_heading);
|
final headingText = GpsService.formatHeading(_heading);
|
||||||
|
|
||||||
@@ -422,11 +512,12 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
customDebugPrint(' speedText: "$speedText"');
|
customDebugPrint(' speedText: "$speedText"');
|
||||||
customDebugPrint(' headingText: "$headingText"');
|
customDebugPrint(' headingText: "$headingText"');
|
||||||
customDebugPrint(' direction: ${_heading.toStringAsFixed(1)}°');
|
customDebugPrint(' direction: ${_heading.toStringAsFixed(1)}°');
|
||||||
customDebugPrint(' unit: ${_currentUnit.label}, theme: $_currentThemeIndex');
|
customDebugPrint(' unit: $unitText, theme: $_currentThemeIndex');
|
||||||
|
|
||||||
FlutterOverlayWindow.shareData({
|
FlutterOverlayWindow.shareData({
|
||||||
'action': 'updateDisplay',
|
'action': 'updateDisplay',
|
||||||
'speedText': speedText,
|
'speedText': speedText,
|
||||||
|
'unitText': unitText,
|
||||||
'headingText': headingText,
|
'headingText': headingText,
|
||||||
'heading': _heading,
|
'heading': _heading,
|
||||||
'unitIndex': _currentUnit.index,
|
'unitIndex': _currentUnit.index,
|
||||||
@@ -493,6 +584,9 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
|||||||
// Start listening to overlay messages after state is set
|
// Start listening to overlay messages after state is set
|
||||||
_startListeningToOverlayMessages();
|
_startListeningToOverlayMessages();
|
||||||
|
|
||||||
|
// Start periodic overlay status check since message-based detection is unreliable
|
||||||
|
_startOverlayStatusCheck();
|
||||||
|
|
||||||
// Force UI update
|
// Force UI update
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -863,6 +957,7 @@ class OverlaySpeedometer extends StatefulWidget {
|
|||||||
|
|
||||||
class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
|
class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
|
||||||
String _speedText = '--';
|
String _speedText = '--';
|
||||||
|
String _unitText = 'km/h';
|
||||||
String _headingText = 'N/A';
|
String _headingText = 'N/A';
|
||||||
double _heading = -1.0;
|
double _heading = -1.0;
|
||||||
int _currentThemeIndex = 0;
|
int _currentThemeIndex = 0;
|
||||||
@@ -873,10 +968,16 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
customDebugPrint('[Overlay] 🟢 CREATED - Floating window initialized');
|
customDebugPrint('[Overlay] 🟢 CREATED - Floating window initialized (${DateTime.now().millisecondsSinceEpoch})');
|
||||||
_getSystemScreenSize();
|
_getSystemScreenSize();
|
||||||
_listenToMainAppMessages();
|
_listenToMainAppMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
customDebugPrint('[Overlay] 🔴 DISPOSING - Overlay widget disposed (${DateTime.now().millisecondsSinceEpoch})');
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void _getSystemScreenSize() {
|
void _getSystemScreenSize() {
|
||||||
try {
|
try {
|
||||||
@@ -910,6 +1011,7 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
|
|||||||
if (data is Map && data['action'] == 'updateDisplay') {
|
if (data is Map && data['action'] == 'updateDisplay') {
|
||||||
setState(() {
|
setState(() {
|
||||||
_speedText = data['speedText'] ?? '--';
|
_speedText = data['speedText'] ?? '--';
|
||||||
|
_unitText = data['unitText'];
|
||||||
_headingText = data['headingText'] ?? 'N/A';
|
_headingText = data['headingText'] ?? 'N/A';
|
||||||
_heading = data['heading']?.toDouble() ?? -1.0;
|
_heading = data['heading']?.toDouble() ?? -1.0;
|
||||||
_currentThemeIndex = data['themeIndex'] ?? 0;
|
_currentThemeIndex = data['themeIndex'] ?? 0;
|
||||||
@@ -936,20 +1038,16 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
customDebugPrint('[Overlay] 🔴 DESTROYED - Floating window disposed');
|
|
||||||
// No GPS subscription to cancel - overlay is display-only
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final buildTimestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
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 * 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()}');
|
customDebugPrint('[Overlay] 📐 Window size: ${_overlayWidth.round()}x${_overlayHeight.round()}, fontSize: ${fontSize.round()}');
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
@@ -976,19 +1074,42 @@ class _OverlaySpeedometerState extends State<OverlaySpeedometer> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(6),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: FittedBox(
|
child: Column(
|
||||||
fit: BoxFit.contain,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Text(
|
children: [
|
||||||
(_speedText == "--" ? "0" : _speedText),
|
Flexible(
|
||||||
style: TextStyle(
|
flex: 3,
|
||||||
fontSize: fontSize,
|
child: FittedBox(
|
||||||
fontWeight: FontWeight.w300,
|
fit: BoxFit.contain,
|
||||||
color: currentTheme.speedText,
|
child: Text(
|
||||||
fontFamily: 'DIN1451Alt',
|
(_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<OverlaySpeedometer> {
|
|||||||
// Transparent full-overlay gesture detection layer
|
// Transparent full-overlay gesture detection layer
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent, // Ensure gestures are captured
|
||||||
|
onPanStart: (details) {
|
||||||
|
customDebugPrint('[Overlay] 🖐️ Pan start detected - gestures are working!');
|
||||||
|
},
|
||||||
onLongPress: () async {
|
onLongPress: () async {
|
||||||
customDebugPrint('[Overlay] 🔴 Long press detected - closing overlay');
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
// Add haptic feedback for long press
|
customDebugPrint('[Overlay] 🔴 Long press detected - closing overlay and bringing main app to front ($timestamp)');
|
||||||
try {
|
try {
|
||||||
await HapticFeedback.mediumImpact();
|
// Skip haptic feedback in overlay - platform services not available
|
||||||
customDebugPrint('[Overlay] 📳 Haptic feedback triggered');
|
customDebugPrint('[Overlay] 📳 Skipping haptic feedback (overlay context limitation)');
|
||||||
} catch (e) {
|
|
||||||
customDebugPrint('[Overlay] ❌ Haptic feedback failed: $e');
|
// Signal main app to bring to foreground when overlay closes (non-blocking)
|
||||||
}
|
customDebugPrint('[Overlay] 📤 Sending bring-to-front close signal...');
|
||||||
// Notify main app before closing
|
FlutterOverlayWindow.shareData({
|
||||||
try {
|
'action': 'longPressClose',
|
||||||
// can we base this on handleOverlayClose() or _closeFloatingWindow()?
|
}).then((_) {
|
||||||
await FlutterOverlayWindow.shareData({
|
customDebugPrint('[Overlay] ✅ Long press close signal sent successfully');
|
||||||
'action': 'overlayClosed',
|
}).catchError((error) {
|
||||||
|
customDebugPrint('[Overlay] ❌ Long press close signal failed (expected): $error');
|
||||||
});
|
});
|
||||||
customDebugPrint('[Overlay] 📤 Close notification sent');
|
|
||||||
} catch (e) {
|
// Small delay to give signal a chance
|
||||||
customDebugPrint('[Overlay] ❌ Close notification failed: $e');
|
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 {
|
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 {
|
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',
|
'action': 'bringToFront',
|
||||||
|
}).catchError((error) {
|
||||||
|
customDebugPrint('[Overlay] ❌ Bring-to-front signal failed (expected): $error');
|
||||||
});
|
});
|
||||||
customDebugPrint('[Overlay] 📤 Bring-to-front message sent');
|
|
||||||
} catch (e) {
|
customDebugPrint('[Overlay] ✅ Tap gesture completed (overlay stays open)');
|
||||||
customDebugPrint('[Overlay] ❌ Bring-to-front message failed: $e');
|
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
customDebugPrint('[Overlay] ❌ Tap gesture failed: $e');
|
||||||
|
customDebugPrint('[Overlay] 📚 Stack trace: $stackTrace');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'GESTURE',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.transparent,
|
||||||
|
fontSize: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -45,4 +45,5 @@ flutter:
|
|||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: true
|
android: true
|
||||||
ios: true
|
ios: true
|
||||||
image_path: "icon/speedo.png"
|
image_path: "icon/speedo.png"
|
||||||
|
adaptive_icon_foreground: "icon/speedo.png"
|
||||||