feat: smoother list using fl_query and waypoint

fix(theme): remove splash effect
feat(artists-albums): horizontal paginated list instead of grid view page
This commit is contained in:
Kingkor Roy Tirtho 2022-10-02 11:04:27 +06:00
parent 7eea968bcf
commit c77b0e198b
21 changed files with 385 additions and 331 deletions

View File

@ -0,0 +1,73 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumCard.dart';
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
import 'package:spotube/components/Shared/Waypoint.dart';
import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
class ArtistAlbumList extends HookConsumerWidget {
final String artistId;
ArtistAlbumList(
this.artistId, {
Key? key,
}) : super(key: key);
final logger = getLogger(ArtistAlbumList);
@override
Widget build(BuildContext context, ref) {
final scrollController = useScrollController();
final albumsQuery = useInfiniteQuery(
job: artistAlbumsQueryJob(artistId),
externalData: ref.watch(spotifyProvider),
);
final albums = useMemoized(() {
return albumsQuery.pages
.expand<Album>((page) => page?.items ?? const Iterable.empty())
.toList();
}, [albumsQuery.pages]);
final hasNextPage = albumsQuery.pages.isEmpty
? false
: (albumsQuery.pages.last?.items?.length ?? 0) == 5;
return SizedBox(
height: 300,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: Scrollbar(
interactive: false,
controller: scrollController,
child: ListView.builder(
itemCount: albums.length,
controller: scrollController,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
if (index == albums.length - 1 && hasNextPage) {
return Waypoint(
onEnter: () {
albumsQuery.fetchNextPage();
},
child: const ShimmerPlaybuttonCard(count: 1),
);
}
return AlbumCard(albums[index]);
},
),
),
),
);
}
}

View File

@ -1,95 +0,0 @@
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumCard.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/SpotifyDI.dart';
class ArtistAlbumView extends ConsumerStatefulWidget {
final String artistId;
final String artistName;
const ArtistAlbumView(
this.artistId,
this.artistName, {
Key? key,
}) : super(key: key);
@override
ConsumerState<ArtistAlbumView> createState() => _ArtistAlbumViewState();
}
class _ArtistAlbumViewState extends ConsumerState<ArtistAlbumView> {
final PagingController<int, Album> _pagingController =
PagingController<int, Album>(firstPageKey: 0);
final logger = getLogger(ArtistAlbumView);
@override
void initState() {
super.initState();
_pagingController.addPageRequestListener((pageKey) {
_fetchPage(pageKey);
});
}
@override
void dispose() {
_pagingController.dispose();
super.dispose();
}
_fetchPage(int pageKey) async {
try {
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
Page<Album> albums =
await spotifyApi.artists.albums(widget.artistId).getPage(8, pageKey);
var items = albums.items!.toList();
if (albums.isLast && albums.items != null) {
_pagingController.appendLastPage(items);
} else if (albums.items != null) {
_pagingController.appendPage(items, albums.nextOffset);
}
} catch (e, stack) {
logger.e(e, null, stack);
_pagingController.error = e;
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: const PageWindowTitleBar(leading: BackButton()),
body: Column(
children: [
Text(
widget.artistName,
style: Theme.of(context).textTheme.headline4,
),
Expanded(
child: PagedGridView(
pagingController: _pagingController,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 260,
childAspectRatio: 9 / 13,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
),
padding: const EdgeInsets.all(10),
builderDelegate: PagedChildBuilderDelegate<Album>(
itemBuilder: (context, item, index) {
return AlbumCard(item);
},
),
),
),
],
),
),
);
}
}

