0.4.0 - translations, download fallback, android auto, radio, infinite flow, bugfixes
|
@ -19,6 +19,7 @@ android/key.properties
|
|||
*.iws
|
||||
.idea/
|
||||
android/.idea/
|
||||
android/local.properties
|
||||
|
||||
# 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
|
||||
|
|
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.
|
||||
|
||||
## Downloads:
|
||||
Under releases tab
|
||||
**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.
|
||||
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.
|
||||
**App not installed** error - try different version (arm32/64) or uninstall old version.
|
||||
|
||||
## Compile from source
|
||||
|
||||
Install flutter SDK: https://flutter.dev/docs/get-started/install
|
||||
(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:
|
||||
```
|
||||
flutter pub get
|
||||
flutter build apk
|
||||
```
|
||||
|
||||
## Telegram
|
||||
## Telegram group
|
||||
https://t.me/freezerandroid
|
||||
|
||||
## Credits
|
||||
Tobs: Beta tester
|
||||
Bas Curtiz: Icon, Logo, Banner, Design suggestions
|
||||
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
|
||||
BTC: `14hcr4PGbgqeXd3SoXY9QyJFNpyurgrL9y`
|
||||
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
|
||||
```
|
||||
|
|
|
@ -41,7 +41,7 @@ android {
|
|||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "f.f.freezer"
|
||||
minSdkVersion 20
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
|
||||
<!-- Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
|
@ -64,5 +65,8 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<meta-data android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
After Width: | Height: | Size: 16 KiB |
|
@ -25,6 +25,7 @@ import java.io.FileInputStream;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -73,10 +74,33 @@ public class MainActivity extends FlutterActivity {
|
|||
tag.setField(FieldKey.ALBUM, call.argument("album").toString());
|
||||
tag.setField(FieldKey.ARTIST, call.argument("artists").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
|
||||
String cover = call.argument("cover").toString();
|
||||
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
|
||||
RandomAccessFile imageFile = new RandomAccessFile(new File(cover), "r");
|
||||
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 userId;
|
||||
String userName;
|
||||
String favoritesPlaylistId;
|
||||
String privateUrl = 'http://www.deezer.com/ajax/gw-light.php';
|
||||
Map<String, String> headers = {
|
||||
|
@ -95,6 +96,7 @@ class DeezerAPI {
|
|||
} else {
|
||||
this.token = data['results']['checkForm'];
|
||||
this.userId = data['results']['USER']['USER_ID'].toString();
|
||||
this.userName = data['results']['USER']['BLOG_NAME'];
|
||||
this.favoritesPlaylistId = data['results']['USER']['LOVEDTRACKS_ID'];
|
||||
return true;
|
||||
}
|
||||
|
@ -384,5 +386,20 @@ class DeezerAPI {
|
|||
|
||||
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 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);
|
||||
|
||||
|
@ -264,16 +267,23 @@ class Artist {
|
|||
Map<dynamic, dynamic> albumsJson = const {},
|
||||
Map<dynamic, dynamic> topJson = const {},
|
||||
bool library = false
|
||||
}) => Artist(
|
||||
id: json['ART_ID'].toString(),
|
||||
name: json['ART_NAME'],
|
||||
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
|
||||
);
|
||||
}) {
|
||||
//Get wether radio is available
|
||||
bool _radio = false;
|
||||
if (json['SMARTRADIO'] == true || json['SMARTRADIO'] == 1) _radio = true;
|
||||
|
||||
return Artist(
|
||||
id: json['ART_ID'].toString(),
|
||||
name: json['ART_NAME'],
|
||||
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}) => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
|
|
|
@ -142,6 +142,7 @@ Artist _$ArtistFromJson(Map<String, dynamic> json) {
|
|||
fans: json['fans'] as int,
|
||||
offline: json['offline'] 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,
|
||||
'offline': instance.offline,
|
||||
'library': instance.library,
|
||||
'radio': instance.radio,
|
||||
};
|
||||
|
||||
Playlist _$PlaylistFromJson(Map<String, dynamic> json) {
|
||||
|
|
|
@ -122,14 +122,6 @@ class DownloadManager {
|
|||
onDone: () async {
|
||||
//On download finished
|
||||
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);
|
||||
_download = null;
|
||||
//Remove notification if no more downloads
|
||||
|
@ -144,7 +136,11 @@ class DownloadManager {
|
|||
//Catch download errors
|
||||
_download = null;
|
||||
_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();
|
||||
});
|
||||
//Show download progress notifications
|
||||
|
@ -318,14 +314,12 @@ class DownloadManager {
|
|||
track = await deezerAPI.track(track.id);
|
||||
}
|
||||
|
||||
String url = track.getUrl(settings.getQualityInt(settings.offlineQuality));
|
||||
if (!private) {
|
||||
//Check permissions
|
||||
if (!(await Permission.storage.request().isGranted)) {
|
||||
return;
|
||||
}
|
||||
//If saving to external
|
||||
url = track.getUrl(settings.getQualityInt(settings.downloadQuality));
|
||||
//Save just extension to path, will be generated before download
|
||||
path = 'mp3';
|
||||
if (settings.downloadQuality == AudioQuality.FLAC) {
|
||||
|
@ -339,7 +333,7 @@ class DownloadManager {
|
|||
} catch (e) {}
|
||||
}
|
||||
|
||||
Download download = Download(track: track, path: path, url: url, private: private);
|
||||
Download download = Download(track: track, path: path, private: private);
|
||||
//Database
|
||||
Batch b = db.batch();
|
||||
b.insert('downloads', download.toSQL());
|
||||
|
@ -539,12 +533,17 @@ class Download {
|
|||
|
||||
//TODO: Check for internet before downloading
|
||||
|
||||
Map rawTrackPublic = {};
|
||||
Map rawAlbumPublic = {};
|
||||
if (!this.private && !(this.path.endsWith('.mp3') || this.path.endsWith('.flac'))) {
|
||||
String ext = this.path;
|
||||
//Get track details
|
||||
Map _rawTrackData = await deezerAPI.callApi('song.getListData', params: {'sng_ids': [track.id]});
|
||||
Map rawTrack = _rawTrackData['results']['data'][0];
|
||||
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
|
||||
RegExp sanitize = RegExp(r'[\/\\\?\%\*\:\|\"\<\>]');
|
||||
|
@ -620,6 +619,11 @@ class Download {
|
|||
//Create file if doesnt exist
|
||||
await downloadFile.create(recursive: true);
|
||||
}
|
||||
|
||||
//Quality fallback
|
||||
if (this.url == null)
|
||||
await _fallback();
|
||||
|
||||
//Download
|
||||
_cancel = CancelToken();
|
||||
Response response = await dio.get(
|
||||
|
@ -658,6 +662,10 @@ class Download {
|
|||
//Tag
|
||||
if (!private) {
|
||||
//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', {
|
||||
'path': path,
|
||||
'title': track.title,
|
||||
|
@ -665,7 +673,16 @@ class Download {
|
|||
'artists': track.artistString,
|
||||
'artist': track.artists[0].name,
|
||||
'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
|
||||
await platformChannel.invokeMethod('rescanLibrary', {
|
||||
|
@ -702,6 +719,36 @@ class Download {
|
|||
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
|
||||
Map<String, dynamic> toSQL() => {
|
||||
'trackId': track.id,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:fluttertoast/fluttertoast.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:connectivity/connectivity.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import 'definitions.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -43,9 +45,21 @@ class PlayerHelper {
|
|||
}
|
||||
if (event['action'] == 'queueEnd') {
|
||||
//If last song is played, load more queue
|
||||
onQueueEnd();
|
||||
this.queueSource = QueueSource.fromJson(event['queueSource']);
|
||||
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) {
|
||||
//Log song (if allowed)
|
||||
|
@ -106,6 +120,30 @@ class PlayerHelper {
|
|||
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
|
||||
Future playFromAlbum(Album album, String trackId) async {
|
||||
await playFromTrackList(album.tracks, trackId, QueueSource(
|
||||
|
@ -144,7 +182,7 @@ class PlayerHelper {
|
|||
if (stl.tracks == null || stl.tracks.length == 0) {
|
||||
if (settings.offlineMode) {
|
||||
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,
|
||||
toastLength: Toast.LENGTH_SHORT
|
||||
);
|
||||
|
@ -188,7 +226,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
ConcatenatingAudioSource _audioSource;
|
||||
|
||||
AudioProcessingState _skipState;
|
||||
bool _interrupted;
|
||||
Seeker _seeker;
|
||||
|
||||
//Stream subscriptions
|
||||
|
@ -200,10 +237,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
QueueSource queueSource;
|
||||
Duration _lastPosition;
|
||||
|
||||
Completer _androidAutoCallback;
|
||||
|
||||
MediaItem get mediaItem => _queue[_queueIndex];
|
||||
|
||||
@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
|
||||
_player.currentIndexStream.listen((index) {
|
||||
|
@ -285,6 +327,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
@override
|
||||
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
|
||||
void _seekContinuously(bool begin, int direction) {
|
||||
_seeker?.stop();
|
||||
|
@ -430,46 +486,14 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
if (name == 'load') await this._loadQueueFile();
|
||||
//Shuffle
|
||||
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;
|
||||
}
|
||||
|
||||
//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
|
||||
Future onTaskRemoved() async {
|
||||
await onStop();
|
||||
|
@ -561,6 +585,16 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
|
||||
@override
|
||||
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
|
||||
await this.onSkipToQueueItem(mediaId);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ class SpotifyPlaylist {
|
|||
factory SpotifyPlaylist.fromJson(Map json) => SpotifyPlaylist(
|
||||
name: json['name'],
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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:custom_navigator/custom_navigator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:freezer/ui/library.dart';
|
||||
import 'package:freezer/ui/login_screen.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:freezer/translations.i18n.dart';
|
||||
|
||||
import 'ui/player_bar.dart';
|
||||
import 'api/deezer.dart';
|
||||
import 'settings.dart';
|
||||
import 'ui/cached_image.dart';
|
||||
import 'api/download.dart';
|
||||
import 'api/player.dart';
|
||||
import 'settings.dart';
|
||||
import 'ui/home_screen.dart';
|
||||
|
||||
import 'ui/player_bar.dart';
|
||||
|
||||
Function updateTheme;
|
||||
Function logOut;
|
||||
GlobalKey<NavigatorState> mainNavigatorKey = GlobalKey<NavigatorState>();
|
||||
GlobalKey<NavigatorState> navigatorKey;
|
||||
|
||||
void main() async {
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
//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
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'freezer',
|
||||
title: 'Freezer',
|
||||
theme: settings.themeData,
|
||||
localizationsDelegates: [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: supportedLocales,
|
||||
home: WillPopScope(
|
||||
onWillPop: () async {
|
||||
//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();
|
||||
return false;
|
||||
},
|
||||
child: LoginMainWrapper(),
|
||||
child: I18n(
|
||||
initialLocale: _locale(),
|
||||
child: LoginMainWrapper(),
|
||||
),
|
||||
),
|
||||
navigatorKey: mainNavigatorKey,
|
||||
);
|
||||
|
@ -86,7 +101,6 @@ class LoginMainWrapper extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (settings.arl != null) {
|
||||
|
@ -116,25 +130,20 @@ class _LoginMainWrapperState extends State<LoginMainWrapper> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (settings.arl == null)
|
||||
return LoginWidget(callback: () => setState(() => {}),);
|
||||
return LoginWidget(
|
||||
callback: () => setState(() => {}),
|
||||
);
|
||||
return MainScreen();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class MainScreen extends StatefulWidget {
|
||||
@override
|
||||
_MainScreenState createState() => _MainScreenState();
|
||||
}
|
||||
|
||||
class _MainScreenState extends State<MainScreen> {
|
||||
|
||||
List<Widget> _screens = [
|
||||
HomeScreen(),
|
||||
SearchScreen(),
|
||||
LibraryScreen()
|
||||
];
|
||||
List<Widget> _screens = [HomeScreen(), SearchScreen(), LibraryScreen()];
|
||||
int _selected = 0;
|
||||
|
||||
@override
|
||||
|
@ -146,50 +155,45 @@ class _MainScreenState extends State<MainScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
PlayerBar(),
|
||||
BottomNavigationBar(
|
||||
backgroundColor: Theme.of(context).bottomAppBarColor,
|
||||
currentIndex: _selected,
|
||||
onTap: (int s) async {
|
||||
//Pop all routes until home screen
|
||||
bottomNavigationBar: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
PlayerBar(),
|
||||
BottomNavigationBar(
|
||||
backgroundColor: Theme.of(context).bottomAppBarColor,
|
||||
currentIndex: _selected,
|
||||
onTap: (int s) async {
|
||||
|
||||
//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;
|
||||
});
|
||||
},
|
||||
selectedItemColor: Theme.of(context).primaryColor,
|
||||
items: <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home),
|
||||
title: Text('Home')
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
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,
|
||||
setState(() {
|
||||
_selected = s;
|
||||
});
|
||||
},
|
||||
selectedItemColor: Theme.of(context).primaryColor,
|
||||
items: <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home), title: Text('Home'.i18n)),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.search),
|
||||
title: Text('Search'.i18n),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.library_music), title: Text('Library'.i18n))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
body: AudioServiceWidget(
|
||||
child: CustomNavigator(
|
||||
navigatorKey: navigatorKey,
|
||||
home: _screens[_selected],
|
||||
pageRoute: PageRoutes.materialPageRoute,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ Settings settings;
|
|||
@JsonSerializable()
|
||||
class Settings {
|
||||
|
||||
//Language
|
||||
@JsonKey(defaultValue: null)
|
||||
String language;
|
||||
|
||||
//Account
|
||||
String arl;
|
||||
@JsonKey(ignore: true)
|
||||
|
|
|
@ -11,6 +11,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
|||
downloadPath: json['downloadPath'] as String,
|
||||
arl: json['arl'] as String,
|
||||
)
|
||||
..language = json['language'] as String
|
||||
..wifiQuality =
|
||||
_$enumDecodeNullable(_$AudioQualityEnumMap, json['wifiQuality']) ??
|
||||
AudioQuality.MP3_320
|
||||
|
@ -39,6 +40,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
|||
}
|
||||
|
||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
'language': instance.language,
|
||||
'arl': instance.arl,
|
||||
'wifiQuality': _$AudioQualityEnumMap[instance.wifiQuality],
|
||||
'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> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
if (widget.circular) return ClipOval(
|
||||
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(
|
||||
imageUrl: widget.url,
|
||||
width: widget.width,
|
||||
|
|
|
@ -6,9 +6,9 @@ import 'package:freezer/api/download.dart';
|
|||
import 'package:freezer/api/player.dart';
|
||||
import 'package:freezer/ui/error.dart';
|
||||
import 'package:freezer/ui/search.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import '../api/definitions.dart';
|
||||
import 'player_bar.dart';
|
||||
import 'cached_image.dart';
|
||||
import 'tiles.dart';
|
||||
import 'menu.dart';
|
||||
|
@ -134,13 +134,13 @@ class AlbumDetails extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Icon(Icons.favorite, size: 32),
|
||||
Container(width: 4,),
|
||||
Text('Library')
|
||||
Text('Library'.i18n)
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
await deezerAPI.addFavoriteAlbum(album.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
msg: 'Added to library'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
|
@ -152,7 +152,7 @@ class AlbumDetails extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Icon(Icons.file_download, size: 32.0,),
|
||||
Container(width: 4,),
|
||||
Text('Download')
|
||||
Text('Download'.i18n)
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
|
@ -168,7 +168,7 @@ class AlbumDetails extends StatelessWidget {
|
|||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Text('Disk ${cdi + 1}'),
|
||||
child: Text('Disk'.i18n + ' ${cdi + 1}'),
|
||||
),
|
||||
...List.generate(tracks.length, (i) => TrackTile(
|
||||
tracks[i],
|
||||
|
@ -237,7 +237,7 @@ class _MakeAlbumOfflineState extends State<MakeAlbumOffline> {
|
|||
),
|
||||
Container(width: 4.0,),
|
||||
Text(
|
||||
'Offline',
|
||||
'Offline'.i18n,
|
||||
style: TextStyle(fontSize: 16),
|
||||
)
|
||||
],
|
||||
|
@ -345,25 +345,43 @@ class ArtistDetails extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Icon(Icons.favorite, size: 32),
|
||||
Container(width: 4,),
|
||||
Text('Library')
|
||||
Text('Library'.i18n)
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
await deezerAPI.addFavoriteArtist(artist.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
msg: 'Added to library'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
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,),
|
||||
//Top tracks
|
||||
Text(
|
||||
'Top Tracks',
|
||||
'Top Tracks'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -390,12 +408,12 @@ class ArtistDetails extends StatelessWidget {
|
|||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show more tracks'),
|
||||
title: Text('Show more tracks'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => TrackListScreen(artist.topTracks, QueueSource(
|
||||
id: artist.id,
|
||||
text: 'Top ${artist.name}',
|
||||
text: 'Top'.i18n + '${artist.name}',
|
||||
source: 'topTracks'
|
||||
)))
|
||||
);
|
||||
|
@ -404,7 +422,7 @@ class ArtistDetails extends StatelessWidget {
|
|||
Divider(),
|
||||
//Albums
|
||||
Text(
|
||||
'Top Albums',
|
||||
'Top Albums'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -415,7 +433,7 @@ class ArtistDetails extends StatelessWidget {
|
|||
//Show discography
|
||||
if (i == 10 || i == artist.albums.length) {
|
||||
return ListTile(
|
||||
title: Text('Show all albums'),
|
||||
title: Text('Show all albums'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => DiscographyScreen(artist: artist,))
|
||||
|
@ -552,7 +570,7 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
|
|||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Discography'),
|
||||
title: Text('Discography'.i18n),
|
||||
bottom: TabBar(
|
||||
tabs: [
|
||||
Tab(icon: Icon(Icons.album)),
|
||||
|
@ -603,6 +621,7 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
|
|||
|
||||
enum SortType {
|
||||
DEFAULT,
|
||||
REVERSE,
|
||||
ALPHABETIC,
|
||||
ARTIST
|
||||
}
|
||||
|
@ -634,6 +653,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||
case SortType.ARTIST:
|
||||
tracks.sort((a, b) => a.artists[0].name.compareTo(b.artists[0].name));
|
||||
return tracks;
|
||||
case SortType.REVERSE:
|
||||
return tracks.reversed.toList();
|
||||
case SortType.DEFAULT:
|
||||
default:
|
||||
return tracks;
|
||||
|
@ -782,32 +803,20 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.favorite, size: 32),
|
||||
Container(width: 4,),
|
||||
Text('Library')
|
||||
],
|
||||
),
|
||||
MakePlaylistOffline(playlist),
|
||||
IconButton(
|
||||
icon: Icon(Icons.favorite, size: 32),
|
||||
onPressed: () async {
|
||||
await deezerAPI.addFavoriteAlbum(playlist.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
msg: 'Added to library'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
},
|
||||
),
|
||||
MakePlaylistOffline(playlist),
|
||||
FlatButton(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.file_download, size: 32.0,),
|
||||
Container(width: 4,),
|
||||
Text('Download')
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.file_download, size: 32.0,),
|
||||
onPressed: () {
|
||||
downloadManager.addOfflinePlaylist(playlist, private: false);
|
||||
},
|
||||
|
@ -816,17 +825,21 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||
child: Icon(Icons.sort, size: 32.0),
|
||||
onSelected: (SortType s) => setState(() => _sort = s),
|
||||
itemBuilder: (context) => <PopupMenuEntry<SortType>>[
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: SortType.DEFAULT,
|
||||
child: Text('Default'),
|
||||
child: Text('Default'.i18n),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: SortType.REVERSE,
|
||||
child: Text('Reverse'.i18n),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: SortType.ALPHABETIC,
|
||||
child: Text('Alphabetic'),
|
||||
child: Text('Alphabetic'.i18n),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: SortType.ARTIST,
|
||||
child: Text('Artist'),
|
||||
child: Text('Artist'.i18n),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -914,7 +927,7 @@ class _MakePlaylistOfflineState extends State<MakePlaylistOffline> {
|
|||
),
|
||||
Container(width: 4.0,),
|
||||
Text(
|
||||
'Offline',
|
||||
'Offline'.i18n,
|
||||
style: TextStyle(fontSize: 16),
|
||||
)
|
||||
],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:filesize/filesize.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import 'cached_image.dart';
|
||||
import '../api/download.dart';
|
||||
|
@ -17,9 +18,9 @@ class DownloadTile extends StatelessWidget {
|
|||
case DownloadState.DOWNLOADING:
|
||||
return '${filesize(download.received)} / ${filesize(download.total)}';
|
||||
case DownloadState.POST:
|
||||
return 'Post processing...';
|
||||
return 'Post processing...'.i18n;
|
||||
case DownloadState.DONE:
|
||||
return 'Done'; //Shouldn't be visible
|
||||
return 'Done'.i18n; //Shouldn't be visible
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
@ -27,6 +28,7 @@ class DownloadTile extends StatelessWidget {
|
|||
Widget get progressBar {
|
||||
switch (download.state) {
|
||||
case DownloadState.DOWNLOADING:
|
||||
print(download.track.id);
|
||||
return LinearProgressIndicator(value: download.received / download.total);
|
||||
case DownloadState.POST:
|
||||
return LinearProgressIndicator();
|
||||
|
@ -62,15 +64,15 @@ class DownloadTile extends StatelessWidget {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Delete'),
|
||||
content: Text('Are you sure, you want to delete this download?'),
|
||||
title: Text('Delete'.i18n),
|
||||
content: Text('Are you sure you want to delete this download?'.i18n),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text('Cancel'.i18n),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Delete'),
|
||||
child: Text('Delete'.i18n),
|
||||
onPressed: () {
|
||||
downloadManager.removeDownload(download);
|
||||
if (this.onDelete != null) this.onDelete();
|
||||
|
@ -100,7 +102,7 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Downloads'),
|
||||
title: Text('Downloads'.i18n),
|
||||
actions: [
|
||||
IconButton(
|
||||
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))
|
||||
ListTile(
|
||||
title: Text('Clear queue'),
|
||||
subtitle: Text("This won't delete currently downloading item"),
|
||||
title: Text('Clear queue'.i18n),
|
||||
subtitle: Text("This won't delete currently downloading item".i18n),
|
||||
leading: Icon(Icons.delete),
|
||||
onTap: () async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Delete'),
|
||||
content: Text('Are you sure, you want to delete all queued downloads?'),
|
||||
title: Text('Delete'.i18n),
|
||||
content: Text('Are you sure you want to delete all queued downloads?'.i18n),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text('Cancel'.i18n),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Delete'),
|
||||
child: Text('Delete'.i18n),
|
||||
onPressed: () async {
|
||||
await downloadManager.clearQueue();
|
||||
Navigator.of(context).pop();
|
||||
|
@ -181,9 +183,9 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
|||
return DownloadTile(d);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Clear downloads history'),
|
||||
title: Text('Clear downloads history'.i18n),
|
||||
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 {
|
||||
await downloadManager.cleanDownloadHistory();
|
||||
setState(() {});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
class ErrorScreen extends StatelessWidget {
|
||||
|
||||
|
@ -18,7 +19,7 @@ class ErrorScreen extends StatelessWidget {
|
|||
size: 64.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/ui/error.dart';
|
||||
import 'package:freezer/ui/menu.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
import 'tiles.dart';
|
||||
import 'details_screens.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -175,7 +176,7 @@ class _HomePageScreenState extends State<HomePageScreen> {
|
|||
if (section.hasMore??false) {
|
||||
return FlatButton(
|
||||
child: Text(
|
||||
'Show more',
|
||||
'Show more'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:freezer/api/deezer.dart';
|
|||
import 'package:freezer/api/definitions.dart';
|
||||
import 'package:freezer/api/spotify.dart';
|
||||
import 'package:freezer/ui/menu.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
class ImporterScreen extends StatefulWidget {
|
||||
@override
|
||||
|
@ -34,8 +35,8 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
|||
setState(() => _data = data);
|
||||
return;
|
||||
|
||||
} catch (e) {
|
||||
print(e);
|
||||
} catch (e, st) {
|
||||
print('$e, $st');
|
||||
setState(() {
|
||||
_error = true;
|
||||
_loading = false;
|
||||
|
@ -49,13 +50,13 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Importer'),
|
||||
title: Text('Importer'.i18n),
|
||||
),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Currently supporting only Spotify, with 100 tracks limit'),
|
||||
subtitle: Text('Due to API limitations'),
|
||||
title: Text('Currently supporting only Spotify, with 100 tracks limit'.i18n),
|
||||
subtitle: Text('Due to API limitations'.i18n),
|
||||
leading: Icon(
|
||||
Icons.warning,
|
||||
color: Colors.deepOrangeAccent,
|
||||
|
@ -64,7 +65,7 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
|||
Divider(),
|
||||
Container(height: 16.0,),
|
||||
Text(
|
||||
'Enter your playlist link below',
|
||||
'Enter your playlist link below'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0
|
||||
|
@ -104,7 +105,7 @@ class _ImporterScreenState extends State<ImporterScreen> {
|
|||
),
|
||||
if (_error)
|
||||
ListTile(
|
||||
title: Text('Error loading URL!'),
|
||||
title: Text('Error loading URL!'.i18n),
|
||||
leading: Icon(Icons.error, color: Colors.red,),
|
||||
),
|
||||
if (_data != null)
|
||||
|
@ -133,13 +134,14 @@ class _ImporterWidgetState extends State<ImporterWidget> {
|
|||
ListTile(
|
||||
title: Text(widget.playlist.name),
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
RaisedButton(
|
||||
child: Text('Convert'),
|
||||
child: Text('Convert'.i18n),
|
||||
color: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
spotify.convertPlaylist(widget.playlist);
|
||||
|
@ -149,7 +151,7 @@ class _ImporterWidgetState extends State<ImporterWidget> {
|
|||
},
|
||||
),
|
||||
RaisedButton(
|
||||
child: Text('Download only'),
|
||||
child: Text('Download only'.i18n),
|
||||
color: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
spotify.convertPlaylist(widget.playlist, downloadOnly: true);
|
||||
|
@ -197,7 +199,7 @@ class CurrentlyImportingScreen extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Importing...'),),
|
||||
appBar: AppBar(title: Text('Importing...'.i18n),),
|
||||
body: StreamBuilder(
|
||||
stream: spotify.importingStream.stream,
|
||||
builder: (context, snapshot) {
|
||||
|
@ -254,7 +256,7 @@ class CurrentlyImportingScreen extends StatelessWidget {
|
|||
),
|
||||
if (snapshot.data != null)
|
||||
FlatButton(
|
||||
child: Text('Playlist menu'),
|
||||
child: Text('Playlist menu'.i18n),
|
||||
onPressed: () async {
|
||||
Playlist p = await deezerAPI.playlist(snapshot.data);
|
||||
p.library = true;
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:freezer/ui/downloads_screen.dart';
|
|||
import 'package:freezer/ui/error.dart';
|
||||
import 'package:freezer/ui/importer_screen.dart';
|
||||
import 'package:freezer/ui/tiles.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import 'menu.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
@ -24,7 +25,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
title: Text('Library'),
|
||||
title: Text('Library'.i18n),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.file_download),
|
||||
|
@ -58,9 +59,9 @@ class LibraryScreen extends StatelessWidget {
|
|||
Container(height: 4.0,),
|
||||
if (downloadManager.stopped && downloadManager.queue.length > 0)
|
||||
ListTile(
|
||||
title: Text('Downloads'),
|
||||
title: Text('Downloads'.i18n),
|
||||
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: () {
|
||||
downloadManager.start();
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
|
@ -73,7 +74,7 @@ class LibraryScreen extends StatelessWidget {
|
|||
Divider(),
|
||||
|
||||
ListTile(
|
||||
title: Text('Tracks'),
|
||||
title: Text('Tracks'.i18n),
|
||||
leading: Icon(Icons.audiotrack),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
|
@ -82,7 +83,7 @@ class LibraryScreen extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Albums'),
|
||||
title: Text('Albums'.i18n),
|
||||
leading: Icon(Icons.album),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
|
@ -91,7 +92,7 @@ class LibraryScreen extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Artists'),
|
||||
title: Text('Artists'.i18n),
|
||||
leading: Icon(Icons.recent_actors),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
|
@ -100,7 +101,7 @@ class LibraryScreen extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Playlists'),
|
||||
title: Text('Playlists'.i18n),
|
||||
leading: Icon(Icons.playlist_play),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
|
@ -110,9 +111,9 @@ class LibraryScreen extends StatelessWidget {
|
|||
),
|
||||
Divider(),
|
||||
ListTile(
|
||||
title: Text('Import'),
|
||||
title: Text('Import'.i18n),
|
||||
leading: Icon(Icons.import_export),
|
||||
subtitle: Text('Import playlists from Spotify'),
|
||||
subtitle: Text('Import playlists from Spotify'.i18n),
|
||||
onTap: () {
|
||||
if (spotify.doneImporting != null) {
|
||||
Navigator.of(context).push(
|
||||
|
@ -128,7 +129,7 @@ class LibraryScreen extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
ExpansionTile(
|
||||
title: Text('Statistics'),
|
||||
title: Text('Statistics'.i18n),
|
||||
leading: Icon(Icons.insert_chart),
|
||||
children: <Widget>[
|
||||
FutureBuilder(
|
||||
|
@ -148,27 +149,27 @@ class LibraryScreen extends StatelessWidget {
|
|||
return Column(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Offline tracks'),
|
||||
title: Text('Offline tracks'.i18n),
|
||||
leading: Icon(Icons.audiotrack),
|
||||
trailing: Text(data[0]),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Offline albums'),
|
||||
title: Text('Offline albums'.i18n),
|
||||
leading: Icon(Icons.album),
|
||||
trailing: Text(data[1]),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Offline playlists'),
|
||||
title: Text('Offline playlists'.i18n),
|
||||
leading: Icon(Icons.playlist_add),
|
||||
trailing: Text(data[2]),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Offline size'),
|
||||
title: Text('Offline size'.i18n),
|
||||
leading: Icon(Icons.sd_card),
|
||||
trailing: Text(data[3]),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Free space'),
|
||||
title: Text('Free space'.i18n),
|
||||
leading: Icon(Icons.disc_full),
|
||||
trailing: Text(data[4]),
|
||||
),
|
||||
|
@ -254,7 +255,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Tracks'),),
|
||||
appBar: AppBar(title: Text('Tracks'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
Card(
|
||||
|
@ -263,7 +264,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
children: <Widget>[
|
||||
Container(height: 8.0,),
|
||||
Text(
|
||||
'Loved tracks',
|
||||
'Loved tracks'.i18n,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24
|
||||
|
@ -279,7 +280,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
children: <Widget>[
|
||||
Icon(Icons.file_download, size: 32.0,),
|
||||
Container(width: 4,),
|
||||
Text('Download')
|
||||
Text('Download'.i18n)
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
|
@ -299,7 +300,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
onTap: () {
|
||||
playerHelper.playFromTrackList(tracks, t.id, QueueSource(
|
||||
id: deezerAPI.favoritesPlaylistId,
|
||||
text: 'Favorites',
|
||||
text: 'Favorites'.i18n,
|
||||
source: 'playlist'
|
||||
));
|
||||
},
|
||||
|
@ -328,7 +329,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
),
|
||||
Divider(),
|
||||
Text(
|
||||
'All offline tracks',
|
||||
'All offline tracks'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
|
@ -343,7 +344,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
|
|||
onTap: () {
|
||||
playerHelper.playFromTrackList(allTracks, t.id, QueueSource(
|
||||
id: 'allTracks',
|
||||
text: 'All offline tracks',
|
||||
text: 'All offline tracks'.i18n,
|
||||
source: 'offline'
|
||||
));
|
||||
},
|
||||
|
@ -386,7 +387,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Albums'),),
|
||||
appBar: AppBar(title: Text('Albums'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
Container(height: 8.0,),
|
||||
|
@ -427,7 +428,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
|
|||
children: <Widget>[
|
||||
Divider(),
|
||||
Text(
|
||||
'Offline albums',
|
||||
'Offline albums'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -473,7 +474,7 @@ class _LibraryArtistsState extends State<LibraryArtists> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Artists'),),
|
||||
appBar: AppBar(title: Text('Artists'.i18n),),
|
||||
body: FutureBuilder(
|
||||
future: deezerAPI.getArtists(),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
|
@ -533,20 +534,30 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
|||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Playlists'),),
|
||||
appBar: AppBar(title: Text('Playlists'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Create new playlist'),
|
||||
title: Text('Create new playlist'.i18n),
|
||||
leading: Icon(Icons.playlist_add),
|
||||
onTap: () {
|
||||
if (settings.offlineMode) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Cannot create playlists in offline mode',
|
||||
msg: 'Cannot create playlists in offline mode'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
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)
|
||||
...List.generate(_playlists.length, (int i) {
|
||||
Playlist p = _playlists[i];
|
||||
|
@ -593,7 +618,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
|
|||
children: <Widget>[
|
||||
Divider(),
|
||||
Text(
|
||||
'Offline playlists',
|
||||
'Offline playlists'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezer/api/deezer.dart';
|
||||
import 'package:freezer/api/player.dart';
|
||||
import 'package:freezer/main.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
import '../api/definitions.dart';
|
||||
|
@ -62,11 +61,11 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Error'),
|
||||
content: Text('Error logging in! Please check your token and internet connection and try again.'),
|
||||
title: Text('Error'.i18n),
|
||||
content: Text('Error logging in! Please check your token and internet connection and try again.'.i18n),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text('Dismiss'),
|
||||
child: Text('Dismiss'.i18n),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
@ -117,7 +116,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
children: <Widget>[
|
||||
Container(height: 16.0,),
|
||||
Text(
|
||||
'Welcome to',
|
||||
'Welcome to'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0
|
||||
|
@ -126,7 +125,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
FreezerTitle(),
|
||||
Container(height: 8.0,),
|
||||
Text(
|
||||
"Please login using your Deezer account.",
|
||||
"Please login using your Deezer account.".i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0
|
||||
|
@ -136,7 +135,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: OutlineButton(
|
||||
child: Text('Login using browser'),
|
||||
child: Text('Login using browser'.i18n),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => LoginBrowser(_update))
|
||||
|
@ -147,18 +146,18 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: OutlineButton(
|
||||
child: Text('Login using token'),
|
||||
child: Text('Login using token'.i18n),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Enter ARL'),
|
||||
title: Text('Enter ARL'.i18n),
|
||||
content: Container(
|
||||
child: TextField(
|
||||
onChanged: (String s) => _arl = s,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Token (ARL)'
|
||||
labelText: 'Token (ARL)'.i18n
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -166,7 +165,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
FlatButton(
|
||||
child: Text('Save'),
|
||||
onPressed: () {
|
||||
settings.arl = _arl;
|
||||
settings.arl = _arl.trim();
|
||||
Navigator.of(context).pop();
|
||||
_update();
|
||||
},
|
||||
|
@ -180,7 +179,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
),
|
||||
Container(height: 16.0,),
|
||||
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,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0
|
||||
|
@ -199,7 +198,7 @@ class _LoginWidgetState extends State<LoginWidget> {
|
|||
Divider(),
|
||||
Container(height: 8.0,),
|
||||
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,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0
|
||||
|
|
|
@ -6,9 +6,9 @@ import 'package:freezer/api/deezer.dart';
|
|||
import 'package:freezer/api/download.dart';
|
||||
import 'package:freezer/ui/details_screens.dart';
|
||||
import 'package:freezer/ui/error.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import '../api/definitions.dart';
|
||||
import '../api/player.dart';
|
||||
import 'cached_image.dart';
|
||||
|
||||
class MenuSheet {
|
||||
|
@ -137,7 +137,7 @@ class MenuSheet {
|
|||
//===================
|
||||
|
||||
Widget addToQueueNext(Track t) => ListTile(
|
||||
title: Text('Play next'),
|
||||
title: Text('Play next'.i18n),
|
||||
leading: Icon(Icons.playlist_play),
|
||||
onTap: () async {
|
||||
//-1 = next
|
||||
|
@ -146,7 +146,7 @@ class MenuSheet {
|
|||
});
|
||||
|
||||
Widget addToQueue(Track t) => ListTile(
|
||||
title: Text('Add to queue'),
|
||||
title: Text('Add to queue'.i18n),
|
||||
leading: Icon(Icons.playlist_add),
|
||||
onTap: () async {
|
||||
await AudioService.addQueueItem(t.toMediaItem());
|
||||
|
@ -155,7 +155,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget addTrackFavorite(Track t) => ListTile(
|
||||
title: Text('Add track to favorites'),
|
||||
title: Text('Add track to favorites'.i18n),
|
||||
leading: Icon(Icons.favorite),
|
||||
onTap: () async {
|
||||
await deezerAPI.addFavoriteTrack(t.id);
|
||||
|
@ -165,7 +165,7 @@ class MenuSheet {
|
|||
downloadManager.addOfflinePlaylist(p);
|
||||
}
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library!',
|
||||
msg: 'Added to library'.i18n,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastLength: Toast.LENGTH_SHORT
|
||||
);
|
||||
|
@ -174,7 +174,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget downloadTrack(Track t) => ListTile(
|
||||
title: Text('Download'),
|
||||
title: Text('Download'.i18n),
|
||||
leading: Icon(Icons.file_download),
|
||||
onTap: () async {
|
||||
await downloadManager.addOfflineTrack(t, private: false);
|
||||
|
@ -183,7 +183,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget addToPlaylist(Track t) => ListTile(
|
||||
title: Text('Add to playlist'),
|
||||
title: Text('Add to playlist'.i18n),
|
||||
leading: Icon(Icons.playlist_add),
|
||||
onTap: () async {
|
||||
|
||||
|
@ -194,7 +194,7 @@ class MenuSheet {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Select playlist'),
|
||||
title: Text('Select playlist'.i18n),
|
||||
content: FutureBuilder(
|
||||
future: deezerAPI.getPlaylists(),
|
||||
builder: (context, snapshot) {
|
||||
|
@ -224,7 +224,7 @@ class MenuSheet {
|
|||
},
|
||||
)),
|
||||
ListTile(
|
||||
title: Text('Create new playlist'),
|
||||
title: Text('Create new playlist'.i18n),
|
||||
leading: Icon(Icons.add),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
|
@ -250,7 +250,7 @@ class MenuSheet {
|
|||
downloadManager.addOfflinePlaylist(p);
|
||||
}
|
||||
Fluttertoast.showToast(
|
||||
msg: "Track added to ${p.title}",
|
||||
msg: "Track added to".i18n + " ${p.title}",
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
|
@ -261,12 +261,12 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget removeFromPlaylist(Track t, Playlist p) => ListTile(
|
||||
title: Text('Remove from playlist'),
|
||||
title: Text('Remove from playlist'.i18n),
|
||||
leading: Icon(Icons.delete),
|
||||
onTap: () async {
|
||||
await deezerAPI.removeFromPlaylist(t.id, p.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Track removed from ${p.title}',
|
||||
msg: 'Track removed from'.i18n + ' ${p.title}',
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
|
@ -275,7 +275,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget removeFavoriteTrack(Track t, {onUpdate}) => ListTile(
|
||||
title: Text('Remove favorite'),
|
||||
title: Text('Remove favorite'.i18n),
|
||||
leading: Icon(Icons.delete),
|
||||
onTap: () async {
|
||||
await deezerAPI.removeFavorite(t.id);
|
||||
|
@ -285,7 +285,7 @@ class MenuSheet {
|
|||
await downloadManager.addOfflinePlaylist(p);
|
||||
}
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Track removed from library',
|
||||
msg: 'Track removed from library'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
|
@ -297,7 +297,7 @@ class MenuSheet {
|
|||
//Redirect to artist page (ie from track)
|
||||
Widget showArtist(Artist a) => ListTile(
|
||||
title: Text(
|
||||
'Go to ${a.name}',
|
||||
'Go to'.i18n + ' ${a.name}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
@ -312,7 +312,7 @@ class MenuSheet {
|
|||
|
||||
Widget showAlbum(Album a) => ListTile(
|
||||
title: Text(
|
||||
'Go to ${a.title}',
|
||||
'Go to'.i18n + ' ${a.title}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
@ -345,7 +345,7 @@ class MenuSheet {
|
|||
//===================
|
||||
|
||||
Widget downloadAlbum(Album a) => ListTile(
|
||||
title: Text('Download'),
|
||||
title: Text('Download'.i18n),
|
||||
leading: Icon(Icons.file_download),
|
||||
onTap: () async {
|
||||
await downloadManager.addOfflineAlbum(a, private: false);
|
||||
|
@ -354,7 +354,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget offlineAlbum(Album a) => ListTile(
|
||||
title: Text('Make offline'),
|
||||
title: Text('Make offline'.i18n),
|
||||
leading: Icon(Icons.offline_pin),
|
||||
onTap: () async {
|
||||
await deezerAPI.addFavoriteAlbum(a.id);
|
||||
|
@ -364,12 +364,12 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget libraryAlbum(Album a) => ListTile(
|
||||
title: Text('Add to library'),
|
||||
title: Text('Add to library'.i18n),
|
||||
leading: Icon(Icons.library_music),
|
||||
onTap: () async {
|
||||
await deezerAPI.addFavoriteAlbum(a.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
msg: 'Added to library'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
_close();
|
||||
|
@ -378,13 +378,13 @@ class MenuSheet {
|
|||
|
||||
//Remove album from favorites
|
||||
Widget removeAlbum(Album a, {Function onRemove}) => ListTile(
|
||||
title: Text('Remove album'),
|
||||
title: Text('Remove album'.i18n),
|
||||
leading: Icon(Icons.delete),
|
||||
onTap: () async {
|
||||
await deezerAPI.removeAlbum(a.id);
|
||||
await downloadManager.removeOfflineAlbum(a.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Album removed',
|
||||
msg: 'Album removed'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
|
@ -409,12 +409,12 @@ class MenuSheet {
|
|||
//===================
|
||||
|
||||
Widget removeArtist(Artist a, {Function onRemove}) => ListTile(
|
||||
title: Text('Remove from favorites'),
|
||||
title: Text('Remove from favorites'.i18n),
|
||||
leading: Icon(Icons.delete),
|
||||
onTap: () async {
|
||||
await deezerAPI.removeArtist(a.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Artist removed from library',
|
||||
msg: 'Artist removed from library'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
|
@ -424,12 +424,12 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget favoriteArtist(Artist a) => ListTile(
|
||||
title: Text('Add to favorites'),
|
||||
title: Text('Add to favorites'.i18n),
|
||||
leading: Icon(Icons.favorite),
|
||||
onTap: () async {
|
||||
await deezerAPI.addFavoriteArtist(a.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
msg: 'Added to library'.i18n,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
|
@ -455,7 +455,7 @@ class MenuSheet {
|
|||
//===================
|
||||
|
||||
Widget removePlaylistLibrary(Playlist p, {Function onRemove}) => ListTile(
|
||||
title: Text('Remove from library'),
|
||||
title: Text('Remove from library'.i18n),
|
||||
leading: Icon(Icons.delete),
|
||||
onTap: () async {
|
||||
if (p.user.id.trim() == deezerAPI.userId) {
|
||||
|
@ -472,12 +472,12 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget addPlaylistLibrary(Playlist p) => ListTile(
|
||||
title: Text('Add playlist to library'),
|
||||
title: Text('Add playlist to library'.i18n),
|
||||
leading: Icon(Icons.favorite),
|
||||
onTap: () async {
|
||||
await deezerAPI.addPlaylist(p.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added playlist to library',
|
||||
msg: 'Added playlist to library'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
_close();
|
||||
|
@ -485,7 +485,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget addPlaylistOffline(Playlist p) => ListTile(
|
||||
title: Text('Make playlist offline'),
|
||||
title: Text('Make playlist offline'.i18n),
|
||||
leading: Icon(Icons.offline_pin),
|
||||
onTap: () async {
|
||||
//Add to library
|
||||
|
@ -496,7 +496,7 @@ class MenuSheet {
|
|||
);
|
||||
|
||||
Widget downloadPlaylist(Playlist p) => ListTile(
|
||||
title: Text('Download playlist'),
|
||||
title: Text('Download playlist'.i18n),
|
||||
leading: Icon(Icons.file_download),
|
||||
onTap: () async {
|
||||
downloadManager.addOfflinePlaylist(p, private: false);
|
||||
|
@ -542,20 +542,20 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Create playlist'),
|
||||
title: Text('Create playlist'.i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Title'
|
||||
labelText: 'Title'.i18n
|
||||
),
|
||||
onChanged: (String s) => _title = s,
|
||||
),
|
||||
TextField(
|
||||
onChanged: (String s) => _description = s,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Description'
|
||||
labelText: 'Description'.i18n
|
||||
),
|
||||
),
|
||||
Container(height: 4.0,),
|
||||
|
@ -567,11 +567,11 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
|||
items: [
|
||||
DropdownMenuItem<int>(
|
||||
value: 1,
|
||||
child: Text('Private'),
|
||||
child: Text('Private'.i18n),
|
||||
),
|
||||
DropdownMenuItem<int>(
|
||||
value: 2,
|
||||
child: Text('Collaborative'),
|
||||
child: Text('Collaborative'.i18n),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -579,11 +579,11 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
|||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text('Cancel'.i18n),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Create'),
|
||||
child: Text('Create'.i18n),
|
||||
onPressed: () async {
|
||||
List<String> tracks = [];
|
||||
if (widget.tracks != null) {
|
||||
|
@ -596,7 +596,7 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
|
|||
trackIds: tracks
|
||||
);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Playlist created!',
|
||||
msg: 'Playlist created!'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
@ -137,7 +137,7 @@ class PlayPauseButton extends StatelessWidget {
|
|||
}
|
||||
|
||||
//Paused
|
||||
if ((!AudioService.playbackState.playing &&
|
||||
if ((!(AudioService.playbackState?.playing??false) &&
|
||||
AudioService.playbackState.processingState == AudioProcessingState.ready) ||
|
||||
//None state (stopped)
|
||||
AudioService.playbackState.processingState == AudioProcessingState.none) {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import 'dart:ui';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter_screenutil/screenutil.dart';
|
||||
import 'package:freezer/api/deezer.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/settings_screen.dart';
|
||||
import 'package:freezer/ui/tiles.dart';
|
||||
|
@ -18,6 +15,9 @@ import 'cached_image.dart';
|
|||
import '../api/definitions.dart';
|
||||
import 'player_bar.dart';
|
||||
|
||||
import 'dart:ui';
|
||||
import 'dart:async';
|
||||
|
||||
class PlayerScreen extends StatefulWidget {
|
||||
@override
|
||||
_PlayerScreenState createState() => _PlayerScreenState();
|
||||
|
@ -527,9 +527,9 @@ class PlayerScreenTopRow extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Container(
|
||||
width: this.textWidth??ScreenUtil().setWidth(600),
|
||||
width: this.textWidth??ScreenUtil().setWidth(550),
|
||||
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,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.left,
|
||||
|
@ -728,7 +728,7 @@ class _QueueScreenState extends State<QueueScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Queue'),
|
||||
title: Text('Queue'.i18n),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:freezer/api/download.dart';
|
|||
import 'package:freezer/api/player.dart';
|
||||
import 'package:freezer/ui/details_screens.dart';
|
||||
import 'package:freezer/ui/menu.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import 'tiles.dart';
|
||||
import '../api/deezer.dart';
|
||||
|
@ -20,6 +21,7 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
String _query;
|
||||
bool _offline = false;
|
||||
TextEditingController _controller = new TextEditingController();
|
||||
List _suggestions = [];
|
||||
|
||||
void _submit(BuildContext context, {String query}) {
|
||||
if (query != null) _query = query;
|
||||
|
@ -41,10 +43,21 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Search'),),
|
||||
appBar: AppBar(title: Text('Search'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
Container(height: 16.0),
|
||||
|
@ -57,9 +70,12 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
alignment: Alignment(1.0, 1.0),
|
||||
children: [
|
||||
TextField(
|
||||
onChanged: (String s) => _query = s,
|
||||
onChanged: (String s) {
|
||||
setState(() => _query = s);
|
||||
_loadSuggestions();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Search'
|
||||
labelText: 'Search'.i18n
|
||||
),
|
||||
controller: _controller,
|
||||
onSubmitted: (String s) => _submit(context, query: s),
|
||||
|
@ -73,7 +89,6 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
],
|
||||
)
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 8, 0, 0),
|
||||
child: IconButton(
|
||||
|
@ -85,14 +100,23 @@ class _SearchScreenState extends State<SearchScreen> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Offline search'),
|
||||
title: Text('Offline search'.i18n),
|
||||
leading: Switch(
|
||||
value: _offline,
|
||||
onChanged: (v) {
|
||||
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) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Search Results'),
|
||||
title: Text('Search Results'.i18n),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _search(),
|
||||
|
@ -139,7 +163,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
Icons.warning,
|
||||
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) {
|
||||
tracks = [
|
||||
Text(
|
||||
'Tracks',
|
||||
'Tracks'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 26.0,
|
||||
|
@ -163,7 +187,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
t,
|
||||
onTap: () {
|
||||
playerHelper.playFromTrackList(results.tracks, t.id, QueueSource(
|
||||
text: 'Search',
|
||||
text: 'Search'.i18n,
|
||||
id: query,
|
||||
source: 'search'
|
||||
));
|
||||
|
@ -175,13 +199,13 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show all tracks'),
|
||||
title: Text('Show all tracks'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => TrackListScreen(results.tracks, QueueSource(
|
||||
id: query,
|
||||
source: 'search',
|
||||
text: 'Search'
|
||||
text: 'Search'.i18n
|
||||
)))
|
||||
);
|
||||
},
|
||||
|
@ -194,7 +218,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
if (results.albums != null && results.albums.length != 0) {
|
||||
albums = [
|
||||
Text(
|
||||
'Albums',
|
||||
'Albums'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 26.0,
|
||||
|
@ -218,7 +242,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show all albums'),
|
||||
title: Text('Show all albums'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => AlbumListScreen(results.albums))
|
||||
|
@ -233,7 +257,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
if (results.artists != null && results.artists.length != 0) {
|
||||
artists = [
|
||||
Text(
|
||||
'Artists',
|
||||
'Artists'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 26.0,
|
||||
|
@ -269,7 +293,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
if (results.playlists != null && results.playlists.length != 0) {
|
||||
playlists = [
|
||||
Text(
|
||||
'Playlists',
|
||||
'Playlists'.i18n,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 26.0,
|
||||
|
@ -293,7 +317,7 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show all playlists'),
|
||||
title: Text('Show all playlists'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => SearchResultPlaylists(results.playlists))
|
||||
|
@ -332,7 +356,7 @@ class TrackListScreen extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Tracks'),),
|
||||
appBar: AppBar(title: Text('Tracks'.i18n),),
|
||||
body: ListView.builder(
|
||||
itemCount: tracks.length,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
|
@ -362,7 +386,7 @@ class AlbumListScreen extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Albums'),),
|
||||
appBar: AppBar(title: Text('Albums'.i18n),),
|
||||
body: ListView.builder(
|
||||
itemCount: albums.length,
|
||||
itemBuilder: (context, i) {
|
||||
|
@ -393,7 +417,7 @@ class SearchResultPlaylists extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Playlists'),),
|
||||
appBar: AppBar(title: Text('Playlists'.i18n),),
|
||||
body: ListView.builder(
|
||||
itemCount: playlists.length,
|
||||
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:freezer/api/deezer.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/languages.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:path_provider_ex/path_provider_ex.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
@ -44,18 +46,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Settings'),),
|
||||
appBar: AppBar(title: Text('Settings'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('General'),
|
||||
title: Text('General'.i18n),
|
||||
leading: Icon(Icons.settings),
|
||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => GeneralSettings()
|
||||
)),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Appearance'),
|
||||
title: Text('Appearance'.i18n),
|
||||
leading: Icon(Icons.color_lens),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
|
@ -63,7 +65,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Quality'),
|
||||
title: Text('Quality'.i18n),
|
||||
leading: Icon(Icons.high_quality),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
|
@ -71,12 +73,54 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Deezer'),
|
||||
title: Text('Deezer'.i18n),
|
||||
leading: Icon(Icons.equalizer),
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(
|
||||
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(),
|
||||
Text(
|
||||
_about,
|
||||
|
@ -97,22 +141,22 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Appearance'),),
|
||||
appBar: AppBar(title: Text('Appearance'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Theme'),
|
||||
subtitle: Text('Currently: ${settings.theme.toString().split('.').last}'),
|
||||
title: Text('Theme'.i18n),
|
||||
subtitle: Text('Currently'.i18n + ': ${settings.theme.toString().split('.').last}'),
|
||||
leading: Icon(Icons.color_lens),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: Text('Select theme'),
|
||||
title: Text('Select theme'.i18n),
|
||||
children: <Widget>[
|
||||
SimpleDialogOption(
|
||||
child: Text('Light (default)'),
|
||||
child: Text('Light (default)'.i18n),
|
||||
onPressed: () {
|
||||
setState(() => settings.theme = Themes.Light);
|
||||
settings.save();
|
||||
|
@ -121,7 +165,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
},
|
||||
),
|
||||
SimpleDialogOption(
|
||||
child: Text('Dark'),
|
||||
child: Text('Dark'.i18n),
|
||||
onPressed: () {
|
||||
setState(() => settings.theme = Themes.Dark);
|
||||
settings.save();
|
||||
|
@ -130,7 +174,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
},
|
||||
),
|
||||
SimpleDialogOption(
|
||||
child: Text('Black (AMOLED)'),
|
||||
child: Text('Black (AMOLED)'.i18n),
|
||||
onPressed: () {
|
||||
setState(() => settings.theme = Themes.Black);
|
||||
settings.save();
|
||||
|
@ -139,7 +183,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
},
|
||||
),
|
||||
SimpleDialogOption(
|
||||
child: Text('Deezer (Dark)'),
|
||||
child: Text('Deezer (Dark)'.i18n),
|
||||
onPressed: () {
|
||||
setState(() => settings.theme = Themes.Deezer);
|
||||
settings.save();
|
||||
|
@ -154,10 +198,10 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Primary color'),
|
||||
title: Text('Primary color'.i18n),
|
||||
leading: Icon(Icons.format_paint),
|
||||
subtitle: Text(
|
||||
'Selected color',
|
||||
'Selected color'.i18n,
|
||||
style: TextStyle(
|
||||
color: settings.primaryColor
|
||||
),
|
||||
|
@ -167,7 +211,7 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Primary color'),
|
||||
title: Text('Primary color'.i18n),
|
||||
content: Container(
|
||||
height: 200,
|
||||
child: MaterialColorPicker(
|
||||
|
@ -189,8 +233,8 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Use album art primary color'),
|
||||
subtitle: Text('Warning: might be buggy'),
|
||||
title: Text('Use album art primary color'.i18n),
|
||||
subtitle: Text('Warning: might be buggy'.i18n),
|
||||
leading: Switch(
|
||||
value: settings.useArtColor,
|
||||
onChanged: (v) => setState(() => settings.updateUseArtColor(v)),
|
||||
|
@ -212,29 +256,29 @@ class _QualitySettingsState extends State<QualitySettings> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Quality'),),
|
||||
appBar: AppBar(title: Text('Quality'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Mobile streaming'),
|
||||
title: Text('Mobile streaming'.i18n),
|
||||
leading: Icon(Icons.network_cell),
|
||||
),
|
||||
QualityPicker('mobile'),
|
||||
Divider(),
|
||||
ListTile(
|
||||
title: Text('Wifi streaming'),
|
||||
title: Text('Wifi streaming'.i18n),
|
||||
leading: Icon(Icons.network_wifi),
|
||||
),
|
||||
QualityPicker('wifi'),
|
||||
Divider(),
|
||||
ListTile(
|
||||
title: Text('Offline'),
|
||||
title: Text('Offline'.i18n),
|
||||
leading: Icon(Icons.offline_pin),
|
||||
),
|
||||
QualityPicker('offline'),
|
||||
Divider(),
|
||||
ListTile(
|
||||
title: Text('External downloads'),
|
||||
title: Text('External downloads'.i18n),
|
||||
leading: Icon(Icons.file_download),
|
||||
),
|
||||
QualityPicker('download'),
|
||||
|
@ -349,12 +393,12 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Deezer'),),
|
||||
appBar: AppBar(title: Text('Deezer'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Content language'),
|
||||
subtitle: Text('Not app language, used in headers. Now: ${settings.deezerLanguage}'),
|
||||
title: Text('Content language'.i18n),
|
||||
subtitle: Text('Not app language, used in headers. Now'.i18n + ': ${settings.deezerLanguage}'),
|
||||
leading: Icon(Icons.language),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
|
@ -362,7 +406,7 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
|||
builder: (context) => LanguagePickerDialog(
|
||||
titlePadding: EdgeInsets.all(8.0),
|
||||
isSearchable: true,
|
||||
title: Text('Select language'),
|
||||
title: Text('Select language'.i18n),
|
||||
onValuePicked: (Language language) {
|
||||
setState(() => settings.deezerLanguage = language.isoCode);
|
||||
settings.save();
|
||||
|
@ -372,8 +416,8 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Content country'),
|
||||
subtitle: Text('Country used in headers. Now: ${settings.deezerCountry}'),
|
||||
title: Text('Content country'.i18n),
|
||||
subtitle: Text('Country used in headers. Now'.i18n + ': ${settings.deezerCountry}'),
|
||||
leading: Icon(Icons.vpn_lock),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
|
@ -390,8 +434,8 @@ class _DeezerSettingsState extends State<DeezerSettings> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Log tracks'),
|
||||
subtitle: Text('Send track listen logs to Deezer, enable it for features like Flow to work properly'),
|
||||
title: Text('Log tracks'.i18n),
|
||||
subtitle: Text('Send track listen logs to Deezer, enable it for features like Flow to work properly'.i18n),
|
||||
leading: Checkbox(
|
||||
value: settings.logListen,
|
||||
onChanged: (bool v) {
|
||||
|
@ -415,12 +459,12 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('General'),),
|
||||
appBar: AppBar(title: Text('General'.i18n),),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text('Offline mode'),
|
||||
subtitle: Text('Will be overwritten on start.'),
|
||||
title: Text('Offline mode'.i18n),
|
||||
subtitle: Text('Will be overwritten on start.'.i18n),
|
||||
leading: Switch(
|
||||
value: settings.offlineMode,
|
||||
onChanged: (bool v) {
|
||||
|
@ -436,7 +480,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
setState(() => settings.offlineMode = false);
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Error logging in, check your internet connections.',
|
||||
msg: 'Error logging in, check your internet connections.'.i18n,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastLength: Toast.LENGTH_SHORT
|
||||
);
|
||||
|
@ -444,7 +488,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
Navigator.of(context).pop();
|
||||
});
|
||||
return AlertDialog(
|
||||
title: Text('Logging in...'),
|
||||
title: Text('Logging in...'.i18n),
|
||||
content: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
@ -459,7 +503,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Download path'),
|
||||
title: Text('Download path'.i18n),
|
||||
leading: Icon(Icons.folder),
|
||||
subtitle: Text(settings.downloadPath),
|
||||
onTap: () async {
|
||||
|
@ -474,8 +518,8 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Downloads naming'),
|
||||
subtitle: Text('Currently: ${settings.downloadFilename}'),
|
||||
title: Text('Downloads naming'.i18n),
|
||||
subtitle: Text('Currently'.i18n + ': ${settings.downloadFilename}'),
|
||||
leading: Icon(Icons.text_format),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
|
@ -485,19 +529,21 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
TextEditingController _controller = TextEditingController();
|
||||
String filename = settings.downloadFilename;
|
||||
_controller.value = _controller.value.copyWith(text: filename);
|
||||
String _new = _controller.value.text;
|
||||
|
||||
//Dialog with filename format
|
||||
return AlertDialog(
|
||||
title: Text('Downloaded tracks filename'),
|
||||
title: Text('Downloaded tracks filename'.i18n),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _controller,
|
||||
onChanged: (String s) => _new = s,
|
||||
),
|
||||
Container(height: 8.0),
|
||||
Text(
|
||||
'Valid variables are: %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%',
|
||||
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%',
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
),
|
||||
|
@ -506,29 +552,30 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text('Cancel'.i18n),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Reset'),
|
||||
child: Text('Reset'.i18n),
|
||||
onPressed: () {
|
||||
_controller.value = _controller.value.copyWith(
|
||||
text: '%artists% - %title%'
|
||||
);
|
||||
_new = '%artists% - %title%';
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Clear'),
|
||||
child: Text('Clear'.i18n),
|
||||
onPressed: () => _controller.clear(),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Save'),
|
||||
onPressed: () {
|
||||
child: Text('Save'.i18n),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
settings.downloadFilename = _controller.text;
|
||||
settings.save();
|
||||
Navigator.of(context).pop();
|
||||
settings.downloadFilename = _new;
|
||||
});
|
||||
await settings.save();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -538,7 +585,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Create folders for artist'),
|
||||
title: Text('Create folders for artist'.i18n),
|
||||
leading: Switch(
|
||||
value: settings.artistFolder,
|
||||
onChanged: (v) {
|
||||
|
@ -548,7 +595,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Create folders for albums'),
|
||||
title: Text('Create folders for albums'.i18n),
|
||||
leading: Switch(
|
||||
value: settings.albumFolder,
|
||||
onChanged: (v) {
|
||||
|
@ -558,7 +605,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Separate albums by discs'),
|
||||
title: Text('Separate albums by discs'.i18n),
|
||||
leading: Switch(
|
||||
value: settings.albumDiscFolder,
|
||||
onChanged: (v) {
|
||||
|
@ -568,7 +615,7 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Overwrite already downloaded files'),
|
||||
title: Text('Overwrite already downloaded files'.i18n),
|
||||
leading: Switch(
|
||||
value: settings.overwriteDownload,
|
||||
onChanged: (v) {
|
||||
|
@ -578,40 +625,40 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Copy ARL'),
|
||||
subtitle: Text('Copy userToken/ARL Cookie for use in other apps.'),
|
||||
title: Text('Copy ARL'.i18n),
|
||||
subtitle: Text('Copy userToken/ARL Cookie for use in other apps.'.i18n),
|
||||
leading: Icon(Icons.lock),
|
||||
onTap: () async {
|
||||
await FlutterClipboard.copy(settings.arl);
|
||||
await Fluttertoast.showToast(
|
||||
msg: 'Copied',
|
||||
msg: 'Copied'.i18n,
|
||||
);
|
||||
},
|
||||
),
|
||||
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),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Log out'),
|
||||
content: Text('Due to plugin incompatibility, login using browser is unavailable without restart.'),
|
||||
title: Text('Log out'.i18n),
|
||||
content: Text('Due to plugin incompatibility, login using browser is unavailable without restart.'.i18n),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text('Cancel'),
|
||||
child: Text('Cancel'.i18n),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('(ARL ONLY) Continue'),
|
||||
child: Text('(ARL ONLY) Continue'.i18n),
|
||||
onPressed: () {
|
||||
logOut();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('Log out & Exit'),
|
||||
child: Text('Log out & Exit'.i18n),
|
||||
onPressed: () async {
|
||||
try {AudioService.stop();} catch (e) {}
|
||||
await logOut();
|
||||
|
@ -656,7 +703,7 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Pick-a-Path'),
|
||||
title: Text('Pick-a-Path'.i18n),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.sd_card),
|
||||
|
@ -667,7 +714,7 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text('Select storage'),
|
||||
title: Text('Select storage'.i18n),
|
||||
content: FutureBuilder(
|
||||
future: PathProviderEx.getStorageInfo(),
|
||||
builder: (context, snapshot) {
|
||||
|
@ -736,13 +783,13 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
|
|||
title: Text(_path),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Go up'),
|
||||
title: Text('Go up'.i18n),
|
||||
leading: Icon(Icons.arrow_upward),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (_root == _path) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Permission denied',
|
||||
msg: 'Permission denied'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
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.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.2.0
|
||||
version: 0.4.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
sdk: ">=2.8.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
dio: ^3.0.9
|
||||
dio_cookie_manager: ^1.0.0
|
||||
cookie_jar: ^1.0.1
|
||||
|
@ -60,8 +63,11 @@ dependencies:
|
|||
flutter_cache_manager: ^1.4.1
|
||||
cached_network_image: ^2.2.0+1
|
||||
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:
|
||||
path: ./just_audio
|
||||
|
||||
|
@ -94,6 +100,7 @@ flutter:
|
|||
- assets/cover.jpg
|
||||
- assets/cover_thumb.jpg
|
||||
- assets/icon.png
|
||||
- assets/favorites_thumb.jpg
|
||||
|
||||
fonts:
|
||||
# - family: Montserrat
|
||||
|
|