From 87bf33321b3aed30844275ffa39b6ac96ce36966 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 14 Jul 2026 18:41:13 +0700 Subject: [PATCH] refactor(search): remove unreachable artist/album/playlist result path TrackState.searchArtists/searchAlbums/searchPlaylists were only ever written empty (the metadata-search parse loops iterated const [] lists and customSearch set []), so the home feed's separate artist/album/playlist result sections never rendered. Drop the fields, the dead parse loops and _parseSearch* helpers, the SearchArtist/Album/Playlist models, the sort caches/methods, and the _Search* result widgets. The live tracks->buckets sections (_CollectionItemWidget via artistItems/albumItems/playlistItems) and the search filter tabs are kept. --- lib/providers/track_provider.dart | 167 +------------------- lib/screens/home_tab.dart | 252 +----------------------------- lib/screens/home_tab_widgets.dart | 208 ------------------------ 3 files changed, 6 insertions(+), 621 deletions(-) diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 16a670d8..d0542dcd 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -27,9 +27,6 @@ class TrackState { final int? monthlyListeners; final List? artistAlbums; final List? artistTopTracks; - final List? searchArtists; - final List? searchAlbums; - final List? searchPlaylists; final bool hasSearchText; final bool isShowingRecentAccess; final String? searchExtensionId; @@ -51,9 +48,6 @@ class TrackState { this.monthlyListeners, this.artistAlbums, this.artistTopTracks, - this.searchArtists, - this.searchAlbums, - this.searchPlaylists, this.hasSearchText = false, this.isShowingRecentAccess = false, this.searchExtensionId, @@ -61,12 +55,7 @@ class TrackState { this.searchSource, }); - bool get hasContent => - tracks.isNotEmpty || - artistAlbums != null || - (searchArtists != null && searchArtists!.isNotEmpty) || - (searchAlbums != null && searchAlbums!.isNotEmpty) || - (searchPlaylists != null && searchPlaylists!.isNotEmpty); + bool get hasContent => tracks.isNotEmpty || artistAlbums != null; TrackState copyWith({ List? tracks, @@ -83,9 +72,6 @@ class TrackState { int? monthlyListeners, List? artistAlbums, List? artistTopTracks, - List? searchArtists, - List? searchAlbums, - List? searchPlaylists, bool? hasSearchText, bool? isShowingRecentAccess, String? searchExtensionId, @@ -109,9 +95,6 @@ class TrackState { monthlyListeners: monthlyListeners ?? this.monthlyListeners, artistAlbums: artistAlbums ?? this.artistAlbums, artistTopTracks: artistTopTracks ?? this.artistTopTracks, - searchArtists: searchArtists ?? this.searchArtists, - searchAlbums: searchAlbums ?? this.searchAlbums, - searchPlaylists: searchPlaylists ?? this.searchPlaylists, hasSearchText: hasSearchText ?? this.hasSearchText, isShowingRecentAccess: isShowingRecentAccess ?? this.isShowingRecentAccess, @@ -148,58 +131,6 @@ class ArtistAlbum { }); } -class SearchArtist { - final String id; - final String name; - final String? imageUrl; - final int followers; - final int popularity; - - const SearchArtist({ - required this.id, - required this.name, - this.imageUrl, - required this.followers, - required this.popularity, - }); -} - -class SearchAlbum { - final String id; - final String name; - final String artists; - final String? imageUrl; - final String? releaseDate; - final int totalTracks; - final String albumType; - - const SearchAlbum({ - required this.id, - required this.name, - required this.artists, - this.imageUrl, - this.releaseDate, - required this.totalTracks, - required this.albumType, - }); -} - -class SearchPlaylist { - final String id; - final String name; - final String owner; - final String? imageUrl; - final int totalTracks; - - const SearchPlaylist({ - required this.id, - required this.name, - required this.owner, - this.imageUrl, - required this.totalTracks, - }); -} - class TrackNotifier extends Notifier { int _currentRequestId = 0; @@ -476,18 +407,9 @@ class TrackNotifier extends Notifier { return; } - final trackSearchResults = metadataTrackResults; - const artistList = []; - const albumList = []; - - _log.d( - 'Raw results: ${trackSearchResults.length} tracks, ${artistList.length} artists, ${albumList.length} albums', - ); - final tracks = []; - - for (int i = 0; i < trackSearchResults.length; i++) { - final t = trackSearchResults[i]; + for (int i = 0; i < metadataTrackResults.length; i++) { + final t = metadataTrackResults[i]; try { tracks.add(Track.fromBackendMap(t)); } catch (e) { @@ -495,58 +417,10 @@ class TrackNotifier extends Notifier { } } - final artists = []; - for (int i = 0; i < artistList.length; i++) { - final a = artistList[i]; - try { - if (a is Map) { - artists.add(_parseSearchArtist(a)); - } else { - _log.w('Artist[$i] is not a Map: ${a.runtimeType}'); - } - } catch (e) { - _log.e('Failed to parse artist[$i]: $e', e); - } - } - - final albums = []; - for (int i = 0; i < albumList.length; i++) { - final a = albumList[i]; - try { - if (a is Map) { - albums.add(_parseSearchAlbum(a)); - } else { - _log.w('Album[$i] is not a Map: ${a.runtimeType}'); - } - } catch (e) { - _log.e('Failed to parse album[$i]: $e', e); - } - } - - const playlistList = []; - final playlists = []; - for (int i = 0; i < playlistList.length; i++) { - final p = playlistList[i]; - try { - if (p is Map) { - playlists.add(_parseSearchPlaylist(p)); - } else { - _log.w('Playlist[$i] is not a Map: ${p.runtimeType}'); - } - } catch (e) { - _log.e('Failed to parse playlist[$i]: $e', e); - } - } - - _log.i( - 'Search complete: ${tracks.length} tracks, ${artists.length} artists, ${albums.length} albums, ${playlists.length} playlists parsed successfully', - ); + _log.i('Search complete: ${tracks.length} tracks parsed successfully'); state = TrackState( tracks: tracks, - searchArtists: artists, - searchAlbums: albums, - searchPlaylists: playlists, isLoading: false, hasSearchText: state.hasSearchText, isShowingRecentAccess: state.isShowingRecentAccess, @@ -622,7 +496,6 @@ class TrackNotifier extends Notifier { state = TrackState( tracks: tracks, - searchArtists: [], isLoading: false, hasSearchText: state.hasSearchText, isShowingRecentAccess: state.isShowingRecentAccess, @@ -731,38 +604,6 @@ class TrackNotifier extends Notifier { providerId: data['provider_id']?.toString(), ); } - - SearchArtist _parseSearchArtist(Map data) { - return SearchArtist( - id: data['id'] as String? ?? '', - name: data['name'] as String? ?? '', - imageUrl: normalizeRemoteHttpUrl(data['images']?.toString()), - followers: data['followers'] as int? ?? 0, - popularity: data['popularity'] as int? ?? 0, - ); - } - - SearchAlbum _parseSearchAlbum(Map data) { - return SearchAlbum( - id: data['id'] as String? ?? '', - name: data['name'] as String? ?? '', - artists: data['artists'] as String? ?? '', - imageUrl: normalizeRemoteHttpUrl(data['images']?.toString()), - releaseDate: data['release_date'] as String?, - totalTracks: data['total_tracks'] as int? ?? 0, - albumType: data['album_type'] as String? ?? 'album', - ); - } - - SearchPlaylist _parseSearchPlaylist(Map data) { - return SearchPlaylist( - id: data['id'] as String? ?? '', - name: data['name'] as String? ?? '', - owner: data['owner'] as String? ?? '', - imageUrl: normalizeRemoteHttpUrl(data['images']?.toString()), - totalTracks: data['total_tracks'] as int? ?? 0, - ); - } } final trackProvider = NotifierProvider( diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index c7ba6b57..c4c95ecd 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -89,15 +89,6 @@ class _HomeTabState extends ConsumerState List? _searchBucketsSourceTracks; _SearchResultBuckets? _searchBucketsCache; _SearchSortOption _searchSortOption = _SearchSortOption.defaultOrder; - List? _sortedArtistsSource; - _SearchSortOption? _sortedArtistsMode; - List? _sortedArtistsCache; - List? _sortedAlbumsSource; - _SearchSortOption? _sortedAlbumsMode; - List? _sortedAlbumsCache; - List? _sortedPlaylistsSource; - _SearchSortOption? _sortedPlaylistsMode; - List? _sortedPlaylistsCache; List? _sortedTracksSource; List? _sortedTrackIndexesSource; _SearchSortOption? _sortedTracksMode; @@ -509,15 +500,6 @@ class _HomeTabState extends ConsumerState } void _invalidateSearchSortCaches() { - _sortedArtistsSource = null; - _sortedArtistsMode = null; - _sortedArtistsCache = null; - _sortedAlbumsSource = null; - _sortedAlbumsMode = null; - _sortedAlbumsCache = null; - _sortedPlaylistsSource = null; - _sortedPlaylistsMode = null; - _sortedPlaylistsCache = null; _sortedTracksSource = null; _sortedTrackIndexesSource = null; _sortedTracksMode = null; @@ -1149,13 +1131,7 @@ class _HomeTabState extends ConsumerState super.build(context); final hasActualResults = ref.watch( - trackProvider.select( - (s) => - s.tracks.isNotEmpty || - (s.searchArtists != null && s.searchArtists!.isNotEmpty) || - (s.searchAlbums != null && s.searchAlbums!.isNotEmpty) || - (s.searchPlaylists != null && s.searchPlaylists!.isNotEmpty), - ), + trackProvider.select((s) => s.tracks.isNotEmpty), ); final isLoading = ref.watch(trackProvider.select((s) => s.isLoading)); final searchError = ref.watch(trackProvider.select((s) => s.error)); @@ -1500,15 +1476,6 @@ class _HomeTabState extends ConsumerState final tracks = ref.watch( trackProvider.select((s) => s.tracks), ); - final searchArtists = ref.watch( - trackProvider.select((s) => s.searchArtists), - ); - final searchAlbums = ref.watch( - trackProvider.select((s) => s.searchAlbums), - ); - final searchPlaylists = ref.watch( - trackProvider.select((s) => s.searchPlaylists), - ); final isLoading = ref.watch( trackProvider.select((s) => s.isLoading), ); @@ -1531,9 +1498,6 @@ class _HomeTabState extends ConsumerState _getThumbnailSizesByExtensionId(extensions); final hasResults = tracks.isNotEmpty || - (searchArtists != null && searchArtists.isNotEmpty) || - (searchAlbums != null && searchAlbums.isNotEmpty) || - (searchPlaylists != null && searchPlaylists.isNotEmpty) || isLoading || error != null || hasActiveSearchSurface; @@ -1541,9 +1505,6 @@ class _HomeTabState extends ConsumerState return SliverMainAxisGroup( slivers: _buildSearchResults( tracks: tracks, - searchArtists: searchArtists, - searchAlbums: searchAlbums, - searchPlaylists: searchPlaylists, isLoading: isLoading, error: error, colorScheme: colorScheme, @@ -2896,78 +2857,6 @@ class _HomeTabState extends ConsumerState return sorted; } - List? _sortSearchArtists(List? artists) { - if (artists == null || - artists.isEmpty || - _searchSortOption == _SearchSortOption.defaultOrder) { - return artists; - } - if (identical(artists, _sortedArtistsSource) && - _sortedArtistsMode == _searchSortOption && - _sortedArtistsCache != null) { - return _sortedArtistsCache; - } - final sorted = _applySortToList( - artists, - (a) => a.name, - (a) => a.name, - (a) => 0, - (a) => null, - ); - _sortedArtistsSource = artists; - _sortedArtistsMode = _searchSortOption; - _sortedArtistsCache = sorted; - return sorted; - } - - List? _sortSearchAlbums(List? albums) { - if (albums == null || - albums.isEmpty || - _searchSortOption == _SearchSortOption.defaultOrder) { - return albums; - } - if (identical(albums, _sortedAlbumsSource) && - _sortedAlbumsMode == _searchSortOption && - _sortedAlbumsCache != null) { - return _sortedAlbumsCache; - } - final sorted = _applySortToList( - albums, - (a) => a.name, - (a) => a.artists, - (a) => 0, - (a) => a.releaseDate, - ); - _sortedAlbumsSource = albums; - _sortedAlbumsMode = _searchSortOption; - _sortedAlbumsCache = sorted; - return sorted; - } - - List? _sortSearchPlaylists(List? playlists) { - if (playlists == null || - playlists.isEmpty || - _searchSortOption == _SearchSortOption.defaultOrder) { - return playlists; - } - if (identical(playlists, _sortedPlaylistsSource) && - _sortedPlaylistsMode == _searchSortOption && - _sortedPlaylistsCache != null) { - return _sortedPlaylistsCache; - } - final sorted = _applySortToList( - playlists, - (p) => p.name, - (p) => p.owner, - (p) => 0, - (p) => null, - ); - _sortedPlaylistsSource = playlists; - _sortedPlaylistsMode = _searchSortOption; - _sortedPlaylistsCache = sorted; - return sorted; - } - ({List tracks, List indexes}) _sortTrackResults( List tracks, List indexes, @@ -3006,9 +2895,6 @@ class _HomeTabState extends ConsumerState List _buildSearchResults({ required List tracks, - required List? searchArtists, - required List? searchAlbums, - required List? searchPlaylists, required bool isLoading, required String? error, required ColorScheme colorScheme, @@ -3018,11 +2904,7 @@ class _HomeTabState extends ConsumerState required bool showLocalLibraryIndicator, required Map thumbnailSizesByExtensionId, }) { - final hasActualData = - tracks.isNotEmpty || - (searchArtists != null && searchArtists.isNotEmpty) || - (searchAlbums != null && searchAlbums.isNotEmpty) || - (searchPlaylists != null && searchPlaylists.isNotEmpty); + final hasActualData = tracks.isNotEmpty; if (!hasActualData && isLoading) { return [const SliverToBoxAdapter(child: HomeSearchSkeleton())]; @@ -3038,9 +2920,6 @@ class _HomeTabState extends ConsumerState final playlistItems = buckets.playlistItems; final artistItems = buckets.artistItems; - final sortedArtists = _sortSearchArtists(searchArtists); - final sortedAlbums = _sortSearchAlbums(searchAlbums); - final sortedPlaylists = _sortSearchPlaylists(searchPlaylists); final sortedTrackResults = _sortTrackResults(realTracks, realTrackIndexes); final sortedTracks = sortedTrackResults.tracks; final sortedTrackIndexes = sortedTrackResults.indexes; @@ -3074,28 +2953,6 @@ class _HomeTabState extends ConsumerState bool sortButtonShown = false; - if (sortedArtists != null && sortedArtists.isNotEmpty) { - slivers.addAll( - _buildVirtualizedResultSection( - title: context.l10n.searchArtists, - itemCount: sortedArtists.length, - colorScheme: colorScheme, - showSortButton: !sortButtonShown, - itemBuilder: (index, showDivider) => _SearchArtistItemWidget( - key: ValueKey('search-artist-${sortedArtists[index].id}'), - artist: sortedArtists[index], - showDivider: showDivider, - onTap: () => _navigateToArtist( - sortedArtists[index].id, - sortedArtists[index].name, - sortedArtists[index].imageUrl, - ), - ), - ), - ); - sortButtonShown = true; - } - if (artistItems.isNotEmpty) { slivers.addAll( _buildVirtualizedResultSection( @@ -3114,24 +2971,6 @@ class _HomeTabState extends ConsumerState sortButtonShown = true; } - if (sortedAlbums != null && sortedAlbums.isNotEmpty) { - slivers.addAll( - _buildVirtualizedResultSection( - title: context.l10n.searchAlbums, - itemCount: sortedAlbums.length, - colorScheme: colorScheme, - showSortButton: !sortButtonShown, - itemBuilder: (index, showDivider) => _SearchAlbumItemWidget( - key: ValueKey('search-album-${sortedAlbums[index].id}'), - album: sortedAlbums[index], - showDivider: showDivider, - onTap: () => _navigateToSearchAlbum(sortedAlbums[index]), - ), - ), - ); - sortButtonShown = true; - } - if (albumItems.isNotEmpty) { slivers.addAll( _buildVirtualizedResultSection( @@ -3150,24 +2989,6 @@ class _HomeTabState extends ConsumerState sortButtonShown = true; } - if (sortedPlaylists != null && sortedPlaylists.isNotEmpty) { - slivers.addAll( - _buildVirtualizedResultSection( - title: context.l10n.searchPlaylists, - itemCount: sortedPlaylists.length, - colorScheme: colorScheme, - showSortButton: !sortButtonShown, - itemBuilder: (index, showDivider) => _SearchPlaylistItemWidget( - key: ValueKey('search-playlist-${sortedPlaylists[index].id}'), - playlist: sortedPlaylists[index], - showDivider: showDivider, - onTap: () => _navigateToSearchPlaylist(sortedPlaylists[index]), - ), - ), - ); - sortButtonShown = true; - } - if (playlistItems.isNotEmpty) { slivers.addAll( _buildVirtualizedResultSection( @@ -3305,75 +3126,6 @@ class _HomeTabState extends ConsumerState ]; } - void _navigateToArtist(String artistId, String artistName, String? imageUrl) { - ref.read(settingsProvider.notifier).setHasSearchedBefore(); - - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ArtistScreen( - artistId: artistId, - artistName: artistName, - coverUrl: imageUrl, - ), - ), - ); - } - - void _navigateToSearchAlbum(SearchAlbum album) { - ref.read(settingsProvider.notifier).setHasSearchedBefore(); - - ref - .read(recentAccessProvider.notifier) - .recordAlbumAccess( - id: album.id, - name: album.name, - artistName: album.artists, - imageUrl: album.imageUrl, - providerId: 'deezer', - ); - - // Keep the full ID with prefix (e.g., "deezer:123") for AlbumScreen to detect source - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AlbumScreen( - albumId: album.id, - albumName: album.name, - coverUrl: album.imageUrl, - tracks: const [], - ), - ), - ); - } - - void _navigateToSearchPlaylist(SearchPlaylist playlist) { - ref.read(settingsProvider.notifier).setHasSearchedBefore(); - - ref - .read(recentAccessProvider.notifier) - .recordPlaylistAccess( - id: playlist.id, - name: playlist.name, - ownerName: playlist.owner, - imageUrl: playlist.imageUrl, - providerId: 'deezer', - ); - - // Keep the full ID with prefix (e.g., "deezer:123") for PlaylistScreen to detect source - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PlaylistScreen( - playlistName: playlist.name, - coverUrl: playlist.imageUrl, - tracks: const [], - playlistId: playlist.id, - ), - ), - ); - } - void _navigateToExtensionAlbum(Track albumItem) async { final extensionId = albumItem.source; if (extensionId == null || extensionId.isEmpty) { diff --git a/lib/screens/home_tab_widgets.dart b/lib/screens/home_tab_widgets.dart index d232bc2e..23e2afb0 100644 --- a/lib/screens/home_tab_widgets.dart +++ b/lib/screens/home_tab_widgets.dart @@ -515,214 +515,6 @@ class _CollectionItemWidget extends StatelessWidget { } } -/// Generic row for artist/album/playlist results from default search -/// (Deezer/Spotify); cover shape, fallback icon, and subtitle vary per type. -class _SearchResultRowItem extends StatelessWidget { - final String? imageUrl; - final double coverBorderRadius; - final IconData fallbackIcon; - final String title; - final Widget subtitle; - final bool showDivider; - final VoidCallback onTap; - - const _SearchResultRowItem({ - required this.imageUrl, - required this.coverBorderRadius, - required this.fallbackIcon, - required this.title, - required this.subtitle, - required this.showDivider, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final hasValidImage = - imageUrl != null && - imageUrl!.isNotEmpty && - Uri.tryParse(imageUrl!)?.hasAuthority == true; - - final cover = ClipRRect( - borderRadius: BorderRadius.circular(coverBorderRadius), - child: hasValidImage - ? CachedCoverImage( - imageUrl: imageUrl!, - width: 56, - height: 56, - fit: BoxFit.cover, - ) - : Container( - width: 56, - height: 56, - color: colorScheme.surfaceContainerHighest, - child: Icon(fallbackIcon, color: colorScheme.onSurfaceVariant), - ), - ); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: onTap, - splashColor: colorScheme.primary.withValues(alpha: 0.12), - highlightColor: colorScheme.primary.withValues(alpha: 0.08), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: Row( - children: [ - cover, - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w500, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - subtitle, - ], - ), - ), - Icon( - Icons.chevron_right, - color: colorScheme.onSurfaceVariant, - size: 24, - ), - ], - ), - ), - ), - if (showDivider) - Divider( - height: 1, - thickness: 1, - indent: 80, - endIndent: 12, - color: colorScheme.outlineVariant.withValues(alpha: 0.3), - ), - ], - ); - } -} - -/// Widget for displaying artist items from default search (Deezer/Spotify) -class _SearchArtistItemWidget extends StatelessWidget { - final SearchArtist artist; - final bool showDivider; - final VoidCallback onTap; - - const _SearchArtistItemWidget({ - super.key, - required this.artist, - required this.showDivider, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return _SearchResultRowItem( - imageUrl: artist.imageUrl, - coverBorderRadius: 28, - fallbackIcon: Icons.person, - title: artist.name, - subtitle: Text( - context.l10n.recentTypeArtist, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: colorScheme.onSurfaceVariant), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - showDivider: showDivider, - onTap: onTap, - ); - } -} - -/// Widget for displaying album items from default search (Deezer/Spotify) -class _SearchAlbumItemWidget extends StatelessWidget { - final SearchAlbum album; - final bool showDivider; - final VoidCallback onTap; - - const _SearchAlbumItemWidget({ - super.key, - required this.album, - required this.showDivider, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return _SearchResultRowItem( - imageUrl: album.imageUrl, - coverBorderRadius: 10, - fallbackIcon: Icons.album, - title: album.name, - subtitle: ClickableArtistName( - artistName: album.artists.isNotEmpty - ? album.artists - : context.l10n.recentTypeAlbum, - coverUrl: album.imageUrl, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: colorScheme.onSurfaceVariant), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - showDivider: showDivider, - onTap: onTap, - ); - } -} - -/// Widget for displaying playlist items from default search (Deezer/Spotify) -class _SearchPlaylistItemWidget extends StatelessWidget { - final SearchPlaylist playlist; - final bool showDivider; - final VoidCallback onTap; - - const _SearchPlaylistItemWidget({ - super.key, - required this.playlist, - required this.showDivider, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return _SearchResultRowItem( - imageUrl: playlist.imageUrl, - coverBorderRadius: 10, - fallbackIcon: Icons.playlist_play, - title: playlist.name, - subtitle: Text( - playlist.owner.isNotEmpty - ? playlist.owner - : context.l10n.recentTypePlaylist, - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: colorScheme.onSurfaceVariant), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - showDivider: showDivider, - onTap: onTap, - ); - } -} - class _DownloadedOrRemoteCover extends StatefulWidget { final String? downloadedFilePath; final String? imageUrl;