View File

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumCard.dart';
import 'package:spotube/components/Artist/ArtistAlbumList.dart';
import 'package:spotube/components/Artist/ArtistCard.dart';
import 'package:spotube/components/LoaderShimmers/ShimmerArtistProfile.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
@ -28,7 +27,6 @@ class ArtistProfile extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
SpotifyApi spotify = ref.watch(spotifyProvider);
final scrollController = useScrollController();
final parentScrollController = useScrollController();
final textTheme = Theme.of(context).textTheme;
final chipTextVariant = useBreakpointValue(
@ -55,7 +53,7 @@ class ArtistProfile extends HookConsumerWidget {
final isFollowingSnapshot =
ref.watch(currentUserFollowsArtistQuery(artistId));
final topTracksSnapshot = ref.watch(artistTopTracksQuery(artistId));
final albums = ref.watch(artistAlbumsQuery(artistId));
final relatedArtists = ref.watch(artistRelatedArtistsQuery(artistId));
return SafeArea(
@ -263,46 +261,12 @@ class ArtistProfile extends HookConsumerWidget {
child: CircularProgressIndicator.adaptive()),
),
const SizedBox(height: 50),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Albums",
style: Theme.of(context).textTheme.headline4,
),
TextButton(
child: const Text("See All"),
onPressed: () {
GoRouter.of(context).push(
"/artist-album/$artistId",
extra: data.name ?? "KRTX",
);
},
)
],
Text(
"Albums",
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 10),
albums.when(
data: (albums) {
return Scrollbar(
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: albums.items
?.map((album) => AlbumCard(album))
.toList() ??
[],
),
),
);
},
error: (error, stackTrack) =>
Text("Failed to get Artist albums $error"),
loading: () => const CircularProgressIndicator.adaptive(),
),
ArtistAlbumList(artistId),
const SizedBox(height: 20),
Text(
"Fans also likes",

View File

@ -1,14 +1,14 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
import 'package:spotube/components/Playlist/PlaylistCard.dart';
import 'package:spotube/components/Shared/NotFound.dart';
import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
import 'package:spotube/components/Shared/Waypoint.dart';
import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
class CategoryCard extends HookConsumerWidget {
@ -25,29 +25,20 @@ class CategoryCard extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final scrollController = useScrollController();
final mounted = useIsMounted();
final pagingController =
usePaginatedFutureProvider<Page<PlaylistSimple>, int, PlaylistSimple>(
(pageKey) => categoryPlaylistsQuery(
[
category.id,
pageKey,
].join("/"),
),
ref: ref,
firstPageKey: 0,
onData: (page, pagingController, pageKey) {
if (playlists != null && playlists?.isNotEmpty == true && mounted()) {
return pagingController.appendLastPage(playlists!.toList());
}
if (page.isLast && page.items != null) {
pagingController.appendLastPage(page.items!.toList());
} else if (page.items != null) {
pagingController.appendPage(page.items!.toList(), page.nextOffset);
}
},
final spotify = ref.watch(spotifyProvider);
final playlistQuery = useInfiniteQuery(
job: categoryPlaylistsQueryJob(category.id!),
externalData: spotify,
);
final hasNextPage = playlistQuery.pages.isEmpty
? false
: (playlistQuery.pages.last?.items?.length ?? 0) == 5;
final playlists = playlistQuery.pages
.expand(
(page) => page?.items ?? const Iterable.empty(),
)
.toList();
return Column(
children: [
@ -62,8 +53,8 @@ class CategoryCard extends HookConsumerWidget {
],
),
),
pagingController.error != null
? const Text("Something Went Wrong")
playlistQuery.hasError
? Text("Something Went Wrong\n${playlistQuery.errors.first}")
: SizedBox(
height: 245,
child: ScrollConfiguration(
@ -76,26 +67,21 @@ class CategoryCard extends HookConsumerWidget {
child: Scrollbar(
controller: scrollController,
interactive: false,
child: PagedListView<int, PlaylistSimple>(
shrinkWrap: true,
pagingController: pagingController,
scrollController: scrollController,
child: ListView.builder(
scrollDirection: Axis.horizontal,
builderDelegate:
PagedChildBuilderDelegate<PlaylistSimple>(
noItemsFoundIndicatorBuilder: (context) {
return const NotFound();
},
firstPageProgressIndicatorBuilder: (context) {
return const ShimmerPlaybuttonCard();
},
newPageProgressIndicatorBuilder: (context) {
return const ShimmerPlaybuttonCard();
},
itemBuilder: (context, playlist, index) {
return PlaylistCard(playlist);
},
),
shrinkWrap: true,
itemCount: playlists.length,
itemBuilder: (context, index) {
if (index == playlists.length - 1 && hasNextPage) {
return Waypoint(
onEnter: () {
playlistQuery.fetchNextPage();
},
child: const ShimmerPlaybuttonCard(count: 1),
);
}
return PlaylistCard(playlists[index]);
},
),
),
),

View File

@ -1,9 +1,9 @@
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:spotify/spotify.dart' hide Image, Player, Search;
import 'package:spotube/components/Category/CategoryCard.dart';
@ -16,12 +16,14 @@ import 'package:spotube/components/Shared/ReplaceDownloadedFileDialog.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Player/Player.dart';
import 'package:spotube/components/Library/UserLibrary.dart';
import 'package:spotube/components/Shared/Waypoint.dart';
import 'package:spotube/hooks/useBreakpointValue.dart';
import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
import 'package:spotube/hooks/useUpdateChecker.dart';
import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/Downloader.dart';
import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/utils/platform.dart';
List<String> spotifyScopes = [
@ -144,38 +146,43 @@ class Home extends HookConsumerWidget {
left: 8.0,
),
child: HookBuilder(builder: (context) {
final pagingController = usePaginatedFutureProvider<
Page<Category>, int, Category>(
(pageKey) => categoriesQuery(pageKey),
ref: ref,
firstPageKey: 0,
onData: (categories, pagingController, pageKey) {
final items = categories.items?.toList();
if (pageKey == 0) {
Category category = Category();
category.id = "user-featured-playlists";
category.name = "Featured";
items?.insert(0, category);
}
if (categories.isLast && items != null) {
pagingController.appendLastPage(items);
} else if (categories.items != null) {
pagingController.appendPage(
items!, categories.nextOffset);
}
final spotify = ref.watch(spotifyProvider);
final recommendationMarket = ref.watch(
userPreferencesProvider
.select((s) => s.recommendationMarket),
);
final categoriesQuery = useInfiniteQuery(
job: categoriesQueryJob,
externalData: {
"spotify": spotify,
"recommendationMarket": recommendationMarket,
},
);
return PagedListView(
pagingController: pagingController,
builderDelegate: PagedChildBuilderDelegate<Category>(
firstPageProgressIndicatorBuilder: (_) =>
const ShimmerCategories(),
newPageProgressIndicatorBuilder: (_) =>
const ShimmerCategories(),
itemBuilder: (context, item, index) {
return CategoryCard(item);
},
),
final categories = categoriesQuery.pages
.expand<Category?>(
(page) => page?.items ?? const Iterable.empty(),
)
.toList();
return ListView.builder(
itemCount: categories.length,
itemBuilder: (context, index) {
final category = categories[index];
if (category == null) return Container();
if (index == categories.length - 1) {
return Waypoint(
onEnter: () {
if (categoriesQuery.hasNextPage) {
categoriesQuery.fetchNextPage();
}
},
child: const ShimmerCategories(),
);
}
return CategoryCard(category);
},
);
}),
),

View File

@ -1,35 +1,35 @@
import 'package:fl_query_hooks/fl_query_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Artist/ArtistCard.dart';
import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
import 'package:spotube/models/Logger.dart';
import 'package:spotube/components/Shared/Waypoint.dart';
import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
class UserArtists extends HookConsumerWidget {
UserArtists({Key? key}) : super(key: key);
final logger = getLogger(UserArtists);
const UserArtists({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final pagingController =
usePaginatedFutureProvider<CursorPage<Artist>, String, Artist>(
(pageKey) => currentUserFollowingArtistsQuery(pageKey),
ref: ref,
firstPageKey: "",
onData: (artists, pagingController, pageKey) {
final items = artists.items!.toList();
if (artists.items != null && items.length < 15) {
pagingController.appendLastPage(items);
} else if (artists.items != null) {
pagingController.appendPage(items, items.last.id);
}
},
final artistQuery = useInfiniteQuery(
job: currentUserFollowingArtistsQueryJob,
externalData: ref.watch(spotifyProvider),
);
return PagedGridView(
final artists = useMemoized(
() => artistQuery.pages
.expand<Artist>((page) => page?.items ?? const Iterable.empty())
.toList(),
[artistQuery.pages]);
final hasNextPage = artistQuery.pages.isEmpty
? false
: (artistQuery.pages.last?.items?.length ?? 0) == 15;
return GridView.builder(
itemCount: artists.length,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisExtent: 250,
@ -37,12 +37,17 @@ class UserArtists extends HookConsumerWidget {
mainAxisSpacing: 20,
),
padding: const EdgeInsets.all(10),
pagingController: pagingController,
builderDelegate: PagedChildBuilderDelegate<Artist>(
itemBuilder: (context, item, index) {
return ArtistCard(item);
},
),
itemBuilder: (context, index) {
if (index == artists.length - 1 && hasNextPage) {
return Waypoint(
onEnter: () {
artistQuery.fetchNextPage();
},
child: ArtistCard(artists[index]),
);
}
return ArtistCard(artists[index]);
},
);
}
}

View File

@ -6,6 +6,7 @@ import 'package:spotube/components/Library/UserDownloads.dart';
import 'package:spotube/components/Library/UserLocalTracks.dart';
import 'package:spotube/components/Library/UserPlaylists.dart';
import 'package:spotube/components/Shared/AnonymousFallback.dart';
import 'package:spotube/components/Shared/ColoredTabBar.dart';
class UserLibrary extends ConsumerWidget {
const UserLibrary({Key? key}) : super(key: key);
@ -16,22 +17,25 @@ class UserLibrary extends ConsumerWidget {
length: 5,
child: SafeArea(
child: Scaffold(
appBar: const TabBar(
isScrollable: true,
tabs: [
Tab(text: "Playlist"),
Tab(text: "Downloads"),
Tab(text: "Local"),
Tab(text: "Artists"),
Tab(text: "Album"),
],
appBar: ColoredTabBar(
color: Theme.of(context).backgroundColor,
child: const TabBar(
isScrollable: true,
tabs: [
Tab(text: "Playlist"),
Tab(text: "Downloads"),
Tab(text: "Local"),
Tab(text: "Artists"),
Tab(text: "Album"),
],
),
),
body: TabBarView(children: [
const AnonymousFallback(child: UserPlaylists()),
const UserDownloads(),
const UserLocalTracks(),
body: const TabBarView(children: [
AnonymousFallback(child: UserPlaylists()),
UserDownloads(),
UserLocalTracks(),
AnonymousFallback(child: UserArtists()),
const AnonymousFallback(child: UserAlbums()),
AnonymousFallback(child: UserAlbums()),
]),
),
),

View File

@ -60,6 +60,10 @@ class Search extends HookConsumerWidget {
controller.value.text;
},
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 7,
),
hintStyle: const TextStyle(height: 2),
hintText: "Search...",
),
@ -93,7 +97,9 @@ class Search extends HookConsumerWidget {
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 20),
vertical: 8,
horizontal: 20,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
class ColoredTabBar extends ColoredBox implements PreferredSizeWidget {
final TabBar child;
const ColoredTabBar({
required super.color,
required this.child,
super.key,
}) : super(child: child);
@override
Size get preferredSize => child.preferredSize;
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/cupertino.dart';
import 'package:visibility_detector/visibility_detector.dart';
class Waypoint extends StatelessWidget {
final void Function()? onEnter;
final void Function()? onLeave;
final Widget? child;
const Waypoint({
Key? key,
this.onEnter,
this.onLeave,
this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return VisibilityDetector(
key: const Key("waypoint"),
onVisibilityChanged: (info) {
if (info.visibleFraction == 0) {
onLeave?.call();
} else if (info.visibleFraction > 0) {
onEnter?.call();
}
},
child: child ?? Container(),
);
}
}

View File

@ -1,42 +0,0 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hookified_infinite_scroll_pagination/hookified_infinite_scroll_pagination.dart';
PagingController<P, ItemType> usePaginatedFutureProvider<T, P, ItemType>(
FutureProvider<T> Function(P pageKey) createSnapshot, {
required P firstPageKey,
required WidgetRef ref,
void Function(
T,
PagingController<P, ItemType> pagingController,
P pageKey,
)?
onData,
void Function(Object)? onError,
void Function()? onLoading,
}) {
final currentPageKey = useState(firstPageKey);
final snapshot = ref.watch(createSnapshot(currentPageKey.value));
final pagingController = usePagingController<P, ItemType>(
firstPageKey: firstPageKey,
onPageRequest: (pageKey, pagingController) {
if (currentPageKey.value != pageKey) {
currentPageKey.value = pageKey;
}
});
useEffect(() {
snapshot.whenOrNull(
data: (data) =>
onData?.call(data, pagingController, currentPageKey.value),
error: (error, _) {
pagingController.error = error;
return onError?.call(error);
},
loading: onLoading,
);
return null;
}, [currentPageKey, snapshot]);
return pagingController;
}

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:audio_service/audio_service.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fl_query/fl_query.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@ -25,6 +26,7 @@ import 'package:spotube/themes/dark-theme.dart';
import 'package:spotube/themes/light-theme.dart';
import 'package:spotube/utils/platform.dart';
final bowl = QueryBowl();
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(CacheTrackAdapter());
@ -124,7 +126,10 @@ void main() async {
),
)
],
child: const Spotube(),
child: QueryBowlScope(
bowl: bowl,
child: const Spotube(),
),
);
},
),

View File

@ -1,7 +1,6 @@
import 'package:go_router/go_router.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumView.dart';
import 'package:spotube/components/Artist/ArtistAlbumView.dart';
import 'package:spotube/components/Artist/ArtistProfile.dart';
import 'package:spotube/components/Home/Home.dart';
import 'package:spotube/components/Login/Login.dart';
@ -49,19 +48,6 @@ GoRouter createGoRouter() => GoRouter(
return SpotubePage(child: ArtistProfile(state.params["id"]!));
},
),
GoRoute(
path: "/artist-album/:id",
pageBuilder: (context, state) {
assert(state.params["id"] != null);
assert(state.extra is String);
return SpotubePage(
child: ArtistAlbumView(
state.params["id"]!,
state.extra as String,
),
);
},
),
GoRoute(
path: "/playlist/:id",
pageBuilder: (context, state) {

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:fl_query/fl_query.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/models/Logger.dart';
import 'package:spotube/models/LyricsModels.dart';
@ -11,29 +12,35 @@ import 'package:collection/collection.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
final categoriesQuery = FutureProvider.family<Page<Category>, int>(
(ref, pageKey) {
final spotify = ref.watch(spotifyProvider);
final recommendationMarket = ref.watch(
userPreferencesProvider.select((s) => s.recommendationMarket),
);
return spotify.categories
final categoriesQueryJob =
InfiniteQueryJob<Page<Category>, Map<String, dynamic>, int>(
queryKey: "categories-query",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 16,
task: (queryKey, pageParam, data) async {
final SpotifyApi spotify = data["spotify"] as SpotifyApi;
final String recommendationMarket = data["recommendationMarket"];
final categories = await spotify.categories
.list(country: recommendationMarket)
.getPage(15, pageKey);
.getPage(15, pageParam);
return categories;
},
);
final categoryPlaylistsQuery =
FutureProvider.family<Page<PlaylistSimple>, String>(
(ref, value) {
final spotify = ref.watch(spotifyProvider);
final List data = value.split("/");
final id = data.first;
final pageKey = data.last;
final categoryPlaylistsQueryJob =
InfiniteQueryJob.withVariableKey<Page<PlaylistSimple>, SpotifyApi, int>(
preQueryKey: "category-playlists",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
task: (queryKey, pageKey, spotify) {
final id = getVariable(queryKey);
return (id != "user-featured-playlists"
? spotify.playlists.getByCategoryId(id)
: spotify.playlists.featured)
.getPage(3, int.parse(pageKey));
.getPage(5, pageKey);
},
);
@ -59,6 +66,18 @@ final currentUserFollowingArtistsQuery =
},
);
final currentUserFollowingArtistsQueryJob =
InfiniteQueryJob<CursorPage<Artist>, SpotifyApi, String>(
queryKey: "user-following-artists",
initialParam: "",
getNextPageParam: (lastPage, lastParam) => lastPage.after,
getPreviousPageParam: (lastPage, lastParam) =>
lastPage.metadata.previous ?? "",
task: (queryKey, pageKey, spotify) {
return spotify.me.following(FollowingType.artist).getPage(15, pageKey);
},
);
final artistProfileQuery = FutureProvider.family<Artist, String>(
(ref, id) {
final spotify = ref.watch(spotifyProvider);
@ -90,6 +109,18 @@ final artistAlbumsQuery = FutureProvider.family<Page<Album>, String>(
},
);
final artistAlbumsQueryJob =
InfiniteQueryJob.withVariableKey<Page<Album>, SpotifyApi, int>(
preQueryKey: "artist-albums",
initialParam: 0,
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
task: (queryKey, pageKey, spotify) {
final id = getVariable(queryKey);
return spotify.artists.albums(id).getPage(5, pageKey);
},
);
final artistRelatedArtistsQuery =
FutureProvider.family<Iterable<Artist>, String>(
(ref, id) {

View File

@ -15,6 +15,7 @@ ThemeData darkTheme({
)
],
primaryColor: accentMaterialColor,
splashFactory: NoSplash.splashFactory,
primarySwatch: accentMaterialColor,
backgroundColor: backgroundMaterialColor[900],
scaffoldBackgroundColor: backgroundMaterialColor[900],
@ -56,7 +57,7 @@ ThemeData darkTheme({
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
onPrimary: accentMaterialColor[300],
foregroundColor: accentMaterialColor[300],
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
),

View File

@ -80,9 +80,10 @@ ThemeData lightTheme({
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
color: backgroundMaterialColor[50],
),
splashFactory: NoSplash.splashFactory,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
onPrimary: accentMaterialColor[800],
foregroundColor: accentMaterialColor[800],
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
),

View File

@ -9,6 +9,7 @@ import audio_service
import audio_session
import audioplayers_darwin
import bitsdojo_window_macos
import connectivity_plus_macos
import package_info_plus_macos
import path_provider_macos
import shared_preferences_macos
@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View File

@ -346,6 +346,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
connectivity_plus:
dependency: transitive
description:
name: connectivity_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.7"
connectivity_plus_linux:
dependency: transitive
description:
name: connectivity_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
connectivity_plus_macos:
dependency: transitive
description:
name: connectivity_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.4"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
connectivity_plus_web:
dependency: transitive
description:
name: connectivity_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.4"
connectivity_plus_windows:
dependency: transitive
description:
name: connectivity_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.2"
convert:
dependency: transitive
description:
@ -437,6 +479,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
fl_query:
dependency: "direct main"
description:
name: fl_query
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
fl_query_hooks:
dependency: "direct main"
description:
name: fl_query_hooks
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
flutter:
dependency: "direct main"
description: flutter
@ -732,6 +788,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
nm:
dependency: transitive
description:
name: nm
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
oauth2:
dependency: transitive
description:
@ -1262,6 +1325,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
visibility_detector:
dependency: "direct main"
description:
name: visibility_detector
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3"
watcher:
dependency: transitive
description:

View File

@ -54,6 +54,9 @@ dependencies:
badges: ^2.0.3
mime: ^1.0.2
metadata_god: ^0.2.0
visibility_detector: ^0.3.3
fl_query: ^0.3.0
fl_query_hooks: ^0.3.0
dev_dependencies:
flutter_test:

View File

@ -8,6 +8,7 @@
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
#include <metadata_god/metadata_god_plugin_c_api.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
BitsdojoWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
MetadataGodPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MetadataGodPluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
bitsdojo_window_windows
connectivity_plus_windows
metadata_god
permission_handler_windows
url_launcher_windows