diff --git a/lib/models/playback/direct_playback_model.dart b/lib/models/playback/direct_playback_model.dart index ab03db8..19edfbf 100644 --- a/lib/models/playback/direct_playback_model.dart +++ b/lib/models/playback/direct_playback_model.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; +import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/media_segments_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/trick_play_model.dart'; @@ -115,6 +116,15 @@ class DirectPlaybackModel extends PlaybackModel { return null; } + @override + DirectPlaybackModel? updateUserData(UserData userData) { + return copyWith( + item: item.copyWith( + userData: userData, + ), + ); + } + @override String toString() => 'DirectPlaybackModel(item: $item, playbackInfo: $playbackInfo)'; diff --git a/lib/models/playback/offline_playback_model.dart b/lib/models/playback/offline_playback_model.dart index 074f166..bc38ff0 100644 --- a/lib/models/playback/offline_playback_model.dart +++ b/lib/models/playback/offline_playback_model.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; +import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/media_segments_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/trick_play_model.dart'; @@ -96,6 +97,15 @@ class OfflinePlaybackModel extends PlaybackModel { return false; } + @override + OfflinePlaybackModel? updateUserData(UserData userData) { + return copyWith( + item: item.copyWith( + userData: userData, + ), + ); + } + @override String toString() => 'OfflinePlaybackModel(item: $item, syncedItem: $syncedItem)'; diff --git a/lib/models/playback/playback_model.dart b/lib/models/playback/playback_model.dart index afac46c..0d2ae7e 100644 --- a/lib/models/playback/playback_model.dart +++ b/lib/models/playback/playback_model.dart @@ -12,6 +12,7 @@ import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/episode_model.dart'; +import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/media_segments_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/season_model.dart'; @@ -84,6 +85,8 @@ class PlaybackModel { Future? startDuration() async => item.userData.playBackPosition; + PlaybackModel? updateUserData(UserData userData) => throw UnimplementedError(); + Future? setSubtitle(SubStreamModel? model, MediaControlsWrapper player) => throw UnimplementedError(); Future? setAudio(AudioStreamModel? model, MediaControlsWrapper player) => throw UnimplementedError(); Future? setQualityOption(Map map) => throw UnimplementedError(); diff --git a/lib/models/playback/transcode_playback_model.dart b/lib/models/playback/transcode_playback_model.dart index 165c3c4..4b01105 100644 --- a/lib/models/playback/transcode_playback_model.dart +++ b/lib/models/playback/transcode_playback_model.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; +import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/media_segments_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/trick_play_model.dart'; @@ -51,7 +52,7 @@ class TranscodePlaybackModel extends PlaybackModel { } @override - Future? setQualityOption(Map map) async => copyWith(bitRateOptions: map); + Future? setQualityOption(Map map) async => copyWith(bitRateOptions: map); @override Future playbackStarted(Duration position, Ref ref) async { @@ -114,6 +115,15 @@ class TranscodePlaybackModel extends PlaybackModel { return this; } + @override + TranscodePlaybackModel? updateUserData(UserData userData) { + return copyWith( + item: item.copyWith( + userData: userData, + ), + ); + } + @override String toString() => 'TranscodePlaybackModel(item: $item, playbackInfo: $playbackInfo)'; diff --git a/lib/widgets/navigation_scaffold/components/floating_player_bar.dart b/lib/widgets/navigation_scaffold/components/floating_player_bar.dart index b9078b7..6a514e1 100644 --- a/lib/widgets/navigation_scaffold/components/floating_player_bar.dart +++ b/lib/widgets/navigation_scaffold/components/floating_player_bar.dart @@ -3,10 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:iconsax_plus/iconsax_plus.dart'; +import 'package:overflow_view/overflow_view.dart'; import 'package:window_manager/window_manager.dart'; import 'package:fladder/models/media_playback_model.dart'; -import 'package:fladder/providers/settings/video_player_settings_provider.dart'; +import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/flat_button.dart'; @@ -15,10 +16,16 @@ import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/duration_extensions.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/refresh_state.dart'; +import 'package:fladder/widgets/shared/fladder_slider.dart'; +import 'package:fladder/widgets/shared/item_actions.dart'; const videoPlayerHeroTag = "HeroPlayer"; -const floatingPlayerHeight = 70.0; +double floatingPlayerHeight(BuildContext context) => switch (AdaptiveLayout.viewSizeOf(context)) { + ViewSize.phone => 75, + ViewSize.tablet => 85, + ViewSize.desktop => 95, + }; class FloatingPlayerBar extends ConsumerStatefulWidget { const FloatingPlayerBar({super.key}); @@ -29,6 +36,8 @@ class FloatingPlayerBar extends ConsumerStatefulWidget { class _CurrentlyPlayingBarState extends ConsumerState { bool showExpandButton = false; + bool changingSliderValue = false; + Duration lastPosition = Duration.zero; Future openFullScreenPlayer() async { setState(() => showExpandButton = false); @@ -58,155 +67,239 @@ class _CurrentlyPlayingBarState extends ConsumerState { Widget build(BuildContext context) { final playbackInfo = ref.watch(mediaPlaybackProvider); final player = ref.watch(videoPlayerProvider); - final playbackModel = ref.watch(playBackModel.select((value) => value?.item)); - final progress = playbackInfo.position.inMilliseconds / playbackInfo.duration.inMilliseconds; - return Dismissible( - key: const Key("CurrentlyPlayingBar"), - confirmDismiss: (direction) async { - if (direction == DismissDirection.up) { - await openFullScreenPlayer(); - } else { - await stopPlayer(); - } - return false; - }, - direction: DismissDirection.vertical, - child: InkWell( - onLongPress: () => fladderSnackbar(context, title: "Swipe up/down to open/close the player"), - child: Card( - elevation: 5, - color: Theme.of(context).colorScheme.primaryContainer, - child: SizedBox( - height: floatingPlayerHeight, - child: LayoutBuilder(builder: (context, constraints) { - return Row( - children: [ - Flexible( - child: Padding( - padding: MediaQuery.paddingOf(context).copyWith(top: 0, bottom: 0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(6), - child: Row( - spacing: 7, - children: [ - if (playbackInfo.state == VideoPlayerState.minimized) - Card( - child: AspectRatio( - aspectRatio: 1.67, - child: MouseRegion( - onEnter: (event) => setState(() => showExpandButton = true), - onExit: (event) => setState(() => showExpandButton = false), - child: Stack( - children: [ - Hero( - tag: videoPlayerHeroTag, - child: player.videoWidget( - UniqueKey(), - BoxFit.fitHeight, - ) ?? - const SizedBox.shrink(), - ), - Positioned.fill( - child: Tooltip( - message: "Expand player", - waitDuration: const Duration(milliseconds: 500), - child: AnimatedOpacity( - opacity: showExpandButton ? 1 : 0, - duration: const Duration(milliseconds: 125), - child: Container( - color: Colors.black.withValues(alpha: 0.6), - child: FlatButton( - onTap: () async => openFullScreenPlayer(), - child: const Icon(Icons.keyboard_arrow_up_rounded), - ), - ), - ), - ), - ) - ], - ), - ), - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + final item = ref.watch(playBackModel.select((value) => value?.item)); + if (!changingSliderValue) { + lastPosition = playbackInfo.position; + } + + var isFavourite = item?.userData.isFavourite == true; + + final isDesktop = AdaptiveLayout.of(context).isDesktop; + + final itemActions = [ + ItemActionButton( + label: Text(context.localized.audio), + icon: Consumer( + builder: (context, ref, child) { + var volume = (player.lastState?.volume ?? 0) <= 0; + return Icon( + volume ? IconsaxPlusBold.volume_cross : IconsaxPlusBold.volume_high, + ); + }, + ), + action: () { + final volume = player.lastState?.volume == 0 ? 100.0 : 0.0; + player.setVolume(volume); + }), + ItemActionButton( + label: Text(context.localized.stop), + action: () async => stopPlayer(), + icon: const Icon(IconsaxPlusBold.stop), + ), + ItemActionButton( + label: Text(isFavourite ? context.localized.removeAsFavorite : context.localized.addAsFavorite), + icon: Icon( + color: isFavourite ? Colors.red : null, + isFavourite ? IconsaxPlusBold.heart : IconsaxPlusLinear.heart, + ), + action: () async { + final result = (await ref.read(userProvider.notifier).setAsFavorite( + !isFavourite, + item?.id ?? "", + )) + ?.body; + + if (result != null) { + ref.read(playBackModel.notifier).update((state) => state?.updateUserData(result)); + } + }, + ), + ]; + return Padding( + padding: + MediaQuery.paddingOf(context).copyWith(top: 0, bottom: isDesktop ? 0 : MediaQuery.paddingOf(context).bottom), + child: Dismissible( + key: const Key("CurrentlyPlayingBar"), + confirmDismiss: (direction) async { + if (direction == DismissDirection.up) { + await openFullScreenPlayer(); + } else { + await stopPlayer(); + } + return false; + }, + direction: DismissDirection.vertical, + child: InkWell( + onLongPress: () => fladderSnackbar(context, title: "Swipe up/down to open/close the player"), + child: Card( + elevation: 5, + color: Theme.of(context).colorScheme.primaryContainer, + child: SizedBox( + height: floatingPlayerHeight(context), + child: LayoutBuilder(builder: (context, constraints) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(6), + child: Row( + spacing: 12, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (playbackInfo.state == VideoPlayerState.minimized) + Card( + child: AspectRatio( + aspectRatio: 1.67, + child: MouseRegion( + onEnter: (event) => setState(() => showExpandButton = true), + onExit: (event) => setState(() => showExpandButton = false), + child: Stack( children: [ - Flexible( - child: Text( - playbackModel?.title ?? "", - style: Theme.of(context).textTheme.titleMedium, - ), + Hero( + tag: videoPlayerHeroTag, + child: player.videoWidget( + UniqueKey(), + BoxFit.fitHeight, + ) ?? + const SizedBox.shrink(), ), - if (playbackModel?.detailedName(context)?.isNotEmpty == true) - Flexible( - child: Text( - playbackModel?.detailedName(context) ?? "", - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: - Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.65), - ), + Positioned.fill( + child: Tooltip( + message: "Expand player", + waitDuration: const Duration(milliseconds: 500), + child: AnimatedOpacity( + opacity: showExpandButton ? 1 : 0, + duration: const Duration(milliseconds: 125), + child: Container( + color: Colors.black.withValues(alpha: 0.6), + child: FlatButton( + onTap: () async => openFullScreenPlayer(), + child: const Icon(Icons.keyboard_arrow_up_rounded), + ), + ), ), ), + ) ], ), ), - if (!progress.isNaN && constraints.maxWidth > 500) - Text( - "${playbackInfo.position.readAbleDuration} / ${playbackInfo.duration.readAbleDuration}"), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: IconButton.filledTonal( - onPressed: () => ref.read(videoPlayerProvider).playOrPause(), - icon: playbackInfo.playing - ? const Icon(Icons.pause_rounded) - : const Icon(Icons.play_arrow_rounded), - ), - ), - if (constraints.maxWidth > 500) ...[ - IconButton( - onPressed: () { - final volume = player.lastState?.volume == 0 ? 100.0 : 0.0; - player.setVolume(volume); - }, - icon: Icon( - ref.watch(videoPlayerSettingsProvider.select((value) => value.volume)) <= 0 - ? IconsaxPlusBold.volume_cross - : IconsaxPlusBold.volume_high, + ), + ), + Expanded( + child: InkWell( + onTap: () => item?.navigateTo(context), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + item?.title ?? "", + style: Theme.of(context).textTheme.titleMedium, + maxLines: 1, ), ), - Tooltip( - message: context.localized.stop, - waitDuration: const Duration(milliseconds: 500), - child: IconButton( - onPressed: () async => stopPlayer(), - icon: const Icon(IconsaxPlusBold.stop), + if (item?.detailedName(context)?.isNotEmpty == true) + Flexible( + child: Text( + item?.detailedName(context) ?? "", + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.65), + ), + maxLines: 1, + ), ), - ), ], - ], + ), ), ), - ), - LinearProgressIndicator( - minHeight: 6, - backgroundColor: Colors.black.withValues(alpha: 0.25), - color: Theme.of(context).colorScheme.primary, - value: progress.clamp(0, 1), - ), - ], + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (constraints.maxWidth > 500) + Flexible( + child: Text( + "${lastPosition.readAbleDuration} / ${playbackInfo.duration.readAbleDuration}"), + ), + Flexible( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: IconButton.filledTonal( + onPressed: () => ref.read(videoPlayerProvider).playOrPause(), + icon: playbackInfo.playing + ? const Icon(Icons.pause_rounded) + : const Icon(Icons.play_arrow_rounded), + ), + ), + ), + Flexible( + child: OverflowView.flexible( + builder: (context, remainingItemCount) => PopupMenuButton( + iconColor: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.45), + padding: EdgeInsets.zero, + itemBuilder: (context) => itemActions + .sublist(itemActions.length - remainingItemCount) + .map( + (e) => e.toPopupMenuItem(useIcons: true), + ) + .toList(), + ), + children: itemActions.map((e) => e.toButton()).toList(), + ), + ) + ], + ), + ) + ], + ), ), ), - ), - ], - ); - }), + AdaptiveLayout.inputDeviceOf(context) == InputDevice.pointer + ? SizedBox( + height: 8, + child: FladderSlider( + value: lastPosition.inMilliseconds.toDouble(), + min: 0.0, + max: playbackInfo.duration.inMilliseconds.toDouble(), + thumbWidth: 8, + onChangeStart: (value) { + setState(() { + changingSliderValue = true; + }); + }, + onChangeEnd: (value) async { + await player.seek(Duration(milliseconds: value ~/ 1)); + await Future.delayed(const Duration(milliseconds: 250)); + if (player.lastState?.playing == true) { + player.play(); + } + setState(() { + lastPosition = Duration(milliseconds: value.toInt()); + changingSliderValue = false; + }); + }, + onChanged: (value) { + setState(() { + lastPosition = Duration(milliseconds: value.toInt()); + }); + }, + ), + ) + : LinearProgressIndicator( + minHeight: 8, + backgroundColor: Colors.black.withValues(alpha: 0.25), + color: Theme.of(context).colorScheme.primary, + value: (playbackInfo.position.inMilliseconds / playbackInfo.duration.inMilliseconds) + .clamp(0, 1), + ) + ], + ); + }), + ), ), ), ), diff --git a/lib/widgets/navigation_scaffold/components/side_navigation_bar.dart b/lib/widgets/navigation_scaffold/components/side_navigation_bar.dart index be63d81..3ba29ae 100644 --- a/lib/widgets/navigation_scaffold/components/side_navigation_bar.dart +++ b/lib/widgets/navigation_scaffold/components/side_navigation_bar.dart @@ -74,13 +74,11 @@ class _SideNavigationBarState extends ConsumerState { child: MouseRegion( child: Column( children: [ - SizedBox(height: padding.top), Expanded( child: Padding( key: const Key('navigation_rail'), - padding: padding.copyWith(right: 0, top: isDesktop ? 8 : null), + padding: padding.copyWith(right: 0, top: isDesktop ? padding.top : null), child: Column( - spacing: 2, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 14), @@ -106,7 +104,6 @@ class _SideNavigationBarState extends ConsumerState { ], ), ), - const SizedBox(height: 8), if (largeBar) ...[ AnimatedFadeSize( duration: const Duration(milliseconds: 250), @@ -290,7 +287,6 @@ class _SideNavigationBarState extends ConsumerState { ), ), ), - if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) const SizedBox(height: 16), ], ), ), diff --git a/lib/widgets/navigation_scaffold/navigation_scaffold.dart b/lib/widgets/navigation_scaffold/navigation_scaffold.dart index c6cbb47..ce54583 100644 --- a/lib/widgets/navigation_scaffold/navigation_scaffold.dart +++ b/lib/widgets/navigation_scaffold/navigation_scaffold.dart @@ -10,7 +10,6 @@ import 'package:fladder/routes/auto_router.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/screens/shared/nested_bottom_appbar.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; -import 'package:fladder/util/theme_extensions.dart'; import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart'; import 'package:fladder/widgets/navigation_scaffold/components/fladder_app_bar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart'; @@ -58,9 +57,15 @@ class _NavigationScaffoldState extends ConsumerState { final isDesktop = AdaptiveLayout.of(context).isDesktop; - final bottomPadding = isDesktop || kIsWeb ? 0.0 : MediaQuery.paddingOf(context).bottom; + final mediaQuery = MediaQuery.of(context); + final paddingOf = mediaQuery.padding; + final viewPaddingOf = mediaQuery.viewPadding; + + final bottomPadding = isDesktop || kIsWeb ? 0.0 : paddingOf.bottom; + final bottomViewPadding = isDesktop || kIsWeb ? 0.0 : viewPaddingOf.bottom; final isHomeScreen = currentIndex != -1; + return PopScope( canPop: currentIndex == 0, onPopInvokedWithResult: (didPop, result) { @@ -72,58 +77,66 @@ class _NavigationScaffoldState extends ConsumerState { alignment: Alignment.bottomCenter, children: [ Positioned.fill( - child: Padding( - padding: EdgeInsets.only(bottom: showPlayerBar ? floatingPlayerHeight - 12 + bottomPadding : 0), - child: Scaffold( - key: _key, - appBar: const FladderAppBar(), - extendBodyBehindAppBar: true, - resizeToAvoidBottomInset: false, - extendBody: true, - floatingActionButton: AdaptiveLayout.layoutModeOf(context) == LayoutMode.single && isHomeScreen - ? widget.destinations.elementAtOrNull(currentIndex)?.floatingActionButton?.normal - : null, - drawer: homeRoutes.any((element) => element.name.contains(currentLocation)) - ? NestedNavigationDrawer( - actionButton: null, - toggleExpanded: (value) => _key.currentState?.closeDrawer(), - views: views, - destinations: widget.destinations, - currentLocation: currentLocation, - ) - : null, - bottomNavigationBar: isHomeScreen && AdaptiveLayout.viewSizeOf(context) == ViewSize.phone - ? HideOnScroll( - controller: AdaptiveLayout.scrollOf(context), - forceHide: !homeRoutes.any((element) => element.name.contains(currentLocation)), - child: NestedBottomAppBar( - child: SizedBox( - height: 65, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: widget.destinations - .map( - (destination) => destination.toNavigationButton( - widget.currentRouteName == destination.route?.routeName, false, false), - ) - .toList(), + child: MediaQuery( + data: mediaQuery.copyWith( + padding: paddingOf.copyWith( + bottom: showPlayerBar ? floatingPlayerHeight(context) + 12 + bottomPadding : bottomPadding), + viewPadding: viewPaddingOf.copyWith( + bottom: showPlayerBar ? floatingPlayerHeight(context) + bottomViewPadding : bottomViewPadding), + ), + //Builder to correctly apply new padding + child: Builder(builder: (context) { + return Scaffold( + key: _key, + appBar: const FladderAppBar(), + extendBodyBehindAppBar: true, + resizeToAvoidBottomInset: false, + extendBody: true, + floatingActionButton: AdaptiveLayout.layoutModeOf(context) == LayoutMode.single && isHomeScreen + ? widget.destinations.elementAtOrNull(currentIndex)?.floatingActionButton?.normal + : null, + drawer: homeRoutes.any((element) => element.name.contains(currentLocation)) + ? NestedNavigationDrawer( + actionButton: null, + toggleExpanded: (value) => _key.currentState?.closeDrawer(), + views: views, + destinations: widget.destinations, + currentLocation: currentLocation, + ) + : null, + bottomNavigationBar: isHomeScreen && AdaptiveLayout.viewSizeOf(context) == ViewSize.phone + ? HideOnScroll( + controller: AdaptiveLayout.scrollOf(context), + forceHide: !homeRoutes.any((element) => element.name.contains(currentLocation)), + child: NestedBottomAppBar( + child: SizedBox( + height: 65, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: widget.destinations + .map( + (destination) => destination.toNavigationButton( + widget.currentRouteName == destination.route?.routeName, false, false), + ) + .toList(), + ), ), ), - ), - ) - : null, - body: widget.nestedChild != null - ? NavigationBody( - child: widget.nestedChild!, - parentContext: context, - currentIndex: currentIndex, - destinations: widget.destinations, - currentLocation: currentLocation, - drawerKey: _key, - ) - : null, - ), + ) + : null, + body: widget.nestedChild != null + ? NavigationBody( + child: widget.nestedChild!, + parentContext: context, + currentIndex: currentIndex, + destinations: widget.destinations, + currentLocation: currentLocation, + drawerKey: _key, + ) + : null, + ); + }), ), ), Material( @@ -131,19 +144,7 @@ class _NavigationScaffoldState extends ConsumerState { child: AnimatedFadeSize( child: Container( width: double.infinity, - decoration: BoxDecoration( - color: context.colors.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(12), - topRight: Radius.circular(12), - ), - ), - child: showPlayerBar - ? Padding( - padding: EdgeInsets.only(bottom: bottomPadding), - child: const FloatingPlayerBar(), - ) - : const SizedBox.shrink(), + child: showPlayerBar ? const FloatingPlayerBar() : const SizedBox.shrink(), ), ), ) diff --git a/lib/widgets/shared/item_actions.dart b/lib/widgets/shared/item_actions.dart index 274cc37..c267f4c 100644 --- a/lib/widgets/shared/item_actions.dart +++ b/lib/widgets/shared/item_actions.dart @@ -10,6 +10,7 @@ abstract class ItemAction { PopupMenuEntry toPopupMenuItem({bool useIcons = false}); Widget toLabel(); Widget toListItem(BuildContext context, {bool useIcons = false, bool shouldPop = true}); + Widget toButton(); } class ItemActionDivider extends ItemAction { @@ -26,6 +27,9 @@ class ItemActionDivider extends ItemAction { @override Widget toListItem(BuildContext context, {bool useIcons = false, bool shouldPop = true}) => const Divider(); + + @override + Widget toButton() => Container(); } class ItemActionButton extends ItemAction { @@ -51,9 +55,10 @@ class ItemActionButton extends ItemAction { } @override - MenuItemButton toMenuItemButton() { - return MenuItemButton(leadingIcon: icon, onPressed: action, child: label); - } + MenuItemButton toMenuItemButton() => MenuItemButton(leadingIcon: icon, onPressed: action, child: label); + + @override + Widget toButton() => IconButton(onPressed: action, icon: icon ?? const SizedBox.shrink()); @override PopupMenuItem toPopupMenuItem({bool useIcons = false}) {