0.5.6 - Android Auto updates, option to disable nomedia, shuffle fix, minor fixes
This commit is contained in:
parent
11d93482ff
commit
e775e74d8e
@ -306,12 +306,15 @@ public class Deezer {
|
|||||||
tag.setField(FieldKey.DISC_NO, Integer.toString(publicTrack.getInt("disk_number")));
|
tag.setField(FieldKey.DISC_NO, Integer.toString(publicTrack.getInt("disk_number")));
|
||||||
tag.setField(FieldKey.ALBUM_ARTIST, publicAlbum.getJSONObject("artist").getString("name"));
|
tag.setField(FieldKey.ALBUM_ARTIST, publicAlbum.getJSONObject("artist").getString("name"));
|
||||||
tag.setField(FieldKey.YEAR, publicTrack.getString("release_date").substring(0, 4));
|
tag.setField(FieldKey.YEAR, publicTrack.getString("release_date").substring(0, 4));
|
||||||
tag.setField(FieldKey.BPM, Integer.toString((int)publicTrack.getDouble("bpm")));
|
|
||||||
tag.setField(FieldKey.RECORD_LABEL, publicAlbum.getString("label"));
|
tag.setField(FieldKey.RECORD_LABEL, publicAlbum.getString("label"));
|
||||||
tag.setField(FieldKey.ISRC, publicTrack.getString("isrc"));
|
tag.setField(FieldKey.ISRC, publicTrack.getString("isrc"));
|
||||||
tag.setField(FieldKey.BARCODE, publicAlbum.getString("upc"));
|
tag.setField(FieldKey.BARCODE, publicAlbum.getString("upc"));
|
||||||
tag.setField(FieldKey.TRACK_TOTAL, Integer.toString(publicAlbum.getInt("nb_tracks")));
|
tag.setField(FieldKey.TRACK_TOTAL, Integer.toString(publicAlbum.getInt("nb_tracks")));
|
||||||
|
|
||||||
|
//BPM
|
||||||
|
if (publicTrack.has("bpm") && (int)publicTrack.getDouble("bpm") > 0)
|
||||||
|
tag.setField(FieldKey.BPM, Integer.toString((int)publicTrack.getDouble("bpm")));
|
||||||
|
|
||||||
//Unsynced lyrics
|
//Unsynced lyrics
|
||||||
if (lyricsData != null) {
|
if (lyricsData != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -610,6 +610,7 @@ public class DownloadService extends Service {
|
|||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
//Create .nomedia to not spam gallery
|
//Create .nomedia to not spam gallery
|
||||||
|
if (settings.nomediaFiles)
|
||||||
new File(parentDir, ".nomedia").createNewFile();
|
new File(parentDir, ".nomedia").createNewFile();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("Error downloading album cover! " + e.toString(), download);
|
logger.warn("Error downloading album cover! " + e.toString(), download);
|
||||||
@ -826,19 +827,21 @@ public class DownloadService extends Service {
|
|||||||
boolean trackCover;
|
boolean trackCover;
|
||||||
String arl;
|
String arl;
|
||||||
boolean albumCover;
|
boolean albumCover;
|
||||||
|
boolean nomediaFiles;
|
||||||
|
|
||||||
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover, String arl, boolean albumCover) {
|
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover, String arl, boolean albumCover, boolean nomediaFiles) {
|
||||||
this.downloadThreads = downloadThreads;
|
this.downloadThreads = downloadThreads;
|
||||||
this.overwriteDownload = overwriteDownload;
|
this.overwriteDownload = overwriteDownload;
|
||||||
this.downloadLyrics = downloadLyrics;
|
this.downloadLyrics = downloadLyrics;
|
||||||
this.trackCover = trackCover;
|
this.trackCover = trackCover;
|
||||||
this.arl = arl;
|
this.arl = arl;
|
||||||
this.albumCover = albumCover;
|
this.albumCover = albumCover;
|
||||||
|
this.nomediaFiles = nomediaFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse settings from bundle sent from UI
|
//Parse settings from bundle sent from UI
|
||||||
static DownloadSettings fromBundle(Bundle b) {
|
static DownloadSettings fromBundle(Bundle b) {
|
||||||
return new DownloadSettings(b.getInt("downloadThreads"), b.getBoolean("overwriteDownload"), b.getBoolean("downloadLyrics"), b.getBoolean("trackCover"), b.getString("arl"), b.getBoolean("albumCover"));
|
return new DownloadSettings(b.getInt("downloadThreads"), b.getBoolean("overwriteDownload"), b.getBoolean("downloadLyrics"), b.getBoolean("trackCover"), b.getString("arl"), b.getBoolean("albumCover"), b.getBoolean("nomediaFiles"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,16 @@ public class MainActivity extends FlutterActivity {
|
|||||||
Messenger activityMessenger;
|
Messenger activityMessenger;
|
||||||
SQLiteDatabase db;
|
SQLiteDatabase db;
|
||||||
|
|
||||||
|
//Data if started from intent
|
||||||
|
String intentPreload;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
Intent intent = getIntent();
|
||||||
|
intentPreload = intent.getStringExtra("preload");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||||
@ -118,6 +128,7 @@ public class MainActivity extends FlutterActivity {
|
|||||||
bundle.putBoolean("trackCover", (boolean)call.argument("trackCover"));
|
bundle.putBoolean("trackCover", (boolean)call.argument("trackCover"));
|
||||||
bundle.putString("arl", (String)call.argument("arl"));
|
bundle.putString("arl", (String)call.argument("arl"));
|
||||||
bundle.putBoolean("albumCover", (boolean)call.argument("albumCover"));
|
bundle.putBoolean("albumCover", (boolean)call.argument("albumCover"));
|
||||||
|
bundle.putBoolean("nomediaFiles", (boolean)call.argument("nomediaFiles"));
|
||||||
sendMessage(DownloadService.SERVICE_SETTINGS_UPDATE, bundle);
|
sendMessage(DownloadService.SERVICE_SETTINGS_UPDATE, bundle);
|
||||||
|
|
||||||
result.success(null);
|
result.success(null);
|
||||||
@ -163,6 +174,12 @@ public class MainActivity extends FlutterActivity {
|
|||||||
result.success(null);
|
result.success(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//If app was started with preload info (Android Auto)
|
||||||
|
if (call.method.equals("getPreloadInfo")) {
|
||||||
|
result.success(intentPreload);
|
||||||
|
intentPreload = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
result.error("0", "Not implemented!", "Not implemented!");
|
result.error("0", "Not implemented!", "Not implemented!");
|
||||||
})));
|
})));
|
||||||
|
@ -35,12 +35,11 @@ class DeezerAPI {
|
|||||||
"Connection": "keep-alive"
|
"Connection": "keep-alive"
|
||||||
};
|
};
|
||||||
Future _authorizing;
|
Future _authorizing;
|
||||||
|
Dio dio = Dio();
|
||||||
CookieJar _cookieJar = new CookieJar();
|
CookieJar _cookieJar = new CookieJar();
|
||||||
|
|
||||||
//Call private api
|
//Call private api
|
||||||
Future<Map<dynamic, dynamic>> callApi(String method, {Map<dynamic, dynamic> params, String gatewayInput}) async {
|
Future<Map<dynamic, dynamic>> callApi(String method, {Map<dynamic, dynamic> params, String gatewayInput}) async {
|
||||||
Dio dio = Dio();
|
|
||||||
|
|
||||||
//Add headers
|
//Add headers
|
||||||
dio.interceptors.add(InterceptorsWrapper(
|
dio.interceptors.add(InterceptorsWrapper(
|
||||||
@ -70,7 +69,6 @@ class DeezerAPI {
|
|||||||
'api_token': this.token,
|
'api_token': this.token,
|
||||||
'input': '3',
|
'input': '3',
|
||||||
'method': method,
|
'method': method,
|
||||||
|
|
||||||
//Used for homepage
|
//Used for homepage
|
||||||
if (gatewayInput != null)
|
if (gatewayInput != null)
|
||||||
'gateway_input': gatewayInput
|
'gateway_input': gatewayInput
|
||||||
|
@ -387,17 +387,17 @@ class DownloadManager {
|
|||||||
Future<SearchResults> search(String query) async {
|
Future<SearchResults> search(String query) async {
|
||||||
SearchResults results = SearchResults(tracks: [], albums: [], artists: [], playlists: []);
|
SearchResults results = SearchResults(tracks: [], albums: [], artists: [], playlists: []);
|
||||||
//Tracks
|
//Tracks
|
||||||
List tracksData = await db.rawQuery('SELECT * FROM tracks WHERE offline == 1 AND title like "%$query%"');
|
List tracksData = await db.rawQuery('SELECT * FROM Tracks WHERE offline == 1 AND title like "%$query%"');
|
||||||
for (Map trackData in tracksData) {
|
for (Map trackData in tracksData) {
|
||||||
results.tracks.add(await getOfflineTrack(trackData['id']));
|
results.tracks.add(await getOfflineTrack(trackData['id']));
|
||||||
}
|
}
|
||||||
//Albums
|
//Albums
|
||||||
List albumsData = await db.rawQuery('SELECT (id) FROM albums WHERE offline == 1 AND title like "%$query%"');
|
List albumsData = await db.rawQuery('SELECT (id) FROM Albums WHERE offline == 1 AND title like "%$query%"');
|
||||||
for (Map rawAlbum in albumsData) {
|
for (Map rawAlbum in albumsData) {
|
||||||
results.albums.add(await getOfflineAlbum(rawAlbum['id']));
|
results.albums.add(await getOfflineAlbum(rawAlbum['id']));
|
||||||
}
|
}
|
||||||
//Playlists
|
//Playlists
|
||||||
List playlists = await db.rawQuery('SELECT * FROM playlists WHERE title like "%$query%"');
|
List playlists = await db.rawQuery('SELECT * FROM Playlists WHERE title like "%$query%"');
|
||||||
for (Map playlist in playlists) {
|
for (Map playlist in playlists) {
|
||||||
results.playlists.add(await getPlaylist(playlist['id']));
|
results.playlists.add(await getPlaylist(playlist['id']));
|
||||||
}
|
}
|
||||||
@ -428,7 +428,7 @@ class DownloadManager {
|
|||||||
//Album folder / with disk number
|
//Album folder / with disk number
|
||||||
if (settings.albumFolder) {
|
if (settings.albumFolder) {
|
||||||
if (settings.albumDiscFolder) {
|
if (settings.albumDiscFolder) {
|
||||||
path = p.join(path, '%album%' + ' - Disk ' + (track.diskNumber??null).toString());
|
path = p.join(path, '%album%' + ' - Disk ' + (track.diskNumber??1).toString());
|
||||||
} else {
|
} else {
|
||||||
path = p.join(path, '%album%');
|
path = p.join(path, '%album%');
|
||||||
}
|
}
|
||||||
@ -450,9 +450,9 @@ class DownloadManager {
|
|||||||
//Get stats for library screen
|
//Get stats for library screen
|
||||||
Future<List<String>> getStats() async {
|
Future<List<String>> getStats() async {
|
||||||
//Get offline counts
|
//Get offline counts
|
||||||
int trackCount = (await db.rawQuery('SELECT COUNT(*) FROM tracks WHERE offline == 1'))[0]['COUNT(*)'];
|
int trackCount = (await db.rawQuery('SELECT COUNT(*) FROM Tracks WHERE offline == 1'))[0]['COUNT(*)'];
|
||||||
int albumCount = (await db.rawQuery('SELECT COUNT(*) FROM albums WHERE offline == 1'))[0]['COUNT(*)'];
|
int albumCount = (await db.rawQuery('SELECT COUNT(*) FROM Albums WHERE offline == 1'))[0]['COUNT(*)'];
|
||||||
int playlistCount = (await db.rawQuery('SELECT COUNT(*) FROM albums WHERE offline == 1'))[0]['COUNT(*)'];
|
int playlistCount = (await db.rawQuery('SELECT COUNT(*) FROM Playlists'))[0]['COUNT(*)'];
|
||||||
//Free space
|
//Free space
|
||||||
double diskSpace = await DiskSpace.getFreeDiskSpace;
|
double diskSpace = await DiskSpace.getFreeDiskSpace;
|
||||||
//Used space
|
//Used space
|
||||||
|
@ -28,8 +28,6 @@ class PlayerHelper {
|
|||||||
StreamSubscription _playbackStateStreamSubscription;
|
StreamSubscription _playbackStateStreamSubscription;
|
||||||
QueueSource queueSource;
|
QueueSource queueSource;
|
||||||
LoopMode repeatType = LoopMode.off;
|
LoopMode repeatType = LoopMode.off;
|
||||||
bool shuffle = false;
|
|
||||||
|
|
||||||
//Find queue index by id
|
//Find queue index by id
|
||||||
int get queueIndex => AudioService.queue.indexWhere((mi) => mi.id == AudioService.currentMediaItem?.id??'Random string so it returns -1');
|
int get queueIndex => AudioService.queue.indexWhere((mi) => mi.id == AudioService.currentMediaItem?.id??'Random string so it returns -1');
|
||||||
|
|
||||||
@ -49,8 +47,8 @@ class PlayerHelper {
|
|||||||
}
|
}
|
||||||
if (event['action'] == 'queueEnd') {
|
if (event['action'] == 'queueEnd') {
|
||||||
//If last song is played, load more queue
|
//If last song is played, load more queue
|
||||||
onQueueEnd();
|
|
||||||
this.queueSource = QueueSource.fromJson(event['queueSource']);
|
this.queueSource = QueueSource.fromJson(event['queueSource']);
|
||||||
|
onQueueEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//Android auto get screen
|
//Android auto get screen
|
||||||
@ -110,8 +108,7 @@ class PlayerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future toggleShuffle() async {
|
Future toggleShuffle() async {
|
||||||
this.shuffle = !this.shuffle;
|
await AudioService.customAction('shuffle');
|
||||||
await AudioService.customAction('shuffle', this.shuffle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Repeat toggle
|
//Repeat toggle
|
||||||
@ -148,6 +145,7 @@ class PlayerHelper {
|
|||||||
Future onQueueEnd() async {
|
Future onQueueEnd() async {
|
||||||
//Flow
|
//Flow
|
||||||
if (queueSource == null) return;
|
if (queueSource == null) return;
|
||||||
|
print('test');
|
||||||
if (queueSource.id == 'flow') {
|
if (queueSource.id == 'flow') {
|
||||||
List<Track> tracks = await deezerAPI.flow();
|
List<Track> tracks = await deezerAPI.flow();
|
||||||
List<MediaItem> mi = tracks.map<MediaItem>((t) => t.toMediaItem()).toList();
|
List<MediaItem> mi = tracks.map<MediaItem>((t) => t.toMediaItem()).toList();
|
||||||
@ -246,7 +244,6 @@ 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;
|
||||||
|
|
||||||
@ -294,6 +291,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
switch(state) {
|
switch(state) {
|
||||||
case ProcessingState.completed:
|
case ProcessingState.completed:
|
||||||
//Player ended, get more songs
|
//Player ended, get more songs
|
||||||
|
if (_queueIndex == _queue.length - 1)
|
||||||
AudioServiceBackground.sendCustomEvent({
|
AudioServiceBackground.sendCustomEvent({
|
||||||
'action': 'queueEnd',
|
'action': 'queueEnd',
|
||||||
'queueSource': (queueSource??QueueSource()).toJson()
|
'queueSource': (queueSource??QueueSource()).toJson()
|
||||||
@ -320,7 +318,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
//Calculate new index
|
//Calculate new index
|
||||||
final newIndex = _queue.indexWhere((i) => i.id == mediaId);
|
final newIndex = _queue.indexWhere((i) => i.id == mediaId);
|
||||||
if (newIndex == -1) return;
|
if (newIndex == -1) return;
|
||||||
|
|
||||||
//Update buffering state
|
//Update buffering state
|
||||||
_skipState = newIndex > _queueIndex
|
_skipState = newIndex > _queueIndex
|
||||||
? AudioProcessingState.skippingToNext
|
? AudioProcessingState.skippingToNext
|
||||||
@ -362,22 +359,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onSkipToNext() async {
|
Future<void> onSkipToNext() async {
|
||||||
//Shuffle
|
print('skipping');
|
||||||
if (_player.shuffleModeEnabled??false) {
|
if (_queueIndex == _queue.length-1) return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Update buffering state
|
//Update buffering state
|
||||||
_skipState = AudioProcessingState.skippingToNext;
|
_skipState = AudioProcessingState.skippingToNext;
|
||||||
_queueIndex++;
|
_queueIndex++;
|
||||||
@ -388,23 +371,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onSkipToPrevious() async {
|
Future<void> onSkipToPrevious() async {
|
||||||
if (_queueIndex == 0 && !(_player.shuffleModeEnabled??false)) return;
|
if (_queueIndex == 0) 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
|
//Normal skip to previous
|
||||||
_queueIndex--;
|
_queueIndex--;
|
||||||
await _player.seekToPrevious();
|
await _player.seekToPrevious();
|
||||||
@ -582,7 +552,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
await this._loadQueueFile();
|
await this._loadQueueFile();
|
||||||
//Shuffle
|
//Shuffle
|
||||||
if (name == 'shuffle') {
|
if (name == 'shuffle') {
|
||||||
await _player.setShuffleModeEnabled(args);
|
_queue.shuffle();
|
||||||
|
AudioServiceBackground.setQueue(_queue);
|
||||||
|
_queueIndex = 0;
|
||||||
|
await _loadQueue();
|
||||||
}
|
}
|
||||||
//Android auto callback
|
//Android auto callback
|
||||||
if (name == 'screenAndroidAuto' && _androidAutoCallback != null) {
|
if (name == 'screenAndroidAuto' && _androidAutoCallback != null) {
|
||||||
|
1
lib/languages/crowdin.dart
Normal file
1
lib/languages/crowdin.dart
Normal file
File diff suppressed because one or more lines are too long
@ -234,6 +234,10 @@ const language_en_us = {
|
|||||||
"Share": "Share",
|
"Share": "Share",
|
||||||
"Save album cover": "Save album cover",
|
"Save album cover": "Save album cover",
|
||||||
"Warning": "Warning",
|
"Warning": "Warning",
|
||||||
"Using too many concurrent downloads on older/weaker devices might cause crashes!": "Using too many concurrent downloads on older/weaker devices might cause crashes!"
|
"Using too many concurrent downloads on older/weaker devices might cause crashes!": "Using too many concurrent downloads on older/weaker devices might cause crashes!",
|
||||||
|
|
||||||
|
//0.5.6 Strings:
|
||||||
|
"Create .nomedia files": "Create .nomedia files",
|
||||||
|
"To prevent gallery being filled with album art": "To prevent gallery being filled with album art"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:freezer/api/cache.dart';
|
import 'package:freezer/api/cache.dart';
|
||||||
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/ui/library.dart';
|
import 'package:freezer/ui/library.dart';
|
||||||
import 'package:freezer/ui/login_screen.dart';
|
import 'package:freezer/ui/login_screen.dart';
|
||||||
import 'package:freezer/ui/search.dart';
|
import 'package:freezer/ui/search.dart';
|
||||||
@ -160,9 +161,28 @@ class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateM
|
|||||||
//Setup URLs
|
//Setup URLs
|
||||||
setupUniLinks();
|
setupUniLinks();
|
||||||
|
|
||||||
|
_loadPreloadInfo();
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _loadPreloadInfo() async {
|
||||||
|
String info = await DownloadManager.platform.invokeMethod('getPreloadInfo');
|
||||||
|
if (info != null) {
|
||||||
|
//Used if started from android auto
|
||||||
|
|
||||||
|
await deezerAPI.authorize();
|
||||||
|
if (info == 'flow') {
|
||||||
|
await playerHelper.playFromSmartTrackList(SmartTrackList(id: 'flow'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info == 'favorites') {
|
||||||
|
Playlist p = await deezerAPI.fullPlaylist(deezerAPI.favoritesPlaylistId);
|
||||||
|
playerHelper.playFromPlaylist(p, p.tracks[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
if (_urlLinkStream != null)
|
if (_urlLinkStream != null)
|
||||||
|
@ -62,6 +62,8 @@ class Settings {
|
|||||||
bool trackCover;
|
bool trackCover;
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool albumCover;
|
bool albumCover;
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool nomediaFiles;
|
||||||
|
|
||||||
//Appearance
|
//Appearance
|
||||||
@JsonKey(defaultValue: Themes.Dark)
|
@JsonKey(defaultValue: Themes.Dark)
|
||||||
@ -114,7 +116,8 @@ class Settings {
|
|||||||
"downloadLyrics": downloadLyrics,
|
"downloadLyrics": downloadLyrics,
|
||||||
"trackCover": trackCover,
|
"trackCover": trackCover,
|
||||||
"arl": arl,
|
"arl": arl,
|
||||||
"albumCover": albumCover
|
"albumCover": albumCover,
|
||||||
|
"nomediaFiles": nomediaFiles
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
|||||||
..downloadLyrics = json['downloadLyrics'] as bool ?? true
|
..downloadLyrics = json['downloadLyrics'] as bool ?? true
|
||||||
..trackCover = json['trackCover'] as bool ?? false
|
..trackCover = json['trackCover'] as bool ?? false
|
||||||
..albumCover = json['albumCover'] as bool ?? true
|
..albumCover = json['albumCover'] as bool ?? true
|
||||||
|
..nomediaFiles = json['nomediaFiles'] as bool ?? false
|
||||||
..theme =
|
..theme =
|
||||||
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Dark
|
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Dark
|
||||||
..useSystemTheme = json['useSystemTheme'] as bool ?? false
|
..useSystemTheme = json['useSystemTheme'] as bool ?? false
|
||||||
@ -64,6 +65,7 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
|||||||
'downloadLyrics': instance.downloadLyrics,
|
'downloadLyrics': instance.downloadLyrics,
|
||||||
'trackCover': instance.trackCover,
|
'trackCover': instance.trackCover,
|
||||||
'albumCover': instance.albumCover,
|
'albumCover': instance.albumCover,
|
||||||
|
'nomediaFiles': instance.nomediaFiles,
|
||||||
'theme': _$ThemesEnumMap[instance.theme],
|
'theme': _$ThemesEnumMap[instance.theme],
|
||||||
'useSystemTheme': instance.useSystemTheme,
|
'useSystemTheme': instance.useSystemTheme,
|
||||||
'primaryColor': Settings._colorToJson(instance.primaryColor),
|
'primaryColor': Settings._colorToJson(instance.primaryColor),
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/languages/ar_ar.dart';
|
import 'package:freezer/languages/crowdin.dart';
|
||||||
import 'package:freezer/languages/de_de.dart';
|
|
||||||
import 'package:freezer/languages/el_gr.dart';
|
|
||||||
import 'package:freezer/languages/en_us.dart';
|
import 'package:freezer/languages/en_us.dart';
|
||||||
import 'package:freezer/languages/es_es.dart';
|
|
||||||
import 'package:freezer/languages/fil_ph.dart';
|
|
||||||
import 'package:freezer/languages/fr_fr.dart';
|
|
||||||
import 'package:freezer/languages/he_il.dart';
|
|
||||||
import 'package:freezer/languages/hr_hr.dart';
|
|
||||||
import 'package:freezer/languages/id_id.dart';
|
|
||||||
import 'package:freezer/languages/it_it.dart';
|
|
||||||
import 'package:freezer/languages/ko_ko.dart';
|
|
||||||
import 'package:freezer/languages/pt_br.dart';
|
|
||||||
import 'package:freezer/languages/ro_ro.dart';
|
|
||||||
import 'package:freezer/languages/ru_ru.dart';
|
|
||||||
import 'package:freezer/languages/tr_tr.dart';
|
|
||||||
import 'package:i18n_extension/i18n_extension.dart';
|
import 'package:i18n_extension/i18n_extension.dart';
|
||||||
|
|
||||||
const supportedLocales = [
|
const supportedLocales = [
|
||||||
@ -33,14 +19,13 @@ const supportedLocales = [
|
|||||||
const Locale('tr', 'TR'),
|
const Locale('tr', 'TR'),
|
||||||
const Locale('ro', 'RO'),
|
const Locale('ro', 'RO'),
|
||||||
const Locale('id', 'ID'),
|
const Locale('id', 'ID'),
|
||||||
|
const Locale('fa', 'IR'),
|
||||||
|
const Locale('pl', 'PL'),
|
||||||
const Locale('fil', 'PH')
|
const Locale('fil', 'PH')
|
||||||
];
|
];
|
||||||
|
|
||||||
extension Localization on String {
|
extension Localization on String {
|
||||||
static var _t = Translations.byLocale("en_US") +
|
static var _t = Translations.byLocale("en_US") + language_en_us + crowdin;
|
||||||
language_en_us + language_ar_ar + language_pt_br + language_it_it + language_de_de + language_ru_ru +
|
|
||||||
language_fil_ph + language_es_es + language_el_gr + language_hr_hr + language_ko_ko + language_fr_fr +
|
|
||||||
language_he_il + language_tr_tr + language_ro_ro + language_id_id;
|
|
||||||
|
|
||||||
String get i18n => localize(this, _t);
|
String get i18n => localize(this, _t);
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,15 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Downloads'.i18n),
|
title: Text('Downloads'.i18n),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.delete_sweep),
|
||||||
|
onPressed: () async {
|
||||||
|
await downloadManager.removeDownloads(DownloadState.ERROR);
|
||||||
|
await downloadManager.removeDownloads(DownloadState.DEEZER_ERROR);
|
||||||
|
await downloadManager.removeDownloads(DownloadState.DONE);
|
||||||
|
await _load();
|
||||||
|
},
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon:
|
icon:
|
||||||
Icon(downloadManager.running ? Icons.stop : Icons.play_arrow),
|
Icon(downloadManager.running ? Icons.stop : Icons.play_arrow),
|
||||||
|
@ -383,6 +383,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Container(width: 8.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
@ -573,6 +574,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Container(width: 8.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
@ -745,7 +747,8 @@ class _LibraryArtistsState extends State<LibraryArtists> {
|
|||||||
child: Text('Popularity'.i18n),
|
child: Text('Popularity'.i18n),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
|
Container(width: 8.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
@ -888,7 +891,8 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
|||||||
child: Text('Alphabetic'.i18n),
|
child: Text('Alphabetic'.i18n),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
|
Container(width: 8.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
|
@ -30,6 +30,7 @@ class PlayerBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
//Left
|
//Left
|
||||||
if (details.delta.dx < -sensitivity) {
|
if (details.delta.dx < -sensitivity) {
|
||||||
|
|
||||||
await AudioService.skipToNext();
|
await AudioService.skipToNext();
|
||||||
}
|
}
|
||||||
_gestureRegistered = false;
|
_gestureRegistered = false;
|
||||||
|
@ -71,25 +71,6 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|||||||
|
|
||||||
double iconSize = ScreenUtil().setWidth(64);
|
double iconSize = ScreenUtil().setWidth(64);
|
||||||
bool _lyrics = false;
|
bool _lyrics = false;
|
||||||
PageController _pageController = PageController(
|
|
||||||
initialPage: playerHelper.queueIndex,
|
|
||||||
);
|
|
||||||
StreamSubscription _currentItemSub;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_currentItemSub = AudioService.currentMediaItemStream.listen((event) {
|
|
||||||
_pageController.animateToPage(playerHelper.queueIndex, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (_currentItemSub != null)
|
|
||||||
_currentItemSub.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -103,16 +84,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
|
|||||||
width: ScreenUtil().setWidth(500),
|
width: ScreenUtil().setWidth(500),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
PageView(
|
BigAlbumArt(),
|
||||||
controller: _pageController,
|
|
||||||
onPageChanged: (int index) {
|
|
||||||
AudioService.skipToQueueItem(AudioService.queue[index].id);
|
|
||||||
},
|
|
||||||
children: List.generate(AudioService.queue.length, (i) => CachedImage(
|
|
||||||
url: AudioService.queue[i].artUri,
|
|
||||||
fullThumb: true,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
if (_lyrics) LyricsWidget(
|
if (_lyrics) LyricsWidget(
|
||||||
artUri: AudioService.currentMediaItem.extras['thumb'],
|
artUri: AudioService.currentMediaItem.extras['thumb'],
|
||||||
trackId: AudioService.currentMediaItem.id,
|
trackId: AudioService.currentMediaItem.id,
|
||||||
@ -251,25 +223,6 @@ class PlayerScreenVertical extends StatefulWidget {
|
|||||||
class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
||||||
double iconSize = ScreenUtil().setWidth(100);
|
double iconSize = ScreenUtil().setWidth(100);
|
||||||
bool _lyrics = false;
|
bool _lyrics = false;
|
||||||
PageController _pageController = PageController(
|
|
||||||
initialPage: playerHelper.queueIndex,
|
|
||||||
);
|
|
||||||
StreamSubscription _currentItemSub;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_currentItemSub = AudioService.currentMediaItemStream.listen((event) {
|
|
||||||
_pageController.animateToPage(playerHelper.queueIndex, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
if (_currentItemSub != null)
|
|
||||||
_currentItemSub.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -287,16 +240,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||||||
height: ScreenUtil().setHeight(1050),
|
height: ScreenUtil().setHeight(1050),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
PageView(
|
BigAlbumArt(),
|
||||||
controller: _pageController,
|
|
||||||
onPageChanged: (int index) {
|
|
||||||
AudioService.skipToQueueItem(AudioService.queue[index].id);
|
|
||||||
},
|
|
||||||
children: List.generate(AudioService.queue.length, (i) => CachedImage(
|
|
||||||
url: AudioService.queue[i].artUri,
|
|
||||||
fullThumb: true,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
if (_lyrics) LyricsWidget(
|
if (_lyrics) LyricsWidget(
|
||||||
artUri: AudioService.currentMediaItem.extras['thumb'],
|
artUri: AudioService.currentMediaItem.extras['thumb'],
|
||||||
trackId: AudioService.currentMediaItem.id,
|
trackId: AudioService.currentMediaItem.id,
|
||||||
@ -398,6 +342,51 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BigAlbumArt extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_BigAlbumArtState createState() => _BigAlbumArtState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BigAlbumArtState extends State<BigAlbumArt> {
|
||||||
|
|
||||||
|
PageController _pageController = PageController(
|
||||||
|
initialPage: playerHelper.queueIndex,
|
||||||
|
);
|
||||||
|
StreamSubscription _currentItemSub;
|
||||||
|
bool _animationLock = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_currentItemSub = AudioService.currentMediaItemStream.listen((event) async {
|
||||||
|
_animationLock = true;
|
||||||
|
await _pageController.animateToPage(playerHelper.queueIndex, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
|
||||||
|
_animationLock = false;
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (_currentItemSub != null)
|
||||||
|
_currentItemSub.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PageView(
|
||||||
|
controller: _pageController,
|
||||||
|
onPageChanged: (int index) {
|
||||||
|
if (_animationLock) return;
|
||||||
|
AudioService.skipToQueueItem(AudioService.queue[index].id);
|
||||||
|
},
|
||||||
|
children: List.generate(AudioService.queue.length, (i) => CachedImage(
|
||||||
|
url: AudioService.queue[i].artUri,
|
||||||
|
fullThumb: true,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class LyricsWidget extends StatefulWidget {
|
class LyricsWidget extends StatefulWidget {
|
||||||
@ -773,17 +762,6 @@ class QueueScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _QueueScreenState extends State<QueueScreen> {
|
class _QueueScreenState extends State<QueueScreen> {
|
||||||
|
|
||||||
//Get proper icon color by theme
|
|
||||||
Color get shuffleIconColor {
|
|
||||||
Color og = Theme.of(context).primaryColor;
|
|
||||||
if (og.computeLuminance() > 0.5) {
|
|
||||||
if (playerHelper.shuffle) return Theme.of(context).primaryColorLight;
|
|
||||||
return Colors.black;
|
|
||||||
}
|
|
||||||
if (playerHelper.shuffle) return Theme.of(context).primaryColorDark;
|
|
||||||
return Colors.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -793,7 +771,6 @@ class _QueueScreenState extends State<QueueScreen> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.shuffle,
|
Icons.shuffle,
|
||||||
color: shuffleIconColor
|
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await playerHelper.toggleShuffle();
|
await playerHelper.toggleShuffle();
|
||||||
|
@ -803,6 +803,20 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('Create .nomedia files'.i18n),
|
||||||
|
subtitle: Text('To prevent gallery being filled with album art'.i18n),
|
||||||
|
leading: Container(
|
||||||
|
width: 30.0,
|
||||||
|
child: Checkbox(
|
||||||
|
value: settings.nomediaFiles,
|
||||||
|
onChanged: (v) {
|
||||||
|
setState(() => settings.nomediaFiles = v);
|
||||||
|
settings.save();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Download Log'.i18n),
|
title: Text('Download Log'.i18n),
|
||||||
|
@ -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.5+1
|
version: 0.5.6+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.8.0 <3.0.0"
|
sdk: ">=2.8.0 <3.0.0"
|
||||||
@ -27,11 +27,11 @@ dependencies:
|
|||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
dio: ^3.0.9
|
dio: ^3.0.10
|
||||||
dio_cookie_manager: ^1.0.0
|
dio_cookie_manager: ^1.0.0
|
||||||
cookie_jar: ^1.0.1
|
cookie_jar: ^1.0.1
|
||||||
json_annotation: ^3.0.1
|
json_annotation: ^3.0.1
|
||||||
path_provider: ^1.6.9
|
path_provider: 1.6.10
|
||||||
path: ^1.6.4
|
path: ^1.6.4
|
||||||
sqflite: ^1.3.0+1
|
sqflite: ^1.3.0+1
|
||||||
crypto: ^2.1.4
|
crypto: ^2.1.4
|
||||||
@ -69,7 +69,7 @@ dependencies:
|
|||||||
uni_links: ^0.4.0
|
uni_links: ^0.4.0
|
||||||
share: ^0.6.5+2
|
share: ^0.6.5+2
|
||||||
|
|
||||||
audio_session: ^0.0.7
|
audio_session: ^0.0.9
|
||||||
audio_service:
|
audio_service:
|
||||||
path: ./audio_service
|
path: ./audio_service
|
||||||
just_audio:
|
just_audio:
|
||||||
|
40
translations/crowdin.py
Normal file
40
translations/crowdin.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import zipfile
|
||||||
|
import json
|
||||||
|
|
||||||
|
lang_crowdin = {
|
||||||
|
'ar': 'ar_ar',
|
||||||
|
'de': 'de_de',
|
||||||
|
'el': 'el_gr',
|
||||||
|
'es-ES': 'es_es',
|
||||||
|
'fa': 'fa_ir',
|
||||||
|
'fil': 'fil_ph',
|
||||||
|
'fr': 'fr_fr',
|
||||||
|
'he': 'he_il',
|
||||||
|
'hr': 'hr_hr',
|
||||||
|
'id': 'id_id',
|
||||||
|
'it': 'it_id',
|
||||||
|
'ko': 'ko_ko',
|
||||||
|
'pt-BR': 'pt_br',
|
||||||
|
'ro': 'ro_ro',
|
||||||
|
'ru': 'ru_ru',
|
||||||
|
'tr': 'tr_tr',
|
||||||
|
'pl': 'pl_pl'
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_dart():
|
||||||
|
out = {}
|
||||||
|
with zipfile.ZipFile('translations.zip') as zip:
|
||||||
|
for file in zip.namelist():
|
||||||
|
if 'freezer.json' in file:
|
||||||
|
data = zip.open(file).read()
|
||||||
|
lang = file.split('/')[0]
|
||||||
|
out[lang_crowdin[lang]] = json.loads(data)
|
||||||
|
|
||||||
|
with open('crowdin.dart', 'w') as f:
|
||||||
|
data = json.dumps(out, ensure_ascii=False).replace('$', '\\$')
|
||||||
|
out = f'const crowdin = {data};'
|
||||||
|
f.write(out)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
generate_dart()
|
226
translations/freezer.json
Normal file
226
translations/freezer.json
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
{
|
||||||
|
"Home": "Home",
|
||||||
|
"Search": "Search",
|
||||||
|
"Library": "Library",
|
||||||
|
"Offline mode, can't play flow or smart track lists.":
|
||||||
|
"Offline mode, can't play flow or smart track lists.",
|
||||||
|
"Added to library": "Added to library",
|
||||||
|
"Download": "Download",
|
||||||
|
"Disk": "Disk",
|
||||||
|
"Offline": "Offline",
|
||||||
|
"Top Tracks": "Top Tracks",
|
||||||
|
"Show more tracks": "Show more tracks",
|
||||||
|
"Top": "Top",
|
||||||
|
"Top Albums": "Top Albums",
|
||||||
|
"Show all albums": "Show all albums",
|
||||||
|
"Discography": "Discography",
|
||||||
|
"Default": "Default",
|
||||||
|
"Reverse": "Reverse",
|
||||||
|
"Alphabetic": "Alphabetic",
|
||||||
|
"Artist": "Artist",
|
||||||
|
"Post processing...": "Post processing...",
|
||||||
|
"Done": "Done",
|
||||||
|
"Delete": "Delete",
|
||||||
|
"Are you sure you want to delete this download?":
|
||||||
|
"Are you sure you want to delete this download?",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Downloads": "Downloads",
|
||||||
|
"Clear queue": "Clear queue",
|
||||||
|
"This won't delete currently downloading item":
|
||||||
|
"This won't delete currently downloading item",
|
||||||
|
"Are you sure you want to delete all queued downloads?":
|
||||||
|
"Are you sure you want to delete all queued downloads?",
|
||||||
|
"Clear downloads history": "Clear downloads history",
|
||||||
|
"WARNING: This will only clear non-offline (external downloads)":
|
||||||
|
"WARNING: This will only clear non-offline (external downloads)",
|
||||||
|
"Please check your connection and try again later...":
|
||||||
|
"Please check your connection and try again later...",
|
||||||
|
"Show more": "Show more",
|
||||||
|
"Importer": "Importer",
|
||||||
|
"Currently supporting only Spotify, with 100 tracks limit":
|
||||||
|
"Currently supporting only Spotify, with 100 tracks limit",
|
||||||
|
"Due to API limitations": "Due to API limitations",
|
||||||
|
"Enter your playlist link below": "Enter your playlist link below",
|
||||||
|
"Error loading URL!": "Error loading URL!",
|
||||||
|
"Convert": "Convert",
|
||||||
|
"Download only": "Download only",
|
||||||
|
"Downloading is currently stopped, click here to resume.":
|
||||||
|
"Downloading is currently stopped, click here to resume.",
|
||||||
|
"Tracks": "Tracks",
|
||||||
|
"Albums": "Albums",
|
||||||
|
"Artists": "Artists",
|
||||||
|
"Playlists": "Playlists",
|
||||||
|
"Import": "Import",
|
||||||
|
"Import playlists from Spotify": "Import playlists from Spotify",
|
||||||
|
"Statistics": "Statistics",
|
||||||
|
"Offline tracks": "Offline tracks",
|
||||||
|
"Offline albums": "Offline albums",
|
||||||
|
"Offline playlists": "Offline playlists",
|
||||||
|
"Offline size": "Offline size",
|
||||||
|
"Free space": "Free space",
|
||||||
|
"Loved tracks": "Loved tracks",
|
||||||
|
"Favorites": "Favorites",
|
||||||
|
"All offline tracks": "All offline tracks",
|
||||||
|
"Create new playlist": "Create new playlist",
|
||||||
|
"Cannot create playlists in offline mode":
|
||||||
|
"Cannot create playlists in offline mode",
|
||||||
|
"Error": "Error",
|
||||||
|
"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.",
|
||||||
|
"Dismiss": "Dismiss",
|
||||||
|
"Welcome to": "Welcome to",
|
||||||
|
"Please login using your Deezer account.":
|
||||||
|
"Please login using your Deezer account.",
|
||||||
|
"Login using browser": "Login using browser",
|
||||||
|
"Login using token": "Login using token",
|
||||||
|
"Enter ARL": "Enter ARL",
|
||||||
|
"Token (ARL)": "Token (ARL)",
|
||||||
|
"Save": "Save",
|
||||||
|
"If you don't have account, you can register on deezer.com for free.":
|
||||||
|
"If you don't have account, you can register on deezer.com for free.",
|
||||||
|
"Open in browser": "Open in browser",
|
||||||
|
"By using this app, you don't agree with the Deezer ToS":
|
||||||
|
"By using this app, you don't agree with the Deezer ToS",
|
||||||
|
"Play next": "Play next",
|
||||||
|
"Add to queue": "Add to queue",
|
||||||
|
"Add track to favorites": "Add track to favorites",
|
||||||
|
"Add to playlist": "Add to playlist",
|
||||||
|
"Select playlist": "Select playlist",
|
||||||
|
"Track added to": "Track added to",
|
||||||
|
"Remove from playlist": "Remove from playlist",
|
||||||
|
"Track removed from": "Track removed from",
|
||||||
|
"Remove favorite": "Remove favorite",
|
||||||
|
"Track removed from library": "Track removed from library",
|
||||||
|
"Go to": "Go to",
|
||||||
|
"Make offline": "Make offline",
|
||||||
|
"Add to library": "Add to library",
|
||||||
|
"Remove album": "Remove album",
|
||||||
|
"Album removed": "Album removed",
|
||||||
|
"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",
|
||||||
|
"Added playlist to library": "Added playlist to library",
|
||||||
|
"Make playlist offline": "Make playlist offline",
|
||||||
|
"Download playlist": "Download playlist",
|
||||||
|
"Create playlist": "Create playlist",
|
||||||
|
"Title": "Title",
|
||||||
|
"Description": "Description",
|
||||||
|
"Private": "Private",
|
||||||
|
"Collaborative": "Collaborative",
|
||||||
|
"Create": "Create",
|
||||||
|
"Playlist created!": "Playlist created!",
|
||||||
|
"Playing from:": "Playing from:",
|
||||||
|
"Queue": "Queue",
|
||||||
|
"Offline search": "Offline search",
|
||||||
|
"Search Results": "Search Results",
|
||||||
|
"No results!": "No results!",
|
||||||
|
"Show all tracks": "Show all tracks",
|
||||||
|
"Show all playlists": "Show all playlists",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"General": "General",
|
||||||
|
"Appearance": "Appearance",
|
||||||
|
"Quality": "Quality",
|
||||||
|
"Deezer": "Deezer",
|
||||||
|
"Theme": "Theme",
|
||||||
|
"Currently": "Currently",
|
||||||
|
"Select theme": "Select theme",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Black (AMOLED)": "Black (AMOLED)",
|
||||||
|
"Deezer (Dark)": "Deezer (Dark)",
|
||||||
|
"Primary color": "Primary color",
|
||||||
|
"Selected color": "Selected color",
|
||||||
|
"Use album art primary color": "Use album art primary color",
|
||||||
|
"Warning: might be buggy": "Warning: might be buggy",
|
||||||
|
"Mobile streaming": "Mobile streaming",
|
||||||
|
"Wifi streaming": "Wifi streaming",
|
||||||
|
"External downloads": "External downloads",
|
||||||
|
"Content language": "Content language",
|
||||||
|
"Not app language, used in headers. Now":
|
||||||
|
"Not app language, used in headers. Now",
|
||||||
|
"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",
|
||||||
|
"Offline mode": "Offline mode",
|
||||||
|
"Will be overwritten on start.": "Will be overwritten on start.",
|
||||||
|
"Error logging in, check your internet connections.":
|
||||||
|
"Error logging in, check your internet connections.",
|
||||||
|
"Logging in...": "Logging in...",
|
||||||
|
"Download path": "Download path",
|
||||||
|
"Downloads naming": "Downloads naming",
|
||||||
|
"Downloaded tracks filename": "Downloaded tracks filename",
|
||||||
|
"Valid variables are": "Valid variables are",
|
||||||
|
"Reset": "Reset",
|
||||||
|
"Clear": "Clear",
|
||||||
|
"Create folders for artist": "Create folders for artist",
|
||||||
|
"Create folders for albums": "Create folders for albums",
|
||||||
|
"Separate albums by discs": "Separate albums by disks",
|
||||||
|
"Overwrite already downloaded files": "Overwrite already downloaded files",
|
||||||
|
"Copy ARL": "Copy ARL",
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.":
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.",
|
||||||
|
"Copied": "Copied",
|
||||||
|
"Log out": "Log out",
|
||||||
|
"Due to plugin incompatibility, login using browser is unavailable without restart.":
|
||||||
|
"Due to plugin incompatibility, login using browser is unavailable without restart.",
|
||||||
|
"(ARL ONLY) Continue": "(ARL ONLY) Continue",
|
||||||
|
"Log out & Exit": "Log out & Exit",
|
||||||
|
"Pick-a-Path": "Pick-a-Path",
|
||||||
|
"Select storage": "Select storage",
|
||||||
|
"Go up": "Go up",
|
||||||
|
"Permission denied": "Permission denied",
|
||||||
|
"Language": "Language",
|
||||||
|
"Language changed, please restart Freezer to apply!":
|
||||||
|
"Language changed, please restart Freezer to apply!",
|
||||||
|
"Importing...": "Importing...",
|
||||||
|
"Radio": "Radio",
|
||||||
|
"Flow": "Flow",
|
||||||
|
"Track is not available on Deezer!": "Track is not available on Deezer!",
|
||||||
|
"Failed to download track! Please restart.": "Failed to download track! Please restart.",
|
||||||
|
"Storage permission denied!": "Storage permission denied!",
|
||||||
|
"Failed": "Failed",
|
||||||
|
"Queued": "Queued",
|
||||||
|
"External": "Storage",
|
||||||
|
"Restart failed downloads": "Restart failed downloads",
|
||||||
|
"Clear failed": "Clear failed",
|
||||||
|
"Download Settings": "Download Settings",
|
||||||
|
"Create folder for playlist": "Create folder for playlist",
|
||||||
|
"Download .LRC lyrics": "Download .LRC lyrics",
|
||||||
|
"Proxy": "Proxy",
|
||||||
|
"Not set": "Not set",
|
||||||
|
"Search or paste URL": "Search or paste URL",
|
||||||
|
"History": "History",
|
||||||
|
"Download threads": "Concurrent downloads",
|
||||||
|
"Lyrics unavailable, empty or failed to load!": "Lyrics unavailable, empty or failed to load!",
|
||||||
|
"About": "About",
|
||||||
|
"Telegram Channel": "Telegram Channel",
|
||||||
|
"To get latest releases": "To get latest releases",
|
||||||
|
"Official chat": "Official chat",
|
||||||
|
"Telegram Group": "Telegram Group",
|
||||||
|
"Huge thanks to all the contributors! <3": "Huge thanks to all the contributors! <3",
|
||||||
|
"Edit playlist": "Edit playlist",
|
||||||
|
"Update": "Update",
|
||||||
|
"Playlist updated!": "Playlist updated!",
|
||||||
|
"Downloads added!": "Downloads added!",
|
||||||
|
"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.",
|
||||||
|
"Use system theme": "Use system theme",
|
||||||
|
"Light": "Light",
|
||||||
|
"Popularity": "Popularity",
|
||||||
|
"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.",
|
||||||
|
"Share": "Share",
|
||||||
|
"Save album cover": "Save album cover",
|
||||||
|
"Warning": "Warning",
|
||||||
|
"Using too many concurrent downloads on older/weaker devices might cause crashes!": "Using too many concurrent downloads on older/weaker devices might cause crashes!",
|
||||||
|
"Create .nomedia files": "Create .nomedia files",
|
||||||
|
"To prevent gallery being filled with album art": "To prevent gallery being filled with album art"
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user