Skip to content

Commit

Permalink
feat: add or remove track, playlist or album to queue support
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Feb 5, 2023
1 parent 9bad8c9 commit b8f3493
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 45 deletions.
2 changes: 2 additions & 0 deletions lib/collections/spotube_icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ abstract class SpotubeIcons {
static const heart = FeatherIcons.heart;
static const heartFilled = Icons.favorite_rounded;
static const queue = Icons.queue_music_rounded;
static const queueAdd = Icons.add_to_photos_outlined;
static const queueRemove = Icons.remove_outlined;
static const download = FeatherIcons.download;
static const done = FeatherIcons.checkCircle;
static const alternativeRoute = Icons.alt_route_rounded;
Expand Down
92 changes: 61 additions & 31 deletions lib/components/album/album_card.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'package:fl_query/fl_query.dart';
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:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/provider/playlist_queue_provider.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:uuid/uuid.dart';

class AlbumCard extends HookConsumerWidget {
final Album album;
Expand All @@ -23,39 +26,66 @@ class AlbumCard extends HookConsumerWidget {
final playlist = ref.watch(PlaylistQueueNotifier.provider);
final playing = useStream(PlaylistQueueNotifier.playing).data ?? false;
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
bool isPlaylistPlaying = playlistNotifier.isPlayingPlaylist(album.tracks!);
final queryBowl = QueryBowl.of(context);
final query = queryBowl.getQuery<List<TrackSimple>, SpotifyApi>(
Queries.album.tracksOf(album.id!).queryKey);
final tracks = useState(query?.data ?? album.tracks ?? <Track>[]);
bool isPlaylistPlaying = playlistNotifier.isPlayingPlaylist(tracks.value);
final int marginH =
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
return PlaybuttonCard(
imageUrl: TypeConversionUtils.image_X_UrlString(
album.images,
placeholder: ImagePlaceholder.collection,
),
viewType: viewType,
margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()),
isPlaying: isPlaylistPlaying && playing,
isLoading: isPlaylistPlaying && playlist?.isLoading == true,
title: album.name!,
description:
"Album • ${TypeConversionUtils.artists_X_String<ArtistSimple>(album.artists ?? [])}",
onTap: () {
ServiceUtils.navigate(context, "/album/${album.id}", extra: album);
},
onPlaybuttonPressed: () async {
SpotifyApi spotify = ref.read(spotifyProvider);
if (isPlaylistPlaying && playing) {
return playlistNotifier.pause();
} else if (isPlaylistPlaying && !playing) {
return playlistNotifier.resume();
}
List<Track> tracks = (await spotify.albums.getTracks(album.id!).all())
.map((track) =>
TypeConversionUtils.simpleTrack_X_Track(track, album))
.toList();
if (tracks.isEmpty) return;
imageUrl: TypeConversionUtils.image_X_UrlString(
album.images,
placeholder: ImagePlaceholder.collection,
),
viewType: viewType,
margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()),
isPlaying: isPlaylistPlaying && playing,
isLoading: isPlaylistPlaying && playlist?.isLoading == true,
title: album.name!,
description:
"Album • ${TypeConversionUtils.artists_X_String<ArtistSimple>(album.artists ?? [])}",
onTap: () {
ServiceUtils.navigate(context, "/album/${album.id}", extra: album);
},
onPlaybuttonPressed: () async {
if (isPlaylistPlaying && playing) {
return playlistNotifier.pause();
} else if (isPlaylistPlaying && !playing) {
return playlistNotifier.resume();
}

await playlistNotifier.loadAndPlay(album.tracks
?.map(
(e) => TypeConversionUtils.simpleTrack_X_Track(e, album))
.toList() ??
[]);
},
onAddToQueuePressed: () async {
if (isPlaylistPlaying) {
return;
}

final fetchedTracks =
await queryBowl.fetchQuery<List<TrackSimple>, SpotifyApi>(
Queries.album.tracksOf(album.id!),
externalData: ref.read(spotifyProvider),
key: ValueKey(const Uuid().v4()),
);

if (fetchedTracks == null || fetchedTracks.isEmpty) return;

await playlistNotifier.loadAndPlay(tracks);
},
);
playlistNotifier.add(
fetchedTracks
.map((e) => TypeConversionUtils.simpleTrack_X_Track(e, album))
.toList(),
);
tracks.value = fetchedTracks;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Added ${album.tracks?.length} tracks to queue"),
),
);
});
}
}
19 changes: 19 additions & 0 deletions lib/components/playlist/playlist_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ class PlaylistCard extends HookConsumerWidget {
await playlistNotifier.loadAndPlay(fetchedTracks);
tracks.value = fetchedTracks;
},
onAddToQueuePressed: () async {
if (isPlaylistPlaying) return;
List<Track> fetchedTracks = await queryBowl.fetchQuery(
key: ValueKey(const Uuid().v4()),
Queries.playlist.tracksOf(playlist.id!),
externalData: ref.read(spotifyProvider),
) ??
[];

if (fetchedTracks.isEmpty) return;

playlistNotifier.add(fetchedTracks);
tracks.value = fetchedTracks;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Added ${fetchedTracks.length} tracks to queue"),
),
);
},
);
}
}
24 changes: 23 additions & 1 deletion lib/components/shared/playbutton_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum PlaybuttonCardViewType { square, list }
class PlaybuttonCard extends HookWidget {
final void Function()? onTap;
final void Function()? onPlaybuttonPressed;
final void Function()? onAddToQueuePressed;
final String? description;
final EdgeInsetsGeometry? margin;
final String imageUrl;
Expand All @@ -29,6 +30,7 @@ class PlaybuttonCard extends HookWidget {
this.margin,
this.description,
this.onPlaybuttonPressed,
this.onAddToQueuePressed,
this.onTap,
this.viewType = PlaybuttonCardViewType.square,
Key? key,
Expand Down Expand Up @@ -101,6 +103,15 @@ class PlaybuttonCard extends HookWidget {
color: Colors.white,
),
);
final addToQueueButton = PlatformIconButton(
onPressed: onAddToQueuePressed,
backgroundColor:
PlatformTheme.of(context).secondaryBackgroundColor,
hoverColor: PlatformTheme.of(context)
.secondaryBackgroundColor
?.withOpacity(0.5),
icon: const Icon(SpotubeIcons.queueAdd),
);
final image = Padding(
padding: EdgeInsets.all(
platform == TargetPlatform.windows ? 5 : 0,
Expand Down Expand Up @@ -131,7 +142,16 @@ class PlaybuttonCard extends HookWidget {
textDirection: TextDirection.ltr,
bottom: 10,
end: 5,
child: playButton,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (!isPlaying) addToQueueButton,
if (platform != TargetPlatform.linux)
const SizedBox(width: 5),
playButton,
],
),
)
],
),
Expand Down Expand Up @@ -192,6 +212,8 @@ class PlaybuttonCard extends HookWidget {
],
),
const Spacer(),
addToQueueButton,
const SizedBox(width: 10),
playButton,
const SizedBox(width: 10),
],
Expand Down
16 changes: 12 additions & 4 deletions lib/components/shared/track_table/track_collection_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
final String titleImage;
final bool isPlaying;
final void Function([Track? currentTrack]) onPlay;
final void Function() onAddToQueue;
final void Function([Track? currentTrack]) onShuffledPlay;
final void Function() onShare;
final Widget? heartBtn;
Expand All @@ -49,6 +50,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
required this.isPlaying,
required this.onPlay,
required this.onShuffledPlay,
required this.onAddToQueue,
required this.onShare,
required this.routePath,
this.heartBtn,
Expand Down Expand Up @@ -87,14 +89,20 @@ class TrackCollectionView<T> extends HookConsumerWidget {
onPressed: onShuffledPlay,
),
const SizedBox(width: 5),
// add to queue playlist
if (!isPlaying)
PlatformIconButton(
onPressed: tracksSnapshot.data != null ? onAddToQueue : null,
icon: Icon(
SpotubeIcons.queueAdd,
color: color?.titleTextColor,
),
),
// play playlist
PlatformIconButton(
backgroundColor: PlatformTheme.of(context).primaryColor,
onPressed: tracksSnapshot.data != null ? onPlay : null,
icon: Icon(
isPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
color: PlatformTextTheme.of(context).body?.color,
),
icon: Icon(isPlaying ? SpotubeIcons.stop : SpotubeIcons.play),
),
const SizedBox(width: 10),
];
Expand Down
32 changes: 31 additions & 1 deletion lib/components/shared/track_table/track_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class TrackTile extends HookConsumerWidget {
);
final auth = ref.watch(authProvider);
final spotify = ref.watch(spotifyProvider);
final playlistQueueNotifier = ref.watch(PlaylistQueueNotifier.notifier);

