diff --git a/lib/components/Album/AlbumCard.dart b/lib/components/Album/AlbumCard.dart index b804099f..7849d13f 100644 --- a/lib/components/Album/AlbumCard.dart +++ b/lib/components/Album/AlbumCard.dart @@ -4,6 +4,7 @@ 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/image-to-url-string.dart'; import 'package:spotube/helpers/simple-track-to-track.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -19,7 +20,7 @@ class AlbumCard extends StatelessWidget { playback.currentPlaylist!.id == album.id; return PlaybuttonCard( - imageUrl: album.images!.first.url!, + imageUrl: imageToUrlString(album.images), isPlaying: playback.currentPlaylist?.id != null && playback.currentPlaylist?.id == album.id, title: album.name!, diff --git a/lib/components/Album/AlbumView.dart b/lib/components/Album/AlbumView.dart index 1f13ffc0..88f74b7e 100644 --- a/lib/components/Album/AlbumView.dart +++ b/lib/components/Album/AlbumView.dart @@ -3,6 +3,7 @@ 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/image-to-url-string.dart'; import 'package:spotube/helpers/simple-track-to-track.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -19,7 +20,7 @@ class AlbumView extends StatelessWidget { tracks: tracks, id: album.id!, name: album.name!, - thumbnail: album.images!.first.url!, + thumbnail: imageToUrlString(album.images), ); playback.setCurrentTrack = currentTrack; } else if (isPlaylistPlaying && diff --git a/lib/components/Artist/ArtistProfile.dart b/lib/components/Artist/ArtistProfile.dart index 3e589617..5eb839c2 100644 --- a/lib/components/Artist/ArtistProfile.dart +++ b/lib/components/Artist/ArtistProfile.dart @@ -8,6 +8,7 @@ import 'package:spotube/components/Artist/ArtistAlbumView.dart'; import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/readable-number.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/provider/Playback.dart'; @@ -47,7 +48,7 @@ class _ArtistProfileState extends State { CircleAvatar( radius: MediaQuery.of(context).size.width * 0.18, backgroundImage: CachedNetworkImageProvider( - snapshot.data!.images!.first.url!, + imageToUrlString(snapshot.data!.images), ), ), Flexible( @@ -143,7 +144,7 @@ class _ArtistProfileState extends State { tracks: tracks, id: snapshot.data!.id!, name: "${snapshot.data!.name!} To Tracks", - thumbnail: snapshot.data!.images!.first.url!, + thumbnail: imageToUrlString(snapshot.data?.images), ); playback.setCurrentTrack = currentTrack; } else if (isPlaylistPlaying && @@ -186,10 +187,11 @@ class _ArtistProfileState extends State { .map((track) { String duration = "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; - String? thumbnailUrl = track.value.album != null && - track.value.album!.images!.isNotEmpty - ? track.value.album!.images!.last.url! - : null; + String? thumbnailUrl = imageToUrlString( + track.value.album?.images, + index: + (track.value.album?.images?.length ?? 1) - + 1); return TracksTableView.buildTrackTile( context, playback, diff --git a/lib/components/Catergory/CategoryCard.dart b/lib/components/Category/CategoryCard.dart similarity index 100% rename from lib/components/Catergory/CategoryCard.dart rename to lib/components/Category/CategoryCard.dart diff --git a/lib/components/Home.dart b/lib/components/Home.dart index 949fbee3..0f89927e 100644 --- a/lib/components/Home.dart +++ b/lib/components/Home.dart @@ -5,14 +5,16 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:oauth2/oauth2.dart' show AuthorizationException; -import 'package:spotify/spotify.dart' hide Image, Player; -import 'package:spotube/components/Catergory/CategoryCard.dart'; +import 'package:spotify/spotify.dart' hide Image, Player, Search; +import 'package:spotube/components/Category/CategoryCard.dart'; import 'package:spotube/components/Login.dart'; import 'package:spotube/components/Lyrics.dart'; +import 'package:spotube/components/Search/Search.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Player/Player.dart'; import 'package:spotube/components/Settings.dart'; import 'package:spotube/components/Library/UserLibrary.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/oauth-login.dart'; import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/sideBarTiles.dart'; @@ -200,11 +202,8 @@ class _HomeState extends State { return FutureBuilder( future: data.spotifyApi.me.get(), builder: (context, snapshot) { - var avatarImg = ((snapshot.data?.images?.isNotEmpty ?? - false) && - snapshot.data?.images?.last.url != null) - ? snapshot.data!.images!.last.url! - : "https://avatars.dicebear.com/api/adventurer/${snapshot.data?.id}.png"; + var avatarImg = imageToUrlString(snapshot.data?.images, + index: (snapshot.data?.images?.length ?? 1) - 1); return Padding( padding: const EdgeInsets.all(16), child: Row( @@ -257,6 +256,7 @@ class _HomeState extends State { ), ), ), + if (_selectedIndex == 1) const Search(), if (_selectedIndex == 2) const UserLibrary(), if (_selectedIndex == 3) const Lyrics(), ], diff --git a/lib/components/Player/Player.dart b/lib/components/Player/Player.dart index b27b5d4a..2bb8fa97 100644 --- a/lib/components/Player/Player.dart +++ b/lib/components/Player/Player.dart @@ -10,6 +10,7 @@ import 'package:spotube/components/Player/PlayerControls.dart'; import 'package:spotube/components/Shared/LinkText.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/artists-to-clickable-artists.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/models/GlobalKeyActions.dart'; import 'package:spotube/provider/Playback.dart'; @@ -211,10 +212,10 @@ class _PlayerState extends State with WidgetsBindingObserver { _playTrack(playback.currentTrack!, playback); } - String? albumArt = - (playback.currentTrack?.album?.images?.isNotEmpty ?? false) - ? playback.currentTrack?.album?.images?.last.url - : null; + String? albumArt = imageToUrlString( + playback.currentTrack?.album?.images, + index: (playback.currentTrack?.album?.images?.length ?? 1) - 1, + ); return Material( type: MaterialType.transparency, diff --git a/lib/components/Playlist/PlaylistCard.dart b/lib/components/Playlist/PlaylistCard.dart index 28819e9a..c79052dc 100644 --- a/lib/components/Playlist/PlaylistCard.dart +++ b/lib/components/Playlist/PlaylistCard.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Playlist/PlaylistView.dart'; import 'package:spotube/components/Shared/PlaybuttonCard.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -49,7 +50,7 @@ class _PlaylistCardState extends State { tracks: tracks, id: widget.playlist.id!, name: widget.playlist.name!, - thumbnail: widget.playlist.images!.first.url!, + thumbnail: imageToUrlString(widget.playlist.images), ); playback.setCurrentTrack = tracks.first; }, diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index 7550d968..524a251f 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -1,5 +1,6 @@ import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -23,7 +24,7 @@ class _PlaylistViewState extends State { tracks: tracks, id: widget.playlist.id!, name: widget.playlist.name!, - thumbnail: widget.playlist.images![0].url!, + thumbnail: imageToUrlString(widget.playlist.images), ); playback.setCurrentTrack = currentTrack; } else if (isPlaylistPlaying && diff --git a/lib/components/Search/Search.dart b/lib/components/Search/Search.dart new file mode 100644 index 00000000..8a65d1f2 --- /dev/null +++ b/lib/components/Search/Search.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart' hide Page; +import 'package:provider/provider.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Album/AlbumCard.dart'; +import 'package:spotube/components/Artist/ArtistCard.dart'; +import 'package:spotube/components/Playlist/PlaylistCard.dart'; +import 'package:spotube/components/Shared/TracksTableView.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; +import 'package:spotube/helpers/simple-album-to-album.dart'; +import 'package:spotube/helpers/zero-pad-num-str.dart'; +import 'package:spotube/provider/Playback.dart'; +import 'package:spotube/provider/SpotifyDI.dart'; + +class Search extends StatefulWidget { + const Search({Key? key}) : super(key: key); + + @override + State createState() => _SearchState(); +} + +class _SearchState extends State { + late TextEditingController _controller; + String searchTerm = ""; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + } + + @override + Widget build(BuildContext context) { + SpotifyApi spotify = context.watch().spotifyApi; + + return Expanded( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Row( + children: [ + Expanded( + child: TextField( + decoration: const InputDecoration(hintText: "Search..."), + controller: _controller, + ), + ), + const SizedBox(width: 5), + MaterialButton( + elevation: 3, + splashColor: Theme.of(context).primaryColor, + padding: const EdgeInsets.symmetric(vertical: 21), + color: Theme.of(context).primaryColor, + textColor: Colors.white, + child: const Icon(Icons.search_rounded), + onPressed: () { + setState(() { + searchTerm = _controller.value.text; + }); + }, + ), + ], + ), + ), + FutureBuilder>( + future: searchTerm.isNotEmpty + ? spotify.search.get(searchTerm).first(5) + : null, + builder: (context, snapshot) { + if (!snapshot.hasData && searchTerm.isNotEmpty) { + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + } else if (!snapshot.hasData && searchTerm.isEmpty) { + return Container(); + } + Playback playback = context.watch(); + List albums = []; + List artists = []; + List tracks = []; + List playlists = []; + for (MapEntry page + in snapshot.data?.asMap().entries ?? []) { + for (var item in page.value.items ?? []) { + if (item is AlbumSimple) { + albums.add(item); + } else if (item is PlaylistSimple) { + playlists.add(item); + } else if (item is Artist) { + artists.add(item); + } else if (item is Track) { + tracks.add(item); + } + } + } + return Expanded( + child: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (tracks.isNotEmpty) + Text( + "Songs", + style: Theme.of(context).textTheme.headline5, + ), + ...tracks.asMap().entries.map((track) { + String duration = + "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; + return TracksTableView.buildTrackTile( + context, + playback, + track: track, + duration: duration, + thumbnailUrl: + imageToUrlString(track.value.album?.images), + onTrackPlayButtonPressed: (currentTrack) {}, + ); + }), + if (albums.isNotEmpty) + Text( + "Albums", + style: Theme.of(context).textTheme.headline5, + ), + const SizedBox(height: 10), + Wrap( + spacing: 20, + runSpacing: 20, + children: albums.map((album) { + return AlbumCard(simpleAlbumToAlbum(album)); + }).toList(), + ), + const SizedBox(height: 20), + if (artists.isNotEmpty) + Text( + "Artists", + style: Theme.of(context).textTheme.headline5, + ), + const SizedBox(height: 10), + Wrap( + spacing: 20, + runSpacing: 20, + children: artists.map((artist) { + return ArtistCard(artist); + }).toList(), + ), + const SizedBox(height: 20), + if (playlists.isNotEmpty) + Text( + "Playlists", + style: Theme.of(context).textTheme.headline5, + ), + const SizedBox(height: 10), + Wrap( + spacing: 20, + runSpacing: 20, + children: playlists.map((playlist) { + return PlaylistCard(playlist); + }).toList(), + ), + ], + ), + ), + ), + ); + }, + ) + ], + ), + ); + } +} diff --git a/lib/components/Shared/TracksTableView.dart b/lib/components/Shared/TracksTableView.dart index c5a30543..7e0b9f8d 100644 --- a/lib/components/Shared/TracksTableView.dart +++ b/lib/components/Shared/TracksTableView.dart @@ -5,6 +5,7 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Shared/LinkText.dart'; import 'package:spotube/helpers/artists-to-clickable-artists.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/provider/Playback.dart'; @@ -148,10 +149,10 @@ class TracksTableView extends StatelessWidget { ], ), ...tracks.asMap().entries.map((track) { - String? thumbnailUrl = - (track.value.album?.images?.isNotEmpty ?? false) - ? track.value.album?.images?.last.url - : null; + String? thumbnailUrl = imageToUrlString( + track.value.album?.images, + index: (track.value.album?.images?.length ?? 1) - 1, + ); String duration = "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; return buildTrackTile(context, playback, diff --git a/lib/helpers/image-to-url-string.dart b/lib/helpers/image-to-url-string.dart new file mode 100644 index 00000000..f9cf0938 --- /dev/null +++ b/lib/helpers/image-to-url-string.dart @@ -0,0 +1,7 @@ +import 'package:spotify/spotify.dart'; + +String imageToUrlString(List? images, {int index = 0}) { + return images != null && images.isNotEmpty + ? images[0].url! + : "https://avatars.dicebear.com/api/croodles-neutral/${DateTime.now().toString()}.png"; +} diff --git a/lib/helpers/simple-album-to-album.dart b/lib/helpers/simple-album-to-album.dart new file mode 100644 index 00000000..fb05a84e --- /dev/null +++ b/lib/helpers/simple-album-to-album.dart @@ -0,0 +1,19 @@ +import 'package:spotify/spotify.dart'; + +simpleAlbumToAlbum(AlbumSimple albumSimple) { + Album album = Album(); + album.albumType = albumSimple.albumType; + album.artists = albumSimple.artists; + album.availableMarkets = albumSimple.availableMarkets; + album.externalUrls = albumSimple.externalUrls; + album.href = albumSimple.href; + album.id = albumSimple.id; + album.images = albumSimple.images; + album.name = albumSimple.name; + album.releaseDate = albumSimple.releaseDate; + album.releaseDatePrecision = albumSimple.releaseDatePrecision; + album.tracks = albumSimple.tracks; + album.type = albumSimple.type; + album.uri = albumSimple.uri; + return album; +}