unsafe access to images url fix

Initial Search page implemented
This commit is contained in:
Kingkor Roy Tirtho 2022-01-26 13:59:35 +06:00
parent 8c3a62569a
commit 79ef853ac9
12 changed files with 233 additions and 25 deletions

View File

@ -4,6 +4,7 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Album/AlbumView.dart';
import 'package:spotube/components/Shared/PlaybuttonCard.dart'; import 'package:spotube/components/Shared/PlaybuttonCard.dart';
import 'package:spotube/helpers/artist-to-string.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/helpers/simple-track-to-track.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -19,7 +20,7 @@ class AlbumCard extends StatelessWidget {
playback.currentPlaylist!.id == album.id; playback.currentPlaylist!.id == album.id;
return PlaybuttonCard( return PlaybuttonCard(
imageUrl: album.images!.first.url!, imageUrl: imageToUrlString(album.images),
isPlaying: playback.currentPlaylist?.id != null && isPlaying: playback.currentPlaylist?.id != null &&
playback.currentPlaylist?.id == album.id, playback.currentPlaylist?.id == album.id,
title: album.name!, title: album.name!,

View File

@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Shared/TracksTableView.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/helpers/simple-track-to-track.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -19,7 +20,7 @@ class AlbumView extends StatelessWidget {
tracks: tracks, tracks: tracks,
id: album.id!, id: album.id!,
name: album.name!, name: album.name!,
thumbnail: album.images!.first.url!, thumbnail: imageToUrlString(album.images),
); );
playback.setCurrentTrack = currentTrack; playback.setCurrentTrack = currentTrack;
} else if (isPlaylistPlaying && } else if (isPlaylistPlaying &&

View File

@ -8,6 +8,7 @@ import 'package:spotube/components/Artist/ArtistAlbumView.dart';
import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/Artist/ArtistCard.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Shared/TracksTableView.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/readable-number.dart';
import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
@ -47,7 +48,7 @@ class _ArtistProfileState extends State<ArtistProfile> {
CircleAvatar( CircleAvatar(
radius: MediaQuery.of(context).size.width * 0.18, radius: MediaQuery.of(context).size.width * 0.18,
backgroundImage: CachedNetworkImageProvider( backgroundImage: CachedNetworkImageProvider(
snapshot.data!.images!.first.url!, imageToUrlString(snapshot.data!.images),
), ),
), ),
Flexible( Flexible(
@ -143,7 +144,7 @@ class _ArtistProfileState extends State<ArtistProfile> {
tracks: tracks, tracks: tracks,
id: snapshot.data!.id!, id: snapshot.data!.id!,
name: "${snapshot.data!.name!} To Tracks", name: "${snapshot.data!.name!} To Tracks",
thumbnail: snapshot.data!.images!.first.url!, thumbnail: imageToUrlString(snapshot.data?.images),
); );
playback.setCurrentTrack = currentTrack; playback.setCurrentTrack = currentTrack;
} else if (isPlaylistPlaying && } else if (isPlaylistPlaying &&
@ -186,10 +187,11 @@ class _ArtistProfileState extends State<ArtistProfile> {
.map((track) { .map((track) {
String duration = String duration =
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
String? thumbnailUrl = track.value.album != null && String? thumbnailUrl = imageToUrlString(
track.value.album!.images!.isNotEmpty track.value.album?.images,
? track.value.album!.images!.last.url! index:
: null; (track.value.album?.images?.length ?? 1) -
1);
return TracksTableView.buildTrackTile( return TracksTableView.buildTrackTile(
context, context,
playback, playback,

View File

@ -5,14 +5,16 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:oauth2/oauth2.dart' show AuthorizationException; import 'package:oauth2/oauth2.dart' show AuthorizationException;
import 'package:spotify/spotify.dart' hide Image, Player; import 'package:spotify/spotify.dart' hide Image, Player, Search;
import 'package:spotube/components/Catergory/CategoryCard.dart'; import 'package:spotube/components/Category/CategoryCard.dart';
import 'package:spotube/components/Login.dart'; import 'package:spotube/components/Login.dart';
import 'package:spotube/components/Lyrics.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/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Player/Player.dart'; import 'package:spotube/components/Player/Player.dart';
import 'package:spotube/components/Settings.dart'; import 'package:spotube/components/Settings.dart';
import 'package:spotube/components/Library/UserLibrary.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/helpers/oauth-login.dart';
import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/models/sideBarTiles.dart'; import 'package:spotube/models/sideBarTiles.dart';
@ -200,11 +202,8 @@ class _HomeState extends State<Home> {
return FutureBuilder<User>( return FutureBuilder<User>(
future: data.spotifyApi.me.get(), future: data.spotifyApi.me.get(),
builder: (context, snapshot) { builder: (context, snapshot) {
var avatarImg = ((snapshot.data?.images?.isNotEmpty ?? var avatarImg = imageToUrlString(snapshot.data?.images,
false) && index: (snapshot.data?.images?.length ?? 1) - 1);
snapshot.data?.images?.last.url != null)
? snapshot.data!.images!.last.url!
: "https://avatars.dicebear.com/api/adventurer/${snapshot.data?.id}.png";
return Padding( return Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Row( child: Row(
@ -257,6 +256,7 @@ class _HomeState extends State<Home> {
), ),
), ),
), ),
if (_selectedIndex == 1) const Search(),
if (_selectedIndex == 2) const UserLibrary(), if (_selectedIndex == 2) const UserLibrary(),
if (_selectedIndex == 3) const Lyrics(), if (_selectedIndex == 3) const Lyrics(),
], ],

View File

@ -10,6 +10,7 @@ import 'package:spotube/components/Player/PlayerControls.dart';
import 'package:spotube/components/Shared/LinkText.dart'; import 'package:spotube/components/Shared/LinkText.dart';
import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/artist-to-string.dart';
import 'package:spotube/helpers/artists-to-clickable-artists.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/helpers/search-youtube.dart';
import 'package:spotube/models/GlobalKeyActions.dart'; import 'package:spotube/models/GlobalKeyActions.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
@ -211,10 +212,10 @@ class _PlayerState extends State<Player> with WidgetsBindingObserver {
_playTrack(playback.currentTrack!, playback); _playTrack(playback.currentTrack!, playback);
} }
String? albumArt = String? albumArt = imageToUrlString(
(playback.currentTrack?.album?.images?.isNotEmpty ?? false) playback.currentTrack?.album?.images,
? playback.currentTrack?.album?.images?.last.url index: (playback.currentTrack?.album?.images?.length ?? 1) - 1,
: null; );
return Material( return Material(
type: MaterialType.transparency, type: MaterialType.transparency,

View File

@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Playlist/PlaylistView.dart'; import 'package:spotube/components/Playlist/PlaylistView.dart';
import 'package:spotube/components/Shared/PlaybuttonCard.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/Playback.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -49,7 +50,7 @@ class _PlaylistCardState extends State<PlaylistCard> {
tracks: tracks, tracks: tracks,
id: widget.playlist.id!, id: widget.playlist.id!,
name: widget.playlist.name!, name: widget.playlist.name!,
thumbnail: widget.playlist.images!.first.url!, thumbnail: imageToUrlString(widget.playlist.images),
); );
playback.setCurrentTrack = tracks.first; playback.setCurrentTrack = tracks.first;
}, },

View File

@ -1,5 +1,6 @@
import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Shared/TracksTableView.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:spotube/provider/Playback.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -23,7 +24,7 @@ class _PlaylistViewState extends State<PlaylistView> {
tracks: tracks, tracks: tracks,
id: widget.playlist.id!, id: widget.playlist.id!,
name: widget.playlist.name!, name: widget.playlist.name!,
thumbnail: widget.playlist.images![0].url!, thumbnail: imageToUrlString(widget.playlist.images),
); );
playback.setCurrentTrack = currentTrack; playback.setCurrentTrack = currentTrack;
} else if (isPlaylistPlaying && } else if (isPlaylistPlaying &&

View File

@ -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<Search> createState() => _SearchState();
}
class _SearchState extends State<Search> {
late TextEditingController _controller;
String searchTerm = "";
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
Widget build(BuildContext context) {
SpotifyApi spotify = context.watch<SpotifyDI>().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<List<Page>>(
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<Playback>();
List<AlbumSimple> albums = [];
List<Artist> artists = [];
List<Track> tracks = [];
List<PlaylistSimple> playlists = [];
for (MapEntry<int, Page> 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(),
),
],
),
),
),
);
},
)
],
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Album/AlbumView.dart';
import 'package:spotube/components/Shared/LinkText.dart'; import 'package:spotube/components/Shared/LinkText.dart';
import 'package:spotube/helpers/artists-to-clickable-artists.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/helpers/zero-pad-num-str.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
@ -148,10 +149,10 @@ class TracksTableView extends StatelessWidget {
], ],
), ),
...tracks.asMap().entries.map((track) { ...tracks.asMap().entries.map((track) {
String? thumbnailUrl = String? thumbnailUrl = imageToUrlString(
(track.value.album?.images?.isNotEmpty ?? false) track.value.album?.images,
? track.value.album?.images?.last.url index: (track.value.album?.images?.length ?? 1) - 1,
: null; );
String duration = String duration =
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; "${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
return buildTrackTile(context, playback, return buildTrackTile(context, playback,

View File

@ -0,0 +1,7 @@
import 'package:spotify/spotify.dart';
String imageToUrlString(List<Image>? images, {int index = 0}) {
return images != null && images.isNotEmpty
? images[0].url!
: "https://avatars.dicebear.com/api/croodles-neutral/${DateTime.now().toString()}.png";
}

View File

@ -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;
}