diff --git a/lib/components/Album/AlbumCard.dart b/lib/components/Album/AlbumCard.dart index 62322289..b804099f 100644 --- a/lib/components/Album/AlbumCard.dart +++ b/lib/components/Album/AlbumCard.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Shared/PlaybuttonCard.dart'; import 'package:spotube/helpers/artist-to-string.dart'; +import 'package:spotube/helpers/simple-track-to-track.dart'; import 'package:spotube/provider/Playback.dart'; +import 'package:spotube/provider/SpotifyDI.dart'; class AlbumCard extends StatelessWidget { final Album album; @@ -12,6 +15,8 @@ class AlbumCard extends StatelessWidget { @override Widget build(BuildContext context) { Playback playback = context.watch(); + bool isPlaylistPlaying = playback.currentPlaylist != null && + playback.currentPlaylist!.id == album.id; return PlaybuttonCard( imageUrl: album.images!.first.url!, @@ -20,8 +25,29 @@ class AlbumCard extends StatelessWidget { title: album.name!, description: "Alubm • ${artistsToString(album.artists ?? [])}", - onTap: () {}, - onPlaybuttonPressed: () => {}, + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) { + return AlbumView(album); + }, + )); + }, + onPlaybuttonPressed: () async { + SpotifyApi spotify = context.read().spotifyApi; + if (isPlaylistPlaying) return; + List tracks = (await spotify.albums.getTracks(album.id!).all()) + .map((track) => simpleTrackToTrack(track, album)) + .toList(); + if (tracks.isEmpty) return; + + playback.setCurrentPlaylist = CurrentPlaylist( + tracks: tracks, + id: album.id!, + name: album.name!, + thumbnail: album.images!.first.url!, + ); + playback.setCurrentTrack = tracks.first; + }, ); } } diff --git a/lib/components/Album/AlbumView.dart b/lib/components/Album/AlbumView.dart new file mode 100644 index 00000000..37019c74 --- /dev/null +++ b/lib/components/Album/AlbumView.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; +import 'package:spotube/components/Shared/TracksTableView.dart'; +import 'package:spotube/helpers/simple-track-to-track.dart'; +import 'package:spotube/provider/Playback.dart'; +import 'package:spotube/provider/SpotifyDI.dart'; + +class AlbumView extends StatelessWidget { + final Album album; + const AlbumView(this.album, {Key? key}) : super(key: key); + + playPlaylist(Playback playback, List tracks, {Track? currentTrack}) { + currentTrack ??= tracks.first; + var isPlaylistPlaying = playback.currentPlaylist?.id == album.id; + if (!isPlaylistPlaying) { + playback.setCurrentPlaylist = CurrentPlaylist( + tracks: tracks, + id: album.id!, + name: album.name!, + thumbnail: album.images!.first.url!, + ); + playback.setCurrentTrack = currentTrack; + } else if (isPlaylistPlaying && + currentTrack.id != null && + currentTrack.id != playback.currentTrack?.id) { + playback.setCurrentTrack = currentTrack; + } + } + + @override + Widget build(BuildContext context) { + Playback playback = context.watch(); + + var isPlaylistPlaying = playback.currentPlaylist?.id == album.id; + SpotifyApi spotify = context.watch().spotifyApi; + return Scaffold( + body: FutureBuilder>( + future: spotify.albums.getTracks(album.id!).all(), + builder: (context, snapshot) { + List tracks = snapshot.data?.map((trackSmp) { + return simpleTrackToTrack(trackSmp, album); + }).toList() ?? + []; + return Column( + children: [ + PageWindowTitleBar( + leading: Row( + children: [ + // nav back + const BackButton(), + // heart playlist + IconButton( + icon: const Icon(Icons.favorite_outline_rounded), + onPressed: () {}, + ), + // play playlist + IconButton( + icon: Icon( + isPlaylistPlaying + ? Icons.stop_rounded + : Icons.play_arrow_rounded, + ), + onPressed: snapshot.hasData + ? () => playPlaylist(playback, tracks) + : null, + ) + ], + ), + ), + Center( + child: Text(album.name!, + style: Theme.of(context).textTheme.headline4), + ), + snapshot.hasError + ? const Center(child: Text("Error occurred")) + : !snapshot.hasData + ? const Expanded( + child: Center( + child: CircularProgressIndicator.adaptive()), + ) + : TracksTableView( + tracks, + onTrackPlayButtonPressed: (currentTrack) => + playPlaylist( + playback, + tracks, + currentTrack: currentTrack, + ), + ), + ], + ); + }), + ); + } +} diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index 1314ab15..7550d968 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -1,6 +1,5 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; -import 'package:spotube/helpers/zero-pad-num-str.dart'; +import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -17,7 +16,8 @@ class PlaylistView extends StatefulWidget { class _PlaylistViewState extends State { playPlaylist(Playback playback, List tracks, {Track? currentTrack}) { currentTrack ??= tracks.first; - var isPlaylistPlaying = playback.currentPlaylist?.id == widget.playlist.id; + var isPlaylistPlaying = playback.currentPlaylist?.id != null && + playback.currentPlaylist?.id == widget.playlist.id; if (!isPlaylistPlaying) { playback.setCurrentPlaylist = CurrentPlaylist( tracks: tracks, @@ -33,111 +33,11 @@ class _PlaylistViewState extends State { } } - List trackToTableRow(Playback playback, List tracks) { - return tracks.asMap().entries.map((track) { - String? thumbnailUrl = (track.value.album?.images?.isNotEmpty ?? false) - ? track.value.album?.images?.last.url - : null; - String duration = - "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; - return (TableRow( - children: [ - TableCell( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - (track.key + 1).toString(), - textAlign: TextAlign.center, - ), - )), - TableCell( - child: Row( - children: [ - IconButton( - icon: Icon( - playback.currentTrack?.id != null && - playback.currentTrack?.id == track.value.id - ? Icons.pause_circle_rounded - : Icons.play_circle_rounded, - color: Theme.of(context).primaryColor, - ), - onPressed: () { - playPlaylist(playback, tracks, currentTrack: track.value); - }, - ), - if (thumbnailUrl != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(5)), - child: CachedNetworkImage( - placeholder: (context, url) { - return Container( - height: 40, - width: 40, - color: Colors.green[300], - ); - }, - imageUrl: thumbnailUrl, - maxHeightDiskCache: 40, - maxWidthDiskCache: 40, - ), - ), - ), - const SizedBox(width: 10), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - track.value.name ?? "", - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 17, - ), - overflow: TextOverflow.ellipsis, - ), - Text( - (track.value.artists ?? []) - .map((e) => e.name) - .join(", "), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ], - ), - ), - TableCell( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - track.value.album?.name ?? "", - overflow: TextOverflow.ellipsis, - ), - ), - ), - TableCell( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - duration, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ), - ), - ) - ], - )); - }).toList(); - } - @override Widget build(BuildContext context) { Playback playback = context.watch(); - var isPlaylistPlaying = - playback.currentPlaylist?.id == this.widget.playlist.id; + var isPlaylistPlaying = playback.currentPlaylist?.id != null && + playback.currentPlaylist?.id == widget.playlist.id; return Consumer(builder: (_, data, __) { return Scaffold( body: FutureBuilder>( @@ -150,8 +50,6 @@ class _PlaylistViewState extends State { .then((tracks) => tracks.map((e) => e.track!)), builder: (context, snapshot) { List tracks = snapshot.data?.toList() ?? []; - TextStyle tableHeadStyle = - const TextStyle(fontWeight: FontWeight.bold, fontSize: 16); return Column( children: [ PageWindowTitleBar( @@ -189,51 +87,13 @@ class _PlaylistViewState extends State { child: Center( child: CircularProgressIndicator.adaptive()), ) - : Expanded( - child: Scrollbar( - child: ListView( - children: [ - SingleChildScrollView( - child: Table( - columnWidths: const { - 0: FixedColumnWidth(40), - 1: FlexColumnWidth(), - 2: FlexColumnWidth(), - 3: FixedColumnWidth(45), - }, - children: [ - TableRow( - children: [ - TableCell( - child: Text( - "#", - textAlign: TextAlign.center, - style: tableHeadStyle, - )), - TableCell( - child: Text( - "Title", - style: tableHeadStyle, - )), - TableCell( - child: Text( - "Album", - style: tableHeadStyle, - )), - TableCell( - child: Text( - "Time", - textAlign: TextAlign.center, - style: tableHeadStyle, - )), - ], - ), - ...trackToTableRow(playback, tracks), - ], - ), - ), - ], - ), + : TracksTableView( + tracks, + onTrackPlayButtonPressed: (currentTrack) => + playPlaylist( + playback, + tracks, + currentTrack: currentTrack, ), ), ], diff --git a/lib/components/Shared/TracksTableView.dart b/lib/components/Shared/TracksTableView.dart new file mode 100644 index 00000000..3e1fc5f9 --- /dev/null +++ b/lib/components/Shared/TracksTableView.dart @@ -0,0 +1,169 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/helpers/zero-pad-num-str.dart'; +import 'package:spotube/provider/Playback.dart'; + +class TracksTableView extends StatelessWidget { + final void Function(Track currentTrack)? onTrackPlayButtonPressed; + final List tracks; + const TracksTableView(this.tracks, {Key? key, this.onTrackPlayButtonPressed}) + : super(key: key); + + List trackToTableRow( + BuildContext context, Playback playback, List tracks) { + return tracks.asMap().entries.map((track) { + String? thumbnailUrl = (track.value.album?.images?.isNotEmpty ?? false) + ? track.value.album?.images?.last.url + : null; + String duration = + "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; + return (TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + (track.key + 1).toString(), + textAlign: TextAlign.center, + ), + )), + TableCell( + child: Row( + children: [ + IconButton( + icon: Icon( + playback.currentTrack?.id != null && + playback.currentTrack?.id == track.value.id + ? Icons.pause_circle_rounded + : Icons.play_circle_rounded, + color: Theme.of(context).primaryColor, + ), + onPressed: () => onTrackPlayButtonPressed?.call( + track.value, + ), + ), + if (thumbnailUrl != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(5)), + child: CachedNetworkImage( + placeholder: (context, url) { + return Container( + height: 40, + width: 40, + color: Colors.green[300], + ); + }, + imageUrl: thumbnailUrl, + maxHeightDiskCache: 40, + maxWidthDiskCache: 40, + ), + ), + ), + const SizedBox(width: 10), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + track.value.name ?? "", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 17, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + (track.value.artists ?? []) + .map((e) => e.name) + .join(", "), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + track.value.album?.name ?? "", + overflow: TextOverflow.ellipsis, + ), + ), + ), + TableCell( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + duration, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ), + ) + ], + )); + }).toList(); + } + + @override + Widget build(BuildContext context) { + Playback playback = context.watch(); + + TextStyle tableHeadStyle = + const TextStyle(fontWeight: FontWeight.bold, fontSize: 16); + return Expanded( + child: Scrollbar( + child: ListView( + children: [ + SingleChildScrollView( + child: Table( + columnWidths: const { + 0: FixedColumnWidth(40), + 1: FlexColumnWidth(), + 2: FlexColumnWidth(), + 3: FixedColumnWidth(45), + }, + children: [ + TableRow( + children: [ + TableCell( + child: Text( + "#", + textAlign: TextAlign.center, + style: tableHeadStyle, + )), + TableCell( + child: Text( + "Title", + style: tableHeadStyle, + )), + TableCell( + child: Text( + "Album", + style: tableHeadStyle, + )), + TableCell( + child: Text( + "Time", + textAlign: TextAlign.center, + style: tableHeadStyle, + )), + ], + ), + ...trackToTableRow(context, playback, tracks), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/helpers/simple-track-to-track.dart b/lib/helpers/simple-track-to-track.dart new file mode 100644 index 00000000..8a6a234a --- /dev/null +++ b/lib/helpers/simple-track-to-track.dart @@ -0,0 +1,23 @@ +import 'package:spotify/spotify.dart'; + +Track simpleTrackToTrack(TrackSimple trackSmp, Album album) { + Track track = Track(); + track.name = trackSmp.name; + track.album = album; + track.artists = trackSmp.artists; + track.availableMarkets = trackSmp.availableMarkets; + track.discNumber = trackSmp.discNumber; + track.durationMs = trackSmp.durationMs; + track.explicit = trackSmp.explicit; + track.externalUrls = trackSmp.externalUrls; + track.href = trackSmp.href; + track.id = trackSmp.id; + track.isPlayable = trackSmp.isPlayable; + track.linkedFrom = trackSmp.linkedFrom; + track.name = trackSmp.name; + track.previewUrl = trackSmp.previewUrl; + track.trackNumber = trackSmp.trackNumber; + track.type = trackSmp.type; + track.uri = trackSmp.uri; + return track; +}