diff --git a/lib/common/view/header_bar.dart b/lib/common/view/header_bar.dart index 191e47583..85bfb1362 100644 --- a/lib/common/view/header_bar.dart +++ b/lib/common/view/header_bar.dart @@ -80,37 +80,45 @@ class HeaderBar extends StatelessWidget : YaruTitleBarStyle.undecorated; } - return YaruWindowTitleBar( - titleSpacing: titleSpacing, - actions: [ - if ((!context.showMasterPanel && Platform.isMacOS) && leading != null) - Padding( - padding: const EdgeInsets.only(left: 10), - child: leading, - ), - ...?actions, - ], - leading: !context.showMasterPanel && Platform.isMacOS ? null : leading, - title: title, - border: BorderSide.none, - backgroundColor: backgroundColor ?? context.theme.scaffoldBackgroundColor, - style: theStyle, - foregroundColor: foregroundColor, - onClose: Platform.isLinux - ? (context) { - switch (closeBtnAction) { - case CloseBtnAction.alwaysAsk: - showDialog( - context: context, - builder: (_) => const CloseWindowActionConfirmDialog(), - ); - case CloseBtnAction.hideToTray: - YaruWindow.hide(context); - case CloseBtnAction.close: - YaruWindow.close(context); + return Theme( + data: context.t.copyWith( + appBarTheme: AppBarTheme.of(context).copyWith( + scrolledUnderElevation: 0, + ), + ), + child: YaruWindowTitleBar( + titleSpacing: titleSpacing, + actions: [ + if ((!context.showMasterPanel && Platform.isMacOS) && leading != null) + Padding( + padding: const EdgeInsets.only(left: 10), + child: leading, + ), + ...?actions, + ], + leading: !context.showMasterPanel && Platform.isMacOS ? null : leading, + title: title, + border: BorderSide.none, + backgroundColor: + backgroundColor ?? context.theme.scaffoldBackgroundColor, + style: theStyle, + foregroundColor: foregroundColor, + onClose: Platform.isLinux + ? (context) { + switch (closeBtnAction) { + case CloseBtnAction.alwaysAsk: + showDialog( + context: context, + builder: (_) => const CloseWindowActionConfirmDialog(), + ); + case CloseBtnAction.hideToTray: + YaruWindow.hide(context); + case CloseBtnAction.close: + YaruWindow.close(context); + } } - } - : null, + : null, + ), ); } diff --git a/lib/player/player_service.dart b/lib/player/player_service.dart index 2cffc67ba..4bed5236e 100644 --- a/lib/player/player_service.dart +++ b/lib/player/player_service.dart @@ -92,6 +92,10 @@ class PlayerService { Audio? get audio => _audio; void _setAudio(Audio value) async { if (value == _audio) return; + if (value.audioType != _audio?.audioType) { + _shuffle = false; + setRate(1); + } _audio = value; _audioController.add(true); setMpvMetaData(null); diff --git a/lib/player/view/bottom_player.dart b/lib/player/view/bottom_player.dart index e1c7efd1b..28e5ebbeb 100644 --- a/lib/player/view/bottom_player.dart +++ b/lib/player/view/bottom_player.dart @@ -43,107 +43,93 @@ class BottomPlayer extends StatelessWidget with WatchItMixin { m.queue.length > 1 || audio?.audioType == AudioType.local, ); - final bottomPlayerImage = ClipRRect( - borderRadius: BorderRadius.circular(4), - child: BottomPlayerImage( - audio: audio, - size: kBottomPlayerHeight - 24, - videoController: model.controller, - isVideo: isVideo, - isOnline: isOnline, - ), - ); - - final titleAndArtist = BottomPlayerTitleArtist( - audio: audio, - ); - - final bottomPlayerControls = PlayerMainControls( - playPrevious: model.playPrevious, - playNext: model.playNext, - active: active, - ); - - final track = PlayerTrack( - active: active, - bottomPlayer: true, - ); - - final bottom = Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + final player = SizedBox( + height: kBottomPlayerHeight, + child: Column( children: [ - Padding( - padding: const EdgeInsets.only(left: 10, right: 20), - child: bottomPlayerImage, + PlayerTrack( + active: active, + bottomPlayer: true, ), - Expanded( - flex: 4, - child: Row( - children: [ - Flexible( - flex: 5, - child: titleAndArtist, - ), - if (!smallWindow) + InkWell( + onTap: () => appModel.setFullWindowMode(true), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ Padding( - padding: const EdgeInsets.only(left: 10), - child: switch (audio?.audioType) { - AudioType.local => LikeIcon( - audio: audio, - color: theme.colorScheme.onSurface, + padding: const EdgeInsets.only(left: 10, right: 20), + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: BottomPlayerImage( + audio: audio, + size: kBottomPlayerHeight - 24, + videoController: model.controller, + isVideo: isVideo, + isOnline: isOnline, + ), + ), + ), + Expanded( + flex: 4, + child: Row( + children: [ + Flexible( + flex: 5, + child: BottomPlayerTitleArtist( + audio: audio, + ), ), - AudioType.radio => RadioLikeIcon(audio: audio), - _ => const SizedBox.shrink(), - }, + if (!smallWindow) + Padding( + padding: const EdgeInsets.only(left: 10), + child: switch (audio?.audioType) { + AudioType.local => LikeIcon( + audio: audio, + color: theme.colorScheme.onSurface, + ), + AudioType.radio => RadioLikeIcon(audio: audio), + _ => const SizedBox.shrink(), + }, + ), + ], + ), ), - ], - ), - ), - if (!smallWindow) - Expanded( - flex: 6, - child: bottomPlayerControls, - ), - if (!smallWindow) - Flexible( - flex: 4, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (audio?.audioType == AudioType.podcast) - PlaybackRateButton(active: active), - const VolumeSliderPopup(), - if (showQueueButton) const QueueButton(), - IconButton( - tooltip: context.l10n.fullWindow, - icon: Icon( - Iconz().fullWindow, - color: theme.colorScheme.onSurface, + if (!smallWindow) + Expanded( + flex: 6, + child: PlayerMainControls(active: active), ), - onPressed: () => appModel.setFullWindowMode(true), + if (!smallWindow) + Flexible( + flex: 4, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (audio?.audioType == AudioType.podcast) + PlaybackRateButton(active: active), + const VolumeSliderPopup(), + if (showQueueButton) const QueueButton(), + IconButton( + tooltip: context.l10n.fullWindow, + icon: Icon( + Iconz().fullWindow, + color: theme.colorScheme.onSurface, + ), + onPressed: () => appModel.setFullWindowMode(true), + ), + ], + ), + ) + else + PlayButton(active: active), + const SizedBox( + width: 10, ), ], ), - ) - else - PlayButton(active: active), - const SizedBox( - width: 10, - ), - ], - ), - ); - - final player = SizedBox( - height: kBottomPlayerHeight, - child: Column( - children: [ - track, - InkWell( - onTap: () => appModel.setFullWindowMode(true), - child: bottom, + ), ), ], ), diff --git a/lib/player/view/full_height_player.dart b/lib/player/view/full_height_player.dart index 79e0fd8b0..7481d81b0 100644 --- a/lib/player/view/full_height_player.dart +++ b/lib/player/view/full_height_player.dart @@ -4,9 +4,11 @@ import 'package:yaru/yaru.dart'; import '../../app/app_model.dart'; import '../../app/connectivity_model.dart'; +import '../../common/data/audio.dart'; import '../../common/view/header_bar.dart'; import '../../extensions/build_context_x.dart'; import '../../player/player_model.dart'; +import '../../radio/view/radio_history_list.dart'; import 'blurred_full_height_player_image.dart'; import 'full_height_player_image.dart'; import 'full_height_player_top_controls.dart'; @@ -15,7 +17,7 @@ import 'full_height_video_player.dart'; import 'player_main_controls.dart'; import 'player_track.dart'; import 'player_view.dart'; -import 'up_next_bubble.dart'; +import 'queue_button.dart'; class FullHeightPlayer extends StatelessWidget with WatchItMixin { const FullHeightPlayer({ @@ -31,74 +33,76 @@ class FullHeightPlayer extends StatelessWidget with WatchItMixin { final size = context.m.size; final isOnline = watchPropertyValue((ConnectivityModel m) => m.isOnline); final appModel = di(); - final nextAudio = watchPropertyValue((PlayerModel m) => m.nextAudio); final audio = watchPropertyValue((PlayerModel m) => m.audio); final isVideo = watchPropertyValue((PlayerModel m) => m.isVideo == true); - final notAlone = watchPropertyValue((PlayerModel m) => m.queue.length > 1); - final showUpNextBubble = notAlone && - nextAudio?.title != null && - nextAudio?.artist != null && - size.width > 600; - final model = di(); + final active = audio?.path != null || isOnline; final iconColor = isVideo ? Colors.white : theme.colorScheme.onSurface; + final playerWithSidePanel = playerPosition == PlayerPosition.fullWindow && + context.m.size.width > 1000; + final Widget bodyWithControls; if (isVideo) { bodyWithControls = FullHeightVideoPlayer( playerPosition: playerPosition, ); } else { + final column = Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const FullHeightPlayerImage(), + const SizedBox( + height: kYaruPagePadding, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: FullHeightTitleAndArtist( + audio: audio, + ), + ), + const SizedBox( + height: kYaruPagePadding, + ), + const SizedBox( + height: kYaruPagePadding, + width: 400, + child: PlayerTrack(), + ), + const SizedBox( + height: kYaruPagePadding, + ), + PlayerMainControls(active: active), + ], + ); + bodyWithControls = Stack( alignment: Alignment.topRight, children: [ Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 35), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const FullHeightPlayerImage(), - const SizedBox( - height: kYaruPagePadding, - ), - FullHeightTitleAndArtist( - audio: audio, - ), - const SizedBox( - height: kYaruPagePadding, - ), - const SizedBox( - height: kYaruPagePadding, - width: 400, - child: PlayerTrack(), - ), - const SizedBox( - height: kYaruPagePadding, - ), - PlayerMainControls( - playPrevious: model.playPrevious, - playNext: model.playNext, - active: active, - ), - ], - ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 490, child: column), + if (playerWithSidePanel) + audio?.audioType == AudioType.radio + ? const SizedBox( + width: 400, + height: 500, + child: RadioHistoryList( + simpleList: true, + ), + ) + : const QueueBody(advancedList: false), + ], ), ), FullHeightPlayerTopControls( iconColor: iconColor, playerPosition: playerPosition, ), - if (showUpNextBubble) - Positioned( - left: 10, - bottom: 10, - child: UpNextBubble( - audio: audio, - nextAudio: nextAudio, - ), - ), ], ); } diff --git a/lib/player/view/full_height_video_player.dart b/lib/player/view/full_height_video_player.dart index 0308b7713..9167cac2c 100644 --- a/lib/player/view/full_height_video_player.dart +++ b/lib/player/view/full_height_video_player.dart @@ -21,7 +21,6 @@ class FullHeightVideoPlayer extends StatelessWidget with WatchItMixin { const baseColor = Colors.white; final audio = watchPropertyValue((PlayerModel m) => m.audio); - final playerModel = di(); final isOnline = watchPropertyValue((ConnectivityModel m) => m.isOnline); final active = audio?.path != null || isOnline; @@ -43,8 +42,6 @@ class FullHeightVideoPlayer extends StatelessWidget with WatchItMixin { width: 300, child: PlayerMainControls( active: active, - playPrevious: playerModel.playNext, - playNext: playerModel.playNext, iconColor: baseColor, avatarColor: baseColor.withOpacity(0.1), ), diff --git a/lib/player/view/player_main_controls.dart b/lib/player/view/player_main_controls.dart index 0df7e433f..f77c846ef 100644 --- a/lib/player/view/player_main_controls.dart +++ b/lib/player/view/player_main_controls.dart @@ -17,15 +17,10 @@ class PlayerMainControls extends StatelessWidget with WatchItMixin { const PlayerMainControls({ super.key, required this.active, - required this.playPrevious, - required this.playNext, this.iconColor, this.avatarColor, }); - final Future Function() playPrevious; - final Future Function() playNext; - final bool active; final Color? iconColor, avatarColor; @@ -55,7 +50,7 @@ class PlayerMainControls extends StatelessWidget with WatchItMixin { IconButton( tooltip: context.l10n.back, color: defaultColor, - onPressed: !active ? null : () => playPrevious(), + onPressed: !active ? null : () => di().playPrevious(), icon: Icon( Iconz().skipBackward, color: defaultColor, @@ -75,7 +70,9 @@ class PlayerMainControls extends StatelessWidget with WatchItMixin { IconButton( tooltip: context.l10n.next, color: defaultColor, - onPressed: !active || queueLength < 2 ? null : () => playNext(), + onPressed: !active || queueLength < 2 + ? null + : () => di().playNext(), icon: Icon( Iconz().skipForward, color: defaultColor, diff --git a/lib/player/view/queue_button.dart b/lib/player/view/queue_button.dart index 8357d620d..e615fd8d9 100644 --- a/lib/player/view/queue_button.dart +++ b/lib/player/view/queue_button.dart @@ -18,7 +18,6 @@ class QueueButton extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.t; - final libraryModel = di(); return IconButton( color: color ?? theme.colorScheme.onSurface, @@ -32,9 +31,7 @@ class QueueButton extends StatelessWidget { showDialog( context: context, builder: (context) { - return QueueDialog( - addPlaylist: libraryModel.addPlaylist, - ); + return const QueueDialog(); }, ); }, @@ -42,20 +39,53 @@ class QueueButton extends StatelessWidget { } } -class QueueDialog extends StatefulWidget with WatchItStatefulWidgetMixin { - const QueueDialog({ +class QueueDialog extends StatelessWidget with WatchItMixin { + const QueueDialog({super.key}); + + @override + Widget build(BuildContext context) { + final queue = watchPropertyValue((PlayerModel m) => m.queue); + final queueLength = watchPropertyValue((PlayerModel m) => m.queue.length); + + return AlertDialog( + key: ValueKey(queueLength), + titlePadding: + const EdgeInsets.only(left: 10, right: 10, top: 20, bottom: 10), + contentPadding: const EdgeInsets.only(bottom: 20, top: 10), + title: const PlayerMainControls(active: true), + actionsAlignment: MainAxisAlignment.center, + actions: [ + OutlinedButton( + onPressed: () { + di().addPlaylist( + '${context.l10n.queue} ${DateTime.now()}', + List.from(queue), + ); + Navigator.of(context).pop(); + }, + child: Text(context.l10n.createNewPlaylist), + ), + ], + content: const QueueBody(), + ); + } +} + +class QueueBody extends StatefulWidget with WatchItStatefulWidgetMixin { + const QueueBody({ super.key, - required this.addPlaylist, + this.advancedList = true, }); - final void Function(String name, List