0.4.0 - translations, download fallback, android auto, radio, infinite flow, bugfixes
This commit is contained in:
parent
a5381f0fed
commit
e984621eeb
88 changed files with 2911 additions and 379 deletions
216
lib/ui/android_auto.dart
Normal file
216
lib/ui/android_auto.dart
Normal file
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue