diff --git a/lib/screens/metadata/info_screen.dart b/lib/screens/metadata/info_screen.dart index 9243675..df541db 100644 --- a/lib/screens/metadata/info_screen.dart +++ b/lib/screens/metadata/info_screen.dart @@ -32,10 +32,134 @@ class ItemInfoScreenState extends ConsumerState { AutoDisposeStateNotifierProvider get provider => informationProvider(widget.item.id); + FocusNode focusNode = FocusNode(); + @override void initState() { super.initState(); - Future.microtask(() => ref.read(provider.notifier).getItemInformation(widget.item)); + Future.microtask(() { + focusNode.requestFocus(); + return ref.read(provider.notifier).getItemInformation(widget.item); + }); + } + + @override + Widget build(BuildContext context) { + final info = ref.watch(provider); + final videoStreams = (info.model?.videoStreams.map((map) => streamModel("Video", map)) ?? []).toList(); + final audioStreams = (info.model?.audioStreams.map((map) => streamModel("Audio", map)) ?? []).toList(); + final subStreams = (info.model?.subStreams.map((map) => streamModel("Subtitle", map)) ?? []).toList(); + return Dialog( + child: Card( + color: Theme.of(context).colorScheme.surface, + child: Focus( + autofocus: true, + focusNode: focusNode, + onKeyEvent: (node, event) => KeyEventResult.ignored, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Text( + widget.item.name, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + const Opacity(opacity: 0.3, child: Divider()), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + const Spacer(), + const SizedBox(width: 6), + IconButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: info.model.toString())); + if (context.mounted) { + fladderSnackbar(context, title: "Copied to clipboard"); + } + }, + icon: const Icon(Icons.copy_all_rounded)), + const SizedBox(width: 6), + IconButton( + onPressed: () => ref.read(provider.notifier).getItemInformation(widget.item), + icon: const Icon(IconsaxOutline.refresh), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 6), + Flexible( + fit: FlexFit.loose, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + shrinkWrap: true, + children: [ + Stack( + alignment: Alignment.center, + children: [ + if (info.model != null) ...{ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: double.infinity, child: streamModel("Info", info.model!.baseInformation)), + if ([...videoStreams, ...audioStreams, ...subStreams].isNotEmpty) ...{ + const Divider(), + Wrap( + alignment: WrapAlignment.start, + runAlignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + runSpacing: 16, + spacing: 16, + children: [ + ...videoStreams, + ...audioStreams, + ...subStreams, + ], + ), + }, + ], + ), + }, + AnimatedOpacity( + opacity: info.loading ? 1 : 0, + duration: const Duration(milliseconds: 250), + child: const Center(child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round)), + ) + ], + ), + ], + ), + ), + ), + Container( + color: Theme.of(context).colorScheme.surface, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton(onPressed: () => Navigator.of(context).pop(), child: Text(context.localized.close)) + ], + ), + ), + ) + ], + ), + ), + ), + ); } Widget tileRow(String title, String value) { @@ -103,117 +227,4 @@ class ItemInfoScreenState extends ConsumerState { ), ); } - - @override - Widget build(BuildContext context) { - final info = ref.watch(provider); - final videoStreams = (info.model?.videoStreams.map((map) => streamModel("Video", map)) ?? []).toList(); - final audioStreams = (info.model?.audioStreams.map((map) => streamModel("Audio", map)) ?? []).toList(); - final subStreams = (info.model?.subStreams.map((map) => streamModel("Subtitle", map)) ?? []).toList(); - return Dialog( - child: Card( - color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - color: Theme.of(context).colorScheme.surface, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - widget.item.name, - style: Theme.of(context).textTheme.titleLarge, - ), - ), - const Opacity(opacity: 0.3, child: Divider()), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - const Spacer(), - const SizedBox(width: 6), - IconButton( - onPressed: () async { - await Clipboard.setData(ClipboardData(text: info.model.toString())); - if (context.mounted) { - fladderSnackbar(context, title: "Copied to clipboard"); - } - }, - icon: const Icon(Icons.copy_all_rounded)), - const SizedBox(width: 6), - IconButton( - onPressed: () => ref.read(provider.notifier).getItemInformation(widget.item), - icon: const Icon(IconsaxOutline.refresh), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 6), - Flexible( - fit: FlexFit.loose, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - shrinkWrap: true, - children: [ - Stack( - alignment: Alignment.center, - children: [ - if (info.model != null) ...{ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(width: double.infinity, child: streamModel("Info", info.model!.baseInformation)), - if ([...videoStreams, ...audioStreams, ...subStreams].isNotEmpty) ...{ - const Divider(), - Wrap( - alignment: WrapAlignment.start, - runAlignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - runSpacing: 16, - spacing: 16, - children: [ - ...videoStreams, - ...audioStreams, - ...subStreams, - ], - ), - }, - ], - ), - }, - AnimatedOpacity( - opacity: info.loading ? 1 : 0, - duration: const Duration(milliseconds: 250), - child: const Center(child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round)), - ) - ], - ), - ], - ), - ), - ), - Container( - color: Theme.of(context).colorScheme.surface, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FilledButton(onPressed: () => Navigator.of(context).pop(), child: Text(context.localized.close)) - ], - ), - ), - ) - ], - ), - ), - ); - } } diff --git a/lib/screens/shared/default_titlebar.dart b/lib/screens/shared/default_titlebar.dart index 26de057..e93e602 100644 --- a/lib/screens/shared/default_titlebar.dart +++ b/lib/screens/shared/default_titlebar.dart @@ -42,28 +42,26 @@ class _DefaultTitleBarState extends ConsumerState with WindowLi return SizedBox( height: widget.height, child: switch (AdaptiveLayout.of(context).platform) { + TargetPlatform.android || TargetPlatform.iOS => SizedBox(height: MediaQuery.paddingOf(context).top), TargetPlatform.windows || TargetPlatform.linux => Row( children: [ Expanded( child: DragToMoveArea( - child: Container( - color: Colors.red.withOpacity(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.max, - children: [ - Container( - padding: const EdgeInsets.only(left: 16), - child: DefaultTextStyle( - style: TextStyle( - color: iconColor, - fontSize: 14, - ), - child: Text(widget.label ?? ""), + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + padding: const EdgeInsets.only(left: 16), + child: DefaultTextStyle( + style: TextStyle( + color: iconColor, + fontSize: 14, ), + child: Text(widget.label ?? ""), ), - ], - ), + ), + ], ), ), ), @@ -161,13 +159,7 @@ class _DefaultTitleBarState extends ConsumerState with WindowLi ), ], ), - TargetPlatform.macOS => const Row( - children: [ - Spacer(), - Text("Fladder"), - SizedBox(width: 16), - ], - ), + TargetPlatform.macOS => const SizedBox.shrink(), _ => Text(widget.label ?? "Fladder"), }, ); diff --git a/lib/screens/video_player/components/video_player_next_wrapper.dart b/lib/screens/video_player/components/video_player_next_wrapper.dart index 90804a4..e92c992 100644 --- a/lib/screens/video_player/components/video_player_next_wrapper.dart +++ b/lib/screens/video_player/components/video_player_next_wrapper.dart @@ -283,16 +283,19 @@ class _VideoPlayerNextWrapperState extends ConsumerState ], ), ), - AnimatedFadeSize( - duration: animSpeed, - child: show - ? Padding( - padding: const EdgeInsets.only(top: 16), - child: _SimpleControls( - skip: nextUp != null ? () => onTimeOut() : null, - ), - ) - : const SizedBox.shrink(), + IgnorePointer( + ignoring: !show, + child: AnimatedFadeSize( + duration: animSpeed, + child: show + ? Padding( + padding: const EdgeInsets.only(top: 16), + child: _SimpleControls( + skip: nextUp != null ? () => onTimeOut() : null, + ), + ) + : const SizedBox.shrink(), + ), ), ], ), @@ -300,12 +303,15 @@ class _VideoPlayerNextWrapperState extends ConsumerState ), ), if (AdaptiveLayout.of(context).isDesktop) - AnimatedOpacity( - duration: animSpeed, - opacity: show ? 1 : 0, - child: const Align( - alignment: Alignment.topRight, - child: DefaultTitleBar(), + IgnorePointer( + ignoring: !show, + child: AnimatedOpacity( + duration: animSpeed, + opacity: show ? 1 : 0, + child: const Align( + alignment: Alignment.topRight, + child: DefaultTitleBar(), + ), ), ), ], diff --git a/lib/screens/video_player/components/video_player_options_sheet.dart b/lib/screens/video_player/components/video_player_options_sheet.dart index 270fa3c..df01914 100644 --- a/lib/screens/video_player/components/video_player_options_sheet.dart +++ b/lib/screens/video_player/components/video_player_options_sheet.dart @@ -255,6 +255,7 @@ class _VideoOptionsMobileState extends ConsumerState { ListView itemInfo(ItemBaseModel? currentItem, BuildContext context) { return ListView( shrinkWrap: true, + controller: widget.controller, children: [ navTitle(currentItem?.title, currentItem?.subTextShort(context)), if (currentItem != null) ...{ diff --git a/lib/screens/video_player/video_player_controls.dart b/lib/screens/video_player/video_player_controls.dart index 83d875d..de9244a 100644 --- a/lib/screens/video_player/video_player_controls.dart +++ b/lib/screens/video_player/video_player_controls.dart @@ -65,12 +65,13 @@ class _DesktopControlsState extends ConsumerState { if (value.logicalKey == LogicalKeyboardKey.arrowUp) { resetTimer(); ref.read(videoPlayerSettingsProvider.notifier).steppedVolume(5); + return true; } if (value.logicalKey == LogicalKeyboardKey.arrowDown) { resetTimer(); ref.read(videoPlayerSettingsProvider.notifier).steppedVolume(-5); + return true; } - return true; } if (value is KeyDownEvent) { if (value.logicalKey == LogicalKeyboardKey.keyS) { @@ -79,25 +80,30 @@ class _DesktopControlsState extends ConsumerState { } else if (showCreditSkipButton) { skipCredits(introSkipModel); } + return true; } if (value.logicalKey == LogicalKeyboardKey.escape) { disableFullscreen(); + return true; } if (value.logicalKey == LogicalKeyboardKey.space) { ref.read(videoPlayerProvider).playOrPause(); + return true; } if (value.logicalKey == LogicalKeyboardKey.keyF) { toggleFullScreen(ref); + return true; } if (value.logicalKey == LogicalKeyboardKey.arrowUp) { resetTimer(); ref.read(videoPlayerSettingsProvider.notifier).steppedVolume(5); + return true; } if (value.logicalKey == LogicalKeyboardKey.arrowDown) { resetTimer(); ref.read(videoPlayerSettingsProvider.notifier).steppedVolume(-5); + return true; } - return true; } return false; } @@ -115,86 +121,83 @@ class _DesktopControlsState extends ConsumerState { return InputHandler( autoFocus: false, onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored, - child: Listener( - onPointerSignal: (event) => resetTimer(), - child: PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) { - if (!didPop) { - closePlayer(); - } - }, - child: GestureDetector( - onTap: () => toggleOverlay(), - child: MouseRegion( - cursor: showOverlay ? SystemMouseCursors.basic : SystemMouseCursors.none, - onEnter: (event) => toggleOverlay(value: true), - onExit: (event) => toggleOverlay(value: false), - onHover: AdaptiveLayout.of(context).isDesktop || kIsWeb ? (event) => toggleOverlay(value: true) : null, - child: Stack( - children: [ - if (player != null) - VideoSubtitles( - key: const Key('subtitles'), - controller: player, - overLayed: showOverlay, - ), - if (AdaptiveLayout.of(context).isDesktop) - Consumer(builder: (context, ref, child) { - final playing = ref.watch(mediaPlaybackProvider.select((value) => value.playing)); - final buffering = ref.watch(mediaPlaybackProvider.select((value) => value.buffering)); - return playButton(playing, buffering); - }), - IgnorePointer( - ignoring: !showOverlay, - child: AnimatedOpacity( - duration: fadeDuration, - opacity: showOverlay ? 1 : 0, - child: Column( - children: [ - topButtons(context), - const Spacer(), - bottomButtons(context), - ], - ), + child: PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (!didPop) { + closePlayer(); + } + }, + child: GestureDetector( + onTap: () => toggleOverlay(), + child: MouseRegion( + cursor: showOverlay ? SystemMouseCursors.basic : SystemMouseCursors.none, + onExit: (event) => toggleOverlay(value: false), + onEnter: (event) => toggleOverlay(value: true), + onHover: AdaptiveLayout.of(context).isDesktop ? (event) => toggleOverlay(value: true) : null, + child: Stack( + children: [ + if (player != null) + VideoSubtitles( + key: const Key('subtitles'), + controller: player, + overLayed: showOverlay, + ), + if (AdaptiveLayout.of(context).isDesktop) + Consumer(builder: (context, ref, child) { + final playing = ref.watch(mediaPlaybackProvider.select((value) => value.playing)); + final buffering = ref.watch(mediaPlaybackProvider.select((value) => value.buffering)); + return playButton(playing, buffering); + }), + IgnorePointer( + ignoring: !showOverlay, + child: AnimatedOpacity( + duration: fadeDuration, + opacity: showOverlay ? 1 : 0, + child: Column( + children: [ + topButtons(context), + const Spacer(), + bottomButtons(context), + ], ), ), - const VideoPlayerSeekIndicator(), - Consumer( - builder: (context, ref, child) { - final position = ref.watch(mediaPlaybackProvider.select((value) => value.position)); - bool showIntroSkipButton = introSkipModel?.introInRange(position) ?? false; - bool showCreditSkipButton = introSkipModel?.creditsInRange(position) ?? false; - return Stack( - children: [ - if (showIntroSkipButton) - Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(32), - child: IntroSkipButton( - isOverlayVisible: showOverlay, - skipIntro: () => skipIntro(introSkipModel), - ), + ), + const VideoPlayerSeekIndicator(), + Consumer( + builder: (context, ref, child) { + final position = ref.watch(mediaPlaybackProvider.select((value) => value.position)); + bool showIntroSkipButton = introSkipModel?.introInRange(position) ?? false; + bool showCreditSkipButton = introSkipModel?.creditsInRange(position) ?? false; + return Stack( + children: [ + if (showIntroSkipButton) + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(32), + child: IntroSkipButton( + isOverlayVisible: showOverlay, + skipIntro: () => skipIntro(introSkipModel), ), ), - if (showCreditSkipButton) - Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(32), - child: CreditsSkipButton( - isOverlayVisible: showOverlay, - skipCredits: () => skipCredits(introSkipModel), - ), + ), + if (showCreditSkipButton) + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(32), + child: CreditsSkipButton( + isOverlayVisible: showOverlay, + skipCredits: () => skipCredits(introSkipModel), ), - ) - ], - ); - }, - ), - ], - ), + ), + ) + ], + ); + }, + ), + ], ), ), ), @@ -236,47 +239,41 @@ class _DesktopControlsState extends ConsumerState { Colors.black.withOpacity(0), ], )), - child: Stack( - children: [ - if (AdaptiveLayout.of(context).isDesktop) - const Align( - alignment: Alignment.topRight, - child: DefaultTitleBar(), - ), - Padding( - padding: MediaQuery.paddingOf(context).copyWith(bottom: 0), - child: Container( - alignment: Alignment.topCenter, - height: 80, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconButton( - onPressed: () => minimizePlayer(context), - icon: const Icon( - IconsaxOutline.arrow_down_1, - size: 24, - ), - ), - const SizedBox(width: 16), - Flexible( - child: Text( - currentItem?.title ?? "", - style: Theme.of(context).textTheme.titleLarge, - ), - ), - ], - ), - ), - ], + child: Padding( + padding: MediaQuery.paddingOf(context).copyWith(bottom: 0, top: 0), + child: Container( + alignment: Alignment.topCenter, + child: Column( + children: [ + const Align( + alignment: Alignment.topRight, + child: DefaultTitleBar(), ), - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () => minimizePlayer(context), + icon: const Icon( + IconsaxOutline.arrow_down_1, + size: 24, + ), + ), + const SizedBox(width: 16), + Flexible( + child: Text( + currentItem?.title ?? "", + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ], + ), + ), + ], ), - ], + ), ), ); }