mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
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:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user