pre-null safety migration code

This commit is contained in:
pato05 2021-08-30 14:51:51 +02:00
parent 34fc597bbb
commit 8d53162099
17 changed files with 1156 additions and 834 deletions

6
.gitmodules vendored
View file

@ -1,6 +0,0 @@
[submodule "audio_service"]
path = audio_service
url = https://git.freezer.life/exttex/audio_service.git
[submodule "just_audio"]
path = just_audio
url = https://git.freezer.life/exttex/just_audio

View file

@ -0,0 +1,138 @@
package f.f.freezer;
// copied from https://gist.github.com/asifmujteba/d89ba9074bc941de1eaa#file-asfurihelper
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
public class FileUtils {
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}

View file

@ -15,6 +15,7 @@ import android.os.IBinder;
import android.os.Message; import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.util.Log; import android.util.Log;
import androidx.core.view.WindowCompat; import androidx.core.view.WindowCompat;
@ -51,6 +52,10 @@ import static f.f.freezer.Deezer.bytesToHex;
public class MainActivity extends FlutterActivity { public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "f.f.freezer/native"; private static final String CHANNEL = "f.f.freezer/native";
private static final String EVENT_CHANNEL = "f.f.freezer/downloads"; private static final String EVENT_CHANNEL = "f.f.freezer/downloads";
private static final int REQUEST_CODE_DIRECTORY_CHOOSER = MainActivity.class.hashCode() + 60;
private MethodChannel.Result pendingResult;
EventChannel.EventSink eventSink; EventChannel.EventSink eventSink;
boolean serviceBound = false; boolean serviceBound = false;
@ -224,7 +229,16 @@ public class MainActivity extends FlutterActivity {
System.exit(0); System.exit(0);
} }
result.error("0", "Not implemented!", "Not implemented!"); // Open the directory picker native dialog
if (call.method.equals("getDirectory")) {
pendingResult = result;
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivityForResult(Intent.createChooser(intent, call.argument("title")), REQUEST_CODE_DIRECTORY_CHOOSER);
return;
}
result.notImplemented();
}))); })));
//Event channel (for download updates) //Event channel (for download updates)
@ -242,6 +256,29 @@ public class MainActivity extends FlutterActivity {
})); }));
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_DIRECTORY_CHOOSER) {
switch (resultCode) {
case RESULT_OK:
Uri uri = data.getData();
Log.v("MainActivity", "trying to get path for uri "+uri.toString());
Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
String path = FileUtils.getPath(getContext(), documentUri);
Log.v("MainActivity", "got path "+path);
pendingResult.success(path);
break;
case RESULT_CANCELED:
pendingResult.success(null);
break;
default:
pendingResult.error("0", "Unknown result code", "code: "+resultCode+", uri data: "+data.getData());
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
//Start/Bind/Reconnect to download service //Start/Bind/Reconnect to download service
private void connectService() { private void connectService() {
if (serviceBound) if (serviceBound)

View file

@ -10,7 +10,6 @@ import 'dart:async';
DeezerAPI deezerAPI = DeezerAPI(); DeezerAPI deezerAPI = DeezerAPI();
class DeezerAPI { class DeezerAPI {
DeezerAPI({this.arl}); DeezerAPI({this.arl});
String arl; String arl;
@ -24,18 +23,22 @@ class DeezerAPI {
//Get headers //Get headers
Map<String, String> get headers => { Map<String, String> get headers => {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", "User-Agent":
"Content-Language": '${settings.deezerLanguage??"en"}-${settings.deezerCountry??'US'}', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Content-Language":
'${settings.deezerLanguage ?? "en"}-${settings.deezerCountry ?? 'US'}',
"Cache-Control": "max-age=0", "Cache-Control": "max-age=0",
"Accept": "*/*", "Accept": "*/*",
"Accept-Charset": "utf-8,ISO-8859-1;q=0.7,*;q=0.3", "Accept-Charset": "utf-8,ISO-8859-1;q=0.7,*;q=0.3",
"Accept-Language": "${settings.deezerLanguage??"en"}-${settings.deezerCountry??'US'},${settings.deezerLanguage??"en"};q=0.9,en-US;q=0.8,en;q=0.7", "Accept-Language":
"${settings.deezerLanguage ?? "en"}-${settings.deezerCountry ?? 'US'},${settings.deezerLanguage ?? "en"};q=0.9,en-US;q=0.8,en;q=0.7",
"Connection": "keep-alive", "Connection": "keep-alive",
"Cookie": "arl=$arl" + ((sid == null) ? '' : '; sid=$sid') "Cookie": "arl=$arl" + ((sid == null) ? '' : '; sid=$sid')
}; };
//Call private API //Call private API
Future<Map<dynamic, dynamic>> callApi(String method, {Map<dynamic, dynamic> params, String gatewayInput}) async { Future<Map<dynamic, dynamic>> callApi(String method,
{Map<dynamic, dynamic> params, String gatewayInput}) async {
//Generate URL //Generate URL
Uri uri = Uri.https('www.deezer.com', '/ajax/gw-light.php', { Uri uri = Uri.https('www.deezer.com', '/ajax/gw-light.php', {
'api_version': '1.0', 'api_version': '1.0',
@ -43,11 +46,11 @@ class DeezerAPI {
'input': '3', 'input': '3',
'method': method, 'method': method,
//Used for homepage //Used for homepage
if (gatewayInput != null) if (gatewayInput != null) 'gateway_input': gatewayInput
'gateway_input': gatewayInput
}); });
//Post //Post
http.Response res = await http.post(uri, headers: headers, body: jsonEncode(params)); http.Response res =
await http.post(uri, headers: headers, body: jsonEncode(params));
dynamic body = jsonDecode(res.body); dynamic body = jsonDecode(res.body);
//Grab SID //Grab SID
if (method == 'deezer.getUserData') { if (method == 'deezer.getUserData') {
@ -58,14 +61,17 @@ class DeezerAPI {
} }
} }
// In case of error "Invalid CSRF token" retrieve new one and retry the same call // In case of error "Invalid CSRF token" retrieve new one and retry the same call
if (body['error'].isNotEmpty && body['error'].containsKey('VALID_TOKEN_REQUIRED') && await rawAuthorize()) { if (body['error'].isNotEmpty &&
body['error'].containsKey('VALID_TOKEN_REQUIRED') &&
await rawAuthorize()) {
return callApi(method, params: params, gatewayInput: gatewayInput); return callApi(method, params: params, gatewayInput: gatewayInput);
} }
return body; return body;
} }
Future<Map<dynamic, dynamic>> callPublicApi(String path) async { Future<Map<dynamic, dynamic>> callPublicApi(String path) async {
http.Response res = await http.get('https://api.deezer.com/' + path); Uri uri = Uri(scheme: 'https', host: 'api.deezer.com', path: '/' + path);
http.Response res = await http.get(uri);
return jsonDecode(res.body); return jsonDecode(res.body);
} }
@ -83,12 +89,14 @@ class DeezerAPI {
Digest digest = md5.convert(utf8.encode(password)); Digest digest = md5.convert(utf8.encode(password));
String md5password = '$digest'; String md5password = '$digest';
//Get access token //Get access token
String url = "https://tv.deezer.com/smarttv/8caf9315c1740316053348a24d25afc7/user_auth.php?login=$email&password=$md5password&device=panasonic&output=json"; String url =
http.Response response = await http.get(url); "https://tv.deezer.com/smarttv/8caf9315c1740316053348a24d25afc7/user_auth.php?login=$email&password=$md5password&device=panasonic&output=json";
http.Response response = await http.get(Uri.parse(url));
String accessToken = jsonDecode(response.body)["access_token"]; String accessToken = jsonDecode(response.body)["access_token"];
//Get SID //Get SID
url = "https://api.deezer.com/platform/generic/track/42069"; url = "https://api.deezer.com/platform/generic/track/42069";
response = await http.get(url, headers: {"Authorization": "Bearer $accessToken"}); response = await http
.get(Uri.parse(url), headers: {"Authorization": "Bearer $accessToken"});
String sid; String sid;
for (String cookieHeader in response.headers['set-cookie'].split(';')) { for (String cookieHeader in response.headers['set-cookie'].split(';')) {
if (cookieHeader.startsWith('sid=')) { if (cookieHeader.startsWith('sid=')) {
@ -97,12 +105,12 @@ class DeezerAPI {
} }
if (sid == null) return null; if (sid == null) return null;
//Get ARL //Get ARL
url = "https://deezer.com/ajax/gw-light.php?api_version=1.0&api_token=null&input=3&method=user.getArl"; url =
response = await http.get(url, headers: {"Cookie": "sid=$sid"}); "https://deezer.com/ajax/gw-light.php?api_version=1.0&api_token=null&input=3&method=user.getArl";
response = await http.get(Uri.parse(url), headers: {"Cookie": "sid=$sid"});
return jsonDecode(response.body)["results"]; return jsonDecode(response.body)["results"];
} }
//Authorize, bool = success //Authorize, bool = success
Future<bool> rawAuthorize({Function onError}) async { Future<bool> rawAuthorize({Function onError}) async {
try { try {
@ -117,8 +125,7 @@ class DeezerAPI {
return true; return true;
} }
} catch (e) { } catch (e) {
if (onError != null) if (onError != null) onError(e);
onError(e);
print('Login Error (D): ' + e.toString()); print('Login Error (D): ' + e.toString());
return false; return false;
} }
@ -130,8 +137,10 @@ class DeezerAPI {
//https://www.deezer.com/NOTHING_OR_COUNTRY/TYPE/ID //https://www.deezer.com/NOTHING_OR_COUNTRY/TYPE/ID
if (uri.host == 'www.deezer.com' || uri.host == 'deezer.com') { if (uri.host == 'www.deezer.com' || uri.host == 'deezer.com') {
if (uri.pathSegments.length < 2) return null; if (uri.pathSegments.length < 2) return null;
DeezerLinkType type = DeezerLinkResponse.typeFromString(uri.pathSegments[uri.pathSegments.length-2]); DeezerLinkType type = DeezerLinkResponse.typeFromString(
return DeezerLinkResponse(type: type, id: uri.pathSegments[uri.pathSegments.length-1]); uri.pathSegments[uri.pathSegments.length - 2]);
return DeezerLinkResponse(
type: type, id: uri.pathSegments[uri.pathSegments.length - 1]);
} }
//Share URL //Share URL
if (uri.host == 'deezer.page.link' || uri.host == 'www.deezer.page.link') { if (uri.host == 'deezer.page.link' || uri.host == 'www.deezer.page.link') {
@ -163,7 +172,8 @@ class DeezerAPI {
//Check if Deezer available in country //Check if Deezer available in country
static Future<bool> chceckAvailability() async { static Future<bool> chceckAvailability() async {
try { try {
http.Response res = await http.get("https://api.deezer.com/infos"); http.Response res =
await http.get(Uri.parse('https://api.deezer.com/infos'));
return jsonDecode(res.body)["open"]; return jsonDecode(res.body)["open"];
} catch (e) { } catch (e) {
return null; return null;
@ -172,16 +182,15 @@ class DeezerAPI {
//Search //Search
Future<SearchResults> search(String query) async { Future<SearchResults> search(String query) async {
Map<dynamic, dynamic> data = await callApi('deezer.pageSearch', params: { Map<dynamic, dynamic> data = await callApi('deezer.pageSearch',
'nb': 128, params: {'nb': 128, 'query': query, 'start': 0});
'query': query,
'start': 0
});
return SearchResults.fromPrivateJson(data['results']); return SearchResults.fromPrivateJson(data['results']);
} }
Future<Track> track(String id) async { Future<Track> track(String id) async {
Map<dynamic, dynamic> data = await callApi('song.getListData', params: {'sng_ids': [id]}); Map<dynamic, dynamic> data = await callApi('song.getListData', params: {
'sng_ids': [id]
});
return Track.fromPrivateJson(data['results']['data'][0]); return Track.fromPrivateJson(data['results']['data'][0]);
} }
@ -190,47 +199,50 @@ class DeezerAPI {
Map<dynamic, dynamic> data = await callApi('deezer.pageAlbum', params: { Map<dynamic, dynamic> data = await callApi('deezer.pageAlbum', params: {
'alb_id': id, 'alb_id': id,
'header': true, 'header': true,
'lang': settings.deezerLanguage??'en' 'lang': settings.deezerLanguage ?? 'en'
}); });
return Album.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); return Album.fromPrivateJson(data['results']['DATA'],
songsJson: data['results']['SONGS']);
} }
//Get artist details //Get artist details
Future<Artist> artist(String id) async { Future<Artist> artist(String id) async {
Map<dynamic, dynamic> data = await callApi('deezer.pageArtist', params: { Map<dynamic, dynamic> data = await callApi('deezer.pageArtist', params: {
'art_id': id, 'art_id': id,
'lang': settings.deezerLanguage??'en', 'lang': settings.deezerLanguage ?? 'en',
}); });
return Artist.fromPrivateJson( return Artist.fromPrivateJson(data['results']['DATA'],
data['results']['DATA'],
topJson: data['results']['TOP'], topJson: data['results']['TOP'],
albumsJson: data['results']['ALBUMS'], albumsJson: data['results']['ALBUMS'],
highlight: data['results']['HIGHLIGHT'] highlight: data['results']['HIGHLIGHT']);
);
} }
//Get playlist tracks at offset //Get playlist tracks at offset
Future<List<Track>> playlistTracksPage(String id, int start, {int nb = 50}) async { Future<List<Track>> playlistTracksPage(String id, int start,
{int nb = 50}) async {
Map data = await callApi('deezer.pagePlaylist', params: { Map data = await callApi('deezer.pagePlaylist', params: {
'playlist_id': id, 'playlist_id': id,
'lang': settings.deezerLanguage??'en', 'lang': settings.deezerLanguage ?? 'en',
'nb': nb, 'nb': nb,
'tags': true, 'tags': true,
'start': start 'start': start
}); });
return data['results']['SONGS']['data'].map<Track>((json) => Track.fromPrivateJson(json)).toList(); return data['results']['SONGS']['data']
.map<Track>((json) => Track.fromPrivateJson(json))
.toList();
} }
//Get playlist details //Get playlist details
Future<Playlist> playlist(String id, {int nb = 100}) async { Future<Playlist> playlist(String id, {int nb = 100}) async {
Map<dynamic, dynamic> data = await callApi('deezer.pagePlaylist', params: { Map<dynamic, dynamic> data = await callApi('deezer.pagePlaylist', params: {
'playlist_id': id, 'playlist_id': id,
'lang': settings.deezerLanguage??'en', 'lang': settings.deezerLanguage ?? 'en',
'nb': nb, 'nb': nb,
'tags': true, 'tags': true,
'start': 0 'start': 0
}); });
return Playlist.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); return Playlist.fromPrivateJson(data['results']['DATA'],
songsJson: data['results']['SONGS']);
} }
//Get playlist with all tracks //Get playlist with all tracks
@ -264,11 +276,14 @@ class DeezerAPI {
} }
//Add tracks to playlist //Add tracks to playlist
Future addToPlaylist(String trackId, String playlistId, {int offset = -1}) async { Future addToPlaylist(String trackId, String playlistId,
{int offset = -1}) async {
await callApi('playlist.addSongs', params: { await callApi('playlist.addSongs', params: {
'offset': offset, 'offset': offset,
'playlist_id': playlistId, 'playlist_id': playlistId,
'songs': [[trackId, 0]] 'songs': [
[trackId, 0]
]
}); });
} }
@ -276,83 +291,89 @@ class DeezerAPI {
Future removeFromPlaylist(String trackId, String playlistId) async { Future removeFromPlaylist(String trackId, String playlistId) async {
await callApi('playlist.deleteSongs', params: { await callApi('playlist.deleteSongs', params: {
'playlist_id': playlistId, 'playlist_id': playlistId,
'songs': [[trackId, 0]] 'songs': [
[trackId, 0]
]
}); });
} }
//Get users playlists //Get users playlists
Future<List<Playlist>> getPlaylists() async { Future<List<Playlist>> getPlaylists() async {
Map data = await callApi('deezer.pageProfile', params: { Map data = await callApi('deezer.pageProfile',
'nb': 100, params: {'nb': 100, 'tab': 'playlists', 'user_id': this.userId});
'tab': 'playlists', return data['results']['TAB']['playlists']['data']
'user_id': this.userId .map<Playlist>((json) => Playlist.fromPrivateJson(json, library: true))
}); .toList();
return data['results']['TAB']['playlists']['data'].map<Playlist>((json) => Playlist.fromPrivateJson(json, library: true)).toList();
} }
//Get favorite albums //Get favorite albums
Future<List<Album>> getAlbums() async { Future<List<Album>> getAlbums() async {
Map data = await callApi('deezer.pageProfile', params: { Map data = await callApi('deezer.pageProfile',
'nb': 50, params: {'nb': 50, 'tab': 'albums', 'user_id': this.userId});
'tab': 'albums',
'user_id': this.userId
});
List albumList = data['results']['TAB']['albums']['data']; List albumList = data['results']['TAB']['albums']['data'];
List<Album> albums = albumList.map<Album>((json) => Album.fromPrivateJson(json, library: true)).toList(); List<Album> albums = albumList
.map<Album>((json) => Album.fromPrivateJson(json, library: true))
.toList();
return albums; return albums;
} }
//Remove album from library //Remove album from library
Future removeAlbum(String id) async { Future removeAlbum(String id) async {
await callApi('album.deleteFavorite', params: { await callApi('album.deleteFavorite', params: {'ALB_ID': id});
'ALB_ID': id
});
} }
//Remove track from favorites //Remove track from favorites
Future removeFavorite(String id) async { Future removeFavorite(String id) async {
await callApi('favorite_song.remove', params: { await callApi('favorite_song.remove', params: {'SNG_ID': id});
'SNG_ID': id
});
} }
//Get favorite artists //Get favorite artists
Future<List<Artist>> getArtists() async { Future<List<Artist>> getArtists() async {
Map data = await callApi('deezer.pageProfile', params: { Map data = await callApi('deezer.pageProfile',
'nb': 40, params: {'nb': 40, 'tab': 'artists', 'user_id': this.userId});
'tab': 'artists', return data['results']['TAB']['artists']['data']
'user_id': this.userId .map<Artist>((json) => Artist.fromPrivateJson(json, library: true))
}); .toList();
return data['results']['TAB']['artists']['data'].map<Artist>((json) => Artist.fromPrivateJson(json, library: true)).toList();
} }
//Get lyrics by track id //Get lyrics by track id
Future<Lyrics> lyrics(String trackId) async { Future<Lyrics> lyrics(String trackId) async {
Map data = await callApi('song.getLyrics', params: { Map data = await callApi('song.getLyrics', params: {'sng_id': trackId});
'sng_id': trackId if (data['error'] != null && data['error'].length > 0)
}); return Lyrics.error();
if (data['error'] != null && data['error'].length > 0) return Lyrics.error();
return Lyrics.fromPrivateJson(data['results']); return Lyrics.fromPrivateJson(data['results']);
} }
Future<SmartTrackList> smartTrackList(String id) async { Future<SmartTrackList> smartTrackList(String id) async {
Map data = await callApi('deezer.pageSmartTracklist', params: { Map data = await callApi('deezer.pageSmartTracklist',
'smarttracklist_id': id params: {'smarttracklist_id': id});
}); return SmartTrackList.fromPrivateJson(data['results']['DATA'],
return SmartTrackList.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); songsJson: data['results']['SONGS']);
} }
Future<List<Track>> flow() async { Future<List<Track>> flow() async {
Map data = await callApi('radio.getUserRadio', params: { Map data = await callApi('radio.getUserRadio', params: {'user_id': userId});
'user_id': userId return data['results']['data']
}); .map<Track>((json) => Track.fromPrivateJson(json))
return data['results']['data'].map<Track>((json) => Track.fromPrivateJson(json)).toList(); .toList();
} }
//Get homepage/music library from deezer //Get homepage/music library from deezer
Future<HomePage> homePage() async { Future<HomePage> homePage() async {
List grid = ['album', 'artist', 'channel', 'flow', 'playlist', 'radio', 'show', 'smarttracklist', 'track', 'user']; List grid = [
Map data = await callApi('page.get', gatewayInput: jsonEncode({ 'album',
'artist',
'channel',
'flow',
'playlist',
'radio',
'show',
'smarttracklist',
'track',
'user'
];
Map data = await callApi('page.get',
gatewayInput: jsonEncode({
"PAGE": "home", "PAGE": "home",
"VERSION": "2.3", "VERSION": "2.3",
"SUPPORT": { "SUPPORT": {
@ -370,7 +391,7 @@ class DeezerAPI {
"large-card": ["album", "playlist", "show", "video-link"], "large-card": ["album", "playlist", "show", "video-link"],
"ads": [] //Nope "ads": [] //Nope
}, },
"LANG": settings.deezerLanguage??'en', "LANG": settings.deezerLanguage ?? 'en',
"OPTIONS": [] "OPTIONS": []
})); }));
return HomePage.fromPrivateJson(data['results']); return HomePage.fromPrivateJson(data['results']);
@ -390,8 +411,20 @@ class DeezerAPI {
} }
Future<HomePage> getChannel(String target) async { Future<HomePage> getChannel(String target) async {
List grid = ['album', 'artist', 'channel', 'flow', 'playlist', 'radio', 'show', 'smarttracklist', 'track', 'user']; List grid = [
Map data = await callApi('page.get', gatewayInput: jsonEncode({ 'album',
'artist',
'channel',
'flow',
'playlist',
'radio',
'show',
'smarttracklist',
'track',
'user'
];
Map data = await callApi('page.get',
gatewayInput: jsonEncode({
'PAGE': target, 'PAGE': target,
"VERSION": "2.3", "VERSION": "2.3",
"SUPPORT": { "SUPPORT": {
@ -409,7 +442,7 @@ class DeezerAPI {
"large-card": ["album", "playlist", "show", "video-link"], "large-card": ["album", "playlist", "show", "video-link"],
"ads": [] //Nope "ads": [] //Nope
}, },
"LANG": settings.deezerLanguage??'en', "LANG": settings.deezerLanguage ?? 'en',
"OPTIONS": [] "OPTIONS": []
})); }));
return HomePage.fromPrivateJson(data['results']); return HomePage.fromPrivateJson(data['results']);
@ -417,30 +450,33 @@ class DeezerAPI {
//Add playlist to library //Add playlist to library
Future addPlaylist(String id) async { Future addPlaylist(String id) async {
await callApi('playlist.addFavorite', params: { await callApi('playlist.addFavorite',
'parent_playlist_id': int.parse(id) params: {'parent_playlist_id': int.parse(id)});
});
} }
//Remove playlist from library //Remove playlist from library
Future removePlaylist(String id) async { Future removePlaylist(String id) async {
await callApi('playlist.deleteFavorite', params: { await callApi('playlist.deleteFavorite',
'playlist_id': int.parse(id) params: {'playlist_id': int.parse(id)});
});
} }
//Delete playlist //Delete playlist
Future deletePlaylist(String id) async { Future deletePlaylist(String id) async {
await callApi('playlist.delete', params: { await callApi('playlist.delete', params: {'playlist_id': id});
'playlist_id': id
});
} }
//Create playlist //Create playlist
//Status 1 - private, 2 - collaborative //Status 1 - private, 2 - collaborative
Future<String> createPlaylist(String title, {String description = "", int status = 1, List<String> trackIds = const []}) async { Future<String> createPlaylist(String title,
{String description = "",
int status = 1,
List<String> trackIds = const []}) async {
Map data = await callApi('playlist.create', params: { Map data = await callApi('playlist.create', params: {
'title': title, 'title': title,
'description': description, 'description': description,
'songs': trackIds.map<List>((id) => [int.parse(id), trackIds.indexOf(id)]).toList(), 'songs': trackIds
.map<List>((id) => [int.parse(id), trackIds.indexOf(id)])
.toList(),
'status': status 'status': status
}); });
//Return playlistId //Return playlistId
@ -448,7 +484,8 @@ class DeezerAPI {
} }
//Get part of discography //Get part of discography
Future<List<Album>> discographyPage(String artistId, {int start = 0, int nb = 50}) async { Future<List<Album>> discographyPage(String artistId,
{int start = 0, int nb = 50}) async {
Map data = await callApi('album.getDiscography', params: { Map data = await callApi('album.getDiscography', params: {
'art_id': int.parse(artistId), 'art_id': int.parse(artistId),
'discography_mode': 'all', 'discography_mode': 'all',
@ -457,26 +494,29 @@ class DeezerAPI {
'nb_songs': 30 'nb_songs': 30
}); });
return data['results']['data'].map<Album>((a) => Album.fromPrivateJson(a)).toList(); return data['results']['data']
.map<Album>((a) => Album.fromPrivateJson(a))
.toList();
} }
Future<List> searchSuggestions(String query) async { Future<List> searchSuggestions(String query) async {
Map data = await callApi('search_getSuggestedQueries', params: { Map data =
'QUERY': query await callApi('search_getSuggestedQueries', params: {'QUERY': query});
});
return data['results']['SUGGESTION'].map((s) => s['QUERY']).toList(); return data['results']['SUGGESTION'].map((s) => s['QUERY']).toList();
} }
//Get smart radio for artist id //Get smart radio for artist id
Future<List<Track>> smartRadio(String artistId) async { Future<List<Track>> smartRadio(String artistId) async {
Map data = await callApi('smart.getSmartRadio', params: { Map data = await callApi('smart.getSmartRadio',
'art_id': int.parse(artistId) params: {'art_id': int.parse(artistId)});
}); return data['results']['data']
return data['results']['data'].map<Track>((t) => Track.fromPrivateJson(t)).toList(); .map<Track>((t) => Track.fromPrivateJson(t))
.toList();
} }
//Update playlist metadata, status = see createPlaylist //Update playlist metadata, status = see createPlaylist
Future updatePlaylist(String id, String title, String description, {int status = 1}) async { Future updatePlaylist(String id, String title, String description,
{int status = 1}) async {
await callApi('playlist.update', params: { await callApi('playlist.update', params: {
'description': description, 'description': description,
'title': title, 'title': title,
@ -487,12 +527,12 @@ class DeezerAPI {
} }
//Get shuffled library //Get shuffled library
Future<List<Track>> libraryShuffle({int start=0}) async { Future<List<Track>> libraryShuffle({int start = 0}) async {
Map data = await callApi('tracklist.getShuffledCollection', params: { Map data = await callApi('tracklist.getShuffledCollection',
'nb': 50, params: {'nb': 50, 'start': start});
'start': start return data['results']['data']
}); .map<Track>((t) => Track.fromPrivateJson(t))
return data['results']['data'].map<Track>((t) => Track.fromPrivateJson(t)).toList(); .toList();
} }
//Get similar tracks for track with id [trackId] //Get similar tracks for track with id [trackId]
@ -500,7 +540,9 @@ class DeezerAPI {
Map data = await callApi('song.getContextualTrackMix', params: { Map data = await callApi('song.getContextualTrackMix', params: {
'sng_ids': [trackId] 'sng_ids': [trackId]
}); });
return data['results']['data'].map<Track>((t) => Track.fromPrivateJson(t)).toList(); return data['results']['data']
.map<Track>((t) => Track.fromPrivateJson(t))
.toList();
} }
Future<List<ShowEpisode>> allShowEpisodes(String showId) async { Future<List<ShowEpisode>> allShowEpisodes(String showId) async {
@ -512,6 +554,8 @@ class DeezerAPI {
'start': 0, 'start': 0,
'user_id': int.parse(deezerAPI.userId) 'user_id': int.parse(deezerAPI.userId)
}); });
return data['results']['EPISODES']['data'].map<ShowEpisode>((e) => ShowEpisode.fromPrivateJson(e)).toList(); return data['results']['EPISODES']['data']
.map<ShowEpisode>((e) => ShowEpisode.fromPrivateJson(e))
.toList();
} }
} }

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:freezer/api/cache.dart'; import 'package:freezer/api/cache.dart';
@ -14,14 +16,14 @@ part 'definitions.g.dart';
@JsonSerializable() @JsonSerializable()
class Track { class Track {
String id; String/*!*//*!*/ id;
String title; String/*!*/ title;
Album album; Album/*!*/ album;
List<Artist> artists; List<Artist>/*!*/ artists;
Duration duration; Duration/*!*/ duration;
ImageDetails albumArt; ImageDetails/*!*/ albumArt;
int trackNumber; int trackNumber;
bool offline; bool/*!*/ offline;
Lyrics lyrics; Lyrics lyrics;
bool favorite; bool favorite;
int diskNumber; int diskNumber;
@ -59,7 +61,7 @@ class Track {
displayTitle: this.title, displayTitle: this.title,
displaySubtitle: this.artistString, displaySubtitle: this.artistString,
displayDescription: this.album.title, displayDescription: this.album.title,
artUri: this.albumArt.full, artUri: Uri.parse(this.albumArt.full),
duration: this.duration, duration: this.duration,
id: this.id, id: this.id,
extras: { extras: {
@ -95,8 +97,8 @@ class Track {
artists: artists, artists: artists,
album: album, album: album,
id: mi.id, id: mi.id,
albumArt: albumArt: ImageDetails(
ImageDetails(fullUrl: mi.artUri, thumbUrl: mi.extras['thumb']), fullUrl: mi.artUri.toString(), thumbUrl: mi.extras['thumb']),
duration: mi.duration, duration: mi.duration,
playbackDetails: playbackDetails, playbackDetails: playbackDetails,
lyrics: lyrics:
@ -470,6 +472,7 @@ class User {
Map<String, dynamic> toJson() => _$UserToJson(this); Map<String, dynamic> toJson() => _$UserToJson(this);
} }
// TODO: migrate to Uri instead of String
@JsonSerializable() @JsonSerializable()
class ImageDetails { class ImageDetails {
String fullUrl; String fullUrl;
@ -1001,7 +1004,7 @@ class ShowEpisode {
}, },
displayDescription: description, displayDescription: description,
duration: duration, duration: duration,
artUri: show.art.full, artUri: Uri.parse(show.art.full),
); );
} }
@ -1058,3 +1061,5 @@ extension Reorder<T> on List<T> {
oldIndex > newIndex ? newIndex : newIndex - 1, this.removeAt(oldIndex)); oldIndex > newIndex ? newIndex : newIndex - 1, this.removeAt(oldIndex));
} }
} }
double hypot(num c1, num c2) => sqrt(pow(c1.abs(), 2) + pow(c2.abs(), 2));

View file

@ -20,10 +20,10 @@ import 'dart:async';
DownloadManager downloadManager = DownloadManager(); DownloadManager downloadManager = DownloadManager();
class DownloadManager { class DownloadManager {
//Platform channels //Platform channels
static MethodChannel platform = MethodChannel('f.f.freezer/native'); static MethodChannel platform = const MethodChannel('f.f.freezer/native');
static EventChannel eventChannel = EventChannel('f.f.freezer/downloads'); static EventChannel eventChannel =
const EventChannel('f.f.freezer/downloads');
bool running = false; bool running = false;
int queueSize = 0; int queueSize = 0;
@ -53,9 +53,7 @@ class DownloadManager {
String dbPath = p.join((await getDatabasesPath()), 'offline2.db'); String dbPath = p.join((await getDatabasesPath()), 'offline2.db');
//Open db //Open db
db = await openDatabase( db = await openDatabase(dbPath, version: 1,
dbPath,
version: 1,
onCreate: (Database db, int version) async { onCreate: (Database db, int version) async {
Batch b = db.batch(); Batch b = db.batch();
//Create tables, if doesn't exit //Create tables, if doesn't exit
@ -68,11 +66,11 @@ class DownloadManager {
b.execute("""CREATE TABLE Playlists ( b.execute("""CREATE TABLE Playlists (
id TEXT PRIMARY KEY, title TEXT, tracks TEXT, image TEXT, duration INTEGER, userId TEXT, userName TEXT, fans INTEGER, library INTEGER, description TEXT)"""); id TEXT PRIMARY KEY, title TEXT, tracks TEXT, image TEXT, duration INTEGER, userId TEXT, userName TEXT, fans INTEGER, library INTEGER, description TEXT)""");
await b.commit(); await b.commit();
} });
);
//Create offline directory //Create offline directory
offlinePath = p.join((await getExternalStorageDirectory()).path, 'offline/'); offlinePath =
p.join((await getExternalStorageDirectory()).path, 'offline/');
await Directory(offlinePath).create(recursive: true); await Directory(offlinePath).create(recursive: true);
//Update settings //Update settings
@ -100,11 +98,16 @@ class DownloadManager {
//Insert track and metadata to DB //Insert track and metadata to DB
Future _addTrackToDB(Batch batch, Track track, bool overwriteTrack) async { Future _addTrackToDB(Batch batch, Track track, bool overwriteTrack) async {
batch.insert('Tracks', track.toSQL(off: true), conflictAlgorithm: overwriteTrack?ConflictAlgorithm.replace:ConflictAlgorithm.ignore); batch.insert('Tracks', track.toSQL(off: true),
batch.insert('Albums', track.album.toSQL(off: false), conflictAlgorithm: ConflictAlgorithm.ignore); conflictAlgorithm: overwriteTrack
? ConflictAlgorithm.replace
: ConflictAlgorithm.ignore);
batch.insert('Albums', track.album.toSQL(off: false),
conflictAlgorithm: ConflictAlgorithm.ignore);
//Artists //Artists
for (Artist a in track.artists) { for (Artist a in track.artists) {
batch.insert('Artists', a.toSQL(off: false), conflictAlgorithm: ConflictAlgorithm.ignore); batch.insert('Artists', a.toSQL(off: false),
conflictAlgorithm: ConflictAlgorithm.ignore);
} }
return batch; return batch;
} }
@ -122,10 +125,7 @@ class DownloadManager {
padding: EdgeInsets.fromLTRB(0, 12, 0, 2), padding: EdgeInsets.fromLTRB(0, 12, 0, 2),
child: Text( child: Text(
'Quality'.i18n, 'Quality'.i18n,
style: TextStyle( style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),
fontWeight: FontWeight.bold,
fontSize: 20.0
),
), ),
), ),
ListTile( ListTile(
@ -151,12 +151,12 @@ class DownloadManager {
) )
], ],
); );
} });
);
return quality; return quality;
} }
Future<bool> addOfflineTrack(Track track, {private = true, BuildContext context, isSingleton = false}) async { Future<bool> addOfflineTrack(Track track,
{private = true, BuildContext context, isSingleton = false}) async {
//Permission //Permission
if (!private && !(await checkPermission())) return false; if (!private && !(await checkPermission())) return false;
@ -168,8 +168,9 @@ class DownloadManager {
} }
//Fetch track if missing meta //Fetch track if missing meta
if (track.artists == null || track.artists.length == 0 || track.album == null) if (track.artists == null ||
track = await deezerAPI.track(track.id); track.artists.length == 0 ||
track.album == null) track = await deezerAPI.track(track.id);
//Add to DB //Add to DB
if (private) { if (private) {
@ -184,12 +185,16 @@ class DownloadManager {
//Get path //Get path
String path = _generatePath(track, private, isSingleton: isSingleton); String path = _generatePath(track, private, isSingleton: isSingleton);
await platform.invokeMethod('addDownloads', [await Download.jsonFromTrack(track, path, private: private, quality: quality)]); await platform.invokeMethod('addDownloads', [
await Download.jsonFromTrack(track, path,
private: private, quality: quality)
]);
await start(); await start();
return true; return true;
} }
Future addOfflineAlbum(Album album, {private = true, BuildContext context}) async { Future addOfflineAlbum(Album album,
{private = true, BuildContext context}) async {
//Permission //Permission
if (!private && !(await checkPermission())) return; if (!private && !(await checkPermission())) return;
@ -212,7 +217,8 @@ class DownloadManager {
DefaultCacheManager().getSingleFile(album.art.full); DefaultCacheManager().getSingleFile(album.art.full);
Batch b = db.batch(); Batch b = db.batch();
b.insert('Albums', album.toSQL(off: true), conflictAlgorithm: ConflictAlgorithm.replace); b.insert('Albums', album.toSQL(off: true),
conflictAlgorithm: ConflictAlgorithm.replace);
for (Track t in album.tracks) { for (Track t in album.tracks) {
b = await _addTrackToDB(b, t, false); b = await _addTrackToDB(b, t, false);
} }
@ -222,31 +228,37 @@ class DownloadManager {
//Create downloads //Create downloads
List<Map> out = []; List<Map> out = [];
for (Track t in album.tracks) { for (Track t in album.tracks) {
out.add(await Download.jsonFromTrack(t, _generatePath(t, private), private: private, quality: quality)); out.add(await Download.jsonFromTrack(t, _generatePath(t, private),
private: private, quality: quality));
} }
await platform.invokeMethod('addDownloads', out); await platform.invokeMethod('addDownloads', out);
await start(); await start();
} }
Future addOfflinePlaylist(Playlist playlist, {private = true, BuildContext context, AudioQuality quality}) async { Future addOfflinePlaylist(Playlist playlist,
{private = true, BuildContext context, AudioQuality quality}) async {
//Permission //Permission
if (!private && !(await checkPermission())) return; if (!private && !(await checkPermission())) return;
//Ask for quality //Ask for quality
if (!private && settings.downloadQuality == AudioQuality.ASK && quality == null) { if (!private &&
settings.downloadQuality == AudioQuality.ASK &&
quality == null) {
quality = await qualitySelect(context); quality = await qualitySelect(context);
if (quality == null) return false; if (quality == null) return false;
} }
//Get tracks if missing //Get tracks if missing
if (playlist.tracks == null || playlist.tracks.length < playlist.trackCount) { if (playlist.tracks == null ||
playlist.tracks.length < playlist.trackCount) {
playlist = await deezerAPI.fullPlaylist(playlist.id); playlist = await deezerAPI.fullPlaylist(playlist.id);
} }
//Add to DB //Add to DB
if (private) { if (private) {
Batch b = db.batch(); Batch b = db.batch();
b.insert('Playlists', playlist.toSQL(), conflictAlgorithm: ConflictAlgorithm.replace); b.insert('Playlists', playlist.toSQL(),
conflictAlgorithm: ConflictAlgorithm.replace);
for (Track t in playlist.tracks) { for (Track t in playlist.tracks) {
b = await _addTrackToDB(b, t, false); b = await _addTrackToDB(b, t, false);
//Cache art //Cache art
@ -258,30 +270,35 @@ class DownloadManager {
//Generate downloads //Generate downloads
List<Map> out = []; List<Map> out = [];
for (int i=0; i<playlist.tracks.length; i++) { for (int i = 0; i < playlist.tracks.length; i++) {
Track t = playlist.tracks[i]; Track t = playlist.tracks[i];
out.add(await Download.jsonFromTrack(t, _generatePath( out.add(await Download.jsonFromTrack(
t,
_generatePath(
t, t,
private, private,
playlistName: playlist.title, playlistName: playlist.title,
playlistTrackNumber: i, playlistTrackNumber: i,
), private: private, quality: quality)); ),
private: private,
quality: quality));
} }
await platform.invokeMethod('addDownloads', out); await platform.invokeMethod('addDownloads', out);
await start(); await start();
} }
//Get track and meta from offline DB //Get track and meta from offline DB
Future<Track> getOfflineTrack(String id, {Album album, List<Artist> artists}) async { Future<Track> getOfflineTrack(String id,
{Album album, List<Artist> artists}) async {
List tracks = await db.query('Tracks', where: 'id == ?', whereArgs: [id]); List tracks = await db.query('Tracks', where: 'id == ?', whereArgs: [id]);
if (tracks.length == 0) return null; if (tracks.length == 0) return null;
Track track = Track.fromSQL(tracks[0]); Track track = Track.fromSQL(tracks[0]);
//Get album //Get album
if (album == null) { if (album == null) {
List rawAlbums = await db.query('Albums', where: 'id == ?', whereArgs: [track.album.id]); List rawAlbums = await db
if (rawAlbums.length > 0) .query('Albums', where: 'id == ?', whereArgs: [track.album.id]);
track.album = Album.fromSQL(rawAlbums[0]); if (rawAlbums.length > 0) track.album = Album.fromSQL(rawAlbums[0]);
} else { } else {
track.album = album; track.album = album;
} }
@ -290,12 +307,11 @@ class DownloadManager {
if (artists == null) { if (artists == null) {
List<Artist> newArtists = []; List<Artist> newArtists = [];
for (Artist artist in track.artists) { for (Artist artist in track.artists) {
List rawArtist = await db.query('Artists', where: 'id == ?', whereArgs: [artist.id]); List rawArtist =
if (rawArtist.length > 0) await db.query('Artists', where: 'id == ?', whereArgs: [artist.id]);
newArtists.add(Artist.fromSQL(rawArtist[0])); if (rawArtist.length > 0) newArtists.add(Artist.fromSQL(rawArtist[0]));
} }
if (newArtists.length > 0) if (newArtists.length > 0) track.artists = newArtists;
track.artists = newArtists;
} else { } else {
track.artists = artists; track.artists = artists;
} }
@ -304,7 +320,8 @@ class DownloadManager {
//Get offline library tracks //Get offline library tracks
Future<List<Track>> getOfflineTracks() async { Future<List<Track>> getOfflineTracks() async {
List rawTracks = await db.query('Tracks', where: 'library == 1 AND offline == 1', columns: ['id']); List rawTracks = await db.query('Tracks',
where: 'library == 1 AND offline == 1', columns: ['id']);
List<Track> out = []; List<Track> out = [];
//Load track meta individually //Load track meta individually
for (Map rawTrack in rawTracks) { for (Map rawTrack in rawTracks) {
@ -315,7 +332,8 @@ class DownloadManager {
//Get all offline available tracks //Get all offline available tracks
Future<List<Track>> allOfflineTracks() async { Future<List<Track>> allOfflineTracks() async {
List rawTracks = await db.query('Tracks', where: 'offline == 1', columns: ['id']); List rawTracks =
await db.query('Tracks', where: 'offline == 1', columns: ['id']);
List<Track> out = []; List<Track> out = [];
//Load track meta individually //Load track meta individually
for (Map rawTrack in rawTracks) { for (Map rawTrack in rawTracks) {
@ -326,7 +344,8 @@ class DownloadManager {
//Get all offline albums //Get all offline albums
Future<List<Album>> getOfflineAlbums() async { Future<List<Album>> getOfflineAlbums() async {
List rawAlbums = await db.query('Albums', where: 'offline == 1', columns: ['id']); List rawAlbums =
await db.query('Albums', where: 'offline == 1', columns: ['id']);
List<Album> out = []; List<Album> out = [];
//Load each album //Load each album
for (Map rawAlbum in rawAlbums) { for (Map rawAlbum in rawAlbums) {
@ -337,20 +356,22 @@ class DownloadManager {
//Get offline album with meta //Get offline album with meta
Future<Album> getOfflineAlbum(String id) async { Future<Album> getOfflineAlbum(String id) async {
List rawAlbums = await db.query('Albums', where: 'id == ?', whereArgs: [id]); List rawAlbums =
await db.query('Albums', where: 'id == ?', whereArgs: [id]);
if (rawAlbums.length == 0) return null; if (rawAlbums.length == 0) return null;
Album album = Album.fromSQL(rawAlbums[0]); Album album = Album.fromSQL(rawAlbums[0]);
List<Track> tracks = []; List<Track> tracks = [];
//Load tracks //Load tracks
for (int i=0; i<album.tracks.length; i++) { for (int i = 0; i < album.tracks.length; i++) {
tracks.add(await getOfflineTrack(album.tracks[i].id, album: album)); tracks.add(await getOfflineTrack(album.tracks[i].id, album: album));
} }
album.tracks = tracks; album.tracks = tracks;
//Load artists //Load artists
List<Artist> artists = []; List<Artist> artists = [];
for (int i=0; i<album.artists.length; i++) { for (int i = 0; i < album.artists.length; i++) {
artists.add((await getOfflineArtist(album.artists[i].id))??album.artists[i]); artists.add(
(await getOfflineArtist(album.artists[i].id)) ?? album.artists[i]);
} }
album.artists = artists; album.artists = artists;
@ -359,7 +380,8 @@ class DownloadManager {
//Get offline artist METADATA, not tracks //Get offline artist METADATA, not tracks
Future<Artist> getOfflineArtist(String id) async { Future<Artist> getOfflineArtist(String id) async {
List rawArtists = await db.query("Artists", where: 'id == ?', whereArgs: [id]); List rawArtists =
await db.query("Artists", where: 'id == ?', whereArgs: [id]);
if (rawArtists.length == 0) return null; if (rawArtists.length == 0) return null;
return Artist.fromSQL(rawArtists[0]); return Artist.fromSQL(rawArtists[0]);
} }
@ -376,7 +398,8 @@ class DownloadManager {
//Get offline playlist //Get offline playlist
Future<Playlist> getPlaylist(String id) async { Future<Playlist> getPlaylist(String id) async {
List rawPlaylists = await db.query('Playlists', where: 'id == ?', whereArgs: [id]); List rawPlaylists =
await db.query('Playlists', where: 'id == ?', whereArgs: [id]);
if (rawPlaylists.length == 0) return null; if (rawPlaylists.length == 0) return null;
Playlist playlist = Playlist.fromSQL(rawPlaylists[0]); Playlist playlist = Playlist.fromSQL(rawPlaylists[0]);
//Load tracks //Load tracks
@ -391,16 +414,21 @@ class DownloadManager {
Future removeOfflineTracks(List<Track> tracks) async { Future removeOfflineTracks(List<Track> tracks) async {
for (Track t in tracks) { for (Track t in tracks) {
//Check if library //Check if library
List rawTrack = await db.query('Tracks', where: 'id == ?', whereArgs: [t.id], columns: ['favorite']); List rawTrack = await db.query('Tracks',
where: 'id == ?', whereArgs: [t.id], columns: ['favorite']);
if (rawTrack.length > 0) { if (rawTrack.length > 0) {
//Count occurrences in playlists and albums //Count occurrences in playlists and albums
List albums = await db.rawQuery('SELECT (id) FROM Albums WHERE tracks LIKE "%${t.id}%"'); List albums = await db
List playlists = await db.rawQuery('SELECT (id) FROM Playlists WHERE tracks LIKE "%${t.id}%"'); .rawQuery('SELECT (id) FROM Albums WHERE tracks LIKE "%${t.id}%"');
if (albums.length + playlists.length == 0 && rawTrack[0]['favorite'] == 0) { List playlists = await db.rawQuery(
'SELECT (id) FROM Playlists WHERE tracks LIKE "%${t.id}%"');
if (albums.length + playlists.length == 0 &&
rawTrack[0]['favorite'] == 0) {
//Safe to remove //Safe to remove
await db.delete('Tracks', where: 'id == ?', whereArgs: [t.id]); await db.delete('Tracks', where: 'id == ?', whereArgs: [t.id]);
} else { } else {
await db.update('Tracks', {'offline': 0}, where: 'id == ?', whereArgs: [t.id]); await db.update('Tracks', {'offline': 0},
where: 'id == ?', whereArgs: [t.id]);
} }
} }
@ -415,7 +443,8 @@ class DownloadManager {
Future removeOfflineAlbum(String id) async { Future removeOfflineAlbum(String id) async {
//Get album //Get album
List rawAlbums = await db.query('Albums', where: 'id == ?', whereArgs: [id]); List rawAlbums =
await db.query('Albums', where: 'id == ?', whereArgs: [id]);
if (rawAlbums.length == 0) return; if (rawAlbums.length == 0) return;
Album album = Album.fromSQL(rawAlbums[0]); Album album = Album.fromSQL(rawAlbums[0]);
//Remove album //Remove album
@ -426,7 +455,8 @@ class DownloadManager {
Future removeOfflinePlaylist(String id) async { Future removeOfflinePlaylist(String id) async {
//Fetch playlist //Fetch playlist
List rawPlaylists = await db.query('Playlists', where: 'id == ?', whereArgs: [id]); List rawPlaylists =
await db.query('Playlists', where: 'id == ?', whereArgs: [id]);
if (rawPlaylists.length == 0) return; if (rawPlaylists.length == 0) return;
Playlist playlist = Playlist.fromSQL(rawPlaylists[0]); Playlist playlist = Playlist.fromSQL(rawPlaylists[0]);
//Remove playlist //Remove playlist
@ -435,22 +465,26 @@ class DownloadManager {
} }
//Check if album, track or playlist is offline //Check if album, track or playlist is offline
Future<bool> checkOffline({Album album, Track track, Playlist playlist}) async { Future<bool> checkOffline(
{Album album, Track track, Playlist playlist}) async {
//Track //Track
if (track != null) { if (track != null) {
List res = await db.query('Tracks', where: 'id == ? AND offline == 1', whereArgs: [track.id]); List res = await db.query('Tracks',
where: 'id == ? AND offline == 1', whereArgs: [track.id]);
if (res.length == 0) return false; if (res.length == 0) return false;
return true; return true;
} }
//Album //Album
if (album != null) { if (album != null) {
List res = await db.query('Albums', where: 'id == ? AND offline == 1', whereArgs: [album.id]); List res = await db.query('Albums',
where: 'id == ? AND offline == 1', whereArgs: [album.id]);
if (res.length == 0) return false; if (res.length == 0) return false;
return true; return true;
} }
//Playlist //Playlist
if (playlist != null && playlist.id != null) { if (playlist != null && playlist.id != null) {
List res = await db.query('Playlists', where: 'id == ?', whereArgs: [playlist.id]); List res = await db
.query('Playlists', where: 'id == ?', whereArgs: [playlist.id]);
if (res.length == 0) return false; if (res.length == 0) return false;
return true; return true;
} }
@ -459,19 +493,23 @@ class DownloadManager {
//Offline search //Offline search
Future<SearchResults> search(String query) async { Future<SearchResults> search(String query) async {
SearchResults results = SearchResults(tracks: [], albums: [], artists: [], playlists: []); SearchResults results =
SearchResults(tracks: [], albums: [], artists: [], playlists: []);
//Tracks //Tracks
List tracksData = await db.rawQuery('SELECT * FROM Tracks WHERE offline == 1 AND title like "%$query%"'); List tracksData = await db.rawQuery(
'SELECT * FROM Tracks WHERE offline == 1 AND title like "%$query%"');
for (Map trackData in tracksData) { for (Map trackData in tracksData) {
results.tracks.add(await getOfflineTrack(trackData['id'])); results.tracks.add(await getOfflineTrack(trackData['id']));
} }
//Albums //Albums
List albumsData = await db.rawQuery('SELECT (id) FROM Albums WHERE offline == 1 AND title like "%$query%"'); List albumsData = await db.rawQuery(
'SELECT (id) FROM Albums WHERE offline == 1 AND title like "%$query%"');
for (Map rawAlbum in albumsData) { for (Map rawAlbum in albumsData) {
results.albums.add(await getOfflineAlbum(rawAlbum['id'])); results.albums.add(await getOfflineAlbum(rawAlbum['id']));
} }
//Playlists //Playlists
List playlists = await db.rawQuery('SELECT * FROM Playlists WHERE title like "%$query%"'); List playlists = await db
.rawQuery('SELECT * FROM Playlists WHERE title like "%$query%"');
for (Map playlist in playlists) { for (Map playlist in playlists) {
results.playlists.add(await getPlaylist(playlist['id'])); results.playlists.add(await getPlaylist(playlist['id']));
} }
@ -485,7 +523,10 @@ class DownloadManager {
} }
//Generate track download path //Generate track download path
String _generatePath(Track track, bool private, {String playlistName, int playlistTrackNumber, bool isSingleton = false}) { String _generatePath(Track track, bool private,
{String playlistName,
int playlistTrackNumber,
bool isSingleton = false}) {
String path; String path;
if (private) { if (private) {
path = p.join(offlinePath, track.id); path = p.join(offlinePath, track.id);
@ -496,23 +537,26 @@ class DownloadManager {
if (settings.playlistFolder && playlistName != null) if (settings.playlistFolder && playlistName != null)
path = p.join(path, sanitize(playlistName)); path = p.join(path, sanitize(playlistName));
if (settings.artistFolder) if (settings.artistFolder) path = p.join(path, '%albumArtist%');
path = p.join(path, '%albumArtist%');
//Album folder / with disk number //Album folder / with disk number
if (settings.albumFolder) { if (settings.albumFolder) {
if (settings.albumDiscFolder) { if (settings.albumDiscFolder) {
path = p.join(path, '%album%' + ' - Disk ' + (track.diskNumber??1).toString()); path = p.join(path,
'%album%' + ' - Disk ' + (track.diskNumber ?? 1).toString());
} else { } else {
path = p.join(path, '%album%'); path = p.join(path, '%album%');
} }
} }
//Final path //Final path
path = p.join(path, isSingleton ? settings.singletonFilename : settings.downloadFilename); path = p.join(path,
isSingleton ? settings.singletonFilename : settings.downloadFilename);
//Playlist track number variable (not accessible in service) //Playlist track number variable (not accessible in service)
if (playlistTrackNumber != null) { if (playlistTrackNumber != null) {
path = path.replaceAll('%playlistTrackNumber%', playlistTrackNumber.toString()); path = path.replaceAll(
path = path.replaceAll('%0playlistTrackNumber%', playlistTrackNumber.toString().padLeft(2, '0')); '%playlistTrackNumber%', playlistTrackNumber.toString());
path = path.replaceAll('%0playlistTrackNumber%',
playlistTrackNumber.toString().padLeft(2, '0'));
} else { } else {
path = path.replaceAll('%playlistTrackNumber%', ''); path = path.replaceAll('%playlistTrackNumber%', '');
path = path.replaceAll('%0playlistTrackNumber%', ''); path = path.replaceAll('%0playlistTrackNumber%', '');
@ -524,13 +568,19 @@ class DownloadManager {
//Get stats for library screen //Get stats for library screen
Future<List<String>> getStats() async { Future<List<String>> getStats() async {
//Get offline counts //Get offline counts
int trackCount = (await db.rawQuery('SELECT COUNT(*) FROM Tracks WHERE offline == 1'))[0]['COUNT(*)']; int trackCount =
int albumCount = (await db.rawQuery('SELECT COUNT(*) FROM Albums WHERE offline == 1'))[0]['COUNT(*)']; (await db.rawQuery('SELECT COUNT(*) FROM Tracks WHERE offline == 1'))[0]
int playlistCount = (await db.rawQuery('SELECT COUNT(*) FROM Playlists'))[0]['COUNT(*)']; ['COUNT(*)'];
int albumCount =
(await db.rawQuery('SELECT COUNT(*) FROM Albums WHERE offline == 1'))[0]
['COUNT(*)'];
int playlistCount =
(await db.rawQuery('SELECT COUNT(*) FROM Playlists'))[0]['COUNT(*)'];
//Free space //Free space
double diskSpace = await DiskSpace.getFreeDiskSpace; double diskSpace = await DiskSpace.getFreeDiskSpace;
//Used space //Used space
List<FileSystemEntity> offlineStat = await Directory(offlinePath).list().toList(); List<FileSystemEntity> offlineStat =
await Directory(offlinePath).list().toList();
int offlineSize = 0; int offlineSize = 0;
for (var fs in offlineStat) { for (var fs in offlineStat) {
offlineSize += (await fs.stat()).size; offlineSize += (await fs.stat()).size;
@ -547,7 +597,8 @@ class DownloadManager {
//Send settings to download service //Send settings to download service
Future updateServiceSettings() async { Future updateServiceSettings() async {
await platform.invokeMethod('updateSettings', settings.getServiceSettings()); await platform.invokeMethod(
'updateSettings', settings.getServiceSettings());
} }
//Check storage permission //Check storage permission
@ -558,8 +609,7 @@ class DownloadManager {
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'Storage permission denied!'.i18n, msg: 'Storage permission denied!'.i18n,
toastLength: Toast.LENGTH_SHORT, toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM gravity: ToastGravity.BOTTOM);
);
return false; return false;
} }
} }
@ -576,9 +626,12 @@ class DownloadManager {
//Delete downloads by state //Delete downloads by state
Future removeDownloads(DownloadState state) async { Future removeDownloads(DownloadState state) async {
await platform.invokeMethod('removeDownloads', {'state': DownloadState.values.indexOf(state)}); await platform.invokeMethod(
'removeDownloads', {'state': DownloadState.values.indexOf(state)});
} }
static Future<String> getDirectory(String title) =>
platform.invokeMethod('getDirectory', <String, String>{'title': title});
} }
class Download { class Download {
@ -596,12 +649,24 @@ class Download {
int received; int received;
int filesize; int filesize;
Download({this.id, this.path, this.private, this.trackId, this.md5origin, this.mediaVersion, Download(
this.title, this.image, this.state, this.received, this.filesize, this.quality}); {this.id,
this.path,
this.private,
this.trackId,
this.md5origin,
this.mediaVersion,
this.title,
this.image,
this.state,
this.received,
this.filesize,
this.quality});
//Get progress between 0 - 1 //Get progress between 0 - 1
double get progress { double get progress {
return ((received.toDouble()??0.0)/(filesize.toDouble()??1.0)).toDouble(); return ((received.toDouble() ?? 0.0) / (filesize.toDouble() ?? 1.0))
.toDouble();
} }
factory Download.fromJson(Map<dynamic, dynamic> data) { factory Download.fromJson(Map<dynamic, dynamic> data) {
@ -613,21 +678,22 @@ class Download {
id: data['id'], id: data['id'],
state: DownloadState.values[data['state']], state: DownloadState.values[data['state']],
title: data['title'], title: data['title'],
quality: data['quality'] quality: data['quality']);
);
} }
//Change values from "update json" //Change values from "update json"
void updateFromJson(Map<dynamic, dynamic> data) { void updateFromJson(Map<dynamic, dynamic> data) {
this.quality = data['quality']; this.quality = data['quality'];
this.received = data['received']??0; this.received = data['received'] ?? 0;
this.state = DownloadState.values[data['state']]; this.state = DownloadState.values[data['state']];
//Prevent null division later //Prevent null division later
this.filesize = ((data['filesize']??0) <= 0) ? 1 : (data['filesize']??1); this.filesize =
((data['filesize'] ?? 0) <= 0) ? 1 : (data['filesize'] ?? 1);
} }
//Track to download JSON for service //Track to download JSON for service
static Future<Map> jsonFromTrack(Track t, String path, {private = true, AudioQuality quality}) async { static Future<Map> jsonFromTrack(Track t, String path,
{private = true, AudioQuality quality}) async {
//Get download info //Get download info
if (t.playbackDetails == null || t.playbackDetails == []) { if (t.playbackDetails == null || t.playbackDetails == []) {
t = await deezerAPI.track(t.id); t = await deezerAPI.track(t.id);
@ -639,7 +705,7 @@ class Download {
"mediaVersion": t.playbackDetails[1], "mediaVersion": t.playbackDetails[1],
"quality": private "quality": private
? settings.getQualityInt(settings.offlineQuality) ? settings.getQualityInt(settings.offlineQuality)
: settings.getQualityInt((quality??settings.downloadQuality)), : settings.getQualityInt((quality ?? settings.downloadQuality)),
"title": t.title, "title": t.title,
"path": path, "path": path,
"image": t.albumArt.thumb "image": t.albumArt.thumb
@ -648,11 +714,4 @@ class Download {
} }
//Has to be same order as in java //Has to be same order as in java
enum DownloadState { enum DownloadState { NONE, DOWNLOADING, POST, DONE, DEEZER_ERROR, ERROR }
NONE,
DOWNLOADING,
POST,
DONE,
DEEZER_ERROR,
ERROR
}

View file

@ -11,7 +11,6 @@ import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:freezer/translations.i18n.dart'; import 'package:freezer/translations.i18n.dart';
import 'package:scrobblenaut/scrobblenaut.dart'; import 'package:scrobblenaut/scrobblenaut.dart';
import 'package:extended_math/extended_math.dart';
import 'definitions.dart'; import 'definitions.dart';
import '../settings.dart'; import '../settings.dart';
@ -339,9 +338,9 @@ class PlayerHelper {
} }
//Start visualizer //Start visualizer
Future startVisualizer() async { // Future startVisualizer() async {
await AudioService.customAction('startVisualizer'); // await AudioService.customAction('startVisualizer');
} // }
//Stop visualizer //Stop visualizer
Future stopVisualizer() async { Future stopVisualizer() async {
@ -769,33 +768,32 @@ class AudioPlayerTask extends BackgroundAudioTask {
this._queueIndex = args; this._queueIndex = args;
break; break;
//Start visualizer //Start visualizer
case 'startVisualizer': // case 'startVisualizer':
if (_visualizerSubscription != null) break; // if (_visualizerSubscription != null) break;
// _player.startVisualizer(
_player.startVisualizer( // enableWaveform: false,
enableWaveform: false, // enableFft: true,
enableFft: true, // captureRate: 15000,
captureRate: 15000, // captureSize: 128);
captureSize: 128); // _visualizerSubscription = _player.visualizerFftStream.listen((event) {
_visualizerSubscription = _player.visualizerFftStream.listen((event) { // //Calculate actual values
//Calculate actual values // List<double> out = [];
List<double> out = []; // for (int i = 0; i < event.length / 2; i++) {
for (int i = 0; i < event.length / 2; i++) { // int rfk = event[i * 2].toSigned(8);
int rfk = event[i * 2].toSigned(8); // int ifk = event[i * 2 + 1].toSigned(8);
int ifk = event[i * 2 + 1].toSigned(8); // out.add(log(hypot(rfk, ifk) + 1) / 5.2);
out.add(log(hypot(rfk, ifk) + 1) / 5.2); // }
} // AudioServiceBackground.sendCustomEvent(
AudioServiceBackground.sendCustomEvent( // {"action": "visualizer", "data": out});
{"action": "visualizer", "data": out}); // });
}); // break;
break; // //Stop visualizer
//Stop visualizer // case 'stopVisualizer':
case 'stopVisualizer': // if (_visualizerSubscription != null) {
if (_visualizerSubscription != null) { // _visualizerSubscription.cancel();
_visualizerSubscription.cancel(); // _visualizerSubscription = null;
_visualizerSubscription = null; // }
} // break;
break;
//Authorize lastfm //Authorize lastfm
case 'authorizeLastFM': case 'authorizeLastFM':
String username = args[0]; String username = args[0];

View file

@ -11,23 +11,26 @@ import 'dart:io';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class SpotifyScrapper { class SpotifyScrapper {
//Parse spotify URL to URI (spotify:track:1234) //Parse spotify URL to URI (spotify:track:1234)
static String parseUrl(String url) { static String parseUrl(String url) {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
if (uri.pathSegments.length > 3) return null; //Invalid URL if (uri.pathSegments.length > 3) return null; //Invalid URL
if (uri.pathSegments.length == 3) return 'spotify:${uri.pathSegments[1]}:${uri.pathSegments[2]}'; if (uri.pathSegments.length == 3)
if (uri.pathSegments.length == 2) return 'spotify:${uri.pathSegments[0]}:${uri.pathSegments[1]}'; return 'spotify:${uri.pathSegments[1]}:${uri.pathSegments[2]}';
if (uri.pathSegments.length == 2)
return 'spotify:${uri.pathSegments[0]}:${uri.pathSegments[1]}';
return null; return null;
} }
//Get spotify embed url from uri //Get spotify embed url from uri
static String getEmbedUrl(String uri) => 'https://embed.spotify.com/?uri=$uri'; static String getEmbedUrl(String uri) =>
'https://embed.spotify.com/?uri=$uri';
//https://link.tospotify.com/ or https://spotify.app.link/ //https://link.tospotify.com/ or https://spotify.app.link/
static Future resolveLinkUrl(String url) async { static Future resolveLinkUrl(String url) async {
http.Response response = await http.get(Uri.parse(url)); http.Response response = await http.get(Uri.parse(url));
Match match = RegExp(r'window\.top\.location = validate\("(.+)"\);').firstMatch(response.body); Match match = RegExp(r'window\.top\.location = validate\("(.+)"\);')
.firstMatch(response.body);
return match.group(1); return match.group(1);
} }
@ -41,7 +44,7 @@ class SpotifyScrapper {
//Extract JSON data form spotify embed page //Extract JSON data form spotify embed page
static Future<Map> getEmbedData(String url) async { static Future<Map> getEmbedData(String url) async {
//Fetch //Fetch
http.Response response = await http.get(url); http.Response response = await http.get(Uri.parse(url));
//Parse //Parse
dom.Document document = parse(response.body); dom.Document document = parse(response.body);
dom.Element element = document.getElementById('resource'); dom.Element element = document.getElementById('resource');
@ -78,7 +81,6 @@ class SpotifyScrapper {
Map deezer = await deezerAPI.callPublicApi('album/upc:' + album.upc); Map deezer = await deezerAPI.callPublicApi('album/upc:' + album.upc);
return deezer['id'].toString(); return deezer['id'].toString();
} }
} }
class SpotifyTrack { class SpotifyTrack {
@ -91,15 +93,14 @@ class SpotifyTrack {
//JSON //JSON
factory SpotifyTrack.fromJson(Map json) => SpotifyTrack( factory SpotifyTrack.fromJson(Map json) => SpotifyTrack(
title: json['name'], title: json['name'],
artists: json['artists'].map<String>((a) => a["name"].toString()).toList(), artists:
isrc: json['external_ids']['isrc'] json['artists'].map<String>((a) => a["name"].toString()).toList(),
); isrc: json['external_ids']['isrc']);
//Convert track to importer track //Convert track to importer track
ImporterTrack toImporter() { ImporterTrack toImporter() {
return ImporterTrack(title, artists, isrc: isrc); return ImporterTrack(title, artists, isrc: isrc);
} }
} }
class SpotifyPlaylist { class SpotifyPlaylist {
@ -115,8 +116,9 @@ class SpotifyPlaylist {
name: json['name'], name: json['name'],
description: json['description'], description: json['description'],
image: (json['images'].length > 0) ? json['images'][0]['url'] : null, image: (json['images'].length > 0) ? json['images'][0]['url'] : null,
tracks: json['tracks']['items'].map<SpotifyTrack>((j) => SpotifyTrack.fromJson(j['track'])).toList() tracks: json['tracks']['items']
); .map<SpotifyTrack>((j) => SpotifyTrack.fromJson(j['track']))
.toList());
//Convert to importer tracks //Convert to importer tracks
List<ImporterTrack> toImporter() { List<ImporterTrack> toImporter() {
@ -130,14 +132,11 @@ class SpotifyAlbum {
SpotifyAlbum({this.upc}); SpotifyAlbum({this.upc});
//JSON //JSON
factory SpotifyAlbum.fromJson(Map json) => SpotifyAlbum( factory SpotifyAlbum.fromJson(Map json) =>
upc: json['external_ids']['upc'] SpotifyAlbum(upc: json['external_ids']['upc']);
);
} }
class SpotifyAPIWrapper { class SpotifyAPIWrapper {
HttpServer _server; HttpServer _server;
SpotifyApi spotify; SpotifyApi spotify;
User me; User me;
@ -145,15 +144,15 @@ class SpotifyAPIWrapper {
//Try authorize with saved credentials //Try authorize with saved credentials
Future<bool> trySaved() async { Future<bool> trySaved() async {
print(settings.spotifyCredentials); print(settings.spotifyCredentials);
if (settings.spotifyClientId == null || settings.spotifyClientSecret == null || settings.spotifyCredentials == null) return false; if (settings.spotifyClientId == null ||
settings.spotifyClientSecret == null ||
settings.spotifyCredentials == null) return false;
final credentials = SpotifyApiCredentials( final credentials = SpotifyApiCredentials(
settings.spotifyClientId, settings.spotifyClientId, settings.spotifyClientSecret,
settings.spotifyClientSecret,
accessToken: settings.spotifyCredentials.accessToken, accessToken: settings.spotifyCredentials.accessToken,
refreshToken: settings.spotifyCredentials.refreshToken, refreshToken: settings.spotifyCredentials.refreshToken,
scopes: settings.spotifyCredentials.scopes, scopes: settings.spotifyCredentials.scopes,
expiration: settings.spotifyCredentials.expiration expiration: settings.spotifyCredentials.expiration);
);
spotify = SpotifyApi(credentials); spotify = SpotifyApi(credentials);
me = await spotify.me.get(); me = await spotify.me.get();
await _save(); await _save();
@ -162,7 +161,8 @@ class SpotifyAPIWrapper {
Future authorize(String clientId, String clientSecret) async { Future authorize(String clientId, String clientSecret) async {
//Spotify //Spotify
SpotifyApiCredentials credentials = SpotifyApiCredentials(clientId, clientSecret); SpotifyApiCredentials credentials =
SpotifyApiCredentials(clientId, clientSecret);
spotify = SpotifyApi(credentials); spotify = SpotifyApi(credentials);
//Create server //Create server
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, 42069); _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 42069);
@ -170,14 +170,21 @@ class SpotifyAPIWrapper {
//Get URL //Get URL
final grant = SpotifyApi.authorizationCodeGrant(credentials); final grant = SpotifyApi.authorizationCodeGrant(credentials);
final redirectUri = "http://localhost:42069"; final redirectUri = "http://localhost:42069";
final scopes = ['user-read-private', 'playlist-read-private', 'playlist-read-collaborative', 'user-library-read']; final scopes = [
final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri), scopes: scopes); 'user-read-private',
'playlist-read-private',
'playlist-read-collaborative',
'user-library-read'
];
final authUri =
grant.getAuthorizationUrl(Uri.parse(redirectUri), scopes: scopes);
launch(authUri.toString()); launch(authUri.toString());
//Wait for code //Wait for code
await for (HttpRequest request in _server) { await for (HttpRequest request in _server) {
//Exit window //Exit window
request.response.headers.set("Content-Type", "text/html; charset=UTF-8"); request.response.headers.set("Content-Type", "text/html; charset=UTF-8");
request.response.write("<body><h1>You can close this page and go back to Freezer.</h1></body><script>window.close();</script>"); request.response.write(
"<body><h1>You can close this page and go back to Freezer.</h1></body><script>window.close();</script>");
request.response.close(); request.response.close();
//Get token //Get token
if (request.uri.queryParameters["code"] != null) { if (request.uri.queryParameters["code"] != null) {
@ -202,8 +209,7 @@ class SpotifyAPIWrapper {
accessToken: spotifyCredentials.accessToken, accessToken: spotifyCredentials.accessToken,
refreshToken: spotifyCredentials.refreshToken, refreshToken: spotifyCredentials.refreshToken,
scopes: spotifyCredentials.scopes, scopes: spotifyCredentials.scopes,
expiration: spotifyCredentials.expiration expiration: spotifyCredentials.expiration);
);
settings.spotifyClientSecret = spotifyCredentials.clientId; settings.spotifyClientSecret = spotifyCredentials.clientId;
settings.spotifyClientSecret = spotifyCredentials.clientSecret; settings.spotifyClientSecret = spotifyCredentials.clientSecret;
settings.spotifyCredentials = saveCredentials; settings.spotifyCredentials = saveCredentials;

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:custom_navigator/custom_navigator.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -32,6 +31,8 @@ Function logOut;
GlobalKey<NavigatorState> mainNavigatorKey = GlobalKey<NavigatorState>(); GlobalKey<NavigatorState> mainNavigatorKey = GlobalKey<NavigatorState>();
GlobalKey<NavigatorState> navigatorKey; GlobalKey<NavigatorState> navigatorKey;
// TODO: migrate to null-safety
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -404,14 +405,52 @@ class _MainScreenState extends State<MainScreen>
], ],
)), )),
body: AudioServiceWidget( body: AudioServiceWidget(
child: CustomNavigator( child: _MainRouteNavigator(
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
home: Focus( home: Focus(
focusNode: screenFocusNode, focusNode: screenFocusNode,
skipTraversal: true, skipTraversal: true,
canRequestFocus: false, canRequestFocus: false,
child: _screens[_selected]), child: _screens[_selected])),
pageRoute: PageRoutes.materialPageRoute),
))); )));
} }
} }
// hella simple reimplementation of custom_navigator, which is NOT null-safe
class _MainRouteNavigator extends StatelessWidget with WidgetsBindingObserver {
final Widget home;
final GlobalKey<NavigatorState> navigatorKey;
const _MainRouteNavigator({Key key, this.home, this.navigatorKey})
: super(key: key);
// A system method that get invoked when user press back button on Android or back slide on iOS
@override
Future<bool> didPopRoute() async {
final NavigatorState navigator = navigatorKey?.currentState;
if (navigator == null) return false;
return await navigator.maybePop();
}
@override
Future<bool> didPushRoute(String route) async {
final NavigatorState navigator = navigatorKey?.currentState;
if (navigator == null) return false;
navigator.pushNamed(route);
return true;
}
@override
Widget build(BuildContext context) {
return Navigator(
initialRoute: Navigator.defaultRouteName,
onGenerateRoute: _onGenerateRoute,
);
}
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
if (settings.name == Navigator.defaultRouteName) {
return MaterialPageRoute(builder: (context) => home, settings: settings);
}
return null;
}
}

View file

@ -6,7 +6,6 @@ import 'package:freezer/ui/cached_image.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:ext_storage/ext_storage.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -181,7 +180,8 @@ class Settings {
_useArtColorSub = _useArtColorSub =
AudioService.currentMediaItemStream.listen((event) async { AudioService.currentMediaItemStream.listen((event) async {
if (event == null || event.artUri == null) return; if (event == null || event.artUri == null) return;
this.primaryColor = await imagesDatabase.getPrimaryColor(event.artUri); this.primaryColor =
await imagesDatabase.getPrimaryColor(event.artUri.toString());
updateTheme(); updateTheme();
}); });
} else { } else {
@ -208,19 +208,20 @@ class Settings {
} }
Settings s = Settings.fromJson({}); Settings s = Settings.fromJson({});
//Set default path, because async //Set default path, because async
s.downloadPath = (await ExtStorage.getExternalStoragePublicDirectory( s.downloadPath =
ExtStorage.DIRECTORY_MUSIC)); await getExternalStorageDirectories(type: StorageDirectory.music)
.then((paths) => paths[0].path);
s.save(); s.save();
return s; return s;
} }
Future save() async { Future<void> save() async {
File f = File(await getPath()); File f = File(await getPath());
await f.writeAsString(jsonEncode(this.toJson())); await f.writeAsString(jsonEncode(this.toJson()));
downloadManager.updateServiceSettings(); downloadManager.updateServiceSettings();
} }
Future updateAudioServiceQuality() async { Future<void> updateAudioServiceQuality() async {
//Send wifi & mobile quality to audio service isolate //Send wifi & mobile quality to audio service isolate
await AudioService.customAction('updateQuality', { await AudioService.customAction('updateQuality', {
'mobileQuality': getQualityInt(mobileQuality), 'mobileQuality': getQualityInt(mobileQuality),

View file

@ -5,7 +5,6 @@ import 'package:freezer/api/player.dart';
import 'package:freezer/translations.i18n.dart'; import 'package:freezer/translations.i18n.dart';
class AndroidAuto { class AndroidAuto {
//Prefix for "playable" MediaItem //Prefix for "playable" MediaItem
static const prefix = '_aa_'; static const prefix = '_aa_';
@ -21,13 +20,14 @@ class AndroidAuto {
//Fetch //Fetch
List<Playlist> playlists = await deezerAPI.getPlaylists(); List<Playlist> playlists = await deezerAPI.getPlaylists();
List<MediaItem> out = playlists.map<MediaItem>((p) => MediaItem( List<MediaItem> out = playlists
.map<MediaItem>((p) => MediaItem(
id: '${prefix}playlist${p.id}', id: '${prefix}playlist${p.id}',
displayTitle: p.title, displayTitle: p.title,
displaySubtitle: p.trackCount.toString() + ' ' + 'Tracks'.i18n, displaySubtitle: p.trackCount.toString() + ' ' + 'Tracks'.i18n,
playable: true, playable: true,
artUri: p.image.thumb artUri: Uri.parse(p.image.thumb)))
)).toList(); .toList();
return out; return out;
} }
@ -35,13 +35,15 @@ class AndroidAuto {
if (parentId == 'albums') { if (parentId == 'albums') {
List<Album> albums = await deezerAPI.getAlbums(); List<Album> albums = await deezerAPI.getAlbums();
List<MediaItem> out = albums.map<MediaItem>((a) => MediaItem( List<MediaItem> out = albums
.map<MediaItem>((a) => MediaItem(
id: '${prefix}album${a.id}', id: '${prefix}album${a.id}',
displayTitle: a.title, displayTitle: a.title,
displaySubtitle: a.artistString, displaySubtitle: a.artistString,
playable: true, playable: true,
artUri: a.art.thumb, artUri: Uri.parse(a.art.thumb),
)).toList(); ))
.toList();
return out; return out;
} }
@ -49,26 +51,29 @@ class AndroidAuto {
if (parentId == 'artists') { if (parentId == 'artists') {
List<Artist> artists = await deezerAPI.getArtists(); List<Artist> artists = await deezerAPI.getArtists();
List<MediaItem> out = artists.map<MediaItem>((a) => MediaItem( List<MediaItem> out = artists
.map<MediaItem>((a) => MediaItem(
id: 'albums${a.id}', id: 'albums${a.id}',
displayTitle: a.name, displayTitle: a.name,
playable: false, playable: false,
artUri: a.picture.thumb artUri: Uri.parse(a.picture.thumb)))
)).toList(); .toList();
return out; return out;
} }
//Artist screen (albums, etc) //Artist screen (albums, etc)
if (parentId.startsWith('albums')) { if (parentId.startsWith('albums')) {
List<Album> albums = await deezerAPI.discographyPage(parentId.replaceFirst('albums', '')); List<Album> albums =
await deezerAPI.discographyPage(parentId.replaceFirst('albums', ''));
List<MediaItem> out = albums.map<MediaItem>((a) => MediaItem( List<MediaItem> out = albums
.map<MediaItem>((a) => MediaItem(
id: '${prefix}album${a.id}', id: '${prefix}album${a.id}',
displayTitle: a.title, displayTitle: a.title,
displaySubtitle: a.artistString, displaySubtitle: a.artistString,
playable: true, playable: true,
artUri: a.art.thumb artUri: Uri.parse(a.art.thumb)))
)).toList(); .toList();
return out; return out;
} }
@ -77,21 +82,19 @@ class AndroidAuto {
HomePage hp = await deezerAPI.homePage(); HomePage hp = await deezerAPI.homePage();
List<MediaItem> out = []; List<MediaItem> out = [];
for (HomePageSection section in hp.sections) { for (HomePageSection section in hp.sections) {
for (int i=0; i<section.items.length; i++) { for (int i = 0; i < section.items.length; i++) {
//Limit to max 5 items //Limit to max 5 items
if (i == 5) break; if (i == 5) break;
//Check type //Check type
var data = section.items[i].value; var data = section.items[i].value;
switch (section.items[i].type) { switch (section.items[i].type) {
case HomePageItemType.PLAYLIST: case HomePageItemType.PLAYLIST:
out.add(MediaItem( out.add(MediaItem(
id: '${prefix}playlist${data.id}', id: '${prefix}playlist${data.id}',
displayTitle: data.title, displayTitle: data.title,
playable: true, playable: true,
artUri: data.image.thumb artUri: data.image.thumb));
));
break; break;
case HomePageItemType.ALBUM: case HomePageItemType.ALBUM:
@ -100,8 +103,7 @@ class AndroidAuto {
displayTitle: data.title, displayTitle: data.title,
displaySubtitle: data.artistString, displaySubtitle: data.artistString,
playable: true, playable: true,
artUri: data.art.thumb artUri: data.art.thumb));
));
break; break;
case HomePageItemType.ARTIST: case HomePageItemType.ARTIST:
@ -109,8 +111,7 @@ class AndroidAuto {
id: 'albums${data.id}', id: 'albums${data.id}',
displayTitle: data.name, displayTitle: data.name,
playable: false, playable: false,
artUri: data.picture.thumb artUri: data.picture.thumb));
));
break; break;
case HomePageItemType.SMARTTRACKLIST: case HomePageItemType.SMARTTRACKLIST:
@ -119,8 +120,7 @@ class AndroidAuto {
displayTitle: data.title, displayTitle: data.title,
displaySubtitle: data.subtitle, displaySubtitle: data.subtitle,
playable: true, playable: true,
artUri: data.cover.thumb artUri: data.cover.thumb));
));
} }
} }
} }
@ -133,12 +133,12 @@ class AndroidAuto {
//Load virtual mediaItem //Load virtual mediaItem
Future playItem(String id) async { Future playItem(String id) async {
print(id); print(id);
//Play flow //Play flow
if (id == 'flow' || id == 'stlflow') { if (id == 'flow' || id == 'stlflow') {
await playerHelper.playFromSmartTrackList(SmartTrackList(id: 'flow', title: 'Flow'.i18n)); await playerHelper.playFromSmartTrackList(
SmartTrackList(id: 'flow', title: 'Flow'.i18n));
return; return;
} }
//Play library tracks //Play library tracks
@ -146,20 +146,26 @@ class AndroidAuto {
//Load tracks //Load tracks
Playlist favPlaylist; Playlist favPlaylist;
try { try {
favPlaylist = await deezerAPI.fullPlaylist(deezerAPI.favoritesPlaylistId); favPlaylist =
} catch (e) {print(e);} await deezerAPI.fullPlaylist(deezerAPI.favoritesPlaylistId);
} catch (e) {
print(e);
}
if (favPlaylist == null || favPlaylist.tracks.length == 0) return; if (favPlaylist == null || favPlaylist.tracks.length == 0) return;
await playerHelper.playFromTrackList(favPlaylist.tracks, favPlaylist.tracks[0].id, QueueSource( await playerHelper.playFromTrackList(
favPlaylist.tracks,
favPlaylist.tracks[0].id,
QueueSource(
id: 'allTracks', id: 'allTracks',
text: 'All offline tracks'.i18n, text: 'All offline tracks'.i18n,
source: 'offline' source: 'offline'));
));
return; return;
} }
//Play playlists //Play playlists
if (id.startsWith('playlist')) { if (id.startsWith('playlist')) {
Playlist p = await deezerAPI.fullPlaylist(id.replaceFirst('playlist', '')); Playlist p =
await deezerAPI.fullPlaylist(id.replaceFirst('playlist', ''));
await playerHelper.playFromPlaylist(p, p.tracks[0].id); await playerHelper.playFromPlaylist(p, p.tracks[0].id);
return; return;
} }
@ -171,7 +177,8 @@ class AndroidAuto {
} }
//Play smart track list //Play smart track list
if (id.startsWith('stl')) { if (id.startsWith('stl')) {
SmartTrackList stl = await deezerAPI.smartTrackList(id.replaceFirst('stl', '')); SmartTrackList stl =
await deezerAPI.smartTrackList(id.replaceFirst('stl', ''));
await playerHelper.playFromSmartTrackList(stl); await playerHelper.playFromSmartTrackList(stl);
return; return;
} }
@ -180,11 +187,7 @@ class AndroidAuto {
//Homescreen items //Homescreen items
List<MediaItem> homeScreen() { List<MediaItem> homeScreen() {
return [ return [
MediaItem( MediaItem(id: '${prefix}flow', displayTitle: 'Flow'.i18n, playable: true),
id: '${prefix}flow',
displayTitle: 'Flow'.i18n,
playable: true
),
MediaItem( MediaItem(
id: 'homescreen', id: 'homescreen',
displayTitle: 'Home'.i18n, displayTitle: 'Home'.i18n,
@ -212,5 +215,4 @@ class AndroidAuto {
), ),
]; ];
} }
} }

View file

@ -78,7 +78,7 @@ class _LyricsScreenState extends State<LyricsScreen> {
_load(); _load();
//Enable visualizer //Enable visualizer
if (settings.lyricsVisualizer) playerHelper.startVisualizer(); // if (settings.lyricsVisualizer) playerHelper.startVisualizer();
Timer.periodic(Duration(milliseconds: 350), (timer) { Timer.periodic(Duration(milliseconds: 350), (timer) {
_timer = timer; _timer = timer;
_currentIndex = lyrics?.lyrics?.lastIndexWhere( _currentIndex = lyrics?.lyrics?.lastIndexWhere(
@ -107,7 +107,7 @@ class _LyricsScreenState extends State<LyricsScreen> {
if (_timer != null) _timer.cancel(); if (_timer != null) _timer.cancel();
if (_mediaItemSub != null) _mediaItemSub.cancel(); if (_mediaItemSub != null) _mediaItemSub.cancel();
//Stop visualizer //Stop visualizer
if (settings.lyricsVisualizer) playerHelper.stopVisualizer(); // if (settings.lyricsVisualizer) playerHelper.stopVisualizer();
super.dispose(); super.dispose();
} }
@ -176,8 +176,8 @@ class _LyricsScreenState extends State<LyricsScreen> {
}, },
child: ListView.builder( child: ListView.builder(
controller: _controller, controller: _controller,
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(0, 0, 0,
0, 0, 0, settings.lyricsVisualizer ? 100 : 0), settings.lyricsVisualizer && false ? 100 : 0),
itemCount: lyrics.lyrics.length, itemCount: lyrics.lyrics.length,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
return Padding( return Padding(
@ -212,30 +212,30 @@ class _LyricsScreenState extends State<LyricsScreen> {
)), )),
//Visualizer //Visualizer
if (settings.lyricsVisualizer) //if (settings.lyricsVisualizer)
Positioned( // Positioned(
bottom: 0, // bottom: 0,
left: 0, // left: 0,
right: 0, // right: 0,
child: StreamBuilder( // child: StreamBuilder(
stream: playerHelper.visualizerStream, // stream: playerHelper.visualizerStream,
builder: (BuildContext context, AsyncSnapshot snapshot) { // builder: (BuildContext context, AsyncSnapshot snapshot) {
List<double> data = snapshot.data ?? []; // List<double> data = snapshot.data ?? [];
double width = MediaQuery.of(context).size.width / // double width = MediaQuery.of(context).size.width /
data.length; //- 0.25; // data.length; //- 0.25;
return Row( // return Row(
crossAxisAlignment: CrossAxisAlignment.end, // crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate( // children: List.generate(
data.length, // data.length,
(i) => AnimatedContainer( // (i) => AnimatedContainer(
duration: Duration(milliseconds: 130), // duration: Duration(milliseconds: 130),
color: settings.primaryColor, // color: settings.primaryColor,
height: data[i] * 100, // height: data[i] * 100,
width: width, // width: width,
)), // )),
); // );
}), // }),
), // ),
], ],
)); ));
} }

View file

@ -671,7 +671,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
children: List.generate( children: List.generate(
AudioService.queue.length, AudioService.queue.length,
(i) => ZoomableImage( (i) => ZoomableImage(
url: AudioService.queue[i].artUri, url: AudioService.queue[i].artUri.toString(),
)), )),
), ),
); );

View file

@ -12,7 +12,6 @@ import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:fluttericon/web_symbols_icons.dart'; import 'package:fluttericon/web_symbols_icons.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:package_info/package_info.dart'; import 'package:package_info/package_info.dart';
import 'package:path_provider_ex/path_provider_ex.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:scrobblenaut/scrobblenaut.dart'; import 'package:scrobblenaut/scrobblenaut.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -161,7 +160,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
SimpleDialogOption( SimpleDialogOption(
child: Text('Light'.i18n), child: Text('Light'.i18n),
onPressed: () { onPressed: () {
setState(() => settings.theme = Themes.Light); settings.theme = Themes.Light;
settings.save(); settings.save();
updateTheme(); updateTheme();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -170,7 +169,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
SimpleDialogOption( SimpleDialogOption(
child: Text('Dark'.i18n), child: Text('Dark'.i18n),
onPressed: () { onPressed: () {
setState(() => settings.theme = Themes.Dark); settings.theme = Themes.Dark;
settings.save(); settings.save();
updateTheme(); updateTheme();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -179,7 +178,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
SimpleDialogOption( SimpleDialogOption(
child: Text('Black (AMOLED)'.i18n), child: Text('Black (AMOLED)'.i18n),
onPressed: () { onPressed: () {
setState(() => settings.theme = Themes.Black); settings.theme = Themes.Black;
settings.save(); settings.save();
updateTheme(); updateTheme();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -188,7 +187,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
SimpleDialogOption( SimpleDialogOption(
child: Text('Deezer (Dark)'.i18n), child: Text('Deezer (Dark)'.i18n),
onPressed: () { onPressed: () {
setState(() => settings.theme = Themes.Deezer); settings.theme = Themes.Deezer;
settings.save(); settings.save();
updateTheme(); updateTheme();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -203,11 +202,10 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
title: Text('Use system theme'.i18n), title: Text('Use system theme'.i18n),
value: settings.useSystemTheme, value: settings.useSystemTheme,
onChanged: (bool v) async { onChanged: (bool v) async {
setState(() {
settings.useSystemTheme = v; settings.useSystemTheme = v;
});
settings.save();
updateTheme(); updateTheme();
await settings.save();
}, },
secondary: Icon(Icons.android)), secondary: Icon(Icons.android)),
ListTile( ListTile(
@ -258,10 +256,11 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
ListTile( ListTile(
title: Text('Primary color'.i18n), title: Text('Primary color'.i18n),
leading: Icon(Icons.format_paint), leading: Icon(Icons.format_paint),
subtitle: Text( trailing: Padding(
'Selected color'.i18n, padding: EdgeInsets.only(left: 8.0),
style: TextStyle(color: settings.primaryColor), child: CircleAvatar(
), backgroundColor: settings.primaryColor,
)),
onTap: () { onTap: () {
showDialog( showDialog(
context: context, context: context,
@ -285,9 +284,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
allowShades: false, allowShades: false,
selectedColor: settings.primaryColor, selectedColor: settings.primaryColor,
onMainColorChange: (ColorSwatch color) { onMainColorChange: (ColorSwatch color) {
setState(() {
settings.primaryColor = color; settings.primaryColor = color;
});
settings.save(); settings.save();
updateTheme(); updateTheme();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -808,16 +805,21 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
subtitle: Text(settings.downloadPath), subtitle: Text(settings.downloadPath),
onTap: () async { onTap: () async {
//Check permissions //Check permissions
if (!(await Permission.storage.request().isGranted)) return; if (!await Permission.storage.request().isGranted) return;
DownloadManager.getDirectory('Pick-a-Path'.i18n).then((path) {
if (path == null) return; // user canceled
setState(() => settings.downloadPath = path);
settings.save();
});
//Navigate //Navigate
Navigator.of(context).push(MaterialPageRoute( // Navigator.of(context).push(MaterialPageRoute(
builder: (context) => DirectoryPicker( // builder: (context) => DirectoryPicker(
settings.downloadPath, // settings.downloadPath,
onSelect: (String p) async { // onSelect: (String p) async {
setState(() => settings.downloadPath = p); // setState(() => settings.downloadPath = p);
await settings.save(); // await settings.save();
}, // },
))); // )));
}, },
), ),
ListTile( ListTile(
@ -1308,165 +1310,165 @@ class _LastFMLoginState extends State<LastFMLogin> {
} }
} }
class DirectoryPicker extends StatefulWidget { // class DirectoryPicker extends StatefulWidget {
final String initialPath; // final String initialPath;
final Function onSelect; // final Function onSelect;
DirectoryPicker(this.initialPath, {this.onSelect, Key key}) : super(key: key); // DirectoryPicker(this.initialPath, {this.onSelect, Key key}) : super(key: key);
@override // @override
_DirectoryPickerState createState() => _DirectoryPickerState(); // _DirectoryPickerState createState() => _DirectoryPickerState();
} // }
class _DirectoryPickerState extends State<DirectoryPicker> { // class _DirectoryPickerState extends State<DirectoryPicker> {
String _path; // String _path;
String _previous; // String _previous;
String _root; // String _root;
@override // @override
void initState() { // void initState() {
_path = widget.initialPath; // _path = widget.initialPath;
super.initState(); // super.initState();
} // }
Future _resetPath() async { // Future _resetPath() async {
StorageInfo si = (await PathProviderEx.getStorageInfo())[0]; // StorageInfo si = (await PathProviderEx.getStorageInfo())[0];
setState(() => _path = si.appFilesDir); // setState(() => _path = si.appFilesDir);
} // }
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
return Scaffold( // return Scaffold(
appBar: FreezerAppBar( // appBar: FreezerAppBar(
'Pick-a-Path'.i18n, // 'Pick-a-Path'.i18n,
actions: <Widget>[ // actions: <Widget>[
IconButton( // IconButton(
icon: Icon( // icon: Icon(
Icons.sd_card, // Icons.sd_card,
semanticLabel: 'Select storage'.i18n, // semanticLabel: 'Select storage'.i18n,
), // ),
onPressed: () { // onPressed: () {
String path = ''; // String path = '';
//Chose storage // //Chose storage
showDialog( // showDialog(
context: context, // context: context,
builder: (context) { // builder: (context) {
return AlertDialog( // return AlertDialog(
title: Text('Select storage'.i18n), // title: Text('Select storage'.i18n),
content: FutureBuilder( // content: FutureBuilder(
future: PathProviderEx.getStorageInfo(), // future: PathProviderEx.getStorageInfo(),
builder: (context, snapshot) { // builder: (context, snapshot) {
if (snapshot.hasError) return ErrorScreen(); // if (snapshot.hasError) return ErrorScreen();
if (!snapshot.hasData) // if (!snapshot.hasData)
return Padding( // return Padding(
padding: EdgeInsets.symmetric(vertical: 8.0), // padding: EdgeInsets.symmetric(vertical: 8.0),
child: Row( // child: Row(
mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ // children: <Widget>[
CircularProgressIndicator() // CircularProgressIndicator()
], // ],
), // ),
); // );
return Column( // return Column(
mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate( // children: List<Widget>.generate(
snapshot.data.length, (int i) { // snapshot.data.length, (int i) {
StorageInfo si = snapshot.data[i]; // StorageInfo si = snapshot.data[i];
return ListTile( // return ListTile(
title: Text(si.rootDir), // title: Text(si.rootDir),
leading: Icon(Icons.sd_card), // leading: Icon(Icons.sd_card),
trailing: Text(filesize(si.availableBytes)), // trailing: Text(filesize(si.availableBytes)),
onTap: () { // onTap: () {
setState(() { // setState(() {
_path = si.appFilesDir; // _path = si.appFilesDir;
//Android 5+ blocks sd card, so this prevents going outside // //Android 5+ blocks sd card, so this prevents going outside
//app data dir, until permission request fix. // //app data dir, until permission request fix.
_root = si.rootDir; // _root = si.rootDir;
if (i != 0) _root = si.appFilesDir; // if (i != 0) _root = si.appFilesDir;
}); // });
Navigator.of(context).pop(); // Navigator.of(context).pop();
}, // },
); // );
})); // }));
}, // },
), // ),
); // );
}); // });
}) // })
], // ],
), // ),
floatingActionButton: FloatingActionButton( // floatingActionButton: FloatingActionButton(
child: Icon(Icons.done), // child: Icon(Icons.done),
onPressed: () { // onPressed: () {
//When folder confirmed // //When folder confirmed
if (widget.onSelect != null) widget.onSelect(_path); // if (widget.onSelect != null) widget.onSelect(_path);
Navigator.of(context).pop(); // Navigator.of(context).pop();
}, // },
), // ),
body: FutureBuilder( // body: FutureBuilder(
future: Directory(_path).list().toList(), // future: Directory(_path).list().toList(),
builder: (BuildContext context, AsyncSnapshot snapshot) { // builder: (BuildContext context, AsyncSnapshot snapshot) {
//On error go to last good path // //On error go to last good path
if (snapshot.hasError) // if (snapshot.hasError)
Future.delayed(Duration(milliseconds: 50), () { // Future.delayed(Duration(milliseconds: 50), () {
if (_previous == null) { // if (_previous == null) {
_resetPath(); // _resetPath();
return; // return;
} // }
setState(() => _path = _previous); // setState(() => _path = _previous);
}); // });
if (!snapshot.hasData) // if (!snapshot.hasData)
return Center( // return Center(
child: CircularProgressIndicator(), // child: CircularProgressIndicator(),
); // );
List<FileSystemEntity> data = snapshot.data; // List<FileSystemEntity> data = snapshot.data;
return ListView( // return ListView(
children: <Widget>[ // children: <Widget>[
ListTile( // ListTile(
title: Text(_path), // title: Text(_path),
), // ),
ListTile( // ListTile(
title: Text('Go up'.i18n), // title: Text('Go up'.i18n),
leading: Icon(Icons.arrow_upward), // leading: Icon(Icons.arrow_upward),
onTap: () { // onTap: () {
setState(() { // setState(() {
if (_root == _path) { // if (_root == _path) {
Fluttertoast.showToast( // Fluttertoast.showToast(
msg: 'Permission denied'.i18n, // msg: 'Permission denied'.i18n,
gravity: ToastGravity.BOTTOM); // gravity: ToastGravity.BOTTOM);
return; // return;
} // }
_previous = _path; // _previous = _path;
_path = Directory(_path).parent.path; // _path = Directory(_path).parent.path;
}); // });
}, // },
), // ),
...List.generate(data.length, (i) { // ...List.generate(data.length, (i) {
FileSystemEntity f = data[i]; // FileSystemEntity f = data[i];
if (f is Directory) { // if (f is Directory) {
return ListTile( // return ListTile(
title: Text(f.path.split('/').last), // title: Text(f.path.split('/').last),
leading: Icon(Icons.folder), // leading: Icon(Icons.folder),
onTap: () { // onTap: () {
setState(() { // setState(() {
_previous = _path; // _previous = _path;
_path = f.path; // _path = f.path;
}); // });
}, // },
); // );
} // }
return Container( // return Container(
height: 0, // height: 0,
width: 0, // width: 0,
); // );
}) // })
], // ],
); // );
}, // },
), // ),
); // );
} // }
} // }
class CreditsScreen extends StatefulWidget { class CreditsScreen extends StatefulWidget {
@override @override

View file

@ -198,7 +198,7 @@ class FreezerVersions {
//Fetch from website API //Fetch from website API
static Future<FreezerVersions> fetch() async { static Future<FreezerVersions> fetch() async {
http.Response response = http.Response response =
await http.get('https://freezer.life/api/versions'); await http.get(Uri.parse('https://freezer.life/api/versions'));
// http.Response response = await http.get('https://cum.freezerapp.workers.dev/api/versions'); // http.Response response = await http.get('https://cum.freezerapp.workers.dev/api/versions');
return FreezerVersions.fromJson(jsonDecode(response.body)); return FreezerVersions.fromJson(jsonDecode(response.body));
} }

View file

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "14.0.0" version: "24.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.41.2" version: "2.1.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -32,17 +32,17 @@ packages:
audio_service: audio_service:
dependency: "direct main" dependency: "direct main"
description: description:
path: audio_service name: audio_service
relative: true url: "https://pub.dartlang.org"
source: path source: hosted
version: "0.15.1" version: "0.17.1"
audio_session: audio_session:
dependency: "direct main" dependency: "direct main"
description: description:
name: audio_session name: audio_session
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.11" version: "0.1.6"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -56,42 +56,42 @@ packages:
name: build name: build
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.2" version: "2.1.0"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.6" version: "1.0.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.10" version: "3.0.0"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.3" version: "2.0.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.11.5" version: "2.1.1"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.10" version: "7.1.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -112,7 +112,21 @@ packages:
name: cached_network_image name: cached_network_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.3" version: "3.1.0"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -133,7 +147,7 @@ packages:
name: checked_yaml name: checked_yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
@ -154,7 +168,7 @@ packages:
name: code_builder name: code_builder
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.7.0" version: "4.1.0"
collection: collection:
dependency: "direct main" dependency: "direct main"
description: description:
@ -196,7 +210,7 @@ packages:
name: convert name: convert
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "3.0.1"
cookie_jar: cookie_jar:
dependency: "direct main" dependency: "direct main"
description: description:
@ -217,7 +231,7 @@ packages:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.5" version: "3.0.1"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -225,29 +239,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.17.0" version: "0.17.0"
custom_navigator:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "84bc85880abaa0d4a0f37098c9e6f4bd58b19b0a"
url: "https://github.com/kjawadDeveloper/Custom-navigator.git"
source: git
version: "0.2.0"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.12" version: "2.0.3"
dio: dio:
dependency: transitive dependency: transitive
description: description:
name: dio name: dio
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.10" version: "4.0.0"
disk_space: disk_space:
dependency: "direct main" dependency: "direct main"
description: description:
@ -265,24 +270,12 @@ packages:
equalizer: equalizer:
dependency: "direct main" dependency: "direct main"
description: description:
name: equalizer path: "."
url: "https://pub.dartlang.org" ref: HEAD
source: hosted resolved-ref: "84c15ca304a8129a1cad5a6891059fb411f0fc55"
url: "https://github.com/gladson97/equalizer.git"
source: git
version: "0.0.2+2" version: "0.0.2+2"
ext_storage:
dependency: "direct main"
description:
name: ext_storage
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
extended_math:
dependency: "direct main"
description:
name: extended_math
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.29+1"
fading_edge_scrollview: fading_edge_scrollview:
dependency: transitive dependency: transitive
description: description:
@ -336,14 +329,14 @@ packages:
name: flutter_blurhash name: flutter_blurhash
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "0.6.0"
flutter_cache_manager: flutter_cache_manager:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_cache_manager name: flutter_cache_manager
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.2" version: "3.1.2"
flutter_displaymode: flutter_displaymode:
dependency: "direct main" dependency: "direct main"
description: description:
@ -364,21 +357,21 @@ packages:
name: flutter_isolate name: flutter_isolate
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0+15" version: "2.0.0"
flutter_local_notifications: flutter_local_notifications:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1+2" version: "8.1.1+1"
flutter_local_notifications_platform_interface: flutter_local_notifications_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_platform_interface name: flutter_local_notifications_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0+1" version: "4.0.1"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -422,6 +415,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.0.8" version: "8.0.8"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
gettext_parser: gettext_parser:
dependency: transitive dependency: transitive
description: description:
@ -442,14 +442,14 @@ packages:
name: google_fonts name: google_fonts
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.2" version: "2.1.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
name: graphs name: graphs
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "2.0.0"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -463,21 +463,21 @@ packages:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2" version: "0.13.3"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
name: http_multi_server name: http_multi_server
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "3.0.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "4.0.0"
i18n_extension: i18n_extension:
dependency: "direct main" dependency: "direct main"
description: description:
@ -505,7 +505,7 @@ packages:
name: io name: io
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.5" version: "1.0.3"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -519,35 +519,37 @@ packages:
name: json_annotation name: json_annotation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.1" version: "4.1.0"
json_serializable: json_serializable:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.5.1" version: "5.0.0"
just_audio: just_audio:
dependency: "direct main" dependency: "direct main"
description: description:
path: "just_audio/just_audio" path: just_audio
relative: true ref: dev
source: path resolved-ref: "7ac783939a758be2799faefc8877c34a84fe1554"
version: "0.6.5" url: "https://github.com/ryanheise/just_audio.git"
source: git
version: "0.9.7"
just_audio_platform_interface: just_audio_platform_interface:
dependency: transitive dependency: transitive
description: description:
path: "just_audio/just_audio_platform_interface" name: just_audio_platform_interface
relative: true url: "https://pub.dartlang.org"
source: path source: hosted
version: "2.0.0" version: "4.0.0"
just_audio_web: just_audio_web:
dependency: transitive dependency: transitive
description: description:
path: "just_audio/just_audio_web" name: just_audio_web
relative: true url: "https://pub.dartlang.org"
source: path source: hosted
version: "0.2.1" version: "0.4.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -603,14 +605,14 @@ packages:
name: oauth2 name: oauth2
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.3" version: "2.0.0"
octo_image: octo_image:
dependency: transitive dependency: transitive
description: description:
name: octo_image name: octo_image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.0" version: "1.0.0+1"
open_file: open_file:
dependency: "direct main" dependency: "direct main"
description: description:
@ -624,7 +626,7 @@ packages:
name: package_config name: package_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.3" version: "2.0.0"
package_info: package_info:
dependency: "direct main" dependency: "direct main"
description: description:
@ -652,42 +654,35 @@ packages:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.28" version: "2.0.2"
path_provider_ex:
dependency: "direct main"
description:
name: path_provider_ex
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+2" version: "2.0.2"
path_provider_macos: path_provider_macos:
dependency: transitive dependency: transitive
description: description:
name: path_provider_macos name: path_provider_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.4+8" version: "2.0.2"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.5" version: "2.0.3"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@ -701,21 +696,21 @@ packages:
name: permission_handler name: permission_handler
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.1.0+2" version: "8.1.4+2"
permission_handler_platform_interface: permission_handler_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "3.6.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "4.2.0"
photo_view: photo_view:
dependency: "direct main" dependency: "direct main"
description: description:
@ -736,7 +731,7 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -764,7 +759,7 @@ packages:
name: pubspec_parse name: pubspec_parse
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.8" version: "1.0.0"
quick_actions: quick_actions:
dependency: "direct main" dependency: "direct main"
description: description:
@ -772,13 +767,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0+1" version: "0.5.0+1"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
random_string: random_string:
dependency: "direct main" dependency: "direct main"
description: description:
@ -792,14 +780,16 @@ packages:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.24.1" version: "0.27.1"
scrobblenaut: scrobblenaut:
dependency: "direct main" dependency: "direct main"
description: description:
name: scrobblenaut path: "."
url: "https://pub.dartlang.org" ref: main
source: hosted resolved-ref: a138aa57796cd1c1b3359d461b49515e58948baa
version: "2.0.5" url: "https://github.com/furgoose/Scrobblenaut.git"
source: git
version: "3.0.0"
share: share:
dependency: "direct main" dependency: "direct main"
description: description:
@ -813,14 +803,14 @@ packages:
name: shelf name: shelf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.9" version: "1.2.0"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.4+1" version: "1.0.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -832,7 +822,14 @@ packages:
name: source_gen name: source_gen
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.10+3" version: "1.0.5"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -846,7 +843,7 @@ packages:
name: spotify name: spotify
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
sprintf: sprintf:
dependency: transitive dependency: transitive
description: description:
@ -860,14 +857,14 @@ packages:
name: sqflite name: sqflite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.2+4" version: "2.0.0+4"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3+3" version: "2.0.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -902,7 +899,7 @@ packages:
name: synchronized name: synchronized
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0+2" version: "3.0.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -923,14 +920,14 @@ packages:
name: timezone name: timezone
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.1" version: "0.7.0"
timing: timing:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.1+3" version: "1.0.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -944,14 +941,21 @@ packages:
name: uni_links name: uni_links
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.0" version: "0.5.1"
universal_io: uni_links_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: universal_io name: uni_links_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.2" version: "1.0.0"
uni_links_web:
dependency: transitive
description:
name: uni_links_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1000,7 +1004,7 @@ packages:
name: uuid name: uuid
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.2" version: "3.0.4"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -1063,7 +1067,7 @@ packages:
name: web_socket_channel name: web_socket_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "2.1.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -1077,14 +1081,14 @@ packages:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.2" version: "0.2.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.5.1" version: "5.2.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -1092,13 +1096,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
zone_local:
dependency: transitive
description:
name: zone_local
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
sdks: sdks:
dart: ">=2.13.0 <3.0.0" dart: ">=2.13.0 <3.0.0"
flutter: ">=2.0.0" flutter: ">=2.2.0"

View file

@ -27,17 +27,16 @@ dependencies:
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
spotify: ^0.5.1 spotify: ^0.6.0
flutter_displaymode: ^0.3.2 flutter_displaymode: ^0.3.2
crypto: ^2.1.5 crypto: ^3.0.0
http: ^0.12.2 http: ^0.13.0
cookie_jar: ^3.0.1 cookie_jar: ^3.0.1
json_annotation: ^3.0.1 json_annotation: ^4.0.0
path_provider: ^1.6.28 path_provider: ^2.0.0
path: ^1.6.4 path: ^1.6.4
sqflite: ^1.3.0+1 sqflite: ^2.0.0+3
ext_storage: ^1.0.3 permission_handler: ^8.1.4+2
permission_handler: ^5.0.0+hotfix.6
connectivity: ^3.0.6 connectivity: ^3.0.6
intl: ^0.17.0 intl: ^0.17.0
filesize: ^2.0.1 filesize: ^2.0.1
@ -48,42 +47,43 @@ dependencies:
country_pickers: ^2.0.0 country_pickers: ^2.0.0
package_info: ^2.0.2 package_info: ^2.0.2
move_to_background: ^1.0.1 move_to_background: ^1.0.1
flutter_local_notifications: ^4.0.1+2 flutter_local_notifications: ^8.1.1+1
collection: ^1.14.12 collection: ^1.14.12
disk_space: ^0.1.1 disk_space: ^0.1.1
path_provider_ex: ^1.0.1
random_string: ^2.0.1 random_string: ^2.0.1
async: ^2.4.1 async: ^2.4.1
html: ^0.15.0 html: ^0.15.0
flutter_screenutil: ^5.0.0+2 flutter_screenutil: ^5.0.0+2
marquee: ^2.2.0 marquee: ^2.2.0
flutter_cache_manager: ^1.4.1 flutter_cache_manager: ^3.0.0
cached_network_image: ^2.3.2+1 cached_network_image: ^3.1.0
i18n_extension: ^4.0.0 i18n_extension: ^4.0.0
fluttericon: ^2.0.0 fluttericon: ^2.0.0
url_launcher: ^6.0.5 url_launcher: ^6.0.5
uni_links: ^0.4.0 uni_links: ^0.5.1
share: ^2.0.4 share: ^2.0.4
numberpicker: ^2.1.1 numberpicker: ^2.1.1
quick_actions: ^0.5.0+1 quick_actions: ^0.5.0+1
photo_view: ^0.12.0 photo_view: ^0.12.0
draggable_scrollbar: ^0.1.0 draggable_scrollbar: ^0.1.0
scrobblenaut: ^2.0.4 scrobblenaut:
git:
url: https://github.com/furgoose/Scrobblenaut.git
ref: main
open_file: ^3.0.3 open_file: ^3.0.3
version: ^2.0.0 version: ^2.0.0
wakelock: ^0.5.3+3 wakelock: ^0.5.3+3
google_fonts: ^1.1.2 google_fonts: ^2.1.0
equalizer: ^0.0.2+2 equalizer:
extended_math: ^0.0.29+1 git: https://github.com/gladson97/equalizer.git
custom_navigator:
git:
url: https://github.com/kjawadDeveloper/Custom-navigator.git
audio_session: ^0.0.9 audio_session: ^0.1.6
audio_service: audio_service: ^0.17.1
path: ./audio_service
just_audio: just_audio:
path: ./just_audio/just_audio git:
url: https://github.com/ryanheise/just_audio.git
ref: dev
path: just_audio/
# cupertino_icons: ^0.1.3 # cupertino_icons: ^0.1.3
@ -91,8 +91,8 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
json_serializable: ^3.3.0 json_serializable: ^5.0.0
build_runner: ^1.10.0 build_runner: ^2.1.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec