feat(03-01): create OverlayService and add retry dependency
- Add retry package (^3.1.2) for robust platform operations - Create OverlayService wrapper for all FlutterOverlayWindow calls - Implement showOverlay/closeOverlay with 3-attempt retry logic - Add shareData fire-and-forget with warn-level logging - Add isActive, overlayListener, checkPermission helpers - Define staleness constants: 3s dim, 10s dash, 0.5 opacity, 1s check - Define GPS_GRACE_PERIOD constant (30 seconds) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,4 +34,16 @@ abstract class OverlayConfig {
|
|||||||
|
|
||||||
/// Default overlay height for calculations
|
/// Default overlay height for calculations
|
||||||
static const double DEFAULT_OVERLAY_HEIGHT = 140.0;
|
static const double DEFAULT_OVERLAY_HEIGHT = 140.0;
|
||||||
|
|
||||||
|
/// Time after which overlay dims speed display due to stale data
|
||||||
|
static const Duration STALENESS_DIM_THRESHOLD = Duration(seconds: 3);
|
||||||
|
|
||||||
|
/// Time after which overlay shows dashes instead of speed
|
||||||
|
static const Duration STALENESS_DASH_THRESHOLD = Duration(seconds: 10);
|
||||||
|
|
||||||
|
/// Opacity value for dimmed stale state
|
||||||
|
static const double STALENESS_DIM_OPACITY = 0.5;
|
||||||
|
|
||||||
|
/// Interval for checking data staleness
|
||||||
|
static const Duration STALENESS_CHECK_INTERVAL = Duration(seconds: 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,7 @@ abstract class TimingConfig {
|
|||||||
|
|
||||||
/// Delay for long press gesture signal
|
/// Delay for long press gesture signal
|
||||||
static const Duration LONG_PRESS_SIGNAL_DELAY = Duration(milliseconds: 50);
|
static const Duration LONG_PRESS_SIGNAL_DELAY = Duration(milliseconds: 50);
|
||||||
|
|
||||||
|
/// GPS grace period - continues for 30s after overlay close when backgrounded
|
||||||
|
static const Duration GPS_GRACE_PERIOD = Duration(seconds: 30);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:retry/retry.dart';
|
||||||
|
import 'logger.dart';
|
||||||
|
|
||||||
|
/// Service wrapper for FlutterOverlayWindow platform calls with retry logic
|
||||||
|
/// Encapsulates all overlay operations with error handling and verification
|
||||||
|
class OverlayService {
|
||||||
|
/// Retry configuration for platform operations
|
||||||
|
static final RetryOptions _retryOptions = RetryOptions(
|
||||||
|
maxAttempts: 3,
|
||||||
|
delayFactor: const Duration(milliseconds: 200),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Show overlay window with retry logic and verification
|
||||||
|
/// Returns true if overlay successfully opened and verified active
|
||||||
|
Future<bool> showOverlay({
|
||||||
|
required int width,
|
||||||
|
required int height,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _retryOptions.retry(
|
||||||
|
() async {
|
||||||
|
await FlutterOverlayWindow.showOverlay(
|
||||||
|
enableDrag: true,
|
||||||
|
overlayTitle: "Speedometer",
|
||||||
|
overlayContent: 'Speedo overlay active',
|
||||||
|
flag: OverlayFlag.defaultFlag,
|
||||||
|
visibility: NotificationVisibility.visibilityPublic,
|
||||||
|
positionGravity: PositionGravity.none,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify overlay actually opened
|
||||||
|
final isActive = await FlutterOverlayWindow.isActive();
|
||||||
|
if (!isActive) {
|
||||||
|
Logger.error('Overlay showOverlay succeeded but isActive returned false', 'OverlayService');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error('Failed to show overlay after retries: $e', 'OverlayService');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close overlay window with retry logic
|
||||||
|
/// Returns true on success, false on failure after retries
|
||||||
|
Future<bool> closeOverlay() async {
|
||||||
|
try {
|
||||||
|
await _retryOptions.retry(
|
||||||
|
() async {
|
||||||
|
await FlutterOverlayWindow.closeOverlay();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error('Failed to close overlay after retries: $e', 'OverlayService');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Share data with overlay window (fire-and-forget)
|
||||||
|
/// Does NOT retry - data loss is expected and handled by staleness detection
|
||||||
|
Future<void> shareData(Map<String, dynamic> data) async {
|
||||||
|
try {
|
||||||
|
await FlutterOverlayWindow.shareData(data);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.warn('Failed to share data with overlay: $e', 'OverlayService');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if overlay window is currently active
|
||||||
|
/// Returns false on error
|
||||||
|
Future<bool> isActive() async {
|
||||||
|
try {
|
||||||
|
return await FlutterOverlayWindow.isActive();
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error('Failed to check overlay active status: $e', 'OverlayService');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream of messages from overlay window
|
||||||
|
/// Direct delegation to platform channel
|
||||||
|
Stream<dynamic> get overlayListener => FlutterOverlayWindow.overlayListener;
|
||||||
|
|
||||||
|
/// Check if system alert window permission is granted
|
||||||
|
Future<bool> checkPermission() async {
|
||||||
|
return await Permission.systemAlertWindow.isGranted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,9 @@ dependencies:
|
|||||||
# Persistent settings storage
|
# Persistent settings storage
|
||||||
shared_preferences: ^2.3.4
|
shared_preferences: ^2.3.4
|
||||||
|
|
||||||
|
# Retry logic for platform operations
|
||||||
|
retry: ^3.1.2
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user