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.
This commit is contained in:
zarzet
2026-07-14 18:41:13 +07:00
parent 11567fee7d
commit 87bf33321b
3 changed files with 6 additions and 621 deletions
+4 -163
View File
@@ -27,9 +27,6 @@ class TrackState {
final int? monthlyListeners;
final List<ArtistAlbum>? artistAlbums;
final List<Track>? artistTopTracks;
final List<SearchArtist>? searchArtists;
final List<SearchAlbum>? searchAlbums;
final List<SearchPlaylist>? 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<Track>? tracks,
@@ -83,9 +72,6 @@ class TrackState {
int? monthlyListeners,
List<ArtistAlbum>? artistAlbums,
List<Track>? artistTopTracks,
List<SearchArtist>? searchArtists,
List<SearchAlbum>? searchAlbums,
List<SearchPlaylist>? 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<TrackState> {
int _currentRequestId = 0;
@@ -476,18 +407,9 @@ class TrackNotifier extends Notifier<TrackState> {
return;
}
final trackSearchResults = metadataTrackResults;
const artistList = <dynamic>[];
const albumList = <dynamic>[];
_log.d(
'Raw results: ${trackSearchResults.length} tracks, ${artistList.length} artists, ${albumList.length} albums',
);
final tracks = <Track>[];
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<TrackState> {
}
}
final artists = <SearchArtist>[];
for (int i = 0; i < artistList.length; i++) {
final a = artistList[i];
try {
if (a is Map<String, dynamic>) {
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 = <SearchAlbum>[];
for (int i = 0; i < albumList.length; i++) {
final a = albumList[i];
try {
if (a is Map<String, dynamic>) {
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 = <dynamic>[];
final playlists = <SearchPlaylist>[];
for (int i = 0; i < playlistList.length; i++) {
final p = playlistList[i];
try {
if (p is Map<String, dynamic>) {
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<TrackState> {
state = TrackState(
tracks: tracks,
searchArtists: [],
isLoading: false,
hasSearchText: state.hasSearchText,
isShowingRecentAccess: state.isShowingRecentAccess,
@@ -731,38 +604,6 @@ class TrackNotifier extends Notifier<TrackState> {
providerId: data['provider_id']?.toString(),
);
}
SearchArtist _parseSearchArtist(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<TrackNotifier, TrackState>(
+2 -250
View File
@@ -89,15 +89,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
List<Track>? _searchBucketsSourceTracks;
_SearchResultBuckets? _searchBucketsCache;
_SearchSortOption _searchSortOption = _SearchSortOption.defaultOrder;
List<SearchArtist>? _sortedArtistsSource;
_SearchSortOption? _sortedArtistsMode;
List<SearchArtist>? _sortedArtistsCache;
List<SearchAlbum>? _sortedAlbumsSource;
_SearchSortOption? _sortedAlbumsMode;
List<SearchAlbum>? _sortedAlbumsCache;
List<SearchPlaylist>? _sortedPlaylistsSource;
_SearchSortOption? _sortedPlaylistsMode;
List<SearchPlaylist>? _sortedPlaylistsCache;
List<Track>? _sortedTracksSource;
List<int>? _sortedTrackIndexesSource;
_SearchSortOption? _sortedTracksMode;
@@ -509,15 +500,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
}
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<HomeTab>
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<HomeTab>
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<HomeTab>
_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<HomeTab>
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<HomeTab>
return sorted;
}
List<SearchArtist>? _sortSearchArtists(List<SearchArtist>? artists) {
if (artists == null ||
artists.isEmpty ||
_searchSortOption == _SearchSortOption.defaultOrder) {
return artists;
}
if (identical(artists, _sortedArtistsSource) &&
_sortedArtistsMode == _searchSortOption &&
_sortedArtistsCache != null) {
return _sortedArtistsCache;
}
final sorted = _applySortToList<SearchArtist>(
artists,
(a) => a.name,
(a) => a.name,
(a) => 0,
(a) => null,
);
_sortedArtistsSource = artists;
_sortedArtistsMode = _searchSortOption;
_sortedArtistsCache = sorted;
return sorted;
}
List<SearchAlbum>? _sortSearchAlbums(List<SearchAlbum>? albums) {
if (albums == null ||
albums.isEmpty ||
_searchSortOption == _SearchSortOption.defaultOrder) {
return albums;
}
if (identical(albums, _sortedAlbumsSource) &&
_sortedAlbumsMode == _searchSortOption &&
_sortedAlbumsCache != null) {
return _sortedAlbumsCache;
}
final sorted = _applySortToList<SearchAlbum>(
albums,
(a) => a.name,
(a) => a.artists,
(a) => 0,
(a) => a.releaseDate,
);
_sortedAlbumsSource = albums;
_sortedAlbumsMode = _searchSortOption;
_sortedAlbumsCache = sorted;
return sorted;
}
List<SearchPlaylist>? _sortSearchPlaylists(List<SearchPlaylist>? playlists) {
if (playlists == null ||
playlists.isEmpty ||
_searchSortOption == _SearchSortOption.defaultOrder) {
return playlists;
}
if (identical(playlists, _sortedPlaylistsSource) &&
_sortedPlaylistsMode == _searchSortOption &&
_sortedPlaylistsCache != null) {
return _sortedPlaylistsCache;
}
final sorted = _applySortToList<SearchPlaylist>(
playlists,
(p) => p.name,
(p) => p.owner,
(p) => 0,
(p) => null,
);
_sortedPlaylistsSource = playlists;
_sortedPlaylistsMode = _searchSortOption;
_sortedPlaylistsCache = sorted;
return sorted;
}
({List<Track> tracks, List<int> indexes}) _sortTrackResults(
List<Track> tracks,
List<int> indexes,
@@ -3006,9 +2895,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
List<Widget> _buildSearchResults({
required List<Track> tracks,
required List<SearchArtist>? searchArtists,
required List<SearchAlbum>? searchAlbums,
required List<SearchPlaylist>? searchPlaylists,
required bool isLoading,
required String? error,
required ColorScheme colorScheme,
@@ -3018,11 +2904,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
required bool showLocalLibraryIndicator,
required Map<String, (double, double)> 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<HomeTab>
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<HomeTab>
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<HomeTab>
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<HomeTab>
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<HomeTab>
];
}
void _navigateToArtist(String artistId, String artistName, String? imageUrl) {
ref.read(settingsProvider.notifier).setHasSearchedBefore();
Navigator.push(
context,
MaterialPageRoute<void>(
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<void>(
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<void>(
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) {
-208
View File
@@ -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;