freezer/lib/ui/menu.dart

985 lines
32 KiB
Dart

import 'dart:async';
import 'package:freezer/api/player/player_helper.dart';
import 'package:freezer/main.dart';
import 'package:freezer/settings.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;
static const kExtent = 84.0 + 16.0 * 2 + 2.0;
const SliverTrackPersistentHeader(this.track);
@override
bool shouldRebuild(oldDelegate) => false;
@override
double get maxExtent => kExtent;
@override
double get minExtent => kExtent;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return DecoratedBox(
decoration:
BoxDecoration(color: Theme.of(context).colorScheme.background),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16.0),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
const SizedBox(width: 16.0),
Semantics(
label: "Album art".i18n,
image: true,
child: CachedImage(
url: track.albumArt!.full,
height: 86.0,
width: 86.0,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
track.title!,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
track.album!.title!,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
Text(
track.artistString,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
],
),
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 VoidCallback? 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,
useSafeArea: true,
builder: (BuildContext context) {
return 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: () {
Navigator.pop(context);
option.onTap.call();
},
))
.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.95,
builder: (context, scrollController) => Material(
type: MaterialType.card,
clipBehavior: Clip.antiAlias,
color: Theme.of(context).colorScheme.background,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(20.0)),
child: SafeArea(
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: SliverTrackPersistentHeader(track)),
SliverList(
delegate: SliverChildListDelegate.fixed(options
.map((option) => ListTile(
title: option.label,
leading: option.icon,
onTap: () {
Navigator.pop(context);
option.onTap.call();
},
))
.toList(growable: false))),
],
),
),
),
);
});
}
//Default track options
void defaultTrackMenu(
Track track, {
List<MenuSheetOption> options = const [],
List<MenuSheetOption> optionsTop = const [],
Function? onRemove,
TapUpDetails? details,
}) {
showWithTrack(
track,
<MenuSheetOption>[
...optionsTop,
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, t.toMediaItem());
});
MenuSheetOption addToQueue(Track t) =>
MenuSheetOption(Text('Add to queue'.i18n),
icon: const Icon(Icons.playlist_add), onTap: () async {
await audioHandler.addQueueItem(t.toMediaItem());
});
MenuSheetOption addTrackFavorite(Track t) =>
MenuSheetOption(Text('Add track to favorites'.i18n),
icon: const Icon(Icons.favorite), onTap: () async {
await DeezerAPI.instance.addFavoriteTrack(t.id);
//Make track offline, if favorites are offline
Playlist p = Playlist(id: DeezerAPI.instance.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 {
// final dl = newDl.DownloadManager();
// await dl.startDebug();
// dl.addOfflineTrack(t, settings.downloadQuality, private: false);
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.instance.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.instance.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.instance.removeFavorite(t.id);
//Check if favorites playlist is offline, update it
Playlist p = Playlist(id: DeezerAPI.instance.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)));
navigateCallback?.call();
},
);
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)));
navigateCallback?.call();
},
);
MenuSheetOption playMix(Track track) => MenuSheetOption(
Text('Play mix'.i18n),
icon: const Icon(Icons.online_prediction),
onTap: () async {
// I couldn't find this API request within the Deezer app, but the
// same button uses the getSearchTrackMix API call, so let's use that
// instead.
// playerHelper.playMix(track.id, track.title!);
playerHelper.playSearchMix(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
], details: details);
}
//===================
// 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.instance.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.instance.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.instance.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.instance.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.instance.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.instance.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.instance.userId) {
//Delete playlist if own
await DeezerAPI.instance.deletePlaylist(p.id);
} else {
//Just remove from library
await DeezerAPI.instance.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.instance.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.instance.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(cache.wakelock
? 'Don\'t keep screen on'.i18n
: '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, super.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.instance.getPlaylists(),
builder: (context, snapshot) {
if (snapshot.hasError) {
const SizedBox(
height: 100,
child: ErrorScreen(),
);
}
if (snapshot.data == null) {
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, super.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.instance.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.instance.createPlaylist(_title,
status: _playlistType,
description: _description,
trackIds: tracks);
ScaffoldMessenger.of(context).snack('Playlist created!'.i18n);
}
Navigator.of(context).pop();
},
)
],
);
}
}