0.5.4 - Download fixes, C decryptor, gestures in full and small player, sharing, link support
This commit is contained in:
parent
af49aeb974
commit
480800857e
170 changed files with 32703 additions and 293 deletions
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue