From d21dbbbc6b0bd3f713373b5404963e71f83fe086 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:35:01 +0200 Subject: [PATCH] Fix Fullscreen mode --- lib/modules/anime/anime_player_view.dart | 350 +++++++++--------- .../anime_player_controller_provider.dart | 3 + lib/modules/anime/widgets/desktop.dart | 57 ++- 3 files changed, 220 insertions(+), 190 deletions(-) diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index 206bc6c1..74bc28c9 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -56,9 +56,6 @@ class _AnimePlayerViewState extends riv.ConsumerState { bool desktopFullScreenPlayer = false; @override void dispose() { - if (_isDesktop) { - setFullScreen(value: desktopFullScreenPlayer); - } for (var infoHash in _infoHashList) { MTorrentServer().removeTorrent(infoHash); } @@ -169,6 +166,9 @@ class AnimeStreamPage extends riv.ConsumerStatefulWidget { enum _AniSkipPhase { none, opening, ending } +/// When the user first opens a video (on Desktop). +bool _firstTime = true; + class _AnimeStreamPageState extends riv.ConsumerState with TickerProviderStateMixin { late final GlobalKey _key = GlobalKey(); @@ -196,7 +196,6 @@ class _AnimeStreamPageState extends riv.ConsumerState ), ); final ValueNotifier _playbackSpeed = ValueNotifier(1.0); - final ValueNotifier _enterFullScreen = ValueNotifier(false); final ValueNotifier _isDoubleSpeed = ValueNotifier(false); late final ValueNotifier _currentPosition = ValueNotifier( _streamController.geTCurrentPosition(), @@ -235,13 +234,16 @@ class _AnimeStreamPageState extends riv.ConsumerState } } // If the last episode of an Anime has ended, exit fullscreen mode - if (!hasNextEpisode && val && _isDesktop && !_enterFullScreen.value) { + final isFullScreen = ref.read(fullscreenProvider); + if (!hasNextEpisode && val && _isDesktop && isFullScreen) { setFullScreen(value: false); + ref.read(fullscreenProvider.notifier).state = false; + widget.desktopFullScreenPlayer.call(false); } }); void pushToNewEpisode(BuildContext context, Chapter episode) { - widget.desktopFullScreenPlayer.call(true); + widget.desktopFullScreenPlayer.call(ref.read(fullscreenProvider)); if (context.mounted) { pushReplacementMangaReaderView(context: context, chapter: episode); } @@ -305,12 +307,24 @@ class _AnimeStreamPageState extends riv.ConsumerState @override void initState() { super.initState(); - if (_isDesktop) { - setFullScreen(value: ref.read(fullScreenPlayerStateProvider)); + // If player is being launched the first time, + // use global "Use Fullscreen" setting. + // Else (if user already watches an episode and just changes it), + // stay in the same mode, the user left it in. + if (_isDesktop && _firstTime) { + final globalFullscreen = ref.read(fullScreenPlayerStateProvider); + setFullScreen(value: globalFullscreen); + Future.microtask(() { + ref.read(fullscreenProvider.notifier).state = globalFullscreen; + widget.desktopFullScreenPlayer.call(globalFullscreen); + }); + _firstTime = false; } _currentPositionSub = _player.stream.position.listen( _unifiedPositionHandler, ); + _completed; + _currentTotalDurationSub; _loadAndroidFont().then((_) { _player.open( Media( @@ -979,6 +993,7 @@ class _AnimeStreamPageState extends riv.ConsumerState /// helper method for _mobileBottomButtonBar() and _desktopBottomButtonBar() Widget _buildSettingsButtons(BuildContext context) { + final isFullscreen = ref.watch(fullscreenProvider); return Row( children: [ IconButton( @@ -1009,20 +1024,19 @@ class _AnimeStreamPageState extends riv.ConsumerState }, ), if (_isDesktop) - CustomMaterialDesktopFullscreenButton(controller: _controller) + CustomMaterialDesktopFullscreenButton( + controller: _controller, + desktopFullScreenPlayer: widget.desktopFullScreenPlayer, + ) else - ValueListenableBuilder( - valueListenable: _enterFullScreen, - builder: (context, snapshot, _) { - return IconButton( - onPressed: () { - _setLandscapeMode(!snapshot); - _enterFullScreen.value = !snapshot; - }, - icon: Icon(snapshot ? Icons.fullscreen_exit : Icons.fullscreen), - iconSize: 25, - color: Colors.white, - ); + IconButton( + icon: Icon(isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen), + iconSize: 25, + color: Colors.white, + onPressed: () { + _setLandscapeMode(!isFullscreen); + ref.read(fullscreenProvider.notifier).state = !isFullscreen; + widget.desktopFullScreenPlayer.call(!isFullscreen); }, ), ], @@ -1030,163 +1044,158 @@ class _AnimeStreamPageState extends riv.ConsumerState } Widget _topButtonBar(BuildContext context) { - return ValueListenableBuilder( - valueListenable: _enterFullScreen, - builder: (context, fullScreen, _) { - return Padding( - padding: EdgeInsets.only( - top: !_isDesktop && !fullScreen - ? MediaQuery.of(context).padding.top - : 0, + final fullScreen = ref.watch(fullscreenProvider); + return Padding( + padding: EdgeInsets.only( + top: !_isDesktop && !fullScreen + ? MediaQuery.of(context).padding.top + : 0, + ), + child: Row( + children: [ + BackButton( + color: Colors.white, + onPressed: () { + if (_isDesktop && fullScreen) { + setFullScreen(value: !fullScreen); + ref.read(fullscreenProvider.notifier).state = !fullScreen; + widget.desktopFullScreenPlayer.call(!fullScreen); + } else { + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: SystemUiOverlay.values, + ); + } + if (mounted) { + // Set variable to true, so the player uses the global + // "Use Fullscreen" setting again. + _firstTime = true; + Navigator.pop(context); + } + }, ), - child: Row( + Flexible( + child: ListTile( + dense: true, + title: SizedBox( + width: context.width(0.8), + child: Text( + widget.episode.manga.value!.name!, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, + ), + ), + subtitle: SizedBox( + width: context.width(0.8), + child: Text( + widget.episode.name!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white.withValues(alpha: 0.7), + ), + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + Flexible( + fit: FlexFit.tight, + child: ValueListenableBuilder( + valueListenable: _isDoubleSpeed, + builder: (context, snapshot, _) { + return Text.rich( + TextSpan( + children: snapshot + ? [ + WidgetSpan(child: Icon(Icons.fast_forward)), + TextSpan(text: " 2X"), + ] + : [], + ), + ); + }, + ), + ), + Row( children: [ - BackButton( - color: Colors.white, - onPressed: () async { - if (_isDesktop) { - if (fullScreen) { - setFullScreen(value: false); - } else { - if (mounted) { - Navigator.pop(context); - } - } + btnToShowChapterListDialog( + context, + context.l10n.episodes, + widget.episode, + onChanged: (v) { + if (v) { + _player.play(); } else { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, - overlays: SystemUiOverlay.values, - ); - if (mounted) { - Navigator.pop(context); - } + _player.pause(); + } + }, + iconColor: Colors.white, + ), + btnToShowShareScreenshot( + widget.episode, + onChanged: (v) { + if (v) { + _player.play(); + } else { + _player.pause(); } }, ), - Flexible( - child: ListTile( - dense: true, - title: SizedBox( - width: context.width(0.8), - child: Text( - widget.episode.manga.value!.name!, - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white, - ), - overflow: TextOverflow.ellipsis, - ), - ), - subtitle: SizedBox( - width: context.width(0.8), - child: Text( - widget.episode.name!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.white.withValues(alpha: 0.7), - ), - overflow: TextOverflow.ellipsis, - ), - ), - ), - ), - Flexible( - fit: FlexFit.tight, - child: ValueListenableBuilder( - valueListenable: _isDoubleSpeed, - builder: (context, snapshot, _) { - return Text.rich( - TextSpan( - children: snapshot - ? [ - WidgetSpan(child: Icon(Icons.fast_forward)), - TextSpan(text: " 2X"), - ] - : [], - ), - ); - }, - ), - ), - Row( - children: [ - btnToShowChapterListDialog( - context, - context.l10n.episodes, - widget.episode, - onChanged: (v) { - if (v) { - _player.play(); - } else { - _player.pause(); - } - }, - iconColor: Colors.white, - ), - btnToShowShareScreenshot( - widget.episode, - onChanged: (v) { - if (v) { - _player.play(); - } else { - _player.pause(); - } - }, - ), - // IconButton( - // onPressed: () { - // showDialog( - // context: context, - // builder: (context) { - // return AlertDialog( - // scrollable: true, - // title: Text("Player Settings"), - // content: SizedBox( - // width: context.width(0.8), - // child: Column( - // crossAxisAlignment: - // CrossAxisAlignment.start, - // children: [ - // SwitchListTile( - // value: false, - // title: Text( - // "Enable Volume and Brightness Gestures", - // style: TextStyle( - // color: Theme.of(context) - // .textTheme - // .bodyLarge! - // .color! - // .withValues(alpha: 0.9), - // fontSize: 14), - // ), - // onChanged: (value) {}), - // SwitchListTile( - // value: false, - // title: Text( - // "Enable Horizonal Seek Gestures", - // style: TextStyle( - // color: Theme.of(context) - // .textTheme - // .bodyLarge! - // .color! - // .withValues(alpha: 0.9), - // fontSize: 14), - // ), - // onChanged: (value) {}), - // ], - // ), - // ), - // ); - // }); - // }, - // icon: Icon(Icons.adaptive.more)) - ], - ), + // IconButton( + // onPressed: () { + // showDialog( + // context: context, + // builder: (context) { + // return AlertDialog( + // scrollable: true, + // title: Text("Player Settings"), + // content: SizedBox( + // width: context.width(0.8), + // child: Column( + // crossAxisAlignment: + // CrossAxisAlignment.start, + // children: [ + // SwitchListTile( + // value: false, + // title: Text( + // "Enable Volume and Brightness Gestures", + // style: TextStyle( + // color: Theme.of(context) + // .textTheme + // .bodyLarge! + // .color! + // .withValues(alpha: 0.9), + // fontSize: 14), + // ), + // onChanged: (value) {}), + // SwitchListTile( + // value: false, + // title: Text( + // "Enable Horizonal Seek Gestures", + // style: TextStyle( + // color: Theme.of(context) + // .textTheme + // .bodyLarge! + // .color! + // .withValues(alpha: 0.9), + // fontSize: 14), + // ), + // onChanged: (value) {}), + // ], + // ), + // ), + // ); + // }); + // }, + // icon: Icon(Icons.adaptive.more)) ], ), - ); - }, + ], + ), ); } @@ -1235,6 +1244,7 @@ class _AnimeStreamPageState extends riv.ConsumerState _isDoubleSpeed.value = value ?? false; }, defaultSkipIntroLength: skipIntroLength, + desktopFullScreenPlayer: widget.desktopFullScreenPlayer, ) : MobileControllerWidget( videoController: _controller, diff --git a/lib/modules/anime/providers/anime_player_controller_provider.dart b/lib/modules/anime/providers/anime_player_controller_provider.dart index 24aa7f93..e74f3609 100644 --- a/lib/modules/anime/providers/anime_player_controller_provider.dart +++ b/lib/modules/anime/providers/anime_player_controller_provider.dart @@ -1,3 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar/isar.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/changed.dart'; @@ -14,6 +15,8 @@ import 'package:mangayomi/utils/chapter_recognition.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'anime_player_controller_provider.g.dart'; +final fullscreenProvider = StateProvider((ref) => false); + @riverpod class AnimeStreamController extends _$AnimeStreamController { @override diff --git a/lib/modules/anime/widgets/desktop.dart b/lib/modules/anime/widgets/desktop.dart index 311f5204..d857ce16 100644 --- a/lib/modules/anime/widgets/desktop.dart +++ b/lib/modules/anime/widgets/desktop.dart @@ -13,7 +13,7 @@ import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart'; import 'package:window_manager/window_manager.dart'; -class DesktopControllerWidget extends StatefulWidget { +class DesktopControllerWidget extends ConsumerStatefulWidget { final Function(Duration?) tempDuration; final Function(bool?) doubleSpeed; final AnimeStreamController streamController; @@ -23,6 +23,7 @@ class DesktopControllerWidget extends StatefulWidget { final Widget bottomButtonBarWidget; final Widget seekToWidget; final int defaultSkipIntroLength; + final void Function(bool) desktopFullScreenPlayer; const DesktopControllerWidget({ super.key, required this.videoController, @@ -34,14 +35,16 @@ class DesktopControllerWidget extends StatefulWidget { required this.tempDuration, required this.doubleSpeed, required this.defaultSkipIntroLength, + required this.desktopFullScreenPlayer, }); @override - State createState() => + ConsumerState createState() => _DesktopControllerWidgetState(); } -class _DesktopControllerWidgetState extends State { +class _DesktopControllerWidgetState + extends ConsumerState { bool mount = true; bool visible = true; bool cursorVisible = true; @@ -201,9 +204,13 @@ class _DesktopControllerWidgetState extends State { final volume = widget.videoController.player.state.volume - 5.0; widget.videoController.player.setVolume(volume.clamp(0.0, 100.0)); }, - const SingleActivator(LogicalKeyboardKey.keyF): () => setFullScreen(), - const SingleActivator(LogicalKeyboardKey.escape): () => - setFullScreen(value: false), + const SingleActivator(LogicalKeyboardKey.keyF): () async { + await _changeFullScreen(ref, widget.desktopFullScreenPlayer); + }, + const SingleActivator(LogicalKeyboardKey.escape): () async { + final desktopFullScreenPlayer = widget.desktopFullScreenPlayer; + await _changeFullScreen(ref, desktopFullScreenPlayer, value: false); + }, }, child: Stack( children: [ @@ -262,12 +269,13 @@ class _DesktopControllerWidgetState extends State { }, onTapUp: !toggleFullscreenOnDoublePress ? null - : (e) { + : (e) async { final now = DateTime.now(); final difference = now.difference(last); last = now; if (difference < const Duration(milliseconds: 400)) { - setFullScreen(); + final fullScreen = widget.desktopFullScreenPlayer; + await _changeFullScreen(ref, fullScreen); } }, onPanUpdate: modifyVolumeOnScroll @@ -489,6 +497,16 @@ class _DesktopControllerWidgetState extends State { } } +Future _changeFullScreen( + WidgetRef ref, + void Function(bool) setFullScreenCallback, { + bool? value, +}) async { + final isFullScreen = await setFullScreen(value: value); + ref.read(fullscreenProvider.notifier).state = isFullScreen; + setFullScreenCallback(isFullScreen); +} + // BUTTON: VOLUME /// MaterialDesktop design volume button & slider. @@ -751,36 +769,35 @@ class _CustomTrackShape extends RoundedRectSliderTrackShape { } } -class CustomMaterialDesktopFullscreenButton extends StatefulWidget { +class CustomMaterialDesktopFullscreenButton extends ConsumerStatefulWidget { final VideoController controller; + final void Function(bool) desktopFullScreenPlayer; const CustomMaterialDesktopFullscreenButton({ super.key, required this.controller, + required this.desktopFullScreenPlayer, }); @override - State createState() => + ConsumerState createState() => _CustomMaterialDesktopFullscreenButtonState(); } class _CustomMaterialDesktopFullscreenButtonState - extends State { - bool _isFullscreen = false; + extends ConsumerState { @override Widget build(BuildContext context) { + final isFullScreen = ref.watch(fullscreenProvider); return IconButton( - onPressed: () async { - final isFullScreen = await setFullScreen(); - setState(() { - _isFullscreen = isFullScreen; - }); - }, - icon: _isFullscreen + icon: isFullScreen ? const Icon(Icons.fullscreen_exit) : const Icon(Icons.fullscreen), iconSize: 25, color: Colors.white, + onPressed: () async { + await _changeFullScreen(ref, widget.desktopFullScreenPlayer); + }, ); } } @@ -799,5 +816,5 @@ Future setFullScreen({bool? value}) async { } else { await windowManager.setFullScreen(false); } - return isFullScreen; + return !isFullScreen; }