0.5.3 - Download fixes, shuffle fix, sorting in library

This commit is contained in:
exttex 2020-10-12 22:49:13 +02:00
parent 952cf0f508
commit 2f471268c6
18 changed files with 556 additions and 167 deletions

View File

@ -312,6 +312,20 @@ public class Deezer {
return original + ".mp3"; return original + ".mp3";
} }
public static String generateUserUploadedMP3Filename(String original, JSONObject privateJson) throws Exception {
//Remove unavailable tags
String[] ignored = {"%feats%", "%trackNumber%", "%0trackNumber%", "%year%", "%date%"};
for (String i : ignored) {
original = original.replaceAll(i, "");
}
//Basic tags
original = original.replaceAll("%title%", privateJson.getString("SNG_TITLE"));
original = original.replaceAll("%album%", privateJson.getString("ALB_TITLE"));
original = original.replaceAll("%artist%", privateJson.getString("ART_NAME"));
original = original.replaceAll("%artists%", privateJson.getString("ART_NAME"));
return original;
}
//Tag track with data from API //Tag track with data from API
public static void tagTrack(String path, JSONObject publicTrack, JSONObject publicAlbum, String cover, JSONObject lyricsData) throws Exception { public static void tagTrack(String path, JSONObject publicTrack, JSONObject publicAlbum, String cover, JSONObject lyricsData) throws Exception {
TagOptionSingleton.getInstance().setAndroid(true); TagOptionSingleton.getInstance().setAndroid(true);

View File

@ -51,6 +51,11 @@ public class Download {
} }
} }
//Negative TrackIDs = User uploaded MP3s.
public boolean isUserUploaded() {
return trackId.startsWith("-");
}
//Get download from SQLite cursor, HAS TO ALIGN //Get download from SQLite cursor, HAS TO ALIGN
static Download fromSQL(Cursor cursor) { static Download fromSQL(Cursor cursor) {
return new Download(cursor.getInt(0), cursor.getString(1), cursor.getInt(2) == 1, cursor.getInt(3), DownloadState.values()[cursor.getInt(4)], return new Download(cursor.getInt(0), cursor.getString(1), cursor.getInt(2) == 1, cursor.getInt(3), DownloadState.values()[cursor.getInt(4)],

View File

@ -274,6 +274,7 @@ public class DownloadService extends Service {
File outFile; File outFile;
JSONObject trackJson; JSONObject trackJson;
JSONObject albumJson; JSONObject albumJson;
JSONObject privateJson;
boolean stopDownload = false; boolean stopDownload = false;
DownloadThread(Download download) { DownloadThread(Download download) {
this.download = download; this.download = download;
@ -293,8 +294,13 @@ public class DownloadService extends Service {
//Fetch metadata //Fetch metadata
try { try {
JSONObject privateRaw = deezer.callGWAPI("song.getListData", "{\"sng_ids\": [" + download.trackId + "]}");
privateJson = privateRaw.getJSONObject("results").getJSONArray("data").getJSONObject(0);
//Don't fetch meta if user uploaded mp3
if (!download.isUserUploaded()) {
trackJson = Deezer.callPublicAPI("track", download.trackId); trackJson = Deezer.callPublicAPI("track", download.trackId);
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id"))); albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
}
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to fetch track and album metadata! " + e.toString(), download); logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
e.printStackTrace(); e.printStackTrace();
@ -305,7 +311,7 @@ public class DownloadService extends Service {
//ISRC Fallback //ISRC Fallback
try { try {
if (trackJson.has("available_countries") && trackJson.getJSONArray("available_countries").length() == 0) { if (!download.isUserUploaded() && trackJson.has("available_countries") && trackJson.getJSONArray("available_countries").length() == 0) {
logger.warn("ISRC Fallback!", download); logger.warn("ISRC Fallback!", download);
JSONObject newTrackJson = Deezer.callPublicAPI("track", "isrc:" + trackJson.getString("isrc")); JSONObject newTrackJson = Deezer.callPublicAPI("track", "isrc:" + trackJson.getString("isrc"));
//Same track check //Same track check
@ -349,7 +355,11 @@ public class DownloadService extends Service {
if (!download.priv) { if (!download.priv) {
//Check file //Check file
try { try {
if (download.isUserUploaded()) {
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, privateJson));
} else {
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality)); outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality));
}
parentDir = new File(outFile.getParent()); parentDir = new File(outFile.getParent());
} catch (Exception e) { } catch (Exception e) {
logger.error("Error generating track filename (" + download.path + "): " + e.toString(), download); logger.error("Error generating track filename (" + download.path + "): " + e.toString(), download);
@ -486,7 +496,8 @@ public class DownloadService extends Service {
} }
} }
if (!download.priv) { //Cover & Tags, ignore on user uploaded
if (!download.priv && !download.isUserUploaded()) {
//Download cover for each track //Download cover for each track
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg"); File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
@ -520,19 +531,19 @@ public class DownloadService extends Service {
JSONObject lyricsData = null; JSONObject lyricsData = null;
//Lyrics //Lyrics
if (settings.downloadLyrics) {
try { try {
lyricsData = deezer.callGWAPI("song.getLyrics", "{\"sng_id\": " + download.trackId + "}"); lyricsData = deezer.callGWAPI("song.getLyrics", "{\"sng_id\": " + download.trackId + "}");
if (settings.downloadLyrics) {
String lrcData = Deezer.generateLRC(lyricsData, trackJson); String lrcData = Deezer.generateLRC(lyricsData, trackJson);
//Create file //Create file
String lrcFilename = outFile.getPath().substring(0, outFile.getPath().lastIndexOf(".")+1) + "lrc"; String lrcFilename = outFile.getPath().substring(0, outFile.getPath().lastIndexOf(".")+1) + "lrc";
FileOutputStream fileOutputStream = new FileOutputStream(lrcFilename); FileOutputStream fileOutputStream = new FileOutputStream(lrcFilename);
fileOutputStream.write(lrcData.getBytes()); fileOutputStream.write(lrcData.getBytes());
fileOutputStream.close(); fileOutputStream.close();
}
} catch (Exception e) { } catch (Exception e) {
logger.warn("Error downloading lyrics! " + e.toString(), download); logger.warn("Error downloading lyrics! " + e.toString(), download);
} }
}
//Tag //Tag

View File

@ -1,6 +1,7 @@
import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart'; import 'package:freezer/api/definitions.dart';
import 'package:freezer/ui/details_screens.dart'; import 'package:freezer/ui/details_screens.dart';
import 'package:freezer/ui/library.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@ -30,6 +31,14 @@ class Cache {
@JsonKey(defaultValue: {}) @JsonKey(defaultValue: {})
Map<String, SortType> playlistSort; Map<String, SortType> playlistSort;
//Sort
@JsonKey(defaultValue: AlbumSortType.DEFAULT)
AlbumSortType albumSort;
@JsonKey(defaultValue: ArtistSortType.DEFAULT)
ArtistSortType artistSort;
@JsonKey(defaultValue: PlaylistSortType.DEFAULT)
PlaylistSortType libraryPlaylistSort;
Cache({this.libraryTracks}); Cache({this.libraryTracks});

View File

@ -19,7 +19,16 @@ Cache _$CacheFromJson(Map<String, dynamic> json) {
..playlistSort = (json['playlistSort'] as Map<String, dynamic>)?.map( ..playlistSort = (json['playlistSort'] as Map<String, dynamic>)?.map(
(k, e) => MapEntry(k, _$enumDecodeNullable(_$SortTypeEnumMap, e)), (k, e) => MapEntry(k, _$enumDecodeNullable(_$SortTypeEnumMap, e)),
) ?? ) ??
{}; {}
..albumSort =
_$enumDecodeNullable(_$AlbumSortTypeEnumMap, json['albumSort']) ??
AlbumSortType.DEFAULT
..artistSort =
_$enumDecodeNullable(_$ArtistSortTypeEnumMap, json['artistSort']) ??
ArtistSortType.DEFAULT
..libraryPlaylistSort = _$enumDecodeNullable(
_$PlaylistSortTypeEnumMap, json['libraryPlaylistSort']) ??
PlaylistSortType.DEFAULT;
} }
Map<String, dynamic> _$CacheToJson(Cache instance) => <String, dynamic>{ Map<String, dynamic> _$CacheToJson(Cache instance) => <String, dynamic>{
@ -27,6 +36,10 @@ Map<String, dynamic> _$CacheToJson(Cache instance) => <String, dynamic>{
'history': instance.history, 'history': instance.history,
'playlistSort': instance.playlistSort 'playlistSort': instance.playlistSort
?.map((k, e) => MapEntry(k, _$SortTypeEnumMap[e])), ?.map((k, e) => MapEntry(k, _$SortTypeEnumMap[e])),
'albumSort': _$AlbumSortTypeEnumMap[instance.albumSort],
'artistSort': _$ArtistSortTypeEnumMap[instance.artistSort],
'libraryPlaylistSort':
_$PlaylistSortTypeEnumMap[instance.libraryPlaylistSort],
}; };
T _$enumDecode<T>( T _$enumDecode<T>(
@ -67,3 +80,25 @@ const _$SortTypeEnumMap = {
SortType.ALPHABETIC: 'ALPHABETIC', SortType.ALPHABETIC: 'ALPHABETIC',
SortType.ARTIST: 'ARTIST', SortType.ARTIST: 'ARTIST',
}; };
const _$AlbumSortTypeEnumMap = {
AlbumSortType.DEFAULT: 'DEFAULT',
AlbumSortType.REVERSE: 'REVERSE',
AlbumSortType.ALPHABETIC: 'ALPHABETIC',
AlbumSortType.ARTIST: 'ARTIST',
};
const _$ArtistSortTypeEnumMap = {
ArtistSortType.DEFAULT: 'DEFAULT',
ArtistSortType.REVERSE: 'REVERSE',
ArtistSortType.POPULARITY: 'POPULARITY',
ArtistSortType.ALPHABETIC: 'ALPHABETIC',
};
const _$PlaylistSortTypeEnumMap = {
PlaylistSortType.DEFAULT: 'DEFAULT',
PlaylistSortType.REVERSE: 'REVERSE',
PlaylistSortType.ALPHABETIC: 'ALPHABETIC',
PlaylistSortType.USER: 'USER',
PlaylistSortType.TRACK_COUNT: 'TRACK_COUNT',
};

View File

@ -246,6 +246,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Queue //Queue
List<MediaItem> _queue = <MediaItem>[]; List<MediaItem> _queue = <MediaItem>[];
List<int> _shuffleHistory = [];
int _queueIndex = 0; int _queueIndex = 0;
ConcatenatingAudioSource _audioSource; ConcatenatingAudioSource _audioSource;
@ -363,13 +364,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
Future<void> onSkipToNext() async { Future<void> onSkipToNext() async {
//Shuffle //Shuffle
if (_player.shuffleModeEnabled??false) { if (_player.shuffleModeEnabled??false) {
int newIndex = Random().nextInt(_queue.length)-1; int newIndex = Random().nextInt(_queue.length-1);
//Update state //Update state
_skipState = newIndex > _queueIndex _skipState = newIndex > _queueIndex
? AudioProcessingState.skippingToNext ? AudioProcessingState.skippingToNext
: AudioProcessingState.skippingToPrevious; : AudioProcessingState.skippingToPrevious;
if (_shuffleHistory.length == 0) _shuffleHistory.add(_queueIndex);
_queueIndex = newIndex; _queueIndex = newIndex;
_shuffleHistory.add(newIndex);
await _player.seek(Duration.zero, index: _queueIndex); await _player.seek(Duration.zero, index: _queueIndex);
_skipState = null; _skipState = null;
return; return;
@ -385,9 +388,24 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override @override
Future<void> onSkipToPrevious() async { Future<void> onSkipToPrevious() async {
if (_queueIndex == 0) return; if (_queueIndex == 0 && !(_player.shuffleModeEnabled??false)) return;
//Update buffering state //Update buffering state
_skipState = AudioProcessingState.skippingToPrevious; _skipState = AudioProcessingState.skippingToPrevious;
//Shuffle history
if ((_player.shuffleModeEnabled??false) && _shuffleHistory.length > 1) {
_shuffleHistory.removeLast();
if (_shuffleHistory.last < _queue.length) {
_queueIndex = _shuffleHistory.last;
await _player.seek(Duration.zero, index: _queueIndex);
_skipState = null;
return;
} else {
_shuffleHistory = [];
}
}
//Normal skip to previous
_queueIndex--; _queueIndex--;
await _player.seekToPrevious(); await _player.seekToPrevious();
_skipState = null; _skipState = null;
@ -553,9 +571,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
if (name == 'repeatType') { if (name == 'repeatType') {
_player.setLoopMode(LoopMode.values[args]); _player.setLoopMode(LoopMode.values[args]);
} }
if (name == 'saveQueue') await this._saveQueue(); if (name == 'saveQueue')
await this._saveQueue();
//Load queue after some initialization in frontend //Load queue after some initialization in frontend
if (name == 'load') await this._loadQueueFile(); if (name == 'load')
await this._loadQueueFile();
//Shuffle //Shuffle
if (name == 'shuffle') { if (name == 'shuffle') {
await _player.setShuffleModeEnabled(args); await _player.setShuffleModeEnabled(args);

View File

@ -208,7 +208,5 @@ const language_ar_ar = {
"User": "المستخدم", "User": "المستخدم",
"Track count": "عدد الاغاني", "Track count": "عدد الاغاني",
"If you want to use custom directory naming - use '/' as directory separator.": "إذا كنت تريد استخدام تسمية مخصصة، استخدم '/' كفاصل بين المسار." "If you want to use custom directory naming - use '/' as directory separator.": "إذا كنت تريد استخدام تسمية مخصصة، استخدم '/' كفاصل بين المسار."
} }
}; };

View File

@ -47,7 +47,8 @@ const language_el_gr = {
"Currently supporting only Spotify, with 100 tracks limit": "Currently supporting only Spotify, with 100 tracks limit":
"Αυτήν τη στιγμή υποστηρίζεται μόνο το Spotify, με όριο 100 κομματιών", "Αυτήν τη στιγμή υποστηρίζεται μόνο το Spotify, με όριο 100 κομματιών",
"Due to API limitations": "Λόγω περιορισμών API", "Due to API limitations": "Λόγω περιορισμών API",
"Enter your playlist link below": "Εισαγάγετε τον σύνδεσμο λίστας αναπαραγωγής παρακάτω", "Enter your playlist link below":
"Εισαγάγετε τον σύνδεσμο λίστας αναπαραγωγής παρακάτω",
"Error loading URL!": "Σφάλμα φόρτωσης διεύθυνσης URL!", "Error loading URL!": "Σφάλμα φόρτωσης διεύθυνσης URL!",
"Convert": "Μετατροπή", "Convert": "Μετατροπή",
"Download only": "Μόνο λήψη", "Download only": "Μόνο λήψη",
@ -58,8 +59,9 @@ const language_el_gr = {
"Artists": "Καλλιτέχνες", "Artists": "Καλλιτέχνες",
"Playlists": "Λίστες αναπαραγωγής", "Playlists": "Λίστες αναπαραγωγής",
"Import": "Εισαγωγή", "Import": "Εισαγωγή",
"Import playlists from Spotify": "Εισαγωγή λιστών αναπαραγωγής από το Spotify", "Import playlists from Spotify":
"Statistics": "Στατιστική", "Εισαγωγή λιστών αναπαραγωγής από το Spotify",
"Statistics": "Στατιστικά",
"Offline tracks": "Κομμάτια εκτός σύνδεσης", "Offline tracks": "Κομμάτια εκτός σύνδεσης",
"Offline albums": "Album εκτός σύνδεσης", "Offline albums": "Album εκτός σύνδεσης",
"Offline playlists": "Λίστες αναπαραγωγής εκτός σύνδεσης", "Offline playlists": "Λίστες αναπαραγωγής εκτός σύνδεσης",
@ -104,7 +106,8 @@ const language_el_gr = {
"Remove album": "Κατάργηση album", "Remove album": "Κατάργηση album",
"Album removed": "Το album καταργήθηκε", "Album removed": "Το album καταργήθηκε",
"Remove from favorites": "Κατάργηση από τα αγαπημένα", "Remove from favorites": "Κατάργηση από τα αγαπημένα",
"Artist removed from library": "Ο καλλιτέχνης καταργήθηκε από τη βιβλιοθήκη", "Artist removed from library":
"Ο καλλιτέχνης καταργήθηκε από τη βιβλιοθήκη",
"Add to favorites": "Προσθήκη στα αγαπημένα", "Add to favorites": "Προσθήκη στα αγαπημένα",
"Remove from library": "Κατάργηση από τη βιβλιοθήκη", "Remove from library": "Κατάργηση από τη βιβλιοθήκη",
"Add playlist to library": "Προσθήκη λίστας αναπαραγωγής στη βιβλιοθήκη", "Add playlist to library": "Προσθήκη λίστας αναπαραγωγής στη βιβλιοθήκη",
@ -126,7 +129,7 @@ const language_el_gr = {
"Show all tracks": "Εμφάνιση όλων των κομματιών", "Show all tracks": "Εμφάνιση όλων των κομματιών",
"Show all playlists": "Εμφάνιση όλων των λιστών αναπαραγωγής", "Show all playlists": "Εμφάνιση όλων των λιστών αναπαραγωγής",
"Settings": "Ρυθμίσεις", "Settings": "Ρυθμίσεις",
"General": "Γενικός", "General": "Γενικά",
"Appearance": "Εμφάνιση", "Appearance": "Εμφάνιση",
"Quality": "Ποιότητα", "Quality": "Ποιότητα",
"Deezer": "Deezer", "Deezer": "Deezer",
@ -139,7 +142,8 @@ const language_el_gr = {
"Deezer (Dark)": "Deezer (Σκούρο)", "Deezer (Dark)": "Deezer (Σκούρο)",
"Primary color": "Πρωτεύον χρώμα", "Primary color": "Πρωτεύον χρώμα",
"Selected color": "Επιλεγμένο χρώμα", "Selected color": "Επιλεγμένο χρώμα",
"Use album art primary color": "Χρησιμοποιήστε το πρωτεύον χρώμα του εξώφυλλου του album", "Use album art primary color":
"Χρησιμοποιήστε το πρωτεύον χρώμα του εξώφυλλου του album",
"Warning: might be buggy": "Προειδοποίηση: μπορεί να μη λειτουργεί σωστά", "Warning: might be buggy": "Προειδοποίηση: μπορεί να μη λειτουργεί σωστά",
"Mobile streaming": "Ροή μέσω δεδομένων κινητού δικτύου", "Mobile streaming": "Ροή μέσω δεδομένων κινητού δικτύου",
"Wifi streaming": "Ροή μέσω WIFI", "Wifi streaming": "Ροή μέσω WIFI",
@ -149,7 +153,8 @@ const language_el_gr = {
"Όχι γλώσσα εφαρμογής, χρησιμοποιείται στις κεφαλίδες. Τρέχουσα", "Όχι γλώσσα εφαρμογής, χρησιμοποιείται στις κεφαλίδες. Τρέχουσα",
"Select language": "Επιλογή γλώσσας", "Select language": "Επιλογή γλώσσας",
"Content country": "Χώρα περιεχομένου", "Content country": "Χώρα περιεχομένου",
"Country used in headers. Now": "Χώρα που χρησιμοποιείται στις κεφαλίδες. Τρέχουσα", "Country used in headers. Now":
"Χώρα που χρησιμοποιείται στις κεφαλίδες. Τρέχουσα",
"Log tracks": "Αρχεία καταγραφής", "Log tracks": "Αρχεία καταγραφής",
"Send track listen logs to Deezer, enable it for features like Flow to work properly": "Send track listen logs to Deezer, enable it for features like Flow to work properly":
"Αποστολή αρχείων καταγραφής ακρόασης στο Deezer, ενεργοποιήστε το για ορθή λειτουργία υπηρεσιών όπως το Flow", "Αποστολή αρχείων καταγραφής ακρόασης στο Deezer, ενεργοποιήστε το για ορθή λειτουργία υπηρεσιών όπως το Flow",
@ -187,7 +192,53 @@ const language_el_gr = {
"Importing...": "Εισαγωγή...", "Importing...": "Εισαγωγή...",
"Radio": "Ραδιόφωνο", "Radio": "Ραδιόφωνο",
"Flow": "Flow", "Flow": "Flow",
"Track is not available on Deezer!": "Το κομμάτι δεν είναι διαθέσιμο στο Deezer!", "Track is not available on Deezer!":
"Failed to download track! Please restart.": "Αποτυχία λήψης κομματιού! Κάντε επανεκκίνηση. " "Το κομμάτι δεν είναι διαθέσιμο στο Deezer!",
"Failed to download track! Please restart.":
"Αποτυχία λήψης κομματιού! Κάντε επανεκκίνηση. ",
//0.5.0 Strings:
"Storage permission denied!": "Η άδεια χώρου αποθήκευσης απορρίφθηκε!",
"Failed": "Απέτυχαν",
"Queued": "Σε ουρά",
//Updated in 0.5.1 - used in context of download:
"External": "Χώρος αποθήκευσης",
//0.5.0
"Restart failed downloads": "Επανεκκίνηση αποτυχημένων λήψεων",
"Clear failed": "Εκκαθάριση αποτυχημένων",
"Download Settings": "Ρυθμίσεις Λήψεων",
"Create folder for playlist": "Δημιουργία φακέλου για λίστα αναπαραγωγής",
"Download .LRC lyrics": "Λήψη στίχων .LRC",
"Proxy": "Μεσολαβητής",
"Not set": "Δεν ρυθμίστηκε",
"Search or paste URL": "Αναζήτηση ή επικόλληση διεύθυνσης URL",
"History": "Ιστορικό",
//Updated 0.5.1
"Download threads": "Ταυτόχρονες λήψεις",
//0.5.0
"Lyrics unavailable, empty or failed to load!":
"Οι στίχοι δεν είναι διαθέσιμοι, είναι άδειοι ή δεν φορτώθηκαν!",
"About": "Σχετικά",
"Telegram Channel": "Κανάλι Telegram ",
"To get latest releases": "Για να λάβετε τις τελευταίες κυκλοφορίες",
"Official chat": "Επίσημη συνομιλία",
"Telegram Group": "Ομάδα Telegram",
"Huge thanks to all the contributors! <3":
"Πολλά ευχαριστώ σε όλους τους συνεισφέροντες! <3",
"Edit playlist": "Edit playlist",
"Update": "Ενημέρωση",
"Playlist updated!": "Η λίστα αναπαραγωγής ενημερώθηκε!",
"Downloads added!": "Προστέθηκαν λήψεις!",
//0.5.1 Strings:
"Save cover file for every track": "Αποθήκευση εξώφυλλου για κάθε κομμάτι",
"Download Log": "Αρχείο καταγραφής λήψεων",
"Repository": "Repository",
"Source code, report issues there.":
"Πηγαίος κώδικας, αναφέρετε ζητήματα εκεί.",
//0.5.2 Strings:
"Use system theme": "Χρησιμοποίηση θέματος συστήματος",
"Light": "Φωτεινο"
} }
}; };

View File

@ -215,7 +215,8 @@ const language_es_es = {
"To get latest releases": "Para obtener los últimos lanzamientos", "To get latest releases": "Para obtener los últimos lanzamientos",
"Official chat": "Chat oficial", "Official chat": "Chat oficial",
"Telegram Group": "Grupo de Telegram", "Telegram Group": "Grupo de Telegram",
"Huge thanks to all the contributors! <3": "Muchas gracias a todos los contribuyentes! <3", "Huge thanks to all the contributors! <3":
"Muchas gracias a todos los contribuyentes contributors! <3",
"Edit playlist": "Editar lista de reproducción", "Edit playlist": "Editar lista de reproducción",
"Update": "Actualizar", "Update": "Actualizar",
"Playlist updated!": "Lista de reproducción actualizada!", "Playlist updated!": "Lista de reproducción actualizada!",

View File

@ -224,6 +224,9 @@ const language_ru_ru = {
"Save cover file for every track": "Обложки для каждого трека отдельным файлом", "Save cover file for every track": "Обложки для каждого трека отдельным файлом",
"Download Log": "Лог загрузок (технические данные)", "Download Log": "Лог загрузок (технические данные)",
"Repository": "Репозиторий", "Repository": "Репозиторий",
"Source code, report issues there.": "Исходный код, вопросы, предложения." "Source code, report issues there.": "Исходный код, вопросы, предложения.",
//0.5.2 Strings:
"Use system theme": "Использовать тему системы",
"Light": "Светлая"
} }
}; };

View File

@ -44,7 +44,7 @@ const language_tr_tr = {
"Please check your connection and try again later...": "Please check your connection and try again later...":
"Lütfen bağlantınızı kontrol edin ve daha sonra tekrar deneyin ...", "Lütfen bağlantınızı kontrol edin ve daha sonra tekrar deneyin ...",
"Show more": "Daha fazla göster", "Show more": "Daha fazla göster",
"Importer": "Importer", "Importer": "Aktar",
"Currently supporting only Spotify, with 100 tracks limit": "Currently supporting only Spotify, with 100 tracks limit":
"Şu anda 100 parça sınırıyla yalnızca Spotify'ı destekliyor", "Şu anda 100 parça sınırıyla yalnızca Spotify'ı destekliyor",
"Due to API limitations": "API sınırlamaları nedeniyle", "Due to API limitations": "API sınırlamaları nedeniyle",
@ -74,8 +74,8 @@ const language_tr_tr = {
"Çevrimdışı modda oynatma listeleri oluşturulamaz", "Çevrimdışı modda oynatma listeleri oluşturulamaz",
"Error": "Hata", "Error": "Hata",
"Error logging in! Please check your token and internet connection and try again.": "Error logging in! Please check your token and internet connection and try again.":
"Oturum açma hatası! Lütfen tokeninizi ve internet bağlantınızı kontrol edin ve tekrar deneyin.", "Oturum açılamadı! Lütfen tokeninizi ve internet bağlantınızı kontrol edin ve tekrar deneyin.",
"Dismiss": "Reddet", "Dismiss": "Kapat",
"Welcome to": "Hoşgeldiniz", "Welcome to": "Hoşgeldiniz",
"Please login using your Deezer account.": "Please login using your Deezer account.":
"Lütfen Deezer hesabınızı kullanarak giriş yapın.", "Lütfen Deezer hesabınızı kullanarak giriş yapın.",
@ -91,37 +91,37 @@ const language_tr_tr = {
"Bu uygulamayı kullanarak Deezer Hizmet Şartları'nı kabul etmiyorsunuz", "Bu uygulamayı kullanarak Deezer Hizmet Şartları'nı kabul etmiyorsunuz",
"Play next": "Sonrakini çal", "Play next": "Sonrakini çal",
"Add to queue": "Sıraya ekle", "Add to queue": "Sıraya ekle",
"Add track to favorites": "Favorilere parça ekle", "Add track to favorites": "Parçayı favorilere ekle",
"Add to playlist": "Oynatma listesine ekle", "Add to playlist": "Oynatma listesine ekle",
"Select playlist": "Oynatma listesi seçin", "Select playlist": "Oynatma listesi seçin",
"Track added to": "Parça şuraya eklendi", "Track added to": "Parça şuraya eklendi",
"Remove from playlist": "Oynatma listesinden kaldır", "Remove from playlist": "Oynatma listesinden kaldır",
"Track removed from": "Parça şuradan kaldırıldı", "Track removed from": "Parça şuradan kaldırıldı",
"Remove favorite": "Favoriyi kaldır", "Remove favorite": "Favorilerden kaldır",
"Track removed from library": "Parça kütüphaneden kaldırıldı", "Track removed from library": "Parça kütüphaneden kaldırıldı",
"Go to": "Git", "Go to": "Git",
"Make offline": "Çevrimdışı yap", "Make offline": "Çevrimdışı yap",
"Add to library": "Kütüphaneye ekle", "Add to library": "Kütüphaneye ekle",
"Remove album": "Albümü kaldır", "Remove album": "Albümü kaldır",
"Album removed": "Albüm kaldırıldı", "Album removed": "Albüm kaldırıldı",
"Remove from favorites": "Favorilerden çıkar", "Remove from favorites": "Favorilerden kaldır",
"Artist removed from library": "Sanatçı kütüphaneden kaldırıldı", "Artist removed from library": "Sanatçı kütüphaneden kaldırıldı",
"Add to favorites": "Favorilere ekle", "Add to favorites": "Favorilere ekle",
"Remove from library": "Kütüphaneden kaldır", "Remove from library": "Kütüphaneden kaldır",
"Add playlist to library": "Oynatma listesini kütüphaneye ekleyin", "Add playlist to library": "Oynatma listesini kütüphaneye ekleyin",
"Added playlist to library": "Kütüphaneye oynatma listesi eklendi", "Added playlist to library": "Oynatma listesi kütüphaneye eklendi",
"Make playlist offline": "Oynatma listesini çevrimdışı yapın", "Make playlist offline": "Oynatma listesini çevrimdışı yapın",
"Download playlist": "Oynatma listesini indirin", "Download playlist": "Oynatma listesini indirin",
"Create playlist": "Oynatma listesi oluştur", "Create playlist": "Oynatma listesi oluştur",
"Title": "Başlık", "Title": "Başlık",
"Description": "ıklama", "Description": "ıklama",
"Private": "Özel", "Private": "Özel",
"Collaborative": "İşbirlikçi", "Collaborative": "Paylaşılan",
"Create": "Oluştur", "Create": "Oluştur",
"Playlist created!": "Oynatma listesi oluşturuldu!", "Playlist created!": "Oynatma listesi oluşturuldu!",
"Playing from:": "Şuradan oynatılıyor:", "Playing from:": "Şuradan oynatılıyor:",
"Queue": "Kuyruk", "Queue": "Kuyruk",
"Offline search": "Offline search", "Offline search": "Çevrimdışı arama",
"Search Results": "Arama Sonuçları", "Search Results": "Arama Sonuçları",
"No results!": "Sonuç yok!", "No results!": "Sonuç yok!",
"Show all tracks": "Tüm parçaları göster", "Show all tracks": "Tüm parçaları göster",
@ -134,24 +134,24 @@ const language_tr_tr = {
"Theme": "Tema", "Theme": "Tema",
"Currently": "Şu anda", "Currently": "Şu anda",
"Select theme": "Tema seçin", "Select theme": "Tema seçin",
"Light (default)": "Light (Varsayılan)", "Light (default)": "ık (Varsayılan)",
"Dark": "Dark", "Dark": "Koyu",
"Black (AMOLED)": "Black (AMOLED)", "Black (AMOLED)": "Siyah (AMOLED)",
"Deezer (Dark)": "Deezer (Dark)", "Deezer (Dark)": "Deezer (Dark)",
"Primary color": "Ana renk", "Primary color": "Ana renk",
"Selected color": "Seçilen renk", "Selected color": "Seçilen renk",
"Use album art primary color": "Albüm resmi ana rengini kullan", "Use album art primary color": "Albüm resmini ana renk olarak kullan",
"Warning: might be buggy": "Uyarı: hatalı olabilir", "Warning: might be buggy": "Uyarı: hatalı olabilir",
"Mobile streaming": "Mobil akış", "Mobile streaming": "Mobil veri",
"Wifi streaming": "Wifi akışı", "Wifi streaming": "Wifi",
"External downloads": "Harici indirmeler", "External downloads": "Harici indirmeler",
"Content language": "İçerik dili", "Content language": "İçerik dili",
"Not app language, used in headers. Now": "Not app language, used in headers. Now":
"Not app language, used in headers. Now", "Uygulama dili değil, başlıklarda kullanılacak. Şuan",
"Select language": "Dil seçin", "Select language": "Dil seçin",
"Content country": "İçerik ülkesi", "Content country": "İçerik ülkesi",
"Country used in headers. Now": "Başlıklarda kullanılan ülke. Şimdi", "Country used in headers. Now": "Başlıklarda kullanılan ülke. Şuan",
"Log tracks": "Log tracks", "Log tracks": "Parça günlükleri",
"Send track listen logs to Deezer, enable it for features like Flow to work properly": "Send track listen logs to Deezer, enable it for features like Flow to work properly":
"Parça dinleme günlüklerini Deezer'a gönderin, Flow gibi özelliklerin düzgün çalışması için etkinleştirin", "Parça dinleme günlüklerini Deezer'a gönderin, Flow gibi özelliklerin düzgün çalışması için etkinleştirin",
"Offline mode": "Çevrimdışı mod", "Offline mode": "Çevrimdışı mod",
@ -159,25 +159,25 @@ const language_tr_tr = {
"Error logging in, check your internet connections.": "Error logging in, check your internet connections.":
"Giriş hatası, internet bağlantılarınızı kontrol edin.", "Giriş hatası, internet bağlantılarınızı kontrol edin.",
"Logging in...": "Giriş yapılıyor...", "Logging in...": "Giriş yapılıyor...",
"Download path": "İndirme yolu", "Download path": "İndirme konumu",
"Downloads naming": "İndirilenler adlandırma", "Downloads naming": "İndirilenleri adlandır",
"Downloaded tracks filename": "İndirilen parçaların dosya adı", "Downloaded tracks filename": "İndirilen parçaların dosya adı",
"Valid variables are": "Geçerli değişkenler", "Valid variables are": "Geçerli değişkenler",
"Reset": "Sıfırla", "Reset": "Sıfırla",
"Clear": "Temizle", "Clear": "Temizle",
"Create folders for artist": "Sanatçı için klasörler oluşturun", "Create folders for artist": "Sanatçılar için klasörler oluşturun",
"Create folders for albums": "Albümler için klasörler oluşturun", "Create folders for albums": "Albümler için klasörler oluşturun",
"Separate albums by discs": "Albümleri disklere göre ayırın", "Separate albums by discs": "Albümleri disklere göre ayırın",
"Overwrite already downloaded files": "Zaten indirilmiş dosyaların üzerine yaz", "Overwrite already downloaded files": "İndirilmiş dosyaların üzerine yaz",
"Copy ARL": "ARL kopyala", "Copy ARL": "ARL kopyala",
"Copy userToken/ARL Cookie for use in other apps.": "Copy userToken/ARL Cookie for use in other apps.":
"Diğer uygulamalarda kullanmak için userToken / ARL Cookie'yi kopyalayın.", "Diğer uygulamalarda kullanmak için userToken / ARL Cookie'yi kopyalayın.",
"Copied": "Kopyalandı", "Copied": "Kopyalandı",
"Log out": "Çıkış yap", "Log out": "Çıkış yap",
"Due to plugin incompatibility, login using browser is unavailable without restart.": "Due to plugin incompatibility, login using browser is unavailable without restart.":
"Eklenti uyumsuzluğu nedeniyle, yeniden başlatmadan tarayıcı kullanarak oturum açılamaz.", "Eklenti uyumsuzluğu nedeniyle, yeniden başlatmadan tarayıcı kullanılarak oturum açılamaz.",
"(ARL ONLY) Continue": "(SADECE ARL) Devam et", "(ARL ONLY) Continue": "(SADECE ARL) Devam et",
"Log out & Exit": "Çıkış yap & Çık", "Log out & Exit": "Çıkış yap & Kapat",
"Pick-a-Path": "Konum seç", "Pick-a-Path": "Konum seç",
"Select storage": "Depolama seç", "Select storage": "Depolama seç",
"Go up": "Yukarı git", "Go up": "Yukarı git",
@ -196,7 +196,7 @@ const language_tr_tr = {
"Failed": "Başarısız", "Failed": "Başarısız",
"Queued": "Sıraya alındı", "Queued": "Sıraya alındı",
//Updated in 0.5.1 - used in context of download: //Updated in 0.5.1 - used in context of download:
"External": "Storage", "External": "Depolama",
//0.5.0 //0.5.0
"Restart failed downloads": "Başarısız indirmeleri yeniden başlatın", "Restart failed downloads": "Başarısız indirmeleri yeniden başlatın",
"Clear failed": "Silinemedi", "Clear failed": "Silinemedi",
@ -213,7 +213,7 @@ const language_tr_tr = {
"Lyrics unavailable, empty or failed to load!": "Sözler mevcut değil, boş veya yüklenemedi!", "Lyrics unavailable, empty or failed to load!": "Sözler mevcut değil, boş veya yüklenemedi!",
"About": "Hakkında", "About": "Hakkında",
"Telegram Channel": "Telegram Kanalı", "Telegram Channel": "Telegram Kanalı",
"To get latest releases": "En son sürümleri almak için", "To get latest releases": "En son sürümleri indirmek için",
"Official chat": "Resmi sohbet", "Official chat": "Resmi sohbet",
"Telegram Group": "Telegram Grubu", "Telegram Group": "Telegram Grubu",
"Huge thanks to all the contributors! <3": "Katkıda bulunanlara çok teşekkürler! <3", "Huge thanks to all the contributors! <3": "Katkıda bulunanlara çok teşekkürler! <3",
@ -225,7 +225,11 @@ const language_tr_tr = {
//0.5.1 Strings: //0.5.1 Strings:
"Save cover file for every track": "Her parça için kapak dosyasını kaydedin", "Save cover file for every track": "Her parça için kapak dosyasını kaydedin",
"Download Log": "İndirme Kayıtları", "Download Log": "İndirme Kayıtları",
"Repository": "Depo", "Repository": "Repo",
"Source code, report issues there.": "Kaynak kodu, sorunları bildirin" "Source code, report issues there.": "Kaynak kodu, sorunları bildirin",
//0.5.2 Strings:
"Use system theme": "Sistem temasını kullan",
"Light": "ık"
} }
}; };

View File

@ -664,7 +664,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
tracks.sort((a, b) => a.title.compareTo(b.title)); tracks.sort((a, b) => a.title.compareTo(b.title));
return tracks; return tracks;
case SortType.ARTIST: case SortType.ARTIST:
tracks.sort((a, b) => a.artists[0].name.compareTo(b.artists[0].name)); tracks.sort((a, b) => a.artists[0].name.toLowerCase().compareTo(b.artists[0].name.toLowerCase()));
return tracks; return tracks;
case SortType.REVERSE: case SortType.REVERSE:
return tracks.reversed.toList(); return tracks.reversed.toList();

View File

@ -419,6 +419,13 @@ class _LibraryTracksState extends State<LibraryTracks> {
} }
enum AlbumSortType {
DEFAULT,
REVERSE,
ALPHABETIC,
ARTIST
}
class LibraryAlbums extends StatefulWidget { class LibraryAlbums extends StatefulWidget {
@override @override
_LibraryAlbumsState createState() => _LibraryAlbumsState(); _LibraryAlbumsState createState() => _LibraryAlbumsState();
@ -427,6 +434,25 @@ class LibraryAlbums extends StatefulWidget {
class _LibraryAlbumsState extends State<LibraryAlbums> { class _LibraryAlbumsState extends State<LibraryAlbums> {
List<Album> _albums; List<Album> _albums;
AlbumSortType _sort = AlbumSortType.DEFAULT;
List<Album> get _sorted {
List<Album> albums = List.from(_albums);
switch (_sort) {
case AlbumSortType.DEFAULT:
return _albums;
case AlbumSortType.REVERSE:
return _albums.reversed.toList();
case AlbumSortType.ALPHABETIC:
albums.sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
return albums;
case AlbumSortType.ARTIST:
albums.sort((a, b) => a.artists[0].name.toLowerCase().compareTo(b.artists[0].name.toLowerCase()));
return albums;
}
return albums;
}
Future _load() async { Future _load() async {
if (settings.offlineMode) return; if (settings.offlineMode) return;
@ -439,13 +465,44 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
@override @override
void initState() { void initState() {
_load(); _load();
_sort = cache.albumSort??AlbumSortType.DEFAULT;
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text('Albums'.i18n),), appBar: AppBar(
title: Text('Albums'.i18n),
actions: [
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
onSelected: (AlbumSortType s) async {
setState(() => _sort = s);
cache.albumSort = s;
await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<AlbumSortType>>[
PopupMenuItem(
value: AlbumSortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: AlbumSortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: AlbumSortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
PopupMenuItem(
value: AlbumSortType.ARTIST,
child: Text('Artist'.i18n),
),
],
),
],
),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
Container(height: 8.0,), Container(height: 8.0,),
@ -459,7 +516,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
if (_albums != null) if (_albums != null)
...List.generate(_albums.length, (int i) { ...List.generate(_albums.length, (int i) {
Album a = _albums[i]; Album a = _sorted[i];
return AlbumTile( return AlbumTile(
a, a,
onTap: () { onTap: () {
@ -523,27 +580,118 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
} }
} }
enum ArtistSortType {
DEFAULT,
REVERSE,
POPULARITY,
ALPHABETIC
}
class LibraryArtists extends StatefulWidget { class LibraryArtists extends StatefulWidget {
@override @override
_LibraryArtistsState createState() => _LibraryArtistsState(); _LibraryArtistsState createState() => _LibraryArtistsState();
} }
class _LibraryArtistsState extends State<LibraryArtists> { class _LibraryArtistsState extends State<LibraryArtists> {
List<Artist> _artists;
ArtistSortType _sort = ArtistSortType.DEFAULT;
bool _loading = true;
bool _error = false;
List<Artist> get _sorted {
List<Artist> artists = List.from(_artists);
switch (_sort) {
case ArtistSortType.DEFAULT:
return _artists;
case ArtistSortType.REVERSE:
return _artists.reversed.toList();
case ArtistSortType.POPULARITY:
artists.sort((a, b) => b.fans - a.fans);
return artists;
case ArtistSortType.ALPHABETIC:
artists.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
return artists;
}
}
//Load data
Future _load() async {
setState(() => _loading = true);
//Fetch
List<Artist> data;
try {
data = await deezerAPI.getArtists();
} catch (e) {}
//Update UI
setState(() {
if (data != null) {
_artists = data;
} else {
_error = true;
}
_loading = false;
});
}
@override
void initState() {
_sort = cache.artistSort;
_load();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text('Artists'.i18n),), appBar: AppBar(
body: FutureBuilder( title: Text('Artists'.i18n),
future: deezerAPI.getArtists(), actions: [
builder: (BuildContext context, AsyncSnapshot snapshot) { PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
if (snapshot.hasError) return ErrorScreen(); onSelected: (ArtistSortType s) async {
if (!snapshot.hasData) return Center(child: CircularProgressIndicator(),); setState(() => _sort = s);
cache.artistSort = s;
return ListView( await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<ArtistSortType>>[
PopupMenuItem(
value: ArtistSortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: ArtistSortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: ArtistSortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
PopupMenuItem(
value: ArtistSortType.POPULARITY,
child: Text('Popularity'.i18n),
),
],
)
],
),
body: ListView(
children: <Widget>[ children: <Widget>[
...List.generate(snapshot.data.length, (i) { if (_loading)
Artist a = snapshot.data[i]; Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [CircularProgressIndicator()],
),
),
if (_error)
Center(child: ErrorScreen()),
if (!_loading && !_error)
...List.generate(_artists.length, (i) {
Artist a = _sorted[i];
return ArtistHorizontalTile( return ArtistHorizontalTile(
a, a,
onTap: () { onTap: () {
@ -554,19 +702,26 @@ class _LibraryArtistsState extends State<LibraryArtists> {
onHold: () { onHold: () {
MenuSheet m = MenuSheet(context); MenuSheet m = MenuSheet(context);
m.defaultArtistMenu(a, onRemove: () { m.defaultArtistMenu(a, onRemove: () {
setState(() => {}); setState(() {
_artists.remove(a);
});
}); });
}, },
); );
}), }),
], ],
);
},
), ),
); );
} }
} }
enum PlaylistSortType {
DEFAULT,
REVERSE,
ALPHABETIC,
USER,
TRACK_COUNT
}
class LibraryPlaylists extends StatefulWidget { class LibraryPlaylists extends StatefulWidget {
@override @override
@ -576,6 +731,26 @@ class LibraryPlaylists extends StatefulWidget {
class _LibraryPlaylistsState extends State<LibraryPlaylists> { class _LibraryPlaylistsState extends State<LibraryPlaylists> {
List<Playlist> _playlists; List<Playlist> _playlists;
PlaylistSortType _sort = PlaylistSortType.DEFAULT;
List<Playlist> get _sorted {
List<Playlist> playlists = List.from(_playlists);
switch (_sort) {
case PlaylistSortType.DEFAULT:
return _playlists;
case PlaylistSortType.REVERSE:
return _playlists.reversed.toList();
case PlaylistSortType.USER:
playlists.sort((a, b) => (a.user.name??deezerAPI.userName).toLowerCase().compareTo((b.user.name??deezerAPI.userName).toLowerCase()));
return playlists;
case PlaylistSortType.TRACK_COUNT:
playlists.sort((a, b) => b.trackCount - a.trackCount);
return playlists;
case PlaylistSortType.ALPHABETIC:
playlists.sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
return playlists;
}
}
Future _load() async { Future _load() async {
if (!settings.offlineMode) { if (!settings.offlineMode) {
@ -588,6 +763,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
@override @override
void initState() { void initState() {
_sort = cache.libraryPlaylistSort;
_load(); _load();
super.initState(); super.initState();
} }
@ -606,7 +782,41 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text('Playlists'.i18n),), appBar: AppBar(
title: Text('Playlists'.i18n),
actions: [
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
onSelected: (PlaylistSortType s) async {
setState(() => _sort = s);
cache.libraryPlaylistSort = s;
await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<PlaylistSortType>>[
PopupMenuItem(
value: PlaylistSortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.USER,
child: Text('User'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.TRACK_COUNT,
child: Text('Track count'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
],
)
],
),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
@ -652,7 +862,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
if (_playlists != null) if (_playlists != null)
...List.generate(_playlists.length, (int i) { ...List.generate(_playlists.length, (int i) {
Playlist p = _playlists[i]; Playlist p = _sorted[i];
return PlaylistTile( return PlaylistTile(
p, p,
onTap: () => Navigator.of(context).push(MaterialPageRoute( onTap: () => Navigator.of(context).push(MaterialPageRoute(

View File

@ -90,15 +90,19 @@ class _LoginWidgetState extends State<LoginWidget> {
//Try logging in //Try logging in
try { try {
deezerAPI.arl = settings.arl; deezerAPI.arl = settings.arl;
bool resp = await deezerAPI.rawAuthorize(onError: (e) => _error = e.toString()); bool resp = await deezerAPI.rawAuthorize(onError: (e) => setState(() => _error = e.toString()));
if (resp == false) { //false, not null if (resp == false) { //false, not null
if (settings.arl.length != 192) {
if (_error == null) _error = '';
_error += 'Invalid ARL length!';
}
setState(() => settings.arl = null); setState(() => settings.arl = null);
errorDialog(); errorDialog();
} }
//On error show dialog and reset to null //On error show dialog and reset to null
} catch (e) { } catch (e) {
_error = e; _error = e.toString();
print('Login error: ' + e); print('Login error: ' + e.toString());
setState(() => settings.arl = null); setState(() => settings.arl = null);
errorDialog(); errorDialog();
} }

View File

@ -498,7 +498,7 @@ class _DeezerSettingsState extends State<DeezerSettings> {
ListTile( ListTile(
title: Text('Proxy'.i18n), title: Text('Proxy'.i18n),
leading: Icon(Icons.vpn_key), leading: Icon(Icons.vpn_key),
subtitle: Text(settings.proxyAddress??'Not set'), subtitle: Text(settings.proxyAddress??'Not set'.i18n),
onTap: () { onTap: () {
String _new; String _new;
showDialog( showDialog(
@ -606,7 +606,8 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
), ),
Container(height: 8.0), Container(height: 8.0),
Text( Text(
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%, %date%', 'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%, %date%\n\n' +
"If you want to use custom directory naming - use '/' as directory separator.".i18n,
style: TextStyle( style: TextStyle(
fontSize: 12.0, fontSize: 12.0,
), ),
@ -658,8 +659,8 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
), ),
Slider( Slider(
min: 1, min: 1,
max: 6, max: 16,
divisions: 5, divisions: 15,
value: _downloadThreads, value: _downloadThreads,
label: _downloadThreads.round().toString(), label: _downloadThreads.round().toString(),
onChanged: (double v) => setState(() => _downloadThreads = v), onChanged: (double v) => setState(() => _downloadThreads = v),
@ -1040,15 +1041,6 @@ class _CreditsScreenState extends State<CreditsScreen> {
String _version = ''; String _version = '';
//Title, Subtitle, URL
static final List<List<String>> credits = [
['exttex', 'Developer'],
['Bas Curtiz', 'Icon, logo, banner, design suggestions, tester'],
['Deemix', 'Better app <3', 'https://codeberg.org/RemixDev/deemix'],
['Tobs, Xandar Null, Francesco', 'Beta testers'],
['Annexhack', 'Android Auto help']
];
static final List<List<String>> translators = [ static final List<List<String>> translators = [
['Xandar Null', 'Arabic'], ['Xandar Null', 'Arabic'],
['Markus', 'German'], ['Markus', 'German'],
@ -1111,22 +1103,51 @@ class _CreditsScreenState extends State<CreditsScreen> {
), ),
ListTile( ListTile(
title: Text('Repository'.i18n), title: Text('Repository'.i18n),
subtitle: Text('Source code, report issues there.'), subtitle: Text('Source code, report issues there.'.i18n),
leading: Icon(Icons.code, color: Colors.green, size: 36.0), leading: Icon(Icons.code, color: Colors.green, size: 36.0),
onTap: () { onTap: () {
launch('https://notabug.org/exttex/freezer'); launch('https://notabug.org/exttex/freezer');
}, },
), ),
Divider(), Divider(),
...List.generate(credits.length, (i) => ListTile( ListTile(
title: Text(credits[i][0]), title: Text('exttex'),
subtitle: Text(credits[i][1]), subtitle: Text('Developer'),
),
ListTile(
title: Text('Bas Curtiz'),
subtitle: Text('Icon, logo, banner, design suggestions, tester'),
),
ListTile(
title: Text('Tobs'),
subtitle: Text('Alpha testers'),
),
ListTile(
title: Text('Deemix'),
subtitle: Text('Better app <3'),
onTap: () { onTap: () {
if (credits[i].length >= 3) { launch('https://codeberg.org/RemixDev/deemix');
launch(credits[i][2]);
}
}, },
)), ),
ListTile(
title: Text('Xandar Null'),
subtitle: Text('Tester, translations help'),
),
ListTile(
title: Text('Francesco'),
subtitle: Text('Tester'),
onTap: () {
setState(() {
settings.primaryColor = Color(0xff333333);
});
updateTheme();
settings.save();
},
),
ListTile(
title: Text('Annexhack'),
subtitle: Text('Android Auto help'),
),
Divider(), Divider(),
...List.generate(translators.length, (i) => ListTile( ...List.generate(translators.length, (i) => ListTile(
title: Text(translators[i][0]), title: Text(translators[i][0]),

View File

@ -205,7 +205,9 @@ class ArtistHorizontalTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
child: ListTile(
title: Text( title: Text(
artist.name, artist.name,
maxLines: 1, maxLines: 1,
@ -217,6 +219,7 @@ class ArtistHorizontalTile extends StatelessWidget {
onTap: onTap, onTap: onTap,
onLongPress: onHold, onLongPress: onHold,
trailing: trailing, trailing: trailing,
),
); );
} }
} }

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.5.2+1 version: 0.5.3+1
environment: environment:
sdk: ">=2.8.0 <3.0.0" sdk: ">=2.8.0 <3.0.0"