freezer/lib/ui/menu.dart
Pato05 2862c9ec05
remove browser login for desktop
restore translations functionality
make scrollViews handle mouse pointers like touch, so that pull to refresh functionality is available
exit app if opening cache or settings fails (another instance running)
remove draggable_scrollbar and use builtin widget instead
fix email login
better way to manage lyrics (less updates and lookups in the lyrics List)
fix player_screen on mobile (too big -> just average :))
right click: use TapUp events instead
desktop: show context menu on triple dots button also
avoid showing connection error if the homepage is cached and available offline
i'm probably forgetting something idk
2023-10-25 00:32:28 +02:00

975 lines
31 KiB
Dart

import 'dart:async';
import 'package:freezer/main.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:flutter/material.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/download.dart';
import 'package:freezer/api/player/audio_handler.dart';
import 'package:freezer/ui/details_screens.dart';
import 'package:freezer/ui/error.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/ui/cached_image.dart';
import 'package:numberpicker/numberpicker.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate {
final Track track;
final double extent;
const SliverTrackPersistentHeader(this.track, {required this.extent});
@override
bool shouldRebuild(oldDelegate) => false;
@override
double get maxExtent => extent;
@override
double get minExtent => extent;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return DecoratedBox(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 16.0),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Semantics(
label: "Album art".i18n,
image: true,
child: CachedImage(
url: track.albumArt!.full,
height: 128,
width: 128,
),
),
SizedBox(
width: 240.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
track.title!,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 22.0, fontWeight: FontWeight.bold),
),
Text(
track.artistString,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(fontSize: 20.0),
),
const SizedBox(height: 8.0),
Text(
track.album!.title!,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(track.durationString)
],
),
),
],
),
const SizedBox(height: 16.0),
],
),
);
}
}
class MenuSheetOption {
final Widget label;
final Widget? icon;
final VoidCallback onTap;
const MenuSheetOption(
this.label, {
required this.onTap,
this.icon,
});
}
class MenuSheet {
final BuildContext context;
final Function? navigateCallback;
MenuSheet(this.context, {this.navigateCallback});
void _showContextMenu(List<MenuSheetOption> options,
{required TapUpDetails details}) {
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final actualPosition = overlay.globalToLocal(details.globalPosition);
showMenu(
clipBehavior: Clip.antiAlias,
elevation: 4.0,
context: context,
constraints: const BoxConstraints(maxWidth: 300.0),
position:
RelativeRect.fromSize(actualPosition & Size.zero, overlay.size),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(28.0))),
items: options
.map((option) => PopupMenuItem(
onTap: option.onTap,
child: option.icon == null
? option.label
: Row(mainAxisSize: MainAxisSize.min, children: [
option.icon!,
const SizedBox(width: 8.0),
Flexible(child: option.label),
])))
.toList(growable: false));
}
//===================
// DEFAULT
//===================
void show(List<MenuSheetOption> options, {TapUpDetails? details}) {
if (details != null) {
_showContextMenu(options, details: details);
return;
}
showModalBottomSheet(
isScrollControlled: false, // true,
context: context,
builder: (BuildContext context) {
return SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: (MediaQuery.of(context).orientation ==
Orientation.landscape)
? 220
: 350,
),
child: SingleChildScrollView(
child: Column(
children: options
.map((option) => ListTile(
title: option.label,
leading: option.icon,
onTap: () {
option.onTap.call();
Navigator.pop(context);
},
))
.toList(growable: false)),
),
),
);
});
}
//===================
// TRACK
//===================
void showWithTrack(Track track, List<MenuSheetOption> options,
{TapUpDetails? details}) {
if (details != null) {
_showContextMenu(options, details: details);
return;
}
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
isScrollControlled: true,
enableDrag: false,
showDragHandle: false,
elevation: 0.0,
builder: (BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.5,
minChildSize: 0.45,
maxChildSize: 0.75,
builder: (context, scrollController) => SafeArea(
child: Material(
type: MaterialType.card,
clipBehavior: Clip.antiAlias,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(20.0)),
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: SliverTrackPersistentHeader(track,
extent: 128.0 + 16.0 + 16.0)),
SliverList(
delegate: SliverChildListDelegate.fixed(options
.map((option) => ListTile(
title: option.label,
leading: option.icon,
onTap: () {
option.onTap.call();
Navigator.pop(context);
},
))
.toList(growable: false))),
],
),
),
),
);
});
}
//Default track options
void defaultTrackMenu(
Track track, {
List<MenuSheetOption> options = const [],
Function? onRemove,
TapUpDetails? details,
}) {
showWithTrack(
track,
<MenuSheetOption>[
addToQueueNext(track),
addToQueue(track),
(cache.checkTrackFavorite(track))
? removeFavoriteTrack(track, onUpdate: onRemove)
: addTrackFavorite(track),
addToPlaylist(track),
downloadTrack(track),
offlineTrack(track),
shareTile('track', track.id),
playMix(track),
showAlbum(track.album!),
...List.generate(
track.artists!.length, (i) => showArtist(track.artists![i])),
...options
],
details: details);
}
//===================
// TRACK OPTIONS
//===================
MenuSheetOption addToQueueNext(Track t) =>
MenuSheetOption(Text('Play next'.i18n),
icon: const Icon(Icons.playlist_play), onTap: () async {
//-1 = next
await audioHandler.insertQueueItem(-1, await t.toMediaItem());
});
MenuSheetOption addToQueue(Track t) =>
MenuSheetOption(Text('Add to queue'.i18n),
icon: const Icon(Icons.playlist_add), onTap: () async {
await audioHandler.addQueueItem(await t.toMediaItem());
});
MenuSheetOption addTrackFavorite(Track t) =>
MenuSheetOption(Text('Add track to favorites'.i18n),
icon: const Icon(Icons.favorite), onTap: () async {
await deezerAPI.addFavoriteTrack(t.id);
//Make track offline, if favorites are offline
Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!);
if (await downloadManager.checkOffline(playlist: p)) {
downloadManager.addOfflinePlaylist(p);
}
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
//Add to cache
cache.libraryTracks.add(t.id);
});
MenuSheetOption downloadTrack(Track t) => MenuSheetOption(
Text('Download'.i18n),
icon: const Icon(Icons.file_download),
onTap: () async {
if (await downloadManager.addOfflineTrack(t,
private: false, context: context, isSingleton: true) !=
false) showDownloadStartedToast();
},
);
MenuSheetOption addToPlaylist(Track t) => MenuSheetOption(
Text('Add to playlist'.i18n),
icon: const Icon(Icons.playlist_add),
onTap: () async {
//Show dialog to pick playlist
await showDialog(
context: context,
builder: (context) {
return SelectPlaylistDialog(
track: t,
callback: (Playlist p) async {
await deezerAPI.addToPlaylist(t.id, p.id);
//Update the playlist if offline
if (await downloadManager.checkOffline(playlist: p)) {
downloadManager.addOfflinePlaylist(p);
}
ScaffoldMessenger.of(context)
.snack("${"Track added to".i18n} ${p.title}");
});
});
},
);
MenuSheetOption removeFromPlaylist(Track t, Playlist? p) => MenuSheetOption(
Text('Remove from playlist'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeFromPlaylist(t.id, p!.id);
ScaffoldMessenger.of(context)
.snack('${'Track removed from'.i18n} ${p.title}');
},
);
MenuSheetOption removeFavoriteTrack(Track t, {onUpdate}) => MenuSheetOption(
Text('Remove favorite'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeFavorite(t.id);
//Check if favorites playlist is offline, update it
Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!);
if (await downloadManager.checkOffline(playlist: p)) {
await downloadManager.addOfflinePlaylist(p);
}
//Remove from cache
cache.libraryTracks.removeWhere((i) => i == t.id);
ScaffoldMessenger.of(context)
.snack('Track removed from library'.i18n);
if (onUpdate != null) onUpdate();
},
);
//Redirect to artist page (ie from track)
MenuSheetOption showArtist(Artist a) => MenuSheetOption(
Text(
'${'Go to'.i18n} ${a.name}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
icon: const Icon(Icons.recent_actors),
onTap: () {
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => ArtistDetails(a)));
if (navigateCallback != null) {
navigateCallback!();
}
},
);
MenuSheetOption showAlbum(Album a) => MenuSheetOption(
Text(
'${'Go to'.i18n} ${a.title}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
icon: const Icon(Icons.album),
onTap: () {
navigatorKey.currentState!
.push(MaterialPageRoute(builder: (context) => AlbumDetails(a)));
if (navigateCallback != null) {
navigateCallback!();
}
},
);
MenuSheetOption playMix(Track track) => MenuSheetOption(
Text('Play mix'.i18n),
icon: const Icon(Icons.online_prediction),
onTap: () async {
playerHelper.playMix(track.id, track.title!);
},
);
MenuSheetOption offlineTrack(Track track) => MenuSheetOption(
FutureBuilder(
future: downloadManager.checkOffline(track: track),
builder: (context, snapshot) {
bool isOffline = snapshot.data ?? track.offline ?? false;
return Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n);
}),
icon: const Icon(Icons.offline_pin), onTap: () async {
if (await downloadManager.checkOffline(track: track)) {
await downloadManager.removeOfflineTracks([track]);
ScaffoldMessenger.of(context)
.snack("Track removed from offline!".i18n);
} else {
await downloadManager.addOfflineTrack(track,
private: true, context: context);
}
});
//===================
// ALBUM
//===================
//Default album options
void defaultAlbumMenu(Album album,
{List<MenuSheetOption> options = const [],
Function? onRemove,
TapUpDetails? details}) {
show([
album.library!
? removeAlbum(album, onRemove: onRemove)
: libraryAlbum(album),
downloadAlbum(album),
offlineAlbum(album),
shareTile('album', album.id),
...options
]);
}
//===================
// ALBUM OPTIONS
//===================
MenuSheetOption downloadAlbum(Album a) =>
MenuSheetOption(Text('Download'.i18n),
icon: const Icon(Icons.file_download), onTap: () async {
if (await downloadManager.addOfflineAlbum(a,
private: false, context: context) !=
false) showDownloadStartedToast();
});
MenuSheetOption offlineAlbum(Album a) => MenuSheetOption(
Text('Make offline'.i18n),
icon: const Icon(Icons.offline_pin),
onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id);
await downloadManager.addOfflineAlbum(a, private: true);
showDownloadStartedToast();
},
);
MenuSheetOption libraryAlbum(Album a) => MenuSheetOption(
Text('Add to library'.i18n),
icon: const Icon(Icons.library_music),
onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id);
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
},
);
//Remove album from favorites
MenuSheetOption removeAlbum(Album a, {Function? onRemove}) => MenuSheetOption(
Text('Remove album'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeAlbum(a.id);
await downloadManager.removeOfflineAlbum(a.id);
ScaffoldMessenger.of(context).snack('Album removed'.i18n);
if (onRemove != null) onRemove();
},
);
//===================
// ARTIST
//===================
void defaultArtistMenu(Artist artist,
{List<MenuSheetOption> options = const [],
Function? onRemove,
TapUpDetails? details}) {
show(details: details, [
artist.library!
? removeArtist(artist, onRemove: onRemove)
: favoriteArtist(artist),
shareTile('artist', artist.id),
...options
]);
}
//===================
// ARTIST OPTIONS
//===================
MenuSheetOption removeArtist(Artist a, {Function? onRemove}) =>
MenuSheetOption(
Text('Remove from favorites'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
await deezerAPI.removeArtist(a.id);
ScaffoldMessenger.of(context)
.snack('Artist removed from library'.i18n);
if (onRemove != null) onRemove();
},
);
MenuSheetOption favoriteArtist(Artist a) => MenuSheetOption(
Text('Add to favorites'.i18n),
icon: const Icon(Icons.favorite),
onTap: () async {
await deezerAPI.addFavoriteArtist(a.id);
ScaffoldMessenger.of(context).snack('Added to library'.i18n);
},
);
//===================
// PLAYLIST
//===================
void defaultPlaylistMenu(Playlist playlist,
{List<MenuSheetOption> options = const [],
Function? onRemove,
Function? onUpdate,
TapUpDetails? details}) {
show(details: details, [
if (playlist.library != null)
playlist.library!
? removePlaylistLibrary(playlist, onRemove: onRemove)
: addPlaylistLibrary(playlist),
addPlaylistOffline(playlist),
downloadPlaylist(playlist),
shareTile('playlist', playlist.id),
if (playlist.user!.id == deezerAPI.userId)
editPlaylist(playlist, onUpdate: onUpdate),
...options
]);
}
//===================
// PLAYLIST OPTIONS
//===================
MenuSheetOption removePlaylistLibrary(Playlist p, {Function? onRemove}) =>
MenuSheetOption(
Text('Remove from library'.i18n),
icon: const Icon(Icons.delete),
onTap: () async {
if (p.user!.id!.trim() == deezerAPI.userId) {
//Delete playlist if own
await deezerAPI.deletePlaylist(p.id);
} else {
//Just remove from library
await deezerAPI.removePlaylist(p.id);
}
downloadManager.removeOfflinePlaylist(p.id);
if (onRemove != null) onRemove();
},
);
MenuSheetOption addPlaylistLibrary(Playlist p) => MenuSheetOption(
Text('Add playlist to library'.i18n),
icon: const Icon(Icons.favorite),
onTap: () async {
await deezerAPI.addPlaylist(p.id);
ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n);
},
);
MenuSheetOption addPlaylistOffline(Playlist p) => MenuSheetOption(
Text('Make playlist offline'.i18n),
icon: const Icon(Icons.offline_pin),
onTap: () async {
//Add to library
await deezerAPI.addPlaylist(p.id);
downloadManager.addOfflinePlaylist(p, private: true);
showDownloadStartedToast();
},
);
MenuSheetOption downloadPlaylist(Playlist p) => MenuSheetOption(
Text('Download playlist'.i18n),
icon: const Icon(Icons.file_download),
onTap: () async {
if (await downloadManager.addOfflinePlaylist(p,
private: false, context: context) !=
false) showDownloadStartedToast();
},
);
MenuSheetOption editPlaylist(Playlist p, {Function? onUpdate}) =>
MenuSheetOption(
Text('Edit playlist'.i18n),
icon: const Icon(Icons.edit),
onTap: () async {
await showDialog(
context: context,
builder: (context) => CreatePlaylistDialog(playlist: p));
if (onUpdate != null) onUpdate();
},
);
//===================
// SHOW/EPISODE
//===================
defaultShowEpisodeMenu(Show s, ShowEpisode e,
{List<MenuSheetOption> options = const [], TapUpDetails? details}) {
show(details: details, [
shareTile('episode', e.id),
shareShow(s.id),
downloadExternalEpisode(e),
...options
]);
}
MenuSheetOption shareShow(String? id) => MenuSheetOption(
Text('Share show'.i18n),
icon: const Icon(Icons.share),
onTap: () async {
Share.share('https://deezer.com/show/$id');
},
);
//Open direct download link in browser
MenuSheetOption downloadExternalEpisode(ShowEpisode e) => MenuSheetOption(
Text('Download externally'.i18n),
icon: const Icon(Icons.file_download),
onTap: () async {
launchUrl(Uri.parse(e.url!));
},
);
//===================
// OTHER
//===================
showDownloadStartedToast() {
ScaffoldMessenger.of(context).snack('Downloads added!'.i18n);
}
//Create playlist
Future createPlaylist() async {
await showDialog(
context: context,
builder: (BuildContext context) {
return const CreatePlaylistDialog();
});
}
MenuSheetOption shareTile(String type, String? id) => MenuSheetOption(
Text('Share'.i18n),
icon: const Icon(Icons.share),
onTap: () async {
Share.share('https://deezer.com/$type/$id');
},
);
MenuSheetOption sleepTimer() => MenuSheetOption(
Text('Sleep timer'.i18n),
icon: const Icon(Icons.access_time),
onTap: () async {
showDialog(
context: context,
builder: (context) {
return const SleepTimerDialog();
});
},
);
MenuSheetOption wakelock() => MenuSheetOption(
Text('Keep the screen on'.i18n),
icon: const Icon(Icons.screen_lock_portrait),
onTap: () async {
//Enable
if (!cache.wakelock) {
WakelockPlus.enable();
ScaffoldMessenger.of(context).snack('Wakelock enabled!'.i18n);
cache.wakelock = true;
return;
}
//Disable
WakelockPlus.disable();
ScaffoldMessenger.of(context).snack('Wakelock disabled!'.i18n);
cache.wakelock = false;
},
);
}
class SleepTimerDialog extends StatefulWidget {
const SleepTimerDialog({super.key});
@override
State<SleepTimerDialog> createState() => _SleepTimerDialogState();
}
class _SleepTimerDialogState extends State<SleepTimerDialog> {
int hours = 0;
int minutes = 30;
String _endTime() {
return '${cache.sleepTimerTime!.hour.toString().padLeft(2, '0')}:${cache.sleepTimerTime!.minute.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Sleep timer'.i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Hours:'.i18n),
NumberPicker(
value: hours,
minValue: 0,
maxValue: 69,
onChanged: (v) => setState(() => hours = v),
),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Minutes:'.i18n),
NumberPicker(
value: minutes,
minValue: 0,
maxValue: 60,
onChanged: (v) => setState(() => minutes = v),
),
],
),
],
),
Container(height: 4.0),
if (cache.sleepTimerTime != null)
Text(
'${'Current timer ends at'.i18n}: ${_endTime()}',
textAlign: TextAlign.center,
)
],
),
actions: [
TextButton(
child: Text('Dismiss'.i18n),
onPressed: () {
Navigator.of(context).pop();
},
),
if (cache.sleepTimer != null)
TextButton(
child: Text('Cancel current timer'.i18n),
onPressed: () {
cache.sleepTimer!.cancel();
cache.sleepTimer = null;
cache.sleepTimerTime = null;
Navigator.of(context).pop();
},
),
TextButton(
child: Text('Save'.i18n),
onPressed: () {
Duration duration = Duration(hours: hours, minutes: minutes);
if (cache.sleepTimer != null) {
cache.sleepTimer!.cancel();
}
//Create timer
cache.sleepTimer =
Stream.fromFuture(Future.delayed(duration)).listen((_) {
audioHandler.pause();
cache.sleepTimer!.cancel();
cache.sleepTimerTime = null;
cache.sleepTimer = null;
});
cache.sleepTimerTime = DateTime.now().add(duration);
Navigator.of(context).pop();
},
),
],
);
}
}
class SelectPlaylistDialog extends StatefulWidget {
final Track? track;
final Function? callback;
const SelectPlaylistDialog({this.track, this.callback, Key? key})
: super(key: key);
@override
State<SelectPlaylistDialog> createState() => _SelectPlaylistDialogState();
}
class _SelectPlaylistDialogState extends State<SelectPlaylistDialog> {
bool createNew = false;
@override
Widget build(BuildContext context) {
//Create new playlist
if (createNew) {
if (widget.track == null) {
return const CreatePlaylistDialog();
}
return CreatePlaylistDialog(tracks: [widget.track]);
}
return AlertDialog(
title: Text('Select playlist'.i18n),
content: FutureBuilder(
future: deezerAPI.getPlaylists(),
builder: (context, snapshot) {
if (snapshot.hasError) {
const SizedBox(
height: 100,
child: ErrorScreen(),
);
}
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox(
height: 100,
child: Center(
child: CircularProgressIndicator(),
),
);
}
List<Playlist> playlists = snapshot.data!;
return SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [
...List.generate(
playlists.length,
(i) => ListTile(
title: Text(playlists[i].title!),
leading: CachedImage(
url: playlists[i].image!.thumb,
),
onTap: () {
if (widget.callback != null) {
widget.callback!(playlists[i]);
}
Navigator.of(context).pop();
},
)),
ListTile(
title: Text('Create new playlist'.i18n),
leading: const Icon(Icons.add),
onTap: () async {
setState(() {
createNew = true;
});
},
)
]),
);
},
),
);
}
}
class CreatePlaylistDialog extends StatefulWidget {
final List<Track?>? tracks;
//If playlist not null, update
final Playlist? playlist;
const CreatePlaylistDialog({this.tracks, this.playlist, Key? key})
: super(key: key);
@override
State<CreatePlaylistDialog> createState() => _CreatePlaylistDialogState();
}
class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
int? _playlistType = 1;
String _title = '';
String _description = '';
TextEditingController? _titleController;
TextEditingController? _descController;
//Create or edit mode
bool get edit => widget.playlist != null;
@override
void initState() {
//Edit playlist mode
if (edit) {
_titleController = TextEditingController(text: widget.playlist!.title);
_descController =
TextEditingController(text: widget.playlist!.description);
}
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(edit ? 'Edit playlist'.i18n : 'Create playlist'.i18n),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Title'.i18n),
controller: _titleController ?? TextEditingController(),
onChanged: (String s) => _title = s,
),
TextField(
onChanged: (String s) => _description = s,
controller: _descController ?? TextEditingController(),
decoration: InputDecoration(labelText: 'Description'.i18n),
),
Container(
height: 4.0,
),
DropdownButton<int>(
value: _playlistType,
onChanged: (int? v) {
setState(() => _playlistType = v);
},
items: [
DropdownMenuItem<int>(
value: 1,
child: Text('Private'.i18n),
),
DropdownMenuItem<int>(
value: 2,
child: Text('Collaborative'.i18n),
),
],
),
],
),
actions: <Widget>[
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: Text(edit ? 'Update'.i18n : 'Create'.i18n),
onPressed: () async {
if (edit) {
//Update
await deezerAPI.updatePlaylist(widget.playlist!.id,
_titleController!.value.text, _descController!.value.text,
status: _playlistType);
ScaffoldMessenger.of(context).snack('Playlist updated!'.i18n);
} else {
List<String> tracks = [];
if (widget.tracks != null) {
tracks = widget.tracks!.map<String>((t) => t!.id).toList();
}
await deezerAPI.createPlaylist(_title,
status: _playlistType,
description: _description,
trackIds: tracks);
ScaffoldMessenger.of(context).snack('Playlist created!'.i18n);
}
Navigator.of(context).pop();
},
)
],
);
}
}