feat(03-02): add staleness detection to overlay screen and error surfacing to SpeedometerScreen
Overlay screen staleness: - Add _lastUpdateTime field to track GPS data freshness - Add _stalenessCheckTimer for periodic staleness monitoring (1s interval) - Update _lastUpdateTime from message.timestamp in _listenToMainAppMessages - Start staleness monitoring in initState, cancel in dispose - Calculate staleness opacity in build() (0.5 alpha after 3s) - Apply staleness dimming to speed text, unit text, heading icon, and heading text - Show "--" for speed after 10s of no data (setState in timer callback) - Immediate snap-back when fresh data arrives (no AnimatedOpacity) SpeedometerScreen error surfacing: - Import permission_handler for openAppSettings - Register error callback on OverlayProvider in initState postFrameCallback - Implement _handleOverlayError to show snackbars for overlay errors - Permission error: snackbar with "Settings" action button that opens app settings - Creation failure: snackbar with error message (no action button) - Communication degraded: snackbar with error message (no action button) - Clean up error callback in dispose (try-catch for disposed provider) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
@@ -28,9 +29,10 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// Delay GPS init to after first frame so context is available
|
||||
// Delay GPS init and error callback setup to after first frame so context is available
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_initializeGpsManager();
|
||||
context.read<OverlayProvider>().onError = _handleOverlayError;
|
||||
});
|
||||
_enableWakelock();
|
||||
_ensureOverlayPermission();
|
||||
@@ -42,6 +44,11 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_backgroundHeartbeatTimer?.cancel();
|
||||
WakelockPlus.disable();
|
||||
try {
|
||||
context.read<OverlayProvider>().onError = null;
|
||||
} catch (e) {
|
||||
// Provider might already be disposed
|
||||
}
|
||||
Logger.info('SpeedometerScreen disposed', 'Main');
|
||||
super.dispose();
|
||||
}
|
||||
@@ -59,6 +66,37 @@ class _SpeedometerScreenState extends State<SpeedometerScreen> with WidgetsBindi
|
||||
}
|
||||
}
|
||||
|
||||
void _handleOverlayError(OverlayError error) {
|
||||
if (!mounted) return;
|
||||
|
||||
String message;
|
||||
SnackBarAction? action;
|
||||
|
||||
switch (error) {
|
||||
case OverlayError.permission:
|
||||
message = 'Overlay permission required';
|
||||
action = SnackBarAction(
|
||||
label: 'Settings',
|
||||
onPressed: () => openAppSettings(),
|
||||
);
|
||||
break;
|
||||
case OverlayError.creationFailed:
|
||||
message = 'Failed to create overlay window';
|
||||
break;
|
||||
case OverlayError.communicationDegraded:
|
||||
message = 'Overlay communication degraded';
|
||||
break;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
action: action,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _enableWakelock() async {
|
||||
try {
|
||||
await WakelockPlus.enable();
|
||||
|
||||
Reference in New Issue
Block a user