final removingTrack = useState<String?>(null);
final removeTrack = useMutation<bool, Tuple2<SpotifyApi, String>>(
job: Mutations.playlist.removeTrackOf(playlistId ?? ""),
Expand Down Expand Up @@ -319,6 +321,34 @@ class TrackTile extends HookConsumerWidget {
if (!isLocal)
AdaptiveActions(
actions: [
if (!playlistQueueNotifier.isTrackOnQueue(track.value))
Action(
icon: const Icon(SpotubeIcons.queueAdd),
text: const PlatformText("Add to queue"),
onPressed: () {
playlistQueueNotifier.add([track.value]);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: PlatformText(
"Added ${track.value.name} to queue"),
),
);
},
)
else
Action(
icon: const Icon(SpotubeIcons.queueRemove),
text: const PlatformText("Remove from queue"),
onPressed: () {
playlistQueueNotifier.remove([track.value]);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: PlatformText(
"Removed ${track.value.name} from queue"),
),
);
},
),
if (toggler.item3.hasData)
Action(
icon: toggler.item1
Expand All @@ -334,7 +364,7 @@ class TrackTile extends HookConsumerWidget {
),
if (auth.isLoggedIn)
Action(
icon: const Icon(SpotubeIcons.addFilled),
icon: const Icon(SpotubeIcons.playlistAdd),
text: const PlatformText("Add To playlist"),
onPressed: actionAddToPlaylist,
),
Expand Down
17 changes: 16 additions & 1 deletion lib/pages/album/album.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,25 @@ class AlbumPage extends HookConsumerWidget {
ref,
);
} else {
playback.stop();
playback.remove(
tracksSnapshot.data!
.map((track) =>
TypeConversionUtils.simpleTrack_X_Track(track, album))
.toList(),
);
}
}
},
onAddToQueue: () {
if (tracksSnapshot.hasData && !isAlbumPlaying) {
playback.add(
tracksSnapshot.data!
.map((track) =>
TypeConversionUtils.simpleTrack_X_Track(track, album))
.toList(),
);
}
},
onShare: () {
Clipboard.setData(
ClipboardData(text: "https://open.spotify.com/album/${album.id}"),
Expand Down
21 changes: 21 additions & 0 deletions lib/pages/artist/artist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,27 @@ class ArtistPage extends HookConsumerWidget {
style:
PlatformTheme.of(context).textTheme?.headline,
),
if (!isPlaylistPlaying)
PlatformIconButton(
icon: const Icon(
SpotubeIcons.queueAdd,
),
onPressed: () {
playlistNotifier.add(topTracks.toList());
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
width: 300,
behavior: SnackBarBehavior.floating,
content: PlatformText(
"Added ${topTracks.length} tracks to queue",
textAlign: TextAlign.center,
),
),
);
},
),
if (platform != TargetPlatform.linux)
const SizedBox(width: 5),
PlatformIconButton(
icon: Icon(
isPlaylistPlaying
Expand Down
7 changes: 6 additions & 1 deletion lib/pages/playlist/playlist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,15 @@ class PlaylistView extends HookConsumerWidget {
currentTrack: track,
);
} else {
playlistNotifier.stop();
playlistNotifier.remove(tracksSnapshot.data!);
}
}
},
onAddToQueue: () {
if (tracksSnapshot.hasData && !isPlaylistPlaying) {
playlistNotifier.add(tracksSnapshot.data!);
}
},
bottomSpace: breakpoint.isLessThanOrEqualTo(Breakpoints.md),
showShare: playlist.id != "user-liked-tracks",
routePath: "/playlist/${playlist.id}",
Expand Down
Loading

0 comments on commit b8f3493

Please sign in to comment.