diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index ea0bfe8a..03c700f9 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -8,7 +8,7 @@ import '../services/settings_binding_owner.dart'; import '../services/settings_service.dart' as settings; import '../theme/mono_theme.dart'; -class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin { +class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin, WidgetsBindingObserver { late final SettingsBindingOwner _settingsBinding; settings.ThemeMode _themeMode = settings.ThemeMode.system; late Brightness _systemBrightness; @@ -25,10 +25,11 @@ class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin { onRefresh: (service) => _syncThemeMode(service.read(settings.SettingsService.themeMode)), ); unawaited(_settingsBinding.bind()); - WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = _onBrightnessChanged; + WidgetsBinding.instance.addObserver(this); } - void _onBrightnessChanged() { + @override + void didChangePlatformBrightness() { _systemBrightness = WidgetsBinding.instance.platformDispatcher.platformBrightness; if (_themeMode == settings.ThemeMode.system) { safeNotifyListeners(); @@ -38,9 +39,7 @@ class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin { @override void dispose() { _settingsBinding.dispose(); - if (WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged == _onBrightnessChanged) { - WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = null; - } + WidgetsBinding.instance.removeObserver(this); super.dispose(); } diff --git a/lib/screens/settings/atmos_diagnostics_screen.dart b/lib/screens/settings/atmos_diagnostics_screen.dart index a26a6017..44efeea6 100644 --- a/lib/screens/settings/atmos_diagnostics_screen.dart +++ b/lib/screens/settings/atmos_diagnostics_screen.dart @@ -63,6 +63,7 @@ class _AtmosDiagnosticsScreenState extends State { } try { await _channel.invokeMethod('start', {'mode': mode, if (needsUrl) 'url': url}); + if (!mounted) return; setState(() => _activeMode = mode); } on PlatformException catch (e) { if (mounted) showErrorSnackBar(context, e.message ?? e.code); diff --git a/lib/utils/smart_deletion_handler.dart b/lib/utils/smart_deletion_handler.dart index 5a14bb3e..de90c0a9 100644 --- a/lib/utils/smart_deletion_handler.dart +++ b/lib/utils/smart_deletion_handler.dart @@ -14,13 +14,12 @@ class SmartDeletionHandler { required String globalKey, int delayMs = 500, }) async { - bool dialogShown = false; bool deletionComplete = false; + final dialogKey = GlobalKey(); Future.delayed(Duration(milliseconds: delayMs), () { if (!deletionComplete && context.mounted) { - dialogShown = true; - _showProgressDialog(context, provider, globalKey); + _showProgressDialog(context, provider, globalKey, dialogKey); } }); @@ -28,32 +27,38 @@ class SmartDeletionHandler { await provider.deleteDownload(globalKey); } finally { deletionComplete = true; - // Close dialog if shown (with canPop guard to prevent double-pop) - if (dialogShown && context.mounted && Navigator.canPop(context)) { - Navigator.of(context).pop(); + final dialogContext = dialogKey.currentContext; + if (dialogContext != null && dialogContext.mounted) { + final route = ModalRoute.of(dialogContext); + if (route != null && route.isActive) { + Navigator.of(dialogContext).removeRoute(route); + } } } } - static void _showProgressDialog(BuildContext context, DownloadProvider _, String globalKey) { + static void _showProgressDialog(BuildContext context, DownloadProvider _, String globalKey, GlobalKey dialogKey) { showScopedDialog( context: context, barrierDismissible: false, - builder: (dialogContext) => Consumer( - builder: (context, provider, child) { - final progress = provider.getDeletionProgress(globalKey); + builder: (dialogContext) => KeyedSubtree( + key: dialogKey, + child: Consumer( + builder: (context, provider, child) { + final progress = provider.getDeletionProgress(globalKey); - if (progress == null) { - return AlertDialog( - content: Row( - mainAxisSize: .min, - children: [const CircularProgressIndicator(), const SizedBox(width: 20), Text(t.downloads.deleting)], - ), - ); - } + if (progress == null) { + return AlertDialog( + content: Row( + mainAxisSize: .min, + children: [const CircularProgressIndicator(), const SizedBox(width: 20), Text(t.downloads.deleting)], + ), + ); + } - return DeletionProgressDialog(progress: progress); - }, + return DeletionProgressDialog(progress: progress); + }, + ), ), ); } diff --git a/lib/widgets/server_activities_button.dart b/lib/widgets/server_activities_button.dart index 9c62a074..8dd3e7dd 100644 --- a/lib/widgets/server_activities_button.dart +++ b/lib/widgets/server_activities_button.dart @@ -141,6 +141,7 @@ class ServerActivitiesButtonState extends State { } catch (_) { return; } + if (!mounted || _overlayEntry == null) return; _pollTimer?.cancel(); _pollTimer = null; _panelNotifier.value = _PanelData.loading; diff --git a/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart b/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart index 7b631f4f..3a21478b 100644 --- a/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart +++ b/lib/widgets/video_controls/widgets/performance_overlay/performance_stats_service.dart @@ -41,6 +41,7 @@ class PerformanceStatsService { String _runtimePlayerType = 'unknown'; StreamSubscription? _backendSwitchedSubscription; bool _fetchInProgress = false; + bool _disposed = false; PerformanceStatsService(this.player); @@ -110,7 +111,7 @@ class PerformanceStatsService { /// Fetch all performance stats from the player. Future _fetchStats() async { - if (_fetchInProgress) return; + if (_disposed || _fetchInProgress) return; _fetchInProgress = true; try { // Ensure we know the runtime type on first fetch @@ -127,7 +128,7 @@ class PerformanceStatsService { await _fetchMpvStats(); } } catch (e) { - appLogger.w('Failed to fetch performance stats', error: e); + if (!_disposed) appLogger.w('Failed to fetch performance stats', error: e); } finally { _fetchInProgress = false; } @@ -185,7 +186,7 @@ class PerformanceStatsService { appMemoryBytes: appMemory, uiFps: _currentUiFps, ); - _statsController.add(stats); + _emit(stats); } else { // Parse ExoPlayer stats format final stats = PerformanceStats( @@ -225,7 +226,7 @@ class PerformanceStatsService { appMemoryBytes: appMemory, uiFps: _currentUiFps, ); - _statsController.add(stats); + _emit(stats); } } @@ -323,7 +324,11 @@ class PerformanceStatsService { uiFps: _currentUiFps, ); - _statsController.add(stats); + _emit(stats); + } + + void _emit(PerformanceStats stats) { + if (!_disposed && !_statsController.isClosed) _statsController.add(stats); } /// Parse a string to int, returning null if parsing fails. @@ -361,6 +366,8 @@ class PerformanceStatsService { /// Dispose of the service and release resources. void dispose() { + if (_disposed) return; + _disposed = true; _backendSwitchedSubscription?.cancel(); _backendSwitchedSubscription = null; stopPolling();