0.5.3 - Download fixes, shuffle fix, sorting in library

This commit is contained in:
exttex 2020-10-12 22:49:13 +02:00
parent 952cf0f508
commit 2f471268c6
18 changed files with 556 additions and 167 deletions

View file

@ -664,7 +664,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
tracks.sort((a, b) => a.title.compareTo(b.title));
return tracks;
case SortType.ARTIST:
tracks.sort((a, b) => a.artists[0].name.compareTo(b.artists[0].name));
tracks.sort((a, b) => a.artists[0].name.toLowerCase().compareTo(b.artists[0].name.toLowerCase()));
return tracks;
case SortType.REVERSE:
return tracks.reversed.toList();

View file

@ -419,6 +419,13 @@ class _LibraryTracksState extends State<LibraryTracks> {
}
enum AlbumSortType {
DEFAULT,
REVERSE,
ALPHABETIC,
ARTIST
}
class LibraryAlbums extends StatefulWidget {
@override
_LibraryAlbumsState createState() => _LibraryAlbumsState();
@ -427,6 +434,25 @@ class LibraryAlbums extends StatefulWidget {
class _LibraryAlbumsState extends State<LibraryAlbums> {
List<Album> _albums;
AlbumSortType _sort = AlbumSortType.DEFAULT;
List<Album> get _sorted {
List<Album> albums = List.from(_albums);
switch (_sort) {
case AlbumSortType.DEFAULT:
return _albums;
case AlbumSortType.REVERSE:
return _albums.reversed.toList();
case AlbumSortType.ALPHABETIC:
albums.sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
return albums;
case AlbumSortType.ARTIST:
albums.sort((a, b) => a.artists[0].name.toLowerCase().compareTo(b.artists[0].name.toLowerCase()));
return albums;
}
return albums;
}
Future _load() async {
if (settings.offlineMode) return;
@ -439,13 +465,44 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
@override
void initState() {
_load();
_sort = cache.albumSort??AlbumSortType.DEFAULT;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Albums'.i18n),),
appBar: AppBar(
title: Text('Albums'.i18n),
actions: [
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
onSelected: (AlbumSortType s) async {
setState(() => _sort = s);
cache.albumSort = s;
await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<AlbumSortType>>[
PopupMenuItem(
value: AlbumSortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: AlbumSortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: AlbumSortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
PopupMenuItem(
value: AlbumSortType.ARTIST,
child: Text('Artist'.i18n),
),
],
),
],
),
body: ListView(
children: <Widget>[
Container(height: 8.0,),
@ -459,7 +516,7 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
if (_albums != null)
...List.generate(_albums.length, (int i) {
Album a = _albums[i];
Album a = _sorted[i];
return AlbumTile(
a,
onTap: () {
@ -523,50 +580,148 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
}
}
enum ArtistSortType {
DEFAULT,
REVERSE,
POPULARITY,
ALPHABETIC
}
class LibraryArtists extends StatefulWidget {
@override
_LibraryArtistsState createState() => _LibraryArtistsState();
}
class _LibraryArtistsState extends State<LibraryArtists> {
List<Artist> _artists;
ArtistSortType _sort = ArtistSortType.DEFAULT;
bool _loading = true;
bool _error = false;
List<Artist> get _sorted {
List<Artist> artists = List.from(_artists);
switch (_sort) {
case ArtistSortType.DEFAULT:
return _artists;
case ArtistSortType.REVERSE:
return _artists.reversed.toList();
case ArtistSortType.POPULARITY:
artists.sort((a, b) => b.fans - a.fans);
return artists;
case ArtistSortType.ALPHABETIC:
artists.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
return artists;
}
}
//Load data
Future _load() async {
setState(() => _loading = true);
//Fetch
List<Artist> data;
try {
data = await deezerAPI.getArtists();
} catch (e) {}
//Update UI
setState(() {
if (data != null) {
_artists = data;
} else {
_error = true;
}
_loading = false;
});
}
@override
void initState() {
_sort = cache.artistSort;
_load();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Artists'.i18n),),
body: FutureBuilder(
future: deezerAPI.getArtists(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) return ErrorScreen();
if (!snapshot.hasData) return Center(child: CircularProgressIndicator(),);
return ListView(
children: <Widget>[
...List.generate(snapshot.data.length, (i) {
Artist a = snapshot.data[i];
return ArtistHorizontalTile(
a,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ArtistDetails(a))
);
},
onHold: () {
MenuSheet m = MenuSheet(context);
m.defaultArtistMenu(a, onRemove: () {
setState(() => {});
});
},
);
}),
appBar: AppBar(
title: Text('Artists'.i18n),
actions: [
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
onSelected: (ArtistSortType s) async {
setState(() => _sort = s);
cache.artistSort = s;
await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<ArtistSortType>>[
PopupMenuItem(
value: ArtistSortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: ArtistSortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: ArtistSortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
PopupMenuItem(
value: ArtistSortType.POPULARITY,
child: Text('Popularity'.i18n),
),
],
);
},
)
],
),
body: ListView(
children: <Widget>[
if (_loading)
Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [CircularProgressIndicator()],
),
),
if (_error)
Center(child: ErrorScreen()),
if (!_loading && !_error)
...List.generate(_artists.length, (i) {
Artist a = _sorted[i];
return ArtistHorizontalTile(
a,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ArtistDetails(a))
);
},
onHold: () {
MenuSheet m = MenuSheet(context);
m.defaultArtistMenu(a, onRemove: () {
setState(() {
_artists.remove(a);
});
});
},
);
}),
],
),
);
}
}
enum PlaylistSortType {
DEFAULT,
REVERSE,
ALPHABETIC,
USER,
TRACK_COUNT
}
class LibraryPlaylists extends StatefulWidget {
@override
@ -576,6 +731,26 @@ class LibraryPlaylists extends StatefulWidget {
class _LibraryPlaylistsState extends State<LibraryPlaylists> {
List<Playlist> _playlists;
PlaylistSortType _sort = PlaylistSortType.DEFAULT;
List<Playlist> get _sorted {
List<Playlist> playlists = List.from(_playlists);
switch (_sort) {
case PlaylistSortType.DEFAULT:
return _playlists;
case PlaylistSortType.REVERSE:
return _playlists.reversed.toList();
case PlaylistSortType.USER:
playlists.sort((a, b) => (a.user.name??deezerAPI.userName).toLowerCase().compareTo((b.user.name??deezerAPI.userName).toLowerCase()));
return playlists;
case PlaylistSortType.TRACK_COUNT:
playlists.sort((a, b) => b.trackCount - a.trackCount);
return playlists;
case PlaylistSortType.ALPHABETIC:
playlists.sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
return playlists;
}
}
Future _load() async {
if (!settings.offlineMode) {
@ -588,6 +763,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
@override
void initState() {
_sort = cache.libraryPlaylistSort;
_load();
super.initState();
}
@ -606,7 +782,41 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Playlists'.i18n),),
appBar: AppBar(
title: Text('Playlists'.i18n),
actions: [
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
onSelected: (PlaylistSortType s) async {
setState(() => _sort = s);
cache.libraryPlaylistSort = s;
await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<PlaylistSortType>>[
PopupMenuItem(
value: PlaylistSortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.USER,
child: Text('User'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.TRACK_COUNT,
child: Text('Track count'.i18n),
),
PopupMenuItem(
value: PlaylistSortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
],
)
],
),
body: ListView(
children: <Widget>[
ListTile(
@ -652,7 +862,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
if (_playlists != null)
...List.generate(_playlists.length, (int i) {
Playlist p = _playlists[i];
Playlist p = _sorted[i];
return PlaylistTile(
p,
onTap: () => Navigator.of(context).push(MaterialPageRoute(

View file

@ -90,15 +90,19 @@ class _LoginWidgetState extends State<LoginWidget> {
//Try logging in
try {
deezerAPI.arl = settings.arl;
bool resp = await deezerAPI.rawAuthorize(onError: (e) => _error = e.toString());
bool resp = await deezerAPI.rawAuthorize(onError: (e) => setState(() => _error = e.toString()));
if (resp == false) { //false, not null
if (settings.arl.length != 192) {
if (_error == null) _error = '';
_error += 'Invalid ARL length!';
}
setState(() => settings.arl = null);
errorDialog();
}
//On error show dialog and reset to null
} catch (e) {
_error = e;
print('Login error: ' + e);
_error = e.toString();
print('Login error: ' + e.toString());
setState(() => settings.arl = null);
errorDialog();
}

View file

@ -498,7 +498,7 @@ class _DeezerSettingsState extends State<DeezerSettings> {
ListTile(
title: Text('Proxy'.i18n),
leading: Icon(Icons.vpn_key),
subtitle: Text(settings.proxyAddress??'Not set'),
subtitle: Text(settings.proxyAddress??'Not set'.i18n),
onTap: () {
String _new;
showDialog(
@ -606,7 +606,8 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
),
Container(height: 8.0),
Text(
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%, %date%',
'Valid variables are'.i18n + ': %artists%, %artist%, %title%, %album%, %trackNumber%, %0trackNumber%, %feats%, %playlistTrackNumber%, %0playlistTrackNumber%, %year%, %date%\n\n' +
"If you want to use custom directory naming - use '/' as directory separator.".i18n,
style: TextStyle(
fontSize: 12.0,
),
@ -658,8 +659,8 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
),
Slider(
min: 1,
max: 6,
divisions: 5,
max: 16,
divisions: 15,
value: _downloadThreads,
label: _downloadThreads.round().toString(),
onChanged: (double v) => setState(() => _downloadThreads = v),
@ -1040,15 +1041,6 @@ class _CreditsScreenState extends State<CreditsScreen> {
String _version = '';
//Title, Subtitle, URL
static final List<List<String>> credits = [
['exttex', 'Developer'],
['Bas Curtiz', 'Icon, logo, banner, design suggestions, tester'],
['Deemix', 'Better app <3', 'https://codeberg.org/RemixDev/deemix'],
['Tobs, Xandar Null, Francesco', 'Beta testers'],
['Annexhack', 'Android Auto help']
];
static final List<List<String>> translators = [
['Xandar Null', 'Arabic'],
['Markus', 'German'],
@ -1111,22 +1103,51 @@ class _CreditsScreenState extends State<CreditsScreen> {
),
ListTile(
title: Text('Repository'.i18n),
subtitle: Text('Source code, report issues there.'),
subtitle: Text('Source code, report issues there.'.i18n),
leading: Icon(Icons.code, color: Colors.green, size: 36.0),
onTap: () {
launch('https://notabug.org/exttex/freezer');
},
),
Divider(),
...List.generate(credits.length, (i) => ListTile(
title: Text(credits[i][0]),
subtitle: Text(credits[i][1]),
ListTile(
title: Text('exttex'),
subtitle: Text('Developer'),
),
ListTile(
title: Text('Bas Curtiz'),
subtitle: Text('Icon, logo, banner, design suggestions, tester'),
),
ListTile(
title: Text('Tobs'),
subtitle: Text('Alpha testers'),
),
ListTile(
title: Text('Deemix'),
subtitle: Text('Better app <3'),
onTap: () {
if (credits[i].length >= 3) {
launch(credits[i][2]);
}
launch('https://codeberg.org/RemixDev/deemix');
},
)),
),
ListTile(
title: Text('Xandar Null'),
subtitle: Text('Tester, translations help'),
),
ListTile(
title: Text('Francesco'),
subtitle: Text('Tester'),
onTap: () {
setState(() {
settings.primaryColor = Color(0xff333333);
});
updateTheme();
settings.save();
},
),
ListTile(
title: Text('Annexhack'),
subtitle: Text('Android Auto help'),
),
Divider(),
...List.generate(translators.length, (i) => ListTile(
title: Text(translators[i][0]),

View file

@ -205,18 +205,21 @@ class ArtistHorizontalTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
artist.name,
maxLines: 1,
return Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
child: ListTile(
title: Text(
artist.name,
maxLines: 1,
),
leading: CachedImage(
url: artist.picture.thumb,
circular: true,
),
onTap: onTap,
onLongPress: onHold,
trailing: trailing,
),
leading: CachedImage(
url: artist.picture.thumb,
circular: true,
),
onTap: onTap,
onLongPress: onHold,
trailing: trailing,
);
}
}