Skip to content

Commit

Permalink
feat: playlist create support for generated playlist
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Jun 10, 2023
1 parent 51e427e commit 91c72f9
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 135 deletions.
2 changes: 1 addition & 1 deletion lib/components/library/user_playlists.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class UserPlaylists extends HookConsumerWidget {
Row(
children: [
const SizedBox(width: 10),
const PlaylistCreateDialog(),
const PlaylistCreateDialogButton(),
const SizedBox(width: 10),
ElevatedButton.icon(
icon: const Icon(SpotubeIcons.magic),
Expand Down
177 changes: 97 additions & 80 deletions lib/components/playlist/playlist_create_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,92 +10,109 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/spotify_provider.dart';

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) {
showDialog(
context: context,
builder: (context) {
return HookBuilder(builder: (context) {
final playlistName = useTextEditingController();
final description = useTextEditingController();
final public = useState(false);
final collaborative = useState(false);
final client = useQueryClient();
final navigator = Navigator.of(context);
@override
Widget build(BuildContext context, ref) {
final spotify = ref.watch(spotifyProvider);
final playlistName = useTextEditingController();
final description = useTextEditingController();
final public = useState(false);
final collaborative = useState(false);
final client = useQueryClient();
final navigator = Navigator.of(context);

onCreate() async {
if (playlistName.text.isEmpty) return;
final me = await spotify.me.get();
await spotify.playlists.createPlaylist(
me.id!,
playlistName.text,
collaborative: collaborative.value,
public: public.value,
description: description.text,
);
await client
.getQuery(
"current-user-playlists",
)
?.refresh();
navigator.pop();
}
Future<void> onCreate() async {
if (playlistName.text.isEmpty) return;
final me = await spotify.me.get();
final playlist = await spotify.playlists.createPlaylist(
me.id!,
playlistName.text,
collaborative: collaborative.value,
public: public.value,
description: description.text,
);
if (trackIds.isNotEmpty) {
await spotify.playlists.addTracks(
trackIds.map((id) => "spotify:track:$id").toList(),
playlist.id!,
);
}
await client
.getQuery(
"current-user-playlists",
)
?.refresh();
navigator.pop(playlist);
}

return AlertDialog(
title: Text(context.l10n.create_a_playlist),
actions: [
OutlinedButton(
child: Text(context.l10n.cancel),
onPressed: () {
Navigator.pop(context);
},
),
FilledButton(
onPressed: onCreate,
child: Text(context.l10n.create),
return AlertDialog(
title: Text(context.l10n.create_a_playlist),
actions: [
OutlinedButton(
child: Text(context.l10n.cancel),
onPressed: () {
Navigator.pop(context);
},
),
FilledButton(
onPressed: onCreate,
child: Text(context.l10n.create),
),
],
content: Container(
width: MediaQuery.of(context).size.width,
constraints: const BoxConstraints(maxWidth: 500),
child: ListView(
shrinkWrap: true,
children: [
TextField(
controller: playlistName,
decoration: InputDecoration(
hintText: context.l10n.name_of_playlist,
labelText: context.l10n.name_of_playlist,
),
],
content: Container(
width: MediaQuery.of(context).size.width,
constraints: const BoxConstraints(maxWidth: 500),
child: ListView(
shrinkWrap: true,
children: [
TextField(
controller: playlistName,
decoration: InputDecoration(
hintText: context.l10n.name_of_playlist,
labelText: context.l10n.name_of_playlist,
),
),
const SizedBox(height: 10),
TextField(
controller: description,
decoration: InputDecoration(
hintText: context.l10n.description,
),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
const SizedBox(height: 10),
CheckboxListTile(
title: Text(context.l10n.public),
value: public.value,
onChanged: (val) => public.value = val ?? false,
),
const SizedBox(height: 10),
CheckboxListTile(
title: Text(context.l10n.collaborative),
value: collaborative.value,
onChanged: (val) => collaborative.value = val ?? false,
),
],
),
const SizedBox(height: 10),
TextField(
controller: description,
decoration: InputDecoration(
hintText: context.l10n.description,
),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
);
});
},
const SizedBox(height: 10),
CheckboxListTile(
title: Text(context.l10n.public),
value: public.value,
onChanged: (val) => public.value = val ?? false,
),
const SizedBox(height: 10),
CheckboxListTile(
title: Text(context.l10n.collaborative),
value: collaborative.value,
onChanged: (val) => collaborative.value = val ?? false,
),
],
),
),
);
}
}

class PlaylistCreateDialogButton extends HookConsumerWidget {
const PlaylistCreateDialogButton({Key? key}) : super(key: key);

showPlaylistDialog(BuildContext context, SpotifyApi spotify) {
showDialog(
context: context,
builder: (context) => const PlaylistCreateDialog(),
);
}

Expand Down
6 changes: 5 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,9 @@
"min": "Min",
"max": "Max",
"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"
}
66 changes: 35 additions & 31 deletions lib/pages/library/playlist_generate/playlist_generate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -454,37 +454,41 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
FilledButton.icon(
icon: const Icon(SpotubeIcons.magic),
label: Text(context.l10n.generate_playlist),
onPressed: () {
final PlaylistGenerateResultRouteState routeState = (
seeds: (
artists: artists.value.map((a) => a.id!).toList(),
tracks: tracks.value.map((t) => t.id!).toList(),
genres: genres.value
),
market: market.value,
limit: limit.value,
parameters: (
acousticness: acousticness.value,
danceability: danceability.value,
energy: energy.value,
instrumentalness: instrumentalness.value,
liveness: liveness.value,
loudness: loudness.value,
speechiness: speechiness.value,
valence: valence.value,
popularity: popularity.value,
key: key.value,
duration_ms: durationMs.value,
tempo: tempo.value,
mode: mode.value,
time_signature: timeSignature.value,
)
);
GoRouter.of(context).push(
"/library/generate/result",
extra: routeState,
);
},
onPressed: artists.value.isEmpty &&
tracks.value.isEmpty &&
genres.value.isEmpty
? null
: () {
final PlaylistGenerateResultRouteState routeState = (
seeds: (
artists: artists.value.map((a) => a.id!).toList(),
tracks: tracks.value.map((t) => t.id!).toList(),
genres: genres.value
),
market: market.value,
limit: limit.value,
parameters: (
acousticness: acousticness.value,
danceability: danceability.value,
energy: energy.value,
instrumentalness: instrumentalness.value,
liveness: liveness.value,
loudness: loudness.value,
speechiness: speechiness.value,
valence: valence.value,
popularity: popularity.value,
key: key.value,
duration_ms: durationMs.value,
tempo: tempo.value,
mode: mode.value,
time_signature: timeSignature.value,
)
);
GoRouter.of(context).push(
"/library/generate/result",
extra: routeState,
);
},
),
],
);
Expand Down
72 changes: 50 additions & 22 deletions lib/pages/library/playlist_generate/playlist_generate_result.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.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/extensions/context.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
Expand All @@ -27,6 +30,7 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {

@override
Widget build(BuildContext context, ref) {
final router = GoRouter.of(context);
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final (:seeds, :parameters, :limit, :market) = state;

Expand Down Expand Up @@ -62,13 +66,13 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
child: Scaffold(
appBar: const PageWindowTitleBar(leading: BackButton()),
body: generatedPlaylist.isLoading
? const Center(
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircularProgressIndicator(),
Text("Generating your custom playlist..."),
const CircularProgressIndicator(),
Text(context.l10n.generating_playlist),
],
),
)
Expand Down Expand Up @@ -128,8 +132,23 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
FilledButton.tonalIcon(
icon: const Icon(SpotubeIcons.addFilled),
label: Text(context.l10n.create_a_playlist),
onPressed:
selectedTracks.value.isEmpty ? null : () {},
onPressed: selectedTracks.value.isEmpty
? 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(
icon: const Icon(SpotubeIcons.playlistAdd),
Expand All @@ -141,24 +160,33 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
),
const SizedBox(height: 16),
if (generatedPlaylist.data != null)
Align(
alignment: Alignment.centerRight,
child: ElevatedButton.icon(
onPressed: () {
if (isAllTrackSelected) {
selectedTracks.value = [];
} else {
selectedTracks.value = generatedPlaylist.data
?.map((e) => e.id!)
.toList() ??
[];
}
},
icon: const Icon(SpotubeIcons.selectionCheck),
label: Text(
isAllTrackSelected ? "Deselect all" : "Select all",
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.l10n.selected_count_tracks(
selectedTracks.value.length,
),
),
),
ElevatedButton.icon(
onPressed: () {
if (isAllTrackSelected) {
selectedTracks.value = [];
} else {
selectedTracks.value = generatedPlaylist.data
?.map((e) => e.id!)
.toList() ??
[];
}
},
icon: const Icon(SpotubeIcons.selectionCheck),
label: Text(
isAllTrackSelected
? context.l10n.deselect_all
: context.l10n.select_all,
),
),
],
),
const SizedBox(height: 8),
Card(
Expand Down

0 comments on commit 91c72f9

Please sign in to comment.