From 0620b62023fe9a3bb2bb5657d97b9cbf909e7e4c Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 18 Jun 2023 22:13:06 +0600 Subject: [PATCH] refactor: extend list item for PopSheetEntry for better interactivity --- lib/collections/spotube_icons.dart | 1 + lib/components/player/player_actions.dart | 32 +++++- .../adaptive/adaptive_pop_sheet_list.dart | 68 +++++++++---- .../shared/sort_tracks_dropdown.dart | 35 ++----- .../shared/track_table/track_options.dart | 97 ++++++++----------- .../shared/track_table/tracks_table_view.dart | 86 +++++++--------- lib/l10n/app_en.arb | 6 +- lib/pages/player/player.dart | 8 +- 8 files changed, 175 insertions(+), 158 deletions(-) diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index 612f23a1..f5caac09 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -86,4 +86,5 @@ abstract class SpotubeIcons { static const volumeMedium = FeatherIcons.volume1; static const volumeLow = FeatherIcons.volume; static const volumeMute = FeatherIcons.volumeX; + static const timer = FeatherIcons.clock; } diff --git a/lib/components/player/player_actions.dart b/lib/components/player/player_actions.dart index 4026ef4d..59deeda3 100644 --- a/lib/components/player/player_actions.dart +++ b/lib/components/player/player_actions.dart @@ -3,10 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotify/spotify.dart' hide Offset; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/player/player_queue.dart'; import 'package:spotube/components/player/sibling_tracks_sheet.dart'; +import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart'; import 'package:spotube/components/shared/heart_button.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/local_track.dart'; @@ -127,6 +128,35 @@ class PlayerActions extends HookConsumerWidget { ), if (playlist.activeTrack != null && !isLocalTrack && auth != null) TrackHeartButton(track: playlist.activeTrack!), + AdaptivePopSheetList( + offset: const Offset(0, -50 * 5), + headings: [ + Text(context.l10n.sleep_timer), + ], + icon: const Icon(SpotubeIcons.timer), + children: [ + PopSheetEntry( + value: const Duration(minutes: 15), + title: Text(context.l10n.mins(15)), + ), + PopSheetEntry( + value: const Duration(minutes: 30), + title: Text(context.l10n.mins(30)), + ), + PopSheetEntry( + value: const Duration(hours: 1), + title: Text(context.l10n.hour(1)), + ), + PopSheetEntry( + value: const Duration(hours: 2), + title: Text(context.l10n.hours(2)), + ), + PopSheetEntry( + value: Duration.zero, + title: Text(context.l10n.cancel), + ), + ], + ), ...(extraActions ?? []) ], ); diff --git a/lib/components/shared/adaptive/adaptive_pop_sheet_list.dart b/lib/components/shared/adaptive/adaptive_pop_sheet_list.dart index 08cc5551..11fe042b 100644 --- a/lib/components/shared/adaptive/adaptive_pop_sheet_list.dart +++ b/lib/components/shared/adaptive/adaptive_pop_sheet_list.dart @@ -2,17 +2,47 @@ import 'package:flutter/material.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/extensions/constrains.dart'; -class PopSheetEntry { - final T? value; - final VoidCallback? onTap; - final Widget child; - final bool enabled; +_emptyCB() {} +class PopSheetEntry extends ListTile { + final T? value; const PopSheetEntry({ - required this.child, this.value, - this.onTap, - this.enabled = true, + super.key, + super.leading, + super.title, + super.subtitle, + super.trailing, + super.isThreeLine = false, + super.dense, + super.visualDensity, + super.shape, + super.style, + super.selectedColor, + super.iconColor, + super.textColor, + super.titleTextStyle, + super.subtitleTextStyle, + super.leadingAndTrailingTextStyle, + super.contentPadding, + super.enabled = true, + super.onTap = _emptyCB, + super.onLongPress, + super.onFocusChange, + super.mouseCursor, + super.selected = false, + super.focusColor, + super.hoverColor, + super.splashColor, + super.focusNode, + super.autofocus = false, + super.tileColor, + super.selectedTileColor, + super.enableFeedback, + super.horizontalTitleGap, + super.minVerticalPadding, + super.minLeadingWidth, + super.titleAlignment, }); } @@ -30,6 +60,7 @@ class AdaptivePopSheetList extends StatelessWidget { final ValueChanged? onSelected; final BorderRadius borderRadius; + final Offset offset; const AdaptivePopSheetList({ super.key, @@ -41,6 +72,7 @@ class AdaptivePopSheetList extends StatelessWidget { this.onSelected, this.borderRadius = const BorderRadius.all(Radius.circular(999)), this.tooltip, + this.offset = Offset.zero, }) : assert( !(icon != null && child != null), 'Either icon or child must be provided', @@ -55,11 +87,13 @@ class AdaptivePopSheetList extends StatelessWidget { return PopupMenuButton( icon: icon, tooltip: tooltip, + offset: offset, child: child == null ? null : IgnorePointer(child: child), itemBuilder: (context) => children .map( (item) => PopupMenuItem( padding: EdgeInsets.zero, + enabled: false, child: _AdaptivePopSheetListItem( item: item, onSelected: onSelected, @@ -151,8 +185,11 @@ class _AdaptivePopSheetListItem extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); + return InkWell( - borderRadius: BorderRadius.circular(10), + borderRadius: (theme.listTileTheme.shape as RoundedRectangleBorder?) + ?.borderRadius as BorderRadius? ?? + const BorderRadius.all(Radius.circular(10)), onTap: !item.enabled ? null : () { @@ -162,16 +199,9 @@ class _AdaptivePopSheetListItem extends StatelessWidget { onSelected?.call(item.value as T); } }, - child: DefaultTextStyle( - style: TextStyle( - color: item.enabled - ? theme.textTheme.bodyMedium!.color - : theme.textTheme.bodyMedium!.color!.withOpacity(0.5), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: item.child, - ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: IgnorePointer(child: item), ), ); } diff --git a/lib/components/shared/sort_tracks_dropdown.dart b/lib/components/shared/sort_tracks_dropdown.dart index 7695f89a..0547beb0 100644 --- a/lib/components/shared/sort_tracks_dropdown.dart +++ b/lib/components/shared/sort_tracks_dropdown.dart @@ -26,58 +26,37 @@ class SortTracksDropdown extends StatelessWidget { PopSheetEntry( value: SortBy.none, enabled: value != SortBy.none, - child: ListTile( - enabled: value != SortBy.none, - title: Text(context.l10n.none), - ), + title: Text(context.l10n.none), ), PopSheetEntry( value: SortBy.ascending, enabled: value != SortBy.ascending, - child: ListTile( - enabled: value != SortBy.ascending, - title: Text(context.l10n.sort_a_z), - ), + title: Text(context.l10n.sort_a_z), ), PopSheetEntry( value: SortBy.descending, enabled: value != SortBy.descending, - child: ListTile( - enabled: value != SortBy.descending, - title: Text(context.l10n.sort_z_a), - ), + title: Text(context.l10n.sort_z_a), ), PopSheetEntry( value: SortBy.newest, enabled: value != SortBy.newest, - child: ListTile( - enabled: value != SortBy.newest, - title: Text(context.l10n.sort_newest), - ), + title: Text(context.l10n.sort_newest), ), PopSheetEntry( value: SortBy.oldest, enabled: value != SortBy.oldest, - child: ListTile( - enabled: value != SortBy.oldest, - title: Text(context.l10n.sort_oldest), - ), + title: Text(context.l10n.sort_oldest), ), PopSheetEntry( value: SortBy.artist, enabled: value != SortBy.artist, - child: ListTile( - enabled: value != SortBy.artist, - title: Text(context.l10n.sort_artist), - ), + title: Text(context.l10n.sort_artist), ), PopSheetEntry( value: SortBy.album, enabled: value != SortBy.album, - child: ListTile( - enabled: value != SortBy.album, - title: Text(context.l10n.sort_album), - ), + title: Text(context.l10n.sort_album), ), ], headings: [ diff --git a/lib/components/shared/track_table/track_options.dart b/lib/components/shared/track_table/track_options.dart index 8dc094ad..e3b5cee2 100644 --- a/lib/components/shared/track_table/track_options.dart +++ b/lib/components/shared/track_table/track_options.dart @@ -207,102 +207,81 @@ class TrackOptions extends HookConsumerWidget { LocalTrack => [ PopSheetEntry( value: TrackOptionValue.delete, - child: ListTile( - leading: const Icon(SpotubeIcons.trash), - title: Text(context.l10n.delete), - ), + leading: const Icon(SpotubeIcons.trash), + title: Text(context.l10n.delete), ) ], _ => [ if (!playlist.containsTrack(track)) ...[ PopSheetEntry( value: TrackOptionValue.addToQueue, - child: ListTile( - leading: const Icon(SpotubeIcons.queueAdd), - title: Text(context.l10n.add_to_queue), - ), + leading: const Icon(SpotubeIcons.queueAdd), + title: Text(context.l10n.add_to_queue), ), PopSheetEntry( value: TrackOptionValue.playNext, - child: ListTile( - leading: const Icon(SpotubeIcons.lightning), - title: Text(context.l10n.play_next), - ), + leading: const Icon(SpotubeIcons.lightning), + title: Text(context.l10n.play_next), ), ] else PopSheetEntry( value: TrackOptionValue.removeFromQueue, enabled: playlist.activeTrack?.id != track.id, - child: ListTile( - enabled: playlist.activeTrack?.id != track.id, - leading: const Icon(SpotubeIcons.queueRemove), - title: Text(context.l10n.remove_from_queue), - ), + leading: const Icon(SpotubeIcons.queueRemove), + title: Text(context.l10n.remove_from_queue), ), if (favorites.me.hasData) PopSheetEntry( value: TrackOptionValue.favorite, - child: ListTile( - leading: favorites.isLiked - ? const Icon( - SpotubeIcons.heartFilled, - color: Colors.pink, - ) - : const Icon(SpotubeIcons.heart), - title: Text( - favorites.isLiked - ? context.l10n.remove_from_favorites - : context.l10n.save_as_favorite, - ), + leading: favorites.isLiked + ? const Icon( + SpotubeIcons.heartFilled, + color: Colors.pink, + ) + : const Icon(SpotubeIcons.heart), + title: Text( + favorites.isLiked + ? context.l10n.remove_from_favorites + : context.l10n.save_as_favorite, ), ), if (auth != null) PopSheetEntry( value: TrackOptionValue.addToPlaylist, - child: ListTile( - leading: const Icon(SpotubeIcons.playlistAdd), - title: Text(context.l10n.add_to_playlist), - ), + leading: const Icon(SpotubeIcons.playlistAdd), + title: Text(context.l10n.add_to_playlist), ), if (userPlaylist && auth != null) PopSheetEntry( value: TrackOptionValue.removeFromPlaylist, - child: ListTile( - leading: (removeTrack.isMutating || !removeTrack.hasData) && - removingTrack.value == track.uri - ? const Center( - child: CircularProgressIndicator(), - ) - : const Icon(SpotubeIcons.removeFilled), - title: Text(context.l10n.remove_from_playlist), - ), + leading: (removeTrack.isMutating || !removeTrack.hasData) && + removingTrack.value == track.uri + ? const Center( + child: CircularProgressIndicator(), + ) + : const Icon(SpotubeIcons.removeFilled), + title: Text(context.l10n.remove_from_playlist), ), PopSheetEntry( value: TrackOptionValue.blacklist, - child: ListTile( - leading: const Icon(SpotubeIcons.playlistRemove), - iconColor: !isBlackListed ? Colors.red[400] : null, - textColor: !isBlackListed ? Colors.red[400] : null, - title: Text( - isBlackListed - ? context.l10n.remove_from_blacklist - : context.l10n.add_to_blacklist, - ), + leading: const Icon(SpotubeIcons.playlistRemove), + iconColor: !isBlackListed ? Colors.red[400] : null, + textColor: !isBlackListed ? Colors.red[400] : null, + title: Text( + isBlackListed + ? context.l10n.remove_from_blacklist + : context.l10n.add_to_blacklist, ), ), PopSheetEntry( value: TrackOptionValue.share, - child: ListTile( - leading: const Icon(SpotubeIcons.share), - title: Text(context.l10n.share), - ), + leading: const Icon(SpotubeIcons.share), + title: Text(context.l10n.share), ), PopSheetEntry( value: TrackOptionValue.details, - child: ListTile( - leading: const Icon(SpotubeIcons.info), - title: Text(context.l10n.details), - ), + leading: const Icon(SpotubeIcons.info), + title: Text(context.l10n.details), ), ] }, diff --git a/lib/components/shared/track_table/tracks_table_view.dart b/lib/components/shared/track_table/tracks_table_view.dart index 8f20b855..5c4333e7 100644 --- a/lib/components/shared/track_table/tracks_table_view.dart +++ b/lib/components/shared/track_table/tracks_table_view.dart @@ -185,55 +185,6 @@ class TracksTableView extends HookConsumerWidget { style: tableHeadStyle, ), ], - children: [ - PopSheetEntry( - enabled: selectedTracks.isNotEmpty, - value: "download", - child: ListTile( - leading: const Icon(SpotubeIcons.download), - enabled: selectedTracks.isNotEmpty, - title: Text( - context.l10n.download_count(selectedTracks.length), - ), - ), - ), - if (!userPlaylist) - PopSheetEntry( - enabled: selectedTracks.isNotEmpty, - value: "add-to-playlist", - child: ListTile( - leading: const Icon(SpotubeIcons.playlistAdd), - enabled: selectedTracks.isNotEmpty, - title: Text( - context.l10n - .add_count_to_playlist(selectedTracks.length), - ), - ), - ), - PopSheetEntry( - enabled: selectedTracks.isNotEmpty, - value: "add-to-queue", - child: ListTile( - leading: const Icon(SpotubeIcons.queueAdd), - enabled: selectedTracks.isNotEmpty, - title: Text( - context.l10n - .add_count_to_queue(selectedTracks.length), - ), - ), - ), - PopSheetEntry( - enabled: selectedTracks.isNotEmpty, - value: "play-next", - child: ListTile( - leading: const Icon(SpotubeIcons.lightning), - enabled: selectedTracks.isNotEmpty, - title: Text( - context.l10n.play_count_next(selectedTracks.length), - ), - ), - ), - ], onSelected: (action) async { switch (action) { case "download": @@ -283,6 +234,43 @@ class TracksTableView extends HookConsumerWidget { } }, icon: const Icon(SpotubeIcons.moreVertical), + children: [ + PopSheetEntry( + value: "download", + leading: const Icon(SpotubeIcons.download), + enabled: selectedTracks.isNotEmpty, + title: Text( + context.l10n.download_count(selectedTracks.length), + ), + ), + if (!userPlaylist) + PopSheetEntry( + value: "add-to-playlist", + leading: const Icon(SpotubeIcons.playlistAdd), + enabled: selectedTracks.isNotEmpty, + title: Text( + context.l10n + .add_count_to_playlist(selectedTracks.length), + ), + ), + PopSheetEntry( + enabled: selectedTracks.isNotEmpty, + value: "add-to-queue", + leading: const Icon(SpotubeIcons.queueAdd), + title: Text( + context.l10n + .add_count_to_queue(selectedTracks.length), + ), + ), + PopSheetEntry( + enabled: selectedTracks.isNotEmpty, + value: "play-next", + leading: const Icon(SpotubeIcons.lightning), + title: Text( + context.l10n.play_count_next(selectedTracks.length), + ), + ), + ], ), const SizedBox(width: 10), ], diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3813a389..f9244468 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -239,5 +239,9 @@ "streamUrl": "Stream URL", "stop": "Stop", "sort_newest": "Sort by newest added", - "sort_oldest": "Sort by oldest added" + "sort_oldest": "Sort by oldest added", + "sleep_timer": "Sleep Timer", + "mins": "{minutes} Minutes", + "hours": "{hours} Hours", + "hour": "{hours} Hour" } \ No newline at end of file diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 61b54d72..8e7750f4 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -197,6 +197,9 @@ class PlayerView extends HookConsumerWidget { label: Text(context.l10n.details), style: OutlinedButton.styleFrom( foregroundColor: bodyTextColor, + side: BorderSide( + color: bodyTextColor ?? Colors.white, + ), ), onPressed: currentTrack == null ? null @@ -218,6 +221,9 @@ class PlayerView extends HookConsumerWidget { icon: const Icon(SpotubeIcons.music), style: OutlinedButton.styleFrom( foregroundColor: bodyTextColor, + side: BorderSide( + color: bodyTextColor ?? Colors.white, + ), ), onPressed: () { showModalBottomSheet( @@ -257,7 +263,7 @@ class PlayerView extends HookConsumerWidget { overlayColor: titleTextColor?.withOpacity(0.2), trackHeight: 2, thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6, + enabledThumbRadius: 8, ), ), child: const Padding(