From 8d53162099f02b171eab07bbc1b813f64f9666d4 Mon Sep 17 00:00:00 2001 From: pato05 Date: Mon, 30 Aug 2021 14:51:51 +0200 Subject: [PATCH] pre-null safety migration code --- .gitmodules | 6 - .../src/main/java/f/f/freezer/FileUtils.java | 138 +++++++ .../main/java/f/f/freezer/MainActivity.java | 39 +- lib/api/deezer.dart | 356 +++++++++-------- lib/api/definitions.dart | 27 +- lib/api/download.dart | 363 ++++++++++-------- lib/api/player.dart | 60 ++- lib/api/spotify.dart | 78 ++-- lib/main.dart | 47 ++- lib/settings.dart | 13 +- lib/ui/android_auto.dart | 138 +++---- lib/ui/lyrics.dart | 56 +-- lib/ui/player_screen.dart | 2 +- lib/ui/settings_screen.dart | 358 ++++++++--------- lib/ui/updater.dart | 2 +- pubspec.lock | 253 ++++++------ pubspec.yaml | 54 +-- 17 files changed, 1156 insertions(+), 834 deletions(-) create mode 100644 android/app/src/main/java/f/f/freezer/FileUtils.java diff --git a/.gitmodules b/.gitmodules index 8653609..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/android/app/src/main/java/f/f/freezer/FileUtils.java b/android/app/src/main/java/f/f/freezer/FileUtils.java new file mode 100644 index 0000000..d166d7b --- /dev/null +++ b/android/app/src/main/java/f/f/freezer/FileUtils.java @@ -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()); + } +} diff --git a/android/app/src/main/java/f/f/freezer/MainActivity.java b/android/app/src/main/java/f/f/freezer/MainActivity.java index e49e2cb..b2a5cbf 100644 --- a/android/app/src/main/java/f/f/freezer/MainActivity.java +++ b/android/app/src/main/java/f/f/freezer/MainActivity.java @@ -15,6 +15,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.provider.DocumentsContract; import android.util.Log; import androidx.core.view.WindowCompat; @@ -51,6 +52,10 @@ import static f.f.freezer.Deezer.bytesToHex; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "f.f.freezer/native"; 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; boolean serviceBound = false; @@ -224,7 +229,16 @@ public class MainActivity extends FlutterActivity { 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) @@ -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 private void connectService() { if (serviceBound) diff --git a/lib/api/deezer.dart b/lib/api/deezer.dart index a6e5ccc..fe28222 100644 --- a/lib/api/deezer.dart +++ b/lib/api/deezer.dart @@ -10,7 +10,6 @@ import 'dart:async'; DeezerAPI deezerAPI = DeezerAPI(); class DeezerAPI { - DeezerAPI({this.arl}); String arl; @@ -24,18 +23,22 @@ class DeezerAPI { //Get headers Map 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", - "Content-Language": '${settings.deezerLanguage??"en"}-${settings.deezerCountry??'US'}', - "Cache-Control": "max-age=0", - "Accept": "*/*", - "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", - "Connection": "keep-alive", - "Cookie": "arl=$arl" + ((sid == null) ? '' : '; sid=$sid') - }; + "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", + "Content-Language": + '${settings.deezerLanguage ?? "en"}-${settings.deezerCountry ?? 'US'}', + "Cache-Control": "max-age=0", + "Accept": "*/*", + "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", + "Connection": "keep-alive", + "Cookie": "arl=$arl" + ((sid == null) ? '' : '; sid=$sid') + }; //Call private API - Future> callApi(String method, {Map params, String gatewayInput}) async { + Future> callApi(String method, + {Map params, String gatewayInput}) async { //Generate URL Uri uri = Uri.https('www.deezer.com', '/ajax/gw-light.php', { 'api_version': '1.0', @@ -43,11 +46,11 @@ class DeezerAPI { 'input': '3', 'method': method, //Used for homepage - if (gatewayInput != null) - 'gateway_input': gatewayInput + if (gatewayInput != null) 'gateway_input': gatewayInput }); //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); //Grab SID 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 - if (body['error'].isNotEmpty && body['error'].containsKey('VALID_TOKEN_REQUIRED') && await rawAuthorize()) { - return callApi(method, params: params, gatewayInput: gatewayInput); + if (body['error'].isNotEmpty && + body['error'].containsKey('VALID_TOKEN_REQUIRED') && + await rawAuthorize()) { + return callApi(method, params: params, gatewayInput: gatewayInput); } return body; } Future> 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); } @@ -83,12 +89,14 @@ class DeezerAPI { Digest digest = md5.convert(utf8.encode(password)); String md5password = '$digest'; //Get access token - String url = "https://tv.deezer.com/smarttv/8caf9315c1740316053348a24d25afc7/user_auth.php?login=$email&password=$md5password&device=panasonic&output=json"; - http.Response response = await http.get(url); + String 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"]; //Get SID 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; for (String cookieHeader in response.headers['set-cookie'].split(';')) { if (cookieHeader.startsWith('sid=')) { @@ -97,12 +105,12 @@ class DeezerAPI { } if (sid == null) return null; //Get ARL - url = "https://deezer.com/ajax/gw-light.php?api_version=1.0&api_token=null&input=3&method=user.getArl"; - response = await http.get(url, headers: {"Cookie": "sid=$sid"}); + url = + "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"]; } - //Authorize, bool = success Future rawAuthorize({Function onError}) async { try { @@ -117,8 +125,7 @@ class DeezerAPI { return true; } } catch (e) { - if (onError != null) - onError(e); + if (onError != null) onError(e); print('Login Error (D): ' + e.toString()); return false; } @@ -130,8 +137,10 @@ class DeezerAPI { //https://www.deezer.com/NOTHING_OR_COUNTRY/TYPE/ID if (uri.host == 'www.deezer.com' || uri.host == 'deezer.com') { if (uri.pathSegments.length < 2) return null; - DeezerLinkType type = DeezerLinkResponse.typeFromString(uri.pathSegments[uri.pathSegments.length-2]); - return DeezerLinkResponse(type: type, id: uri.pathSegments[uri.pathSegments.length-1]); + DeezerLinkType type = DeezerLinkResponse.typeFromString( + uri.pathSegments[uri.pathSegments.length - 2]); + return DeezerLinkResponse( + type: type, id: uri.pathSegments[uri.pathSegments.length - 1]); } //Share URL if (uri.host == 'deezer.page.link' || uri.host == 'www.deezer.page.link') { @@ -162,75 +171,78 @@ class DeezerAPI { //Check if Deezer available in country static Future chceckAvailability() async { - try { - http.Response res = await http.get("https://api.deezer.com/infos"); - return jsonDecode(res.body)["open"]; - } catch (e) { - return null; - } + try { + http.Response res = + await http.get(Uri.parse('https://api.deezer.com/infos')); + return jsonDecode(res.body)["open"]; + } catch (e) { + return null; + } } //Search Future search(String query) async { - Map data = await callApi('deezer.pageSearch', params: { - 'nb': 128, - 'query': query, - 'start': 0 - }); + Map data = await callApi('deezer.pageSearch', + params: {'nb': 128, 'query': query, 'start': 0}); return SearchResults.fromPrivateJson(data['results']); } Future track(String id) async { - Map data = await callApi('song.getListData', params: {'sng_ids': [id]}); + Map data = await callApi('song.getListData', params: { + 'sng_ids': [id] + }); return Track.fromPrivateJson(data['results']['data'][0]); } //Get album details, tracks Future album(String id) async { - Map data = await callApi('deezer.pageAlbum', params: { - 'alb_id': id, - 'header': true, - 'lang': settings.deezerLanguage??'en' - }); - return Album.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); + Map data = await callApi('deezer.pageAlbum', params: { + 'alb_id': id, + 'header': true, + 'lang': settings.deezerLanguage ?? 'en' + }); + return Album.fromPrivateJson(data['results']['DATA'], + songsJson: data['results']['SONGS']); } //Get artist details Future artist(String id) async { Map data = await callApi('deezer.pageArtist', params: { 'art_id': id, - 'lang': settings.deezerLanguage??'en', + 'lang': settings.deezerLanguage ?? 'en', }); - return Artist.fromPrivateJson( - data['results']['DATA'], - topJson: data['results']['TOP'], - albumsJson: data['results']['ALBUMS'], - highlight: data['results']['HIGHLIGHT'] - ); + return Artist.fromPrivateJson(data['results']['DATA'], + topJson: data['results']['TOP'], + albumsJson: data['results']['ALBUMS'], + highlight: data['results']['HIGHLIGHT']); } //Get playlist tracks at offset - Future> playlistTracksPage(String id, int start, {int nb = 50}) async { + Future> playlistTracksPage(String id, int start, + {int nb = 50}) async { Map data = await callApi('deezer.pagePlaylist', params: { 'playlist_id': id, - 'lang': settings.deezerLanguage??'en', + 'lang': settings.deezerLanguage ?? 'en', 'nb': nb, 'tags': true, 'start': start }); - return data['results']['SONGS']['data'].map((json) => Track.fromPrivateJson(json)).toList(); + return data['results']['SONGS']['data'] + .map((json) => Track.fromPrivateJson(json)) + .toList(); } //Get playlist details Future playlist(String id, {int nb = 100}) async { Map data = await callApi('deezer.pagePlaylist', params: { 'playlist_id': id, - 'lang': settings.deezerLanguage??'en', + 'lang': settings.deezerLanguage ?? 'en', 'nb': nb, 'tags': true, '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 @@ -264,11 +276,14 @@ class DeezerAPI { } //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: { 'offset': offset, 'playlist_id': playlistId, - 'songs': [[trackId, 0]] + 'songs': [ + [trackId, 0] + ] }); } @@ -276,87 +291,93 @@ class DeezerAPI { Future removeFromPlaylist(String trackId, String playlistId) async { await callApi('playlist.deleteSongs', params: { 'playlist_id': playlistId, - 'songs': [[trackId, 0]] + 'songs': [ + [trackId, 0] + ] }); } //Get users playlists Future> getPlaylists() async { - Map data = await callApi('deezer.pageProfile', params: { - 'nb': 100, - 'tab': 'playlists', - 'user_id': this.userId - }); - return data['results']['TAB']['playlists']['data'].map((json) => Playlist.fromPrivateJson(json, library: true)).toList(); + Map data = await callApi('deezer.pageProfile', + params: {'nb': 100, 'tab': 'playlists', 'user_id': this.userId}); + return data['results']['TAB']['playlists']['data'] + .map((json) => Playlist.fromPrivateJson(json, library: true)) + .toList(); } //Get favorite albums Future> getAlbums() async { - Map data = await callApi('deezer.pageProfile', params: { - 'nb': 50, - 'tab': 'albums', - 'user_id': this.userId - }); + Map data = await callApi('deezer.pageProfile', + params: {'nb': 50, 'tab': 'albums', 'user_id': this.userId}); List albumList = data['results']['TAB']['albums']['data']; - List albums = albumList.map((json) => Album.fromPrivateJson(json, library: true)).toList(); + List albums = albumList + .map((json) => Album.fromPrivateJson(json, library: true)) + .toList(); return albums; } //Remove album from library Future removeAlbum(String id) async { - await callApi('album.deleteFavorite', params: { - 'ALB_ID': id - }); + await callApi('album.deleteFavorite', params: {'ALB_ID': id}); } //Remove track from favorites Future removeFavorite(String id) async { - await callApi('favorite_song.remove', params: { - 'SNG_ID': id - }); + await callApi('favorite_song.remove', params: {'SNG_ID': id}); } //Get favorite artists Future> getArtists() async { - Map data = await callApi('deezer.pageProfile', params: { - 'nb': 40, - 'tab': 'artists', - 'user_id': this.userId - }); - return data['results']['TAB']['artists']['data'].map((json) => Artist.fromPrivateJson(json, library: true)).toList(); + Map data = await callApi('deezer.pageProfile', + params: {'nb': 40, 'tab': 'artists', 'user_id': this.userId}); + return data['results']['TAB']['artists']['data'] + .map((json) => Artist.fromPrivateJson(json, library: true)) + .toList(); } //Get lyrics by track id Future lyrics(String trackId) async { - Map data = await callApi('song.getLyrics', params: { - 'sng_id': trackId - }); - if (data['error'] != null && data['error'].length > 0) return Lyrics.error(); + Map data = await callApi('song.getLyrics', params: {'sng_id': trackId}); + if (data['error'] != null && data['error'].length > 0) + return Lyrics.error(); return Lyrics.fromPrivateJson(data['results']); } Future smartTrackList(String id) async { - Map data = await callApi('deezer.pageSmartTracklist', params: { - 'smarttracklist_id': id - }); - return SmartTrackList.fromPrivateJson(data['results']['DATA'], songsJson: data['results']['SONGS']); + Map data = await callApi('deezer.pageSmartTracklist', + params: {'smarttracklist_id': id}); + return SmartTrackList.fromPrivateJson(data['results']['DATA'], + songsJson: data['results']['SONGS']); } Future> flow() async { - Map data = await callApi('radio.getUserRadio', params: { - 'user_id': userId - }); - return data['results']['data'].map((json) => Track.fromPrivateJson(json)).toList(); + Map data = await callApi('radio.getUserRadio', params: {'user_id': userId}); + return data['results']['data'] + .map((json) => Track.fromPrivateJson(json)) + .toList(); } //Get homepage/music library from deezer Future homePage() async { - List grid = ['album', 'artist', 'channel', 'flow', 'playlist', 'radio', 'show', 'smarttracklist', 'track', 'user']; - Map data = await callApi('page.get', gatewayInput: jsonEncode({ - "PAGE": "home", - "VERSION": "2.3", - "SUPPORT": { - /* + List grid = [ + 'album', + 'artist', + 'channel', + 'flow', + 'playlist', + 'radio', + 'show', + 'smarttracklist', + 'track', + 'user' + ]; + Map data = await callApi('page.get', + gatewayInput: jsonEncode({ + "PAGE": "home", + "VERSION": "2.3", + "SUPPORT": { + /* "deeplink-list": ["deeplink"], "list": ["episode"], "grid-preview-one": grid, @@ -364,15 +385,15 @@ class DeezerAPI { "slideshow": grid, "message": ["call_onboarding"], */ - "grid": grid, - "horizontal-grid": grid, - "item-highlight": ["radio"], - "large-card": ["album", "playlist", "show", "video-link"], - "ads": [] //Nope - }, - "LANG": settings.deezerLanguage??'en', - "OPTIONS": [] - })); + "grid": grid, + "horizontal-grid": grid, + "item-highlight": ["radio"], + "large-card": ["album", "playlist", "show", "video-link"], + "ads": [] //Nope + }, + "LANG": settings.deezerLanguage ?? 'en', + "OPTIONS": [] + })); return HomePage.fromPrivateJson(data['results']); } @@ -390,12 +411,24 @@ class DeezerAPI { } Future getChannel(String target) async { - List grid = ['album', 'artist', 'channel', 'flow', 'playlist', 'radio', 'show', 'smarttracklist', 'track', 'user']; - Map data = await callApi('page.get', gatewayInput: jsonEncode({ - 'PAGE': target, - "VERSION": "2.3", - "SUPPORT": { - /* + List grid = [ + 'album', + 'artist', + 'channel', + 'flow', + 'playlist', + 'radio', + 'show', + 'smarttracklist', + 'track', + 'user' + ]; + Map data = await callApi('page.get', + gatewayInput: jsonEncode({ + 'PAGE': target, + "VERSION": "2.3", + "SUPPORT": { + /* "deeplink-list": ["deeplink"], "list": ["episode"], "grid-preview-one": grid, @@ -403,44 +436,47 @@ class DeezerAPI { "slideshow": grid, "message": ["call_onboarding"], */ - "grid": grid, - "horizontal-grid": grid, - "item-highlight": ["radio"], - "large-card": ["album", "playlist", "show", "video-link"], - "ads": [] //Nope - }, - "LANG": settings.deezerLanguage??'en', - "OPTIONS": [] - })); + "grid": grid, + "horizontal-grid": grid, + "item-highlight": ["radio"], + "large-card": ["album", "playlist", "show", "video-link"], + "ads": [] //Nope + }, + "LANG": settings.deezerLanguage ?? 'en', + "OPTIONS": [] + })); return HomePage.fromPrivateJson(data['results']); } //Add playlist to library Future addPlaylist(String id) async { - await callApi('playlist.addFavorite', params: { - 'parent_playlist_id': int.parse(id) - }); + await callApi('playlist.addFavorite', + params: {'parent_playlist_id': int.parse(id)}); } + //Remove playlist from library Future removePlaylist(String id) async { - await callApi('playlist.deleteFavorite', params: { - 'playlist_id': int.parse(id) - }); + await callApi('playlist.deleteFavorite', + params: {'playlist_id': int.parse(id)}); } + //Delete playlist Future deletePlaylist(String id) async { - await callApi('playlist.delete', params: { - 'playlist_id': id - }); + await callApi('playlist.delete', params: {'playlist_id': id}); } //Create playlist //Status 1 - private, 2 - collaborative - Future createPlaylist(String title, {String description = "", int status = 1, List trackIds = const []}) async { + Future createPlaylist(String title, + {String description = "", + int status = 1, + List trackIds = const []}) async { Map data = await callApi('playlist.create', params: { 'title': title, 'description': description, - 'songs': trackIds.map((id) => [int.parse(id), trackIds.indexOf(id)]).toList(), + 'songs': trackIds + .map((id) => [int.parse(id), trackIds.indexOf(id)]) + .toList(), 'status': status }); //Return playlistId @@ -448,7 +484,8 @@ class DeezerAPI { } //Get part of discography - Future> discographyPage(String artistId, {int start = 0, int nb = 50}) async { + Future> discographyPage(String artistId, + {int start = 0, int nb = 50}) async { Map data = await callApi('album.getDiscography', params: { 'art_id': int.parse(artistId), 'discography_mode': 'all', @@ -457,26 +494,29 @@ class DeezerAPI { 'nb_songs': 30 }); - return data['results']['data'].map((a) => Album.fromPrivateJson(a)).toList(); + return data['results']['data'] + .map((a) => Album.fromPrivateJson(a)) + .toList(); } Future searchSuggestions(String query) async { - Map data = await callApi('search_getSuggestedQueries', params: { - 'QUERY': query - }); + Map data = + await callApi('search_getSuggestedQueries', params: {'QUERY': query}); return data['results']['SUGGESTION'].map((s) => s['QUERY']).toList(); } //Get smart radio for artist id Future> smartRadio(String artistId) async { - Map data = await callApi('smart.getSmartRadio', params: { - 'art_id': int.parse(artistId) - }); - return data['results']['data'].map((t) => Track.fromPrivateJson(t)).toList(); + Map data = await callApi('smart.getSmartRadio', + params: {'art_id': int.parse(artistId)}); + return data['results']['data'] + .map((t) => Track.fromPrivateJson(t)) + .toList(); } //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: { 'description': description, 'title': title, @@ -487,12 +527,12 @@ class DeezerAPI { } //Get shuffled library - Future> libraryShuffle({int start=0}) async { - Map data = await callApi('tracklist.getShuffledCollection', params: { - 'nb': 50, - 'start': start - }); - return data['results']['data'].map((t) => Track.fromPrivateJson(t)).toList(); + Future> libraryShuffle({int start = 0}) async { + Map data = await callApi('tracklist.getShuffledCollection', + params: {'nb': 50, 'start': start}); + return data['results']['data'] + .map((t) => Track.fromPrivateJson(t)) + .toList(); } //Get similar tracks for track with id [trackId] @@ -500,7 +540,9 @@ class DeezerAPI { Map data = await callApi('song.getContextualTrackMix', params: { 'sng_ids': [trackId] }); - return data['results']['data'].map((t) => Track.fromPrivateJson(t)).toList(); + return data['results']['data'] + .map((t) => Track.fromPrivateJson(t)) + .toList(); } Future> allShowEpisodes(String showId) async { @@ -512,6 +554,8 @@ class DeezerAPI { 'start': 0, 'user_id': int.parse(deezerAPI.userId) }); - return data['results']['EPISODES']['data'].map((e) => ShowEpisode.fromPrivateJson(e)).toList(); + return data['results']['EPISODES']['data'] + .map((e) => ShowEpisode.fromPrivateJson(e)) + .toList(); } -} \ No newline at end of file +} diff --git a/lib/api/definitions.dart b/lib/api/definitions.dart index cb42f62..0193584 100644 --- a/lib/api/definitions.dart +++ b/lib/api/definitions.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:audio_service/audio_service.dart'; import 'package:freezer/api/cache.dart'; @@ -14,14 +16,14 @@ part 'definitions.g.dart'; @JsonSerializable() class Track { - String id; - String title; - Album album; - List artists; - Duration duration; - ImageDetails albumArt; + String/*!*//*!*/ id; + String/*!*/ title; + Album/*!*/ album; + List/*!*/ artists; + Duration/*!*/ duration; + ImageDetails/*!*/ albumArt; int trackNumber; - bool offline; + bool/*!*/ offline; Lyrics lyrics; bool favorite; int diskNumber; @@ -59,7 +61,7 @@ class Track { displayTitle: this.title, displaySubtitle: this.artistString, displayDescription: this.album.title, - artUri: this.albumArt.full, + artUri: Uri.parse(this.albumArt.full), duration: this.duration, id: this.id, extras: { @@ -95,8 +97,8 @@ class Track { artists: artists, album: album, id: mi.id, - albumArt: - ImageDetails(fullUrl: mi.artUri, thumbUrl: mi.extras['thumb']), + albumArt: ImageDetails( + fullUrl: mi.artUri.toString(), thumbUrl: mi.extras['thumb']), duration: mi.duration, playbackDetails: playbackDetails, lyrics: @@ -470,6 +472,7 @@ class User { Map toJson() => _$UserToJson(this); } +// TODO: migrate to Uri instead of String @JsonSerializable() class ImageDetails { String fullUrl; @@ -1001,7 +1004,7 @@ class ShowEpisode { }, displayDescription: description, duration: duration, - artUri: show.art.full, + artUri: Uri.parse(show.art.full), ); } @@ -1058,3 +1061,5 @@ extension Reorder on List { oldIndex > newIndex ? newIndex : newIndex - 1, this.removeAt(oldIndex)); } } + +double hypot(num c1, num c2) => sqrt(pow(c1.abs(), 2) + pow(c2.abs(), 2)); diff --git a/lib/api/download.dart b/lib/api/download.dart index 5e81db1..3d2faa7 100644 --- a/lib/api/download.dart +++ b/lib/api/download.dart @@ -20,10 +20,10 @@ import 'dart:async'; DownloadManager downloadManager = DownloadManager(); class DownloadManager { - //Platform channels - static MethodChannel platform = MethodChannel('f.f.freezer/native'); - static EventChannel eventChannel = EventChannel('f.f.freezer/downloads'); + static MethodChannel platform = const MethodChannel('f.f.freezer/native'); + static EventChannel eventChannel = + const EventChannel('f.f.freezer/downloads'); bool running = false; int queueSize = 0; @@ -53,26 +53,24 @@ class DownloadManager { String dbPath = p.join((await getDatabasesPath()), 'offline2.db'); //Open db - db = await openDatabase( - dbPath, - version: 1, - onCreate: (Database db, int version) async { - Batch b = db.batch(); - //Create tables, if doesn't exit - b.execute("""CREATE TABLE Tracks ( + db = await openDatabase(dbPath, version: 1, + onCreate: (Database db, int version) async { + Batch b = db.batch(); + //Create tables, if doesn't exit + b.execute("""CREATE TABLE Tracks ( id TEXT PRIMARY KEY, title TEXT, album TEXT, artists TEXT, duration INTEGER, albumArt TEXT, trackNumber INTEGER, offline INTEGER, lyrics TEXT, favorite INTEGER, diskNumber INTEGER, explicit INTEGER)"""); - b.execute("""CREATE TABLE Albums ( + b.execute("""CREATE TABLE Albums ( id TEXT PRIMARY KEY, title TEXT, artists TEXT, tracks TEXT, art TEXT, fans INTEGER, offline INTEGER, library INTEGER, type INTEGER, releaseDate TEXT)"""); - b.execute("""CREATE TABLE Artists ( + b.execute("""CREATE TABLE Artists ( id TEXT PRIMARY KEY, name TEXT, albums TEXT, topTracks TEXT, picture TEXT, fans INTEGER, albumCount INTEGER, offline INTEGER, library INTEGER, radio INTEGER)"""); - 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)"""); - await b.commit(); - } - ); + await b.commit(); + }); //Create offline directory - offlinePath = p.join((await getExternalStorageDirectory()).path, 'offline/'); + offlinePath = + p.join((await getExternalStorageDirectory()).path, 'offline/'); await Directory(offlinePath).create(recursive: true); //Update settings @@ -100,11 +98,16 @@ class DownloadManager { //Insert track and metadata to DB Future _addTrackToDB(Batch batch, Track track, bool overwriteTrack) async { - batch.insert('Tracks', track.toSQL(off: true), conflictAlgorithm: overwriteTrack?ConflictAlgorithm.replace:ConflictAlgorithm.ignore); - batch.insert('Albums', track.album.toSQL(off: false), conflictAlgorithm: ConflictAlgorithm.ignore); + batch.insert('Tracks', track.toSQL(off: true), + conflictAlgorithm: overwriteTrack + ? ConflictAlgorithm.replace + : ConflictAlgorithm.ignore); + batch.insert('Albums', track.album.toSQL(off: false), + conflictAlgorithm: ConflictAlgorithm.ignore); //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; } @@ -113,63 +116,61 @@ class DownloadManager { Future qualitySelect(BuildContext context) async { AudioQuality quality; await showModalBottomSheet( - context: context, - builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(0, 12, 0, 2), - child: Text( - 'Quality'.i18n, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20.0 + context: context, + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 12, 0, 2), + child: Text( + 'Quality'.i18n, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0), ), ), - ), - ListTile( - title: Text('MP3 128kbps'), - onTap: () { - quality = AudioQuality.MP3_128; - Navigator.of(context).pop(); - }, - ), - ListTile( - title: Text('MP3 320kbps'), - onTap: () { - quality = AudioQuality.MP3_320; - Navigator.of(context).pop(); - }, - ), - ListTile( - title: Text('FLAC'), - onTap: () { - quality = AudioQuality.FLAC; - Navigator.of(context).pop(); - }, - ) - ], - ); - } - ); + ListTile( + title: Text('MP3 128kbps'), + onTap: () { + quality = AudioQuality.MP3_128; + Navigator.of(context).pop(); + }, + ), + ListTile( + title: Text('MP3 320kbps'), + onTap: () { + quality = AudioQuality.MP3_320; + Navigator.of(context).pop(); + }, + ), + ListTile( + title: Text('FLAC'), + onTap: () { + quality = AudioQuality.FLAC; + Navigator.of(context).pop(); + }, + ) + ], + ); + }); return quality; } - Future addOfflineTrack(Track track, {private = true, BuildContext context, isSingleton = false}) async { + Future addOfflineTrack(Track track, + {private = true, BuildContext context, isSingleton = false}) async { //Permission if (!private && !(await checkPermission())) return false; //Ask for quality AudioQuality quality; if (!private && settings.downloadQuality == AudioQuality.ASK) { - quality = await qualitySelect(context); + quality = await qualitySelect(context); if (quality == null) return false; } //Fetch track if missing meta - if (track.artists == null || track.artists.length == 0 || track.album == null) - track = await deezerAPI.track(track.id); + if (track.artists == null || + track.artists.length == 0 || + track.album == null) track = await deezerAPI.track(track.id); //Add to DB if (private) { @@ -184,12 +185,16 @@ class DownloadManager { //Get path 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(); return true; } - Future addOfflineAlbum(Album album, {private = true, BuildContext context}) async { + Future addOfflineAlbum(Album album, + {private = true, BuildContext context}) async { //Permission if (!private && !(await checkPermission())) return; @@ -212,7 +217,8 @@ class DownloadManager { DefaultCacheManager().getSingleFile(album.art.full); 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) { b = await _addTrackToDB(b, t, false); } @@ -222,31 +228,37 @@ class DownloadManager { //Create downloads List out = []; 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 start(); } - Future addOfflinePlaylist(Playlist playlist, {private = true, BuildContext context, AudioQuality quality}) async { + Future addOfflinePlaylist(Playlist playlist, + {private = true, BuildContext context, AudioQuality quality}) async { //Permission if (!private && !(await checkPermission())) return; //Ask for quality - if (!private && settings.downloadQuality == AudioQuality.ASK && quality == null) { + if (!private && + settings.downloadQuality == AudioQuality.ASK && + quality == null) { quality = await qualitySelect(context); if (quality == null) return false; } //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); } //Add to DB if (private) { 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) { b = await _addTrackToDB(b, t, false); //Cache art @@ -258,30 +270,35 @@ class DownloadManager { //Generate downloads List out = []; - for (int i=0; i getOfflineTrack(String id, {Album album, List artists}) async { + Future getOfflineTrack(String id, + {Album album, List artists}) async { List tracks = await db.query('Tracks', where: 'id == ?', whereArgs: [id]); if (tracks.length == 0) return null; Track track = Track.fromSQL(tracks[0]); //Get album if (album == null) { - List rawAlbums = await db.query('Albums', where: 'id == ?', whereArgs: [track.album.id]); - if (rawAlbums.length > 0) - track.album = Album.fromSQL(rawAlbums[0]); + List rawAlbums = await db + .query('Albums', where: 'id == ?', whereArgs: [track.album.id]); + if (rawAlbums.length > 0) track.album = Album.fromSQL(rawAlbums[0]); } else { track.album = album; } @@ -290,12 +307,11 @@ class DownloadManager { if (artists == null) { List newArtists = []; for (Artist artist in track.artists) { - List rawArtist = await db.query('Artists', where: 'id == ?', whereArgs: [artist.id]); - if (rawArtist.length > 0) - newArtists.add(Artist.fromSQL(rawArtist[0])); + List rawArtist = + await db.query('Artists', where: 'id == ?', whereArgs: [artist.id]); + if (rawArtist.length > 0) newArtists.add(Artist.fromSQL(rawArtist[0])); } - if (newArtists.length > 0) - track.artists = newArtists; + if (newArtists.length > 0) track.artists = newArtists; } else { track.artists = artists; } @@ -304,7 +320,8 @@ class DownloadManager { //Get offline library tracks Future> 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 out = []; //Load track meta individually for (Map rawTrack in rawTracks) { @@ -315,7 +332,8 @@ class DownloadManager { //Get all offline available tracks Future> 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 out = []; //Load track meta individually for (Map rawTrack in rawTracks) { @@ -326,7 +344,8 @@ class DownloadManager { //Get all offline albums Future> 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 out = []; //Load each album for (Map rawAlbum in rawAlbums) { @@ -337,20 +356,22 @@ class DownloadManager { //Get offline album with meta Future 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; Album album = Album.fromSQL(rawAlbums[0]); List tracks = []; //Load tracks - for (int i=0; i artists = []; - for (int i=0; i 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; return Artist.fromSQL(rawArtists[0]); } @@ -376,7 +398,8 @@ class DownloadManager { //Get offline playlist Future 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; Playlist playlist = Playlist.fromSQL(rawPlaylists[0]); //Load tracks @@ -391,16 +414,21 @@ class DownloadManager { Future removeOfflineTracks(List tracks) async { for (Track t in tracks) { //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) { //Count occurrences in playlists and albums - List albums = await db.rawQuery('SELECT (id) FROM Albums WHERE tracks LIKE "%${t.id}%"'); - List playlists = await db.rawQuery('SELECT (id) FROM Playlists WHERE tracks LIKE "%${t.id}%"'); - if (albums.length + playlists.length == 0 && rawTrack[0]['favorite'] == 0) { + List albums = await db + .rawQuery('SELECT (id) FROM Albums WHERE tracks LIKE "%${t.id}%"'); + 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 await db.delete('Tracks', where: 'id == ?', whereArgs: [t.id]); } 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 { //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; Album album = Album.fromSQL(rawAlbums[0]); //Remove album @@ -426,7 +455,8 @@ class DownloadManager { Future removeOfflinePlaylist(String id) async { //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; Playlist playlist = Playlist.fromSQL(rawPlaylists[0]); //Remove playlist @@ -435,22 +465,26 @@ class DownloadManager { } //Check if album, track or playlist is offline - Future checkOffline({Album album, Track track, Playlist playlist}) async { + Future checkOffline( + {Album album, Track track, Playlist playlist}) async { //Track 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; return true; } //Album 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; return true; } //Playlist 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; return true; } @@ -459,19 +493,23 @@ class DownloadManager { //Offline search Future search(String query) async { - SearchResults results = SearchResults(tracks: [], albums: [], artists: [], playlists: []); + SearchResults results = + SearchResults(tracks: [], albums: [], artists: [], playlists: []); //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) { results.tracks.add(await getOfflineTrack(trackData['id'])); } //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) { results.albums.add(await getOfflineAlbum(rawAlbum['id'])); } //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) { results.playlists.add(await getPlaylist(playlist['id'])); } @@ -485,7 +523,10 @@ class DownloadManager { } //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; if (private) { path = p.join(offlinePath, track.id); @@ -496,23 +537,26 @@ class DownloadManager { if (settings.playlistFolder && playlistName != null) path = p.join(path, sanitize(playlistName)); - if (settings.artistFolder) - path = p.join(path, '%albumArtist%'); + if (settings.artistFolder) path = p.join(path, '%albumArtist%'); //Album folder / with disk number if (settings.albumFolder) { if (settings.albumDiscFolder) { - path = p.join(path, '%album%' + ' - Disk ' + (track.diskNumber??1).toString()); + path = p.join(path, + '%album%' + ' - Disk ' + (track.diskNumber ?? 1).toString()); } else { path = p.join(path, '%album%'); } } //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) if (playlistTrackNumber != null) { - path = path.replaceAll('%playlistTrackNumber%', playlistTrackNumber.toString()); - path = path.replaceAll('%0playlistTrackNumber%', playlistTrackNumber.toString().padLeft(2, '0')); + path = path.replaceAll( + '%playlistTrackNumber%', playlistTrackNumber.toString()); + path = path.replaceAll('%0playlistTrackNumber%', + playlistTrackNumber.toString().padLeft(2, '0')); } else { path = path.replaceAll('%playlistTrackNumber%', ''); path = path.replaceAll('%0playlistTrackNumber%', ''); @@ -524,13 +568,19 @@ class DownloadManager { //Get stats for library screen Future> getStats() async { //Get offline counts - int trackCount = (await db.rawQuery('SELECT COUNT(*) FROM Tracks WHERE offline == 1'))[0]['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(*)']; + int trackCount = + (await db.rawQuery('SELECT COUNT(*) FROM Tracks WHERE offline == 1'))[0] + ['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 double diskSpace = await DiskSpace.getFreeDiskSpace; //Used space - List offlineStat = await Directory(offlinePath).list().toList(); + List offlineStat = + await Directory(offlinePath).list().toList(); int offlineSize = 0; for (var fs in offlineStat) { offlineSize += (await fs.stat()).size; @@ -547,7 +597,8 @@ class DownloadManager { //Send settings to download service Future updateServiceSettings() async { - await platform.invokeMethod('updateSettings', settings.getServiceSettings()); + await platform.invokeMethod( + 'updateSettings', settings.getServiceSettings()); } //Check storage permission @@ -556,10 +607,9 @@ class DownloadManager { return true; } else { Fluttertoast.showToast( - msg: 'Storage permission denied!'.i18n, - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM - ); + msg: 'Storage permission denied!'.i18n, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM); return false; } } @@ -576,9 +626,12 @@ class DownloadManager { //Delete downloads by state 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 getDirectory(String title) => + platform.invokeMethod('getDirectory', {'title': title}); } class Download { @@ -596,38 +649,51 @@ class Download { int received; int filesize; - Download({this.id, this.path, this.private, this.trackId, this.md5origin, this.mediaVersion, - this.title, this.image, this.state, this.received, this.filesize, this.quality}); + Download( + {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 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 data) { return Download( - path: data['path'], - image: data['image'], - private: data['private'], - trackId: data['trackId'], - id: data['id'], - state: DownloadState.values[data['state']], - title: data['title'], - quality: data['quality'] - ); + path: data['path'], + image: data['image'], + private: data['private'], + trackId: data['trackId'], + id: data['id'], + state: DownloadState.values[data['state']], + title: data['title'], + quality: data['quality']); } //Change values from "update json" void updateFromJson(Map data) { this.quality = data['quality']; - this.received = data['received']??0; + this.received = data['received'] ?? 0; this.state = DownloadState.values[data['state']]; //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 - static Future jsonFromTrack(Track t, String path, {private = true, AudioQuality quality}) async { + static Future jsonFromTrack(Track t, String path, + {private = true, AudioQuality quality}) async { //Get download info if (t.playbackDetails == null || t.playbackDetails == []) { t = await deezerAPI.track(t.id); @@ -638,8 +704,8 @@ class Download { "md5origin": t.playbackDetails[0], "mediaVersion": t.playbackDetails[1], "quality": private - ? settings.getQualityInt(settings.offlineQuality) - : settings.getQualityInt((quality??settings.downloadQuality)), + ? settings.getQualityInt(settings.offlineQuality) + : settings.getQualityInt((quality ?? settings.downloadQuality)), "title": t.title, "path": path, "image": t.albumArt.thumb @@ -648,11 +714,4 @@ class Download { } //Has to be same order as in java -enum DownloadState { - NONE, - DOWNLOADING, - POST, - DONE, - DEEZER_ERROR, - ERROR -} \ No newline at end of file +enum DownloadState { NONE, DOWNLOADING, POST, DONE, DEEZER_ERROR, ERROR } diff --git a/lib/api/player.dart b/lib/api/player.dart index 341f894..af68f51 100644 --- a/lib/api/player.dart +++ b/lib/api/player.dart @@ -11,7 +11,6 @@ import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:freezer/translations.i18n.dart'; import 'package:scrobblenaut/scrobblenaut.dart'; -import 'package:extended_math/extended_math.dart'; import 'definitions.dart'; import '../settings.dart'; @@ -339,9 +338,9 @@ class PlayerHelper { } //Start visualizer - Future startVisualizer() async { - await AudioService.customAction('startVisualizer'); - } + // Future startVisualizer() async { + // await AudioService.customAction('startVisualizer'); + // } //Stop visualizer Future stopVisualizer() async { @@ -769,33 +768,32 @@ class AudioPlayerTask extends BackgroundAudioTask { this._queueIndex = args; break; //Start visualizer - case 'startVisualizer': - if (_visualizerSubscription != null) break; - - _player.startVisualizer( - enableWaveform: false, - enableFft: true, - captureRate: 15000, - captureSize: 128); - _visualizerSubscription = _player.visualizerFftStream.listen((event) { - //Calculate actual values - List out = []; - for (int i = 0; i < event.length / 2; i++) { - int rfk = event[i * 2].toSigned(8); - int ifk = event[i * 2 + 1].toSigned(8); - out.add(log(hypot(rfk, ifk) + 1) / 5.2); - } - AudioServiceBackground.sendCustomEvent( - {"action": "visualizer", "data": out}); - }); - break; - //Stop visualizer - case 'stopVisualizer': - if (_visualizerSubscription != null) { - _visualizerSubscription.cancel(); - _visualizerSubscription = null; - } - break; + // case 'startVisualizer': + // if (_visualizerSubscription != null) break; + // _player.startVisualizer( + // enableWaveform: false, + // enableFft: true, + // captureRate: 15000, + // captureSize: 128); + // _visualizerSubscription = _player.visualizerFftStream.listen((event) { + // //Calculate actual values + // List out = []; + // for (int i = 0; i < event.length / 2; i++) { + // int rfk = event[i * 2].toSigned(8); + // int ifk = event[i * 2 + 1].toSigned(8); + // out.add(log(hypot(rfk, ifk) + 1) / 5.2); + // } + // AudioServiceBackground.sendCustomEvent( + // {"action": "visualizer", "data": out}); + // }); + // break; + // //Stop visualizer + // case 'stopVisualizer': + // if (_visualizerSubscription != null) { + // _visualizerSubscription.cancel(); + // _visualizerSubscription = null; + // } + // break; //Authorize lastfm case 'authorizeLastFM': String username = args[0]; diff --git a/lib/api/spotify.dart b/lib/api/spotify.dart index d0cfed1..7414bd2 100644 --- a/lib/api/spotify.dart +++ b/lib/api/spotify.dart @@ -11,23 +11,26 @@ import 'dart:io'; import 'package:url_launcher/url_launcher.dart'; class SpotifyScrapper { - //Parse spotify URL to URI (spotify:track:1234) static String parseUrl(String url) { Uri uri = Uri.parse(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 == 2) return 'spotify:${uri.pathSegments[0]}:${uri.pathSegments[1]}'; + if (uri.pathSegments.length == 3) + return 'spotify:${uri.pathSegments[1]}:${uri.pathSegments[2]}'; + if (uri.pathSegments.length == 2) + return 'spotify:${uri.pathSegments[0]}:${uri.pathSegments[1]}'; return null; } //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/ static Future resolveLinkUrl(String url) async { 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); } @@ -41,7 +44,7 @@ class SpotifyScrapper { //Extract JSON data form spotify embed page static Future getEmbedData(String url) async { //Fetch - http.Response response = await http.get(url); + http.Response response = await http.get(Uri.parse(url)); //Parse dom.Document document = parse(response.body); dom.Element element = document.getElementById('resource'); @@ -78,7 +81,6 @@ class SpotifyScrapper { Map deezer = await deezerAPI.callPublicApi('album/upc:' + album.upc); return deezer['id'].toString(); } - } class SpotifyTrack { @@ -90,16 +92,15 @@ class SpotifyTrack { //JSON factory SpotifyTrack.fromJson(Map json) => SpotifyTrack( - title: json['name'], - artists: json['artists'].map((a) => a["name"].toString()).toList(), - isrc: json['external_ids']['isrc'] - ); + title: json['name'], + artists: + json['artists'].map((a) => a["name"].toString()).toList(), + isrc: json['external_ids']['isrc']); //Convert track to importer track ImporterTrack toImporter() { return ImporterTrack(title, artists, isrc: isrc); } - } class SpotifyPlaylist { @@ -112,11 +113,12 @@ class SpotifyPlaylist { //JSON factory SpotifyPlaylist.fromJson(Map json) => SpotifyPlaylist( - name: json['name'], - description: json['description'], - image: (json['images'].length > 0) ? json['images'][0]['url'] : null, - tracks: json['tracks']['items'].map((j) => SpotifyTrack.fromJson(j['track'])).toList() - ); + name: json['name'], + description: json['description'], + image: (json['images'].length > 0) ? json['images'][0]['url'] : null, + tracks: json['tracks']['items'] + .map((j) => SpotifyTrack.fromJson(j['track'])) + .toList()); //Convert to importer tracks List toImporter() { @@ -130,14 +132,11 @@ class SpotifyAlbum { SpotifyAlbum({this.upc}); //JSON - factory SpotifyAlbum.fromJson(Map json) => SpotifyAlbum( - upc: json['external_ids']['upc'] - ); + factory SpotifyAlbum.fromJson(Map json) => + SpotifyAlbum(upc: json['external_ids']['upc']); } - class SpotifyAPIWrapper { - HttpServer _server; SpotifyApi spotify; User me; @@ -145,15 +144,15 @@ class SpotifyAPIWrapper { //Try authorize with saved credentials Future trySaved() async { 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( - settings.spotifyClientId, - settings.spotifyClientSecret, - accessToken: settings.spotifyCredentials.accessToken, - refreshToken: settings.spotifyCredentials.refreshToken, - scopes: settings.spotifyCredentials.scopes, - expiration: settings.spotifyCredentials.expiration - ); + settings.spotifyClientId, settings.spotifyClientSecret, + accessToken: settings.spotifyCredentials.accessToken, + refreshToken: settings.spotifyCredentials.refreshToken, + scopes: settings.spotifyCredentials.scopes, + expiration: settings.spotifyCredentials.expiration); spotify = SpotifyApi(credentials); me = await spotify.me.get(); await _save(); @@ -162,7 +161,8 @@ class SpotifyAPIWrapper { Future authorize(String clientId, String clientSecret) async { //Spotify - SpotifyApiCredentials credentials = SpotifyApiCredentials(clientId, clientSecret); + SpotifyApiCredentials credentials = + SpotifyApiCredentials(clientId, clientSecret); spotify = SpotifyApi(credentials); //Create server _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 42069); @@ -170,14 +170,21 @@ class SpotifyAPIWrapper { //Get URL final grant = SpotifyApi.authorizationCodeGrant(credentials); final redirectUri = "http://localhost:42069"; - final scopes = ['user-read-private', 'playlist-read-private', 'playlist-read-collaborative', 'user-library-read']; - final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri), scopes: scopes); + final 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()); //Wait for code await for (HttpRequest request in _server) { //Exit window request.response.headers.set("Content-Type", "text/html; charset=UTF-8"); - request.response.write("

You can close this page and go back to Freezer.

"); + request.response.write( + "

You can close this page and go back to Freezer.

"); request.response.close(); //Get token if (request.uri.queryParameters["code"] != null) { @@ -202,8 +209,7 @@ class SpotifyAPIWrapper { accessToken: spotifyCredentials.accessToken, refreshToken: spotifyCredentials.refreshToken, scopes: spotifyCredentials.scopes, - expiration: spotifyCredentials.expiration - ); + expiration: spotifyCredentials.expiration); settings.spotifyClientSecret = spotifyCredentials.clientId; settings.spotifyClientSecret = spotifyCredentials.clientSecret; settings.spotifyCredentials = saveCredentials; diff --git a/lib/main.dart b/lib/main.dart index 2d0021a..b02b13b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:audio_service/audio_service.dart'; -import 'package:custom_navigator/custom_navigator.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -32,6 +31,8 @@ Function logOut; GlobalKey mainNavigatorKey = GlobalKey(); GlobalKey navigatorKey; +// TODO: migrate to null-safety + void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -404,14 +405,52 @@ class _MainScreenState extends State ], )), body: AudioServiceWidget( - child: CustomNavigator( + child: _MainRouteNavigator( navigatorKey: navigatorKey, home: Focus( focusNode: screenFocusNode, skipTraversal: true, canRequestFocus: false, - child: _screens[_selected]), - pageRoute: PageRoutes.materialPageRoute), + child: _screens[_selected])), ))); } } + +// hella simple reimplementation of custom_navigator, which is NOT null-safe +class _MainRouteNavigator extends StatelessWidget with WidgetsBindingObserver { + final Widget home; + final GlobalKey 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 didPopRoute() async { + final NavigatorState navigator = navigatorKey?.currentState; + if (navigator == null) return false; + return await navigator.maybePop(); + } + + @override + Future 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 _onGenerateRoute(RouteSettings settings) { + if (settings.name == Navigator.defaultRouteName) { + return MaterialPageRoute(builder: (context) => home, settings: settings); + } + return null; + } +} diff --git a/lib/settings.dart b/lib/settings.dart index a7db444..1e097b4 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -6,7 +6,6 @@ import 'package:freezer/ui/cached_image.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:ext_storage/ext_storage.dart'; import 'package:path/path.dart' as p; import 'package:flutter/material.dart'; @@ -181,7 +180,8 @@ class Settings { _useArtColorSub = AudioService.currentMediaItemStream.listen((event) async { if (event == null || event.artUri == null) return; - this.primaryColor = await imagesDatabase.getPrimaryColor(event.artUri); + this.primaryColor = + await imagesDatabase.getPrimaryColor(event.artUri.toString()); updateTheme(); }); } else { @@ -208,19 +208,20 @@ class Settings { } Settings s = Settings.fromJson({}); //Set default path, because async - s.downloadPath = (await ExtStorage.getExternalStoragePublicDirectory( - ExtStorage.DIRECTORY_MUSIC)); + s.downloadPath = + await getExternalStorageDirectories(type: StorageDirectory.music) + .then((paths) => paths[0].path); s.save(); return s; } - Future save() async { + Future save() async { File f = File(await getPath()); await f.writeAsString(jsonEncode(this.toJson())); downloadManager.updateServiceSettings(); } - Future updateAudioServiceQuality() async { + Future updateAudioServiceQuality() async { //Send wifi & mobile quality to audio service isolate await AudioService.customAction('updateQuality', { 'mobileQuality': getQualityInt(mobileQuality), diff --git a/lib/ui/android_auto.dart b/lib/ui/android_auto.dart index 3d0e6c9..32d8dd0 100644 --- a/lib/ui/android_auto.dart +++ b/lib/ui/android_auto.dart @@ -5,7 +5,6 @@ import 'package:freezer/api/player.dart'; import 'package:freezer/translations.i18n.dart'; class AndroidAuto { - //Prefix for "playable" MediaItem static const prefix = '_aa_'; @@ -21,13 +20,14 @@ class AndroidAuto { //Fetch List playlists = await deezerAPI.getPlaylists(); - List out = playlists.map((p) => MediaItem( - id: '${prefix}playlist${p.id}', - displayTitle: p.title, - displaySubtitle: p.trackCount.toString() + ' ' + 'Tracks'.i18n, - playable: true, - artUri: p.image.thumb - )).toList(); + List out = playlists + .map((p) => MediaItem( + id: '${prefix}playlist${p.id}', + displayTitle: p.title, + displaySubtitle: p.trackCount.toString() + ' ' + 'Tracks'.i18n, + playable: true, + artUri: Uri.parse(p.image.thumb))) + .toList(); return out; } @@ -35,13 +35,15 @@ class AndroidAuto { if (parentId == 'albums') { List albums = await deezerAPI.getAlbums(); - List out = albums.map((a) => MediaItem( - id: '${prefix}album${a.id}', - displayTitle: a.title, - displaySubtitle: a.artistString, - playable: true, - artUri: a.art.thumb, - )).toList(); + List out = albums + .map((a) => MediaItem( + id: '${prefix}album${a.id}', + displayTitle: a.title, + displaySubtitle: a.artistString, + playable: true, + artUri: Uri.parse(a.art.thumb), + )) + .toList(); return out; } @@ -49,26 +51,29 @@ class AndroidAuto { if (parentId == 'artists') { List artists = await deezerAPI.getArtists(); - List out = artists.map((a) => MediaItem( - id: 'albums${a.id}', - displayTitle: a.name, - playable: false, - artUri: a.picture.thumb - )).toList(); + List out = artists + .map((a) => MediaItem( + id: 'albums${a.id}', + displayTitle: a.name, + playable: false, + artUri: Uri.parse(a.picture.thumb))) + .toList(); return out; } //Artist screen (albums, etc) if (parentId.startsWith('albums')) { - List albums = await deezerAPI.discographyPage(parentId.replaceFirst('albums', '')); + List albums = + await deezerAPI.discographyPage(parentId.replaceFirst('albums', '')); - List out = albums.map((a) => MediaItem( - id: '${prefix}album${a.id}', - displayTitle: a.title, - displaySubtitle: a.artistString, - playable: true, - artUri: a.art.thumb - )).toList(); + List out = albums + .map((a) => MediaItem( + id: '${prefix}album${a.id}', + displayTitle: a.title, + displaySubtitle: a.artistString, + playable: true, + artUri: Uri.parse(a.art.thumb))) + .toList(); return out; } @@ -77,31 +82,28 @@ class AndroidAuto { HomePage hp = await deezerAPI.homePage(); List out = []; for (HomePageSection section in hp.sections) { - for (int i=0; i homeScreen() { return [ - MediaItem( - id: '${prefix}flow', - displayTitle: 'Flow'.i18n, - playable: true - ), + MediaItem(id: '${prefix}flow', displayTitle: 'Flow'.i18n, playable: true), MediaItem( id: 'homescreen', displayTitle: 'Home'.i18n, @@ -212,5 +215,4 @@ class AndroidAuto { ), ]; } - -} \ No newline at end of file +} diff --git a/lib/ui/lyrics.dart b/lib/ui/lyrics.dart index b0615ba..8a7465f 100644 --- a/lib/ui/lyrics.dart +++ b/lib/ui/lyrics.dart @@ -78,7 +78,7 @@ class _LyricsScreenState extends State { _load(); //Enable visualizer - if (settings.lyricsVisualizer) playerHelper.startVisualizer(); + // if (settings.lyricsVisualizer) playerHelper.startVisualizer(); Timer.periodic(Duration(milliseconds: 350), (timer) { _timer = timer; _currentIndex = lyrics?.lyrics?.lastIndexWhere( @@ -107,7 +107,7 @@ class _LyricsScreenState extends State { if (_timer != null) _timer.cancel(); if (_mediaItemSub != null) _mediaItemSub.cancel(); //Stop visualizer - if (settings.lyricsVisualizer) playerHelper.stopVisualizer(); + // if (settings.lyricsVisualizer) playerHelper.stopVisualizer(); super.dispose(); } @@ -176,8 +176,8 @@ class _LyricsScreenState extends State { }, child: ListView.builder( controller: _controller, - padding: EdgeInsets.fromLTRB( - 0, 0, 0, settings.lyricsVisualizer ? 100 : 0), + padding: EdgeInsets.fromLTRB(0, 0, 0, + settings.lyricsVisualizer && false ? 100 : 0), itemCount: lyrics.lyrics.length, itemBuilder: (BuildContext context, int i) { return Padding( @@ -212,30 +212,30 @@ class _LyricsScreenState extends State { )), //Visualizer - if (settings.lyricsVisualizer) - Positioned( - bottom: 0, - left: 0, - right: 0, - child: StreamBuilder( - stream: playerHelper.visualizerStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - List data = snapshot.data ?? []; - double width = MediaQuery.of(context).size.width / - data.length; //- 0.25; - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: List.generate( - data.length, - (i) => AnimatedContainer( - duration: Duration(milliseconds: 130), - color: settings.primaryColor, - height: data[i] * 100, - width: width, - )), - ); - }), - ), + //if (settings.lyricsVisualizer) + // Positioned( + // bottom: 0, + // left: 0, + // right: 0, + // child: StreamBuilder( + // stream: playerHelper.visualizerStream, + // builder: (BuildContext context, AsyncSnapshot snapshot) { + // List data = snapshot.data ?? []; + // double width = MediaQuery.of(context).size.width / + // data.length; //- 0.25; + // return Row( + // crossAxisAlignment: CrossAxisAlignment.end, + // children: List.generate( + // data.length, + // (i) => AnimatedContainer( + // duration: Duration(milliseconds: 130), + // color: settings.primaryColor, + // height: data[i] * 100, + // width: width, + // )), + // ); + // }), + // ), ], )); } diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index bd42703..5be6643 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -671,7 +671,7 @@ class _BigAlbumArtState extends State { children: List.generate( AudioService.queue.length, (i) => ZoomableImage( - url: AudioService.queue[i].artUri, + url: AudioService.queue[i].artUri.toString(), )), ), ); diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index 0a65ba7..fe801fa 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -12,7 +12,6 @@ import 'package:fluttericon/font_awesome5_icons.dart'; import 'package:fluttericon/web_symbols_icons.dart'; import 'package:fluttertoast/fluttertoast.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:scrobblenaut/scrobblenaut.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -161,7 +160,7 @@ class _AppearanceSettingsState extends State { SimpleDialogOption( child: Text('Light'.i18n), onPressed: () { - setState(() => settings.theme = Themes.Light); + settings.theme = Themes.Light; settings.save(); updateTheme(); Navigator.of(context).pop(); @@ -170,7 +169,7 @@ class _AppearanceSettingsState extends State { SimpleDialogOption( child: Text('Dark'.i18n), onPressed: () { - setState(() => settings.theme = Themes.Dark); + settings.theme = Themes.Dark; settings.save(); updateTheme(); Navigator.of(context).pop(); @@ -179,7 +178,7 @@ class _AppearanceSettingsState extends State { SimpleDialogOption( child: Text('Black (AMOLED)'.i18n), onPressed: () { - setState(() => settings.theme = Themes.Black); + settings.theme = Themes.Black; settings.save(); updateTheme(); Navigator.of(context).pop(); @@ -188,7 +187,7 @@ class _AppearanceSettingsState extends State { SimpleDialogOption( child: Text('Deezer (Dark)'.i18n), onPressed: () { - setState(() => settings.theme = Themes.Deezer); + settings.theme = Themes.Deezer; settings.save(); updateTheme(); Navigator.of(context).pop(); @@ -203,11 +202,10 @@ class _AppearanceSettingsState extends State { title: Text('Use system theme'.i18n), value: settings.useSystemTheme, onChanged: (bool v) async { - setState(() { - settings.useSystemTheme = v; - }); + settings.useSystemTheme = v; + + settings.save(); updateTheme(); - await settings.save(); }, secondary: Icon(Icons.android)), ListTile( @@ -258,10 +256,11 @@ class _AppearanceSettingsState extends State { ListTile( title: Text('Primary color'.i18n), leading: Icon(Icons.format_paint), - subtitle: Text( - 'Selected color'.i18n, - style: TextStyle(color: settings.primaryColor), - ), + trailing: Padding( + padding: EdgeInsets.only(left: 8.0), + child: CircleAvatar( + backgroundColor: settings.primaryColor, + )), onTap: () { showDialog( context: context, @@ -285,9 +284,7 @@ class _AppearanceSettingsState extends State { allowShades: false, selectedColor: settings.primaryColor, onMainColorChange: (ColorSwatch color) { - setState(() { - settings.primaryColor = color; - }); + settings.primaryColor = color; settings.save(); updateTheme(); Navigator.of(context).pop(); @@ -808,16 +805,21 @@ class _DownloadsSettingsState extends State { subtitle: Text(settings.downloadPath), onTap: () async { //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 - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => DirectoryPicker( - settings.downloadPath, - onSelect: (String p) async { - setState(() => settings.downloadPath = p); - await settings.save(); - }, - ))); + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => DirectoryPicker( + // settings.downloadPath, + // onSelect: (String p) async { + // setState(() => settings.downloadPath = p); + // await settings.save(); + // }, + // ))); }, ), ListTile( @@ -1308,165 +1310,165 @@ class _LastFMLoginState extends State { } } -class DirectoryPicker extends StatefulWidget { - final String initialPath; - final Function onSelect; - DirectoryPicker(this.initialPath, {this.onSelect, Key key}) : super(key: key); +// class DirectoryPicker extends StatefulWidget { +// final String initialPath; +// final Function onSelect; +// DirectoryPicker(this.initialPath, {this.onSelect, Key key}) : super(key: key); - @override - _DirectoryPickerState createState() => _DirectoryPickerState(); -} +// @override +// _DirectoryPickerState createState() => _DirectoryPickerState(); +// } -class _DirectoryPickerState extends State { - String _path; - String _previous; - String _root; +// class _DirectoryPickerState extends State { +// String _path; +// String _previous; +// String _root; - @override - void initState() { - _path = widget.initialPath; - super.initState(); - } +// @override +// void initState() { +// _path = widget.initialPath; +// super.initState(); +// } - Future _resetPath() async { - StorageInfo si = (await PathProviderEx.getStorageInfo())[0]; - setState(() => _path = si.appFilesDir); - } +// Future _resetPath() async { +// StorageInfo si = (await PathProviderEx.getStorageInfo())[0]; +// setState(() => _path = si.appFilesDir); +// } - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: FreezerAppBar( - 'Pick-a-Path'.i18n, - actions: [ - IconButton( - icon: Icon( - Icons.sd_card, - semanticLabel: 'Select storage'.i18n, - ), - onPressed: () { - String path = ''; - //Chose storage - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Select storage'.i18n), - content: FutureBuilder( - future: PathProviderEx.getStorageInfo(), - builder: (context, snapshot) { - if (snapshot.hasError) return ErrorScreen(); - if (!snapshot.hasData) - return Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator() - ], - ), - ); - return Column( - mainAxisSize: MainAxisSize.min, - children: List.generate( - snapshot.data.length, (int i) { - StorageInfo si = snapshot.data[i]; - return ListTile( - title: Text(si.rootDir), - leading: Icon(Icons.sd_card), - trailing: Text(filesize(si.availableBytes)), - onTap: () { - setState(() { - _path = si.appFilesDir; - //Android 5+ blocks sd card, so this prevents going outside - //app data dir, until permission request fix. - _root = si.rootDir; - if (i != 0) _root = si.appFilesDir; - }); - Navigator.of(context).pop(); - }, - ); - })); - }, - ), - ); - }); - }) - ], - ), - floatingActionButton: FloatingActionButton( - child: Icon(Icons.done), - onPressed: () { - //When folder confirmed - if (widget.onSelect != null) widget.onSelect(_path); - Navigator.of(context).pop(); - }, - ), - body: FutureBuilder( - future: Directory(_path).list().toList(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - //On error go to last good path - if (snapshot.hasError) - Future.delayed(Duration(milliseconds: 50), () { - if (_previous == null) { - _resetPath(); - return; - } - setState(() => _path = _previous); - }); - if (!snapshot.hasData) - return Center( - child: CircularProgressIndicator(), - ); +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: FreezerAppBar( +// 'Pick-a-Path'.i18n, +// actions: [ +// IconButton( +// icon: Icon( +// Icons.sd_card, +// semanticLabel: 'Select storage'.i18n, +// ), +// onPressed: () { +// String path = ''; +// //Chose storage +// showDialog( +// context: context, +// builder: (context) { +// return AlertDialog( +// title: Text('Select storage'.i18n), +// content: FutureBuilder( +// future: PathProviderEx.getStorageInfo(), +// builder: (context, snapshot) { +// if (snapshot.hasError) return ErrorScreen(); +// if (!snapshot.hasData) +// return Padding( +// padding: EdgeInsets.symmetric(vertical: 8.0), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// CircularProgressIndicator() +// ], +// ), +// ); +// return Column( +// mainAxisSize: MainAxisSize.min, +// children: List.generate( +// snapshot.data.length, (int i) { +// StorageInfo si = snapshot.data[i]; +// return ListTile( +// title: Text(si.rootDir), +// leading: Icon(Icons.sd_card), +// trailing: Text(filesize(si.availableBytes)), +// onTap: () { +// setState(() { +// _path = si.appFilesDir; +// //Android 5+ blocks sd card, so this prevents going outside +// //app data dir, until permission request fix. +// _root = si.rootDir; +// if (i != 0) _root = si.appFilesDir; +// }); +// Navigator.of(context).pop(); +// }, +// ); +// })); +// }, +// ), +// ); +// }); +// }) +// ], +// ), +// floatingActionButton: FloatingActionButton( +// child: Icon(Icons.done), +// onPressed: () { +// //When folder confirmed +// if (widget.onSelect != null) widget.onSelect(_path); +// Navigator.of(context).pop(); +// }, +// ), +// body: FutureBuilder( +// future: Directory(_path).list().toList(), +// builder: (BuildContext context, AsyncSnapshot snapshot) { +// //On error go to last good path +// if (snapshot.hasError) +// Future.delayed(Duration(milliseconds: 50), () { +// if (_previous == null) { +// _resetPath(); +// return; +// } +// setState(() => _path = _previous); +// }); +// if (!snapshot.hasData) +// return Center( +// child: CircularProgressIndicator(), +// ); - List data = snapshot.data; - return ListView( - children: [ - ListTile( - title: Text(_path), - ), - ListTile( - title: Text('Go up'.i18n), - leading: Icon(Icons.arrow_upward), - onTap: () { - setState(() { - if (_root == _path) { - Fluttertoast.showToast( - msg: 'Permission denied'.i18n, - gravity: ToastGravity.BOTTOM); - return; - } - _previous = _path; - _path = Directory(_path).parent.path; - }); - }, - ), - ...List.generate(data.length, (i) { - FileSystemEntity f = data[i]; - if (f is Directory) { - return ListTile( - title: Text(f.path.split('/').last), - leading: Icon(Icons.folder), - onTap: () { - setState(() { - _previous = _path; - _path = f.path; - }); - }, - ); - } - return Container( - height: 0, - width: 0, - ); - }) - ], - ); - }, - ), - ); - } -} +// List data = snapshot.data; +// return ListView( +// children: [ +// ListTile( +// title: Text(_path), +// ), +// ListTile( +// title: Text('Go up'.i18n), +// leading: Icon(Icons.arrow_upward), +// onTap: () { +// setState(() { +// if (_root == _path) { +// Fluttertoast.showToast( +// msg: 'Permission denied'.i18n, +// gravity: ToastGravity.BOTTOM); +// return; +// } +// _previous = _path; +// _path = Directory(_path).parent.path; +// }); +// }, +// ), +// ...List.generate(data.length, (i) { +// FileSystemEntity f = data[i]; +// if (f is Directory) { +// return ListTile( +// title: Text(f.path.split('/').last), +// leading: Icon(Icons.folder), +// onTap: () { +// setState(() { +// _previous = _path; +// _path = f.path; +// }); +// }, +// ); +// } +// return Container( +// height: 0, +// width: 0, +// ); +// }) +// ], +// ); +// }, +// ), +// ); +// } +// } class CreditsScreen extends StatefulWidget { @override diff --git a/lib/ui/updater.dart b/lib/ui/updater.dart index 23fda34..3d24b7e 100644 --- a/lib/ui/updater.dart +++ b/lib/ui/updater.dart @@ -198,7 +198,7 @@ class FreezerVersions { //Fetch from website API static Future fetch() async { 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'); return FreezerVersions.fromJson(jsonDecode(response.body)); } diff --git a/pubspec.lock b/pubspec.lock index b08495c..c97ea0e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "24.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "2.1.0" args: dependency: transitive description: @@ -32,17 +32,17 @@ packages: audio_service: dependency: "direct main" description: - path: audio_service - relative: true - source: path - version: "0.15.1" + name: audio_service + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.1" audio_session: dependency: "direct main" description: name: audio_session url: "https://pub.dartlang.org" source: hosted - version: "0.0.11" + version: "0.1.6" boolean_selector: dependency: transitive description: @@ -56,42 +56,42 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "2.1.0" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.6" + version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.10" + version: "3.0.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "2.0.4" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.5" + version: "2.1.1" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.10" + version: "7.1.0" built_collection: dependency: transitive description: @@ -112,7 +112,21 @@ packages: name: cached_network_image url: "https://pub.dartlang.org" 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: dependency: transitive description: @@ -133,7 +147,7 @@ packages: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" cli_util: dependency: transitive description: @@ -154,7 +168,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.7.0" + version: "4.1.0" collection: dependency: "direct main" description: @@ -196,7 +210,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.1" cookie_jar: dependency: "direct main" description: @@ -217,7 +231,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.1" csslib: dependency: transitive description: @@ -225,29 +239,20 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" + version: "2.0.3" dio: dependency: transitive description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "3.0.10" + version: "4.0.0" disk_space: dependency: "direct main" description: @@ -265,24 +270,12 @@ packages: equalizer: dependency: "direct main" description: - name: equalizer - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: HEAD + resolved-ref: "84c15ca304a8129a1cad5a6891059fb411f0fc55" + url: "https://github.com/gladson97/equalizer.git" + source: git 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: dependency: transitive description: @@ -336,14 +329,14 @@ packages: name: flutter_blurhash url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "0.6.0" flutter_cache_manager: dependency: "direct main" description: name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" + version: "3.1.2" flutter_displaymode: dependency: "direct main" description: @@ -364,21 +357,21 @@ packages: name: flutter_isolate url: "https://pub.dartlang.org" source: hosted - version: "1.0.0+15" + version: "2.0.0" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "4.0.1+2" + version: "8.1.1+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0+1" + version: "4.0.1" flutter_localizations: dependency: "direct main" description: flutter @@ -422,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted 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: dependency: transitive description: @@ -442,14 +442,14 @@ packages: name: google_fonts url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "2.1.0" graphs: dependency: transitive description: name: graphs url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "2.0.0" html: dependency: "direct main" description: @@ -463,21 +463,21 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.3" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.1" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" i18n_extension: dependency: "direct main" description: @@ -505,7 +505,7 @@ packages: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "1.0.3" js: dependency: transitive description: @@ -519,35 +519,37 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.1" + version: "4.1.0" json_serializable: dependency: "direct dev" description: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "3.5.1" + version: "5.0.0" just_audio: dependency: "direct main" description: - path: "just_audio/just_audio" - relative: true - source: path - version: "0.6.5" + path: just_audio + ref: dev + resolved-ref: "7ac783939a758be2799faefc8877c34a84fe1554" + url: "https://github.com/ryanheise/just_audio.git" + source: git + version: "0.9.7" just_audio_platform_interface: dependency: transitive description: - path: "just_audio/just_audio_platform_interface" - relative: true - source: path - version: "2.0.0" + name: just_audio_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" just_audio_web: dependency: transitive description: - path: "just_audio/just_audio_web" - relative: true - source: path - version: "0.2.1" + name: just_audio_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" logging: dependency: transitive description: @@ -603,14 +605,14 @@ packages: name: oauth2 url: "https://pub.dartlang.org" source: hosted - version: "1.6.3" + version: "2.0.0" octo_image: dependency: transitive description: name: octo_image url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "1.0.0+1" open_file: dependency: "direct main" description: @@ -624,7 +626,7 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" package_info: dependency: "direct main" description: @@ -652,42 +654,35 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.28" - path_provider_ex: - dependency: "direct main" - description: - name: path_provider_ex - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" + version: "2.0.2" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "2.0.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+8" + version: "2.0.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.5" + version: "2.0.3" pedantic: dependency: transitive description: @@ -701,21 +696,21 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "5.1.0+2" + version: "8.1.4+2" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.6.1" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "4.2.0" photo_view: dependency: "direct main" description: @@ -736,7 +731,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.1" pool: dependency: transitive description: @@ -764,7 +759,7 @@ packages: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.8" + version: "1.0.0" quick_actions: dependency: "direct main" description: @@ -772,13 +767,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.5.0+1" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.5" random_string: dependency: "direct main" description: @@ -792,14 +780,16 @@ packages: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.24.1" + version: "0.27.1" scrobblenaut: dependency: "direct main" description: - name: scrobblenaut - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" + path: "." + ref: main + resolved-ref: a138aa57796cd1c1b3359d461b49515e58948baa + url: "https://github.com/furgoose/Scrobblenaut.git" + source: git + version: "3.0.0" share: dependency: "direct main" description: @@ -813,14 +803,14 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.2.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.4+1" + version: "1.0.1" sky_engine: dependency: transitive description: flutter @@ -832,7 +822,14 @@ packages: name: source_gen url: "https://pub.dartlang.org" 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: dependency: transitive description: @@ -846,7 +843,7 @@ packages: name: spotify url: "https://pub.dartlang.org" source: hosted - version: "0.5.1" + version: "0.6.0" sprintf: dependency: transitive description: @@ -860,14 +857,14 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.2+4" + version: "2.0.0+4" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.3+3" + version: "2.0.1" stack_trace: dependency: transitive description: @@ -902,7 +899,7 @@ packages: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+2" + version: "3.0.0" term_glyph: dependency: transitive description: @@ -923,14 +920,14 @@ packages: name: timezone url: "https://pub.dartlang.org" source: hosted - version: "0.6.1" + version: "0.7.0" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+3" + version: "1.0.0" typed_data: dependency: transitive description: @@ -944,14 +941,21 @@ packages: name: uni_links url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" - universal_io: + version: "0.5.1" + uni_links_platform_interface: dependency: transitive description: - name: universal_io + name: uni_links_platform_interface url: "https://pub.dartlang.org" 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: dependency: "direct main" description: @@ -1000,7 +1004,7 @@ packages: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "2.2.2" + version: "3.0.4" vector_math: dependency: transitive description: @@ -1063,7 +1067,7 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.1.0" win32: dependency: transitive description: @@ -1077,14 +1081,14 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "4.5.1" + version: "5.2.0" yaml: dependency: transitive description: @@ -1092,13 +1096,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.0" - zone_local: - dependency: transitive - description: - name: zone_local - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2" sdks: dart: ">=2.13.0 <3.0.0" - flutter: ">=2.0.0" + flutter: ">=2.2.0" diff --git a/pubspec.yaml b/pubspec.yaml index d5d82cf..aee55ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,17 +27,16 @@ dependencies: flutter_localizations: sdk: flutter - spotify: ^0.5.1 + spotify: ^0.6.0 flutter_displaymode: ^0.3.2 - crypto: ^2.1.5 - http: ^0.12.2 + crypto: ^3.0.0 + http: ^0.13.0 cookie_jar: ^3.0.1 - json_annotation: ^3.0.1 - path_provider: ^1.6.28 + json_annotation: ^4.0.0 + path_provider: ^2.0.0 path: ^1.6.4 - sqflite: ^1.3.0+1 - ext_storage: ^1.0.3 - permission_handler: ^5.0.0+hotfix.6 + sqflite: ^2.0.0+3 + permission_handler: ^8.1.4+2 connectivity: ^3.0.6 intl: ^0.17.0 filesize: ^2.0.1 @@ -48,42 +47,43 @@ dependencies: country_pickers: ^2.0.0 package_info: ^2.0.2 move_to_background: ^1.0.1 - flutter_local_notifications: ^4.0.1+2 + flutter_local_notifications: ^8.1.1+1 collection: ^1.14.12 disk_space: ^0.1.1 - path_provider_ex: ^1.0.1 random_string: ^2.0.1 async: ^2.4.1 html: ^0.15.0 flutter_screenutil: ^5.0.0+2 marquee: ^2.2.0 - flutter_cache_manager: ^1.4.1 - cached_network_image: ^2.3.2+1 + flutter_cache_manager: ^3.0.0 + cached_network_image: ^3.1.0 i18n_extension: ^4.0.0 fluttericon: ^2.0.0 url_launcher: ^6.0.5 - uni_links: ^0.4.0 + uni_links: ^0.5.1 share: ^2.0.4 numberpicker: ^2.1.1 quick_actions: ^0.5.0+1 photo_view: ^0.12.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 version: ^2.0.0 wakelock: ^0.5.3+3 - google_fonts: ^1.1.2 - equalizer: ^0.0.2+2 - extended_math: ^0.0.29+1 - custom_navigator: - git: - url: https://github.com/kjawadDeveloper/Custom-navigator.git + google_fonts: ^2.1.0 + equalizer: + git: https://github.com/gladson97/equalizer.git - audio_session: ^0.0.9 - audio_service: - path: ./audio_service - just_audio: - path: ./just_audio/just_audio + audio_session: ^0.1.6 + audio_service: ^0.17.1 + just_audio: + git: + url: https://github.com/ryanheise/just_audio.git + ref: dev + path: just_audio/ # cupertino_icons: ^0.1.3 @@ -91,8 +91,8 @@ dev_dependencies: flutter_test: sdk: flutter - json_serializable: ^3.3.0 - build_runner: ^1.10.0 + json_serializable: ^5.0.0 + build_runner: ^2.1.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec