mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
Squashed commit of the following:
commit e160d4f561ff2e945fd67bf12b223c012da58b1e Author: Kingkor Roy Tirtho <krtirtho@gmail.com> Date: Sat Sep 14 10:48:08 2024 +0600 fix: pagination issues in playlist and album pages
This commit is contained in:
parent
40bfcc1961
commit
3afe3cea80
@ -65,6 +65,56 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
|
||||
final isActive = playlist.collections.contains(props.collectionId);
|
||||
|
||||
final onTapTrackTile = useCallback((Track track, int index) async {
|
||||
if (trackViewState.isSelecting) {
|
||||
trackViewState.toggleTrackSelection(track.id!);
|
||||
return;
|
||||
}
|
||||
|
||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||
|
||||
if (isRemoteDevice) {
|
||||
final remotePlayback = ref.read(connectProvider.notifier);
|
||||
final remoteQueue = ref.read(queueProvider);
|
||||
if (remoteQueue.collections.contains(props.collectionId) ||
|
||||
remoteQueue.tracks.any((s) => s.id == track.id)) {
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
} else {
|
||||
final tracks = await props.pagination.onFetchAll();
|
||||
await remotePlayback.load(
|
||||
props.collection is AlbumSimple
|
||||
? WebSocketLoadEventData.album(
|
||||
tracks: tracks,
|
||||
collection: props.collection as AlbumSimple,
|
||||
initialIndex: index,
|
||||
)
|
||||
: WebSocketLoadEventData.playlist(
|
||||
tracks: tracks,
|
||||
collection: props.collection as PlaylistSimple,
|
||||
initialIndex: index,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (isActive || playlist.tracks.contains(track)) {
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
} else {
|
||||
final tracks = await props.pagination.onFetchAll();
|
||||
await playlistNotifier.load(
|
||||
tracks,
|
||||
initialIndex: index,
|
||||
autoPlay: true,
|
||||
);
|
||||
playlistNotifier.addCollection(props.collectionId);
|
||||
if (props.collection is AlbumSimple) {
|
||||
historyNotifier.addAlbums([props.collection as AlbumSimple]);
|
||||
} else {
|
||||
historyNotifier.addPlaylists([props.collection as PlaylistSimple]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isActive, playlist, props, playlistNotifier, historyNotifier]);
|
||||
|
||||
return SliverMainAxisGroup(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
@ -130,58 +180,7 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
trackViewState.selectTrack(track.id!);
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
onTap: () async {
|
||||
if (trackViewState.isSelecting) {
|
||||
trackViewState.toggleTrackSelection(track.id!);
|
||||
return;
|
||||
}
|
||||
|
||||
final isRemoteDevice =
|
||||
await showSelectDeviceDialog(context, ref);
|
||||
|
||||
if (isRemoteDevice) {
|
||||
final remotePlayback = ref.read(connectProvider.notifier);
|
||||
final remoteQueue = ref.read(queueProvider);
|
||||
if (remoteQueue.collections.contains(props.collectionId) ||
|
||||
remoteQueue.tracks.any((s) => s.id == track.id)) {
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
} else {
|
||||
final tracks = await props.pagination.onFetchAll();
|
||||
await remotePlayback.load(
|
||||
props.collection is AlbumSimple
|
||||
? WebSocketLoadEventData.album(
|
||||
tracks: tracks,
|
||||
collection: props.collection as AlbumSimple,
|
||||
initialIndex: index,
|
||||
)
|
||||
: WebSocketLoadEventData.playlist(
|
||||
tracks: tracks,
|
||||
collection: props.collection as PlaylistSimple,
|
||||
initialIndex: index,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (isActive || playlist.tracks.contains(track)) {
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
} else {
|
||||
final tracks = await props.pagination.onFetchAll();
|
||||
await playlistNotifier.load(
|
||||
tracks,
|
||||
initialIndex: index,
|
||||
autoPlay: true,
|
||||
);
|
||||
playlistNotifier.addCollection(props.collectionId);
|
||||
if (props.collection is AlbumSimple) {
|
||||
historyNotifier
|
||||
.addAlbums([props.collection as AlbumSimple]);
|
||||
} else {
|
||||
historyNotifier
|
||||
.addPlaylists([props.collection as PlaylistSimple]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onTap: () => onTapTrackTile(track, index),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -90,12 +90,18 @@ class HistoryTopAlbumsNotifier extends FamilyPaginatedAsyncNotifier<
|
||||
fetch(arg, offset, limit) async {
|
||||
final albumsQuery = createAlbumsQuery(limit: limit, offset: offset);
|
||||
|
||||
return getAlbumsWithCount(await albumsQuery.get());
|
||||
final items = getAlbumsWithCount(await albumsQuery.get());
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: items.length == limit,
|
||||
nextOffset: offset + limit,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
build(arg) async {
|
||||
final albums = await fetch(arg, 0, 20);
|
||||
final (items: albums, :hasMore, :nextOffset) = await fetch(arg, 0, 20);
|
||||
|
||||
final subscription = createAlbumsQuery().watch().listen((event) {
|
||||
if (state.asData == null) return;
|
||||
@ -111,9 +117,9 @@ class HistoryTopAlbumsNotifier extends FamilyPaginatedAsyncNotifier<
|
||||
|
||||
return HistoryTopAlbumsState(
|
||||
items: albums,
|
||||
offset: albums.length,
|
||||
offset: nextOffset,
|
||||
limit: 20,
|
||||
hasMore: true,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -55,12 +55,18 @@ class HistoryTopPlaylistsNotifier extends FamilyPaginatedAsyncNotifier<
|
||||
fetch(arg, offset, limit) async {
|
||||
final playlistsQuery = createPlaylistsQuery()..limit(limit, offset: offset);
|
||||
|
||||
return getPlaylistsWithCount(await playlistsQuery.get());
|
||||
final items = getPlaylistsWithCount(await playlistsQuery.get());
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: items.length == limit,
|
||||
nextOffset: offset + limit,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
build(arg) async {
|
||||
final playlists = await fetch(arg, 0, 20);
|
||||
final (items: playlists, :hasMore, :nextOffset) = await fetch(arg, 0, 20);
|
||||
|
||||
final subscription = createPlaylistsQuery().watch().listen((event) {
|
||||
if (state.asData == null) return;
|
||||
@ -76,9 +82,9 @@ class HistoryTopPlaylistsNotifier extends FamilyPaginatedAsyncNotifier<
|
||||
|
||||
return HistoryTopPlaylistsState(
|
||||
items: playlists,
|
||||
offset: playlists.length,
|
||||
offset: nextOffset,
|
||||
limit: 20,
|
||||
hasMore: true,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -89,12 +89,18 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier<
|
||||
fetch(arg, offset, limit) async {
|
||||
final tracksQuery = createTracksQuery()..limit(limit, offset: offset);
|
||||
|
||||
return getTracksWithCount(await tracksQuery.get());
|
||||
final items = getTracksWithCount(await tracksQuery.get());
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: items.length == limit,
|
||||
nextOffset: offset + limit,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
build(arg) async {
|
||||
final tracks = await fetch(arg, 0, 20);
|
||||
final (items: tracks, :hasMore, :nextOffset) = await fetch(arg, 0, 20);
|
||||
|
||||
final subscription = createTracksQuery().watch().listen((event) {
|
||||
if (state.asData == null) return;
|
||||
@ -110,9 +116,9 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier<
|
||||
|
||||
return HistoryTopTracksState(
|
||||
items: tracks,
|
||||
offset: tracks.length,
|
||||
offset: nextOffset,
|
||||
limit: 20,
|
||||
hasMore: true,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,13 @@ class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<Track,
|
||||
@override
|
||||
fetch(arg, offset, limit) async {
|
||||
final tracks = await spotify.albums.tracks(arg.id!).getPage(limit, offset);
|
||||
return tracks.items?.map((e) => e.asTrack(arg)).toList() ?? [];
|
||||
final items = tracks.items?.map((e) => e.asTrack(arg)).toList() ?? [];
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: !tracks.isLast,
|
||||
nextOffset: tracks.nextOffset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -39,12 +45,12 @@ class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<Track,
|
||||
ref.cacheFor();
|
||||
|
||||
ref.watch(spotifyProvider);
|
||||
final tracks = await fetch(arg, 0, 20);
|
||||
final (:items, :nextOffset, :hasMore) = await fetch(arg, 0, 20);
|
||||
return AlbumTracksState(
|
||||
items: tracks,
|
||||
offset: 0,
|
||||
items: items,
|
||||
offset: nextOffset,
|
||||
limit: 20,
|
||||
hasMore: tracks.length == 20,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,13 @@ class ArtistAlbumsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
.albums(arg, country: market)
|
||||
.getPage(limit, offset);
|
||||
|
||||
return albums.items?.toList() ?? [];
|
||||
final items = albums.items?.toList() ?? [];
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: !albums.isLast,
|
||||
nextOffset: albums.nextOffset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -46,12 +52,12 @@ class ArtistAlbumsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
ref.watch(
|
||||
userPreferencesProvider.select((s) => s.market),
|
||||
);
|
||||
final albums = await fetch(arg, 0, 20);
|
||||
final (:items, :hasMore, :nextOffset) = await fetch(arg, 0, 20);
|
||||
return ArtistAlbumsState(
|
||||
items: albums,
|
||||
offset: 0,
|
||||
items: items,
|
||||
offset: nextOffset,
|
||||
limit: 20,
|
||||
hasMore: albums.length == 20,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,13 @@ class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
(json) => PlaylistsFeatured.fromJson(json),
|
||||
).getPage(limit, offset);
|
||||
|
||||
return playlists.items?.whereNotNull().toList() ?? [];
|
||||
final items = playlists.items?.whereNotNull().toList() ?? [];
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: !playlists.isLast,
|
||||
nextOffset: playlists.nextOffset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -50,13 +56,13 @@ class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
ref.watch(userPreferencesProvider.select((s) => s.locale));
|
||||
ref.watch(userPreferencesProvider.select((s) => s.market));
|
||||
|
||||
final playlists = await fetch(arg, 0, 8);
|
||||
final (:items, :hasMore, :nextOffset) = await fetch(arg, 0, 8);
|
||||
|
||||
return CategoryPlaylistsState(
|
||||
items: playlists,
|
||||
offset: 0,
|
||||
items: items,
|
||||
offset: nextOffset,
|
||||
limit: 8,
|
||||
hasMore: playlists.length == 8,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,16 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
|
||||
/// Filter out tracks with null id because some personal playlists
|
||||
/// may contain local tracks that are not available in the Spotify catalog
|
||||
return tracks.items
|
||||
final items = tracks.items
|
||||
?.where((track) => track.id != null && track.type == "track")
|
||||
.toList() ??
|
||||
<Track>[];
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: !tracks.isLast,
|
||||
nextOffset: tracks.nextOffset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -47,13 +53,13 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
ref.cacheFor();
|
||||
|
||||
ref.watch(spotifyProvider);
|
||||
final tracks = await fetch(arg, 0, 20);
|
||||
final (items: tracks, :hasMore, :nextOffset) = await fetch(arg, 0, 20);
|
||||
|
||||
return PlaylistTracksState(
|
||||
items: tracks,
|
||||
offset: 0,
|
||||
offset: nextOffset,
|
||||
limit: 20,
|
||||
hasMore: tracks.length == 20,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,13 @@ class SearchNotifier<Y> extends AutoDisposeFamilyPaginatedAsyncNotifier<Y,
|
||||
|
||||
@override
|
||||
fetch(arg, offset, limit) async {
|
||||
if (state.value == null) return [];
|
||||
if (state.value == null) {
|
||||
return (
|
||||
items: <Y>[],
|
||||
hasMore: false,
|
||||
nextOffset: 0,
|
||||
);
|
||||
}
|
||||
final results = await spotify.search
|
||||
.get(
|
||||
ref.read(searchTermStateProvider),
|
||||
@ -46,7 +52,13 @@ class SearchNotifier<Y> extends AutoDisposeFamilyPaginatedAsyncNotifier<Y,
|
||||
)
|
||||
.getPage(limit, offset);
|
||||
|
||||
return results.expand((e) => e.items ?? <Y>[]).toList().cast<Y>();
|
||||
final items = results.expand((e) => e.items ?? <Y>[]).toList().cast<Y>();
|
||||
|
||||
return (
|
||||
items: items,
|
||||
hasMore: items.length == limit,
|
||||
nextOffset: offset + limit,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -59,13 +71,13 @@ class SearchNotifier<Y> extends AutoDisposeFamilyPaginatedAsyncNotifier<Y,
|
||||
userPreferencesProvider.select((value) => value.market),
|
||||
);
|
||||
|
||||
final results = await fetch(arg, 0, 10);
|
||||
final (:items, :hasMore, :nextOffset) = await fetch(arg, 0, 10);
|
||||
|
||||
return SearchState<Y>(
|
||||
items: results,
|
||||
offset: 0,
|
||||
items: items,
|
||||
offset: nextOffset,
|
||||
limit: 10,
|
||||
hasMore: results.length == 10,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
part of '../../spotify.dart';
|
||||
|
||||
typedef PseudoPaginatedProps<T> = ({
|
||||
List<T> items,
|
||||
int nextOffset,
|
||||
bool hasMore,
|
||||
});
|
||||
|
||||
abstract class FamilyPaginatedAsyncNotifier<
|
||||
K,
|
||||
T extends BasePaginatedState<K, dynamic>,
|
||||
A> extends FamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
|
||||
Future<List<K>> fetch(A arg, int offset, int limit);
|
||||
Future<PseudoPaginatedProps<K>> fetch(A arg, int offset, int limit);
|
||||
|
||||
Future<void> fetchMore() async {
|
||||
if (state.value == null || !state.value!.hasMore) return;
|
||||
@ -13,18 +19,18 @@ abstract class FamilyPaginatedAsyncNotifier<
|
||||
|
||||
state = await AsyncValue.guard(
|
||||
() async {
|
||||
final items = await fetch(
|
||||
final (:items, :hasMore, :nextOffset) = await fetch(
|
||||
arg,
|
||||
state.value!.offset + state.value!.limit,
|
||||
state.value!.offset,
|
||||
state.value!.limit,
|
||||
);
|
||||
return state.value!.copyWith(
|
||||
hasMore: items.length == state.value!.limit,
|
||||
hasMore: hasMore,
|
||||
items: [
|
||||
...state.value!.items,
|
||||
...items,
|
||||
],
|
||||
offset: state.value!.offset + state.value!.limit,
|
||||
offset: nextOffset,
|
||||
) as T;
|
||||
},
|
||||
);
|
||||
@ -37,16 +43,16 @@ abstract class FamilyPaginatedAsyncNotifier<
|
||||
bool hasMore = true;
|
||||
while (hasMore) {
|
||||
await update((state) async {
|
||||
final items = await fetch(
|
||||
final res = await fetch(
|
||||
arg,
|
||||
state.offset + state.limit,
|
||||
state.offset,
|
||||
state.limit,
|
||||
);
|
||||
|
||||
hasMore = items.length == state.limit;
|
||||
hasMore = res.hasMore;
|
||||
return state.copyWith(
|
||||
items: [...state.items, ...items],
|
||||
offset: state.offset + state.limit,
|
||||
items: [...state.items, ...res.items],
|
||||
offset: res.nextOffset,
|
||||
hasMore: hasMore,
|
||||
) as T;
|
||||
});
|
||||
@ -60,7 +66,7 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
K,
|
||||
T extends BasePaginatedState<K, dynamic>,
|
||||
A> extends AutoDisposeFamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
|
||||
Future<List<K>> fetch(A arg, int offset, int limit);
|
||||
Future<PseudoPaginatedProps<K>> fetch(A arg, int offset, int limit);
|
||||
|
||||
Future<void> fetchMore() async {
|
||||
if (state.value == null || !state.value!.hasMore) return;
|
||||
@ -69,18 +75,19 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
|
||||
state = await AsyncValue.guard(
|
||||
() async {
|
||||
final items = await fetch(
|
||||
final (:items, :hasMore, :nextOffset) = await fetch(
|
||||
arg,
|
||||
state.value!.offset + state.value!.limit,
|
||||
state.value!.offset,
|
||||
state.value!.limit,
|
||||
);
|
||||
|
||||
return state.value!.copyWith(
|
||||
hasMore: items.length == state.value!.limit,
|
||||
hasMore: hasMore,
|
||||
items: [
|
||||
...state.value!.items,
|
||||
...items,
|
||||
],
|
||||
offset: state.value!.offset + state.value!.limit,
|
||||
offset: nextOffset,
|
||||
) as T;
|
||||
},
|
||||
);
|
||||
@ -93,16 +100,16 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<
|
||||
bool hasMore = true;
|
||||
while (hasMore) {
|
||||
await update((state) async {
|
||||
final items = await fetch(
|
||||
final res = await fetch(
|
||||
arg,
|
||||
state.offset + state.limit,
|
||||
state.offset,
|
||||
state.limit,
|
||||
);
|
||||
|
||||
hasMore = items.length == state.limit;
|
||||
hasMore = res.hasMore;
|
||||
return state.copyWith(
|
||||
items: [...state.items, ...items],
|
||||
offset: state.offset + state.limit,
|
||||
items: [...state.items, ...res.items],
|
||||
offset: res.nextOffset,
|
||||
hasMore: hasMore,
|
||||
) as T;
|
||||
});
|
||||
|
@ -758,6 +758,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.5"
|
||||
flutter_hooks_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_hooks_lint
|
||||
sha256: fc6e18505b597737e5d620656e340ac60e7a58980cca29e18c1216bd15083674
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
flutter_inappwebview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -158,6 +158,7 @@ dev_dependencies:
|
||||
xml: ^6.5.0
|
||||
io: ^1.0.4
|
||||
drift_dev: ^2.18.0
|
||||
flutter_hooks_lint: ^1.2.0
|
||||
|
||||
dependency_overrides:
|
||||
uuid: ^4.4.0
|
||||
|
Loading…
Reference in New Issue
Block a user