diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index 48033a14..47c18a46 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -65,6 +65,7 @@ class MediaDetailScreen extends StatefulWidget { class _MediaDetailScreenState extends State with WatchStateAware, DeletionAware { List _seasons = []; bool _isLoadingSeasons = false; + Completer? _seasonsCompleter; PlexMetadata? _fullMetadata; PlexMetadata? _onDeckEpisode; PlexVideoPlaybackData? _playbackData; @@ -1025,6 +1026,7 @@ class _MediaDetailScreenState extends State with WatchStateAw } Future _loadSeasons() async { + _seasonsCompleter = Completer(); setState(() { _isLoadingSeasons = true; }); @@ -1048,11 +1050,16 @@ class _MediaDetailScreenState extends State with WatchStateAw setState(() { _isLoadingSeasons = false; }); + } finally { + if (!(_seasonsCompleter?.isCompleted ?? true)) { + _seasonsCompleter?.complete(); + } } } /// Load seasons from downloaded episodes (offline mode) void _loadSeasonsFromDownloads() { + _seasonsCompleter = Completer(); setState(() { _isLoadingSeasons = true; }); @@ -1087,6 +1094,9 @@ class _MediaDetailScreenState extends State with WatchStateAw _seasons = seasons; _isLoadingSeasons = false; }); + if (!(_seasonsCompleter?.isCompleted ?? true)) { + _seasonsCompleter?.complete(); + } } /// Load extras (trailers, behind-the-scenes, etc.) @@ -1654,8 +1664,8 @@ class _MediaDetailScreenState extends State with WatchStateAw } // Wait for seasons to finish loading if they're currently loading - while (_isLoadingSeasons) { - await Future.delayed(const Duration(milliseconds: 100)); + if (_isLoadingSeasons && _seasonsCompleter != null) { + await _seasonsCompleter!.future.timeout(const Duration(seconds: 10), onTimeout: () {}); } if (!mounted) return; diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 67a54592..6d2c0e90 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -173,6 +173,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin // Screen-level focus node: persists across loading/initialized phases so // key events never escape the video player route. late final FocusNode _screenFocusNode; + bool _reclaimingFocus = false; // Cached setting: when false on Windows/Linux, ESC should not exit the player bool _videoPlayerNavigationEnabled = false; @@ -1723,8 +1724,11 @@ class VideoPlayerScreenState extends State with WidgetsBindin /// descendant has focus, so internal movement between child controls /// does NOT trigger this. void _onScreenFocusChanged() { + if (_reclaimingFocus) return; if (!_screenFocusNode.hasFocus && mounted && !_isExiting.value) { + _reclaimingFocus = true; WidgetsBinding.instance.addPostFrameCallback((_) { + _reclaimingFocus = false; if (mounted && !_isExiting.value && !_screenFocusNode.hasFocus) { _screenFocusNode.requestFocus(); }