mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: extend list item for PopSheetEntry for better interactivity
This commit is contained in:
parent
b4713e377a
commit
0620b62023
@ -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;
|
||||
}
|
||||
|
@ -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 ?? [])
|
||||
],
|
||||
);
|
||||
|
@ -2,17 +2,47 @@ import 'package:flutter/material.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
|
||||
class PopSheetEntry<T> {
|
||||
final T? value;
|
||||
final VoidCallback? onTap;
|
||||
final Widget child;
|
||||
final bool enabled;
|
||||
_emptyCB() {}
|
||||
|
||||
class PopSheetEntry<T> 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<T> extends StatelessWidget {
|
||||
final ValueChanged<T>? onSelected;
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
final Offset offset;
|
||||
|
||||
const AdaptivePopSheetList({
|
||||
super.key,
|
||||
@ -41,6 +72,7 @@ class AdaptivePopSheetList<T> 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<T> 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<T> 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<T> 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: IgnorePointer(child: item),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -25,60 +25,39 @@ class SortTracksDropdown extends StatelessWidget {
|
||||
children: [
|
||||
PopSheetEntry(
|
||||
value: SortBy.none,
|
||||
enabled: value != SortBy.none,
|
||||
child: ListTile(
|
||||
enabled: value != SortBy.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),
|
||||
),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: SortBy.descending,
|
||||
enabled: value != SortBy.descending,
|
||||
child: ListTile(
|
||||
enabled: value != SortBy.descending,
|
||||
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),
|
||||
),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: SortBy.oldest,
|
||||
enabled: value != SortBy.oldest,
|
||||
child: ListTile(
|
||||
enabled: value != SortBy.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),
|
||||
),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: SortBy.album,
|
||||
enabled: value != SortBy.album,
|
||||
child: ListTile(
|
||||
enabled: value != SortBy.album,
|
||||
title: Text(context.l10n.sort_album),
|
||||
),
|
||||
),
|
||||
],
|
||||
headings: [
|
||||
Text(context.l10n.sort_tracks),
|
||||
|
@ -207,42 +207,32 @@ class TrackOptions extends HookConsumerWidget {
|
||||
LocalTrack => [
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.delete,
|
||||
child: ListTile(
|
||||
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),
|
||||
),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.playNext,
|
||||
child: ListTile(
|
||||
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),
|
||||
),
|
||||
),
|
||||
if (favorites.me.hasData)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.favorite,
|
||||
child: ListTile(
|
||||
leading: favorites.isLiked
|
||||
? const Icon(
|
||||
SpotubeIcons.heartFilled,
|
||||
@ -255,19 +245,15 @@ class TrackOptions extends HookConsumerWidget {
|
||||
: 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),
|
||||
),
|
||||
),
|
||||
if (userPlaylist && auth != null)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.removeFromPlaylist,
|
||||
child: ListTile(
|
||||
leading: (removeTrack.isMutating || !removeTrack.hasData) &&
|
||||
removingTrack.value == track.uri
|
||||
? const Center(
|
||||
@ -276,10 +262,8 @@ class TrackOptions extends HookConsumerWidget {
|
||||
: 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,
|
||||
@ -289,21 +273,16 @@ class TrackOptions extends HookConsumerWidget {
|
||||
: context.l10n.add_to_blacklist,
|
||||
),
|
||||
),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.share,
|
||||
child: ListTile(
|
||||
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),
|
||||
),
|
||||
),
|
||||
]
|
||||
},
|
||||
),
|
||||
|
@ -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),
|
||||
],
|
||||
|
@ -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"
|
||||
}
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user