mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: use providers in home screen
This commit is contained in:
parent
f83d6e08e1
commit
94b3e160c2
@ -1,35 +1,28 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class HomeFeaturedSection extends HookConsumerWidget {
|
class HomeFeaturedSection extends HookConsumerWidget {
|
||||||
const HomeFeaturedSection({Key? key}) : super(key: key);
|
const HomeFeaturedSection({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final featuredPlaylistsQuery = useQueries.playlist.featured(ref);
|
final featuredPlaylists = ref.watch(featuredPlaylistsProvider);
|
||||||
final playlists = useMemoized(
|
final featuredPlaylistsNotifier =
|
||||||
() => featuredPlaylistsQuery.pages
|
ref.read(featuredPlaylistsProvider.notifier);
|
||||||
.whereType<Page<PlaylistSimple>>()
|
|
||||||
.expand((page) => page.items ?? const <PlaylistSimple>[]),
|
|
||||||
[featuredPlaylistsQuery.pages],
|
|
||||||
);
|
|
||||||
final isLoadingFeaturedPlaylists = !featuredPlaylistsQuery.hasPageData &&
|
|
||||||
!featuredPlaylistsQuery.isLoadingNextPage;
|
|
||||||
|
|
||||||
return Skeletonizer(
|
return Skeletonizer(
|
||||||
enabled: isLoadingFeaturedPlaylists,
|
enabled: featuredPlaylists.isLoadingAndEmpty,
|
||||||
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
child: HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||||
items: playlists.toList(),
|
items: featuredPlaylists.value?.items ?? [],
|
||||||
title: Text(context.l10n.featured),
|
title: Text(context.l10n.featured),
|
||||||
isLoadingNextPage: featuredPlaylistsQuery.isLoadingNextPage,
|
isLoadingNextPage: featuredPlaylists.isLoadingNextPage,
|
||||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
hasNextPage: featuredPlaylists.value?.hasMore ?? false,
|
||||||
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
onFetchMore: featuredPlaylistsNotifier.fetchMore,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'dart:ffi';
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -8,15 +7,15 @@ import 'package:spotube/collections/fake.dart';
|
|||||||
import 'package:spotube/components/home/sections/friends/friend_item.dart';
|
import 'package:spotube/components/home/sections/friends/friend_item.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/models/spotify_friends.dart';
|
import 'package:spotube/models/spotify_friends.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class HomePageFriendsSection extends HookConsumerWidget {
|
class HomePageFriendsSection extends HookConsumerWidget {
|
||||||
const HomePageFriendsSection({Key? key}) : super(key: key);
|
const HomePageFriendsSection({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final friendsQuery = useQueries.user.friendActivity(ref);
|
final friendsQuery = ref.watch(friendsProvider);
|
||||||
final friends = friendsQuery.data?.friends ?? FakeData.friends.friends;
|
final friends = friendsQuery.value?.friends ?? FakeData.friends.friends;
|
||||||
|
|
||||||
final groupCount = useBreakpointValue(
|
final groupCount = useBreakpointValue(
|
||||||
sm: 3,
|
sm: 3,
|
||||||
@ -51,8 +50,7 @@ class HomePageFriendsSection extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!friendsQuery.isLoading &&
|
if (friendsQuery.isLoading || friendsQuery.value?.friends.isEmpty == true) {
|
||||||
(!friendsQuery.hasData || friendsQuery.data!.friends.isEmpty)) {
|
|
||||||
return const SliverToBoxAdapter(
|
return const SliverToBoxAdapter(
|
||||||
child: SizedBox.shrink(),
|
child: SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -13,9 +13,9 @@ import 'package:spotube/provider/spotify_provider.dart';
|
|||||||
class FriendItem extends HookConsumerWidget {
|
class FriendItem extends HookConsumerWidget {
|
||||||
final SpotifyFriendActivity friend;
|
final SpotifyFriendActivity friend;
|
||||||
const FriendItem({
|
const FriendItem({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.friend,
|
required this.friend,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
|||||||
@ -13,28 +13,26 @@ import 'package:spotube/collections/spotube_icons.dart';
|
|||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
|
||||||
|
|
||||||
class HomeGenresSection extends HookConsumerWidget {
|
class HomeGenresSection extends HookConsumerWidget {
|
||||||
const HomeGenresSection({Key? key}) : super(key: key);
|
const HomeGenresSection({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
|
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
final recommendationMarket = ref.watch(
|
final categoriesQuery = ref.watch(categoriesProvider);
|
||||||
userPreferencesProvider.select((s) => s.recommendationMarket),
|
final categories = useMemoized(
|
||||||
|
() =>
|
||||||
|
categoriesQuery.value
|
||||||
|
?.where((c) => (c.icons?.length ?? 0) > 0)
|
||||||
|
.take(mediaQuery.mdAndDown ? 6 : 10)
|
||||||
|
.toList() ??
|
||||||
|
<Category>[],
|
||||||
|
[mediaQuery.mdAndDown, categoriesQuery.value],
|
||||||
);
|
);
|
||||||
final categoriesQuery =
|
|
||||||
useQueries.category.listAll(ref, recommendationMarket);
|
|
||||||
|
|
||||||
final categories = categoriesQuery.data
|
|
||||||
?.where((c) => (c.icons?.length ?? 0) > 0)
|
|
||||||
.take(mediaQuery.mdAndDown ? 6 : 10)
|
|
||||||
.toList() ??
|
|
||||||
<Category>[];
|
|
||||||
|
|
||||||
return SliverMainAxisGroup(
|
return SliverMainAxisGroup(
|
||||||
slivers: [
|
slivers: [
|
||||||
|
|||||||
@ -2,19 +2,19 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class HomeMadeForUserSection extends HookConsumerWidget {
|
class HomeMadeForUserSection extends HookConsumerWidget {
|
||||||
const HomeMadeForUserSection({Key? key}) : super(key: key);
|
const HomeMadeForUserSection({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final madeForUser = useQueries.views.get(ref, "made-for-x-hub");
|
final madeForUser = ref.watch(viewProvider("made-for-x-hub"));
|
||||||
|
|
||||||
return SliverList.builder(
|
return SliverList.builder(
|
||||||
itemCount: madeForUser.data?["content"]?["items"]?.length ?? 0,
|
itemCount: madeForUser.value?["content"]?["items"]?.length ?? 0,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = madeForUser.data?["content"]?["items"]?[index];
|
final item = madeForUser.value?["content"]?["items"]?[index];
|
||||||
final playlists = item["content"]?["items"]
|
final playlists = item["content"]?["items"]
|
||||||
?.where((itemL2) => itemL2["type"] == "playlist")
|
?.where((itemL2) => itemL2["type"] == "playlist")
|
||||||
.map((itemL2) => PlaylistSimple.fromJson(itemL2))
|
.map((itemL2) => PlaylistSimple.fromJson(itemL2))
|
||||||
|
|||||||
@ -5,52 +5,32 @@ import 'package:spotify/spotify.dart';
|
|||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
|
||||||
|
|
||||||
class HomeNewReleasesSection extends HookConsumerWidget {
|
class HomeNewReleasesSection extends HookConsumerWidget {
|
||||||
const HomeNewReleasesSection({Key? key}) : super(key: key);
|
const HomeNewReleasesSection({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final newReleases = useQueries.album.newReleases(ref);
|
final newReleases = ref.watch(albumReleasesProvider);
|
||||||
final userArtistsQuery = useQueries.artist.followedByMeAll(ref);
|
final newReleasesNotifier = ref.read(albumReleasesProvider.notifier);
|
||||||
final userArtists =
|
|
||||||
userArtistsQuery.data?.map((s) => s.id!).toList() ?? const [];
|
|
||||||
|
|
||||||
final albums = useMemoized(
|
final albums = ref.watch(userArtistAlbumReleasesProvider);
|
||||||
() {
|
|
||||||
final allReleases = newReleases.pages
|
|
||||||
.whereType<Page<AlbumSimple>>()
|
|
||||||
.expand((page) => page.items ?? const <AlbumSimple>[])
|
|
||||||
.map((album) => TypeConversionUtils.simpleAlbum_X_Album(album));
|
|
||||||
|
|
||||||
final userArtistReleases = allReleases.where((album) {
|
if (auth == null ||
|
||||||
return album.artists
|
newReleases.isLoading ||
|
||||||
?.any((artist) => userArtists.contains(artist.id!)) ==
|
newReleases.value?.items.isEmpty == true) {
|
||||||
true;
|
return const SizedBox.shrink();
|
||||||
}).toList();
|
}
|
||||||
|
|
||||||
if (userArtistReleases.isEmpty) return allReleases.toList();
|
|
||||||
return userArtistReleases;
|
|
||||||
},
|
|
||||||
[newReleases.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
final hasNewReleases = newReleases.hasPageData &&
|
|
||||||
userArtistsQuery.hasData &&
|
|
||||||
!newReleases.isLoadingNextPage;
|
|
||||||
|
|
||||||
if (auth == null || !hasNewReleases) return const SizedBox.shrink();
|
|
||||||
|
|
||||||
return HorizontalPlaybuttonCardView<Album>(
|
return HorizontalPlaybuttonCardView<Album>(
|
||||||
items: albums,
|
items: albums,
|
||||||
title: Text(context.l10n.new_releases),
|
title: Text(context.l10n.new_releases),
|
||||||
isLoadingNextPage: newReleases.isLoadingNextPage,
|
isLoadingNextPage: newReleases.isLoadingNextPage,
|
||||||
hasNextPage: newReleases.hasNextPage,
|
hasNextPage: newReleases.value?.hasMore ?? false,
|
||||||
onFetchMore: newReleases.fetchNext,
|
onFetchMore: newReleasesNotifier.fetchMore,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,13 +24,12 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
|||||||
required this.hasNextPage,
|
required this.hasNextPage,
|
||||||
required this.onFetchMore,
|
required this.onFetchMore,
|
||||||
required this.isLoadingNextPage,
|
required this.isLoadingNextPage,
|
||||||
Key? key,
|
super.key,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
items is List<PlaylistSimple> ||
|
items is List<PlaylistSimple> ||
|
||||||
items is List<Album> ||
|
items is List<Album> ||
|
||||||
items is List<Artist>,
|
items is List<Artist>,
|
||||||
),
|
);
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@ -12,7 +10,7 @@ import 'package:spotube/components/shared/image/universal_image.dart';
|
|||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
|
|
||||||
@ -22,23 +20,10 @@ class GenrePlaylistsPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final playlistsQuery = useQueries.category.playlistsOf(
|
|
||||||
ref,
|
|
||||||
category.id!,
|
|
||||||
);
|
|
||||||
|
|
||||||
final playlists = useMemoized(
|
|
||||||
() => playlistsQuery.pages.expand(
|
|
||||||
(page) {
|
|
||||||
return page.items?.whereNotNull() ??
|
|
||||||
const Iterable<PlaylistSimple>.empty();
|
|
||||||
},
|
|
||||||
).toList(),
|
|
||||||
[playlistsQuery.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
final playlists = ref.watch(categoryPlaylistsProvider(category.id!));
|
||||||
|
final playlistsNotifier =
|
||||||
|
ref.read(categoryPlaylistsProvider(category.id!).notifier);
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -109,7 +94,7 @@ class GenrePlaylistsPage extends HookConsumerWidget {
|
|||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: mediaQuery.mdAndDown ? 12 : 24,
|
horizontal: mediaQuery.mdAndDown ? 12 : 24,
|
||||||
),
|
),
|
||||||
sliver: playlists.isEmpty
|
sliver: playlists.value?.items.isNotEmpty != true
|
||||||
? Skeletonizer.sliver(
|
? Skeletonizer.sliver(
|
||||||
child: SliverToBoxAdapter(
|
child: SliverToBoxAdapter(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
@ -129,12 +114,13 @@ class GenrePlaylistsPage extends HookConsumerWidget {
|
|||||||
crossAxisSpacing: 12,
|
crossAxisSpacing: 12,
|
||||||
mainAxisSpacing: 12,
|
mainAxisSpacing: 12,
|
||||||
),
|
),
|
||||||
itemCount: playlists.length + 1,
|
itemCount: (playlists.value?.items.length ?? 0) + 1,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final playlist = playlists.elementAtOrNull(index);
|
final playlist =
|
||||||
|
playlists.value?.items.elementAtOrNull(index);
|
||||||
|
|
||||||
if (playlist == null) {
|
if (playlist == null) {
|
||||||
if (!playlistsQuery.hasNextPage) {
|
if (playlists.value?.hasMore == false) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return Skeletonizer(
|
return Skeletonizer(
|
||||||
@ -142,11 +128,7 @@ class GenrePlaylistsPage extends HookConsumerWidget {
|
|||||||
child: Waypoint(
|
child: Waypoint(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
isGrid: true,
|
isGrid: true,
|
||||||
onTouchEdge: () async {
|
onTouchEdge: playlistsNotifier.fetchMore,
|
||||||
if (playlistsQuery.hasNextPage) {
|
|
||||||
await playlistsQuery.fetchNext();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: PlaylistCard(FakeData.playlist),
|
child: PlaylistCard(FakeData.playlist),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,14 +5,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart' hide Offset;
|
|
||||||
import 'package:spotube/collections/gradients.dart';
|
import 'package:spotube/collections/gradients.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
|
||||||
|
|
||||||
class GenrePage extends HookConsumerWidget {
|
class GenrePage extends HookConsumerWidget {
|
||||||
const GenrePage({super.key});
|
const GenrePage({super.key});
|
||||||
@ -21,13 +18,7 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme) = Theme.of(context);
|
final ThemeData(:textTheme) = Theme.of(context);
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final recommendationMarket = ref.watch(
|
final categories = ref.watch(categoriesProvider);
|
||||||
userPreferencesProvider.select((s) => s.recommendationMarket),
|
|
||||||
);
|
|
||||||
final categoriesQuery =
|
|
||||||
useQueries.category.listAll(ref, recommendationMarket);
|
|
||||||
|
|
||||||
final categories = categoriesQuery.data ?? <Category>[];
|
|
||||||
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
@ -48,9 +39,9 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
crossAxisSpacing: 12,
|
crossAxisSpacing: 12,
|
||||||
mainAxisSpacing: 12,
|
mainAxisSpacing: 12,
|
||||||
),
|
),
|
||||||
itemCount: categories.length,
|
itemCount: categories.value!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final category = categories[index];
|
final category = categories.value![index];
|
||||||
final gradient = gradients[Random().nextInt(gradients.length)];
|
final gradient = gradients[Random().nextInt(gradients.length)];
|
||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import 'package:spotube/components/home/sections/new_releases.dart';
|
|||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
|
|
||||||
class HomePage extends HookConsumerWidget {
|
class HomePage extends HookConsumerWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
part of '../spotify.dart';
|
part of '../spotify.dart';
|
||||||
|
|
||||||
class AlbumReleasesState extends PaginatedState<AlbumSimple> {
|
class AlbumReleasesState extends PaginatedState<Album> {
|
||||||
AlbumReleasesState({
|
AlbumReleasesState({
|
||||||
required super.items,
|
required super.items,
|
||||||
required super.offset,
|
required super.offset,
|
||||||
@ -10,7 +10,7 @@ class AlbumReleasesState extends PaginatedState<AlbumSimple> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
AlbumReleasesState copyWith({
|
AlbumReleasesState copyWith({
|
||||||
List<AlbumSimple>? items,
|
List<Album>? items,
|
||||||
int? offset,
|
int? offset,
|
||||||
int? limit,
|
int? limit,
|
||||||
bool? hasMore,
|
bool? hasMore,
|
||||||
@ -25,16 +25,21 @@ class AlbumReleasesState extends PaginatedState<AlbumSimple> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AlbumReleasesNotifier
|
class AlbumReleasesNotifier
|
||||||
extends PaginatedAsyncNotifier<AlbumSimple, AlbumReleasesState> {
|
extends PaginatedAsyncNotifier<Album, AlbumReleasesState> {
|
||||||
AlbumReleasesNotifier() : super();
|
AlbumReleasesNotifier() : super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
fetch(int offset, int limit) async {
|
fetch(int offset, int limit) async {
|
||||||
final market = ref.read(userPreferencesProvider).recommendationMarket;
|
final market = ref.read(userPreferencesProvider).recommendationMarket;
|
||||||
|
|
||||||
final albums = await spotify.browse
|
final albums = await spotify.browse
|
||||||
.newReleases(country: market)
|
.newReleases(country: market)
|
||||||
.getPage(offset, limit);
|
.getPage(limit, offset);
|
||||||
return albums.items?.toList() ?? [];
|
|
||||||
|
return albums.items
|
||||||
|
?.map(TypeConversionUtils.simpleAlbum_X_Album)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -43,7 +48,10 @@ class AlbumReleasesNotifier
|
|||||||
ref.watch(
|
ref.watch(
|
||||||
userPreferencesProvider.select((s) => s.recommendationMarket),
|
userPreferencesProvider.select((s) => s.recommendationMarket),
|
||||||
);
|
);
|
||||||
|
ref.watch(allFollowedArtistsProvider);
|
||||||
|
|
||||||
final albums = await fetch(0, 20);
|
final albums = await fetch(0, 20);
|
||||||
|
|
||||||
return AlbumReleasesState(
|
return AlbumReleasesState(
|
||||||
items: albums,
|
items: albums,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
@ -57,3 +65,26 @@ final albumReleasesProvider =
|
|||||||
AsyncNotifierProvider<AlbumReleasesNotifier, AlbumReleasesState>(
|
AsyncNotifierProvider<AlbumReleasesNotifier, AlbumReleasesState>(
|
||||||
() => AlbumReleasesNotifier(),
|
() => AlbumReleasesNotifier(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final userArtistAlbumReleasesProvider = Provider<List<Album>>((ref) {
|
||||||
|
final newReleases = ref.watch(albumReleasesProvider);
|
||||||
|
final userArtistsQuery = ref.watch(allFollowedArtistsProvider);
|
||||||
|
|
||||||
|
if (newReleases.isLoading || userArtistsQuery.isLoading) {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final userArtists =
|
||||||
|
userArtistsQuery.value?.map((s) => s.id!).toList() ?? const [];
|
||||||
|
|
||||||
|
final allReleases = newReleases.value?.items;
|
||||||
|
final userArtistReleases = allReleases?.where((album) {
|
||||||
|
return album.artists?.any((artist) => userArtists.contains(artist.id!)) ==
|
||||||
|
true;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (userArtistReleases?.isEmpty == true) {
|
||||||
|
return allReleases?.toList() ?? [];
|
||||||
|
}
|
||||||
|
return userArtistReleases ?? [];
|
||||||
|
});
|
||||||
|
|||||||
@ -30,7 +30,7 @@ class AlbumTracksNotifier extends FamilyPaginatedAsyncNotifier<TrackSimple,
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
fetch(arg, offset, limit) async {
|
fetch(arg, offset, limit) async {
|
||||||
final tracks = await spotify.albums.tracks(arg).getPage(offset, limit);
|
final tracks = await spotify.albums.tracks(arg).getPage(limit, offset);
|
||||||
return tracks.items?.toList() ?? [];
|
return tracks.items?.toList() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class ArtistAlbumsNotifier extends FamilyPaginatedAsyncNotifier<AlbumSimple,
|
|||||||
final market = ref.read(userPreferencesProvider).recommendationMarket;
|
final market = ref.read(userPreferencesProvider).recommendationMarket;
|
||||||
final albums = await spotify.artists
|
final albums = await spotify.artists
|
||||||
.albums(arg, country: market)
|
.albums(arg, country: market)
|
||||||
.getPage(offset, limit);
|
.getPage(limit, offset);
|
||||||
|
|
||||||
return albums.items?.toList() ?? [];
|
return albums.items?.toList() ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class CategoryPlaylistsNotifier extends FamilyPaginatedAsyncNotifier<
|
|||||||
(json) => json == null ? null : PlaylistSimple.fromJson(json),
|
(json) => json == null ? null : PlaylistSimple.fromJson(json),
|
||||||
'playlists',
|
'playlists',
|
||||||
(json) => PlaylistsFeatured.fromJson(json),
|
(json) => PlaylistsFeatured.fromJson(json),
|
||||||
).getPage(offset, limit);
|
).getPage(limit, offset);
|
||||||
|
|
||||||
return playlists.items?.whereNotNull().toList() ?? [];
|
return playlists.items?.whereNotNull().toList() ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,6 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final playlistProvider =
|
final playlistProvider =
|
||||||
AsyncNotifierProvider.family<PlaylistNotifier, String, PlaylistNotifier>(
|
AsyncNotifierProviderFamily<PlaylistNotifier, Playlist, String>(
|
||||||
() => PlaylistNotifier(),
|
() => PlaylistNotifier(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import 'package:spotube/provider/spotify_provider.dart';
|
|||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
part 'album/favorite.dart';
|
part 'album/favorite.dart';
|
||||||
part 'album/tracks.dart';
|
part 'album/tracks.dart';
|
||||||
@ -58,3 +59,4 @@ part 'utils/mixin.dart';
|
|||||||
part 'utils/state.dart';
|
part 'utils/state.dart';
|
||||||
part 'utils/provider.dart';
|
part 'utils/provider.dart';
|
||||||
part 'utils/persistence.dart';
|
part 'utils/persistence.dart';
|
||||||
|
part 'utils/async.dart';
|
||||||
|
|||||||
6
lib/provider/spotify/utils/async.dart
Normal file
6
lib/provider/spotify/utils/async.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
part of '../spotify.dart';
|
||||||
|
|
||||||
|
extension PaginationExtension<T> on AsyncValue<T> {
|
||||||
|
bool get isLoadingAndEmpty => value == null && isLoading;
|
||||||
|
bool get isLoadingNextPage => value != null && isLoading;
|
||||||
|
}
|
||||||
@ -6,17 +6,19 @@ abstract class PaginatedAsyncNotifier<K, T extends BasePaginatedState<K, int>>
|
|||||||
|
|
||||||
Future<void> fetchMore() async {
|
Future<void> fetchMore() async {
|
||||||
if (state.value == null || !state.value!.hasMore) return;
|
if (state.value == null || !state.value!.hasMore) return;
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
await update(
|
state = await AsyncValue.guard(
|
||||||
(state) async {
|
() async {
|
||||||
final items = await fetch(state.offset + state.limit, state.limit);
|
final items = await fetch(
|
||||||
return state.copyWith(
|
state.value!.offset + state.value!.limit, state.value!.limit);
|
||||||
hasMore: items.length == state.limit,
|
return state.value!.copyWith(
|
||||||
|
hasMore: items.length == state.value!.limit,
|
||||||
items: [
|
items: [
|
||||||
...state.items,
|
...state.value!.items,
|
||||||
...items,
|
...items,
|
||||||
],
|
],
|
||||||
offset: state.offset + state.limit,
|
offset: state.value!.offset + state.value!.limit,
|
||||||
) as T;
|
) as T;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -31,13 +33,15 @@ abstract class CursorPaginatedAsyncNotifier<K,
|
|||||||
Future<void> fetchMore() async {
|
Future<void> fetchMore() async {
|
||||||
if (state.value == null || !state.value!.hasMore) return;
|
if (state.value == null || !state.value!.hasMore) return;
|
||||||
|
|
||||||
await update(
|
state = const AsyncValue.loading();
|
||||||
(state) async {
|
|
||||||
final items = await fetch(state.offset, state.limit);
|
state = await AsyncValue.guard(
|
||||||
return state.copyWith(
|
() async {
|
||||||
hasMore: items.$1.length == state.limit,
|
final items = await fetch(state.value!.offset, state.value!.limit);
|
||||||
|
return state.value!.copyWith(
|
||||||
|
hasMore: items.$1.length == state.value!.limit,
|
||||||
items: [
|
items: [
|
||||||
...state.items,
|
...state.value!.items,
|
||||||
...items.$1,
|
...items.$1,
|
||||||
],
|
],
|
||||||
offset: items.$2,
|
offset: items.$2,
|
||||||
@ -56,20 +60,22 @@ abstract class FamilyPaginatedAsyncNotifier<
|
|||||||
Future<void> fetchMore() async {
|
Future<void> fetchMore() async {
|
||||||
if (state.value == null || !state.value!.hasMore) return;
|
if (state.value == null || !state.value!.hasMore) return;
|
||||||
|
|
||||||
await update(
|
state = const AsyncLoading();
|
||||||
(state) async {
|
|
||||||
|
state = await AsyncValue.guard(
|
||||||
|
() async {
|
||||||
final items = await fetch(
|
final items = await fetch(
|
||||||
arg,
|
arg,
|
||||||
state.offset + state.limit,
|
state.value!.offset + state.value!.limit,
|
||||||
state.limit,
|
state.value!.limit,
|
||||||
);
|
);
|
||||||
return state.copyWith(
|
return state.value!.copyWith(
|
||||||
hasMore: items.length == state.limit,
|
hasMore: items.length == state.value!.limit,
|
||||||
items: [
|
items: [
|
||||||
...state.items,
|
...state.value!.items,
|
||||||
...items,
|
...items,
|
||||||
],
|
],
|
||||||
offset: state.offset + state.limit,
|
offset: state.value!.offset + state.value!.limit,
|
||||||
) as T;
|
) as T;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -89,17 +95,15 @@ abstract class FamilyCursorPaginatedAsyncNotifier<
|
|||||||
Future<void> fetchMore() async {
|
Future<void> fetchMore() async {
|
||||||
if (state.value == null || !state.value!.hasMore) return;
|
if (state.value == null || !state.value!.hasMore) return;
|
||||||
|
|
||||||
await update(
|
state = const AsyncLoading();
|
||||||
(state) async {
|
|
||||||
final items = await fetch(
|
state = await AsyncValue.guard(
|
||||||
arg,
|
() async {
|
||||||
state.offset,
|
final items = await fetch(arg, state.value!.offset, state.value!.limit);
|
||||||
state.limit,
|
return state.value!.copyWith(
|
||||||
);
|
hasMore: items.$1.length == state.value!.limit,
|
||||||
return state.copyWith(
|
|
||||||
hasMore: items.$1.length == state.limit,
|
|
||||||
items: [
|
items: [
|
||||||
...state.items,
|
...state.value!.items,
|
||||||
...items.$1,
|
...items.$1,
|
||||||
],
|
],
|
||||||
offset: items.$2,
|
offset: items.$2,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user