Initial commit
This commit is contained in:
commit
ed087bc583
123 changed files with 10390 additions and 0 deletions
697
lib/ui/details_screens.dart
Normal file
697
lib/ui/details_screens.dart
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:freezer/api/deezer.dart';
|
||||
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 '../api/definitions.dart';
|
||||
import 'player_bar.dart';
|
||||
import 'cached_image.dart';
|
||||
import 'tiles.dart';
|
||||
import 'menu.dart';
|
||||
|
||||
class AlbumDetails extends StatelessWidget {
|
||||
|
||||
Album album;
|
||||
|
||||
AlbumDetails(this.album);
|
||||
|
||||
Future _loadAlbum() async {
|
||||
//Get album from API, if doesn't have tracks
|
||||
if (this.album.tracks == null || this.album.tracks.length == 0) {
|
||||
this.album = await deezerAPI.album(album.id);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: FutureBuilder(
|
||||
future: _loadAlbum(),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
|
||||
//Wait for data
|
||||
if (snapshot.connectionState != ConnectionState.done) return Center(child: CircularProgressIndicator(),);
|
||||
//On error
|
||||
if (snapshot.hasError) return ErrorScreen();
|
||||
|
||||
return ListView(
|
||||
children: <Widget>[
|
||||
//Album art, title, artists
|
||||
Card(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(height: 8.0,),
|
||||
CachedImage(
|
||||
url: album.art.full,
|
||||
height: 256.0,
|
||||
),
|
||||
Container(height: 8,),
|
||||
Text(
|
||||
album.title,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
Text(
|
||||
album.artistString,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Theme.of(context).primaryColor
|
||||
),
|
||||
),
|
||||
Container(height: 8.0,),
|
||||
],
|
||||
),
|
||||
),
|
||||
//Details
|
||||
Card(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.audiotrack, size: 32.0,),
|
||||
Container(width: 8.0, height: 42.0,), //Height to adjust card height
|
||||
Text(
|
||||
album.tracks.length.toString(),
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.timelapse, size: 32.0,),
|
||||
Container(width: 8.0,),
|
||||
Text(
|
||||
album.durationString,
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.people, size: 32.0,),
|
||||
Container(width: 8.0,),
|
||||
Text(
|
||||
album.fansString,
|
||||
style: TextStyle(fontSize: 16.0),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
//Options (offline, download...)
|
||||
Card(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.favorite, size: 32),
|
||||
Container(width: 4,),
|
||||
Text('Library')
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
await deezerAPI.addFavoriteAlbum(album.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
},
|
||||
),
|
||||
MakeAlbumOffline(album: album),
|
||||
FlatButton(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.file_download, size: 32.0,),
|
||||
Container(width: 4,),
|
||||
Text('Download')
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
downloadManager.addOfflineAlbum(album, private: false);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
...List.generate(album.tracks.length, (i) {
|
||||
Track t = album.tracks[i];
|
||||
return TrackTile(
|
||||
t,
|
||||
onTap: () {
|
||||
playerHelper.playFromAlbum(album, t.id);
|
||||
},
|
||||
onHold: () {
|
||||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultTrackMenu(t);
|
||||
}
|
||||
);
|
||||
})
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MakeAlbumOffline extends StatefulWidget {
|
||||
|
||||
Album album;
|
||||
MakeAlbumOffline({Key key, this.album}): super(key: key);
|
||||
|
||||
@override
|
||||
_MakeAlbumOfflineState createState() => _MakeAlbumOfflineState();
|
||||
}
|
||||
|
||||
class _MakeAlbumOfflineState extends State<MakeAlbumOffline> {
|
||||
|
||||
bool _offline = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
downloadManager.checkOffline(album: widget.album).then((v) {
|
||||
setState(() {
|
||||
_offline = v;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Switch(
|
||||
value: _offline,
|
||||
onChanged: (v) async {
|
||||
if (v) {
|
||||
//Add to offline
|
||||
await deezerAPI.addFavoriteAlbum(widget.album.id);
|
||||
downloadManager.addOfflineAlbum(widget.album, private: true);
|
||||
setState(() {
|
||||
_offline = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
downloadManager.removeOfflineAlbum(widget.album.id);
|
||||
setState(() {
|
||||
_offline = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
Container(width: 4.0,),
|
||||
Text(
|
||||
'Offline',
|
||||
style: TextStyle(fontSize: 16),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ArtistDetails extends StatelessWidget {
|
||||
|
||||
Artist artist;
|
||||
ArtistDetails(this.artist);
|
||||
|
||||
Future _loadArtist() async {
|
||||
//Load artist from api if no albums
|
||||
if ((this.artist.albums??[]).length == 0) {
|
||||
this.artist = await deezerAPI.artist(artist.id);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: FutureBuilder(
|
||||
future: _loadArtist(),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
//Error / not done
|
||||
if (snapshot.hasError) return ErrorScreen();
|
||||
if (snapshot.connectionState != ConnectionState.done) return Center(child: CircularProgressIndicator(),);
|
||||
|
||||
return ListView(
|
||||
children: <Widget>[
|
||||
Card(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
CachedImage(
|
||||
url: artist.picture.full,
|
||||
height: 200,
|
||||
),
|
||||
Container(
|
||||
width: 200.0,
|
||||
height: 220,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
artist.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 4,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Container(
|
||||
height: 8.0,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.people,
|
||||
size: 32.0,
|
||||
),
|
||||
Container(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
artist.fansString,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
height: 4.0,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(Icons.album, size: 32.0),
|
||||
Container(
|
||||
width: 8.0,
|
||||
),
|
||||
Text(
|
||||
artist.albumCount.toString(),
|
||||
style: TextStyle(fontSize: 16),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(height: 4.0,),
|
||||
Card(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.favorite, size: 32),
|
||||
Container(width: 4,),
|
||||
Text('Library')
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
await deezerAPI.addFavoriteArtist(artist.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(height: 16.0,),
|
||||
//Top tracks
|
||||
Text(
|
||||
'Top Tracks',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0
|
||||
),
|
||||
),
|
||||
Container(height: 4.0),
|
||||
...List.generate(5, (i) {
|
||||
if (artist.topTracks.length <= i) return Container(height: 0, width: 0,);
|
||||
Track t = artist.topTracks[i];
|
||||
return TrackTile(
|
||||
t,
|
||||
onTap: () {
|
||||
playerHelper.playFromTopTracks(
|
||||
artist.topTracks,
|
||||
t.id,
|
||||
artist
|
||||
);
|
||||
},
|
||||
onHold: () {
|
||||
MenuSheet mi = MenuSheet(context);
|
||||
mi.defaultTrackMenu(t);
|
||||
},
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show more tracks'),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => TrackListScreen(artist.topTracks, QueueSource(
|
||||
id: artist.id,
|
||||
text: 'Top ${artist.name}',
|
||||
source: 'topTracks'
|
||||
)))
|
||||
);
|
||||
}
|
||||
),
|
||||
Divider(),
|
||||
//Albums
|
||||
Text(
|
||||
'Top Albums',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0
|
||||
),
|
||||
),
|
||||
...List.generate(artist.albums.length, (i) {
|
||||
Album a = artist.albums[i];
|
||||
return AlbumTile(
|
||||
a,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => AlbumDetails(a))
|
||||
);
|
||||
},
|
||||
onHold: () {
|
||||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultAlbumMenu(
|
||||
a
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PlaylistDetails extends StatefulWidget {
|
||||
|
||||
Playlist playlist;
|
||||
PlaylistDetails(this.playlist, {Key key}): super(key: key);
|
||||
|
||||
@override
|
||||
_PlaylistDetailsState createState() => _PlaylistDetailsState();
|
||||
}
|
||||
|
||||
class _PlaylistDetailsState extends State<PlaylistDetails> {
|
||||
|
||||
Playlist playlist;
|
||||
bool _loading = false;
|
||||
bool _error = false;
|
||||
ScrollController _scrollController = ScrollController();
|
||||
|
||||
//Load tracks from api
|
||||
void _load() async {
|
||||
if (playlist.tracks.length < playlist.trackCount && !_loading) {
|
||||
setState(() => _loading = true);
|
||||
int pos = playlist.tracks.length;
|
||||
//Get another page of tracks
|
||||
List<Track> tracks;
|
||||
try {
|
||||
tracks = await deezerAPI.playlistTracksPage(playlist.id, pos);
|
||||
} catch (e) {
|
||||
setState(() => _error = true);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
playlist.tracks.addAll(tracks);
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
playlist = widget.playlist;
|
||||
//If scrolled past 90% load next tracks
|
||||
_scrollController.addListener(() {
|
||||
double off = _scrollController.position.maxScrollExtent * 0.90;
|
||||
if (_scrollController.position.pixels > off) {
|
||||
_load();
|
||||
}
|
||||
});
|
||||
//Load if no tracks
|
||||
if (playlist.tracks.length == 0) {
|
||||
//Get correct metadata
|
||||
deezerAPI.playlist(playlist.id)
|
||||
.catchError((e) => setState(() => _error = true))
|
||||
.then((Playlist p) {
|
||||
if (p == null) return;
|
||||
setState(() {
|
||||
playlist = p;
|
||||
});
|
||||
//Load tracks
|
||||
_load();
|
||||
});
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ListView(
|
||||
controller: _scrollController,
|
||||
children: <Widget>[
|
||||
Container(height: 4.0,),
|
||||
Card(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
CachedImage(
|
||||
url: playlist.image.full,
|
||||
height: 180.0,
|
||||
),
|
||||
Container(
|
||||
width: 180,
|
||||
height: 200, //Card padding
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
playlist.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
Text(
|
||||
playlist.user.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontSize: 18.0
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 8.0,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.audiotrack,
|
||||
size: 32.0,
|
||||
),
|
||||
Container(width: 8.0,),
|
||||
Text(playlist.trackCount.toString(), style: TextStyle(fontSize: 16),)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.timelapse,
|
||||
size: 32.0,
|
||||
),
|
||||
Container(width: 8.0,),
|
||||
Text(playlist.durationString, style: TextStyle(fontSize: 16),)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(height: 4.0,),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
playlist.description ?? '',
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
Card(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
FlatButton(
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(Icons.favorite, size: 32),
|
||||
Container(width: 4,),
|
||||
Text('Library')
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
await deezerAPI.addFavoriteAlbum(playlist.id);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Added to library',
|
||||
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')
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
downloadManager.addOfflinePlaylist(playlist, private: false);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
...List.generate(playlist.tracks.length, (i) {
|
||||
Track t = playlist.tracks[i];
|
||||
return TrackTile(
|
||||
t,
|
||||
onTap: () {
|
||||
playerHelper.playFromPlaylist(playlist, t.id);
|
||||
},
|
||||
onHold: () {
|
||||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultTrackMenu(t, options: [
|
||||
(playlist.user.id == deezerAPI.userId) ? m.removeFromPlaylist(t, playlist) : Container(width: 0, height: 0,)
|
||||
]);
|
||||
}
|
||||
);
|
||||
}),
|
||||
if (_loading)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
CircularProgressIndicator()
|
||||
],
|
||||
),
|
||||
if (_error)
|
||||
ErrorScreen()
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MakePlaylistOffline extends StatefulWidget {
|
||||
Playlist playlist;
|
||||
MakePlaylistOffline(this.playlist, {Key key}): super(key: key);
|
||||
|
||||
@override
|
||||
_MakePlaylistOfflineState createState() => _MakePlaylistOfflineState();
|
||||
}
|
||||
|
||||
class _MakePlaylistOfflineState extends State<MakePlaylistOffline> {
|
||||
bool _offline = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
downloadManager.checkOffline(playlist: widget.playlist).then((v) {
|
||||
setState(() {
|
||||
_offline = v;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Switch(
|
||||
value: _offline,
|
||||
onChanged: (v) async {
|
||||
if (v) {
|
||||
//Add to offline
|
||||
if (widget.playlist.user != null && widget.playlist.user.id != deezerAPI.userId)
|
||||
await deezerAPI.addPlaylist(widget.playlist.id);
|
||||
downloadManager.addOfflinePlaylist(widget.playlist, private: true);
|
||||
setState(() {
|
||||
_offline = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
downloadManager.removeOfflinePlaylist(widget.playlist.id);
|
||||
setState(() {
|
||||
_offline = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
Container(width: 4.0,),
|
||||
Text(
|
||||
'Offline',
|
||||
style: TextStyle(fontSize: 16),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue