0.5.3 - Download fixes, shuffle fix, sorting in library
This commit is contained in:
parent
952cf0f508
commit
2f471268c6
|
@ -312,6 +312,20 @@ public class Deezer {
|
|||
return original + ".mp3";
|
||||
}
|
||||
|
||||
public static String generateUserUploadedMP3Filename(String original, JSONObject privateJson) throws Exception {
|
||||
//Remove unavailable tags
|
||||
String[] ignored = {"%feats%", "%trackNumber%", "%0trackNumber%", "%year%", "%date%"};
|
||||
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
|
||||
public static void tagTrack(String path, JSONObject publicTrack, JSONObject publicAlbum, String cover, JSONObject lyricsData) throws Exception {
|
||||
TagOptionSingleton.getInstance().setAndroid(true);
|
||||
|
|
|
@ -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
|
||||
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)],
|
||||
|
|
|
@ -274,6 +274,7 @@ public class DownloadService extends Service {
|
|||
File outFile;
|
||||
JSONObject trackJson;
|
||||
JSONObject albumJson;
|
||||
JSONObject privateJson;
|
||||
boolean stopDownload = false;
|
||||
DownloadThread(Download download) {
|
||||
this.download = download;
|
||||
|
@ -293,8 +294,13 @@ public class DownloadService extends Service {
|
|||
|
||||
//Fetch metadata
|
||||
try {
|
||||
trackJson = Deezer.callPublicAPI("track", download.trackId);
|
||||
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
||||
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);
|
||||
albumJson = Deezer.callPublicAPI("album", Integer.toString(trackJson.getJSONObject("album").getInt("id")));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to fetch track and album metadata! " + e.toString(), download);
|
||||
e.printStackTrace();
|
||||
|
@ -305,7 +311,7 @@ public class DownloadService extends Service {
|
|||
|
||||
//ISRC Fallback
|
||||
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);
|
||||
JSONObject newTrackJson = Deezer.callPublicAPI("track", "isrc:" + trackJson.getString("isrc"));
|
||||
//Same track check
|
||||
|
@ -349,7 +355,11 @@ public class DownloadService extends Service {
|
|||
if (!download.priv) {
|
||||
//Check file
|
||||
try {
|
||||
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality));
|
||||
if (download.isUserUploaded()) {
|
||||
outFile = new File(Deezer.generateUserUploadedMP3Filename(download.path, privateJson));
|
||||
} else {
|
||||
outFile = new File(Deezer.generateFilename(download.path, trackJson, albumJson, newQuality));
|
||||
}
|
||||
parentDir = new File(outFile.getParent());
|
||||
} catch (Exception e) {
|
||||
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
|
||||
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
|
||||
|
@ -520,18 +531,18 @@ public class DownloadService extends Service {
|
|||
|
||||
JSONObject lyricsData = null;
|
||||
//Lyrics
|
||||
if (settings.downloadLyrics) {
|
||||
try {
|
||||
lyricsData = deezer.callGWAPI("song.getLyrics", "{\"sng_id\": " + download.trackId + "}");
|
||||
try {
|
||||
lyricsData = deezer.callGWAPI("song.getLyrics", "{\"sng_id\": " + download.trackId + "}");
|
||||
if (settings.downloadLyrics) {
|
||||
String lrcData = Deezer.generateLRC(lyricsData, trackJson);
|
||||
//Create file
|
||||
String lrcFilename = outFile.getPath().substring(0, outFile.getPath().lastIndexOf(".")+1) + "lrc";
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(lrcFilename);
|
||||
fileOutputStream.write(lrcData.getBytes());
|
||||
fileOutputStream.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error downloading lyrics! " + e.toString(), download);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error downloading lyrics! " + e.toString(), download);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:freezer/api/deezer.dart';
|
||||
import 'package:freezer/api/definitions.dart';
|
||||
import 'package:freezer/ui/details_screens.dart';
|
||||
import 'package:freezer/ui/library.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
@ -30,6 +31,14 @@ class Cache {
|
|||
@JsonKey(defaultValue: {})
|
||||
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});
|
||||
|
||||
|
|
|
@ -19,7 +19,16 @@ Cache _$CacheFromJson(Map<String, dynamic> json) {
|
|||
..playlistSort = (json['playlistSort'] as Map<String, dynamic>)?.map(
|
||||
(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>{
|
||||
|
@ -27,6 +36,10 @@ Map<String, dynamic> _$CacheToJson(Cache instance) => <String, dynamic>{
|
|||
'history': instance.history,
|
||||
'playlistSort': instance.playlistSort
|
||||
?.map((k, e) => MapEntry(k, _$SortTypeEnumMap[e])),
|
||||
'albumSort': _$AlbumSortTypeEnumMap[instance.albumSort],
|
||||
'artistSort': _$ArtistSortTypeEnumMap[instance.artistSort],
|
||||
'libraryPlaylistSort':
|
||||
_$PlaylistSortTypeEnumMap[instance.libraryPlaylistSort],
|
||||
};
|
||||
|
||||
T _$enumDecode<T>(
|
||||
|
@ -67,3 +80,25 @@ const _$SortTypeEnumMap = {
|
|||
SortType.ALPHABETIC: 'ALPHABETIC',
|
||||
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',
|
||||
};
|
||||
|
|
|
@ -246,6 +246,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
|
||||
//Queue
|
||||
List<MediaItem> _queue = <MediaItem>[];
|
||||
List<int> _shuffleHistory = [];
|
||||
int _queueIndex = 0;
|
||||
ConcatenatingAudioSource _audioSource;
|
||||
|
||||
|
@ -363,13 +364,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
Future<void> onSkipToNext() async {
|
||||
//Shuffle
|
||||
if (_player.shuffleModeEnabled??false) {
|
||||
int newIndex = Random().nextInt(_queue.length)-1;
|
||||
int newIndex = Random().nextInt(_queue.length-1);
|
||||
//Update state
|
||||
_skipState = newIndex > _queueIndex
|
||||
? AudioProcessingState.skippingToNext
|
||||
: AudioProcessingState.skippingToPrevious;
|
||||
|
||||
if (_shuffleHistory.length == 0) _shuffleHistory.add(_queueIndex);
|
||||
_queueIndex = newIndex;
|
||||
_shuffleHistory.add(newIndex);
|
||||
await _player.seek(Duration.zero, index: _queueIndex);
|
||||
_skipState = null;
|
||||
return;
|
||||
|
@ -385,9 +388,24 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
|
||||
@override
|
||||
Future<void> onSkipToPrevious() async {
|
||||
if (_queueIndex == 0) return;
|
||||
if (_queueIndex == 0 && !(_player.shuffleModeEnabled??false)) return;
|
||||
//Update buffering state
|
||||
_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--;
|
||||
await _player.seekToPrevious();
|
||||
_skipState = null;
|
||||
|
@ -553,9 +571,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
if (name == 'repeatType') {
|
||||
_player.setLoopMode(LoopMode.values[args]);
|
||||
}
|
||||
if (name == 'saveQueue') await this._saveQueue();
|
||||
if (name == 'saveQueue')
|
||||
await this._saveQueue();
|
||||
//Load queue after some initialization in frontend
|
||||
if (name == 'load') await this._loadQueueFile();
|
||||
if (name == 'load')
|
||||
await this._loadQueueFile();
|
||||
//Shuffle
|
||||
if (name == 'shuffle') {
|
||||
await _player.setShuffleModeEnabled(args);
|
||||
|
|
|
@ -202,13 +202,11 @@ const language_ar_ar = {
|
|||
//0.5.2 Strings:
|
||||
"Use system theme": "استخدم ثيم النظام",
|
||||
"Light": "ابيض",
|
||||
|
||||
//0.5.3 Strings:
|
||||
|
||||
//0.5.3 Strings:
|
||||
"Popularity": "الشعبية",
|
||||
"User": "المستخدم",
|
||||
"Track count": "عدد الاغاني",
|
||||
"If you want to use custom directory naming - use '/' as directory separator.": "إذا كنت تريد استخدام تسمية مخصصة، استخدم '/' كفاصل بين المسار."
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ const language_de_de = {
|
|||
"Search": "Suche",
|
||||
"Library": "Mediathek",
|
||||
"Offline mode, can't play flow or smart track lists.":
|
||||
"Offline-Modus, kann keine Flow- oder Smart Track-Listen abspielen.",
|
||||
"Offline-Modus, kann keine Flow- oder Smart Track-Listen abspielen.",
|
||||
"Added to library": "Zur Mediathek hinzufügen",
|
||||
"Download": "Download",
|
||||
"Disk": "Disk",
|
||||
|
@ -28,31 +28,31 @@ const language_de_de = {
|
|||
"Done": "Erledigt",
|
||||
"Delete": "Gelöscht",
|
||||
"Are you sure you want to delete this download?":
|
||||
"Bist du sicher, dass du diesen Download löschen willst?",
|
||||
"Bist du sicher, dass du diesen Download löschen willst?",
|
||||
"Cancel": "Abbrechen",
|
||||
"Downloads": "Downloads",
|
||||
"Clear queue": "Warteschleife löschen",
|
||||
"This won't delete currently downloading item":
|
||||
"Dies löscht das derzeit heruntergeladene Element nicht",
|
||||
"Dies löscht das derzeit heruntergeladene Element nicht",
|
||||
"Are you sure you want to delete all queued downloads?":
|
||||
"Bist du sicher, dass du alle Downloads aus der Warteschleife löschen willst?",
|
||||
"Bist du sicher, dass du alle Downloads aus der Warteschleife löschen willst?",
|
||||
"Clear downloads history": "Download-Verlauf löschen",
|
||||
"WARNING: This will only clear non-offline (external downloads)":
|
||||
"ACHTUNG: (Externe Downloads) werden entfernt",
|
||||
"ACHTUNG: (Externe Downloads) werden entfernt",
|
||||
"Please check your connection and try again later...":
|
||||
"Bitte überprüfe deine Verbindung und versuche es später noch einmal...",
|
||||
"Bitte überprüfe deine Verbindung und versuche es später noch einmal...",
|
||||
"Show more": "Mehr anzeigen",
|
||||
"Importer": "Importieren",
|
||||
"Currently supporting only Spotify, with 100 tracks limit":
|
||||
"Derzeit begrenzt auf maximal 100 Titel",
|
||||
"Derzeit begrenzt auf maximal 100 Titel",
|
||||
"Due to API limitations": "Aufgrund von API-Einschränkungen",
|
||||
"Enter your playlist link below":
|
||||
"Gebe deinen Wiedergabelisten-Link unten ein",
|
||||
"Gebe deinen Wiedergabelisten-Link unten ein",
|
||||
"Error loading URL!": "Fehler beim Laden der URL!",
|
||||
"Convert": "Konvertieren",
|
||||
"Download only": "Nur Herunterladen",
|
||||
"Downloading is currently stopped, click here to resume.":
|
||||
"Das Herunterladen ist derzeit gestoppt, klicke hier, um fortzufahren.",
|
||||
"Das Herunterladen ist derzeit gestoppt, klicke hier, um fortzufahren.",
|
||||
"Tracks": "Titel",
|
||||
"Albums": "Alben",
|
||||
"Artists": "Künstler",
|
||||
|
@ -70,24 +70,24 @@ const language_de_de = {
|
|||
"All offline tracks": "Alle Offline-Titel",
|
||||
"Create new playlist": "Neue Wiedergabeliste erstellen",
|
||||
"Cannot create playlists in offline mode":
|
||||
"Wiedergabelisten können im Offline-Modus nicht erstellt werden",
|
||||
"Wiedergabelisten können im Offline-Modus nicht erstellt werden",
|
||||
"Error": "Fehler",
|
||||
"Error logging in! Please check your token and internet connection and try again.":
|
||||
"Fehler beim Einloggen! Bitte überprüfe dein Token und deine Internetverbindung und versuche es erneut.",
|
||||
"Fehler beim Einloggen! Bitte überprüfe dein Token und deine Internetverbindung und versuche es erneut.",
|
||||
"Dismiss": "Verwerfen",
|
||||
"Welcome to": "Willkommen bei",
|
||||
"Please login using your Deezer account.":
|
||||
"Bitte melde dich mit deinem Deezer-Konto an.",
|
||||
"Bitte melde dich mit deinem Deezer-Konto an.",
|
||||
"Login using browser": "Anmeldung über Browser",
|
||||
"Login using token": "Anmeldung per Token",
|
||||
"Enter ARL": "ARL eingeben",
|
||||
"Token (ARL)": "Token (ARL)",
|
||||
"Save": "Speichern",
|
||||
"If you don't have account, you can register on deezer.com for free.":
|
||||
"Wenn Du noch kein Konto hast, kannst Du Dich kostenlos auf deezer.com registrieren.",
|
||||
"Wenn Du noch kein Konto hast, kannst Du Dich kostenlos auf deezer.com registrieren.",
|
||||
"Open in browser": "Im Browser öffnen",
|
||||
"By using this app, you don't agree with the Deezer ToS":
|
||||
"Wenn Du diese Anwendung verwendest, bist Du nicht mit den Deezer ToS einverstanden",
|
||||
"Wenn Du diese Anwendung verwendest, bist Du nicht mit den Deezer ToS einverstanden",
|
||||
"Play next": "Als nächstes spielen",
|
||||
"Add to queue": "Zur Warteschleife hinzufügen",
|
||||
"Add track to favorites": "Titel zu Favoriten hinzufügen",
|
||||
|
@ -151,11 +151,11 @@ const language_de_de = {
|
|||
"Country used in headers. Now": "Aktuell",
|
||||
"Log tracks": "Protokolliere Titel",
|
||||
"Send track listen logs to Deezer, enable it for features like Flow to work properly":
|
||||
"Gehörte Titel-Protokolle an Deezer senden, damit Flow richtig funktioniert",
|
||||
"Gehörte Titel-Protokolle an Deezer senden, damit Flow richtig funktioniert",
|
||||
"Offline mode": "Offline-Modus",
|
||||
"Will be overwritten on start.": "Wird beim Start überschrieben.",
|
||||
"Error logging in, check your internet connections.":
|
||||
"Fehler beim Anmelden, überprüfe deine Internetverbindung.",
|
||||
"Fehler beim Anmelden, überprüfe deine Internetverbindung.",
|
||||
"Logging in...": "Angemeldet...",
|
||||
"Download path": "Download-Pfad",
|
||||
"Downloads naming": "Benennung der Downloads",
|
||||
|
@ -167,14 +167,14 @@ const language_de_de = {
|
|||
"Create folders for albums": "Ordner für Alben erstellen",
|
||||
"Separate albums by discs": "Alben nach Discs trennen",
|
||||
"Overwrite already downloaded files":
|
||||
"Bereits heruntergeladene Dateien überschreiben",
|
||||
"Bereits heruntergeladene Dateien überschreiben",
|
||||
"Copy ARL": "ARL kopieren",
|
||||
"Copy userToken/ARL Cookie for use in other apps.":
|
||||
"UserToken / ARL-Cookie zur Verwendung in anderen Anwendungen kopieren.",
|
||||
"UserToken / ARL-Cookie zur Verwendung in anderen Anwendungen kopieren.",
|
||||
"Copied": "Kopiert",
|
||||
"Log out": "Abmelden",
|
||||
"Due to plugin incompatibility, login using browser is unavailable without restart.":
|
||||
"Aufgrund von Plugin-Inkompatibilität ist die Anmeldung mit dem Browser ohne Neustart nicht möglich.",
|
||||
"Aufgrund von Plugin-Inkompatibilität ist die Anmeldung mit dem Browser ohne Neustart nicht möglich.",
|
||||
"(ARL ONLY) Continue": "(NUR ARL) Fortfahren",
|
||||
"Log out & Exit": "Abmelden & Beenden",
|
||||
"Pick-a-Path": "Wähle einen Pfad",
|
||||
|
@ -183,7 +183,7 @@ const language_de_de = {
|
|||
"Permission denied": "Zugriff verweigert",
|
||||
"Language": "Sprache",
|
||||
"Language changed, please restart Freezer to apply!":
|
||||
"Sprache geändert, bitte Freezer neu starten!",
|
||||
"Sprache geändert, bitte Freezer neu starten!",
|
||||
"Importing...": "Importiere...",
|
||||
"Radio": "Radio",
|
||||
//0.5.0 Strings:
|
||||
|
|
|
@ -10,7 +10,7 @@ const language_el_gr = {
|
|||
"Search": "Αναζήτηση",
|
||||
"Library": "Βιβλιοθήκη",
|
||||
"Offline mode, can't play flow or smart track lists.":
|
||||
"Λειτουργία εκτός σύνδεσης, δεν είναι δυνατή η αναπαραγωγή flow ή έξυπνων λιστών κομματιών.",
|
||||
"Λειτουργία εκτός σύνδεσης, δεν είναι δυνατή η αναπαραγωγή flow ή έξυπνων λιστών κομματιών.",
|
||||
"Added to library": "Προστέθηκε στη βιβλιοθήκη",
|
||||
"Download": "Λήψη",
|
||||
"Disk": "Δίσκος",
|
||||
|
@ -29,37 +29,39 @@ const language_el_gr = {
|
|||
"Done": "Ολοκληρώθηκε",
|
||||
"Delete": "Διαγραφή",
|
||||
"Are you sure you want to delete this download?":
|
||||
"Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη λήψη;",
|
||||
"Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη λήψη;",
|
||||
"Cancel": "Άκυρο",
|
||||
"Downloads": "Λήψεις",
|
||||
"Clear queue": "Εκκαθάριση ουράς",
|
||||
"This won't delete currently downloading item":
|
||||
"Αυτό δεν θα διαγράψει το τρέχον αντικείμενο λήψης",
|
||||
"Αυτό δεν θα διαγράψει το τρέχον αντικείμενο λήψης",
|
||||
"Are you sure you want to delete all queued downloads?":
|
||||
"Είστε βέβαιοι ότι θέλετε να διαγράψετε όλες τις λήψεις στην ουρά;",
|
||||
"Είστε βέβαιοι ότι θέλετε να διαγράψετε όλες τις λήψεις στην ουρά;",
|
||||
"Clear downloads history": "Διαγραφή ιστορικού λήψεων",
|
||||
"WARNING: This will only clear non-offline (external downloads)":
|
||||
"ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα καθαρίσει μόνο τις εκτός σύνδεσης (εξωτερικές) λήψεις",
|
||||
"ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αυτό θα καθαρίσει μόνο τις εκτός σύνδεσης (εξωτερικές) λήψεις",
|
||||
"Please check your connection and try again later...":
|
||||
"Ελέγξτε τη σύνδεσή σας και δοκιμάστε ξανά αργότερα...",
|
||||
"Ελέγξτε τη σύνδεσή σας και δοκιμάστε ξανά αργότερα...",
|
||||
"Show more": "Δείτε περισσότερα",
|
||||
"Importer": "Εισαγωγέας",
|
||||
"Currently supporting only Spotify, with 100 tracks limit":
|
||||
"Αυτήν τη στιγμή υποστηρίζεται μόνο το Spotify, με όριο 100 κομματιών",
|
||||
"Αυτήν τη στιγμή υποστηρίζεται μόνο το Spotify, με όριο 100 κομματιών",
|
||||
"Due to API limitations": "Λόγω περιορισμών API",
|
||||
"Enter your playlist link below": "Εισαγάγετε τον σύνδεσμο λίστας αναπαραγωγής παρακάτω",
|
||||
"Enter your playlist link below":
|
||||
"Εισαγάγετε τον σύνδεσμο λίστας αναπαραγωγής παρακάτω",
|
||||
"Error loading URL!": "Σφάλμα φόρτωσης διεύθυνσης URL!",
|
||||
"Convert": "Μετατροπή",
|
||||
"Download only": "Μόνο λήψη",
|
||||
"Downloading is currently stopped, click here to resume.":
|
||||
"Η λήψη έχει σταματήσει, κάντε κλικ εδώ για να συνεχίσετε.",
|
||||
"Η λήψη έχει σταματήσει, κάντε κλικ εδώ για να συνεχίσετε.",
|
||||
"Tracks": "Κομμάτια",
|
||||
"Albums": "Album",
|
||||
"Artists": "Καλλιτέχνες",
|
||||
"Playlists": "Λίστες αναπαραγωγής",
|
||||
"Import": "Εισαγωγή",
|
||||
"Import playlists from Spotify": "Εισαγωγή λιστών αναπαραγωγής από το Spotify",
|
||||
"Statistics": "Στατιστική",
|
||||
"Import playlists from Spotify":
|
||||
"Εισαγωγή λιστών αναπαραγωγής από το Spotify",
|
||||
"Statistics": "Στατιστικά",
|
||||
"Offline tracks": "Κομμάτια εκτός σύνδεσης",
|
||||
"Offline albums": "Album εκτός σύνδεσης",
|
||||
"Offline playlists": "Λίστες αναπαραγωγής εκτός σύνδεσης",
|
||||
|
@ -70,24 +72,24 @@ const language_el_gr = {
|
|||
"All offline tracks": "Όλα τα κομμάτια εκτός σύνδεσης",
|
||||
"Create new playlist": "Δημιουργία λίστας αναπαραγωγής",
|
||||
"Cannot create playlists in offline mode":
|
||||
"Δεν είναι δυνατή η δημιουργία λιστών αναπαραγωγής σε λειτουργία εκτός σύνδεσης",
|
||||
"Δεν είναι δυνατή η δημιουργία λιστών αναπαραγωγής σε λειτουργία εκτός σύνδεσης",
|
||||
"Error": "Σφάλμα",
|
||||
"Error logging in! Please check your token and internet connection and try again.":
|
||||
"Σφάλμα σύνδεσης! Ελέγξτε το token και τη σύνδεσή σας στο δίκτυο και δοκιμάστε ξανά.",
|
||||
"Σφάλμα σύνδεσης! Ελέγξτε το token και τη σύνδεσή σας στο δίκτυο και δοκιμάστε ξανά.",
|
||||
"Dismiss": "Απόρριψη",
|
||||
"Welcome to": "Καλωσήρθατε στο",
|
||||
"Please login using your Deezer account.":
|
||||
"Συνδεθείτε χρησιμοποιώντας τον λογαριασμό σας στο Deezer.",
|
||||
"Συνδεθείτε χρησιμοποιώντας τον λογαριασμό σας στο Deezer.",
|
||||
"Login using browser": "Σύνδεση χρησιμοποιώντας το πρόγραμμα περιήγησης",
|
||||
"Login using token": "Σύνδεση χρησιμοποιώντας token",
|
||||
"Enter ARL": "Εισαγωγή ARL",
|
||||
"Token (ARL)": "Token (ARL)",
|
||||
"Save": "Αποθήκευση",
|
||||
"If you don't have account, you can register on deezer.com for free.":
|
||||
"Εάν δεν έχετε λογαριασμό, μπορείτε να εγγραφείτε δωρεάν στο deezer.com.",
|
||||
"Εάν δεν έχετε λογαριασμό, μπορείτε να εγγραφείτε δωρεάν στο deezer.com.",
|
||||
"Open in browser": "Ανοιγμα σε πρόγραμμα περιήγησης",
|
||||
"By using this app, you don't agree with the Deezer ToS":
|
||||
"Χρησιμοποιώντας αυτήν την εφαρμογή, δεν συμφωνείτε με τους κανονισμούς χρήσης Deezer",
|
||||
"Χρησιμοποιώντας αυτήν την εφαρμογή, δεν συμφωνείτε με τους κανονισμούς χρήσης Deezer",
|
||||
"Play next": "Παίξε αμέσως μετά",
|
||||
"Add to queue": "Προσθήκη στην ουρά",
|
||||
"Add track to favorites": "Προσθήκη κομμάτι στα αγαπημένα",
|
||||
|
@ -104,7 +106,8 @@ const language_el_gr = {
|
|||
"Remove album": "Κατάργηση album",
|
||||
"Album removed": "Το album καταργήθηκε",
|
||||
"Remove from favorites": "Κατάργηση από τα αγαπημένα",
|
||||
"Artist removed from library": "Ο καλλιτέχνης καταργήθηκε από τη βιβλιοθήκη",
|
||||
"Artist removed from library":
|
||||
"Ο καλλιτέχνης καταργήθηκε από τη βιβλιοθήκη",
|
||||
"Add to favorites": "Προσθήκη στα αγαπημένα",
|
||||
"Remove from library": "Κατάργηση από τη βιβλιοθήκη",
|
||||
"Add playlist to library": "Προσθήκη λίστας αναπαραγωγής στη βιβλιοθήκη",
|
||||
|
@ -126,7 +129,7 @@ const language_el_gr = {
|
|||
"Show all tracks": "Εμφάνιση όλων των κομματιών",
|
||||
"Show all playlists": "Εμφάνιση όλων των λιστών αναπαραγωγής",
|
||||
"Settings": "Ρυθμίσεις",
|
||||
"General": "Γενικός",
|
||||
"General": "Γενικά",
|
||||
"Appearance": "Εμφάνιση",
|
||||
"Quality": "Ποιότητα",
|
||||
"Deezer": "Deezer",
|
||||
|
@ -139,24 +142,26 @@ const language_el_gr = {
|
|||
"Deezer (Dark)": "Deezer (Σκούρο)",
|
||||
"Primary color": "Πρωτεύον χρώμα",
|
||||
"Selected color": "Επιλεγμένο χρώμα",
|
||||
"Use album art primary color": "Χρησιμοποιήστε το πρωτεύον χρώμα του εξώφυλλου του album",
|
||||
"Use album art primary color":
|
||||
"Χρησιμοποιήστε το πρωτεύον χρώμα του εξώφυλλου του album",
|
||||
"Warning: might be buggy": "Προειδοποίηση: μπορεί να μη λειτουργεί σωστά",
|
||||
"Mobile streaming": "Ροή μέσω δεδομένων κινητού δικτύου",
|
||||
"Wifi streaming": "Ροή μέσω WIFI",
|
||||
"External downloads": "Εξωτερικές λήψεις",
|
||||
"Content language": "Γλώσσα περιεχομένου",
|
||||
"Not app language, used in headers. Now":
|
||||
"Όχι γλώσσα εφαρμογής, χρησιμοποιείται στις κεφαλίδες. Τρέχουσα",
|
||||
"Όχι γλώσσα εφαρμογής, χρησιμοποιείται στις κεφαλίδες. Τρέχουσα",
|
||||
"Select language": "Επιλογή γλώσσας",
|
||||
"Content country": "Χώρα περιεχομένου",
|
||||
"Country used in headers. Now": "Χώρα που χρησιμοποιείται στις κεφαλίδες. Τρέχουσα",
|
||||
"Country used in headers. Now":
|
||||
"Χώρα που χρησιμοποιείται στις κεφαλίδες. Τρέχουσα",
|
||||
"Log tracks": "Αρχεία καταγραφής",
|
||||
"Send track listen logs to Deezer, enable it for features like Flow to work properly":
|
||||
"Αποστολή αρχείων καταγραφής ακρόασης στο Deezer, ενεργοποιήστε το για ορθή λειτουργία υπηρεσιών όπως το Flow",
|
||||
"Αποστολή αρχείων καταγραφής ακρόασης στο Deezer, ενεργοποιήστε το για ορθή λειτουργία υπηρεσιών όπως το Flow",
|
||||
"Offline mode": "Λειτουργία εκτός σύνδεσης",
|
||||
"Will be overwritten on start.": "Θα αντικατασταθεί κατά την εκκίνηση.",
|
||||
"Error logging in, check your internet connections.":
|
||||
"Σφάλμα σύνδεσης, ελέγξτε την σύνδεσή σας στο Δίκτυο.",
|
||||
"Σφάλμα σύνδεσης, ελέγξτε την σύνδεσή σας στο Δίκτυο.",
|
||||
"Logging in...": "Σύνδεση...",
|
||||
"Download path": "Διαδρομή λήψεων",
|
||||
"Downloads naming": "Ονομασία λήψεων",
|
||||
|
@ -170,11 +175,11 @@ const language_el_gr = {
|
|||
"Overwrite already downloaded files": "Αντικατάσταση ήδη ληφθέντων αρχείων",
|
||||
"Copy ARL": "Αντιγραφή ARL",
|
||||
"Copy userToken/ARL Cookie for use in other apps.":
|
||||
"Αντιγραφή userToken/ARL Cookie για χρήση σε άλλες εφαρμογές.",
|
||||
"Αντιγραφή userToken/ARL Cookie για χρήση σε άλλες εφαρμογές.",
|
||||
"Copied": "Αντιγράφηκε",
|
||||
"Log out": "Αποσύνδεση",
|
||||
"Due to plugin incompatibility, login using browser is unavailable without restart.":
|
||||
"Λόγω ασυμβατότητας προσθηκών, η σύνδεση μέσω προγράμματος περιήγησης δεν είναι διαθέσιμη χωρίς επανεκκίνηση.",
|
||||
"Λόγω ασυμβατότητας προσθηκών, η σύνδεση μέσω προγράμματος περιήγησης δεν είναι διαθέσιμη χωρίς επανεκκίνηση.",
|
||||
"(ARL ONLY) Continue": "(ARL ΜΟΝΟ) Συνέχεια",
|
||||
"Log out & Exit": "Αποσύνδεση & Έξοδος",
|
||||
"Pick-a-Path": "Διαλέξτε ένα μονοπάτι",
|
||||
|
@ -183,11 +188,57 @@ const language_el_gr = {
|
|||
"Permission denied": "Η άδεια απορρίφθηκε",
|
||||
"Language": "Γλώσσα",
|
||||
"Language changed, please restart Freezer to apply!":
|
||||
"Η γλώσσα άλλαξε, κάντε επανεκκίνηση του Freezer για εφαρμογή!",
|
||||
"Η γλώσσα άλλαξε, κάντε επανεκκίνηση του Freezer για εφαρμογή!",
|
||||
"Importing...": "Εισαγωγή...",
|
||||
"Radio": "Ραδιόφωνο",
|
||||
"Flow": "Flow",
|
||||
"Track is not available on Deezer!": "Το κομμάτι δεν είναι διαθέσιμο στο Deezer!",
|
||||
"Failed to download track! Please restart.": "Αποτυχία λήψης κομματιού! Κάντε επανεκκίνηση. "
|
||||
"Track is not available on Deezer!":
|
||||
"Το κομμάτι δεν είναι διαθέσιμο στο 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": "Φωτεινο"
|
||||
}
|
||||
};
|
||||
|
|
|
@ -215,7 +215,8 @@ const language_es_es = {
|
|||
"To get latest releases": "Para obtener los últimos lanzamientos",
|
||||
"Official chat": "Chat oficial",
|
||||
"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",
|
||||
"Update": "Actualizar",
|
||||
"Playlist updated!": "Lista de reproducción actualizada!",
|
||||
|
|
|
@ -224,6 +224,9 @@ const language_ru_ru = {
|
|||
"Save cover file for every track": "Обложки для каждого трека отдельным файлом",
|
||||
"Download Log": "Лог загрузок (технические данные)",
|
||||
"Repository": "Репозиторий",
|
||||
"Source code, report issues there.": "Исходный код, вопросы, предложения."
|
||||
"Source code, report issues there.": "Исходный код, вопросы, предложения.",
|
||||
//0.5.2 Strings:
|
||||
"Use system theme": "Использовать тему системы",
|
||||
"Light": "Светлая"
|
||||
}
|
||||
};
|
|
@ -44,7 +44,7 @@ const language_tr_tr = {
|
|||
"Please check your connection and try again later...":
|
||||
"Lütfen bağlantınızı kontrol edin ve daha sonra tekrar deneyin ...",
|
||||
"Show more": "Daha fazla göster",
|
||||
"Importer": "Importer",
|
||||
"Importer": "Aktar",
|
||||
"Currently supporting only Spotify, with 100 tracks limit":
|
||||
"Şu anda 100 parça sınırıyla yalnızca Spotify'ı destekliyor",
|
||||
"Due to API limitations": "API sınırlamaları nedeniyle",
|
||||
|
@ -74,8 +74,8 @@ const language_tr_tr = {
|
|||
"Çevrimdışı modda oynatma listeleri oluşturulamaz",
|
||||
"Error": "Hata",
|
||||
"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.",
|
||||
"Dismiss": "Reddet",
|
||||
"Oturum açılamadı! Lütfen tokeninizi ve internet bağlantınızı kontrol edin ve tekrar deneyin.",
|
||||
"Dismiss": "Kapat",
|
||||
"Welcome to": "Hoşgeldiniz",
|
||||
"Please login using your Deezer account.":
|
||||
"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",
|
||||
"Play next": "Sonrakini çal",
|
||||
"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",
|
||||
"Select playlist": "Oynatma listesi seçin",
|
||||
"Track added to": "Parça şuraya eklendi",
|
||||
"Remove from playlist": "Oynatma listesinden kaldır",
|
||||
"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ı",
|
||||
"Go to": "Git",
|
||||
"Make offline": "Çevrimdışı yap",
|
||||
"Add to library": "Kütüphaneye ekle",
|
||||
"Remove album": "Albümü kaldır",
|
||||
"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ı",
|
||||
"Add to favorites": "Favorilere ekle",
|
||||
"Remove from library": "Kütüphaneden kaldır",
|
||||
"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",
|
||||
"Download playlist": "Oynatma listesini indirin",
|
||||
"Create playlist": "Oynatma listesi oluştur",
|
||||
"Title": "Başlık",
|
||||
"Description": "Açıklama",
|
||||
"Private": "Özel",
|
||||
"Collaborative": "İşbirlikçi",
|
||||
"Collaborative": "Paylaşılan",
|
||||
"Create": "Oluştur",
|
||||
"Playlist created!": "Oynatma listesi oluşturuldu!",
|
||||
"Playing from:": "Şuradan oynatılıyor:",
|
||||
"Queue": "Kuyruk",
|
||||
"Offline search": "Offline search",
|
||||
"Offline search": "Çevrimdışı arama",
|
||||
"Search Results": "Arama Sonuçları",
|
||||
"No results!": "Sonuç yok!",
|
||||
"Show all tracks": "Tüm parçaları göster",
|
||||
|
@ -134,24 +134,24 @@ const language_tr_tr = {
|
|||
"Theme": "Tema",
|
||||
"Currently": "Şu anda",
|
||||
"Select theme": "Tema seçin",
|
||||
"Light (default)": "Light (Varsayılan)",
|
||||
"Dark": "Dark",
|
||||
"Black (AMOLED)": "Black (AMOLED)",
|
||||
"Light (default)": "Açık (Varsayılan)",
|
||||
"Dark": "Koyu",
|
||||
"Black (AMOLED)": "Siyah (AMOLED)",
|
||||
"Deezer (Dark)": "Deezer (Dark)",
|
||||
"Primary color": "Ana 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",
|
||||
"Mobile streaming": "Mobil akış",
|
||||
"Wifi streaming": "Wifi akışı",
|
||||
"Mobile streaming": "Mobil veri",
|
||||
"Wifi streaming": "Wifi",
|
||||
"External downloads": "Harici indirmeler",
|
||||
"Content language": "İçerik dili",
|
||||
"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",
|
||||
"Content country": "İçerik ülkesi",
|
||||
"Country used in headers. Now": "Başlıklarda kullanılan ülke. Şimdi",
|
||||
"Log tracks": "Log tracks",
|
||||
"Country used in headers. Now": "Başlıklarda kullanılan ülke. Şuan",
|
||||
"Log tracks": "Parça günlükleri",
|
||||
"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",
|
||||
"Offline mode": "Çevrimdışı mod",
|
||||
|
@ -159,25 +159,25 @@ const language_tr_tr = {
|
|||
"Error logging in, check your internet connections.":
|
||||
"Giriş hatası, internet bağlantılarınızı kontrol edin.",
|
||||
"Logging in...": "Giriş yapılıyor...",
|
||||
"Download path": "İndirme yolu",
|
||||
"Downloads naming": "İndirilenler adlandırma",
|
||||
"Download path": "İndirme konumu",
|
||||
"Downloads naming": "İndirilenleri adlandır",
|
||||
"Downloaded tracks filename": "İndirilen parçaların dosya adı",
|
||||
"Valid variables are": "Geçerli değişkenler",
|
||||
"Reset": "Sıfırla",
|
||||
"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",
|
||||
"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 userToken/ARL Cookie for use in other apps.":
|
||||
"Diğer uygulamalarda kullanmak için userToken / ARL Cookie'yi kopyalayın.",
|
||||
"Copied": "Kopyalandı",
|
||||
"Log out": "Çıkış yap",
|
||||
"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",
|
||||
"Log out & Exit": "Çıkış yap & Çık",
|
||||
"Log out & Exit": "Çıkış yap & Kapat",
|
||||
"Pick-a-Path": "Konum seç",
|
||||
"Select storage": "Depolama seç",
|
||||
"Go up": "Yukarı git",
|
||||
|
@ -196,7 +196,7 @@ const language_tr_tr = {
|
|||
"Failed": "Başarısız",
|
||||
"Queued": "Sıraya alındı",
|
||||
//Updated in 0.5.1 - used in context of download:
|
||||
"External": "Storage",
|
||||
"External": "Depolama",
|
||||
//0.5.0
|
||||
"Restart failed downloads": "Başarısız indirmeleri yeniden başlatın",
|
||||
"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!",
|
||||
"About": "Hakkında",
|
||||
"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",
|
||||
"Telegram Group": "Telegram Grubu",
|
||||
"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:
|
||||
"Save cover file for every track": "Her parça için kapak dosyasını kaydedin",
|
||||
"Download Log": "İndirme Kayıtları",
|
||||
"Repository": "Depo",
|
||||
"Source code, report issues there.": "Kaynak kodu, sorunları bildirin"
|
||||
"Repository": "Repo",
|
||||
"Source code, report issues there.": "Kaynak kodu, sorunları bildirin",
|
||||
|
||||
//0.5.2 Strings:
|
||||
"Use system theme": "Sistem temasını kullan",
|
||||
"Light": "Açık"
|
||||
}
|
||||
};
|
|
@ -664,7 +664,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||
tracks.sort((a, b) => a.title.compareTo(b.title));
|
||||
return tracks;
|
||||
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;
|
||||
case SortType.REVERSE:
|
||||
return tracks.reversed.toList();
|
||||
|
|
|
@ -419,6 +419,13 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
}
|
||||
|
||||
|
||||
enum AlbumSortType {
|
||||
DEFAULT,
|
||||
REVERSE,
|
||||
ALPHABETIC,
|
||||
ARTIST
|
||||
}
|
||||
|
||||
class LibraryAlbums extends StatefulWidget {
|
||||
@override
|
||||
_LibraryAlbumsState createState() => _LibraryAlbumsState();
|
||||
|
@ -427,6 +434,25 @@ class LibraryAlbums extends StatefulWidget {
|
|||
class _LibraryAlbumsState extends State<LibraryAlbums> {
|
||||
|
||||
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 {
|
||||
if (settings.offlineMode) return;
|
||||
|
@ -439,13 +465,44 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
|||
@override
|
||||
void initState() {
|
||||
_load();
|
||||
_sort = cache.albumSort??AlbumSortType.DEFAULT;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
children: <Widget>[
|
||||
Container(height: 8.0,),
|
||||
|
@ -459,7 +516,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
|||
|
||||
if (_albums != null)
|
||||
...List.generate(_albums.length, (int i) {
|
||||
Album a = _albums[i];
|
||||
Album a = _sorted[i];
|
||||
return AlbumTile(
|
||||
a,
|
||||
onTap: () {
|
||||
|
@ -523,50 +580,148 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
|||
}
|
||||
}
|
||||
|
||||
enum ArtistSortType {
|
||||
DEFAULT,
|
||||
REVERSE,
|
||||
POPULARITY,
|
||||
ALPHABETIC
|
||||
}
|
||||
|
||||
class LibraryArtists extends StatefulWidget {
|
||||
@override
|
||||
_LibraryArtistsState createState() => _LibraryArtistsState();
|
||||
}
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Artists'.i18n),),
|
||||
body: FutureBuilder(
|
||||
future: deezerAPI.getArtists(),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
|
||||
if (snapshot.hasError) return ErrorScreen();
|
||||
if (!snapshot.hasData) return Center(child: CircularProgressIndicator(),);
|
||||
|
||||
return ListView(
|
||||
children: <Widget>[
|
||||
...List.generate(snapshot.data.length, (i) {
|
||||
Artist a = snapshot.data[i];
|
||||
return ArtistHorizontalTile(
|
||||
a,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => ArtistDetails(a))
|
||||
);
|
||||
},
|
||||
onHold: () {
|
||||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultArtistMenu(a, onRemove: () {
|
||||
setState(() => {});
|
||||
});
|
||||
},
|
||||
);
|
||||
}),
|
||||
appBar: AppBar(
|
||||
title: Text('Artists'.i18n),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
child: Icon(Icons.sort, size: 32.0),
|
||||
onSelected: (ArtistSortType s) async {
|
||||
setState(() => _sort = s);
|
||||
cache.artistSort = s;
|
||||
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>[
|
||||
if (_loading)
|
||||
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(
|
||||
a,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => ArtistDetails(a))
|
||||
);
|
||||
},
|
||||
onHold: () {
|
||||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultArtistMenu(a, onRemove: () {
|
||||
setState(() {
|
||||
_artists.remove(a);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum PlaylistSortType {
|
||||
DEFAULT,
|
||||
REVERSE,
|
||||
ALPHABETIC,
|
||||
USER,
|
||||
TRACK_COUNT
|
||||
}
|
||||
|
||||
class LibraryPlaylists extends StatefulWidget {
|
||||
@override
|
||||
|
@ -576,6 +731,26 @@ class LibraryPlaylists extends StatefulWidget {
|
|||
class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
||||
|
||||
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 {
|
||||
if (!settings.offlineMode) {
|
||||
|
@ -588,6 +763,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
_sort = cache.libraryPlaylistSort;
|
||||
_load();
|
||||
super.initState();
|
||||
}
|
||||
|
@ -606,7 +782,41 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
|
@ -652,7 +862,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
|||
|
||||
if (_playlists != null)
|
||||
...List.generate(_playlists.length, (int i) {
|
||||
Playlist p = _playlists[i];
|
||||
Playlist p = _sorted[i];
|
||||
return PlaylistTile(
|
||||
p,
|
||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||
|
|
|
@ -90,15 +90,19 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
//Try logging in
|
||||
try {
|
||||
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 (settings.arl.length != 192) {
|
||||
if (_error == null) _error = '';
|
||||
_error += 'Invalid ARL length!';
|
||||
}
|
||||
setState(() => settings.arl = null);
|
||||
errorDialog();
|
||||
}
|
||||
//On error show dialog and reset to null
|
||||
} catch (e) {
|
||||
_error = e;
|
||||
print('Login error: ' + e);
|
||||
_error = e.toString();
|
||||
print('Login error: ' + e.toString());
|
||||
setState(() => settings.arl = null);
|
||||
errorDialog();
|
||||
}
|
||||
|
|
|
@ -498,7 +498,7 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
|||
ListTile(
|
||||
title: Text('Proxy'.i18n),
|
||||
leading: Icon(Icons.vpn_key),
|
||||
subtitle: Text(settings.proxyAddress??'Not set'),
|
||||
subtitle: Text(settings.proxyAddress??'Not set'.i18n),
|
||||
onTap: () {
|
||||
String _new;
|
||||
showDialog(
|
||||
|
@ -606,7 +606,8 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
|||
),
|
||||
Container(height: 8.0),
|
||||
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(
|
||||
fontSize: 12.0,
|
||||
),
|
||||
|
@ -658,8 +659,8 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
|||
),
|
||||
Slider(
|
||||
min: 1,
|
||||
max: 6,
|
||||
divisions: 5,
|
||||
max: 16,
|
||||
divisions: 15,
|
||||
value: _downloadThreads,
|
||||
label: _downloadThreads.round().toString(),
|
||||
onChanged: (double v) => setState(() => _downloadThreads = v),
|
||||
|
@ -1040,15 +1041,6 @@ class _CreditsScreenState extends State<CreditsScreen> {
|
|||
|
||||
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 = [
|
||||
['Xandar Null', 'Arabic'],
|
||||
['Markus', 'German'],
|
||||
|
@ -1111,22 +1103,51 @@ class _CreditsScreenState extends State<CreditsScreen> {
|
|||
),
|
||||
ListTile(
|
||||
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),
|
||||
onTap: () {
|
||||
launch('https://notabug.org/exttex/freezer');
|
||||
},
|
||||
),
|
||||
Divider(),
|
||||
...List.generate(credits.length, (i) => ListTile(
|
||||
title: Text(credits[i][0]),
|
||||
subtitle: Text(credits[i][1]),
|
||||
ListTile(
|
||||
title: Text('exttex'),
|
||||
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: () {
|
||||
if (credits[i].length >= 3) {
|
||||
launch(credits[i][2]);
|
||||
}
|
||||
launch('https://codeberg.org/RemixDev/deemix');
|
||||
},
|
||||
)),
|
||||
),
|
||||
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(),
|
||||
...List.generate(translators.length, (i) => ListTile(
|
||||
title: Text(translators[i][0]),
|
||||
|
|
|
@ -205,18 +205,21 @@ class ArtistHorizontalTile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
artist.name,
|
||||
maxLines: 1,
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
artist.name,
|
||||
maxLines: 1,
|
||||
),
|
||||
leading: CachedImage(
|
||||
url: artist.picture.thumb,
|
||||
circular: true,
|
||||
),
|
||||
onTap: onTap,
|
||||
onLongPress: onHold,
|
||||
trailing: trailing,
|
||||
),
|
||||
leading: CachedImage(
|
||||
url: artist.picture.thumb,
|
||||
circular: true,
|
||||
),
|
||||
onTap: onTap,
|
||||
onLongPress: onHold,
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.5.2+1
|
||||
version: 0.5.3+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.8.0 <3.0.0"
|
||||
|
|
Loading…
Reference in New Issue