mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 21:35:32 +00:00
Fix Fullscreen mode
This commit is contained in:
parent
e9dfb516ab
commit
d21dbbbc6b
3 changed files with 220 additions and 190 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue