feat: playlist create support for generated playlist

This commit is contained in:
Kingkor Roy Tirtho 2023-06-10 23:12:02 +06:00
parent 51e427e83c
commit 91c72f9ec9
5 changed files with 190 additions and 137 deletions

View File

@ -98,7 +98,7 @@ class UserPlaylists extends HookConsumerWidget {
Row( Row(
children: [ children: [
const SizedBox(width: 10), const SizedBox(width: 10),
const PlaylistCreateDialog(), const PlaylistCreateDialogButton(),
const SizedBox(width: 10), const SizedBox(width: 10),
ElevatedButton.icon( ElevatedButton.icon(
icon: const Icon(SpotubeIcons.magic), icon: const Icon(SpotubeIcons.magic),

View File

@ -10,13 +10,16 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/spotify_provider.dart';
class PlaylistCreateDialog extends HookConsumerWidget { class PlaylistCreateDialog extends HookConsumerWidget {
const PlaylistCreateDialog({Key? key}) : super(key: key); /// Track ids to add to the playlist
final List<String> trackIds;
const PlaylistCreateDialog({
Key? key,
this.trackIds = const [],
}) : super(key: key);
showPlaylistDialog(BuildContext context, SpotifyApi spotify) { @override
showDialog( Widget build(BuildContext context, ref) {
context: context, final spotify = ref.watch(spotifyProvider);
builder: (context) {
return HookBuilder(builder: (context) {
final playlistName = useTextEditingController(); final playlistName = useTextEditingController();
final description = useTextEditingController(); final description = useTextEditingController();
final public = useState(false); final public = useState(false);
@ -24,22 +27,28 @@ class PlaylistCreateDialog extends HookConsumerWidget {
final client = useQueryClient(); final client = useQueryClient();
final navigator = Navigator.of(context); final navigator = Navigator.of(context);
onCreate() async { Future<void> onCreate() async {
if (playlistName.text.isEmpty) return; if (playlistName.text.isEmpty) return;
final me = await spotify.me.get(); final me = await spotify.me.get();
await spotify.playlists.createPlaylist( final playlist = await spotify.playlists.createPlaylist(
me.id!, me.id!,
playlistName.text, playlistName.text,
collaborative: collaborative.value, collaborative: collaborative.value,
public: public.value, public: public.value,
description: description.text, description: description.text,
); );
if (trackIds.isNotEmpty) {
await spotify.playlists.addTracks(
trackIds.map((id) => "spotify:track:$id").toList(),
playlist.id!,
);
}
await client await client
.getQuery( .getQuery(
"current-user-playlists", "current-user-playlists",
) )
?.refresh(); ?.refresh();
navigator.pop(); navigator.pop(playlist);
} }
return AlertDialog( return AlertDialog(
@ -94,8 +103,16 @@ class PlaylistCreateDialog extends HookConsumerWidget {
), ),
), ),
); );
}); }
}, }
class PlaylistCreateDialogButton extends HookConsumerWidget {
const PlaylistCreateDialogButton({Key? key}) : super(key: key);
showPlaylistDialog(BuildContext context, SpotifyApi spotify) {
showDialog(
context: context,
builder: (context) => const PlaylistCreateDialog(),
); );
} }

View File

@ -217,5 +217,9 @@
"min": "Min", "min": "Min",
"max": "Max", "max": "Max",
"target": "Target", "target": "Target",
"moderate": "Moderate" "moderate": "Moderate",
"deselect_all": "Deselect All",
"select_all": "Select All",
"generating_playlist": "Generating your custom playlist...",
"selected_count_tracks": "Selected {count} tracks"
} }

View File

@ -454,7 +454,11 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
FilledButton.icon( FilledButton.icon(
icon: const Icon(SpotubeIcons.magic), icon: const Icon(SpotubeIcons.magic),
label: Text(context.l10n.generate_playlist), label: Text(context.l10n.generate_playlist),
onPressed: () { onPressed: artists.value.isEmpty &&
tracks.value.isEmpty &&
genres.value.isEmpty
? null
: () {
final PlaylistGenerateResultRouteState routeState = ( final PlaylistGenerateResultRouteState routeState = (
seeds: ( seeds: (
artists: artists.value.map((a) => a.id!).toList(), artists: artists.value.map((a) => a.id!).toList(),

View File

@ -1,9 +1,12 @@
import 'package:fl_query_hooks/fl_query_hooks.dart'; import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/library/playlist_generate/simple_track_tile.dart'; import 'package:spotube/components/library/playlist_generate/simple_track_tile.dart';
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
@ -27,6 +30,7 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final router = GoRouter.of(context);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final (:seeds, :parameters, :limit, :market) = state; final (:seeds, :parameters, :limit, :market) = state;
@ -62,13 +66,13 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
child: Scaffold( child: Scaffold(
appBar: const PageWindowTitleBar(leading: BackButton()), appBar: const PageWindowTitleBar(leading: BackButton()),
body: generatedPlaylist.isLoading body: generatedPlaylist.isLoading
? const Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
CircularProgressIndicator(), const CircularProgressIndicator(),
Text("Generating your custom playlist..."), Text(context.l10n.generating_playlist),
], ],
), ),
) )
@ -128,8 +132,23 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
FilledButton.tonalIcon( FilledButton.tonalIcon(
icon: const Icon(SpotubeIcons.addFilled), icon: const Icon(SpotubeIcons.addFilled),
label: Text(context.l10n.create_a_playlist), label: Text(context.l10n.create_a_playlist),
onPressed: onPressed: selectedTracks.value.isEmpty
selectedTracks.value.isEmpty ? null : () {}, ? null
: () async {
final playlist = await showDialog<Playlist>(
context: context,
builder: (context) => PlaylistCreateDialog(
trackIds: selectedTracks.value,
),
);
if (playlist != null) {
router.go(
'/playlist/${playlist.id}',
extra: playlist,
);
}
},
), ),
FilledButton.tonalIcon( FilledButton.tonalIcon(
icon: const Icon(SpotubeIcons.playlistAdd), icon: const Icon(SpotubeIcons.playlistAdd),
@ -141,9 +160,15 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (generatedPlaylist.data != null) if (generatedPlaylist.data != null)
Align( Row(
alignment: Alignment.centerRight, mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: ElevatedButton.icon( children: [
Text(
context.l10n.selected_count_tracks(
selectedTracks.value.length,
),
),
ElevatedButton.icon(
onPressed: () { onPressed: () {
if (isAllTrackSelected) { if (isAllTrackSelected) {
selectedTracks.value = []; selectedTracks.value = [];
@ -156,9 +181,12 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
}, },
icon: const Icon(SpotubeIcons.selectionCheck), icon: const Icon(SpotubeIcons.selectionCheck),
label: Text( label: Text(
isAllTrackSelected ? "Deselect all" : "Select all", isAllTrackSelected
? context.l10n.deselect_all
: context.l10n.select_all,
), ),
), ),
],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Card( Card(