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: [ //Album art, title, artists Card( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container(height: 8.0,), CachedImage( url: album.art.full, width: MediaQuery.of(context).size.width / 2 ), 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: [ Row( children: [ 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: [ Icon(Icons.timelapse, size: 32.0,), Container(width: 8.0,), Text( album.durationString, style: TextStyle(fontSize: 16.0), ) ], ), Row( children: [ 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: [ FlatButton( child: Row( children: [ 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: [ 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 { bool _offline = false; @override void initState() { downloadManager.checkOffline(album: widget.album).then((v) { setState(() { _offline = v; }); }); } @override Widget build(BuildContext context) { return Row( children: [ 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: [ Card( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CachedImage( url: artist.picture.full, width: MediaQuery.of(context).size.width / 2 - 8, ), Container( width: MediaQuery.of(context).size.width / 2 - 8, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ 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: [ 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: [ 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: [ FlatButton( child: Row( children: [ 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 > 10 ? 11 : artist.albums.length + 1, (i) { //Show discography if (i == 10 || i == artist.albums.length) { return ListTile( title: Text('Show all albums'), onTap: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => DiscographyScreen(artist: artist,)) ); } ); } //Top albums 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 DiscographyScreen extends StatefulWidget { Artist artist; DiscographyScreen({@required this.artist, Key key}): super(key: key); @override _DiscographyScreenState createState() => _DiscographyScreenState(); } class _DiscographyScreenState extends State { Artist artist; bool _loading = false; bool _error = false; ScrollController _scrollController = ScrollController(); Future _load() async { if (artist.albums.length >= artist.albumCount || _loading) return; setState(() => _loading = true); //Fetch data List data; try { data = await deezerAPI.discographyPage(artist.id, start: artist.albums.length); } catch (e) { setState(() { _error = true; _loading = false; }); return; } //Save setState(() { artist.albums.addAll(data); _loading = false; }); } @override void initState() { artist = widget.artist; //Lazy loading scroll _scrollController.addListener(() { double off = _scrollController.position.maxScrollExtent * 0.90; if (_scrollController.position.pixels > off) { _load(); } }); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Discography'),), body: ListView.builder( controller: _scrollController, itemCount: artist.albums.length + 1, itemBuilder: (context, i) { //Loading if (i == artist.albums.length) { if (_loading) return Row( mainAxisAlignment: MainAxisAlignment.center, children: [CircularProgressIndicator()], ); //Error if (_error) return ErrorScreen(); //Success return Container(width: 0, height: 0,); } 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 { 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 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: [ Container(height: 4.0,), Card( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.max, children: [ CachedImage( url: playlist.image.full, height: MediaQuery.of(context).size.width / 2 - 8, ), Container( width: MediaQuery.of(context).size.width / 2 - 8, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ 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: [ Icon( Icons.audiotrack, size: 32.0, ), Container(width: 8.0,), Text(playlist.trackCount.toString(), style: TextStyle(fontSize: 16),) ], ), Row( mainAxisSize: MainAxisSize.min, children: [ 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: [ FlatButton( child: Row( children: [ 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: [ 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: [ 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 { bool _offline = false; @override void initState() { downloadManager.checkOffline(playlist: widget.playlist).then((v) { setState(() { _offline = v; }); }); } @override Widget build(BuildContext context) { return Row( children: [ 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), ) ], ); } }