0.6.8 - bug fixes
This commit is contained in:
parent
c3a26b0e3b
commit
ff239aaf86
@ -247,17 +247,28 @@ public class Deezer {
|
|||||||
return original + ".mp3";
|
return original + ".mp3";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateUserUploadedMP3Filename(String original, JSONObject privateJson) throws Exception {
|
// public static String generateUserUploadedMP3Filename(String original, JSONObject privateJson) throws Exception {
|
||||||
//Remove unavailable tags
|
// //Remove unavailable tags
|
||||||
String[] ignored = {"%feats%", "%trackNumber%", "%0trackNumber%", "%year%", "%date%"};
|
// String[] ignored = {"%feats%", "%trackNumber%", "%0trackNumber%", "%year%", "%date%"};
|
||||||
|
// for (String i : ignored) {
|
||||||
|
// original = original.replaceAll(i, "");
|
||||||
|
// }
|
||||||
|
// //Basic tags
|
||||||
|
// original = original.replaceAll("%title%", privateJson.getString("SNG_TITLE"));
|
||||||
|
// original = original.replaceAll("%album%", privateJson.getString("ALB_TITLE"));
|
||||||
|
// original = original.replaceAll("%artist%", privateJson.getString("ART_NAME"));
|
||||||
|
// original = original.replaceAll("%artists%", privateJson.getString("ART_NAME"));
|
||||||
|
// return original;
|
||||||
|
// }
|
||||||
|
|
||||||
|
//Deezer patched something so getting metadata of user uploaded MP3s is not working anymore
|
||||||
|
public static String generateUserUploadedMP3Filename(String original, String title) throws Exception {
|
||||||
|
String[] ignored = {"%feats%", "%trackNumber%", "%0trackNumber%", "%year%", "%date%", "%album%", "%artist%", "%artists%"};
|
||||||
for (String i : ignored) {
|
for (String i : ignored) {
|
||||||
original = original.replaceAll(i, "");
|
original = original.replaceAll(i, "");
|
||||||
}
|
}
|
||||||
//Basic tags
|
|
||||||
original = original.replaceAll("%title%", privateJson.getString("SNG_TITLE"));
|
original = original.replace("%title%", sanitize(title));
|
||||||
original = original.replaceAll("%album%", privateJson.getString("ALB_TITLE"));
|
|
||||||
original = original.replaceAll("%artist%", privateJson.getString("ART_NAME"));
|
|
||||||
original = original.replaceAll("%artists%", privateJson.getString("ART_NAME"));
|
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,10 +96,8 @@ public class DownloadService extends Service {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
//Cancel notifications
|
//Cancel notifications
|
||||||
notificationManager.cancelAll();
|
notificationManager.cancelAll();
|
||||||
|
|
||||||
//Logger
|
//Logger
|
||||||
logger.close();
|
logger.close();
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,8 +114,10 @@ public class DownloadService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
//Get messenger
|
//Get messenger
|
||||||
if (intent != null)
|
if (intent != null) {
|
||||||
activityMessenger = intent.getParcelableExtra("activityMessenger");
|
activityMessenger = intent.getParcelableExtra("activityMessenger");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//return super.onStartCommand(intent, flags, startId);
|
//return super.onStartCommand(intent, flags, startId);
|
||||||
//Prevent battery savers I guess
|
//Prevent battery savers I guess
|
||||||
@ -295,24 +295,24 @@ public class DownloadService extends Service {
|
|||||||
while (deezer.authorizing)
|
while (deezer.authorizing)
|
||||||
try {Thread.sleep(50);} catch (Exception ignored) {}
|
try {Thread.sleep(50);} catch (Exception ignored) {}
|
||||||
|
|
||||||
//Fetch metadata
|
//Don't fetch meta if user uploaded mp3
|
||||||
try {
|
if (!download.isUserUploaded()) {
|
||||||
JSONObject privateRaw = deezer.callGWAPI("deezer.pageTrack", "{\"sng_id\": \"" + download.trackId + "\"}");
|
try {
|
||||||
privateJson = privateRaw.getJSONObject("results").getJSONObject("DATA");
|
JSONObject privateRaw = deezer.callGWAPI("deezer.pageTrack", "{\"sng_id\": \"" + download.trackId + "\"}");
|
||||||
if (privateRaw.getJSONObject("results").has("LYRICS")) {
|
privateJson = privateRaw.getJSONObject("results").getJSONObject("DATA");
|
||||||
lyricsData = privateRaw.getJSONObject("results").getJSONObject("LYRICS");
|
if (privateRaw.getJSONObject("results").has("LYRICS")) {
|
||||||
}
|
lyricsData = privateRaw.getJSONObject("results").getJSONObject("LYRICS");
|
||||||
//Don't fetch meta if user uploaded mp3
|
}
|
||||||
if (!download.isUserUploaded()) {
|
|
||||||
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
||||||
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
|
||||||
|
e.printStackTrace();
|
||||||
|
download.state = Download.DownloadState.ERROR;
|
||||||
|
exit();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
|
|
||||||
e.printStackTrace();
|
|
||||||
download.state = Download.DownloadState.ERROR;
|
|
||||||
exit();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fallback
|
//Fallback
|
||||||
@ -339,7 +339,7 @@ public class DownloadService extends Service {
|
|||||||
//Check file
|
//Check file
|
||||||
try {
|
try {
|
||||||
if (download.isUserUploaded()) {
|
if (download.isUserUploaded()) {
|
||||||
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, privateJson));
|
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, download.title));
|
||||||
} else {
|
} else {
|
||||||
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, qualityInfo.quality));
|
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, qualityInfo.quality));
|
||||||
}
|
}
|
||||||
@ -700,6 +700,8 @@ public class DownloadService extends Service {
|
|||||||
//Start/Resume
|
//Start/Resume
|
||||||
case SERVICE_START_DOWNLOAD:
|
case SERVICE_START_DOWNLOAD:
|
||||||
running = true;
|
running = true;
|
||||||
|
if (downloads.size() == 0)
|
||||||
|
loadDownloads();
|
||||||
updateQueue();
|
updateQueue();
|
||||||
updateState();
|
updateState();
|
||||||
break;
|
break;
|
||||||
|
@ -144,8 +144,9 @@ public class MainActivity extends FlutterActivity {
|
|||||||
}
|
}
|
||||||
//Start/Resume downloading
|
//Start/Resume downloading
|
||||||
if (call.method.equals("start")) {
|
if (call.method.equals("start")) {
|
||||||
|
//Connected
|
||||||
sendMessage(DownloadService.SERVICE_START_DOWNLOAD, null);
|
sendMessage(DownloadService.SERVICE_START_DOWNLOAD, null);
|
||||||
result.success(null);
|
result.success(serviceBound);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Stop downloading
|
//Stop downloading
|
||||||
@ -239,17 +240,24 @@ public class MainActivity extends FlutterActivity {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Start/Bind/Reconnect to download service
|
||||||
|
private void connectService() {
|
||||||
|
if (serviceBound)
|
||||||
|
return;
|
||||||
|
//Create messenger
|
||||||
|
activityMessenger = new Messenger(new IncomingHandler(this));
|
||||||
|
//Start
|
||||||
|
Intent intent = new Intent(this, DownloadService.class);
|
||||||
|
intent.putExtra("activityMessenger", activityMessenger);
|
||||||
|
startService(intent);
|
||||||
|
bindService(intent, connection, BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
//Bind downloader service
|
connectService();
|
||||||
activityMessenger = new Messenger(new IncomingHandler(this));
|
|
||||||
Intent intent = new Intent(this, DownloadService.class);
|
|
||||||
intent.putExtra("activityMessenger", activityMessenger);
|
|
||||||
startService(intent);
|
|
||||||
bindService(intent, connection, 0);
|
|
||||||
//Get DB
|
//Get DB
|
||||||
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
|
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
|
||||||
db = dbHelper.getWritableDatabase();
|
db = dbHelper.getWritableDatabase();
|
||||||
@ -274,17 +282,18 @@ public class MainActivity extends FlutterActivity {
|
|||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
Log.e(this.getLocalClassName(), e.getMessage());
|
Log.e(this.getLocalClassName(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
//Try reconnect
|
||||||
|
connectService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
//Unbind service on exit
|
|
||||||
if (serviceBound) {
|
|
||||||
unbindService(connection);
|
|
||||||
serviceBound = false;
|
|
||||||
}
|
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +303,12 @@ public class MainActivity extends FlutterActivity {
|
|||||||
//Stop server
|
//Stop server
|
||||||
if (streamServer != null)
|
if (streamServer != null)
|
||||||
streamServer.stop();
|
streamServer.stop();
|
||||||
|
|
||||||
|
//Unbind service on exit
|
||||||
|
if (serviceBound) {
|
||||||
|
unbindService(connection);
|
||||||
|
serviceBound = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Connection to download service
|
//Connection to download service
|
||||||
@ -302,12 +317,14 @@ public class MainActivity extends FlutterActivity {
|
|||||||
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
|
||||||
serviceMessenger = new Messenger(iBinder);
|
serviceMessenger = new Messenger(iBinder);
|
||||||
serviceBound = true;
|
serviceBound = true;
|
||||||
|
Log.d("DD", "Service Bound!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName componentName) {
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
serviceMessenger = null;
|
serviceMessenger = null;
|
||||||
serviceBound = false;
|
serviceBound = false;
|
||||||
|
Log.d("DD", "Service UnBound!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ class DownloadManager {
|
|||||||
|
|
||||||
//Start/Resume downloads
|
//Start/Resume downloads
|
||||||
Future start() async {
|
Future start() async {
|
||||||
|
//Returns whether service is bound or not, the delay is really shitty/hacky way, until i find a real solution
|
||||||
await updateServiceSettings();
|
await updateServiceSettings();
|
||||||
await platform.invokeMethod('start');
|
await platform.invokeMethod('start');
|
||||||
}
|
}
|
||||||
|
@ -319,6 +319,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
|
|
||||||
//Queue
|
//Queue
|
||||||
List<MediaItem> _queue = <MediaItem>[];
|
List<MediaItem> _queue = <MediaItem>[];
|
||||||
|
List<MediaItem> _originalQueue;
|
||||||
|
bool _shuffle = false;
|
||||||
int _queueIndex = 0;
|
int _queueIndex = 0;
|
||||||
ConcatenatingAudioSource _audioSource;
|
ConcatenatingAudioSource _audioSource;
|
||||||
|
|
||||||
@ -471,6 +473,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
//Update buffering state
|
//Update buffering state
|
||||||
_skipState = AudioProcessingState.skippingToPrevious;
|
_skipState = AudioProcessingState.skippingToPrevious;
|
||||||
|
|
||||||
|
|
||||||
//Normal skip to previous
|
//Normal skip to previous
|
||||||
_queueIndex--;
|
_queueIndex--;
|
||||||
await _player.seekToPrevious();
|
await _player.seekToPrevious();
|
||||||
@ -542,7 +545,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
if (_skipState != null) return _skipState;
|
if (_skipState != null) return _skipState;
|
||||||
//SRC: audio_service example
|
//SRC: audio_service example
|
||||||
switch (_player.processingState) {
|
switch (_player.processingState) {
|
||||||
case ProcessingState.none:
|
case ProcessingState.idle:
|
||||||
return AudioProcessingState.stopped;
|
return AudioProcessingState.stopped;
|
||||||
case ProcessingState.loading:
|
case ProcessingState.loading:
|
||||||
return AudioProcessingState.connecting;
|
return AudioProcessingState.connecting;
|
||||||
@ -586,8 +589,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
_audioSource = ConcatenatingAudioSource(children: sources);
|
_audioSource = ConcatenatingAudioSource(children: sources);
|
||||||
//Load in just_audio
|
//Load in just_audio
|
||||||
try {
|
try {
|
||||||
await _player.load(_audioSource, initialPosition: Duration.zero, initialIndex: qi);
|
await _player.setAudioSource(_audioSource, initialIndex: qi, initialPosition: Duration.zero);
|
||||||
// await _player.seek(Duration.zero, index: qi);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//Error loading tracks
|
//Error loading tracks
|
||||||
}
|
}
|
||||||
@ -599,7 +601,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
String url = await _getTrackUrl(mi);
|
String url = await _getTrackUrl(mi);
|
||||||
if (url == null) return null;
|
if (url == null) return null;
|
||||||
if (url.startsWith('http')) return ProgressiveAudioSource(Uri.parse(url));
|
if (url.startsWith('http')) return ProgressiveAudioSource(Uri.parse(url));
|
||||||
return AudioSource.uri(Uri.parse(url));
|
return AudioSource.uri(Uri.parse(url), tag: mi.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getTrackUrl(MediaItem mediaItem, {int quality}) async {
|
Future _getTrackUrl(MediaItem mediaItem, {int quality}) async {
|
||||||
@ -655,10 +657,26 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
await this._loadQueueFile();
|
await this._loadQueueFile();
|
||||||
//Shuffle
|
//Shuffle
|
||||||
if (name == 'shuffle') {
|
if (name == 'shuffle') {
|
||||||
_queue.shuffle();
|
String originalId = mediaItem.id;
|
||||||
AudioServiceBackground.setQueue(_queue);
|
if (!_shuffle) {
|
||||||
|
_shuffle = true;
|
||||||
|
_originalQueue = List.from(_queue);
|
||||||
|
_queue.shuffle();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_shuffle = false;
|
||||||
|
_queue = _originalQueue;
|
||||||
|
_originalQueue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Broken
|
||||||
|
// _queueIndex = _queue.indexWhere((mi) => mi.id == originalId);
|
||||||
_queueIndex = 0;
|
_queueIndex = 0;
|
||||||
|
AudioServiceBackground.setQueue(_queue);
|
||||||
|
AudioServiceBackground.setMediaItem(mediaItem);
|
||||||
|
await _player.stop();
|
||||||
await _loadQueue();
|
await _loadQueue();
|
||||||
|
await _player.play();
|
||||||
}
|
}
|
||||||
//Android auto callback
|
//Android auto callback
|
||||||
if (name == 'screenAndroidAuto' && _androidAutoCallback != null) {
|
if (name == 'screenAndroidAuto' && _androidAutoCallback != null) {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -307,6 +307,11 @@ const language_en_us = {
|
|||||||
"Episodes": "Episodes",
|
"Episodes": "Episodes",
|
||||||
"Show all episodes": "Show all episodes",
|
"Show all episodes": "Show all episodes",
|
||||||
"Album cover resolution": "Album cover resolution",
|
"Album cover resolution": "Album cover resolution",
|
||||||
"WARNING: Resolutions above 1200 aren't officially supported": "WARNING: Resolutions above 1200 aren't officially supported"
|
"WARNING: Resolutions above 1200 aren't officially supported": "WARNING: Resolutions above 1200 aren't officially supported",
|
||||||
|
|
||||||
|
//0.6.8:
|
||||||
|
"Album removed from library!": "Album removed from library!",
|
||||||
|
"Remove offline": "Remove offline",
|
||||||
|
"Playlist removed from library!": "Playlist removed from library!"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttericon/font_awesome5_icons.dart';
|
import 'package:fluttericon/font_awesome5_icons.dart';
|
||||||
@ -18,17 +19,34 @@ import 'cached_image.dart';
|
|||||||
import 'tiles.dart';
|
import 'tiles.dart';
|
||||||
import 'menu.dart';
|
import 'menu.dart';
|
||||||
|
|
||||||
class AlbumDetails extends StatelessWidget {
|
class AlbumDetails extends StatefulWidget {
|
||||||
|
|
||||||
Album album;
|
Album album;
|
||||||
|
AlbumDetails(this.album, {Key key}): super(key: key);
|
||||||
|
|
||||||
AlbumDetails(this.album);
|
@override
|
||||||
|
_AlbumDetailsState createState() => _AlbumDetailsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlbumDetailsState extends State<AlbumDetails> {
|
||||||
|
|
||||||
|
Album album;
|
||||||
|
bool _loading = true;
|
||||||
|
bool _error = false;
|
||||||
|
|
||||||
Future _loadAlbum() async {
|
Future _loadAlbum() async {
|
||||||
//Get album from API, if doesn't have tracks
|
//Get album from API, if doesn't have tracks
|
||||||
if (this.album.tracks == null || this.album.tracks.length == 0) {
|
if (this.album.tracks == null || this.album.tracks.length == 0) {
|
||||||
this.album = await deezerAPI.album(album.id);
|
try {
|
||||||
|
Album a = await deezerAPI.album(album.id);
|
||||||
|
//Preserve library
|
||||||
|
a.library = album.library;
|
||||||
|
setState(() => album = a);
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _error = true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
setState(() => _loading = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get count of CDs in album
|
//Get count of CDs in album
|
||||||
@ -40,164 +58,176 @@ class AlbumDetails extends StatelessWidget {
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
this.album = widget.album;
|
||||||
|
_loadAlbum();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: FutureBuilder(
|
body: _error ? ErrorScreen() : _loading ? Center(child: CircularProgressIndicator()) :
|
||||||
future: _loadAlbum(),
|
ListView(
|
||||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
children: <Widget>[
|
||||||
|
//Album art, title, artists
|
||||||
//Wait for data
|
Container(
|
||||||
if (snapshot.connectionState != ConnectionState.done) return Center(child: CircularProgressIndicator(),);
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
//On error
|
child: Column(
|
||||||
if (snapshot.hasError) return ErrorScreen();
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
return ListView(
|
Container(height: 8.0,),
|
||||||
children: <Widget>[
|
ZoomableImage(
|
||||||
//Album art, title, artists
|
url: album.art.full,
|
||||||
Container(
|
width: MediaQuery.of(context).size.width / 2,
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
rounded: true,
|
||||||
child: Column(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
Container(height: 8,),
|
||||||
children: <Widget>[
|
Text(
|
||||||
Container(height: 8.0,),
|
album.title,
|
||||||
ZoomableImage(
|
textAlign: TextAlign.center,
|
||||||
url: album.art.full,
|
overflow: TextOverflow.ellipsis,
|
||||||
width: MediaQuery.of(context).size.width / 2,
|
maxLines: 2,
|
||||||
rounded: true,
|
style: TextStyle(
|
||||||
|
fontSize: 20.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
),
|
),
|
||||||
Container(height: 8,),
|
),
|
||||||
|
Text(
|
||||||
|
album.artistString,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0,
|
||||||
|
color: Theme.of(context).primaryColor
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 4.0),
|
||||||
|
if (album.releaseDate != null && album.releaseDate.length >= 4)
|
||||||
Text(
|
Text(
|
||||||
album.title,
|
album.releaseDate,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20.0,
|
|
||||||
fontWeight: FontWeight.bold
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
album.artistString,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0,
|
|
||||||
color: Theme.of(context).primaryColor
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 4.0),
|
|
||||||
if (album.releaseDate != null && album.releaseDate.length >= 4)
|
|
||||||
Text(
|
|
||||||
album.releaseDate,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
color: Theme.of(context).disabledColor
|
color: Theme.of(context).disabledColor
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Container(height: 8.0,),
|
),
|
||||||
],
|
Container(height: 8.0,),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
FreezerDivider(),
|
),
|
||||||
//Details
|
FreezerDivider(),
|
||||||
Container(
|
//Details
|
||||||
child: Row(
|
Container(
|
||||||
mainAxisSize: MainAxisSize.max,
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: <Widget>[
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
Row(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Row(
|
||||||
Icon(Icons.audiotrack, size: 32.0,),
|
children: <Widget>[
|
||||||
Container(width: 8.0, height: 42.0,), //Height to adjust card height
|
Icon(Icons.audiotrack, size: 32.0,),
|
||||||
Text(
|
Container(width: 8.0, height: 42.0,), //Height to adjust card height
|
||||||
album.tracks.length.toString(),
|
Text(
|
||||||
style: TextStyle(fontSize: 16.0),
|
album.tracks.length.toString(),
|
||||||
)
|
style: TextStyle(fontSize: 16.0),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
Row(
|
),
|
||||||
children: <Widget>[
|
Row(
|
||||||
Icon(Icons.timelapse, size: 32.0,),
|
children: <Widget>[
|
||||||
Container(width: 8.0,),
|
Icon(Icons.timelapse, size: 32.0,),
|
||||||
Text(
|
Container(width: 8.0,),
|
||||||
album.durationString,
|
Text(
|
||||||
style: TextStyle(fontSize: 16.0),
|
album.durationString,
|
||||||
)
|
style: TextStyle(fontSize: 16.0),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
Row(
|
),
|
||||||
children: <Widget>[
|
Row(
|
||||||
Icon(Icons.people, size: 32.0,),
|
children: <Widget>[
|
||||||
Container(width: 8.0,),
|
Icon(Icons.people, size: 32.0,),
|
||||||
Text(
|
Container(width: 8.0,),
|
||||||
album.fansString,
|
Text(
|
||||||
style: TextStyle(fontSize: 16.0),
|
album.fansString,
|
||||||
)
|
style: TextStyle(fontSize: 16.0),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
FreezerDivider(),
|
),
|
||||||
//Options (offline, download...)
|
FreezerDivider(),
|
||||||
Container(
|
//Options (offline, download...)
|
||||||
child: Row(
|
Container(
|
||||||
mainAxisSize: MainAxisSize.max,
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: <Widget>[
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
FlatButton(
|
children: <Widget>[
|
||||||
child: Row(
|
FlatButton(
|
||||||
children: <Widget>[
|
child: Row(
|
||||||
Icon(Icons.favorite, size: 32),
|
children: <Widget>[
|
||||||
Container(width: 4,),
|
Icon((album.library??false)? Icons.favorite : Icons.favorite_border, size: 32),
|
||||||
Text('Library'.i18n)
|
Container(width: 4,),
|
||||||
],
|
Text('Library'.i18n)
|
||||||
),
|
],
|
||||||
onPressed: () async {
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
//Add to library
|
||||||
|
if (!album.library) {
|
||||||
await deezerAPI.addFavoriteAlbum(album.id);
|
await deezerAPI.addFavoriteAlbum(album.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library'.i18n,
|
msg: 'Added to library'.i18n,
|
||||||
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.BOTTOM
|
||||||
|
);
|
||||||
|
setState(() => album.library = true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Remove
|
||||||
|
await deezerAPI.removeAlbum(album.id);
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Album removed from library!'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
},
|
setState(() => album.library = false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MakeAlbumOffline(album: album),
|
||||||
|
FlatButton(
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(Icons.file_download, size: 32.0,),
|
||||||
|
Container(width: 4,),
|
||||||
|
Text('Download'.i18n)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
MakeAlbumOffline(album: album),
|
onPressed: () async {
|
||||||
FlatButton(
|
if (await downloadManager.addOfflineAlbum(album, private: false, context: context) != false)
|
||||||
child: Row(
|
MenuSheet(context).showDownloadStartedToast();
|
||||||
children: <Widget>[
|
},
|
||||||
Icon(Icons.file_download, size: 32.0,),
|
)
|
||||||
Container(width: 4,),
|
],
|
||||||
Text('Download'.i18n)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
if (await downloadManager.addOfflineAlbum(album, private: false, context: context) != false)
|
|
||||||
MenuSheet(context).showDownloadStartedToast();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
FreezerDivider(),
|
),
|
||||||
...List.generate(cdCount, (cdi) {
|
FreezerDivider(),
|
||||||
List<Track> tracks = album.tracks.where((t) => (t.diskNumber??1) == cdi + 1).toList();
|
...List.generate(cdCount, (cdi) {
|
||||||
return Column(
|
List<Track> tracks = album.tracks.where((t) => (t.diskNumber??1) == cdi + 1).toList();
|
||||||
children: [
|
return Column(
|
||||||
Padding(
|
children: [
|
||||||
padding: EdgeInsets.symmetric(vertical: 4.0),
|
Padding(
|
||||||
child: Text(
|
padding: EdgeInsets.symmetric(vertical: 4.0),
|
||||||
'Disk'.i18n.toUpperCase() + ' ${cdi + 1}',
|
child: Text(
|
||||||
style: TextStyle(
|
'Disk'.i18n.toUpperCase() + ' ${cdi + 1}',
|
||||||
|
style: TextStyle(
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
fontWeight: FontWeight.w300
|
fontWeight: FontWeight.w300
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...List.generate(tracks.length, (i) => TrackTile(
|
),
|
||||||
|
...List.generate(tracks.length, (i) => TrackTile(
|
||||||
tracks[i],
|
tracks[i],
|
||||||
onTap: () {
|
onTap: () {
|
||||||
playerHelper.playFromAlbum(album, tracks[i].id);
|
playerHelper.playFromAlbum(album, tracks[i].id);
|
||||||
@ -206,14 +236,12 @@ class AlbumDetails extends StatelessWidget {
|
|||||||
MenuSheet m = MenuSheet(context);
|
MenuSheet m = MenuSheet(context);
|
||||||
m.defaultTrackMenu(tracks[i]);
|
m.defaultTrackMenu(tracks[i]);
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
);
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -812,200 +840,220 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: ListView(
|
body: DraggableScrollbar.rrect(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
children: <Widget>[
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
Container(height: 4.0,),
|
child: ListView(
|
||||||
Padding(
|
controller: _scrollController,
|
||||||
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
children: <Widget>[
|
||||||
child: Row(
|
Container(height: 4.0,),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
CachedImage(
|
|
||||||
url: playlist.image.full,
|
|
||||||
height: MediaQuery.of(context).size.width / 2 - 8,
|
|
||||||
rounded: true,
|
|
||||||
fullThumb: true,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: MediaQuery.of(context).size.width / 2 - 8,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
playlist.title,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
maxLines: 3,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20.0,
|
|
||||||
fontWeight: FontWeight.bold
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 4.0),
|
|
||||||
Text(
|
|
||||||
playlist.user.name??'',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
fontSize: 17.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 10.0),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(
|
|
||||||
Icons.audiotrack,
|
|
||||||
size: 32.0,
|
|
||||||
),
|
|
||||||
Container(width: 8.0,),
|
|
||||||
Text((playlist.trackCount??playlist.tracks.length).toString(), style: TextStyle(fontSize: 16),)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(
|
|
||||||
Icons.timelapse,
|
|
||||||
size: 32.0,
|
|
||||||
),
|
|
||||||
Container(width: 8.0,),
|
|
||||||
Text(playlist.durationString, style: TextStyle(fontSize: 16),)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (playlist.description != null && playlist.description.length > 0)
|
|
||||||
FreezerDivider(),
|
|
||||||
if (playlist.description != null && playlist.description.length > 0)
|
|
||||||
Container(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(6.0),
|
|
||||||
child: Text(
|
|
||||||
playlist.description ?? '',
|
|
||||||
maxLines: 4,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
FreezerDivider(),
|
|
||||||
Container(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: <Widget>[
|
|
||||||
MakePlaylistOffline(playlist),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.favorite, size: 32),
|
|
||||||
onPressed: () async {
|
|
||||||
await deezerAPI.addPlaylist(playlist.id);
|
|
||||||
Fluttertoast.showToast(
|
|
||||||
msg: 'Added to library'.i18n,
|
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
|
||||||
gravity: ToastGravity.BOTTOM
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.file_download, size: 32.0,),
|
|
||||||
onPressed: () async {
|
|
||||||
if (await downloadManager.addOfflinePlaylist(playlist, private: false, context: context) != false)
|
|
||||||
MenuSheet(context).showDownloadStartedToast();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
PopupMenuButton(
|
|
||||||
child: Icon(Icons.sort, size: 32.0),
|
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
onSelected: (SortType s) async {
|
|
||||||
if (playlist.tracks.length < playlist.trackCount) {
|
|
||||||
//Preload whole playlist
|
|
||||||
playlist = await deezerAPI.fullPlaylist(playlist.id);
|
|
||||||
}
|
|
||||||
setState(() => _sort.type = s);
|
|
||||||
|
|
||||||
//Save sort type to cache
|
|
||||||
int index = Sorting.index(SortSourceTypes.PLAYLIST, id: playlist.id);
|
|
||||||
if (index == null) {
|
|
||||||
cache.sorts.add(_sort);
|
|
||||||
} else {
|
|
||||||
cache.sorts[index] = _sort;
|
|
||||||
}
|
|
||||||
await cache.save();
|
|
||||||
},
|
|
||||||
itemBuilder: (context) => <PopupMenuEntry<SortType>>[
|
|
||||||
PopupMenuItem(
|
|
||||||
value: SortType.DEFAULT,
|
|
||||||
child: Text('Default'.i18n, style: popupMenuTextStyle()),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: SortType.ALPHABETIC,
|
|
||||||
child: Text('Alphabetic'.i18n, style: popupMenuTextStyle()),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: SortType.ARTIST,
|
|
||||||
child: Text('Artist'.i18n, style: popupMenuTextStyle()),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: SortType.DATE_ADDED,
|
|
||||||
child: Text('Date added'.i18n, style: popupMenuTextStyle()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
|
|
||||||
onPressed: () => _reverse(),
|
|
||||||
),
|
|
||||||
Container(width: 4.0)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FreezerDivider(),
|
|
||||||
...List.generate(playlist.tracks.length, (i) {
|
|
||||||
Track t = sorted[i];
|
|
||||||
return TrackTile(
|
|
||||||
t,
|
|
||||||
onTap: () {
|
|
||||||
Playlist p = Playlist(
|
|
||||||
title: playlist.title,
|
|
||||||
id: playlist.id,
|
|
||||||
tracks: sorted
|
|
||||||
);
|
|
||||||
playerHelper.playFromPlaylist(p, t.id);
|
|
||||||
},
|
|
||||||
onHold: () {
|
|
||||||
MenuSheet m = MenuSheet(context);
|
|
||||||
m.defaultTrackMenu(t, options: [
|
|
||||||
(playlist.user.id == deezerAPI.userId) ? m.removeFromPlaylist(t, playlist) : Container(width: 0, height: 0,)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
if (_loading)
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CircularProgressIndicator()
|
CachedImage(
|
||||||
|
url: playlist.image.full,
|
||||||
|
height: MediaQuery.of(context).size.width / 2 - 8,
|
||||||
|
rounded: true,
|
||||||
|
fullThumb: true,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width / 2 - 8,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
playlist.title,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 3,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.0,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 4.0),
|
||||||
|
Text(
|
||||||
|
playlist.user.name??'',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
fontSize: 17.0
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: 10.0),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(
|
||||||
|
Icons.audiotrack,
|
||||||
|
size: 32.0,
|
||||||
|
),
|
||||||
|
Container(width: 8.0,),
|
||||||
|
Text((playlist.trackCount??playlist.tracks.length).toString(), style: TextStyle(fontSize: 16),)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(
|
||||||
|
Icons.timelapse,
|
||||||
|
size: 32.0,
|
||||||
|
),
|
||||||
|
Container(width: 8.0,),
|
||||||
|
Text(playlist.durationString, style: TextStyle(fontSize: 16),)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_error)
|
if (playlist.description != null && playlist.description.length > 0)
|
||||||
ErrorScreen()
|
FreezerDivider(),
|
||||||
],
|
if (playlist.description != null && playlist.description.length > 0)
|
||||||
|
Container(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(6.0),
|
||||||
|
child: Text(
|
||||||
|
playlist.description ?? '',
|
||||||
|
maxLines: 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FreezerDivider(),
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: <Widget>[
|
||||||
|
MakePlaylistOffline(playlist),
|
||||||
|
|
||||||
|
if (playlist.user.name != deezerAPI.userName)
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(playlist.library ? Icons.favorite : Icons.favorite_outline, size: 32),
|
||||||
|
onPressed: () async {
|
||||||
|
//Add to library
|
||||||
|
if (!playlist.library) {
|
||||||
|
await deezerAPI.addPlaylist(playlist.id);
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Added to library'.i18n,
|
||||||
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.BOTTOM
|
||||||
|
);
|
||||||
|
setState(() => playlist.library = true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Remove
|
||||||
|
await deezerAPI.removePlaylist(playlist.id);
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Playlist removed from library!'.i18n,
|
||||||
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
|
gravity: ToastGravity.BOTTOM
|
||||||
|
);
|
||||||
|
setState(() => playlist.library = false);
|
||||||
|
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.file_download, size: 32.0,),
|
||||||
|
onPressed: () async {
|
||||||
|
if (await downloadManager.addOfflinePlaylist(playlist, private: false, context: context) != false)
|
||||||
|
MenuSheet(context).showDownloadStartedToast();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
child: Icon(Icons.sort, size: 32.0),
|
||||||
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
onSelected: (SortType s) async {
|
||||||
|
if (playlist.tracks.length < playlist.trackCount) {
|
||||||
|
//Preload whole playlist
|
||||||
|
playlist = await deezerAPI.fullPlaylist(playlist.id);
|
||||||
|
}
|
||||||
|
setState(() => _sort.type = s);
|
||||||
|
|
||||||
|
//Save sort type to cache
|
||||||
|
int index = Sorting.index(SortSourceTypes.PLAYLIST, id: playlist.id);
|
||||||
|
if (index == null) {
|
||||||
|
cache.sorts.add(_sort);
|
||||||
|
} else {
|
||||||
|
cache.sorts[index] = _sort;
|
||||||
|
}
|
||||||
|
await cache.save();
|
||||||
|
},
|
||||||
|
itemBuilder: (context) => <PopupMenuEntry<SortType>>[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: SortType.DEFAULT,
|
||||||
|
child: Text('Default'.i18n, style: popupMenuTextStyle()),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: SortType.ALPHABETIC,
|
||||||
|
child: Text('Alphabetic'.i18n, style: popupMenuTextStyle()),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: SortType.ARTIST,
|
||||||
|
child: Text('Artist'.i18n, style: popupMenuTextStyle()),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: SortType.DATE_ADDED,
|
||||||
|
child: Text('Date added'.i18n, style: popupMenuTextStyle()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
|
||||||
|
onPressed: () => _reverse(),
|
||||||
|
),
|
||||||
|
Container(width: 4.0)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FreezerDivider(),
|
||||||
|
...List.generate(playlist.tracks.length, (i) {
|
||||||
|
Track t = sorted[i];
|
||||||
|
return TrackTile(
|
||||||
|
t,
|
||||||
|
onTap: () {
|
||||||
|
Playlist p = Playlist(
|
||||||
|
title: playlist.title,
|
||||||
|
id: playlist.id,
|
||||||
|
tracks: sorted
|
||||||
|
);
|
||||||
|
playerHelper.playFromPlaylist(p, t.id);
|
||||||
|
},
|
||||||
|
onHold: () {
|
||||||
|
MenuSheet m = MenuSheet(context);
|
||||||
|
m.defaultTrackMenu(t, options: [
|
||||||
|
(playlist.user.id == deezerAPI.userId) ? m.removeFromPlaylist(t, playlist) : Container(width: 0, height: 0,)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
if (_loading)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
CircularProgressIndicator()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_error)
|
||||||
|
ErrorScreen()
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:freezer/main.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
@ -12,18 +13,19 @@ import 'package:freezer/api/player.dart';
|
|||||||
import 'package:freezer/ui/details_screens.dart';
|
import 'package:freezer/ui/details_screens.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
import 'package:freezer/translations.i18n.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:numberpicker/numberpicker.dart';
|
||||||
import 'package:share/share.dart';
|
import 'package:share/share.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../api/definitions.dart';
|
|
||||||
import 'cached_image.dart';
|
|
||||||
|
|
||||||
class MenuSheet {
|
class MenuSheet {
|
||||||
|
|
||||||
BuildContext context;
|
BuildContext context;
|
||||||
|
Function navigateCallback;
|
||||||
|
|
||||||
MenuSheet(this.context);
|
MenuSheet(this.context, {this.navigateCallback});
|
||||||
|
|
||||||
//===================
|
//===================
|
||||||
// DEFAULT
|
// DEFAULT
|
||||||
@ -273,9 +275,13 @@ class MenuSheet {
|
|||||||
leading: Icon(Icons.recent_actors),
|
leading: Icon(Icons.recent_actors),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_close();
|
_close();
|
||||||
Navigator.of(context).push(
|
navigatorKey.currentState.push(
|
||||||
MaterialPageRoute(builder: (context) => ArtistDetails(a))
|
MaterialPageRoute(builder: (context) => ArtistDetails(a))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.navigateCallback != null) {
|
||||||
|
this.navigateCallback();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -288,9 +294,13 @@ class MenuSheet {
|
|||||||
leading: Icon(Icons.album),
|
leading: Icon(Icons.album),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_close();
|
_close();
|
||||||
Navigator.of(context).push(
|
navigatorKey.currentState.push(
|
||||||
MaterialPageRoute(builder: (context) => AlbumDetails(a))
|
MaterialPageRoute(builder: (context) => AlbumDetails(a))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.navigateCallback != null) {
|
||||||
|
this.navigateCallback();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -303,12 +313,22 @@ class MenuSheet {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget offlineTrack(Track track) => ListTile(
|
Widget offlineTrack(Track track) => FutureBuilder(
|
||||||
title: Text('Offline'.i18n),
|
future: downloadManager.checkOffline(track: track),
|
||||||
leading: Icon(Icons.offline_pin),
|
builder: (context, snapshot) {
|
||||||
onTap: () async {
|
bool isOffline = snapshot.data??(track.offline??false);
|
||||||
await downloadManager.addOfflineTrack(track, private: true, context: context);
|
return ListTile(
|
||||||
_close();
|
title: Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n),
|
||||||
|
leading: Icon(Icons.offline_pin),
|
||||||
|
onTap: () async {
|
||||||
|
if (isOffline) {
|
||||||
|
await downloadManager.removeOfflineTracks([track]);
|
||||||
|
} else {
|
||||||
|
await downloadManager.addOfflineTrack(track, private: true, context: context);
|
||||||
|
}
|
||||||
|
_close();
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -51,16 +51,16 @@ class _PlayerScreenState extends State<PlayerScreen> {
|
|||||||
|
|
||||||
//Update notification
|
//Update notification
|
||||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||||
statusBarColor: palette.dominantColor.color.withOpacity(0.5)
|
statusBarColor: palette.dominantColor.color.withOpacity(0.7)
|
||||||
));
|
));
|
||||||
|
|
||||||
setState(() => _bgGradient = LinearGradient(
|
setState(() => _bgGradient = LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [palette.dominantColor.color.withOpacity(0.5), Color.fromARGB(0, 0, 0, 0)],
|
colors: [palette.dominantColor.color.withOpacity(0.7), Color.fromARGB(0, 0, 0, 0)],
|
||||||
stops: [
|
stops: [
|
||||||
0.0,
|
0.0,
|
||||||
0.4
|
0.6
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -408,7 +408,9 @@ class PlayerMenuButton extends StatelessWidget {
|
|||||||
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(46)),
|
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(46)),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
||||||
MenuSheet m = MenuSheet(context);
|
MenuSheet m = MenuSheet(context, navigateCallback: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
});
|
||||||
if (AudioService.currentMediaItem.extras['show'] == null)
|
if (AudioService.currentMediaItem.extras['show'] == null)
|
||||||
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
|
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
|
||||||
else
|
else
|
||||||
|
@ -1362,7 +1362,7 @@ class _CreditsScreenState extends State<CreditsScreen> {
|
|||||||
subtitle: Text('Official Discord server'.i18n),
|
subtitle: Text('Official Discord server'.i18n),
|
||||||
leading: Icon(FontAwesome5.discord, color: Color(0xff7289da), size: 36.0),
|
leading: Icon(FontAwesome5.discord, color: Color(0xff7289da), size: 36.0),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launch('https://discord.gg/7ap654Tp3z');
|
launch('https://discord.gg/qwJpa3r4dQ');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -1373,6 +1373,14 @@ class _CreditsScreenState extends State<CreditsScreen> {
|
|||||||
launch('https://git.rip/freezer/');
|
launch('https://git.rip/freezer/');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Donate'),
|
||||||
|
subtitle: Text('You should rather support your favorite artists, instead of this app!'),
|
||||||
|
leading: Icon(FontAwesome5.paypal, color: Colors.blue, size: 36.0),
|
||||||
|
onTap: () {
|
||||||
|
launch('https://paypal.me/exttex');
|
||||||
|
},
|
||||||
|
),
|
||||||
FreezerDivider(),
|
FreezerDivider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('exttex'),
|
title: Text('exttex'),
|
||||||
|
@ -517,21 +517,21 @@ packages:
|
|||||||
name: just_audio
|
name: just_audio
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.6"
|
version: "0.6.1"
|
||||||
just_audio_platform_interface:
|
just_audio_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: just_audio_platform_interface
|
name: just_audio_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "2.0.0"
|
||||||
just_audio_web:
|
just_audio_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: just_audio_web
|
name: just_audio_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
version: "0.2.0"
|
||||||
language_pickers:
|
language_pickers:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 0.6.7+1
|
version: 0.6.8+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.8.0 <3.0.0"
|
sdk: ">=2.8.0 <3.0.0"
|
||||||
@ -79,7 +79,7 @@ dependencies:
|
|||||||
audio_session: ^0.0.9
|
audio_session: ^0.0.9
|
||||||
audio_service:
|
audio_service:
|
||||||
path: ./audio_service
|
path: ./audio_service
|
||||||
just_audio: ^0.5.6
|
just_audio: 0.6.1
|
||||||
# path: ./just_audio
|
# path: ./just_audio
|
||||||
|
|
||||||
# cupertino_icons: ^0.1.3
|
# cupertino_icons: ^0.1.3
|
||||||
|
Loading…
Reference in New Issue
Block a user