pre-null safety migration code
This commit is contained in:
parent
34fc597bbb
commit
8d53162099
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -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
|
|
138
android/app/src/main/java/f/f/freezer/FileUtils.java
Normal file
138
android/app/src/main/java/f/f/freezer/FileUtils.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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));
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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,
|
||||||
)),
|
// )),
|
||||||
);
|
// );
|
||||||
}),
|
// }),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
253
pubspec.lock
253
pubspec.lock
|
@ -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"
|
||||||
|
|
52
pubspec.yaml
52
pubspec.yaml
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue