Unsuccessful fix on floating window interactions & new icons
@@ -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.
|
||||
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" : "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"}}
|
||||
|
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),
|
||||
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),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -76,6 +76,8 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
||||
String _errorMessage = '';
|
||||
bool _isInBackground = false;
|
||||
bool _isOverlayActive = false;
|
||||
bool _tapCloseRequested = false;
|
||||
Timer? _tapCloseTimer;
|
||||
|
||||
// Single source of truth for overlay sizing
|
||||
Map<String, int> _getOverlaySize() {
|
||||
@@ -100,6 +102,7 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
||||
|
||||
Timer? _backgroundHeartbeatTimer;
|
||||
Timer? _overlayDataPushTimer;
|
||||
Timer? _overlayStatusCheckTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -121,23 +124,49 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> 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<SpeedometerScreen> 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<SpeedometerScreen> 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<SpeedometerScreen> 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<SpeedometerScreen> 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<SpeedometerScreen> 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<SpeedometerScreen> 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<OverlaySpeedometer> {
|
||||
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<OverlaySpeedometer> {
|
||||
@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<OverlaySpeedometer> {
|
||||
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<OverlaySpeedometer> {
|
||||
});
|
||||
}
|
||||
|
||||
@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<OverlaySpeedometer> {
|
||||
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<OverlaySpeedometer> {
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -45,4 +45,5 @@ flutter:
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "icon/speedo.png"
|
||||
image_path: "icon/speedo.png"
|
||||
adaptive_icon_foreground: "icon/speedo.png"
|
||||