Fix Fullscreen mode

This commit is contained in:
NBA2K1 2025-06-27 08:35:01 +02:00
parent e9dfb516ab
commit d21dbbbc6b
3 changed files with 220 additions and 190 deletions

View file

@ -56,9 +56,6 @@ class _AnimePlayerViewState extends riv.ConsumerState<AnimePlayerView> {
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<AnimeStreamPage>
with TickerProviderStateMixin {
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
@ -196,7 +196,6 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
),
);
final ValueNotifier<double> _playbackSpeed = ValueNotifier(1.0);
final ValueNotifier<bool> _enterFullScreen = ValueNotifier(false);
final ValueNotifier<bool> _isDoubleSpeed = ValueNotifier(false);
late final ValueNotifier<Duration> _currentPosition = ValueNotifier(
_streamController.geTCurrentPosition(),
@ -235,13 +234,16 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
}
}
// 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<AnimeStreamPage>
@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<AnimeStreamPage>
/// 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<AnimeStreamPage>
},
),
if (_isDesktop)
CustomMaterialDesktopFullscreenButton(controller: _controller)
CustomMaterialDesktopFullscreenButton(
controller: _controller,
desktopFullScreenPlayer: widget.desktopFullScreenPlayer,
)
else
ValueListenableBuilder<bool>(
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<AnimeStreamPage>
}
Widget _topButtonBar(BuildContext context) {
return ValueListenableBuilder<bool>(
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<bool>(
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<bool>(
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<AnimeStreamPage>
_isDoubleSpeed.value = value ?? false;
},
defaultSkipIntroLength: skipIntroLength,
desktopFullScreenPlayer: widget.desktopFullScreenPlayer,
)
: MobileControllerWidget(
videoController: _controller,

View file

@ -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<bool>((ref) => false);
@riverpod
class AnimeStreamController extends _$AnimeStreamController {
@override

View file

@ -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<DesktopControllerWidget> createState() =>
ConsumerState<DesktopControllerWidget> createState() =>
_DesktopControllerWidgetState();
}
class _DesktopControllerWidgetState extends State<DesktopControllerWidget> {
class _DesktopControllerWidgetState
extends ConsumerState<DesktopControllerWidget> {
bool mount = true;
bool visible = true;
bool cursorVisible = true;
@ -201,9 +204,13 @@ class _DesktopControllerWidgetState extends State<DesktopControllerWidget> {
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<DesktopControllerWidget> {
},
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<DesktopControllerWidget> {
}
}
Future<void> _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<CustomMaterialDesktopFullscreenButton> createState() =>
ConsumerState<CustomMaterialDesktopFullscreenButton> createState() =>
_CustomMaterialDesktopFullscreenButtonState();
}
class _CustomMaterialDesktopFullscreenButtonState
extends State<CustomMaterialDesktopFullscreenButton> {
bool _isFullscreen = false;
extends ConsumerState<CustomMaterialDesktopFullscreenButton> {
@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<bool> setFullScreen({bool? value}) async {
} else {
await windowManager.setFullScreen(false);
}
return isFullScreen;
return !isFullScreen;
}