0.4.0 - translations, download fallback, android auto, radio, infinite flow, bugfixes
|
@ -19,6 +19,7 @@ android/key.properties
|
||||||
*.iws
|
*.iws
|
||||||
.idea/
|
.idea/
|
||||||
android/.idea/
|
android/.idea/
|
||||||
|
android/local.properties
|
||||||
|
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
|
34
README.md
|
@ -7,36 +7,56 @@ This app is still in BETA, so it is missing features and contains bugs.
|
||||||
If you want to report bug or request feature, please open an issue.
|
If you want to report bug or request feature, please open an issue.
|
||||||
|
|
||||||
## Downloads:
|
## Downloads:
|
||||||
Under releases tab
|
Downloads are currently distributed in Telegram channel: https://t.me/freezereleases
|
||||||
**You might get Play Protect warning - just select install anyway or disable Play Protect** - it is because the keys used for signing this app are new.
|
**You might get Play Protect warning - just select install anyway or disable Play Protect** - it is because the keys used for signing this app are new.
|
||||||
|
**App not installed** error - try different version (arm32/64) or uninstall old version.
|
||||||
|
|
||||||
## Compile from source
|
## Compile from source
|
||||||
|
|
||||||
Install flutter SDK: https://flutter.dev/docs/get-started/install
|
Install flutter SDK: https://flutter.dev/docs/get-started/install
|
||||||
(Optional) Generate keys for release build: https://flutter.dev/docs/deployment/android
|
(Optional) Generate keys for release build: https://flutter.dev/docs/deployment/android
|
||||||
|
|
||||||
|
Download source:
|
||||||
|
```
|
||||||
|
git clone https://notabug.org/exttex/freezer
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
|
```
|
||||||
|
|
||||||
Compile:
|
Compile:
|
||||||
```
|
```
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter build apk
|
flutter build apk
|
||||||
```
|
```
|
||||||
|
|
||||||
## Telegram
|
## Telegram group
|
||||||
https://t.me/freezerandroid
|
https://t.me/freezerandroid
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
Tobs: Beta tester
|
Tobs: Beta tester
|
||||||
Bas Curtiz: Icon, Logo, Banner, Design suggestions
|
Bas Curtiz: Icon, Logo, Banner, Design suggestions
|
||||||
Deemix: https://notabug.org/RemixDev/deemix
|
Deemix: https://notabug.org/RemixDev/deemix
|
||||||
just_audio && audio_service: https://github.com/ryanheise/just_audio
|
Annexhack: Android Auto help and resources
|
||||||
|
|
||||||
|
### Translators:
|
||||||
|
Homam Al-Rawi: Arabic
|
||||||
|
Markus: German
|
||||||
|
Andrea: Italian
|
||||||
|
Diego Hiro: Portuguese
|
||||||
|
Annexhack: Russian
|
||||||
|
|
||||||
|
### just_audio, audio_service
|
||||||
|
This app depends on modified just_audio and audio_service plugins with Deezer support.
|
||||||
|
Both plugins were originally written by ryanheise, all credits to him.
|
||||||
|
Forked versions for Freezer:
|
||||||
|
https://notabug.org/exttex/just_audio/
|
||||||
|
https://notabug.org/exttex/audio_service/
|
||||||
|
|
||||||
|
|
||||||
## Support me
|
## Support me
|
||||||
BTC: `14hcr4PGbgqeXd3SoXY9QyJFNpyurgrL9y`
|
BTC: `14hcr4PGbgqeXd3SoXY9QyJFNpyurgrL9y`
|
||||||
ETH: `0xb4D1893195404E1F4b45e5BDA77F202Ac4012288`
|
ETH: `0xb4D1893195404E1F4b45e5BDA77F202Ac4012288`
|
||||||
|
|
||||||
## just_audio
|
|
||||||
This app depends on modified just_audio plugin with Deezer support.
|
|
||||||
The fork repo is deprecated, current version available in this repo.
|
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
```
|
```
|
||||||
|
|
|
@ -41,7 +41,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "f.f.freezer"
|
applicationId "f.f.freezer"
|
||||||
minSdkVersion 20
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Displays an Android View that continues showing the launch screen
|
<!-- Displays an Android View that continues showing the launch screen
|
||||||
Drawable until Flutter paints its first frame, then this splash
|
Drawable until Flutter paints its first frame, then this splash
|
||||||
screen fades out. A splash screen is useful to avoid any visual
|
screen fades out. A splash screen is useful to avoid any visual
|
||||||
|
@ -64,5 +65,8 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<meta-data android:name="com.google.android.gms.car.application"
|
||||||
|
android:resource="@xml/automotive_app_desc"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
After Width: | Height: | Size: 16 KiB |
|
@ -25,6 +25,7 @@ import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
@ -73,10 +74,33 @@ public class MainActivity extends FlutterActivity {
|
||||||
tag.setField(FieldKey.ALBUM, call.argument("album").toString());
|
tag.setField(FieldKey.ALBUM, call.argument("album").toString());
|
||||||
tag.setField(FieldKey.ARTIST, call.argument("artists").toString());
|
tag.setField(FieldKey.ARTIST, call.argument("artists").toString());
|
||||||
tag.setField(FieldKey.TRACK, call.argument("trackNumber").toString());
|
tag.setField(FieldKey.TRACK, call.argument("trackNumber").toString());
|
||||||
|
if (call.argument("albumArtist") != null)
|
||||||
|
tag.setField(FieldKey.ALBUM_ARTIST, call.argument("albumArtist").toString());
|
||||||
|
if (call.argument("diskNumber") != null)
|
||||||
|
tag.setField(FieldKey.DISC_NO, call.argument("diskNumber").toString());
|
||||||
|
if (call.argument("year") != null)
|
||||||
|
tag.setField(FieldKey.YEAR, call.argument("year").toString());
|
||||||
|
if (call.argument("bpm") != null)
|
||||||
|
tag.setField(FieldKey.BPM, call.argument("bpm").toString());
|
||||||
|
if (call.argument("label") != null)
|
||||||
|
tag.setField(FieldKey.RECORD_LABEL, call.argument("label").toString());
|
||||||
|
//Genres
|
||||||
|
ArrayList<String> genres = call.argument("genres");
|
||||||
|
for(int i=0; i<genres.size(); i++) {
|
||||||
|
tag.addField(FieldKey.GENRE, genres.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
//Album art
|
//Album art
|
||||||
String cover = call.argument("cover").toString();
|
String cover = call.argument("cover").toString();
|
||||||
if (isFlac) {
|
if (isFlac) {
|
||||||
|
//FLAC Specific tags
|
||||||
|
if (call.argument("date") != null)
|
||||||
|
((FlacTag) tag).setField("DATE", call.argument("date").toString());
|
||||||
|
if (call.argument("albumTracks") != null)
|
||||||
|
((FlacTag) tag).setField("TRACKTOTAL", call.argument("albumTracks").toString());
|
||||||
|
((FlacTag) tag).setField("ITUNESADVISORY", call.argument("explicit").toString());
|
||||||
|
|
||||||
|
|
||||||
//FLAC requires different cover adding
|
//FLAC requires different cover adding
|
||||||
RandomAccessFile imageFile = new RandomAccessFile(new File(cover), "r");
|
RandomAccessFile imageFile = new RandomAccessFile(new File(cover), "r");
|
||||||
byte[] imageData = new byte[(int) imageFile.length()];
|
byte[] imageData = new byte[(int) imageFile.length()];
|
||||||
|
|
After Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 196 B |
Before Width: | Height: | Size: 390 B |
Before Width: | Height: | Size: 415 B |
Before Width: | Height: | Size: 401 B |
Before Width: | Height: | Size: 137 B |
After Width: | Height: | Size: 439 B |
Before Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 251 B |
Before Width: | Height: | Size: 250 B |
Before Width: | Height: | Size: 122 B |
After Width: | Height: | Size: 778 B |
Before Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 410 B |
Before Width: | Height: | Size: 456 B |
Before Width: | Height: | Size: 440 B |
Before Width: | Height: | Size: 181 B |
After Width: | Height: | Size: 810 B |
Before Width: | Height: | Size: 302 B |
Before Width: | Height: | Size: 683 B |
Before Width: | Height: | Size: 734 B |
Before Width: | Height: | Size: 723 B |
Before Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 983 B |
Before Width: | Height: | Size: 953 B |
Before Width: | Height: | Size: 304 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#1F1A16</color>
|
||||||
|
</resources>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<automotiveApp>
|
||||||
|
<uses name="media"/>
|
||||||
|
</automotiveApp>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -20,6 +20,7 @@ class DeezerAPI {
|
||||||
|
|
||||||
String token;
|
String token;
|
||||||
String userId;
|
String userId;
|
||||||
|
String userName;
|
||||||
String favoritesPlaylistId;
|
String favoritesPlaylistId;
|
||||||
String privateUrl = 'http://www.deezer.com/ajax/gw-light.php';
|
String privateUrl = 'http://www.deezer.com/ajax/gw-light.php';
|
||||||
Map<String, String> headers = {
|
Map<String, String> headers = {
|
||||||
|
@ -95,6 +96,7 @@ class DeezerAPI {
|
||||||
} else {
|
} else {
|
||||||
this.token = data['results']['checkForm'];
|
this.token = data['results']['checkForm'];
|
||||||
this.userId = data['results']['USER']['USER_ID'].toString();
|
this.userId = data['results']['USER']['USER_ID'].toString();
|
||||||
|
this.userName = data['results']['USER']['BLOG_NAME'];
|
||||||
this.favoritesPlaylistId = data['results']['USER']['LOVEDTRACKS_ID'];
|
this.favoritesPlaylistId = data['results']['USER']['LOVEDTRACKS_ID'];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -384,5 +386,20 @@ class DeezerAPI {
|
||||||
|
|
||||||
return data['results']['data'].map<Album>((a) => Album.fromPrivateJson(a)).toList();
|
return data['results']['data'].map<Album>((a) => Album.fromPrivateJson(a)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List> searchSuggestions(String query) async {
|
||||||
|
Map data = await callApi('search_getSuggestedQueries', params: {
|
||||||
|
'QUERY': query
|
||||||
|
});
|
||||||
|
return data['results']['SUGGESTION'].map((s) => s['QUERY']).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get smart radio for artist id
|
||||||
|
Future<List<Track>> smartRadio(String artistId) async {
|
||||||
|
Map data = await callApi('smart.getSmartRadio', params: {
|
||||||
|
'art_id': int.parse(artistId)
|
||||||
|
});
|
||||||
|
return data['results']['data'].map<Track>((t) => Track.fromPrivateJson(t)).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -254,7 +254,10 @@ class Artist {
|
||||||
bool offline;
|
bool offline;
|
||||||
bool library;
|
bool library;
|
||||||
|
|
||||||
Artist({this.id, this.name, this.albums, this.albumCount, this.topTracks, this.picture, this.fans, this.offline, this.library});
|
//TODO: NOT IN DB
|
||||||
|
bool radio;
|
||||||
|
|
||||||
|
Artist({this.id, this.name, this.albums, this.albumCount, this.topTracks, this.picture, this.fans, this.offline, this.library, this.radio});
|
||||||
|
|
||||||
String get fansString => NumberFormat.compact().format(fans);
|
String get fansString => NumberFormat.compact().format(fans);
|
||||||
|
|
||||||
|
@ -264,16 +267,23 @@ class Artist {
|
||||||
Map<dynamic, dynamic> albumsJson = const {},
|
Map<dynamic, dynamic> albumsJson = const {},
|
||||||
Map<dynamic, dynamic> topJson = const {},
|
Map<dynamic, dynamic> topJson = const {},
|
||||||
bool library = false
|
bool library = false
|
||||||
}) => Artist(
|
}) {
|
||||||
id: json['ART_ID'].toString(),
|
//Get wether radio is available
|
||||||
name: json['ART_NAME'],
|
bool _radio = false;
|
||||||
fans: json['NB_FAN'],
|
if (json['SMARTRADIO'] == true || json['SMARTRADIO'] == 1) _radio = true;
|
||||||
picture: ImageDetails.fromPrivateString(json['ART_PICTURE'], type: 'artist'),
|
|
||||||
albumCount: albumsJson['total'],
|
return Artist(
|
||||||
albums: (albumsJson['data']??[]).map<Album>((dynamic data) => Album.fromPrivateJson(data)).toList(),
|
id: json['ART_ID'].toString(),
|
||||||
topTracks: (topJson['data']??[]).map<Track>((dynamic data) => Track.fromPrivateJson(data)).toList(),
|
name: json['ART_NAME'],
|
||||||
library: library
|
fans: json['NB_FAN'],
|
||||||
);
|
picture: ImageDetails.fromPrivateString(json['ART_PICTURE'], type: 'artist'),
|
||||||
|
albumCount: albumsJson['total'],
|
||||||
|
albums: (albumsJson['data']??[]).map<Album>((dynamic data) => Album.fromPrivateJson(data)).toList(),
|
||||||
|
topTracks: (topJson['data']??[]).map<Track>((dynamic data) => Track.fromPrivateJson(data)).toList(),
|
||||||
|
library: library,
|
||||||
|
radio: _radio
|
||||||
|
);
|
||||||
|
}
|
||||||
Map<String, dynamic> toSQL({off = false}) => {
|
Map<String, dynamic> toSQL({off = false}) => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
|
|
@ -142,6 +142,7 @@ Artist _$ArtistFromJson(Map<String, dynamic> json) {
|
||||||
fans: json['fans'] as int,
|
fans: json['fans'] as int,
|
||||||
offline: json['offline'] as bool,
|
offline: json['offline'] as bool,
|
||||||
library: json['library'] as bool,
|
library: json['library'] as bool,
|
||||||
|
radio: json['radio'] as bool,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +156,7 @@ Map<String, dynamic> _$ArtistToJson(Artist instance) => <String, dynamic>{
|
||||||
'fans': instance.fans,
|
'fans': instance.fans,
|
||||||
'offline': instance.offline,
|
'offline': instance.offline,
|
||||||
'library': instance.library,
|
'library': instance.library,
|
||||||
|
'radio': instance.radio,
|
||||||
};
|
};
|
||||||
|
|
||||||
Playlist _$PlaylistFromJson(Map<String, dynamic> json) {
|
Playlist _$PlaylistFromJson(Map<String, dynamic> json) {
|
||||||
|
|
|
@ -122,14 +122,6 @@ class DownloadManager {
|
||||||
onDone: () async {
|
onDone: () async {
|
||||||
//On download finished
|
//On download finished
|
||||||
await db.rawUpdate('UPDATE downloads SET state = 1 WHERE trackId = ?', [queue[0].track.id]);
|
await db.rawUpdate('UPDATE downloads SET state = 1 WHERE trackId = ?', [queue[0].track.id]);
|
||||||
/*
|
|
||||||
if (queue[0].private) {
|
|
||||||
await db.rawUpdate('UPDATE downloads SET state = 1 WHERE trackId = ?', [queue[0].track.id]);
|
|
||||||
} else {
|
|
||||||
//Remove on db if public
|
|
||||||
await db.delete('downloads', where: 'trackId = ?', whereArgs: [queue[0].track.id]);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
queue.removeAt(0);
|
queue.removeAt(0);
|
||||||
_download = null;
|
_download = null;
|
||||||
//Remove notification if no more downloads
|
//Remove notification if no more downloads
|
||||||
|
@ -144,7 +136,11 @@ class DownloadManager {
|
||||||
//Catch download errors
|
//Catch download errors
|
||||||
_download = null;
|
_download = null;
|
||||||
_cancelNotifications = true;
|
_cancelNotifications = true;
|
||||||
//Cancellation error i guess
|
queue[0].state = DownloadState.NONE;
|
||||||
|
//Shift to end
|
||||||
|
queue.add(queue[0]);
|
||||||
|
queue.removeAt(0);
|
||||||
|
//Show error
|
||||||
await _showError();
|
await _showError();
|
||||||
});
|
});
|
||||||
//Show download progress notifications
|
//Show download progress notifications
|
||||||
|
@ -318,14 +314,12 @@ class DownloadManager {
|
||||||
track = await deezerAPI.track(track.id);
|
track = await deezerAPI.track(track.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = track.getUrl(settings.getQualityInt(settings.offlineQuality));
|
|
||||||
if (!private) {
|
if (!private) {
|
||||||
//Check permissions
|
//Check permissions
|
||||||
if (!(await Permission.storage.request().isGranted)) {
|
if (!(await Permission.storage.request().isGranted)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//If saving to external
|
//If saving to external
|
||||||
url = track.getUrl(settings.getQualityInt(settings.downloadQuality));
|
|
||||||
//Save just extension to path, will be generated before download
|
//Save just extension to path, will be generated before download
|
||||||
path = 'mp3';
|
path = 'mp3';
|
||||||
if (settings.downloadQuality == AudioQuality.FLAC) {
|
if (settings.downloadQuality == AudioQuality.FLAC) {
|
||||||
|
@ -339,7 +333,7 @@ class DownloadManager {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Download download = Download(track: track, path: path, url: url, private: private);
|
Download download = Download(track: track, path: path, private: private);
|
||||||
//Database
|
//Database
|
||||||
Batch b = db.batch();
|
Batch b = db.batch();
|
||||||
b.insert('downloads', download.toSQL());
|
b.insert('downloads', download.toSQL());
|
||||||
|
@ -539,12 +533,17 @@ class Download {
|
||||||
|
|
||||||
//TODO: Check for internet before downloading
|
//TODO: Check for internet before downloading
|
||||||
|
|
||||||
|
Map rawTrackPublic = {};
|
||||||
|
Map rawAlbumPublic = {};
|
||||||
if (!this.private && !(this.path.endsWith('.mp3') || this.path.endsWith('.flac'))) {
|
if (!this.private && !(this.path.endsWith('.mp3') || this.path.endsWith('.flac'))) {
|
||||||
String ext = this.path;
|
String ext = this.path;
|
||||||
//Get track details
|
//Get track details
|
||||||
Map _rawTrackData = await deezerAPI.callApi('song.getListData', params: {'sng_ids': [track.id]});
|
Map _rawTrackData = await deezerAPI.callApi('song.getListData', params: {'sng_ids': [track.id]});
|
||||||
Map rawTrack = _rawTrackData['results']['data'][0];
|
Map rawTrack = _rawTrackData['results']['data'][0];
|
||||||
this.track = Track.fromPrivateJson(rawTrack);
|
this.track = Track.fromPrivateJson(rawTrack);
|
||||||
|
//RAW Public API call (for genre and other tags)
|
||||||
|
try {rawTrackPublic = await deezerAPI.callPublicApi('track/${this.track.id}');} catch (e) {rawTrackPublic = {};}
|
||||||
|
try {rawAlbumPublic = await deezerAPI.callPublicApi('album/${this.track.album.id}');} catch (e) {rawAlbumPublic = {};}
|
||||||
|
|
||||||
//Get path if public
|
//Get path if public
|
||||||
RegExp sanitize = RegExp(r'[\/\\\?\%\*\:\|\"\<\>]');
|
RegExp sanitize = RegExp(r'[\/\\\?\%\*\:\|\"\<\>]');
|
||||||
|
@ -620,6 +619,11 @@ class Download {
|
||||||
//Create file if doesnt exist
|
//Create file if doesnt exist
|
||||||
await downloadFile.create(recursive: true);
|
await downloadFile.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Quality fallback
|
||||||
|
if (this.url == null)
|
||||||
|
await _fallback();
|
||||||
|
|
||||||
//Download
|
//Download
|
||||||
_cancel = CancelToken();
|
_cancel = CancelToken();
|
||||||
Response response = await dio.get(
|
Response response = await dio.get(
|
||||||
|
@ -658,6 +662,10 @@ class Download {
|
||||||
//Tag
|
//Tag
|
||||||
if (!private) {
|
if (!private) {
|
||||||
//Tag track in native
|
//Tag track in native
|
||||||
|
String year;
|
||||||
|
if (rawTrackPublic['release_date'] != null && rawTrackPublic['release_date'].length >= 4)
|
||||||
|
year = rawTrackPublic['release_date'].substring(0, 4);
|
||||||
|
|
||||||
await platformChannel.invokeMethod('tagTrack', {
|
await platformChannel.invokeMethod('tagTrack', {
|
||||||
'path': path,
|
'path': path,
|
||||||
'title': track.title,
|
'title': track.title,
|
||||||
|
@ -665,7 +673,16 @@ class Download {
|
||||||
'artists': track.artistString,
|
'artists': track.artistString,
|
||||||
'artist': track.artists[0].name,
|
'artist': track.artists[0].name,
|
||||||
'cover': _cover,
|
'cover': _cover,
|
||||||
'trackNumber': track.trackNumber
|
'trackNumber': track.trackNumber,
|
||||||
|
'diskNumber': track.diskNumber,
|
||||||
|
'genres': ((rawAlbumPublic['genres']??{})['data']??[]).map((g) => g['name']).toList(),
|
||||||
|
'year': year,
|
||||||
|
'bpm': rawTrackPublic['bpm'],
|
||||||
|
'explicit': (track.explicit??false) ? "1":"0",
|
||||||
|
'label': rawAlbumPublic['label'],
|
||||||
|
'albumTracks': rawAlbumPublic['nb_tracks'],
|
||||||
|
'date': rawTrackPublic['release_date'],
|
||||||
|
'albumArtist': (rawAlbumPublic['artist']??{})['name']
|
||||||
});
|
});
|
||||||
//Rescan android library
|
//Rescan android library
|
||||||
await platformChannel.invokeMethod('rescanLibrary', {
|
await platformChannel.invokeMethod('rescanLibrary', {
|
||||||
|
@ -702,6 +719,36 @@ class Download {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future _fallback({fallback}) async {
|
||||||
|
//Get quality
|
||||||
|
AudioQuality quality = private ? settings.offlineQuality : settings.downloadQuality;
|
||||||
|
if (fallback == AudioQuality.MP3_320) quality = AudioQuality.MP3_128;
|
||||||
|
if (fallback == AudioQuality.FLAC) {
|
||||||
|
quality = AudioQuality.MP3_320;
|
||||||
|
if (this.path.toLowerCase().endsWith('flac'))
|
||||||
|
this.path = this.path.substring(0, this.path.length - 4) + 'mp3';
|
||||||
|
}
|
||||||
|
|
||||||
|
//No more fallback
|
||||||
|
if (quality == AudioQuality.MP3_128) {
|
||||||
|
url = track.getUrl(settings.getQualityInt(quality));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check
|
||||||
|
int q = settings.getQualityInt(quality);
|
||||||
|
try {
|
||||||
|
Response res = await Dio().head(track.getUrl(q));
|
||||||
|
if (res.statusCode == 200 || res.statusCode == 206) {
|
||||||
|
this.url = track.getUrl(q);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
//Fallback
|
||||||
|
return _fallback(fallback: quality);
|
||||||
|
}
|
||||||
|
|
||||||
//JSON
|
//JSON
|
||||||
Map<String, dynamic> toSQL() => {
|
Map<String, dynamic> toSQL() => {
|
||||||
'trackId': track.id,
|
'trackId': track.id,
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/ui/details_screens.dart';
|
import 'package:freezer/ui/android_auto.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:connectivity/connectivity.dart';
|
import 'package:connectivity/connectivity.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import 'definitions.dart';
|
import 'definitions.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
@ -43,9 +45,21 @@ 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']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//Android auto get screen
|
||||||
|
if (event['action'] == 'screenAndroidAuto') {
|
||||||
|
AndroidAuto androidAuto = AndroidAuto();
|
||||||
|
List<MediaItem> data = await androidAuto.getScreen(event['id']);
|
||||||
|
await AudioService.customAction('screenAndroidAuto', jsonEncode(data));
|
||||||
|
}
|
||||||
|
//Android auto play list
|
||||||
|
if (event['action'] == 'tracksAndroidAuto') {
|
||||||
|
AndroidAuto androidAuto = AndroidAuto();
|
||||||
|
await androidAuto.playItem(event['id']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
_playbackStateStreamSubscription = AudioService.playbackStateStream.listen((event) {
|
_playbackStateStreamSubscription = AudioService.playbackStateStream.listen((event) {
|
||||||
//Log song (if allowed)
|
//Log song (if allowed)
|
||||||
|
@ -106,6 +120,30 @@ class PlayerHelper {
|
||||||
await AudioService.skipToQueueItem(trackId);
|
await AudioService.skipToQueueItem(trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Called when queue ends to load more tracks
|
||||||
|
Future onQueueEnd() async {
|
||||||
|
//Flow
|
||||||
|
if (queueSource == null) return;
|
||||||
|
if (queueSource.id == 'flow') {
|
||||||
|
List<Track> tracks = await deezerAPI.flow();
|
||||||
|
List<MediaItem> mi = tracks.map<MediaItem>((t) => t.toMediaItem()).toList();
|
||||||
|
await AudioService.addQueueItems(mi);
|
||||||
|
AudioService.skipToNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//SmartRadio/Artist radio
|
||||||
|
if (queueSource.source == 'smartradio') {
|
||||||
|
List<Track> tracks = await deezerAPI.smartRadio(queueSource.id);
|
||||||
|
List<MediaItem> mi = tracks.map<MediaItem>((t) => t.toMediaItem()).toList();
|
||||||
|
await AudioService.addQueueItems(mi);
|
||||||
|
AudioService.skipToNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(queueSource.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
//Play track from album
|
//Play track from album
|
||||||
Future playFromAlbum(Album album, String trackId) async {
|
Future playFromAlbum(Album album, String trackId) async {
|
||||||
await playFromTrackList(album.tracks, trackId, QueueSource(
|
await playFromTrackList(album.tracks, trackId, QueueSource(
|
||||||
|
@ -144,7 +182,7 @@ class PlayerHelper {
|
||||||
if (stl.tracks == null || stl.tracks.length == 0) {
|
if (stl.tracks == null || stl.tracks.length == 0) {
|
||||||
if (settings.offlineMode) {
|
if (settings.offlineMode) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: "Offline mode, can't play flow/smart track lists.",
|
msg: "Offline mode, can't play flow or smart track lists.".i18n,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastLength: Toast.LENGTH_SHORT
|
toastLength: Toast.LENGTH_SHORT
|
||||||
);
|
);
|
||||||
|
@ -188,7 +226,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
ConcatenatingAudioSource _audioSource;
|
ConcatenatingAudioSource _audioSource;
|
||||||
|
|
||||||
AudioProcessingState _skipState;
|
AudioProcessingState _skipState;
|
||||||
bool _interrupted;
|
|
||||||
Seeker _seeker;
|
Seeker _seeker;
|
||||||
|
|
||||||
//Stream subscriptions
|
//Stream subscriptions
|
||||||
|
@ -200,10 +237,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
QueueSource queueSource;
|
QueueSource queueSource;
|
||||||
Duration _lastPosition;
|
Duration _lastPosition;
|
||||||
|
|
||||||
|
Completer _androidAutoCallback;
|
||||||
|
|
||||||
MediaItem get mediaItem => _queue[_queueIndex];
|
MediaItem get mediaItem => _queue[_queueIndex];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future onStart(Map<String, dynamic> params) {
|
Future onStart(Map<String, dynamic> params) async {
|
||||||
|
|
||||||
|
final session = await AudioSession.instance;
|
||||||
|
session.configure(AudioSessionConfiguration.music());
|
||||||
|
|
||||||
//Update track index
|
//Update track index
|
||||||
_player.currentIndexStream.listen((index) {
|
_player.currentIndexStream.listen((index) {
|
||||||
|
@ -285,6 +327,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
@override
|
@override
|
||||||
Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin, -1);
|
Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin, -1);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MediaItem>> onLoadChildren(String parentMediaId) async {
|
||||||
|
AudioServiceBackground.sendCustomEvent({
|
||||||
|
'action': 'screenAndroidAuto',
|
||||||
|
'id': parentMediaId
|
||||||
|
});
|
||||||
|
|
||||||
|
//Wait for data from main thread
|
||||||
|
_androidAutoCallback = Completer();
|
||||||
|
List<MediaItem> data = (await _androidAutoCallback.future) as List<MediaItem>;
|
||||||
|
_androidAutoCallback = null;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
//While seeking, jump 10s every 1s
|
//While seeking, jump 10s every 1s
|
||||||
void _seekContinuously(bool begin, int direction) {
|
void _seekContinuously(bool begin, int direction) {
|
||||||
_seeker?.stop();
|
_seeker?.stop();
|
||||||
|
@ -430,46 +486,14 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
if (name == 'load') await this._loadQueueFile();
|
if (name == 'load') await this._loadQueueFile();
|
||||||
//Shuffle
|
//Shuffle
|
||||||
if (name == 'shuffle') await _player.setShuffleModeEnabled(args);
|
if (name == 'shuffle') await _player.setShuffleModeEnabled(args);
|
||||||
|
//Android auto callback
|
||||||
|
if (name == 'screenAndroidAuto' && _androidAutoCallback != null) {
|
||||||
|
_androidAutoCallback.complete(jsonDecode(args).map<MediaItem>((m) => MediaItem.fromJson(m)).toList());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Audio interruptions
|
|
||||||
@override
|
|
||||||
Future onAudioFocusLost(AudioInterruption interruption) {
|
|
||||||
if (_player.playing) _interrupted = true;
|
|
||||||
switch (interruption) {
|
|
||||||
case AudioInterruption.pause:
|
|
||||||
case AudioInterruption.temporaryPause:
|
|
||||||
case AudioInterruption.unknownPause:
|
|
||||||
if (_player.playing) onPause();
|
|
||||||
break;
|
|
||||||
case AudioInterruption.temporaryDuck:
|
|
||||||
_player.setVolume(0.5);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future onAudioFocusGained(AudioInterruption interruption) {
|
|
||||||
switch (interruption) {
|
|
||||||
case AudioInterruption.temporaryPause:
|
|
||||||
if (!_player.playing && _interrupted) onPlay();
|
|
||||||
break;
|
|
||||||
case AudioInterruption.temporaryDuck:
|
|
||||||
_player.setVolume(1.0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_interrupted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future onAudioBecomingNoisy() {
|
|
||||||
onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future onTaskRemoved() async {
|
Future onTaskRemoved() async {
|
||||||
await onStop();
|
await onStop();
|
||||||
|
@ -561,6 +585,16 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future onPlayFromMediaId(String mediaId) async {
|
Future onPlayFromMediaId(String mediaId) async {
|
||||||
|
|
||||||
|
//Android auto load tracks
|
||||||
|
if (mediaId.startsWith(AndroidAuto.prefix)) {
|
||||||
|
AudioServiceBackground.sendCustomEvent({
|
||||||
|
'action': 'tracksAndroidAuto',
|
||||||
|
'id': mediaId.replaceFirst(AndroidAuto.prefix, '')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Does the same thing
|
//Does the same thing
|
||||||
await this.onSkipToQueueItem(mediaId);
|
await this.onSkipToQueueItem(mediaId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ class SpotifyPlaylist {
|
||||||
factory SpotifyPlaylist.fromJson(Map json) => SpotifyPlaylist(
|
factory SpotifyPlaylist.fromJson(Map json) => SpotifyPlaylist(
|
||||||
name: json['name'],
|
name: json['name'],
|
||||||
description: json['description'],
|
description: json['description'],
|
||||||
image: json['images'][0]['url'],
|
image: (json['images'].length > 0) ? json['images'][0]['url'] : null,
|
||||||
tracks: json['tracks']['items'].map<SpotifyTrack>((j) => SpotifyTrack.fromJson(j['track'])).toList()
|
tracks: json['tracks']['items'].map<SpotifyTrack>((j) => SpotifyTrack.fromJson(j['track'])).toList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Translated by: Homam Al-Rawi
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const language_ar_ar = {
|
||||||
|
"ar_ar": {
|
||||||
|
"Home": "القائمة الرئيسية",
|
||||||
|
"Search": "بحث",
|
||||||
|
"Library": "المكتبة",
|
||||||
|
"Offline mode, can't play flow or smart track lists.": "وضع خارج الشبكة, لا تستطيع تشغيل اغاني من قوائم ديزر فلو",
|
||||||
|
"Added to library": "تمت الاضافة الى المكتبة",
|
||||||
|
"Download": "تحميل",
|
||||||
|
"Disk": "القرص",
|
||||||
|
"Offline": "خارج الشبكة",
|
||||||
|
"Top Tracks": "افضل الاغاني",
|
||||||
|
"Show more tracks": "اضهار المزيد من الاغاني",
|
||||||
|
"Top": "الافضل",
|
||||||
|
"Top Albums": "افضل الالبومات",
|
||||||
|
"Show all albums": "اضهار كل الالبومات",
|
||||||
|
"Discography": "كل الالومات و الاغاني",
|
||||||
|
"Default": "افتراضي",
|
||||||
|
"Reverse": "عكس",
|
||||||
|
"Alphabetic": "أبجدي",
|
||||||
|
"Artist": "فنان",
|
||||||
|
"Post processing...": "بعد المعالجة...",
|
||||||
|
"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": "حاليا يدعم سبوتفاي فقط, بحد اقصى 100 اغنية",
|
||||||
|
"Due to API limitations": "بسبب قيود API",
|
||||||
|
"Enter your playlist link below": "أدخل رابط قائمة التشغيل أدناه",
|
||||||
|
"Error loading URL!": "خطأ في تحميل الرابط!",
|
||||||
|
"Convert": "تحويل",
|
||||||
|
"Download only": "تحميل فقط",
|
||||||
|
"Downloading is currently stopped, click here to resume.": "التنزيل متوقف حاليًا ، انقر هنا للاستئناف.",
|
||||||
|
"Tracks": "اغاني",
|
||||||
|
"Albums": "البومات",
|
||||||
|
"Artists": "فنانون",
|
||||||
|
"Playlists": "قوائم تشغيل",
|
||||||
|
"Import": "استيراد",
|
||||||
|
"Import playlists from Spotify": "استيراد قائمة تشغيل من سبوتيفاي",
|
||||||
|
"Statistics": "احصائيات",
|
||||||
|
"Offline tracks": "اغاني بدون اتصال",
|
||||||
|
"Offline albums": "البومات بدون اتصال",
|
||||||
|
"Offline playlists": "قوائم تشغيل بدون اتصال",
|
||||||
|
"Offline size": "حجم بدون اتصال",
|
||||||
|
"Free space": "مساحة فارغة",
|
||||||
|
"Loved tracks": "الاغاني المحبوبة",
|
||||||
|
"Favorites": "المفضلات",
|
||||||
|
"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.": "خطأ في تسجيل الدخول! يرجى التحقق من الرمز المميز والاتصال بالإنترنت وحاول مرة أخرى.",
|
||||||
|
"Dismiss": "رفض",
|
||||||
|
"Welcome to": "مرحبا بك في",
|
||||||
|
"Please login using your Deezer account.": "يرجى تسجيل الدخول باستخدام حساب ديزر الخاص بك.",
|
||||||
|
"Login using browser": "تسجيل الدخول باستخدام المتصفح",
|
||||||
|
"Login using token": "تسجيل الدخول باستخدام الرمز المميز",
|
||||||
|
"Enter ARL": "أدخل الرمز المميز (arl)",
|
||||||
|
"Token (ARL)": "الرمز المميز (ARL)",
|
||||||
|
"Save": "حفظ",
|
||||||
|
"If you don't have account, you can register on deezer.com for free.": "إذا لم يكن لديك حساب ، يمكنك التسجيل على deezer.com مجانًا.",
|
||||||
|
"Open in browser": "افتح في المتصفح",
|
||||||
|
"By using this app, you don't agree with the Deezer ToS": "باستخدام هذا التطبيق ، أنت لا توافق على شروط خدمة ديزر",
|
||||||
|
"Play next": "شغل التالي",
|
||||||
|
"Add to queue": "إضافة إلى قائمة الانتظار",
|
||||||
|
"Add track to favorites": "اضافة الاغنية الى المفضلة",
|
||||||
|
"Add to playlist": "اضافة الى قائمة التشغيل",
|
||||||
|
"Select playlist": "اختيار قائمة التشغيل",
|
||||||
|
"Track added to": "تم اضافة الاغنية الى",
|
||||||
|
"Remove from playlist": "إزالة من قائمة التشغيل",
|
||||||
|
"Track removed from": "تم إزالة الاغنية من",
|
||||||
|
"Remove favorite": "إزالة المفضلة",
|
||||||
|
"Track removed from library": "تم إزالة الاغنية من المكتبة",
|
||||||
|
"Go to": "الذهاب الى",
|
||||||
|
"Make offline": "جعله في وضع عدم الاتصال",
|
||||||
|
"Add to library": "إضافة إلى مكتبة",
|
||||||
|
"Remove album": "إزالة الالبوم",
|
||||||
|
"Album removed": "تم إزالة الالبوم",
|
||||||
|
"Remove from favorites": "تم الإزالة من المفضلة",
|
||||||
|
"Artist removed from library": "تم إزالة الفنان من المكتبة",
|
||||||
|
"Add to favorites": "اضافة الى المفضلة",
|
||||||
|
"Remove from library": "إزالة من المكتبة",
|
||||||
|
"Add playlist to library": "أضف قائمة التشغيل إلى المكتبة",
|
||||||
|
"Added playlist to library": "تم اضافة قائمة التشغيل الى المكتبة",
|
||||||
|
"Make playlist offline": "جعل قائمة التشغيل في وضع عدم الاتصال",
|
||||||
|
"Download playlist": "تنزيل قائمة التشغيل",
|
||||||
|
"Create playlist": "إنشاء قائمة التشغيل",
|
||||||
|
"Title": "عنوان",
|
||||||
|
"Description": "وصف",
|
||||||
|
"Private": "خاص",
|
||||||
|
"Collaborative": "التعاونيه",
|
||||||
|
"Create": "إنشاء",
|
||||||
|
"Playlist created!": "تم إنشاء قائمة التشغيل",
|
||||||
|
"Playing from:": "التشغيل من:",
|
||||||
|
"Queue": "قائمة الانتظار",
|
||||||
|
"Offline search": "البحث دون اتصال",
|
||||||
|
"Search Results": "نتائج البحث",
|
||||||
|
"No results!": "لا نتائج!",
|
||||||
|
"Show all tracks": "عرض كل الاغاني",
|
||||||
|
"Show all playlists": "عرض كل قوائم التشغيل",
|
||||||
|
"Settings": "الإعدادات",
|
||||||
|
"General": "عام",
|
||||||
|
"Appearance": "المظهر",
|
||||||
|
"Quality": "الجودة",
|
||||||
|
"Deezer": "ديزر",
|
||||||
|
"Theme": "ثيم",
|
||||||
|
"Currently": "حاليا",
|
||||||
|
"Select theme": "اختر ثيم",
|
||||||
|
"Light (default)": "ابيض (افتراضي)",
|
||||||
|
"Dark": "داكن (أفضل)",
|
||||||
|
"Black (AMOLED)": "أسود",
|
||||||
|
"Deezer (Dark)": "داكن (ديزر)",
|
||||||
|
"Primary color": "اللون اساسي",
|
||||||
|
"Selected color": "اللون المحدد",
|
||||||
|
"Use album art primary color": "استخدم اللون الأساسي لصورة الألبوم",
|
||||||
|
"Warning: might be buggy": "تحذير: قد يكون غير مستقر",
|
||||||
|
"Mobile streaming": "البث عبر شبكة الجوان",
|
||||||
|
"Wifi streaming": "البث عبر الوايفاي",
|
||||||
|
"External downloads": "التنزيلات الخارجية",
|
||||||
|
"Content language": "لغة المحتوى",
|
||||||
|
"Not app language, used in headers. Now": "ليست لغة التطبيق المستخدمة في العناوين. الآن",
|
||||||
|
"Select language": "اختار اللغة",
|
||||||
|
"Content country": "بلد المحتوى",
|
||||||
|
"Country used in headers. Now": "البلد المستخدم في العناوين. الآن",
|
||||||
|
"Log tracks": "تسجيل الاغاني",
|
||||||
|
"Send track listen logs to Deezer, enable it for features like Flow to work properly": "أرسال سجلات الاستماع إلى ديزر ، قم بتمكينها لميزات مثل فلو لتعمل بشكل صحيح (ينصح تفعيلها)",
|
||||||
|
"Offline mode": "وضع عدم الاتصال",
|
||||||
|
"Will be overwritten on start.": "سيتم الكتابة فوقها في البداية.",
|
||||||
|
"Error logging in, check your internet connections.": "خطأ في تسجيل الدخول ، تحقق من اتصالات الإنترنت الخاص بك.",
|
||||||
|
"Logging in...": "جار تسجيل الدخول...",
|
||||||
|
"Download path": "مسار التنزيل",
|
||||||
|
"Downloads naming": "تسمية التنزيلات",
|
||||||
|
"Downloaded tracks filename": "اسم ملف الاغاني التي تم تنزيلها",
|
||||||
|
"Valid variables are": "المتغيرات الصالحة هي",
|
||||||
|
"Reset": "إعادة تعيين",
|
||||||
|
"Clear": "تنضيف",
|
||||||
|
"Create folders for artist": "إنشاء ملفات للفنان",
|
||||||
|
"Create folders for albums": "إنشاء ملفات للالبوم",
|
||||||
|
"Separate albums by discs": "افصل الالبومات عبر رقم الاقراص",
|
||||||
|
"Overwrite already downloaded files": "الكتابة فوق الملفات التي تم تنزيلها",
|
||||||
|
"Copy ARL": "نسخ الرمز المميز (ARL)",
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.": "انسخ ملف الرابط\الرمز المميز لاستخدامه في تطبيقات أخرى.",
|
||||||
|
"Copied": "تم النسخ",
|
||||||
|
"Log out": "تسجيل خروج",
|
||||||
|
"Due to plugin incompatibility, login using browser is unavailable without restart.": "نظرًا لعدم توافق المكون الإضافي ، لا يتوفر تسجيل الدخول باستخدام المتصفح بدون إعادة التشغيل.",
|
||||||
|
"(ARL ONLY) Continue": "استمر (رمز مميز فقط ARL)",
|
||||||
|
"Log out & Exit": "تسجيل الخروج والخروج",
|
||||||
|
"Pick-a-Path": "اختر المسار",
|
||||||
|
"Select storage": "حدد وحدة التخزين",
|
||||||
|
"Go up": "اذهب للأعلى",
|
||||||
|
"Permission denied": "طلب الاذن مرفوض",
|
||||||
|
"Language": "اللغة",
|
||||||
|
"Language changed, please restart Freezer to apply!": "تم تغيير اللغة، الرجاء إعادة تشغيل فريزر لتطبيق!",
|
||||||
|
"Importing...": "جار الاستيراد...",
|
||||||
|
"Radio": "راديو"
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Translated by: Markus
|
||||||
|
|
||||||
|
*/
|
||||||
|
const language_de_de = {
|
||||||
|
"de_de": {
|
||||||
|
"Home": "Start",
|
||||||
|
"Search": "Suche",
|
||||||
|
"Library": "Mediathek",
|
||||||
|
"Offline mode, can't play flow or smart track lists.":
|
||||||
|
"Offline-Modus, kann keine Flow- oder Smart Track-Listen abspielen.",
|
||||||
|
"Added to library": "Zur Mediathek hinzufügen",
|
||||||
|
"Download": "Download",
|
||||||
|
"Disk": "Disk",
|
||||||
|
"Offline": "Offline",
|
||||||
|
"Top Tracks": "Top Titel",
|
||||||
|
"Show more tracks": "Zeige mehr Titel",
|
||||||
|
"Top": "Top",
|
||||||
|
"Top Albums": "Top Alben",
|
||||||
|
"Show all albums": "Zeige alle Alben",
|
||||||
|
"Discography": "Diskografie",
|
||||||
|
"Default": "Standard",
|
||||||
|
"Reverse": "Rückwärts",
|
||||||
|
"Alphabetic": "Alphabetisch",
|
||||||
|
"Artist": "Künstler",
|
||||||
|
"Post processing...": "Nachbearbeitung...",
|
||||||
|
"Done": "Erledigt",
|
||||||
|
"Delete": "Gelöscht",
|
||||||
|
"Are you sure you want to delete this download?":
|
||||||
|
"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",
|
||||||
|
"Are you sure you want to delete all queued downloads?":
|
||||||
|
"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",
|
||||||
|
"Please check your connection and try again later...":
|
||||||
|
"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",
|
||||||
|
"Due to API limitations": "Aufgrund von API-Einschränkungen",
|
||||||
|
"Enter your playlist link below":
|
||||||
|
"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.",
|
||||||
|
"Tracks": "Titel",
|
||||||
|
"Albums": "Alben",
|
||||||
|
"Artists": "Künstler",
|
||||||
|
"Playlists": "Wiedergabelisten",
|
||||||
|
"Import": "Importieren",
|
||||||
|
"Import playlists from Spotify": "Wiedergabelisten aus Spotify importieren",
|
||||||
|
"Statistics": "Statistiken",
|
||||||
|
"Offline tracks": "Offline-Titel",
|
||||||
|
"Offline albums": "Offline-Alben",
|
||||||
|
"Offline playlists": "Offline-Wiedergabelisten",
|
||||||
|
"Offline size": "Offline-Größe",
|
||||||
|
"Free space": "Freier Speicherplatz",
|
||||||
|
"Loved tracks": "Beliebte Titel",
|
||||||
|
"Favorites": "Favoriten",
|
||||||
|
"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",
|
||||||
|
"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.",
|
||||||
|
"Dismiss": "Verwerfen",
|
||||||
|
"Welcome to": "Willkommen bei",
|
||||||
|
"Please login using your Deezer account.":
|
||||||
|
"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.",
|
||||||
|
"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",
|
||||||
|
"Play next": "Als nächstes spielen",
|
||||||
|
"Add to queue": "Zur Warteschleife hinzufügen",
|
||||||
|
"Add track to favorites": "Titel zu Favoriten hinzufügen",
|
||||||
|
"Add to playlist": "Zur Wiedergabeliste hinzufügen",
|
||||||
|
"Select playlist": "Wiedergabeliste auswählen",
|
||||||
|
"Track added to": "Titel hinzugefügt zu",
|
||||||
|
"Remove from playlist": "Aus Wiedergabeliste entfernen",
|
||||||
|
"Track removed from": "Titel entfernt aus",
|
||||||
|
"Remove favorite": "Favorit entfernen",
|
||||||
|
"Track removed from library": "Titel aus Mediathek entfernt",
|
||||||
|
"Go to": "Gehe zu",
|
||||||
|
"Make offline": "Offline verfügbar machen",
|
||||||
|
"Add to library": "Zur Mediathek hinzufügen",
|
||||||
|
"Remove album": "Album entfernen",
|
||||||
|
"Album removed": "Album entfernt",
|
||||||
|
"Remove from favorites": "Aus Favoriten entfernen",
|
||||||
|
"Artist removed from library": "Künstler aus Bibliothek entfernt",
|
||||||
|
"Add to favorites": "Zu Favoriten hinzufügen",
|
||||||
|
"Remove from library": "Aus der Mediathek entfernen",
|
||||||
|
"Add playlist to library": "Wiedergabeliste zur Mediathek hinzufügen",
|
||||||
|
"Added playlist to library": "Wiedergabeliste zur Mediathek hinzugefügt",
|
||||||
|
"Make playlist offline": "Wiedergabeliste offline verfügbar machen",
|
||||||
|
"Download playlist": "Wiedergabeliste herunterladen",
|
||||||
|
"Create playlist": "Wiedergabeliste erstellen",
|
||||||
|
"Title": "Titel",
|
||||||
|
"Description": "Beschreibung",
|
||||||
|
"Private": "Privat",
|
||||||
|
"Collaborative": "Collaborative",
|
||||||
|
"Create": "Erstellen",
|
||||||
|
"Playlist created!": "Wiedergabeliste erstellt!",
|
||||||
|
"Playing from:": "Wiedergabe von:",
|
||||||
|
"Queue": "Warteschleife",
|
||||||
|
"Offline search": "Offline-Suche",
|
||||||
|
"Search Results": "Suchergebnisse",
|
||||||
|
"No results!": "Keine Ergebnisse!",
|
||||||
|
"Show all tracks": "Alle Titel anzeigen",
|
||||||
|
"Show all playlists": "Alle Wiedergabelisten anzeigen",
|
||||||
|
"Settings": "Einstellungen",
|
||||||
|
"General": "Allgemein",
|
||||||
|
"Appearance": "Aussehen",
|
||||||
|
"Quality": "Qualität",
|
||||||
|
"Deezer": "Deezer",
|
||||||
|
"Theme": "App-Design",
|
||||||
|
"Currently": "Aktuell",
|
||||||
|
"Select theme": "App-Design auswählen",
|
||||||
|
"Light (default)": "Heller Modus (Standard)",
|
||||||
|
"Dark": "Dunkler Modus",
|
||||||
|
"Black (AMOLED)": "Schwarz (AMOLED)",
|
||||||
|
"Deezer (Dark)": "Deezer (Dunkel)",
|
||||||
|
"Primary color": "Primärfarbe",
|
||||||
|
"Selected color": "Ausgewählte Farbe",
|
||||||
|
"Use album art primary color": "Verwende die Primärfarbe des Albumcovers",
|
||||||
|
"Warning: might be buggy": "Warnung: könnte fehlerhaft sein",
|
||||||
|
"Mobile streaming": "Wiedergabe über Mobilfunknetz",
|
||||||
|
"Wifi streaming": "Wiedergabe über WLAN",
|
||||||
|
"External downloads": "Externe Downloads",
|
||||||
|
"Content language": "Content-Sprache",
|
||||||
|
"Not app language, used in headers. Now": "Aktuell",
|
||||||
|
"Select language": "Sprache auswählen",
|
||||||
|
"Content country": "Content-Land",
|
||||||
|
"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",
|
||||||
|
"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.",
|
||||||
|
"Logging in...": "Angemeldet...",
|
||||||
|
"Download path": "Download-Pfad",
|
||||||
|
"Downloads naming": "Benennung der Downloads",
|
||||||
|
"Downloaded tracks filename": "Dateiname der heruntergeladenen Titel",
|
||||||
|
"Valid variables are": "Gültige Variablen sind",
|
||||||
|
"Reset": "Zurücksetzen",
|
||||||
|
"Clear": "Löschen",
|
||||||
|
"Create folders for artist": "Ordner für Künstler erstellen",
|
||||||
|
"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",
|
||||||
|
"Copy ARL": "ARL kopieren",
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.":
|
||||||
|
"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.",
|
||||||
|
"(ARL ONLY) Continue": "(NUR ARL) Fortfahren",
|
||||||
|
"Log out & Exit": "Abmelden & Beenden",
|
||||||
|
"Pick-a-Path": "Wähle einen Pfad",
|
||||||
|
"Select storage": "Verzeichnis auswählen",
|
||||||
|
"Go up": "Nach oben",
|
||||||
|
"Permission denied": "Zugriff verweigert",
|
||||||
|
"Language": "Sprache",
|
||||||
|
"Language changed, please restart Freezer to apply!":
|
||||||
|
"Sprache geändert, bitte Freezer neu starten!",
|
||||||
|
"Importing...": "Importiere...",
|
||||||
|
"Radio": "Radio"
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,187 @@
|
||||||
|
const language_en_us = {
|
||||||
|
"en_us": {
|
||||||
|
"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",
|
||||||
|
"Light (default)": "Light (Default)",
|
||||||
|
"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 discs",
|
||||||
|
"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",
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Translated by: Andrea
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const language_it_it = {
|
||||||
|
"it_it": {
|
||||||
|
"Home": "Pagina Iniziale",
|
||||||
|
"Search": "Cerca",
|
||||||
|
"Library": "Libreria",
|
||||||
|
"Offline mode, can't play flow or smart track lists.":
|
||||||
|
"Modalità offline, non è possibile riprodurre flow o tracklist smart",
|
||||||
|
"Added to library": "Aggiunto Alla Libreria",
|
||||||
|
"Download": "Download",
|
||||||
|
"Disk": "Disco",
|
||||||
|
"Offline": "Offline",
|
||||||
|
"Top Tracks": "Top Tracks",
|
||||||
|
"Show more tracks": "Mostra più tracce",
|
||||||
|
"Top": "Top",
|
||||||
|
"Top Albums": "Top Albums",
|
||||||
|
"Show all albums": "Mostra tutti gli albums",
|
||||||
|
"Discography": "Discografia",
|
||||||
|
"Default": "Default",
|
||||||
|
"Reverse": "Reverse",
|
||||||
|
"Alphabetic": "Alfabetico",
|
||||||
|
"Artist": "Artista",
|
||||||
|
"Post processing...": "Post processing...",
|
||||||
|
"Done": "Terminato",
|
||||||
|
"Delete": "Cancellato",
|
||||||
|
"Are you sure you want to delete this download?":
|
||||||
|
"Sei sicuro di voler cancellare questo download?",
|
||||||
|
"Cancel": "Cancella",
|
||||||
|
"Downloads": "Downloads",
|
||||||
|
"Clear queue": "Pulisci la coda",
|
||||||
|
"This won't delete currently downloading item":
|
||||||
|
"Questa azione non cancellerà i downloads",
|
||||||
|
"Are you sure you want to delete all queued downloads?":
|
||||||
|
"Sei sicuro di voler cancellare tutti i downloads in coda?",
|
||||||
|
"Clear downloads history": "Pulisci la cronologia dei downloads",
|
||||||
|
"WARNING: This will only clear non-offline (external downloads)":
|
||||||
|
"ATTENZIONE: Questa azione, pulirà solo i files che non sono offline (download esterni)",
|
||||||
|
"Please check your connection and try again later...":
|
||||||
|
"Per favore controlla la tua connessione e riprova più tardi...",
|
||||||
|
"Show more": "Mostra di più",
|
||||||
|
"Importer": "Importa",
|
||||||
|
"Currently supporting only Spotify, with 100 tracks limit":
|
||||||
|
"Attualmente supporta solo Spotify, con un limite di 100 tracce",
|
||||||
|
"Due to API limitations": "A Causa delle limitazioni dell' API",
|
||||||
|
"Enter your playlist link below":
|
||||||
|
"Inserisci il link della tua playlist qui sotto",
|
||||||
|
"Error loading URL!": "Errore di caricamento dell' URL!",
|
||||||
|
"Convert": "Converti",
|
||||||
|
"Download only": "Solo Download",
|
||||||
|
"Downloading is currently stopped, click here to resume.":
|
||||||
|
"Il download è attualmente interrotto, fare clic qui per riprenderlo.",
|
||||||
|
"Tracks": "Tracce",
|
||||||
|
"Albums": "Albums",
|
||||||
|
"Artists": "Artisti",
|
||||||
|
"Playlists": "Playlists",
|
||||||
|
"Import": "Importa",
|
||||||
|
"Import playlists from Spotify": "Importa playlists da Spotify",
|
||||||
|
"Statistics": "Statistiche",
|
||||||
|
"Offline tracks": "Tracce Offline",
|
||||||
|
"Offline albums": "Offline albums",
|
||||||
|
"Offline playlists": "Offline playlists",
|
||||||
|
"Offline size": "Offline size",
|
||||||
|
"Free space": "Spazio Libero",
|
||||||
|
"Loved tracks": "Tracce Amate",
|
||||||
|
"Favorites": "Preferiti",
|
||||||
|
"All offline tracks": "tutte le tracce offline",
|
||||||
|
"Create new playlist": "Crea nuova playlist",
|
||||||
|
"Cannot create playlists in offline mode":
|
||||||
|
"Impossibile creare playlist in modalità offline",
|
||||||
|
"Error": "Errore",
|
||||||
|
"Error logging in! Please check your token and internet connection and try again.":
|
||||||
|
"Errore durante l'accesso! Controlla il token e la connessione Internet e riprova.",
|
||||||
|
"Dismiss": "Respinto",
|
||||||
|
"Welcome to": "Benvenuto In",
|
||||||
|
"Please login using your Deezer account.":
|
||||||
|
"Per favore, esegui il login utilizzando il tuo account Deezer.",
|
||||||
|
"Login using browser": "Login utilizzando il browser",
|
||||||
|
"Login using token": "Login utilizzando il token",
|
||||||
|
"Enter ARL": "Inserisci l'ARL",
|
||||||
|
"Token (ARL)": "Token (ARL)",
|
||||||
|
"Save": "Salva",
|
||||||
|
"If you don't have account, you can register on deezer.com for free.":
|
||||||
|
"Se non possiede l'account, puoi registrarti sul sito deezer.com gratuitamente.",
|
||||||
|
"Open in browser": "Apri nel browser",
|
||||||
|
"By using this app, you don't agree with the Deezer ToS":
|
||||||
|
"Utilizzando questa applicazione, non sei daccordo con i ToS di Deezer",
|
||||||
|
"Play next": "Riproduci la prossima",
|
||||||
|
"Add to queue": "Aggiungi alla coda",
|
||||||
|
"Add track to favorites": "Aggiungi la traccia ai preferiti",
|
||||||
|
"Add to playlist": "Aggiungi alla Playlist",
|
||||||
|
"Select playlist": "Seleziona playlist",
|
||||||
|
"Track added to": "Traccia aggiunta a",
|
||||||
|
"Remove from playlist": "Rimuovi dalla playlist",
|
||||||
|
"Track removed from": "Traccia Rimossa Da",
|
||||||
|
"Remove favorite": "Rimuovi preferito",
|
||||||
|
"Track removed from library": "Traccia rimossa dalla libreria",
|
||||||
|
"Go to": "Vai A",
|
||||||
|
"Make offline": "Rendi Offline",
|
||||||
|
"Add to library": "Aggiungi alla libreria",
|
||||||
|
"Remove album": "Rimuovi album",
|
||||||
|
"Album removed": "Album rimosso",
|
||||||
|
"Remove from favorites": "Rimuovi dai preferiti",
|
||||||
|
"Artist removed from library": "Artista rimosso dalla libreria",
|
||||||
|
"Add to favorites": "Aggiungi ai preferiti",
|
||||||
|
"Remove from library": "Rimuovi dalla libreria",
|
||||||
|
"Add playlist to library": "Aggiungi playlist alla libreria",
|
||||||
|
"Added playlist to library": "Playlist aggiunta alla libreria",
|
||||||
|
"Make playlist offline": "Rendi la playlist offline",
|
||||||
|
"Download playlist": "Scarica Playlist",
|
||||||
|
"Create playlist": "Crea Playlist",
|
||||||
|
"Title": "Titolo",
|
||||||
|
"Description": "Descrizione",
|
||||||
|
"Private": "Privata",
|
||||||
|
"Collaborative": "Collaborativa",
|
||||||
|
"Create": "Crea",
|
||||||
|
"Playlist created!": "Playlist creata!",
|
||||||
|
"Playing from:": "Riproducendo da:",
|
||||||
|
"Queue": "Coda",
|
||||||
|
"Offline search": "Ricerca offline",
|
||||||
|
"Search Results": "Risultati della ricerca",
|
||||||
|
"No results!": "Nessun risultato!",
|
||||||
|
"Show all tracks": "Mostra tutte le tracce",
|
||||||
|
"Show all playlists": "Mostra tutte le playlists",
|
||||||
|
"Settings": "Opzioni",
|
||||||
|
"General": "Generale",
|
||||||
|
"Appearance": "Aspetto",
|
||||||
|
"Quality": "Qualità",
|
||||||
|
"Deezer": "Deezer",
|
||||||
|
"Theme": "Tema",
|
||||||
|
"Currently": "Attuale",
|
||||||
|
"Select theme": "Seleziona Tema",
|
||||||
|
"Light (default)": "Chiaro (Default)",
|
||||||
|
"Dark": "Scuro",
|
||||||
|
"Black (AMOLED)": "Nero (AMOLED)",
|
||||||
|
"Deezer (Dark)": "Deezer (Scuro)",
|
||||||
|
"Primary color": "Colore Principale",
|
||||||
|
"Selected color": "Colore Selezionato",
|
||||||
|
"Use album art primary color":
|
||||||
|
"Usa il colore principale della copertina dell'album",
|
||||||
|
"Warning: might be buggy": "Attenzione: potrebbe essere fallato",
|
||||||
|
"Mobile streaming": "Mobile Streaming",
|
||||||
|
"Wifi streaming": "Wifi streaming",
|
||||||
|
"External downloads": "Download Esterni",
|
||||||
|
"Content language": "Lingua dei contenuti",
|
||||||
|
"Not app language, used in headers. Now":
|
||||||
|
"Non la lingua dell'app, utilizzata nelle intestazioni. Adesso",
|
||||||
|
"Select language": "Seleziona la Lingua",
|
||||||
|
"Content country": "Contenuto Del Paese",
|
||||||
|
"Country used in headers. Now": "Paese contenuto nelle intestazioni. Ora",
|
||||||
|
"Log tracks": "Log delle tracce",
|
||||||
|
"Send track listen logs to Deezer, enable it for features like Flow to work properly":
|
||||||
|
"Invia i log delle canzioni ascoltate a Deezer, abilitalo affinché funzioni come Flow funzionino correttamente ",
|
||||||
|
"Offline mode": "Modalità Offline",
|
||||||
|
"Will be overwritten on start.": "Sarà sovrascritto all'avvio.",
|
||||||
|
"Error logging in, check your internet connections.":
|
||||||
|
"Errore durante l'accesso, controlla la tua connessione Internet.",
|
||||||
|
"Logging in...": "Accesso in corso...",
|
||||||
|
"Download path": "Percorso di download",
|
||||||
|
"Downloads naming": "Denominazione dei download",
|
||||||
|
"Downloaded tracks filename": "Nome del file delle tracce scaricate",
|
||||||
|
"Valid variables are": "Le variabili valide sono",
|
||||||
|
"Reset": "Reset",
|
||||||
|
"Clear": "Pulisci",
|
||||||
|
"Create folders for artist": "Crea cartelle per artista",
|
||||||
|
"Create folders for albums": "Crea cartelle per albums",
|
||||||
|
"Separate albums by discs": "Albums separati per disco",
|
||||||
|
"Overwrite already downloaded files": "Sovrascrivi i file già scaricati",
|
||||||
|
"Copy ARL": "Copia ARL",
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.":
|
||||||
|
"Copia userToken / ARL Cookie da utilizzare in altre apps.",
|
||||||
|
"Copied": "Copiato",
|
||||||
|
"Log out": "Log out",
|
||||||
|
"Due to plugin incompatibility, login using browser is unavailable without restart.":
|
||||||
|
"A causa dell'incompatibilità del plug-in, l'accesso tramite browser non è disponibile senza riavvio.",
|
||||||
|
"(ARL ONLY) Continue": "(SOLO ARL) Continua",
|
||||||
|
"Log out & Exit": "Log out & Esci",
|
||||||
|
"Pick-a-Path": "Scegli un percorso",
|
||||||
|
"Select storage": "Seleziona spazio di archiviazione",
|
||||||
|
"Go up": "Vai su",
|
||||||
|
"Permission denied": "Permesso negato",
|
||||||
|
"Language": "Lingua",
|
||||||
|
"Language changed, please restart Freezer to apply!":
|
||||||
|
"Lingua cambiata, riavvia Freezer per la modifica della lingua!",
|
||||||
|
"Importing...": "Importando...",
|
||||||
|
"Radio": "Radio"
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Translated by: Diego Hiro
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const language_pt_br = {"pt_br": {
|
||||||
|
"Home": "Início",
|
||||||
|
"Search": "Pesquisar",
|
||||||
|
"Library": "Biblioteca",
|
||||||
|
"Offline mode, can't play flow or smart track lists.": "Modo offline, incapaz de reproduzir faixas do flow(personalizadas) ou playlist inteligentes.",
|
||||||
|
"Added to library": "Adicionado para sua biblioteca",
|
||||||
|
"Download": "Download",
|
||||||
|
"Disk": "Disco",
|
||||||
|
"Offline": "Offline",
|
||||||
|
"Top Tracks": "Faixas no Top",
|
||||||
|
"Show more tracks": "Exibir mais faixas",
|
||||||
|
"Top": "Top",
|
||||||
|
"Top Albums": "Álbuns no Top",
|
||||||
|
"Show all albums": "Mostrar todos os álbuns",
|
||||||
|
"Discography": "Discografia",
|
||||||
|
"Default": "Padrão",
|
||||||
|
"Reverse": "Reverter",
|
||||||
|
"Alphabetic": "Alfabética",
|
||||||
|
"Artist": "Artista",
|
||||||
|
"Post processing...": "Processando...",
|
||||||
|
"Done": "Feito",
|
||||||
|
"Delete": "Deletar",
|
||||||
|
"Are you sure you want to delete this download?": "Tem certeza que deseja excluir este download?",
|
||||||
|
"Cancel": "Cancelar",
|
||||||
|
"Downloads": "Downloads",
|
||||||
|
"Clear queue": "Limpar fila",
|
||||||
|
"This won't delete currently downloading item": "Isso não excluirá os itens que estão fazendo download",
|
||||||
|
"Are you sure you want to delete all queued downloads?": "Tem certeza que deseja excluir todos os downloads que estão na fila?",
|
||||||
|
"Clear downloads history": "Limpar histórico de downloads",
|
||||||
|
"WARNING: This will only clear non-offline (external downloads)": "Cuidado: Isso limpará apenas faixas e listas off-line (downloads externos)",
|
||||||
|
"Please check your connection and try again later...": "Verifique sua conexão e tente novamente. Caso sua rede não esteja estável, tente mais tarde...",
|
||||||
|
"Show more": "Mostrar Mais",
|
||||||
|
"Importer": "importador",
|
||||||
|
"Currently supporting only Spotify, with 100 tracks limit": "Atualmente suportando apenas Spotify, com limite de 100 faixas",
|
||||||
|
"Due to API limitations": "Devido às limitações da API",
|
||||||
|
"Enter your playlist link below": "Insira o link da sua lista de reprodução abaixo",
|
||||||
|
"Error loading URL!": "Erro ao carregar URL!",
|
||||||
|
"Convert": "Converter",
|
||||||
|
"Download only": "Somente download",
|
||||||
|
"Downloading is currently stopped, click here to resume.": "O download está parado no momento, clique aqui para retomar.",
|
||||||
|
"Tracks": "Faixas",
|
||||||
|
"Albums": "Álbuns",
|
||||||
|
"Artists": "Artistas",
|
||||||
|
"Playlists": "Playlists",
|
||||||
|
"Import": "Importar",
|
||||||
|
"Import playlists from Spotify": "Importar playlists do Spotify",
|
||||||
|
"Statistics": "Estatísticas",
|
||||||
|
"Offline tracks": "Faixas Offline",
|
||||||
|
"Offline albums": "Álbuns Offline",
|
||||||
|
"Offline playlists": "Playlists Offline",
|
||||||
|
"Offline size": "Espaço ocupado Offline",
|
||||||
|
"Free space": "Espaço livre",
|
||||||
|
"Loved tracks": "Faixas que gostou",
|
||||||
|
"Favorites": "Favoritos",
|
||||||
|
"All offline tracks": "Todas as faixas offline",
|
||||||
|
"Create new playlist": "Criar nova playlist",
|
||||||
|
"Cannot create playlists in offline mode": "Não é possível criar playlists no modo offline",
|
||||||
|
"Error": "Erro",
|
||||||
|
"Error logging in! Please check your token and internet connection and try again.": "Erro ao tentar login! Verifique seu token e sua conexão com a Internet, tente novamente.",
|
||||||
|
"Dismiss": "Dispensar",
|
||||||
|
"Welcome to": "Bem-vindo ao",
|
||||||
|
"Please login using your Deezer account.": "Faça login usando sua conta Deezer.",
|
||||||
|
"Login using browser": "Faça login usando o navegador",
|
||||||
|
"Login using token": "Faça login usando o token",
|
||||||
|
"Enter ARL": "Inserir ARL",
|
||||||
|
"Token (ARL)": "Token (ARL)",
|
||||||
|
"Save": "Salvar",
|
||||||
|
"If you don't have account, you can register on deezer.com for free.": "Se você não tem uma conta, pode se registrar em deezer.com gratuitamente.",
|
||||||
|
"Open in browser": "Abra no navegador",
|
||||||
|
"By using this app, you don't agree with the Deezer ToS": "Ao usar este aplicativo, você não concorda com os Termos de Uso com a Deezer",
|
||||||
|
"Play next": "Tocar próxima",
|
||||||
|
"Add to queue": "Adicionar à fila",
|
||||||
|
"Add track to favorites": "Adicionar faixa aos favoritos",
|
||||||
|
"Add to playlist": "Adicionar à Playlist",
|
||||||
|
"Select playlist": "Selecionar playlist",
|
||||||
|
"Track added to": "Faixa adicionada para",
|
||||||
|
"Remove from playlist": "Remover da playlist",
|
||||||
|
"Track removed from": "Faixa removida do(a)",
|
||||||
|
"Remove favorite": "Remover favorito",
|
||||||
|
"Track removed from library": "Faixa removida da biblioteca",
|
||||||
|
"Go to": "Ir para",
|
||||||
|
"Make offline": "Reproduzir offline",
|
||||||
|
"Add to library": "Adicionar à biblioteca",
|
||||||
|
"Remove album": "Remover álbum",
|
||||||
|
"Album removed": "Álbum removido",
|
||||||
|
"Remove from favorites": "Remover do favoritos",
|
||||||
|
"Artist removed from library": "Artista Removido da biblioteca",
|
||||||
|
"Add to favorites": "Adicionar para favoritos",
|
||||||
|
"Remove from library": "Remover da biblioteca",
|
||||||
|
"Add playlist to library": "Adicionar playlist para biblioteca",
|
||||||
|
"Added playlist to library": "Playlist adicionada para biblioteca",
|
||||||
|
"Make playlist offline": "Converter playlist para modo offline",
|
||||||
|
"Download playlist": " Efetuar download da playlist",
|
||||||
|
"Create playlist": "Criar playlist",
|
||||||
|
"Title": "Título",
|
||||||
|
"Description": "Descrição",
|
||||||
|
"Private": "Privado",
|
||||||
|
"Collaborative": "Colaborativo",
|
||||||
|
"Create": "Criar",
|
||||||
|
"Playlist created!": "Playlist criada!",
|
||||||
|
"Playing from:": "Playing de:",
|
||||||
|
"Queue": "Fila",
|
||||||
|
"Offline search": "Pesquisa Offline",
|
||||||
|
"Search Results": "Resultado da pesquisa",
|
||||||
|
"No results!": "Nenhum resultado encontrado!",
|
||||||
|
"Show all tracks": "Mostrar todas as faixas",
|
||||||
|
"Show all playlists": "Mostrar todas playlists",
|
||||||
|
"Settings": "Configurações",
|
||||||
|
"General": "Geral",
|
||||||
|
"Appearance": "Aparência",
|
||||||
|
"Quality": "Qualidade",
|
||||||
|
"Deezer": "Deezer",
|
||||||
|
"Theme": "Tema",
|
||||||
|
"Currently": "Atualmente",
|
||||||
|
"Select theme": "Selecionar tema",
|
||||||
|
"Light (default)": "Claro (Padrão)",
|
||||||
|
"Dark": "Escuro",
|
||||||
|
"Black (AMOLED)": "Preto (AMOLED)",
|
||||||
|
"Deezer (Dark)": "Deezer (Escuro - Dark Mode)",
|
||||||
|
"Primary color": "Cor Primária",
|
||||||
|
"Selected color": "Cor selecionada",
|
||||||
|
"Use album art primary color": "Use a cor primária da capa do álbum",
|
||||||
|
"Warning: might be buggy": "Cuidado: pode ter erros dependendo do dispositivo",
|
||||||
|
"Mobile streaming": "Streaming por dados móveis",
|
||||||
|
"Wifi streaming": "Streaming por Rede Wifi",
|
||||||
|
"External downloads": "Downloads Externos",
|
||||||
|
"Content language": "Linguagem do conteúdo",
|
||||||
|
"Not app language, used in headers. Now": "Não é o idioma do aplicativo, programação feita em outra Linguagem. Agora",
|
||||||
|
"Select language": "Selecione a linguagem",
|
||||||
|
"Content country": "País do conteúdo a Exibir",
|
||||||
|
"Country used in headers. Now": "País habilitado no banco de dados. Agora",
|
||||||
|
"Log tracks": "Log de faixas",
|
||||||
|
"Send track listen logs to Deezer, enable it for features like Flow to work properly": "Enviar registros de faixas de trilhas para o Deezer, habilite-o para o funcionamento de recursos, como o Flow para funcionar corretamente",
|
||||||
|
"Offline mode": "Modo Offline",
|
||||||
|
"Will be overwritten on start.": "Será sobrescrito no próximo início do aplicativo.",
|
||||||
|
"Error logging in, check your internet connections.": "Erro ao fazer login, verifique suas conexões de internet.",
|
||||||
|
"Logging in...": "Logando em...",
|
||||||
|
"Download path": "Caminho de download",
|
||||||
|
"Downloads naming": "Nomenclatura de downloads",
|
||||||
|
"Downloaded tracks filename": "Nome de arquivo das faixas baixadas",
|
||||||
|
"Valid variables are": "Variáveis válidas são",
|
||||||
|
"Reset": "Resetar",
|
||||||
|
"Clear": "Limpar",
|
||||||
|
"Create folders for artist": "Create folders for artist",
|
||||||
|
"Create folders for albums": "Create folders for albums",
|
||||||
|
"Separate albums by discs": "Separate albums by discs",
|
||||||
|
"Overwrite already downloaded files": "Overwrite already downloaded files",
|
||||||
|
"Copy ARL": "Copiar ARL",
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.": "Copiar userToken/ARL Cookie para uso em outros aplicativos.",
|
||||||
|
"Copied": "Copiado",
|
||||||
|
"Log out": "Deslogar",
|
||||||
|
"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": "(Somente ARL) Continuar",
|
||||||
|
"Log out & Exit": "Deslogar & Sair",
|
||||||
|
"Pick-a-Path": "Escola-um-Caminho",
|
||||||
|
"Select storage": "Selecione o armazenamento",
|
||||||
|
"Go up": "Subir",
|
||||||
|
"Permission denied": "Permissão negada",
|
||||||
|
"Language": "Linguagem",
|
||||||
|
"Language changed, please restart Freezer to apply!": "Idioma alterado, reinicie o Freezer para aplicar!",
|
||||||
|
"Importing...": "Importando..."
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Translated by: Annexhack
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const language_ru_ru = {
|
||||||
|
"ru_ru": {
|
||||||
|
"Home": "Главная",
|
||||||
|
"Search": "Поиск",
|
||||||
|
"Library": "Библиотека",
|
||||||
|
"Offline mode, can't play flow or smart track lists.":
|
||||||
|
"Автономный режим, нельзя воспроизводить потоки или умные списки треков.",
|
||||||
|
"Added to library": "Добавить в библиотеку",
|
||||||
|
"Download": "Скачать",
|
||||||
|
"Disk": "Disk",
|
||||||
|
"Offline": "Офлайн",
|
||||||
|
"Top Tracks": "Лучшие треки",
|
||||||
|
"Show more tracks": "Показать больше треков",
|
||||||
|
"Top": "Top",
|
||||||
|
"Top Albums": "Лучшие альбомы",
|
||||||
|
"Show all albums": "Показать все альбомы",
|
||||||
|
"Discography": "Дискография",
|
||||||
|
"Default": "По умолчанию",
|
||||||
|
"Reverse": "Обратный",
|
||||||
|
"Alphabetic": "По алфавиту",
|
||||||
|
"Artist": "Артист",
|
||||||
|
"Post processing...": "Постобработка...",
|
||||||
|
"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)":
|
||||||
|
"ВНИМАНИЕ: Это очистит только не офлайн(external downloads)",
|
||||||
|
"Please check your connection and try again later...":
|
||||||
|
"Пожалуйста, проверьте ваше соединение и повторите попытку позже...",
|
||||||
|
"Show more": "Показать больше",
|
||||||
|
"Importer": "Импортер",
|
||||||
|
"Currently supporting only Spotify, with 100 tracks limit":
|
||||||
|
"В настоящее время поддерживается только Spotify с ограничением 100 треков",
|
||||||
|
"Due to API limitations": "Из-за ограничений API",
|
||||||
|
"Enter your playlist link below": "Введите ссылку на свой плейлист ниже",
|
||||||
|
"Error loading URL!": "Ошибка загрузки URL!",
|
||||||
|
"Convert": "Перерабатывать",
|
||||||
|
"Download only": "Только скачиные",
|
||||||
|
"Downloading is currently stopped, click here to resume.":
|
||||||
|
"В настоящее время загрузка остановлена, нажмите здесь, чтобы возобновить.",
|
||||||
|
"Tracks": "Треки",
|
||||||
|
"Albums": "Альбомы",
|
||||||
|
"Artists": "Артисты",
|
||||||
|
"Playlists": "Плейлисты",
|
||||||
|
"Import": "Import",
|
||||||
|
"Import playlists from Spotify": "Импортировать плейлисты из Spotify",
|
||||||
|
"Statistics": "Статистика",
|
||||||
|
"Offline tracks": "Автономные треки",
|
||||||
|
"Offline albums": "Автономные альбомы",
|
||||||
|
"Offline playlists": "Офлайн-плейлисты",
|
||||||
|
"Offline size": "Автономный размер",
|
||||||
|
"Free space": "Свободное место",
|
||||||
|
"Loved tracks": "Любимые треки",
|
||||||
|
"Favorites": "Избранное",
|
||||||
|
"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.":
|
||||||
|
"Ошибка входа! Проверьте свой токен и подключение к Интернету и повторите попытку.",
|
||||||
|
"Dismiss": "Отклонить",
|
||||||
|
"Welcome to": "Добро пожаловать в",
|
||||||
|
"Please login using your Deezer account.":
|
||||||
|
"Пожалуйста, войдите, используя свою учетную запись Deezer.",
|
||||||
|
"Login using browser": "Войти через браузер",
|
||||||
|
"Login using token": "Войти с помощью токена",
|
||||||
|
"Enter ARL": "Введите ARL",
|
||||||
|
"Token (ARL)": "Токен (ARL)",
|
||||||
|
"Save": "Сохранить",
|
||||||
|
"If you don't have account, you can register on deezer.com for free.":
|
||||||
|
"Если у вас нет учетной записи, вы можете бесплатно зарегистрироваться на deezer.com.",
|
||||||
|
"Open in browser": "Открыть в браузере",
|
||||||
|
"By using this app, you don't agree with the Deezer ToS":
|
||||||
|
"Используя это приложение, вы не соглашаетесь с Условиями использования Deezer.",
|
||||||
|
"Play next": "Следующая песня",
|
||||||
|
"Add to queue": "Добавить в очередь",
|
||||||
|
"Add track to favorites": "Добавить в избранное",
|
||||||
|
"Add to playlist": "Добавить в плейлист",
|
||||||
|
"Select playlist": "Выбрать плейлист",
|
||||||
|
"Track added to": "Трек добавлен в",
|
||||||
|
"Remove from playlist": "Удалить из плейлиста",
|
||||||
|
"Track removed from": "Трек удален из",
|
||||||
|
"Remove favorite": "Удалить избранное",
|
||||||
|
"Track removed from library": "Трек удален из библиотеки",
|
||||||
|
"Go to": "Перейти к",
|
||||||
|
"Make offline": "Сделать офлайн",
|
||||||
|
"Add to library": "Добавить в библиотеку",
|
||||||
|
"Remove album": "Удалить альбом",
|
||||||
|
"Album removed": "Альбом удален",
|
||||||
|
"Remove from favorites": "Удалить из Избранного",
|
||||||
|
"Artist removed from library": "Артист удален из библиотеки",
|
||||||
|
"Add to favorites": "Добавить в избранное",
|
||||||
|
"Remove from library": "Удалить из библиотеки",
|
||||||
|
"Add playlist to library": "Добавить плейлист в библиотеку",
|
||||||
|
"Added playlist to library": "Добавлен плейлист в библиотеку",
|
||||||
|
"Make playlist offline": "Сделать плейлист офлайн",
|
||||||
|
"Download playlist": "Скачать плейлист",
|
||||||
|
"Create playlist": "Создать плейлист",
|
||||||
|
"Title": "Название",
|
||||||
|
"Description": "Описание",
|
||||||
|
"Private": "Частный",
|
||||||
|
"Collaborative": "Совместная",
|
||||||
|
"Create": "Создать",
|
||||||
|
"Playlist created!": "Плейлист создан!",
|
||||||
|
"Playing from:": "Играя с:",
|
||||||
|
"Queue": "Очередь",
|
||||||
|
"Offline search": "Оффлайн поиск",
|
||||||
|
"Search Results": "Результаты поиска",
|
||||||
|
"No results!": "Нет результатов!",
|
||||||
|
"Show all tracks": "Показать все треки",
|
||||||
|
"Show all playlists": "Показать все плейлисты",
|
||||||
|
"Settings": "Настройки",
|
||||||
|
"General": "Общее",
|
||||||
|
"Appearance": "Внешность",
|
||||||
|
"Quality": "Качественный",
|
||||||
|
"Deezer": "Deezer",
|
||||||
|
"Theme": "Тема",
|
||||||
|
"Currently": "В настоящее время",
|
||||||
|
"Select theme": "Выберите тему",
|
||||||
|
"Light (default)": "Светлая (По умолчанию)",
|
||||||
|
"Dark": "Темная",
|
||||||
|
"Black (AMOLED)": "Черная (AMOLED)",
|
||||||
|
"Deezer (Dark)": "Deezer (Dark)",
|
||||||
|
"Primary color": "Основной цвет",
|
||||||
|
"Selected color": "Выбранный цвет",
|
||||||
|
"Use album art primary color": "Использовать основной цвет обложки альбома",
|
||||||
|
"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": "Страна, используемая в заголовках. Сейчас",
|
||||||
|
"Log tracks": "Журнал треков",
|
||||||
|
"Send track listen logs to Deezer, enable it for features like Flow to work properly":
|
||||||
|
"Отправьте журналы прослушивания треков в Deezer, включите его, чтобы такие функции, как Flow, работали правильно",
|
||||||
|
"Offline mode": "Автономный режим",
|
||||||
|
"Will be overwritten on start.": "Будет перезаписан при запуске.",
|
||||||
|
"Error logging in, check your internet connections.":
|
||||||
|
"Ошибка при входе, проверьте свои интернет-соединения.",
|
||||||
|
"Logging in...": "Происходит вход в систему...",
|
||||||
|
"Download path": "Скачать путь",
|
||||||
|
"Downloads naming": "Именование загрузок",
|
||||||
|
"Downloaded tracks filename": "Имя файла загруженных треков",
|
||||||
|
"Valid variables are": "Допустимые переменные:",
|
||||||
|
"Reset": "Сброс",
|
||||||
|
"Clear": "Очистить",
|
||||||
|
"Create folders for artist": "Создавайте папки для исполнителя",
|
||||||
|
"Create folders for albums": "Создавайте папки для альбомов",
|
||||||
|
"Separate albums by discs": "Отдельные альбомы по дискам",
|
||||||
|
"Overwrite already downloaded files": "Перезаписать уже загруженные файлы",
|
||||||
|
"Copy ARL": "Копировать ARL",
|
||||||
|
"Copy userToken/ARL Cookie for use in other apps.":
|
||||||
|
"Копировать userToken/ARL Cookie для использования в других приложениях.",
|
||||||
|
"Copied": "Скопировано",
|
||||||
|
"Log out": "Выйти",
|
||||||
|
"Due to plugin incompatibility, login using browser is unavailable without restart.":
|
||||||
|
"Из-за несовместимости плагинов вход через браузер без перезапуска невозможен.",
|
||||||
|
"(ARL ONLY) Continue": "(ARL ONLY) Продолжать",
|
||||||
|
"Log out & Exit": "Выйти и закрыть",
|
||||||
|
"Pick-a-Path": "Выбери путь",
|
||||||
|
"Select storage": "Выберите хранилище",
|
||||||
|
"Go up": "Подниматься",
|
||||||
|
"Permission denied": "Доступ запрещен",
|
||||||
|
"Language": "Язык",
|
||||||
|
"Language changed, please restart Freezer to apply!":
|
||||||
|
"Язык изменен, перезапустите Freezer, чтобы применить!",
|
||||||
|
"Importing...": "Импорт...",
|
||||||
|
"Radio": "Радио"
|
||||||
|
}
|
||||||
|
};
|
124
lib/main.dart
|
@ -1,28 +1,29 @@
|
||||||
import 'package:custom_navigator/custom_navigator.dart';
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:custom_navigator/custom_navigator.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.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';
|
||||||
|
import 'package:i18n_extension/i18n_widget.dart';
|
||||||
import 'package:move_to_background/move_to_background.dart';
|
import 'package:move_to_background/move_to_background.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import 'ui/player_bar.dart';
|
|
||||||
import 'api/deezer.dart';
|
import 'api/deezer.dart';
|
||||||
import 'settings.dart';
|
|
||||||
import 'ui/cached_image.dart';
|
|
||||||
import 'api/download.dart';
|
import 'api/download.dart';
|
||||||
import 'api/player.dart';
|
import 'api/player.dart';
|
||||||
|
import 'settings.dart';
|
||||||
import 'ui/home_screen.dart';
|
import 'ui/home_screen.dart';
|
||||||
|
import 'ui/player_bar.dart';
|
||||||
|
|
||||||
Function updateTheme;
|
Function updateTheme;
|
||||||
Function logOut;
|
Function logOut;
|
||||||
GlobalKey<NavigatorState> mainNavigatorKey = GlobalKey<NavigatorState>();
|
GlobalKey<NavigatorState> mainNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
GlobalKey<NavigatorState> navigatorKey;
|
GlobalKey<NavigatorState> navigatorKey;
|
||||||
|
|
||||||
void main() async {
|
|
||||||
|
|
||||||
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
//Initialize globals
|
//Initialize globals
|
||||||
|
@ -57,11 +58,22 @@ class _FreezerAppState extends State<FreezerApp> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Locale _locale() {
|
||||||
|
if (settings.language == null || settings.language.split('_').length < 2) return null;
|
||||||
|
return Locale(settings.language.split('_')[0], settings.language.split('_')[1]);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'freezer',
|
title: 'Freezer',
|
||||||
theme: settings.themeData,
|
theme: settings.themeData,
|
||||||
|
localizationsDelegates: [
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: supportedLocales,
|
||||||
home: WillPopScope(
|
home: WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
//For some reason AudioServiceWidget caused the app to freeze after 2 back button presses. "fix"
|
//For some reason AudioServiceWidget caused the app to freeze after 2 back button presses. "fix"
|
||||||
|
@ -72,7 +84,10 @@ class _FreezerAppState extends State<FreezerApp> {
|
||||||
await MoveToBackground.moveTaskToBack();
|
await MoveToBackground.moveTaskToBack();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: LoginMainWrapper(),
|
child: I18n(
|
||||||
|
initialLocale: _locale(),
|
||||||
|
child: LoginMainWrapper(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
navigatorKey: mainNavigatorKey,
|
navigatorKey: mainNavigatorKey,
|
||||||
);
|
);
|
||||||
|
@ -86,7 +101,6 @@ class LoginMainWrapper extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (settings.arl != null) {
|
if (settings.arl != null) {
|
||||||
|
@ -116,25 +130,20 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (settings.arl == null)
|
if (settings.arl == null)
|
||||||
return LoginWidget(callback: () => setState(() => {}),);
|
return LoginWidget(
|
||||||
|
callback: () => setState(() => {}),
|
||||||
|
);
|
||||||
return MainScreen();
|
return MainScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MainScreen extends StatefulWidget {
|
class MainScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_MainScreenState createState() => _MainScreenState();
|
_MainScreenState createState() => _MainScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MainScreenState extends State<MainScreen> {
|
class _MainScreenState extends State<MainScreen> {
|
||||||
|
List<Widget> _screens = [HomeScreen(), SearchScreen(), LibraryScreen()];
|
||||||
List<Widget> _screens = [
|
|
||||||
HomeScreen(),
|
|
||||||
SearchScreen(),
|
|
||||||
LibraryScreen()
|
|
||||||
];
|
|
||||||
int _selected = 0;
|
int _selected = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -146,50 +155,45 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
bottomNavigationBar: Column(
|
bottomNavigationBar: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
PlayerBar(),
|
PlayerBar(),
|
||||||
BottomNavigationBar(
|
BottomNavigationBar(
|
||||||
backgroundColor: Theme.of(context).bottomAppBarColor,
|
backgroundColor: Theme.of(context).bottomAppBarColor,
|
||||||
currentIndex: _selected,
|
currentIndex: _selected,
|
||||||
onTap: (int s) async {
|
onTap: (int s) async {
|
||||||
//Pop all routes until home screen
|
|
||||||
|
//Pop all routes until home screen
|
||||||
|
while (navigatorKey.currentState.canPop()) {
|
||||||
|
await navigatorKey.currentState.maybePop();
|
||||||
|
}
|
||||||
|
|
||||||
while (navigatorKey.currentState.canPop()) {
|
|
||||||
await navigatorKey.currentState.maybePop();
|
await navigatorKey.currentState.maybePop();
|
||||||
}
|
setState(() {
|
||||||
|
_selected = s;
|
||||||
await navigatorKey.currentState.maybePop();
|
});
|
||||||
setState(() {
|
},
|
||||||
_selected = s;
|
selectedItemColor: Theme.of(context).primaryColor,
|
||||||
});
|
items: <BottomNavigationBarItem>[
|
||||||
},
|
BottomNavigationBarItem(
|
||||||
selectedItemColor: Theme.of(context).primaryColor,
|
icon: Icon(Icons.home), title: Text('Home'.i18n)),
|
||||||
items: <BottomNavigationBarItem>[
|
BottomNavigationBarItem(
|
||||||
BottomNavigationBarItem(
|
icon: Icon(Icons.search),
|
||||||
icon: Icon(Icons.home),
|
title: Text('Search'.i18n),
|
||||||
title: Text('Home')
|
),
|
||||||
),
|
BottomNavigationBarItem(
|
||||||
BottomNavigationBarItem(
|
icon: Icon(Icons.library_music), title: Text('Library'.i18n))
|
||||||
icon: Icon(Icons.search),
|
],
|
||||||
title: Text('Search'),
|
)
|
||||||
),
|
],
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.library_music),
|
|
||||||
title: Text('Library')
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: AudioServiceWidget(
|
|
||||||
child: CustomNavigator(
|
|
||||||
navigatorKey: navigatorKey,
|
|
||||||
home: _screens[_selected],
|
|
||||||
pageRoute: PageRoutes.materialPageRoute,
|
|
||||||
),
|
),
|
||||||
)
|
body: AudioServiceWidget(
|
||||||
);
|
child: CustomNavigator(
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
|
home: _screens[_selected],
|
||||||
|
pageRoute: PageRoutes.materialPageRoute,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@ Settings settings;
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Settings {
|
class Settings {
|
||||||
|
|
||||||
|
//Language
|
||||||
|
@JsonKey(defaultValue: null)
|
||||||
|
String language;
|
||||||
|
|
||||||
//Account
|
//Account
|
||||||
String arl;
|
String arl;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
|
|
@ -11,6 +11,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
||||||
downloadPath: json['downloadPath'] as String,
|
downloadPath: json['downloadPath'] as String,
|
||||||
arl: json['arl'] as String,
|
arl: json['arl'] as String,
|
||||||
)
|
)
|
||||||
|
..language = json['language'] as String
|
||||||
..wifiQuality =
|
..wifiQuality =
|
||||||
_$enumDecodeNullable(_$AudioQualityEnumMap, json['wifiQuality']) ??
|
_$enumDecodeNullable(_$AudioQualityEnumMap, json['wifiQuality']) ??
|
||||||
AudioQuality.MP3_320
|
AudioQuality.MP3_320
|
||||||
|
@ -39,6 +40,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||||
|
'language': instance.language,
|
||||||
'arl': instance.arl,
|
'arl': instance.arl,
|
||||||
'wifiQuality': _$AudioQualityEnumMap[instance.wifiQuality],
|
'wifiQuality': _$AudioQualityEnumMap[instance.wifiQuality],
|
||||||
'mobileQuality': _$AudioQualityEnumMap[instance.mobileQuality],
|
'mobileQuality': _$AudioQualityEnumMap[instance.mobileQuality],
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/languages/ar_ar.dart';
|
||||||
|
import 'package:freezer/languages/de_de.dart';
|
||||||
|
import 'package:freezer/languages/en_us.dart';
|
||||||
|
import 'package:freezer/languages/it_it.dart';
|
||||||
|
import 'package:freezer/languages/pt_br.dart';
|
||||||
|
import 'package:freezer/languages/ru_ru.dart';
|
||||||
|
import 'package:i18n_extension/i18n_extension.dart';
|
||||||
|
|
||||||
|
const supportedLocales = [
|
||||||
|
const Locale('en', 'US'),
|
||||||
|
const Locale('ar', 'AR'),
|
||||||
|
const Locale('pt', 'BR'),
|
||||||
|
const Locale('it', 'IT'),
|
||||||
|
const Locale('de', 'DE'),
|
||||||
|
const Locale('ru', 'RU')
|
||||||
|
];
|
||||||
|
|
||||||
|
extension Localization on String {
|
||||||
|
static var _t = Translations.byLocale("en_US") +
|
||||||
|
language_en_us + language_ar_ar + language_pt_br + language_it_it + language_de_de + language_ru_ru;
|
||||||
|
|
||||||
|
String get i18n => localize(this, _t);
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:freezer/api/deezer.dart';
|
||||||
|
import 'package:freezer/api/definitions.dart';
|
||||||
|
import 'package:freezer/api/player.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
|
class AndroidAuto {
|
||||||
|
|
||||||
|
//Prefix for "playable" MediaItem
|
||||||
|
static const prefix = '_aa_';
|
||||||
|
|
||||||
|
//Get media items for parent id
|
||||||
|
Future<List<MediaItem>> getScreen(String parentId) async {
|
||||||
|
print(parentId);
|
||||||
|
|
||||||
|
//Homescreen
|
||||||
|
if (parentId == 'root' || parentId == null) return homeScreen();
|
||||||
|
|
||||||
|
//Playlists screen
|
||||||
|
if (parentId == 'playlists') {
|
||||||
|
//Fetch
|
||||||
|
List<Playlist> playlists = await deezerAPI.getPlaylists();
|
||||||
|
|
||||||
|
List<MediaItem> out = playlists.map<MediaItem>((p) => MediaItem(
|
||||||
|
id: '${prefix}playlist${p.id}',
|
||||||
|
displayTitle: p.title,
|
||||||
|
displaySubtitle: p.trackCount.toString() + ' ' + 'Tracks'.i18n,
|
||||||
|
playable: true,
|
||||||
|
artUri: p.image.thumb
|
||||||
|
)).toList();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Albums screen
|
||||||
|
if (parentId == 'albums') {
|
||||||
|
List<Album> albums = await deezerAPI.getAlbums();
|
||||||
|
|
||||||
|
List<MediaItem> out = albums.map<MediaItem>((a) => MediaItem(
|
||||||
|
id: '${prefix}album${a.id}',
|
||||||
|
displayTitle: a.title,
|
||||||
|
displaySubtitle: a.artistString,
|
||||||
|
playable: true,
|
||||||
|
artUri: a.art.thumb,
|
||||||
|
)).toList();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Artists screen
|
||||||
|
if (parentId == 'artists') {
|
||||||
|
List<Artist> artists = await deezerAPI.getArtists();
|
||||||
|
|
||||||
|
List<MediaItem> out = artists.map<MediaItem>((a) => MediaItem(
|
||||||
|
id: 'albums${a.id}',
|
||||||
|
displayTitle: a.name,
|
||||||
|
playable: false,
|
||||||
|
artUri: a.picture.thumb
|
||||||
|
)).toList();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Artist screen (albums, etc)
|
||||||
|
if (parentId.startsWith('albums')) {
|
||||||
|
List<Album> albums = await deezerAPI.discographyPage(parentId.replaceFirst('albums', ''));
|
||||||
|
|
||||||
|
List<MediaItem> out = albums.map<MediaItem>((a) => MediaItem(
|
||||||
|
id: '${prefix}album${a.id}',
|
||||||
|
displayTitle: a.title,
|
||||||
|
displaySubtitle: a.artistString,
|
||||||
|
playable: true,
|
||||||
|
artUri: a.art.thumb
|
||||||
|
)).toList();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Homescreen
|
||||||
|
if (parentId == 'homescreen') {
|
||||||
|
HomePage hp = await deezerAPI.homePage();
|
||||||
|
List<MediaItem> out = [];
|
||||||
|
for (HomePageSection section in hp.sections) {
|
||||||
|
for (int i=0; i<section.items.length; i++) {
|
||||||
|
//Limit to max 5 items
|
||||||
|
if (i == 5) break;
|
||||||
|
|
||||||
|
//Check type
|
||||||
|
var data = section.items[i].value;
|
||||||
|
switch (section.items[i].type) {
|
||||||
|
|
||||||
|
case HomePageItemType.PLAYLIST:
|
||||||
|
out.add(MediaItem(
|
||||||
|
id: '${prefix}playlist${data.id}',
|
||||||
|
displayTitle: data.title,
|
||||||
|
playable: true,
|
||||||
|
artUri: data.image.thumb
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HomePageItemType.ALBUM:
|
||||||
|
out.add(MediaItem(
|
||||||
|
id: '${prefix}album${data.id}',
|
||||||
|
displayTitle: data.title,
|
||||||
|
displaySubtitle: data.artistString,
|
||||||
|
playable: true,
|
||||||
|
artUri: data.art.thumb
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HomePageItemType.ARTIST:
|
||||||
|
out.add(MediaItem(
|
||||||
|
id: 'albums${data.id}',
|
||||||
|
displayTitle: data.name,
|
||||||
|
playable: false,
|
||||||
|
artUri: data.picture.thumb
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HomePageItemType.SMARTTRACKLIST:
|
||||||
|
out.add(MediaItem(
|
||||||
|
id: '${prefix}stl${data.id}',
|
||||||
|
displayTitle: data.title,
|
||||||
|
displaySubtitle: data.subtitle,
|
||||||
|
playable: true,
|
||||||
|
artUri: data.cover.thumb
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load virtual mediaItem
|
||||||
|
Future playItem(String id) async {
|
||||||
|
|
||||||
|
print(id);
|
||||||
|
|
||||||
|
//Play flow
|
||||||
|
if (id == 'flow' || id == 'stlflow') {
|
||||||
|
await playerHelper.playFromSmartTrackList(SmartTrackList(id: 'flow', title: 'Flow'.i18n));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Play library tracks
|
||||||
|
if (id == 'tracks') {
|
||||||
|
//Load tracks
|
||||||
|
Playlist favPlaylist;
|
||||||
|
try {
|
||||||
|
favPlaylist = await deezerAPI.fullPlaylist(deezerAPI.favoritesPlaylistId);
|
||||||
|
} catch (e) {print(e);}
|
||||||
|
if (favPlaylist == null || favPlaylist.tracks.length == 0) return;
|
||||||
|
|
||||||
|
await playerHelper.playFromTrackList(favPlaylist.tracks, favPlaylist.tracks[0].id, QueueSource(
|
||||||
|
id: 'allTracks',
|
||||||
|
text: 'All offline tracks'.i18n,
|
||||||
|
source: 'offline'
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Play playlists
|
||||||
|
if (id.startsWith('playlist')) {
|
||||||
|
Playlist p = await deezerAPI.fullPlaylist(id.replaceFirst('playlist', ''));
|
||||||
|
await playerHelper.playFromPlaylist(p, p.tracks[0].id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Play albums
|
||||||
|
if (id.startsWith('album')) {
|
||||||
|
Album a = await deezerAPI.album(id.replaceFirst('album', ''));
|
||||||
|
await playerHelper.playFromAlbum(a, a.tracks[0].id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Play smart track list
|
||||||
|
if (id.startsWith('stl')) {
|
||||||
|
SmartTrackList stl = await deezerAPI.smartTrackList(id.replaceFirst('stl', ''));
|
||||||
|
await playerHelper.playFromSmartTrackList(stl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Homescreen items
|
||||||
|
List<MediaItem> homeScreen() {
|
||||||
|
return [
|
||||||
|
MediaItem(
|
||||||
|
id: '${prefix}flow',
|
||||||
|
displayTitle: 'Flow'.i18n,
|
||||||
|
playable: true
|
||||||
|
),
|
||||||
|
MediaItem(
|
||||||
|
id: 'homescreen',
|
||||||
|
displayTitle: 'Home'.i18n,
|
||||||
|
playable: false,
|
||||||
|
),
|
||||||
|
MediaItem(
|
||||||
|
id: '${prefix}tracks',
|
||||||
|
displayTitle: 'Loved tracks'.i18n,
|
||||||
|
playable: true,
|
||||||
|
),
|
||||||
|
MediaItem(
|
||||||
|
id: 'playlists',
|
||||||
|
displayTitle: 'Playlists'.i18n,
|
||||||
|
playable: false,
|
||||||
|
),
|
||||||
|
MediaItem(
|
||||||
|
id: 'albums',
|
||||||
|
displayTitle: 'Albums'.i18n,
|
||||||
|
playable: false,
|
||||||
|
),
|
||||||
|
MediaItem(
|
||||||
|
id: 'artists',
|
||||||
|
displayTitle: 'Artists'.i18n,
|
||||||
|
playable: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -48,10 +48,18 @@ class CachedImage extends StatefulWidget {
|
||||||
class _CachedImageState extends State<CachedImage> {
|
class _CachedImageState extends State<CachedImage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
if (widget.circular) return ClipOval(
|
if (widget.circular) return ClipOval(
|
||||||
child: CachedImage(url: widget.url, height: widget.height, width: widget.width, circular: false)
|
child: CachedImage(url: widget.url, height: widget.height, width: widget.width, circular: false)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!widget.url.startsWith('http'))
|
||||||
|
return Image.asset(
|
||||||
|
widget.url,
|
||||||
|
width: widget.width,
|
||||||
|
height: widget.height,
|
||||||
|
);
|
||||||
|
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: widget.url,
|
imageUrl: widget.url,
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
|
|
|
@ -6,9 +6,9 @@ import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
import 'package:freezer/ui/search.dart';
|
import 'package:freezer/ui/search.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import '../api/definitions.dart';
|
import '../api/definitions.dart';
|
||||||
import 'player_bar.dart';
|
|
||||||
import 'cached_image.dart';
|
import 'cached_image.dart';
|
||||||
import 'tiles.dart';
|
import 'tiles.dart';
|
||||||
import 'menu.dart';
|
import 'menu.dart';
|
||||||
|
@ -134,13 +134,13 @@ class AlbumDetails extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(Icons.favorite, size: 32),
|
Icon(Icons.favorite, size: 32),
|
||||||
Container(width: 4,),
|
Container(width: 4,),
|
||||||
Text('Library')
|
Text('Library'.i18n)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await deezerAPI.addFavoriteAlbum(album.id);
|
await deezerAPI.addFavoriteAlbum(album.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library',
|
msg: 'Added to library'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
|
@ -152,7 +152,7 @@ class AlbumDetails extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(Icons.file_download, size: 32.0,),
|
Icon(Icons.file_download, size: 32.0,),
|
||||||
Container(width: 4,),
|
Container(width: 4,),
|
||||||
Text('Download')
|
Text('Download'.i18n)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -168,7 +168,7 @@ class AlbumDetails extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 4.0),
|
padding: EdgeInsets.symmetric(vertical: 4.0),
|
||||||
child: Text('Disk ${cdi + 1}'),
|
child: Text('Disk'.i18n + ' ${cdi + 1}'),
|
||||||
),
|
),
|
||||||
...List.generate(tracks.length, (i) => TrackTile(
|
...List.generate(tracks.length, (i) => TrackTile(
|
||||||
tracks[i],
|
tracks[i],
|
||||||
|
@ -237,7 +237,7 @@ class _MakeAlbumOfflineState extends State<MakeAlbumOffline> {
|
||||||
),
|
),
|
||||||
Container(width: 4.0,),
|
Container(width: 4.0,),
|
||||||
Text(
|
Text(
|
||||||
'Offline',
|
'Offline'.i18n,
|
||||||
style: TextStyle(fontSize: 16),
|
style: TextStyle(fontSize: 16),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -345,25 +345,43 @@ class ArtistDetails extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(Icons.favorite, size: 32),
|
Icon(Icons.favorite, size: 32),
|
||||||
Container(width: 4,),
|
Container(width: 4,),
|
||||||
Text('Library')
|
Text('Library'.i18n)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await deezerAPI.addFavoriteArtist(artist.id);
|
await deezerAPI.addFavoriteArtist(artist.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library',
|
msg: 'Added to library'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if ((artist.radio??false))
|
||||||
|
FlatButton(
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(Icons.radio, size: 32),
|
||||||
|
Container(width: 4,),
|
||||||
|
Text('Radio'.i18n)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
List<Track> tracks = await deezerAPI.smartRadio(artist.id);
|
||||||
|
playerHelper.playFromTrackList(tracks, tracks[0].id, QueueSource(
|
||||||
|
id: artist.id,
|
||||||
|
text: 'Radio'.i18n + ' ${artist.name}',
|
||||||
|
source: 'smartradio'
|
||||||
|
));
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 16.0,),
|
Container(height: 16.0,),
|
||||||
//Top tracks
|
//Top tracks
|
||||||
Text(
|
Text(
|
||||||
'Top Tracks',
|
'Top Tracks'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
@ -390,12 +408,12 @@ class ArtistDetails extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Show more tracks'),
|
title: Text('Show more tracks'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => TrackListScreen(artist.topTracks, QueueSource(
|
MaterialPageRoute(builder: (context) => TrackListScreen(artist.topTracks, QueueSource(
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
text: 'Top ${artist.name}',
|
text: 'Top'.i18n + '${artist.name}',
|
||||||
source: 'topTracks'
|
source: 'topTracks'
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
|
@ -404,7 +422,7 @@ class ArtistDetails extends StatelessWidget {
|
||||||
Divider(),
|
Divider(),
|
||||||
//Albums
|
//Albums
|
||||||
Text(
|
Text(
|
||||||
'Top Albums',
|
'Top Albums'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
@ -415,7 +433,7 @@ class ArtistDetails extends StatelessWidget {
|
||||||
//Show discography
|
//Show discography
|
||||||
if (i == 10 || i == artist.albums.length) {
|
if (i == 10 || i == artist.albums.length) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text('Show all albums'),
|
title: Text('Show all albums'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => DiscographyScreen(artist: artist,))
|
MaterialPageRoute(builder: (context) => DiscographyScreen(artist: artist,))
|
||||||
|
@ -552,7 +570,7 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Discography'),
|
title: Text('Discography'.i18n),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(icon: Icon(Icons.album)),
|
Tab(icon: Icon(Icons.album)),
|
||||||
|
@ -603,6 +621,7 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
|
||||||
|
|
||||||
enum SortType {
|
enum SortType {
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
REVERSE,
|
||||||
ALPHABETIC,
|
ALPHABETIC,
|
||||||
ARTIST
|
ARTIST
|
||||||
}
|
}
|
||||||
|
@ -634,6 +653,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
case SortType.ARTIST:
|
case SortType.ARTIST:
|
||||||
tracks.sort((a, b) => a.artists[0].name.compareTo(b.artists[0].name));
|
tracks.sort((a, b) => a.artists[0].name.compareTo(b.artists[0].name));
|
||||||
return tracks;
|
return tracks;
|
||||||
|
case SortType.REVERSE:
|
||||||
|
return tracks.reversed.toList();
|
||||||
case SortType.DEFAULT:
|
case SortType.DEFAULT:
|
||||||
default:
|
default:
|
||||||
return tracks;
|
return tracks;
|
||||||
|
@ -782,32 +803,20 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FlatButton(
|
MakePlaylistOffline(playlist),
|
||||||
child: Row(
|
IconButton(
|
||||||
children: <Widget>[
|
icon: Icon(Icons.favorite, size: 32),
|
||||||
Icon(Icons.favorite, size: 32),
|
|
||||||
Container(width: 4,),
|
|
||||||
Text('Library')
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await deezerAPI.addFavoriteAlbum(playlist.id);
|
await deezerAPI.addFavoriteAlbum(playlist.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library',
|
msg: 'Added to library'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MakePlaylistOffline(playlist),
|
IconButton(
|
||||||
FlatButton(
|
icon: Icon(Icons.file_download, size: 32.0,),
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(Icons.file_download, size: 32.0,),
|
|
||||||
Container(width: 4,),
|
|
||||||
Text('Download')
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
downloadManager.addOfflinePlaylist(playlist, private: false);
|
downloadManager.addOfflinePlaylist(playlist, private: false);
|
||||||
},
|
},
|
||||||
|
@ -816,17 +825,21 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||||
child: Icon(Icons.sort, size: 32.0),
|
child: Icon(Icons.sort, size: 32.0),
|
||||||
onSelected: (SortType s) => setState(() => _sort = s),
|
onSelected: (SortType s) => setState(() => _sort = s),
|
||||||
itemBuilder: (context) => <PopupMenuEntry<SortType>>[
|
itemBuilder: (context) => <PopupMenuEntry<SortType>>[
|
||||||
const PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SortType.DEFAULT,
|
value: SortType.DEFAULT,
|
||||||
child: Text('Default'),
|
child: Text('Default'.i18n),
|
||||||
),
|
),
|
||||||
const PopupMenuItem(
|
PopupMenuItem(
|
||||||
|
value: SortType.REVERSE,
|
||||||
|
child: Text('Reverse'.i18n),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
value: SortType.ALPHABETIC,
|
value: SortType.ALPHABETIC,
|
||||||
child: Text('Alphabetic'),
|
child: Text('Alphabetic'.i18n),
|
||||||
),
|
),
|
||||||
const PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: SortType.ARTIST,
|
value: SortType.ARTIST,
|
||||||
child: Text('Artist'),
|
child: Text('Artist'.i18n),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -914,7 +927,7 @@ class _MakePlaylistOfflineState extends State<MakePlaylistOffline> {
|
||||||
),
|
),
|
||||||
Container(width: 4.0,),
|
Container(width: 4.0,),
|
||||||
Text(
|
Text(
|
||||||
'Offline',
|
'Offline'.i18n,
|
||||||
style: TextStyle(fontSize: 16),
|
style: TextStyle(fontSize: 16),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:filesize/filesize.dart';
|
import 'package:filesize/filesize.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import 'cached_image.dart';
|
import 'cached_image.dart';
|
||||||
import '../api/download.dart';
|
import '../api/download.dart';
|
||||||
|
@ -17,9 +18,9 @@ class DownloadTile extends StatelessWidget {
|
||||||
case DownloadState.DOWNLOADING:
|
case DownloadState.DOWNLOADING:
|
||||||
return '${filesize(download.received)} / ${filesize(download.total)}';
|
return '${filesize(download.received)} / ${filesize(download.total)}';
|
||||||
case DownloadState.POST:
|
case DownloadState.POST:
|
||||||
return 'Post processing...';
|
return 'Post processing...'.i18n;
|
||||||
case DownloadState.DONE:
|
case DownloadState.DONE:
|
||||||
return 'Done'; //Shouldn't be visible
|
return 'Done'.i18n; //Shouldn't be visible
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -27,6 +28,7 @@ class DownloadTile extends StatelessWidget {
|
||||||
Widget get progressBar {
|
Widget get progressBar {
|
||||||
switch (download.state) {
|
switch (download.state) {
|
||||||
case DownloadState.DOWNLOADING:
|
case DownloadState.DOWNLOADING:
|
||||||
|
print(download.track.id);
|
||||||
return LinearProgressIndicator(value: download.received / download.total);
|
return LinearProgressIndicator(value: download.received / download.total);
|
||||||
case DownloadState.POST:
|
case DownloadState.POST:
|
||||||
return LinearProgressIndicator();
|
return LinearProgressIndicator();
|
||||||
|
@ -62,15 +64,15 @@ class DownloadTile extends StatelessWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Delete'),
|
title: Text('Delete'.i18n),
|
||||||
content: Text('Are you sure, you want to delete this download?'),
|
content: Text('Are you sure you want to delete this download?'.i18n),
|
||||||
actions: [
|
actions: [
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Cancel'),
|
child: Text('Cancel'.i18n),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Delete'),
|
child: Text('Delete'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
downloadManager.removeDownload(download);
|
downloadManager.removeDownload(download);
|
||||||
if (this.onDelete != null) this.onDelete();
|
if (this.onDelete != null) this.onDelete();
|
||||||
|
@ -100,7 +102,7 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Downloads'),
|
title: Text('Downloads'.i18n),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(downloadManager.stopped ? Icons.play_arrow : Icons.stop),
|
icon: Icon(downloadManager.stopped ? Icons.play_arrow : Icons.stop),
|
||||||
|
@ -129,23 +131,23 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
||||||
}),
|
}),
|
||||||
if (downloadManager.queue.length > 1 || (downloadManager.stopped && downloadManager.queue.length > 0))
|
if (downloadManager.queue.length > 1 || (downloadManager.stopped && downloadManager.queue.length > 0))
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Clear queue'),
|
title: Text('Clear queue'.i18n),
|
||||||
subtitle: Text("This won't delete currently downloading item"),
|
subtitle: Text("This won't delete currently downloading item".i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Delete'),
|
title: Text('Delete'.i18n),
|
||||||
content: Text('Are you sure, you want to delete all queued downloads?'),
|
content: Text('Are you sure you want to delete all queued downloads?'.i18n),
|
||||||
actions: [
|
actions: [
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Cancel'),
|
child: Text('Cancel'.i18n),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Delete'),
|
child: Text('Delete'.i18n),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await downloadManager.clearQueue();
|
await downloadManager.clearQueue();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -181,9 +183,9 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
||||||
return DownloadTile(d);
|
return DownloadTile(d);
|
||||||
}),
|
}),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Clear downloads history'),
|
title: Text('Clear downloads history'.i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
subtitle: Text('WARNING: This will only clear non-offline (external downloads)'),
|
subtitle: Text('WARNING: This will only clear non-offline (external downloads)'.i18n),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await downloadManager.cleanDownloadHistory();
|
await downloadManager.cleanDownloadHistory();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
class ErrorScreen extends StatelessWidget {
|
class ErrorScreen extends StatelessWidget {
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class ErrorScreen extends StatelessWidget {
|
||||||
size: 64.0,
|
size: 64.0,
|
||||||
),
|
),
|
||||||
Container(height: 4.0,),
|
Container(height: 4.0,),
|
||||||
Text(message ?? 'Please check your connection and try again later...')
|
Text(message ?? 'Please check your connection and try again later...'.i18n)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
import 'package:freezer/ui/menu.dart';
|
import 'package:freezer/ui/menu.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'tiles.dart';
|
import 'tiles.dart';
|
||||||
import 'details_screens.dart';
|
import 'details_screens.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
@ -175,7 +176,7 @@ class _HomePageScreenState extends State<HomePageScreen> {
|
||||||
if (section.hasMore??false) {
|
if (section.hasMore??false) {
|
||||||
return FlatButton(
|
return FlatButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Show more',
|
'Show more'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20.0
|
fontSize: 20.0
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/definitions.dart';
|
import 'package:freezer/api/definitions.dart';
|
||||||
import 'package:freezer/api/spotify.dart';
|
import 'package:freezer/api/spotify.dart';
|
||||||
import 'package:freezer/ui/menu.dart';
|
import 'package:freezer/ui/menu.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
class ImporterScreen extends StatefulWidget {
|
class ImporterScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
|
@ -34,8 +35,8 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
||||||
setState(() => _data = data);
|
setState(() => _data = data);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
print(e);
|
print('$e, $st');
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = true;
|
_error = true;
|
||||||
_loading = false;
|
_loading = false;
|
||||||
|
@ -49,13 +50,13 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Importer'),
|
title: Text('Importer'.i18n),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Currently supporting only Spotify, with 100 tracks limit'),
|
title: Text('Currently supporting only Spotify, with 100 tracks limit'.i18n),
|
||||||
subtitle: Text('Due to API limitations'),
|
subtitle: Text('Due to API limitations'.i18n),
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Icons.warning,
|
Icons.warning,
|
||||||
color: Colors.deepOrangeAccent,
|
color: Colors.deepOrangeAccent,
|
||||||
|
@ -64,7 +65,7 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
||||||
Divider(),
|
Divider(),
|
||||||
Container(height: 16.0,),
|
Container(height: 16.0,),
|
||||||
Text(
|
Text(
|
||||||
'Enter your playlist link below',
|
'Enter your playlist link below'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20.0
|
fontSize: 20.0
|
||||||
|
@ -104,7 +105,7 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
||||||
),
|
),
|
||||||
if (_error)
|
if (_error)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Error loading URL!'),
|
title: Text('Error loading URL!'.i18n),
|
||||||
leading: Icon(Icons.error, color: Colors.red,),
|
leading: Icon(Icons.error, color: Colors.red,),
|
||||||
),
|
),
|
||||||
if (_data != null)
|
if (_data != null)
|
||||||
|
@ -133,13 +134,14 @@ class _ImporterWidgetState extends State<ImporterWidget> {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(widget.playlist.name),
|
title: Text(widget.playlist.name),
|
||||||
subtitle: Text(widget.playlist.description),
|
subtitle: Text(widget.playlist.description),
|
||||||
leading: Image.network(widget.playlist.image),
|
//Default image
|
||||||
|
leading: Image.network(widget.playlist.image??'http://cdn-images.deezer.com/images/cover//256x256-000000-80-0-0.jpg'),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
RaisedButton(
|
RaisedButton(
|
||||||
child: Text('Convert'),
|
child: Text('Convert'.i18n),
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
spotify.convertPlaylist(widget.playlist);
|
spotify.convertPlaylist(widget.playlist);
|
||||||
|
@ -149,7 +151,7 @@ class _ImporterWidgetState extends State<ImporterWidget> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
RaisedButton(
|
RaisedButton(
|
||||||
child: Text('Download only'),
|
child: Text('Download only'.i18n),
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
spotify.convertPlaylist(widget.playlist, downloadOnly: true);
|
spotify.convertPlaylist(widget.playlist, downloadOnly: true);
|
||||||
|
@ -197,7 +199,7 @@ class CurrentlyImportingScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Importing...'),),
|
appBar: AppBar(title: Text('Importing...'.i18n),),
|
||||||
body: StreamBuilder(
|
body: StreamBuilder(
|
||||||
stream: spotify.importingStream.stream,
|
stream: spotify.importingStream.stream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -254,7 +256,7 @@ class CurrentlyImportingScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (snapshot.data != null)
|
if (snapshot.data != null)
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Playlist menu'),
|
child: Text('Playlist menu'.i18n),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Playlist p = await deezerAPI.playlist(snapshot.data);
|
Playlist p = await deezerAPI.playlist(snapshot.data);
|
||||||
p.library = true;
|
p.library = true;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:freezer/ui/downloads_screen.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
import 'package:freezer/ui/importer_screen.dart';
|
import 'package:freezer/ui/importer_screen.dart';
|
||||||
import 'package:freezer/ui/tiles.dart';
|
import 'package:freezer/ui/tiles.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import 'menu.dart';
|
import 'menu.dart';
|
||||||
import 'settings_screen.dart';
|
import 'settings_screen.dart';
|
||||||
|
@ -24,7 +25,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text('Library'),
|
title: Text('Library'.i18n),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.file_download),
|
icon: Icon(Icons.file_download),
|
||||||
|
@ -58,9 +59,9 @@ class LibraryScreen extends StatelessWidget {
|
||||||
Container(height: 4.0,),
|
Container(height: 4.0,),
|
||||||
if (downloadManager.stopped && downloadManager.queue.length > 0)
|
if (downloadManager.stopped && downloadManager.queue.length > 0)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Downloads'),
|
title: Text('Downloads'.i18n),
|
||||||
leading: Icon(Icons.file_download),
|
leading: Icon(Icons.file_download),
|
||||||
subtitle: Text('Downloading is currently stopped, click here to resume.'),
|
subtitle: Text('Downloading is currently stopped, click here to resume.'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
downloadManager.start();
|
downloadManager.start();
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
@ -73,7 +74,7 @@ class LibraryScreen extends StatelessWidget {
|
||||||
Divider(),
|
Divider(),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Tracks'),
|
title: Text('Tracks'.i18n),
|
||||||
leading: Icon(Icons.audiotrack),
|
leading: Icon(Icons.audiotrack),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -82,7 +83,7 @@ class LibraryScreen extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Albums'),
|
title: Text('Albums'.i18n),
|
||||||
leading: Icon(Icons.album),
|
leading: Icon(Icons.album),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -91,7 +92,7 @@ class LibraryScreen extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Artists'),
|
title: Text('Artists'.i18n),
|
||||||
leading: Icon(Icons.recent_actors),
|
leading: Icon(Icons.recent_actors),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -100,7 +101,7 @@ class LibraryScreen extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Playlists'),
|
title: Text('Playlists'.i18n),
|
||||||
leading: Icon(Icons.playlist_play),
|
leading: Icon(Icons.playlist_play),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -110,9 +111,9 @@ class LibraryScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Import'),
|
title: Text('Import'.i18n),
|
||||||
leading: Icon(Icons.import_export),
|
leading: Icon(Icons.import_export),
|
||||||
subtitle: Text('Import playlists from Spotify'),
|
subtitle: Text('Import playlists from Spotify'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (spotify.doneImporting != null) {
|
if (spotify.doneImporting != null) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -128,7 +129,7 @@ class LibraryScreen extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
title: Text('Statistics'),
|
title: Text('Statistics'.i18n),
|
||||||
leading: Icon(Icons.insert_chart),
|
leading: Icon(Icons.insert_chart),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
|
@ -148,27 +149,27 @@ class LibraryScreen extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline tracks'),
|
title: Text('Offline tracks'.i18n),
|
||||||
leading: Icon(Icons.audiotrack),
|
leading: Icon(Icons.audiotrack),
|
||||||
trailing: Text(data[0]),
|
trailing: Text(data[0]),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline albums'),
|
title: Text('Offline albums'.i18n),
|
||||||
leading: Icon(Icons.album),
|
leading: Icon(Icons.album),
|
||||||
trailing: Text(data[1]),
|
trailing: Text(data[1]),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline playlists'),
|
title: Text('Offline playlists'.i18n),
|
||||||
leading: Icon(Icons.playlist_add),
|
leading: Icon(Icons.playlist_add),
|
||||||
trailing: Text(data[2]),
|
trailing: Text(data[2]),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline size'),
|
title: Text('Offline size'.i18n),
|
||||||
leading: Icon(Icons.sd_card),
|
leading: Icon(Icons.sd_card),
|
||||||
trailing: Text(data[3]),
|
trailing: Text(data[3]),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Free space'),
|
title: Text('Free space'.i18n),
|
||||||
leading: Icon(Icons.disc_full),
|
leading: Icon(Icons.disc_full),
|
||||||
trailing: Text(data[4]),
|
trailing: Text(data[4]),
|
||||||
),
|
),
|
||||||
|
@ -254,7 +255,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Tracks'),),
|
appBar: AppBar(title: Text('Tracks'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Card(
|
Card(
|
||||||
|
@ -263,7 +264,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(height: 8.0,),
|
Container(height: 8.0,),
|
||||||
Text(
|
Text(
|
||||||
'Loved tracks',
|
'Loved tracks'.i18n,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 24
|
fontSize: 24
|
||||||
|
@ -279,7 +280,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(Icons.file_download, size: 32.0,),
|
Icon(Icons.file_download, size: 32.0,),
|
||||||
Container(width: 4,),
|
Container(width: 4,),
|
||||||
Text('Download')
|
Text('Download'.i18n)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
@ -299,7 +300,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
playerHelper.playFromTrackList(tracks, t.id, QueueSource(
|
playerHelper.playFromTrackList(tracks, t.id, QueueSource(
|
||||||
id: deezerAPI.favoritesPlaylistId,
|
id: deezerAPI.favoritesPlaylistId,
|
||||||
text: 'Favorites',
|
text: 'Favorites'.i18n,
|
||||||
source: 'playlist'
|
source: 'playlist'
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
@ -328,7 +329,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
Text(
|
Text(
|
||||||
'All offline tracks',
|
'All offline tracks'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
@ -343,7 +344,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
playerHelper.playFromTrackList(allTracks, t.id, QueueSource(
|
playerHelper.playFromTrackList(allTracks, t.id, QueueSource(
|
||||||
id: 'allTracks',
|
id: 'allTracks',
|
||||||
text: 'All offline tracks',
|
text: 'All offline tracks'.i18n,
|
||||||
source: 'offline'
|
source: 'offline'
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
@ -386,7 +387,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Albums'),),
|
appBar: AppBar(title: Text('Albums'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(height: 8.0,),
|
Container(height: 8.0,),
|
||||||
|
@ -427,7 +428,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Divider(),
|
Divider(),
|
||||||
Text(
|
Text(
|
||||||
'Offline albums',
|
'Offline albums'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
@ -473,7 +474,7 @@ class _LibraryArtistsState extends State<LibraryArtists> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Artists'),),
|
appBar: AppBar(title: Text('Artists'.i18n),),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: deezerAPI.getArtists(),
|
future: deezerAPI.getArtists(),
|
||||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
@ -533,20 +534,30 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Playlist get favoritesPlaylist => Playlist(
|
||||||
|
id: deezerAPI.favoritesPlaylistId,
|
||||||
|
title: 'Favorites'.i18n,
|
||||||
|
user: User(name: deezerAPI.userName),
|
||||||
|
image: ImageDetails(thumbUrl: 'assets/favorites_thumb.jpg'),
|
||||||
|
tracks: [],
|
||||||
|
trackCount: 1,
|
||||||
|
duration: Duration(seconds: 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Playlists'),),
|
appBar: AppBar(title: Text('Playlists'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Create new playlist'),
|
title: Text('Create new playlist'.i18n),
|
||||||
leading: Icon(Icons.playlist_add),
|
leading: Icon(Icons.playlist_add),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (settings.offlineMode) {
|
if (settings.offlineMode) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Cannot create playlists in offline mode',
|
msg: 'Cannot create playlists in offline mode'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -565,6 +576,20 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
//Favorites playlist
|
||||||
|
PlaylistTile(
|
||||||
|
favoritesPlaylist,
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) => PlaylistDetails(favoritesPlaylist)
|
||||||
|
));
|
||||||
|
},
|
||||||
|
onHold: () {
|
||||||
|
MenuSheet m = MenuSheet(context);
|
||||||
|
m.defaultPlaylistMenu(favoritesPlaylist);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
if (_playlists != null)
|
if (_playlists != null)
|
||||||
...List.generate(_playlists.length, (int i) {
|
...List.generate(_playlists.length, (int i) {
|
||||||
Playlist p = _playlists[i];
|
Playlist p = _playlists[i];
|
||||||
|
@ -593,7 +618,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Divider(),
|
Divider(),
|
||||||
Text(
|
Text(
|
||||||
'Offline playlists',
|
'Offline playlists'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24.0,
|
fontSize: 24.0,
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import 'package:audio_service/audio_service.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
import 'package:freezer/main.dart';
|
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import '../api/definitions.dart';
|
import '../api/definitions.dart';
|
||||||
|
@ -62,11 +61,11 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Error'),
|
title: Text('Error'.i18n),
|
||||||
content: Text('Error logging in! Please check your token and internet connection and try again.'),
|
content: Text('Error logging in! Please check your token and internet connection and try again.'.i18n),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Dismiss'),
|
child: Text('Dismiss'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
@ -117,7 +116,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(height: 16.0,),
|
Container(height: 16.0,),
|
||||||
Text(
|
Text(
|
||||||
'Welcome to',
|
'Welcome to'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 16.0
|
||||||
|
@ -126,7 +125,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
FreezerTitle(),
|
FreezerTitle(),
|
||||||
Container(height: 8.0,),
|
Container(height: 8.0,),
|
||||||
Text(
|
Text(
|
||||||
"Please login using your Deezer account.",
|
"Please login using your Deezer account.".i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 16.0
|
||||||
|
@ -136,7 +135,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
||||||
child: OutlineButton(
|
child: OutlineButton(
|
||||||
child: Text('Login using browser'),
|
child: Text('Login using browser'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => LoginBrowser(_update))
|
MaterialPageRoute(builder: (context) => LoginBrowser(_update))
|
||||||
|
@ -147,18 +146,18 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
||||||
child: OutlineButton(
|
child: OutlineButton(
|
||||||
child: Text('Login using token'),
|
child: Text('Login using token'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Enter ARL'),
|
title: Text('Enter ARL'.i18n),
|
||||||
content: Container(
|
content: Container(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: (String s) => _arl = s,
|
onChanged: (String s) => _arl = s,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Token (ARL)'
|
labelText: 'Token (ARL)'.i18n
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -166,7 +165,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Save'),
|
child: Text('Save'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
settings.arl = _arl;
|
settings.arl = _arl.trim();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
_update();
|
_update();
|
||||||
},
|
},
|
||||||
|
@ -180,7 +179,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
),
|
),
|
||||||
Container(height: 16.0,),
|
Container(height: 16.0,),
|
||||||
Text(
|
Text(
|
||||||
"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.".i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 16.0
|
||||||
|
@ -199,7 +198,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
||||||
Divider(),
|
Divider(),
|
||||||
Container(height: 8.0,),
|
Container(height: 8.0,),
|
||||||
Text(
|
Text(
|
||||||
"By using this app, you don't agree with the Deezer ToS",
|
"By using this app, you don't agree with the Deezer ToS".i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0
|
fontSize: 16.0
|
||||||
|
|
|
@ -6,9 +6,9 @@ import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/download.dart';
|
import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/ui/details_screens.dart';
|
import 'package:freezer/ui/details_screens.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import '../api/definitions.dart';
|
import '../api/definitions.dart';
|
||||||
import '../api/player.dart';
|
|
||||||
import 'cached_image.dart';
|
import 'cached_image.dart';
|
||||||
|
|
||||||
class MenuSheet {
|
class MenuSheet {
|
||||||
|
@ -137,7 +137,7 @@ class MenuSheet {
|
||||||
//===================
|
//===================
|
||||||
|
|
||||||
Widget addToQueueNext(Track t) => ListTile(
|
Widget addToQueueNext(Track t) => ListTile(
|
||||||
title: Text('Play next'),
|
title: Text('Play next'.i18n),
|
||||||
leading: Icon(Icons.playlist_play),
|
leading: Icon(Icons.playlist_play),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
//-1 = next
|
//-1 = next
|
||||||
|
@ -146,7 +146,7 @@ class MenuSheet {
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget addToQueue(Track t) => ListTile(
|
Widget addToQueue(Track t) => ListTile(
|
||||||
title: Text('Add to queue'),
|
title: Text('Add to queue'.i18n),
|
||||||
leading: Icon(Icons.playlist_add),
|
leading: Icon(Icons.playlist_add),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await AudioService.addQueueItem(t.toMediaItem());
|
await AudioService.addQueueItem(t.toMediaItem());
|
||||||
|
@ -155,7 +155,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget addTrackFavorite(Track t) => ListTile(
|
Widget addTrackFavorite(Track t) => ListTile(
|
||||||
title: Text('Add track to favorites'),
|
title: Text('Add track to favorites'.i18n),
|
||||||
leading: Icon(Icons.favorite),
|
leading: Icon(Icons.favorite),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteTrack(t.id);
|
await deezerAPI.addFavoriteTrack(t.id);
|
||||||
|
@ -165,7 +165,7 @@ class MenuSheet {
|
||||||
downloadManager.addOfflinePlaylist(p);
|
downloadManager.addOfflinePlaylist(p);
|
||||||
}
|
}
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library!',
|
msg: 'Added to library'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastLength: Toast.LENGTH_SHORT
|
toastLength: Toast.LENGTH_SHORT
|
||||||
);
|
);
|
||||||
|
@ -174,7 +174,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget downloadTrack(Track t) => ListTile(
|
Widget downloadTrack(Track t) => ListTile(
|
||||||
title: Text('Download'),
|
title: Text('Download'.i18n),
|
||||||
leading: Icon(Icons.file_download),
|
leading: Icon(Icons.file_download),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await downloadManager.addOfflineTrack(t, private: false);
|
await downloadManager.addOfflineTrack(t, private: false);
|
||||||
|
@ -183,7 +183,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget addToPlaylist(Track t) => ListTile(
|
Widget addToPlaylist(Track t) => ListTile(
|
||||||
title: Text('Add to playlist'),
|
title: Text('Add to playlist'.i18n),
|
||||||
leading: Icon(Icons.playlist_add),
|
leading: Icon(Icons.playlist_add),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ class MenuSheet {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Select playlist'),
|
title: Text('Select playlist'.i18n),
|
||||||
content: FutureBuilder(
|
content: FutureBuilder(
|
||||||
future: deezerAPI.getPlaylists(),
|
future: deezerAPI.getPlaylists(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -224,7 +224,7 @@ class MenuSheet {
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Create new playlist'),
|
title: Text('Create new playlist'.i18n),
|
||||||
leading: Icon(Icons.add),
|
leading: Icon(Icons.add),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -250,7 +250,7 @@ class MenuSheet {
|
||||||
downloadManager.addOfflinePlaylist(p);
|
downloadManager.addOfflinePlaylist(p);
|
||||||
}
|
}
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: "Track added to ${p.title}",
|
msg: "Track added to".i18n + " ${p.title}",
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
|
@ -261,12 +261,12 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget removeFromPlaylist(Track t, Playlist p) => ListTile(
|
Widget removeFromPlaylist(Track t, Playlist p) => ListTile(
|
||||||
title: Text('Remove from playlist'),
|
title: Text('Remove from playlist'.i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeFromPlaylist(t.id, p.id);
|
await deezerAPI.removeFromPlaylist(t.id, p.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Track removed from ${p.title}',
|
msg: 'Track removed from'.i18n + ' ${p.title}',
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
|
@ -275,7 +275,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget removeFavoriteTrack(Track t, {onUpdate}) => ListTile(
|
Widget removeFavoriteTrack(Track t, {onUpdate}) => ListTile(
|
||||||
title: Text('Remove favorite'),
|
title: Text('Remove favorite'.i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeFavorite(t.id);
|
await deezerAPI.removeFavorite(t.id);
|
||||||
|
@ -285,7 +285,7 @@ class MenuSheet {
|
||||||
await downloadManager.addOfflinePlaylist(p);
|
await downloadManager.addOfflinePlaylist(p);
|
||||||
}
|
}
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Track removed from library',
|
msg: 'Track removed from library'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
|
@ -297,7 +297,7 @@ class MenuSheet {
|
||||||
//Redirect to artist page (ie from track)
|
//Redirect to artist page (ie from track)
|
||||||
Widget showArtist(Artist a) => ListTile(
|
Widget showArtist(Artist a) => ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Go to ${a.name}',
|
'Go to'.i18n + ' ${a.name}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -312,7 +312,7 @@ class MenuSheet {
|
||||||
|
|
||||||
Widget showAlbum(Album a) => ListTile(
|
Widget showAlbum(Album a) => ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'Go to ${a.title}',
|
'Go to'.i18n + ' ${a.title}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -345,7 +345,7 @@ class MenuSheet {
|
||||||
//===================
|
//===================
|
||||||
|
|
||||||
Widget downloadAlbum(Album a) => ListTile(
|
Widget downloadAlbum(Album a) => ListTile(
|
||||||
title: Text('Download'),
|
title: Text('Download'.i18n),
|
||||||
leading: Icon(Icons.file_download),
|
leading: Icon(Icons.file_download),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await downloadManager.addOfflineAlbum(a, private: false);
|
await downloadManager.addOfflineAlbum(a, private: false);
|
||||||
|
@ -354,7 +354,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget offlineAlbum(Album a) => ListTile(
|
Widget offlineAlbum(Album a) => ListTile(
|
||||||
title: Text('Make offline'),
|
title: Text('Make offline'.i18n),
|
||||||
leading: Icon(Icons.offline_pin),
|
leading: Icon(Icons.offline_pin),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteAlbum(a.id);
|
await deezerAPI.addFavoriteAlbum(a.id);
|
||||||
|
@ -364,12 +364,12 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget libraryAlbum(Album a) => ListTile(
|
Widget libraryAlbum(Album a) => ListTile(
|
||||||
title: Text('Add to library'),
|
title: Text('Add to library'.i18n),
|
||||||
leading: Icon(Icons.library_music),
|
leading: Icon(Icons.library_music),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteAlbum(a.id);
|
await deezerAPI.addFavoriteAlbum(a.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library',
|
msg: 'Added to library'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
_close();
|
_close();
|
||||||
|
@ -378,13 +378,13 @@ class MenuSheet {
|
||||||
|
|
||||||
//Remove album from favorites
|
//Remove album from favorites
|
||||||
Widget removeAlbum(Album a, {Function onRemove}) => ListTile(
|
Widget removeAlbum(Album a, {Function onRemove}) => ListTile(
|
||||||
title: Text('Remove album'),
|
title: Text('Remove album'.i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeAlbum(a.id);
|
await deezerAPI.removeAlbum(a.id);
|
||||||
await downloadManager.removeOfflineAlbum(a.id);
|
await downloadManager.removeOfflineAlbum(a.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Album removed',
|
msg: 'Album removed'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
|
@ -409,12 +409,12 @@ class MenuSheet {
|
||||||
//===================
|
//===================
|
||||||
|
|
||||||
Widget removeArtist(Artist a, {Function onRemove}) => ListTile(
|
Widget removeArtist(Artist a, {Function onRemove}) => ListTile(
|
||||||
title: Text('Remove from favorites'),
|
title: Text('Remove from favorites'.i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.removeArtist(a.id);
|
await deezerAPI.removeArtist(a.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Artist removed from library',
|
msg: 'Artist removed from library'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
|
@ -424,12 +424,12 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget favoriteArtist(Artist a) => ListTile(
|
Widget favoriteArtist(Artist a) => ListTile(
|
||||||
title: Text('Add to favorites'),
|
title: Text('Add to favorites'.i18n),
|
||||||
leading: Icon(Icons.favorite),
|
leading: Icon(Icons.favorite),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addFavoriteArtist(a.id);
|
await deezerAPI.addFavoriteArtist(a.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added to library',
|
msg: 'Added to library'.i18n,
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
toastLength: Toast.LENGTH_SHORT,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
|
@ -455,7 +455,7 @@ class MenuSheet {
|
||||||
//===================
|
//===================
|
||||||
|
|
||||||
Widget removePlaylistLibrary(Playlist p, {Function onRemove}) => ListTile(
|
Widget removePlaylistLibrary(Playlist p, {Function onRemove}) => ListTile(
|
||||||
title: Text('Remove from library'),
|
title: Text('Remove from library'.i18n),
|
||||||
leading: Icon(Icons.delete),
|
leading: Icon(Icons.delete),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (p.user.id.trim() == deezerAPI.userId) {
|
if (p.user.id.trim() == deezerAPI.userId) {
|
||||||
|
@ -472,12 +472,12 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget addPlaylistLibrary(Playlist p) => ListTile(
|
Widget addPlaylistLibrary(Playlist p) => ListTile(
|
||||||
title: Text('Add playlist to library'),
|
title: Text('Add playlist to library'.i18n),
|
||||||
leading: Icon(Icons.favorite),
|
leading: Icon(Icons.favorite),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await deezerAPI.addPlaylist(p.id);
|
await deezerAPI.addPlaylist(p.id);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Added playlist to library',
|
msg: 'Added playlist to library'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
_close();
|
_close();
|
||||||
|
@ -485,7 +485,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget addPlaylistOffline(Playlist p) => ListTile(
|
Widget addPlaylistOffline(Playlist p) => ListTile(
|
||||||
title: Text('Make playlist offline'),
|
title: Text('Make playlist offline'.i18n),
|
||||||
leading: Icon(Icons.offline_pin),
|
leading: Icon(Icons.offline_pin),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
//Add to library
|
//Add to library
|
||||||
|
@ -496,7 +496,7 @@ class MenuSheet {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget downloadPlaylist(Playlist p) => ListTile(
|
Widget downloadPlaylist(Playlist p) => ListTile(
|
||||||
title: Text('Download playlist'),
|
title: Text('Download playlist'.i18n),
|
||||||
leading: Icon(Icons.file_download),
|
leading: Icon(Icons.file_download),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
downloadManager.addOfflinePlaylist(p, private: false);
|
downloadManager.addOfflinePlaylist(p, private: false);
|
||||||
|
@ -542,20 +542,20 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Create playlist'),
|
title: Text('Create playlist'.i18n),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextField(
|
TextField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Title'
|
labelText: 'Title'.i18n
|
||||||
),
|
),
|
||||||
onChanged: (String s) => _title = s,
|
onChanged: (String s) => _title = s,
|
||||||
),
|
),
|
||||||
TextField(
|
TextField(
|
||||||
onChanged: (String s) => _description = s,
|
onChanged: (String s) => _description = s,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Description'
|
labelText: 'Description'.i18n
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(height: 4.0,),
|
Container(height: 4.0,),
|
||||||
|
@ -567,11 +567,11 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem<int>(
|
DropdownMenuItem<int>(
|
||||||
value: 1,
|
value: 1,
|
||||||
child: Text('Private'),
|
child: Text('Private'.i18n),
|
||||||
),
|
),
|
||||||
DropdownMenuItem<int>(
|
DropdownMenuItem<int>(
|
||||||
value: 2,
|
value: 2,
|
||||||
child: Text('Collaborative'),
|
child: Text('Collaborative'.i18n),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -579,11 +579,11 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Cancel'),
|
child: Text('Cancel'.i18n),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Create'),
|
child: Text('Create'.i18n),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
List<String> tracks = [];
|
List<String> tracks = [];
|
||||||
if (widget.tracks != null) {
|
if (widget.tracks != null) {
|
||||||
|
@ -596,7 +596,7 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
||||||
trackIds: tracks
|
trackIds: tracks
|
||||||
);
|
);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Playlist created!',
|
msg: 'Playlist created!'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
|
@ -137,7 +137,7 @@ class PlayPauseButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Paused
|
//Paused
|
||||||
if ((!AudioService.playbackState.playing &&
|
if ((!(AudioService.playbackState?.playing??false) &&
|
||||||
AudioService.playbackState.processingState == AudioProcessingState.ready) ||
|
AudioService.playbackState.processingState == AudioProcessingState.ready) ||
|
||||||
//None state (stopped)
|
//None state (stopped)
|
||||||
AudioService.playbackState.processingState == AudioProcessingState.none) {
|
AudioService.playbackState.processingState == AudioProcessingState.none) {
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import 'dart:ui';
|
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter_screenutil/screenutil.dart';
|
import 'package:flutter_screenutil/screenutil.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
import 'package:freezer/settings.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:freezer/ui/menu.dart';
|
import 'package:freezer/ui/menu.dart';
|
||||||
import 'package:freezer/ui/settings_screen.dart';
|
import 'package:freezer/ui/settings_screen.dart';
|
||||||
import 'package:freezer/ui/tiles.dart';
|
import 'package:freezer/ui/tiles.dart';
|
||||||
|
@ -18,6 +15,9 @@ import 'cached_image.dart';
|
||||||
import '../api/definitions.dart';
|
import '../api/definitions.dart';
|
||||||
import 'player_bar.dart';
|
import 'player_bar.dart';
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
class PlayerScreen extends StatefulWidget {
|
class PlayerScreen extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_PlayerScreenState createState() => _PlayerScreenState();
|
_PlayerScreenState createState() => _PlayerScreenState();
|
||||||
|
@ -527,9 +527,9 @@ class PlayerScreenTopRow extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
width: this.textWidth??ScreenUtil().setWidth(600),
|
width: this.textWidth??ScreenUtil().setWidth(550),
|
||||||
child: Text(
|
child: Text(
|
||||||
(short??false)?playerHelper.queueSource.text:'Playing from: ' + playerHelper.queueSource.text,
|
(short??false)?playerHelper.queueSource.text:'Playing from:'.i18n + ' ' + playerHelper.queueSource.text,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
|
@ -728,7 +728,7 @@ class _QueueScreenState extends State<QueueScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Queue'),
|
title: Text('Queue'.i18n),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
import 'package:freezer/ui/details_screens.dart';
|
import 'package:freezer/ui/details_screens.dart';
|
||||||
import 'package:freezer/ui/menu.dart';
|
import 'package:freezer/ui/menu.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import 'tiles.dart';
|
import 'tiles.dart';
|
||||||
import '../api/deezer.dart';
|
import '../api/deezer.dart';
|
||||||
|
@ -20,6 +21,7 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
String _query;
|
String _query;
|
||||||
bool _offline = false;
|
bool _offline = false;
|
||||||
TextEditingController _controller = new TextEditingController();
|
TextEditingController _controller = new TextEditingController();
|
||||||
|
List _suggestions = [];
|
||||||
|
|
||||||
void _submit(BuildContext context, {String query}) {
|
void _submit(BuildContext context, {String query}) {
|
||||||
if (query != null) _query = query;
|
if (query != null) _query = query;
|
||||||
|
@ -41,10 +43,21 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Load search suggestions
|
||||||
|
Future<List<String>> _loadSuggestions() async {
|
||||||
|
if (_query == null || _query.length < 2) return null;
|
||||||
|
String q = _query;
|
||||||
|
await Future.delayed(Duration(milliseconds: 300));
|
||||||
|
if (q != _query) return null;
|
||||||
|
//Load
|
||||||
|
List sugg = await deezerAPI.searchSuggestions(_query);
|
||||||
|
setState(() => _suggestions = sugg);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Search'),),
|
appBar: AppBar(title: Text('Search'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(height: 16.0),
|
Container(height: 16.0),
|
||||||
|
@ -57,9 +70,12 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
alignment: Alignment(1.0, 1.0),
|
alignment: Alignment(1.0, 1.0),
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
onChanged: (String s) => _query = s,
|
onChanged: (String s) {
|
||||||
|
setState(() => _query = s);
|
||||||
|
_loadSuggestions();
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Search'
|
labelText: 'Search'.i18n
|
||||||
),
|
),
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
onSubmitted: (String s) => _submit(context, query: s),
|
onSubmitted: (String s) => _submit(context, query: s),
|
||||||
|
@ -73,7 +89,6 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0, 8, 0, 0),
|
padding: EdgeInsets.fromLTRB(0, 8, 0, 0),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
@ -85,14 +100,23 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline search'),
|
title: Text('Offline search'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: _offline,
|
value: _offline,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
setState(() => _offline = !_offline);
|
setState(() => _offline = !_offline);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
Divider(),
|
||||||
|
...List.generate((_suggestions??[]).length, (i) => ListTile(
|
||||||
|
title: Text(_suggestions[i]),
|
||||||
|
leading: Icon(Icons.search),
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _query = _suggestions[i]);
|
||||||
|
_submit(context);
|
||||||
|
},
|
||||||
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -119,7 +143,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Search Results'),
|
title: Text('Search Results'.i18n),
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: _search(),
|
future: _search(),
|
||||||
|
@ -139,7 +163,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
Icons.warning,
|
Icons.warning,
|
||||||
size: 64,
|
size: 64,
|
||||||
),
|
),
|
||||||
Text('No results!')
|
Text('No results!'.i18n)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -149,7 +173,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
if (results.tracks != null && results.tracks.length != 0) {
|
if (results.tracks != null && results.tracks.length != 0) {
|
||||||
tracks = [
|
tracks = [
|
||||||
Text(
|
Text(
|
||||||
'Tracks',
|
'Tracks'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0,
|
fontSize: 26.0,
|
||||||
|
@ -163,7 +187,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
t,
|
t,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
playerHelper.playFromTrackList(results.tracks, t.id, QueueSource(
|
playerHelper.playFromTrackList(results.tracks, t.id, QueueSource(
|
||||||
text: 'Search',
|
text: 'Search'.i18n,
|
||||||
id: query,
|
id: query,
|
||||||
source: 'search'
|
source: 'search'
|
||||||
));
|
));
|
||||||
|
@ -175,13 +199,13 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Show all tracks'),
|
title: Text('Show all tracks'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => TrackListScreen(results.tracks, QueueSource(
|
MaterialPageRoute(builder: (context) => TrackListScreen(results.tracks, QueueSource(
|
||||||
id: query,
|
id: query,
|
||||||
source: 'search',
|
source: 'search',
|
||||||
text: 'Search'
|
text: 'Search'.i18n
|
||||||
)))
|
)))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -194,7 +218,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
if (results.albums != null && results.albums.length != 0) {
|
if (results.albums != null && results.albums.length != 0) {
|
||||||
albums = [
|
albums = [
|
||||||
Text(
|
Text(
|
||||||
'Albums',
|
'Albums'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0,
|
fontSize: 26.0,
|
||||||
|
@ -218,7 +242,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Show all albums'),
|
title: Text('Show all albums'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => AlbumListScreen(results.albums))
|
MaterialPageRoute(builder: (context) => AlbumListScreen(results.albums))
|
||||||
|
@ -233,7 +257,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
if (results.artists != null && results.artists.length != 0) {
|
if (results.artists != null && results.artists.length != 0) {
|
||||||
artists = [
|
artists = [
|
||||||
Text(
|
Text(
|
||||||
'Artists',
|
'Artists'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0,
|
fontSize: 26.0,
|
||||||
|
@ -269,7 +293,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
if (results.playlists != null && results.playlists.length != 0) {
|
if (results.playlists != null && results.playlists.length != 0) {
|
||||||
playlists = [
|
playlists = [
|
||||||
Text(
|
Text(
|
||||||
'Playlists',
|
'Playlists'.i18n,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.0,
|
fontSize: 26.0,
|
||||||
|
@ -293,7 +317,7 @@ class SearchResultsScreen extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Show all playlists'),
|
title: Text('Show all playlists'.i18n),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => SearchResultPlaylists(results.playlists))
|
MaterialPageRoute(builder: (context) => SearchResultPlaylists(results.playlists))
|
||||||
|
@ -332,7 +356,7 @@ class TrackListScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Tracks'),),
|
appBar: AppBar(title: Text('Tracks'.i18n),),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: tracks.length,
|
itemCount: tracks.length,
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
|
@ -362,7 +386,7 @@ class AlbumListScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Albums'),),
|
appBar: AppBar(title: Text('Albums'.i18n),),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: albums.length,
|
itemCount: albums.length,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
|
@ -393,7 +417,7 @@ class SearchResultPlaylists extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Playlists'),),
|
appBar: AppBar(title: Text('Playlists'.i18n),),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: playlists.length,
|
itemCount: playlists.length,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
|
|
|
@ -9,11 +9,13 @@ import 'package:flutter_material_color_picker/flutter_material_color_picker.dart
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
import 'package:freezer/ui/error.dart';
|
import 'package:freezer/ui/error.dart';
|
||||||
|
import 'package:i18n_extension/i18n_widget.dart';
|
||||||
import 'package:language_pickers/language_pickers.dart';
|
import 'package:language_pickers/language_pickers.dart';
|
||||||
import 'package:language_pickers/languages.dart';
|
import 'package:language_pickers/languages.dart';
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info/package_info.dart';
|
||||||
import 'package:path_provider_ex/path_provider_ex.dart';
|
import 'package:path_provider_ex/path_provider_ex.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:freezer/translations.i18n.dart';
|
||||||
import 'package:clipboard/clipboard.dart';
|
import 'package:clipboard/clipboard.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
@ -44,18 +46,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Settings'),),
|
appBar: AppBar(title: Text('Settings'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('General'),
|
title: Text('General'.i18n),
|
||||||
leading: Icon(Icons.settings),
|
leading: Icon(Icons.settings),
|
||||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => GeneralSettings()
|
builder: (context) => GeneralSettings()
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Appearance'),
|
title: Text('Appearance'.i18n),
|
||||||
leading: Icon(Icons.color_lens),
|
leading: Icon(Icons.color_lens),
|
||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -63,7 +65,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Quality'),
|
title: Text('Quality'.i18n),
|
||||||
leading: Icon(Icons.high_quality),
|
leading: Icon(Icons.high_quality),
|
||||||
onTap: () => Navigator.push(
|
onTap: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -71,12 +73,54 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Deezer'),
|
title: Text('Deezer'.i18n),
|
||||||
leading: Icon(Icons.equalizer),
|
leading: Icon(Icons.equalizer),
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (context) => DeezerSettings()
|
builder: (context) => DeezerSettings()
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
//Language select
|
||||||
|
ListTile(
|
||||||
|
title: Text('Language'.i18n),
|
||||||
|
leading: Icon(Icons.language),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => LanguagePickerDialog(
|
||||||
|
titlePadding: EdgeInsets.all(8.0),
|
||||||
|
title: Text('Select language'.i18n),
|
||||||
|
isSearchable: false,
|
||||||
|
languagesList: supportedLocales.map<Map<String, String>>((l) {
|
||||||
|
Map _lang = defaultLanguagesList.firstWhere((lang) => lang['isoCode'] == l.languageCode);
|
||||||
|
_lang['name'] = _lang['name'] + ' (${l.toString()})';
|
||||||
|
return _lang;
|
||||||
|
}).toList(),
|
||||||
|
onValuePicked: (Language l) async {
|
||||||
|
setState(() {
|
||||||
|
Locale locale = supportedLocales.firstWhere((_l) => _l.languageCode == l.isoCode);
|
||||||
|
settings.language = locale.toString();
|
||||||
|
});
|
||||||
|
await settings.save();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('Language'.i18n),
|
||||||
|
content: Text('Language changed, please restart Freezer to apply!'.i18n),
|
||||||
|
actions: [
|
||||||
|
FlatButton(
|
||||||
|
child: Text('OK'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
Text(
|
Text(
|
||||||
_about,
|
_about,
|
||||||
|
@ -97,22 +141,22 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Appearance'),),
|
appBar: AppBar(title: Text('Appearance'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Theme'),
|
title: Text('Theme'.i18n),
|
||||||
subtitle: Text('Currently: ${settings.theme.toString().split('.').last}'),
|
subtitle: Text('Currently'.i18n + ': ${settings.theme.toString().split('.').last}'),
|
||||||
leading: Icon(Icons.color_lens),
|
leading: Icon(Icons.color_lens),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
title: Text('Select theme'),
|
title: Text('Select theme'.i18n),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SimpleDialogOption(
|
SimpleDialogOption(
|
||||||
child: Text('Light (default)'),
|
child: Text('Light (default)'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => settings.theme = Themes.Light);
|
setState(() => settings.theme = Themes.Light);
|
||||||
settings.save();
|
settings.save();
|
||||||
|
@ -121,7 +165,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SimpleDialogOption(
|
SimpleDialogOption(
|
||||||
child: Text('Dark'),
|
child: Text('Dark'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => settings.theme = Themes.Dark);
|
setState(() => settings.theme = Themes.Dark);
|
||||||
settings.save();
|
settings.save();
|
||||||
|
@ -130,7 +174,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SimpleDialogOption(
|
SimpleDialogOption(
|
||||||
child: Text('Black (AMOLED)'),
|
child: Text('Black (AMOLED)'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => settings.theme = Themes.Black);
|
setState(() => settings.theme = Themes.Black);
|
||||||
settings.save();
|
settings.save();
|
||||||
|
@ -139,7 +183,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SimpleDialogOption(
|
SimpleDialogOption(
|
||||||
child: Text('Deezer (Dark)'),
|
child: Text('Deezer (Dark)'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => settings.theme = Themes.Deezer);
|
setState(() => settings.theme = Themes.Deezer);
|
||||||
settings.save();
|
settings.save();
|
||||||
|
@ -154,10 +198,10 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Primary color'),
|
title: Text('Primary color'.i18n),
|
||||||
leading: Icon(Icons.format_paint),
|
leading: Icon(Icons.format_paint),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'Selected color',
|
'Selected color'.i18n,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: settings.primaryColor
|
color: settings.primaryColor
|
||||||
),
|
),
|
||||||
|
@ -167,7 +211,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Primary color'),
|
title: Text('Primary color'.i18n),
|
||||||
content: Container(
|
content: Container(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: MaterialColorPicker(
|
child: MaterialColorPicker(
|
||||||
|
@ -189,8 +233,8 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Use album art primary color'),
|
title: Text('Use album art primary color'.i18n),
|
||||||
subtitle: Text('Warning: might be buggy'),
|
subtitle: Text('Warning: might be buggy'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: settings.useArtColor,
|
value: settings.useArtColor,
|
||||||
onChanged: (v) => setState(() => settings.updateUseArtColor(v)),
|
onChanged: (v) => setState(() => settings.updateUseArtColor(v)),
|
||||||
|
@ -212,29 +256,29 @@ class _QualitySettingsState extends State<QualitySettings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Quality'),),
|
appBar: AppBar(title: Text('Quality'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Mobile streaming'),
|
title: Text('Mobile streaming'.i18n),
|
||||||
leading: Icon(Icons.network_cell),
|
leading: Icon(Icons.network_cell),
|
||||||
),
|
),
|
||||||
QualityPicker('mobile'),
|
QualityPicker('mobile'),
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Wifi streaming'),
|
title: Text('Wifi streaming'.i18n),
|
||||||
leading: Icon(Icons.network_wifi),
|
leading: Icon(Icons.network_wifi),
|
||||||
),
|
),
|
||||||
QualityPicker('wifi'),
|
QualityPicker('wifi'),
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline'),
|
title: Text('Offline'.i18n),
|
||||||
leading: Icon(Icons.offline_pin),
|
leading: Icon(Icons.offline_pin),
|
||||||
),
|
),
|
||||||
QualityPicker('offline'),
|
QualityPicker('offline'),
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('External downloads'),
|
title: Text('External downloads'.i18n),
|
||||||
leading: Icon(Icons.file_download),
|
leading: Icon(Icons.file_download),
|
||||||
),
|
),
|
||||||
QualityPicker('download'),
|
QualityPicker('download'),
|
||||||
|
@ -349,12 +393,12 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('Deezer'),),
|
appBar: AppBar(title: Text('Deezer'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Content language'),
|
title: Text('Content language'.i18n),
|
||||||
subtitle: Text('Not app language, used in headers. Now: ${settings.deezerLanguage}'),
|
subtitle: Text('Not app language, used in headers. Now'.i18n + ': ${settings.deezerLanguage}'),
|
||||||
leading: Icon(Icons.language),
|
leading: Icon(Icons.language),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -362,7 +406,7 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
||||||
builder: (context) => LanguagePickerDialog(
|
builder: (context) => LanguagePickerDialog(
|
||||||
titlePadding: EdgeInsets.all(8.0),
|
titlePadding: EdgeInsets.all(8.0),
|
||||||
isSearchable: true,
|
isSearchable: true,
|
||||||
title: Text('Select language'),
|
title: Text('Select language'.i18n),
|
||||||
onValuePicked: (Language language) {
|
onValuePicked: (Language language) {
|
||||||
setState(() => settings.deezerLanguage = language.isoCode);
|
setState(() => settings.deezerLanguage = language.isoCode);
|
||||||
settings.save();
|
settings.save();
|
||||||
|
@ -372,8 +416,8 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Content country'),
|
title: Text('Content country'.i18n),
|
||||||
subtitle: Text('Country used in headers. Now: ${settings.deezerCountry}'),
|
subtitle: Text('Country used in headers. Now'.i18n + ': ${settings.deezerCountry}'),
|
||||||
leading: Icon(Icons.vpn_lock),
|
leading: Icon(Icons.vpn_lock),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -390,8 +434,8 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Log tracks'),
|
title: Text('Log tracks'.i18n),
|
||||||
subtitle: Text('Send track listen logs to Deezer, enable it for features like Flow to work properly'),
|
subtitle: Text('Send track listen logs to Deezer, enable it for features like Flow to work properly'.i18n),
|
||||||
leading: Checkbox(
|
leading: Checkbox(
|
||||||
value: settings.logListen,
|
value: settings.logListen,
|
||||||
onChanged: (bool v) {
|
onChanged: (bool v) {
|
||||||
|
@ -415,12 +459,12 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text('General'),),
|
appBar: AppBar(title: Text('General'.i18n),),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Offline mode'),
|
title: Text('Offline mode'.i18n),
|
||||||
subtitle: Text('Will be overwritten on start.'),
|
subtitle: Text('Will be overwritten on start.'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: settings.offlineMode,
|
value: settings.offlineMode,
|
||||||
onChanged: (bool v) {
|
onChanged: (bool v) {
|
||||||
|
@ -436,7 +480,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
setState(() => settings.offlineMode = false);
|
setState(() => settings.offlineMode = false);
|
||||||
} else {
|
} else {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Error logging in, check your internet connections.',
|
msg: 'Error logging in, check your internet connections.'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastLength: Toast.LENGTH_SHORT
|
toastLength: Toast.LENGTH_SHORT
|
||||||
);
|
);
|
||||||
|
@ -444,7 +488,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
});
|
});
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Logging in...'),
|
title: Text('Logging in...'.i18n),
|
||||||
content: Row(
|
content: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -459,7 +503,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Download path'),
|
title: Text('Download path'.i18n),
|
||||||
leading: Icon(Icons.folder),
|
leading: Icon(Icons.folder),
|
||||||
subtitle: Text(settings.downloadPath),
|
subtitle: Text(settings.downloadPath),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
@ -474,8 +518,8 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Downloads naming'),
|
title: Text('Downloads naming'.i18n),
|
||||||
subtitle: Text('Currently: ${settings.downloadFilename}'),
|
subtitle: Text('Currently'.i18n + ': ${settings.downloadFilename}'),
|
||||||
leading: Icon(Icons.text_format),
|
leading: Icon(Icons.text_format),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
@ -485,19 +529,21 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
TextEditingController _controller = TextEditingController();
|
TextEditingController _controller = TextEditingController();
|
||||||
String filename = settings.downloadFilename;
|
String filename = settings.downloadFilename;
|
||||||
_controller.value = _controller.value.copyWith(text: filename);
|
_controller.value = _controller.value.copyWith(text: filename);
|
||||||
|
String _new = _controller.value.text;
|
||||||
|
|
||||||
//Dialog with filename format
|
//Dialog with filename format
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Downloaded tracks filename'),
|
title: Text('Downloaded tracks filename'.i18n),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
|
onChanged: (String s) => _new = s,
|
||||||
),
|
),
|
||||||
Container(height: 8.0),
|
Container(height: 8.0),
|
||||||
Text(
|
Text(
|
||||||
'Valid variables are: %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%',
|
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
),
|
),
|
||||||
|
@ -506,29 +552,30 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Cancel'),
|
child: Text('Cancel'.i18n),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Reset'),
|
child: Text('Reset'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_controller.value = _controller.value.copyWith(
|
_controller.value = _controller.value.copyWith(
|
||||||
text: '%artists% - %title%'
|
text: '%artists% - %title%'
|
||||||
);
|
);
|
||||||
|
_new = '%artists% - %title%';
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Clear'),
|
child: Text('Clear'.i18n),
|
||||||
onPressed: () => _controller.clear(),
|
onPressed: () => _controller.clear(),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Save'),
|
child: Text('Save'.i18n),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
settings.downloadFilename = _controller.text;
|
settings.downloadFilename = _new;
|
||||||
settings.save();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
});
|
});
|
||||||
|
await settings.save();
|
||||||
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -538,7 +585,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Create folders for artist'),
|
title: Text('Create folders for artist'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: settings.artistFolder,
|
value: settings.artistFolder,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
|
@ -548,7 +595,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Create folders for albums'),
|
title: Text('Create folders for albums'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: settings.albumFolder,
|
value: settings.albumFolder,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
|
@ -558,7 +605,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Separate albums by discs'),
|
title: Text('Separate albums by discs'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: settings.albumDiscFolder,
|
value: settings.albumDiscFolder,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
|
@ -568,7 +615,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Overwrite already downloaded files'),
|
title: Text('Overwrite already downloaded files'.i18n),
|
||||||
leading: Switch(
|
leading: Switch(
|
||||||
value: settings.overwriteDownload,
|
value: settings.overwriteDownload,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
|
@ -578,40 +625,40 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Copy ARL'),
|
title: Text('Copy ARL'.i18n),
|
||||||
subtitle: Text('Copy userToken/ARL Cookie for use in other apps.'),
|
subtitle: Text('Copy userToken/ARL Cookie for use in other apps.'.i18n),
|
||||||
leading: Icon(Icons.lock),
|
leading: Icon(Icons.lock),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await FlutterClipboard.copy(settings.arl);
|
await FlutterClipboard.copy(settings.arl);
|
||||||
await Fluttertoast.showToast(
|
await Fluttertoast.showToast(
|
||||||
msg: 'Copied',
|
msg: 'Copied'.i18n,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Log out', style: TextStyle(color: Colors.red),),
|
title: Text('Log out'.i18n, style: TextStyle(color: Colors.red),),
|
||||||
leading: Icon(Icons.exit_to_app),
|
leading: Icon(Icons.exit_to_app),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Log out'),
|
title: Text('Log out'.i18n),
|
||||||
content: Text('Due to plugin incompatibility, login using browser is unavailable without restart.'),
|
content: Text('Due to plugin incompatibility, login using browser is unavailable without restart.'.i18n),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Cancel'),
|
child: Text('Cancel'.i18n),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('(ARL ONLY) Continue'),
|
child: Text('(ARL ONLY) Continue'.i18n),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
logOut();
|
logOut();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text('Log out & Exit'),
|
child: Text('Log out & Exit'.i18n),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {AudioService.stop();} catch (e) {}
|
try {AudioService.stop();} catch (e) {}
|
||||||
await logOut();
|
await logOut();
|
||||||
|
@ -656,7 +703,7 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Pick-a-Path'),
|
title: Text('Pick-a-Path'.i18n),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.sd_card),
|
icon: Icon(Icons.sd_card),
|
||||||
|
@ -667,7 +714,7 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Select storage'),
|
title: Text('Select storage'.i18n),
|
||||||
content: FutureBuilder(
|
content: FutureBuilder(
|
||||||
future: PathProviderEx.getStorageInfo(),
|
future: PathProviderEx.getStorageInfo(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -736,13 +783,13 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
|
||||||
title: Text(_path),
|
title: Text(_path),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Go up'),
|
title: Text('Go up'.i18n),
|
||||||
leading: Icon(Icons.arrow_upward),
|
leading: Icon(Icons.arrow_upward),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (_root == _path) {
|
if (_root == _path) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Permission denied',
|
msg: 'Permission denied'.i18n,
|
||||||
gravity: ToastGravity.BOTTOM
|
gravity: ToastGravity.BOTTOM
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,878 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.39.14"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
async:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
audio_service:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: audio_service
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.15.0"
|
||||||
|
audio_session:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: audio_session
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.7"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.11"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.1"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.2"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "7.1.0"
|
||||||
|
cached_network_image:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cached_network_image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2+1"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4"
|
||||||
|
clipboard:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: clipboard
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2+8"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.1"
|
||||||
|
collection:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.14.13"
|
||||||
|
connectivity:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: connectivity
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.9+2"
|
||||||
|
connectivity_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_for_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.1+2"
|
||||||
|
connectivity_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0+4"
|
||||||
|
connectivity_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
cookie_jar:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cookie_jar
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
country_pickers:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: country_pickers
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
crypto:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.16.2"
|
||||||
|
custom_navigator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: custom_navigator
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.6"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
dio_cookie_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio_cookie_manager
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
disk_space:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: disk_space
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.3"
|
||||||
|
ext_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: ext_storage
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
fading_edge_scrollview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fading_edge_scrollview
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.1"
|
||||||
|
filesize:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: filesize
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.11"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_blurhash:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_blurhash
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
|
flutter_cache_manager:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_cache_manager
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
flutter_inappwebview:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_inappwebview
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0+4"
|
||||||
|
flutter_isolate:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_isolate
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0+14"
|
||||||
|
flutter_local_notifications:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.4+4"
|
||||||
|
flutter_local_notifications_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_material_color_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_material_color_picker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
flutter_screenutil:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_screenutil
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
fluttertoast:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: fluttertoast
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "7.1.1"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
hex:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hex
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
|
html:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.0+3"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
i18n_extension:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: i18n_extension
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.4"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.16.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.2"
|
||||||
|
json_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
json_serializable:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.1"
|
||||||
|
just_audio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: just_audio
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.3.1"
|
||||||
|
language_pickers:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: language_pickers
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0+1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.4"
|
||||||
|
marquee:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: marquee
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.1"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.8"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.8"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.7"
|
||||||
|
move_to_background:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: move_to_background
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
node_interop:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_interop
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
node_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
octo_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: octo_image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.3"
|
||||||
|
package_info:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: package_info
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.3"
|
||||||
|
palette_generator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: palette_generator
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.3"
|
||||||
|
path:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.7.0"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.14"
|
||||||
|
path_provider_ex:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider_ex
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.1+2"
|
||||||
|
path_provider_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.4+3"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
pedantic:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pedantic
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.0"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.1+1"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
pointycastle:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: pointycastle
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.13"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.4"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.5"
|
||||||
|
quiver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: quiver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
random_string:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: random_string
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.24.1"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.9"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.3"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.6"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.7.0"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
|
sqflite:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1+1"
|
||||||
|
sqflite_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_common
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2+1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.5"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0+2"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.17"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1+2"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.8"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.7+15"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=2.9.0 <3.0.0"
|
||||||
|
flutter: ">=1.20.0 <2.0.0"
|
13
pubspec.yaml
|
@ -15,15 +15,18 @@ 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.2.0
|
version: 0.4.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.6.0 <3.0.0"
|
sdk: ">=2.8.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
dio: ^3.0.9
|
dio: ^3.0.9
|
||||||
dio_cookie_manager: ^1.0.0
|
dio_cookie_manager: ^1.0.0
|
||||||
cookie_jar: ^1.0.1
|
cookie_jar: ^1.0.1
|
||||||
|
@ -60,8 +63,11 @@ dependencies:
|
||||||
flutter_cache_manager: ^1.4.1
|
flutter_cache_manager: ^1.4.1
|
||||||
cached_network_image: ^2.2.0+1
|
cached_network_image: ^2.2.0+1
|
||||||
clipboard: ^0.1.2+8
|
clipboard: ^0.1.2+8
|
||||||
|
i18n_extension: ^1.4.4
|
||||||
|
|
||||||
audio_service: ^0.13.0
|
audio_session: ^0.0.7
|
||||||
|
audio_service:
|
||||||
|
path: ./audio_service
|
||||||
just_audio:
|
just_audio:
|
||||||
path: ./just_audio
|
path: ./just_audio
|
||||||
|
|
||||||
|
@ -94,6 +100,7 @@ flutter:
|
||||||
- assets/cover.jpg
|
- assets/cover.jpg
|
||||||
- assets/cover_thumb.jpg
|
- assets/cover_thumb.jpg
|
||||||
- assets/icon.png
|
- assets/icon.png
|
||||||
|
- assets/favorites_thumb.jpg
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
# - family: Montserrat
|
# - family: Montserrat
|
||||||
|
|