0.6.8 - bug fixes

This commit is contained in:
exttex 2020-12-27 19:33:59 +01:00
parent c3a26b0e3b
commit ff239aaf86
13 changed files with 534 additions and 402 deletions

View File

@ -247,17 +247,28 @@ public class Deezer {
return original + ".mp3";
}
public static String generateUserUploadedMP3Filename(String original, JSONObject privateJson) throws Exception {
//Remove unavailable tags
String[] ignored = {"%feats%", "%trackNumber%", "%0trackNumber%", "%year%", "%date%"};
// public static String generateUserUploadedMP3Filename(String original, JSONObject privateJson) throws Exception {
// //Remove unavailable tags
// 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) {
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"));
original = original.replace("%title%", sanitize(title));
return original;
}

View File

@ -96,10 +96,8 @@ public class DownloadService extends Service {
public void onDestroy() {
//Cancel notifications
notificationManager.cancelAll();
//Logger
logger.close();
super.onDestroy();
}
@ -116,8 +114,10 @@ public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Get messenger
if (intent != null)
if (intent != null) {
activityMessenger = intent.getParcelableExtra("activityMessenger");
}
//return super.onStartCommand(intent, flags, startId);
//Prevent battery savers I guess
@ -295,24 +295,24 @@ public class DownloadService extends Service {
while (deezer.authorizing)
try {Thread.sleep(50);} catch (Exception ignored) {}
//Fetch metadata
try {
JSONObject privateRaw = deezer.callGWAPI("deezer.pageTrack", "{\"sng_id\": \"" + download.trackId + "\"}");
privateJson = privateRaw.getJSONObject("results").getJSONObject("DATA");
if (privateRaw.getJSONObject("results").has("LYRICS")) {
lyricsData = privateRaw.getJSONObject("results").getJSONObject("LYRICS");
}
//Don't fetch meta if user uploaded mp3
if (!download.isUserUploaded()) {
//Don't fetch meta if user uploaded mp3
if (!download.isUserUploaded()) {
try {
JSONObject privateRaw = deezer.callGWAPI("deezer.pageTrack", "{\"sng_id\": \"" + download.trackId + "\"}");
privateJson = privateRaw.getJSONObject("results").getJSONObject("DATA");
if (privateRaw.getJSONObject("results").has("LYRICS")) {
lyricsData = privateRaw.getJSONObject("results").getJSONObject("LYRICS");
}
trackJson = Deezer.callPublicAPI("track", download.trackId);
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
@ -339,7 +339,7 @@ public class DownloadService extends Service {
//Check file
try {
if (download.isUserUploaded()) {
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, privateJson));
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, download.title));
} else {
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, qualityInfo.quality));
}
@ -700,6 +700,8 @@ public class DownloadService extends Service {
//Start/Resume
case SERVICE_START_DOWNLOAD:
running = true;
if (downloads.size() == 0)
loadDownloads();
updateQueue();
updateState();
break;

View File

@ -144,8 +144,9 @@ public class MainActivity extends FlutterActivity {
}
//Start/Resume downloading
if (call.method.equals("start")) {
//Connected
sendMessage(DownloadService.SERVICE_START_DOWNLOAD, null);
result.success(null);
result.success(serviceBound);
return;
}
//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
protected void onStart() {
super.onStart();
//Bind downloader service
activityMessenger = new Messenger(new IncomingHandler(this));
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("activityMessenger", activityMessenger);
startService(intent);
bindService(intent, connection, 0);
connectService();
//Get DB
DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext());
db = dbHelper.getWritableDatabase();
@ -274,17 +282,18 @@ public class MainActivity extends FlutterActivity {
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Log.e(this.getLocalClassName(), e.getMessage());
}
}
@Override
protected void onResume() {
super.onResume();
//Try reconnect
connectService();
}
@Override
protected void onStop() {
super.onStop();
//Unbind service on exit
if (serviceBound) {
unbindService(connection);
serviceBound = false;
}
db.close();
}
@ -294,6 +303,12 @@ public class MainActivity extends FlutterActivity {
//Stop server
if (streamServer != null)
streamServer.stop();
//Unbind service on exit
if (serviceBound) {
unbindService(connection);
serviceBound = false;
}
}
//Connection to download service
@ -302,12 +317,14 @@ public class MainActivity extends FlutterActivity {
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
serviceMessenger = new Messenger(iBinder);
serviceBound = true;
Log.d("DD", "Service Bound!");
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
serviceMessenger = null;
serviceBound = false;
Log.d("DD", "Service UnBound!");
}
};

View File

@ -36,6 +36,7 @@ class DownloadManager {
//Start/Resume downloads
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 platform.invokeMethod('start');
}

View File

@ -319,6 +319,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Queue
List<MediaItem> _queue = <MediaItem>[];
List<MediaItem> _originalQueue;
bool _shuffle = false;
int _queueIndex = 0;
ConcatenatingAudioSource _audioSource;
@ -471,6 +473,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Update buffering state
_skipState = AudioProcessingState.skippingToPrevious;
//Normal skip to previous
_queueIndex--;
await _player.seekToPrevious();
@ -542,7 +545,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
if (_skipState != null) return _skipState;
//SRC: audio_service example
switch (_player.processingState) {
case ProcessingState.none:
case ProcessingState.idle:
return AudioProcessingState.stopped;
case ProcessingState.loading:
return AudioProcessingState.connecting;
@ -586,8 +589,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
_audioSource = ConcatenatingAudioSource(children: sources);
//Load in just_audio
try {
await _player.load(_audioSource, initialPosition: Duration.zero, initialIndex: qi);
// await _player.seek(Duration.zero, index: qi);
await _player.setAudioSource(_audioSource, initialIndex: qi, initialPosition: Duration.zero);
} catch (e) {
//Error loading tracks
}
@ -599,7 +601,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
String url = await _getTrackUrl(mi);
if (url == null) return null;
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 {
@ -655,10 +657,26 @@ class AudioPlayerTask extends BackgroundAudioTask {
await this._loadQueueFile();
//Shuffle
if (name == 'shuffle') {
_queue.shuffle();
AudioServiceBackground.setQueue(_queue);
String originalId = mediaItem.id;
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;
AudioServiceBackground.setQueue(_queue);
AudioServiceBackground.setMediaItem(mediaItem);
await _player.stop();
await _loadQueue();
await _player.play();
}
//Android auto callback
if (name == 'screenAndroidAuto' && _androidAutoCallback != null) {

File diff suppressed because one or more lines are too long

View File

@ -307,6 +307,11 @@ const language_en_us = {
"Episodes": "Episodes",
"Show all episodes": "Show all episodes",
"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!"
}
};

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttericon/font_awesome5_icons.dart';
@ -18,17 +19,34 @@ import 'cached_image.dart';
import 'tiles.dart';
import 'menu.dart';
class AlbumDetails extends StatelessWidget {
class AlbumDetails extends StatefulWidget {
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 {
//Get album from API, if doesn't have tracks
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
@ -40,164 +58,176 @@ class AlbumDetails extends StatelessWidget {
return c;
}
@override
void initState() {
this.album = widget.album;
_loadAlbum();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _loadAlbum(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
//Wait for data
if (snapshot.connectionState != ConnectionState.done) return Center(child: CircularProgressIndicator(),);
//On error
if (snapshot.hasError) return ErrorScreen();
return ListView(
children: <Widget>[
//Album art, title, artists
Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(height: 8.0,),
ZoomableImage(
url: album.art.full,
width: MediaQuery.of(context).size.width / 2,
rounded: true,
body: _error ? ErrorScreen() : _loading ? Center(child: CircularProgressIndicator()) :
ListView(
children: <Widget>[
//Album art, title, artists
Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(height: 8.0,),
ZoomableImage(
url: album.art.full,
width: MediaQuery.of(context).size.width / 2,
rounded: true,
),
Container(height: 8,),
Text(
album.title,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
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(
album.title,
album.releaseDate,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
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,
color: Theme.of(context).disabledColor
),
),
Container(height: 8.0,),
],
),
),
Container(height: 8.0,),
],
),
FreezerDivider(),
//Details
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
children: <Widget>[
Icon(Icons.audiotrack, size: 32.0,),
Container(width: 8.0, height: 42.0,), //Height to adjust card height
Text(
album.tracks.length.toString(),
style: TextStyle(fontSize: 16.0),
)
],
),
Row(
children: <Widget>[
Icon(Icons.timelapse, size: 32.0,),
Container(width: 8.0,),
Text(
album.durationString,
style: TextStyle(fontSize: 16.0),
)
],
),
Row(
children: <Widget>[
Icon(Icons.people, size: 32.0,),
Container(width: 8.0,),
Text(
album.fansString,
style: TextStyle(fontSize: 16.0),
)
],
),
],
),
),
FreezerDivider(),
//Details
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Row(
children: <Widget>[
Icon(Icons.audiotrack, size: 32.0,),
Container(width: 8.0, height: 42.0,), //Height to adjust card height
Text(
album.tracks.length.toString(),
style: TextStyle(fontSize: 16.0),
)
],
),
Row(
children: <Widget>[
Icon(Icons.timelapse, size: 32.0,),
Container(width: 8.0,),
Text(
album.durationString,
style: TextStyle(fontSize: 16.0),
)
],
),
Row(
children: <Widget>[
Icon(Icons.people, size: 32.0,),
Container(width: 8.0,),
Text(
album.fansString,
style: TextStyle(fontSize: 16.0),
)
],
),
],
),
FreezerDivider(),
//Options (offline, download...)
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
FlatButton(
child: Row(
children: <Widget>[
Icon(Icons.favorite, size: 32),
Container(width: 4,),
Text('Library'.i18n)
],
),
onPressed: () async {
),
FreezerDivider(),
//Options (offline, download...)
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
FlatButton(
child: Row(
children: <Widget>[
Icon((album.library??false)? Icons.favorite : Icons.favorite_border, size: 32),
Container(width: 4,),
Text('Library'.i18n)
],
),
onPressed: () async {
//Add to library
if (!album.library) {
await deezerAPI.addFavoriteAlbum(album.id);
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,
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),
FlatButton(
child: Row(
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();
},
)
],
),
onPressed: () async {
if (await downloadManager.addOfflineAlbum(album, private: false, context: context) != false)
MenuSheet(context).showDownloadStartedToast();
},
)
],
),
FreezerDivider(),
...List.generate(cdCount, (cdi) {
List<Track> tracks = album.tracks.where((t) => (t.diskNumber??1) == cdi + 1).toList();
return Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0),
child: Text(
'Disk'.i18n.toUpperCase() + ' ${cdi + 1}',
style: TextStyle(
),
FreezerDivider(),
...List.generate(cdCount, (cdi) {
List<Track> tracks = album.tracks.where((t) => (t.diskNumber??1) == cdi + 1).toList();
return Column(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0),
child: Text(
'Disk'.i18n.toUpperCase() + ' ${cdi + 1}',
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w300
),
),
),
...List.generate(tracks.length, (i) => TrackTile(
),
...List.generate(tracks.length, (i) => TrackTile(
tracks[i],
onTap: () {
playerHelper.playFromAlbum(album, tracks[i].id);
@ -206,14 +236,12 @@ class AlbumDetails extends StatelessWidget {
MenuSheet m = MenuSheet(context);
m.defaultTrackMenu(tracks[i]);
}
))
],
);
}),
],
);
},
)
))
],
);
}),
],
)
);
}
}
@ -812,200 +840,220 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
body: DraggableScrollbar.rrect(
controller: _scrollController,
children: <Widget>[
Container(height: 4.0,),
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: Row(
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)
backgroundColor: Theme.of(context).primaryColor,
child: ListView(
controller: _scrollController,
children: <Widget>[
Container(height: 4.0,),
Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
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)
ErrorScreen()
],
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),
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()
],
),
)
);
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:freezer/main.dart';
import 'package:wakelock/wakelock.dart';
import 'package:flutter/material.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/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/share.dart';
import 'package:url_launcher/url_launcher.dart';
import '../api/definitions.dart';
import 'cached_image.dart';
class MenuSheet {
BuildContext context;
Function navigateCallback;
MenuSheet(this.context);
MenuSheet(this.context, {this.navigateCallback});
//===================
// DEFAULT
@ -273,9 +275,13 @@ class MenuSheet {
leading: Icon(Icons.recent_actors),
onTap: () {
_close();
Navigator.of(context).push(
navigatorKey.currentState.push(
MaterialPageRoute(builder: (context) => ArtistDetails(a))
);
if (this.navigateCallback != null) {
this.navigateCallback();
}
},
);
@ -288,9 +294,13 @@ class MenuSheet {
leading: Icon(Icons.album),
onTap: () {
_close();
Navigator.of(context).push(
navigatorKey.currentState.push(
MaterialPageRoute(builder: (context) => AlbumDetails(a))
);
if (this.navigateCallback != null) {
this.navigateCallback();
}
},
);
@ -303,12 +313,22 @@ class MenuSheet {
},
);
Widget offlineTrack(Track track) => ListTile(
title: Text('Offline'.i18n),
leading: Icon(Icons.offline_pin),
onTap: () async {
await downloadManager.addOfflineTrack(track, private: true, context: context);
_close();
Widget offlineTrack(Track track) => FutureBuilder(
future: downloadManager.checkOffline(track: track),
builder: (context, snapshot) {
bool isOffline = snapshot.data??(track.offline??false);
return ListTile(
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();
},
);
},
);

View File

@ -51,16 +51,16 @@ class _PlayerScreenState extends State<PlayerScreen> {
//Update notification
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: palette.dominantColor.color.withOpacity(0.5)
statusBarColor: palette.dominantColor.color.withOpacity(0.7)
));
setState(() => _bgGradient = LinearGradient(
begin: Alignment.topCenter,
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: [
0.0,
0.4
0.6
]
));
}
@ -408,7 +408,9 @@ class PlayerMenuButton extends StatelessWidget {
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(46)),
onPressed: () {
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)
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
else

View File

@ -1362,7 +1362,7 @@ class _CreditsScreenState extends State<CreditsScreen> {
subtitle: Text('Official Discord server'.i18n),
leading: Icon(FontAwesome5.discord, color: Color(0xff7289da), size: 36.0),
onTap: () {
launch('https://discord.gg/7ap654Tp3z');
launch('https://discord.gg/qwJpa3r4dQ');
},
),
ListTile(
@ -1373,6 +1373,14 @@ class _CreditsScreenState extends State<CreditsScreen> {
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(),
ListTile(
title: Text('exttex'),

View File

@ -517,21 +517,21 @@ packages:
name: just_audio
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.6"
version: "0.6.1"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
version: "2.0.0"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1"
version: "0.2.0"
language_pickers:
dependency: "direct main"
description:

View File

@ -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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.6.7+1
version: 0.6.8+1
environment:
sdk: ">=2.8.0 <3.0.0"
@ -79,7 +79,7 @@ dependencies:
audio_session: ^0.0.9
audio_service:
path: ./audio_service
just_audio: ^0.5.6
just_audio: 0.6.1
# path: ./just_audio
# cupertino_icons: ^0.1.3