0.5.4 - Download fixes, C decryptor, gestures in full and small player, sharing, link support

This commit is contained in:
exttex 2020-10-14 21:09:16 +02:00
parent af49aeb974
commit 480800857e
170 changed files with 32703 additions and 293 deletions

View file

@ -676,7 +676,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
//Load tracks from api
void _load() async {
if (playlist.tracks.length < playlist.trackCount && !_loading) {
if (playlist.tracks.length < (playlist.trackCount??playlist.tracks.length) && !_loading) {
setState(() => _loading = true);
int pos = playlist.tracks.length;
//Get another page of tracks
@ -794,7 +794,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
size: 32.0,
),
Container(width: 8.0,),
Text(playlist.trackCount.toString(), style: TextStyle(fontSize: 16),)
Text((playlist.trackCount??playlist.tracks.length).toString(), style: TextStyle(fontSize: 16),)
],
),
Row(

View file

@ -208,9 +208,27 @@ class _LibraryTracksState extends State<LibraryTracks> {
List<Track> tracks = [];
List<Track> allTracks = [];
int trackCount;
SortType _sort = SortType.DEFAULT;
Playlist get _playlist => Playlist(id: deezerAPI.favoritesPlaylistId);
List<Track> get _sorted {
List<Track> tcopy = List.from(tracks);
switch (_sort) {
case SortType.ALPHABETIC:
tcopy.sort((a, b) => a.title.compareTo(b.title));
return tcopy;
case SortType.ARTIST:
tcopy.sort((a, b) => a.artists[0].name.toLowerCase().compareTo(b.artists[0].name.toLowerCase()));
return tcopy;
case SortType.REVERSE:
return tcopy.reversed.toList();
case SortType.DEFAULT:
default:
return tcopy;
}
}
Future _load() async {
//Already loaded
if (trackCount != null && tracks.length >= trackCount) {
@ -273,6 +291,22 @@ class _LibraryTracksState extends State<LibraryTracks> {
}
}
//Load all tracks
Future _loadFull() async {
if (tracks.length < (trackCount??0)) {
Playlist p;
try {
p = await deezerAPI.fullPlaylist(deezerAPI.favoritesPlaylistId);
} catch (e) {}
if (p != null) {
setState(() {
tracks = p.tracks;
trackCount = p.trackCount;
});
}
}
}
Future _loadOffline() async {
Playlist p = await downloadManager.getPlaylist(deezerAPI.favoritesPlaylistId);
if (p != null) setState(() {
@ -280,7 +314,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
});
}
Future _loadAll() async {
Future _loadAllOffline() async {
List tracks = await downloadManager.allOfflineTracks();
setState(() {
allTracks = tracks;
@ -301,10 +335,14 @@ class _LibraryTracksState extends State<LibraryTracks> {
if (_scrollController.position.pixels > off) _load();
});
_load();
_sort = cache.trackSort??SortType.DEFAULT;
//Load all tracks
_loadAll();
_load();
//Load all offline tracks
_loadAllOffline();
if (_sort != SortType.DEFAULT)
_loadFull();
super.initState();
}
@ -312,7 +350,41 @@ class _LibraryTracksState extends State<LibraryTracks> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Tracks'.i18n),),
appBar: AppBar(
title: Text('Tracks'.i18n),
actions: [
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
onSelected: (SortType s) async {
//Preload for sorting
if (tracks.length < (trackCount??0))
await _loadFull();
setState(() => _sort = s);
cache.trackSort = s;
await cache.save();
},
itemBuilder: (context) => <PopupMenuEntry<SortType>>[
PopupMenuItem(
value: SortType.DEFAULT,
child: Text('Default'.i18n),
),
PopupMenuItem(
value: SortType.REVERSE,
child: Text('Reverse'.i18n),
),
PopupMenuItem(
value: SortType.ALPHABETIC,
child: Text('Alphabetic'.i18n),
),
PopupMenuItem(
value: SortType.ARTIST,
child: Text('Artist'.i18n),
),
],
),
],
),
body: ListView(
controller: _scrollController,
children: <Widget>[
@ -352,11 +424,11 @@ class _LibraryTracksState extends State<LibraryTracks> {
),
//Loved tracks
...List.generate(tracks.length, (i) {
Track t = tracks[i];
Track t = (tracks.length == (trackCount??0))?_sorted[i]:tracks[i];
return TrackTile(
t,
onTap: () {
playerHelper.playFromTrackList(tracks, t.id, QueueSource(
playerHelper.playFromTrackList((tracks.length == (trackCount??0))?_sorted:tracks, t.id, QueueSource(
id: deezerAPI.favoritesPlaylistId,
text: 'Favorites'.i18n,
source: 'playlist'
@ -613,6 +685,7 @@ class _LibraryArtistsState extends State<LibraryArtists> {
artists.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
return artists;
}
return artists;
}
//Load data
@ -750,6 +823,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
playlists.sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
return playlists;
}
return playlists;
}
Future _load() async {
@ -862,7 +936,7 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
if (_playlists != null)
...List.generate(_playlists.length, (int i) {
Playlist p = _sorted[i];
Playlist p = (_sorted??[])[i];
return PlaylistTile(
p,
onTap: () => Navigator.of(context).push(MaterialPageRoute(

View file

@ -1,5 +1,3 @@
import 'dart:ffi';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:audio_service/audio_service.dart';
@ -10,6 +8,7 @@ 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 'package:share/share.dart';
import '../api/definitions.dart';
import 'cached_image.dart';
@ -129,6 +128,7 @@ class MenuSheet {
(cache.checkTrackFavorite(track))?removeFavoriteTrack(track, onUpdate: onRemove):addTrackFavorite(track),
addToPlaylist(track),
downloadTrack(track),
shareTile('track', track.id),
showAlbum(track.album),
...List.generate(track.artists.length, (i) => showArtist(track.artists[i])),
...options
@ -297,6 +297,7 @@ class MenuSheet {
album.library?removeAlbum(album, onRemove: onRemove):libraryAlbum(album),
downloadAlbum(album),
offlineAlbum(album),
shareTile('album', album.id),
...options
]);
}
@ -363,6 +364,7 @@ class MenuSheet {
void defaultArtistMenu(Artist artist, {List<Widget> options = const [], Function onRemove}) {
show([
artist.library?removeArtist(artist, onRemove: onRemove):favoriteArtist(artist),
shareTile('artist', artist.id),
...options
]);
}
@ -409,6 +411,7 @@ class MenuSheet {
playlist.library?removePlaylistLibrary(playlist, onRemove: onRemove):addPlaylistLibrary(playlist),
addPlaylistOffline(playlist),
downloadPlaylist(playlist),
shareTile('playlist', playlist.id),
if (playlist.user.id == deezerAPI.userId)
editPlaylist(playlist, onUpdate: onUpdate),
...options
@ -508,6 +511,14 @@ class MenuSheet {
);
}
Widget shareTile(String type, String id) => ListTile(
title: Text('Share'.i18n),
leading: Icon(Icons.share),
onTap: () async {
Share.share('https://deezer.com/$type/$id');
},
);
void _close() => Navigator.of(context).pop();
}

View file

@ -15,55 +15,73 @@ class PlayerBar extends StatelessWidget {
}
double iconSize = 32;
bool _gestureRegistered = false;
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Stream.periodic(Duration(milliseconds: 250)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (AudioService.currentMediaItem == null) return Container(width: 0, height: 0,);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
color: Theme.of(context).bottomAppBarColor,
child: ListTile(
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => PlayerScreen())),
leading: CachedImage(
width: 50,
height: 50,
url: AudioService.currentMediaItem.extras['thumb'] ?? AudioService.currentMediaItem.artUri,
),
title: Text(
AudioService.currentMediaItem.displayTitle,
overflow: TextOverflow.clip,
maxLines: 1,
),
subtitle: Text(
AudioService.currentMediaItem.displaySubtitle,
overflow: TextOverflow.clip,
maxLines: 1,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PrevNextButton(iconSize, prev: true, hidePrev: true,),
PlayPauseButton(iconSize),
PrevNextButton(iconSize)
],
)
),
),
Container(
height: 3.0,
child: LinearProgressIndicator(
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
value: progress,
),
)
],
);
return GestureDetector(
onHorizontalDragUpdate: (details) async {
if (_gestureRegistered) return;
final double sensitivity = 12.69;
//Right swipe
_gestureRegistered = true;
if (details.delta.dx > sensitivity) {
await AudioService.skipToPrevious();
}
//Left
if (details.delta.dx < -sensitivity) {
await AudioService.skipToNext();
}
_gestureRegistered = false;
return;
},
child: StreamBuilder(
stream: Stream.periodic(Duration(milliseconds: 250)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (AudioService.currentMediaItem == null) return Container(width: 0, height: 0,);
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
color: Theme.of(context).bottomAppBarColor,
child: ListTile(
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => PlayerScreen())),
leading: CachedImage(
width: 50,
height: 50,
url: AudioService.currentMediaItem.extras['thumb'] ?? AudioService.currentMediaItem.artUri,
),
title: Text(
AudioService.currentMediaItem.displayTitle,
overflow: TextOverflow.clip,
maxLines: 1,
),
subtitle: Text(
AudioService.currentMediaItem.displaySubtitle,
overflow: TextOverflow.clip,
maxLines: 1,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PrevNextButton(iconSize, prev: true, hidePrev: true,),
PlayPauseButton(iconSize),
PrevNextButton(iconSize)
],
)
),
),
Container(
height: 3.0,
child: LinearProgressIndicator(
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
value: progress,
),
)
],
);
},
),
);
}
}

View file

@ -71,6 +71,9 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
double iconSize = ScreenUtil().setWidth(64);
bool _lyrics = false;
PageController _pageController = PageController(
initialPage: playerHelper.queueIndex,
);
@override
Widget build(BuildContext context) {
@ -79,14 +82,20 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 8),
child: Container(
padding: EdgeInsets.fromLTRB(16, 0, 16, 8),
child: Container(
width: ScreenUtil().setWidth(500),
child: Stack(
children: <Widget>[
CachedImage(
url: AudioService.currentMediaItem.artUri,
fullThumb: true,
PageView(
controller: _pageController,
onPageChanged: (int index) {
AudioService.skipToQueueItem(AudioService.queue[index].id);
},
children: List.generate(AudioService.queue.length, (i) => CachedImage(
url: AudioService.queue[i].artUri,
fullThumb: true,
)),
),
if (_lyrics) LyricsWidget(
artUri: AudioService.currentMediaItem.extras['thumb'],
@ -96,7 +105,7 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
),
],
),
)
),
),
//Right side
SizedBox(
@ -226,6 +235,9 @@ class PlayerScreenVertical extends StatefulWidget {
class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
double iconSize = ScreenUtil().setWidth(100);
bool _lyrics = false;
PageController _pageController = PageController(
initialPage: playerHelper.queueIndex,
);
@override
Widget build(BuildContext context) {
@ -243,9 +255,15 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
height: ScreenUtil().setHeight(1050),
child: Stack(
children: <Widget>[
CachedImage(
url: AudioService.currentMediaItem.artUri,
fullThumb: true,
PageView(
controller: _pageController,
onPageChanged: (int index) {
AudioService.skipToQueueItem(AudioService.queue[index].id);
},
children: List.generate(AudioService.queue.length, (i) => CachedImage(
url: AudioService.queue[i].artUri,
fullThumb: true,
)),
),
if (_lyrics) LyricsWidget(
artUri: AudioService.currentMediaItem.extras['thumb'],
@ -255,7 +273,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
),
],
),
)
),
),
Column(
mainAxisSize: MainAxisSize.min,

View file

@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/download.dart';
import 'package:freezer/ui/downloads_screen.dart';
@ -671,8 +672,31 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
_downloadThreads = settings.downloadThreads.toDouble();
});
await settings.save();
//Prevent null
if (val > 8 && cache.threadsWarning != true) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Warning'.i18n),
content: Text('Using too many concurrent downloads on older/weaker devices might cause crashes!'.i18n),
actions: [
FlatButton(
child: Text('Dismiss'.i18n),
onPressed: () => Navigator.of(context).pop(),
)
],
);
}
);
cache.threadsWarning = true;
await cache.save();
}
}
),
Divider(),
ListTile(
title: Text('Create folders for artist'.i18n),
leading: Container(
@ -699,6 +723,20 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
),
),
),
ListTile(
title: Text('Create folder for playlist'.i18n),
leading: Container(
width: 30.0,
child: Checkbox(
value: settings.playlistFolder,
onChanged: (v) {
setState(() => settings.playlistFolder = v);
settings.save();
},
),
),
),
Divider(),
ListTile(
title: Text('Separate albums by discs'.i18n),
leading: Container(
@ -725,19 +763,6 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
),
),
),
ListTile(
title: Text('Create folder for playlist'.i18n),
leading: Container(
width: 30.0,
child: Checkbox(
value: settings.playlistFolder,
onChanged: (v) {
setState(() => settings.playlistFolder = v);
settings.save();
},
),
),
),
ListTile(
title: Text('Download .LRC lyrics'.i18n),
leading: Container(
@ -751,6 +776,7 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
),
),
),
Divider(),
ListTile(
title: Text('Save cover file for every track'.i18n),
leading: Container(
@ -764,6 +790,20 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
),
),
),
ListTile(
title: Text('Save album cover'.i18n),
leading: Container(
width: 30.0,
child: Checkbox(
value: settings.albumCover,
onChanged: (v) {
setState(() => settings.albumCover = v);
settings.save();
},
),
),
),
Divider(),
ListTile(
title: Text('Download Log'.i18n),
leading: Icon(Icons.sticky_note_2),
@ -1055,7 +1095,8 @@ class _CreditsScreenState extends State<CreditsScreen> {
['Fwwwwwwwwwweze', 'French'],
['kobyrevah', 'Hebrew'],
['HoScHaKaL', 'Turkish'],
['MicroMihai', 'Romanian']
['MicroMihai', 'Romanian'],
['LenteraMalam', 'Indonesian']
];
@override