diff --git a/lib/common/view/audio_tile_bottom_sheet.dart b/lib/common/view/audio_tile_bottom_sheet.dart index f564b0de..5fefd4ba 100644 --- a/lib/common/view/audio_tile_bottom_sheet.dart +++ b/lib/common/view/audio_tile_bottom_sheet.dart @@ -1,10 +1,14 @@ import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:watch_it/watch_it.dart'; -import 'ui_constants.dart'; import '../../constants.dart'; import '../../extensions/build_context_x.dart'; import '../../l10n/l10n.dart'; import '../../library/library_model.dart'; +import '../../local_audio/local_audio_model.dart'; +import '../../local_audio/view/album_page.dart'; +import '../../local_audio/view/artist_page.dart'; import '../../player/player_model.dart'; import '../../playlists/view/add_to_playlist_dialog.dart'; import '../data/audio.dart'; @@ -14,12 +18,11 @@ import 'icons.dart'; import 'like_all_icon.dart'; import 'like_icon.dart'; import 'meta_data_dialog.dart'; -import 'package:flutter/material.dart'; -import 'package:watch_it/watch_it.dart'; import 'snackbars.dart'; import 'spaced_divider.dart'; import 'stream_provider_share_button.dart'; import 'theme.dart'; +import 'ui_constants.dart'; class AudioTileBottomSheet extends StatelessWidget { const AudioTileBottomSheet({ @@ -179,6 +182,56 @@ class AudioTileBottomSheet extends StatelessWidget { child: ListView( shrinkWrap: true, children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric( + vertical: kSmallestSpace, + horizontal: kLargestSpace, + ), + leading: Icon(Iconz.artist), + minLeadingWidth: 2 * kLargestSpace, + title: Text(l10n.showArtistPage), + onTap: () { + final artistId = audios.firstOrNull?.artist; + if (artistId != null) { + final artistAudios = di() + .findTitlesOfArtist(artistId); + if (artistAudios != null) { + libraryModel.push( + pageId: artistId, + builder: (c) => ArtistPage( + artistAudios: artistAudios, + ), + ); + } + } + }, + ), + ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: kLargestSpace, + vertical: kSmallestSpace, + ), + leading: Icon(Iconz.album), + minLeadingWidth: 2 * kLargestSpace, + title: Text(l10n.showAlbumPage), + onTap: () { + final albumId = audios.firstOrNull?.albumId; + final albumName = audios.firstOrNull?.album; + if (albumName != null) { + final albumAudios = + di().findAlbum(albumName); + if (albumId != null && albumAudios != null) { + libraryModel.push( + pageId: albumId, + builder: (context) => AlbumPage( + id: albumId, + album: albumAudios, + ), + ); + } + } + }, + ), StreamProviderShareButton( streamProvider: StreamProvider.youTubeMusic, text: searchTerm, diff --git a/lib/common/view/icons.dart b/lib/common/view/icons.dart index ad721a42..b8d73240 100644 --- a/lib/common/view/icons.dart +++ b/lib/common/view/icons.dart @@ -72,6 +72,16 @@ class Iconz { : appleStyled ? CupertinoIcons.double_music_note : Icons.music_note_rounded; + static IconData get artist => yaruStyled + ? YaruIcons.music_artist + : appleStyled + ? CupertinoIcons.music_albums + : Icons.interpreter_mode_outlined; + static IconData get album => yaruStyled + ? YaruIcons.disk + : appleStyled + ? CupertinoIcons.music_albums + : Icons.album_rounded; static IconData get external => yaruStyled ? YaruIcons.external_link : appleStyled diff --git a/lib/common/view/stream_provider_share_button.dart b/lib/common/view/stream_provider_share_button.dart index 440a91de..e8116ad9 100644 --- a/lib/common/view/stream_provider_share_button.dart +++ b/lib/common/view/stream_provider_share_button.dart @@ -6,6 +6,7 @@ import 'package:watch_it/watch_it.dart'; import '../../app/app_model.dart'; import '../../l10n/l10n.dart'; import 'icons.dart'; +import 'ui_constants.dart'; class StreamProviderShareButton extends StatelessWidget { const StreamProviderShareButton({ @@ -49,9 +50,10 @@ class StreamProviderShareButton extends StatelessWidget { if (tile) { return ListTile( contentPadding: const EdgeInsets.symmetric( - horizontal: 15, - vertical: 5, + horizontal: kLargestSpace, + vertical: kSmallestSpace, ), + minLeadingWidth: 2 * kLargestSpace, leading: Icon(iconData), title: Text('$tooltip ${context.l10n.search}'), onTap: () => launchUrl( diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 81134f4f..467c215c 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -43,6 +43,8 @@ "titles": "Titel", "artist": "Künstler", "artists": "Künstler", + "showArtistPage": "Zeige Künstlerseite", + "showAlbumPage": "Zeige Albumseite", "album": "Album", "albums": "Alben", "genres": "Genres", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5cac6207..29d7f4b9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -43,6 +43,8 @@ "titles": "Titles", "artist": "Artist", "artists": "Artists", + "showArtistPage": "Show artist page", + "showAlbumPage": "Show album page", "album": "Album", "albums": "Albums", "genres": "Genres", diff --git a/lib/library/library_model.dart b/lib/library/library_model.dart index 7c6aad65..a8990c36 100644 --- a/lib/library/library_model.dart +++ b/lib/library/library_model.dart @@ -201,15 +201,18 @@ class LibraryModel extends SafeChangeNotifier implements NavigatorObserver { await _masterNavigatorKey.currentState?.pushNamed(pageId); } } else if (builder != null) { - final materialPageRoute = MaterialPageRoute( - builder: (context) => isMobilePlatform - ? MobilePage(page: builder(context)) - : BackGesture(child: builder(context)), + final materialPageRoute = PageRouteBuilder( maintainState: maintainState, settings: RouteSettings( name: pageId, ), + pageBuilder: (context, __, ___) => isMobilePlatform + ? MobilePage(page: builder(context)) + : BackGesture(child: builder(context)), + transitionsBuilder: (_, a, __, c) => + FadeTransition(opacity: a, child: c), ); + if (replace) { await _masterNavigatorKey.currentState?.pushReplacement( materialPageRoute, diff --git a/lib/player/view/bottom_player.dart b/lib/player/view/bottom_player.dart index 93b3151c..82e4264b 100644 --- a/lib/player/view/bottom_player.dart +++ b/lib/player/view/bottom_player.dart @@ -20,7 +20,7 @@ import 'player_main_controls.dart'; import 'player_title_and_artist.dart'; import 'player_track.dart'; import 'player_view.dart'; -import 'queue_button.dart'; +import 'queue/queue_button.dart'; import 'volume_popup.dart'; class BottomPlayer extends StatelessWidget with WatchItMixin { diff --git a/lib/player/view/full_height_player.dart b/lib/player/view/full_height_player.dart index 7a3a35bd..5036d6e5 100644 --- a/lib/player/view/full_height_player.dart +++ b/lib/player/view/full_height_player.dart @@ -18,7 +18,8 @@ import 'player_main_controls.dart'; import 'player_title_and_artist.dart'; import 'player_track.dart'; import 'player_view.dart'; -import 'queue_button.dart'; +import 'queue/queue_body.dart'; +import 'queue/queue_button.dart'; class FullHeightPlayer extends StatelessWidget with WatchItMixin { const FullHeightPlayer({ diff --git a/lib/player/view/full_height_player_top_controls.dart b/lib/player/view/full_height_player_top_controls.dart index 2ed01d3c..106f07e7 100644 --- a/lib/player/view/full_height_player_top_controls.dart +++ b/lib/player/view/full_height_player_top_controls.dart @@ -19,7 +19,7 @@ import '../../player/player_model.dart'; import '../../search/search_model.dart'; import 'playback_rate_button.dart'; import 'player_view.dart'; -import 'queue_button.dart'; +import 'queue/queue_button.dart'; import 'volume_popup.dart'; class FullHeightPlayerTopControls extends StatelessWidget with WatchItMixin { diff --git a/lib/player/view/full_height_video_player.dart b/lib/player/view/full_height_video_player.dart index bad776a6..20c85c55 100644 --- a/lib/player/view/full_height_video_player.dart +++ b/lib/player/view/full_height_video_player.dart @@ -55,7 +55,10 @@ class FullHeightVideoPlayer extends StatelessWidget with WatchItMixin { ), ], seekBarMargin: const EdgeInsets.all(kLargestSpace), - topButtonBarMargin: const EdgeInsets.only(right: kLargestSpace), + topButtonBarMargin: EdgeInsets.only( + right: kLargestSpace, + top: isMobilePlatform ? 2 * kLargestSpace : 0, + ), topButtonBar: [ const Spacer(), controls, diff --git a/lib/player/view/queue_button.dart b/lib/player/view/queue/queue_body.dart similarity index 60% rename from lib/player/view/queue_button.dart rename to lib/player/view/queue/queue_body.dart index eac2d059..b00932f4 100644 --- a/lib/player/view/queue_button.dart +++ b/lib/player/view/queue/queue_body.dart @@ -2,124 +2,10 @@ import 'package:flutter/material.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:watch_it/watch_it.dart'; -import '../../app/app_model.dart'; -import '../../app_config.dart'; -import '../../common/data/audio.dart'; -import '../../common/data/audio_type.dart'; -import '../../common/view/icons.dart'; -import '../../common/view/modals.dart'; -import '../../common/view/ui_constants.dart'; -import '../../extensions/build_context_x.dart'; -import '../../l10n/l10n.dart'; -import '../../library/library_model.dart'; -import '../player_model.dart'; -import 'player_main_controls.dart'; - -enum _Mode { - icon, - text; -} - -class QueueButton extends StatelessWidget with WatchItMixin { - const QueueButton({super.key, this.color, this.isSelected}) - : _mode = _Mode.icon; - - const QueueButton.text({super.key, this.color, this.isSelected}) - : _mode = _Mode.text; - - final Color? color; - final bool? isSelected; - - final _Mode _mode; - - @override - Widget build(BuildContext context) { - final theme = context.theme; - final playerToTheRight = context.mediaQuerySize.width > kSideBarThreshHold; - final isFullScreen = watchPropertyValue((AppModel m) => m.fullWindowMode); - final radio = watchPropertyValue( - (PlayerModel m) => m.audio?.audioType == AudioType.radio, - ); - - return switch (_mode) { - _Mode.icon => IconButton( - isSelected: isSelected ?? - watchPropertyValue((AppModel m) => m.showQueueOverlay), - color: color ?? theme.colorScheme.onSurface, - padding: EdgeInsets.zero, - tooltip: radio ? context.l10n.hearingHistory : context.l10n.queue, - icon: Icon( - Iconz.playlist, - color: color ?? theme.colorScheme.onSurface, - ), - onPressed: () => onPressed(playerToTheRight, isFullScreen, context), - ), - _Mode.text => TextButton( - onPressed: () => onPressed(playerToTheRight, isFullScreen, context), - child: Text( - context.l10n.queue, - style: context.textTheme.bodyLarge?.copyWith( - color: context.colorScheme.onSurface, - ), - ), - ) - }; - } - - void onPressed( - bool playerToTheRight, - bool? isFullScreen, - BuildContext context, - ) { - if ((playerToTheRight || isFullScreen == true) && !isMobilePlatform) { - di().setOrToggleQueueOverlay(); - } else { - showModal( - context: context, - isScrollControlled: true, - showDragHandle: true, - content: ModalMode.platformModalMode == ModalMode.bottomSheet - ? const QueueBody() - : const QueueDialog(), - mode: ModalMode.platformModalMode, - ); - } - } -} - -class QueueDialog extends StatelessWidget with WatchItMixin { - const QueueDialog({super.key}); - - @override - Widget build(BuildContext context) { - final queue = watchPropertyValue((PlayerModel m) => m.queue); - - return AlertDialog( - titlePadding: const EdgeInsets.only( - left: 10, - right: 10, - top: kLargestSpace, - bottom: 10, - ), - contentPadding: const EdgeInsets.only(bottom: kLargestSpace, 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(), - ); - } -} +import '../../../common/data/audio.dart'; +import '../../../common/view/icons.dart'; +import '../../../extensions/build_context_x.dart'; +import '../../player_model.dart'; class QueueBody extends StatefulWidget with WatchItStatefulWidgetMixin { const QueueBody({ @@ -200,7 +86,7 @@ class _QueueBodyState extends State { key: ValueKey(index), index: index, controller: _controller, - selectedColor: context.colorScheme.primary, + selectedColor: context.colorScheme.onSurface, queueName: queueName, queue: queue, audio: audio, diff --git a/lib/player/view/queue/queue_button.dart b/lib/player/view/queue/queue_button.dart new file mode 100644 index 00000000..573d4aac --- /dev/null +++ b/lib/player/view/queue/queue_button.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:watch_it/watch_it.dart'; + +import '../../../app/app_model.dart'; +import '../../../app_config.dart'; +import '../../../common/data/audio_type.dart'; +import '../../../common/view/icons.dart'; +import '../../../common/view/modals.dart'; +import '../../../common/view/ui_constants.dart'; +import '../../../extensions/build_context_x.dart'; +import '../../../l10n/l10n.dart'; +import '../../player_model.dart'; +import 'queue_body.dart'; +import 'queue_dialog.dart'; + +class QueueButton extends StatelessWidget with WatchItMixin { + const QueueButton({super.key, this.color, this.isSelected}) + : _mode = _QueueButtonMode.icon; + + const QueueButton.text({super.key, this.color, this.isSelected}) + : _mode = _QueueButtonMode.text; + + final Color? color; + final bool? isSelected; + + final _QueueButtonMode _mode; + + @override + Widget build(BuildContext context) { + final theme = context.theme; + final playerToTheRight = context.mediaQuerySize.width > kSideBarThreshHold; + final isFullScreen = watchPropertyValue((AppModel m) => m.fullWindowMode); + final radio = watchPropertyValue( + (PlayerModel m) => m.audio?.audioType == AudioType.radio, + ); + + return switch (_mode) { + _QueueButtonMode.icon => IconButton( + isSelected: isSelected ?? + watchPropertyValue((AppModel m) => m.showQueueOverlay), + color: color ?? theme.colorScheme.onSurface, + padding: EdgeInsets.zero, + tooltip: radio ? context.l10n.hearingHistory : context.l10n.queue, + icon: Icon( + Iconz.playlist, + color: color ?? theme.colorScheme.onSurface, + ), + onPressed: () => onPressed(playerToTheRight, isFullScreen, context), + ), + _QueueButtonMode.text => TextButton( + onPressed: () => onPressed(playerToTheRight, isFullScreen, context), + child: Text( + context.l10n.queue, + style: context.textTheme.bodyLarge?.copyWith( + color: context.colorScheme.onSurface, + ), + ), + ) + }; + } + + void onPressed( + bool playerToTheRight, + bool? isFullScreen, + BuildContext context, + ) { + if ((playerToTheRight || isFullScreen == true) && !isMobilePlatform) { + di().setOrToggleQueueOverlay(); + } else { + showModal( + context: context, + isScrollControlled: true, + showDragHandle: true, + content: ModalMode.platformModalMode == ModalMode.bottomSheet + ? const QueueBody() + : const QueueDialog(), + mode: ModalMode.platformModalMode, + ); + } + } +} + +enum _QueueButtonMode { + icon, + text; +} diff --git a/lib/player/view/queue/queue_dialog.dart b/lib/player/view/queue/queue_dialog.dart new file mode 100644 index 00000000..b9df2f72 --- /dev/null +++ b/lib/player/view/queue/queue_dialog.dart @@ -0,0 +1,42 @@ +import '../../../common/view/ui_constants.dart'; +import '../../../l10n/l10n.dart'; +import '../../../library/library_model.dart'; +import '../../player_model.dart'; +import '../player_main_controls.dart'; +import 'package:flutter/material.dart'; +import 'package:watch_it/watch_it.dart'; +import 'queue_body.dart'; + +class QueueDialog extends StatelessWidget with WatchItMixin { + const QueueDialog({super.key}); + + @override + Widget build(BuildContext context) { + final queue = watchPropertyValue((PlayerModel m) => m.queue); + + return AlertDialog( + titlePadding: const EdgeInsets.only( + left: 10, + right: 10, + top: kLargestSpace, + bottom: 10, + ), + contentPadding: const EdgeInsets.only(bottom: kLargestSpace, 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(), + ); + } +} diff --git a/needs_translation.json b/needs_translation.json index c1d7ccae..e7f03ddc 100644 --- a/needs_translation.json +++ b/needs_translation.json @@ -3,6 +3,8 @@ "home", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "downloadsDirectory", "downloadsDirectoryDescription", "downloadsChangeWarning", @@ -303,6 +305,8 @@ "home", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "insertedIntoQueue", "downloadsDirectory", "downloadsDirectoryDescription", @@ -614,13 +618,22 @@ ], "es": [ - "home" + "home", + "showArtistPage", + "showAlbumPage" + ], + + "eu": [ + "showArtistPage", + "showAlbumPage" ], "fr": [ "home", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "downloadsDirectory", "downloadsDirectoryDescription", "downloadsChangeWarning", @@ -648,8 +661,15 @@ "regionNone" ], + "it": [ + "showArtistPage", + "showAlbumPage" + ], + "nl": [ "home", + "showArtistPage", + "showAlbumPage", "noStationFound", "nothingFound", "noStarredStations", @@ -1121,6 +1141,8 @@ "share", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "stations", "downloadsDirectory", "downloadsDirectoryDescription", @@ -1372,6 +1394,8 @@ "share", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "genre", "year", "albumArtist", @@ -1863,6 +1887,8 @@ "share", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "genre", "year", "albumArtist", @@ -2337,6 +2363,8 @@ "ru": [ "home", + "showArtistPage", + "showAlbumPage", "clicks", "regionAruba", "regionAustralia", @@ -2577,10 +2605,22 @@ "regionZimbabwe" ], + "sk": [ + "showArtistPage", + "showAlbumPage" + ], + + "sv": [ + "showArtistPage", + "showAlbumPage" + ], + "tr": [ "home", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "downloadsDirectory", "downloadsDirectoryDescription", "downloadsChangeWarning", @@ -2880,6 +2920,8 @@ "zh": [ "home", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "useMoreAnimationsTitle", "useMoreAnimationsDescription", "showPositionDurationTitle", @@ -2900,6 +2942,8 @@ "home", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "downloadsDirectory", "downloadsDirectoryDescription", "downloadsChangeWarning", @@ -3192,6 +3236,8 @@ "home", "local", "saveAndAuthorize", + "showArtistPage", + "showAlbumPage", "downloadsDirectory", "downloadsDirectoryDescription", "downloadsChangeWarning",