mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
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:
parent
7eea968bcf
commit
c77b0e198b
73
lib/components/Artist/ArtistAlbumList.dart
Normal file
73
lib/components/Artist/ArtistAlbumList.dart
Normal 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]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.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';
|
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/Artist/ArtistCard.dart';
|
||||||
import 'package:spotube/components/LoaderShimmers/ShimmerArtistProfile.dart';
|
import 'package:spotube/components/LoaderShimmers/ShimmerArtistProfile.dart';
|
||||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||||
@ -28,7 +27,6 @@ class ArtistProfile extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
SpotifyApi spotify = ref.watch(spotifyProvider);
|
SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||||
final scrollController = useScrollController();
|
|
||||||
final parentScrollController = useScrollController();
|
final parentScrollController = useScrollController();
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final chipTextVariant = useBreakpointValue(
|
final chipTextVariant = useBreakpointValue(
|
||||||
@ -55,7 +53,7 @@ class ArtistProfile extends HookConsumerWidget {
|
|||||||
final isFollowingSnapshot =
|
final isFollowingSnapshot =
|
||||||
ref.watch(currentUserFollowsArtistQuery(artistId));
|
ref.watch(currentUserFollowsArtistQuery(artistId));
|
||||||
final topTracksSnapshot = ref.watch(artistTopTracksQuery(artistId));
|
final topTracksSnapshot = ref.watch(artistTopTracksQuery(artistId));
|
||||||
final albums = ref.watch(artistAlbumsQuery(artistId));
|
|
||||||
final relatedArtists = ref.watch(artistRelatedArtistsQuery(artistId));
|
final relatedArtists = ref.watch(artistRelatedArtistsQuery(artistId));
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
@ -263,46 +261,12 @@ class ArtistProfile extends HookConsumerWidget {
|
|||||||
child: CircularProgressIndicator.adaptive()),
|
child: CircularProgressIndicator.adaptive()),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
Text(
|
||||||
"Albums",
|
"Albums",
|
||||||
style: Theme.of(context).textTheme.headline4,
|
style: Theme.of(context).textTheme.headline4,
|
||||||
),
|
),
|
||||||
TextButton(
|
|
||||||
child: const Text("See All"),
|
|
||||||
onPressed: () {
|
|
||||||
GoRouter.of(context).push(
|
|
||||||
"/artist-album/$artistId",
|
|
||||||
extra: data.name ?? "KRTX",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
albums.when(
|
ArtistAlbumList(artistId),
|
||||||
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(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
"Fans also likes",
|
"Fans also likes",
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
|
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
||||||
import 'package:spotube/components/Shared/NotFound.dart';
|
import 'package:spotube/components/Shared/Waypoint.dart';
|
||||||
import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
|
|
||||||
import 'package:spotube/models/Logger.dart';
|
import 'package:spotube/models/Logger.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
|
||||||
class CategoryCard extends HookConsumerWidget {
|
class CategoryCard extends HookConsumerWidget {
|
||||||
@ -25,29 +25,20 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final mounted = useIsMounted();
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
final playlistQuery = useInfiniteQuery(
|
||||||
final pagingController =
|
job: categoryPlaylistsQueryJob(category.id!),
|
||||||
usePaginatedFutureProvider<Page<PlaylistSimple>, int, PlaylistSimple>(
|
externalData: spotify,
|
||||||
(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 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(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@ -62,8 +53,8 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pagingController.error != null
|
playlistQuery.hasError
|
||||||
? const Text("Something Went Wrong")
|
? Text("Something Went Wrong\n${playlistQuery.errors.first}")
|
||||||
: SizedBox(
|
: SizedBox(
|
||||||
height: 245,
|
height: 245,
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
@ -76,26 +67,21 @@ class CategoryCard extends HookConsumerWidget {
|
|||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
child: PagedListView<int, PlaylistSimple>(
|
child: ListView.builder(
|
||||||
shrinkWrap: true,
|
|
||||||
pagingController: pagingController,
|
|
||||||
scrollController: scrollController,
|
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
builderDelegate:
|
shrinkWrap: true,
|
||||||
PagedChildBuilderDelegate<PlaylistSimple>(
|
itemCount: playlists.length,
|
||||||
noItemsFoundIndicatorBuilder: (context) {
|
itemBuilder: (context, index) {
|
||||||
return const NotFound();
|
if (index == playlists.length - 1 && hasNextPage) {
|
||||||
|
return Waypoint(
|
||||||
|
onEnter: () {
|
||||||
|
playlistQuery.fetchNextPage();
|
||||||
},
|
},
|
||||||
firstPageProgressIndicatorBuilder: (context) {
|
child: const ShimmerPlaybuttonCard(count: 1),
|
||||||
return const ShimmerPlaybuttonCard();
|
);
|
||||||
|
}
|
||||||
|
return PlaylistCard(playlists[index]);
|
||||||
},
|
},
|
||||||
newPageProgressIndicatorBuilder: (context) {
|
|
||||||
return const ShimmerPlaybuttonCard();
|
|
||||||
},
|
|
||||||
itemBuilder: (context, playlist, index) {
|
|
||||||
return PlaylistCard(playlist);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
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/material.dart' hide Page;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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:spotify/spotify.dart' hide Image, Player, Search;
|
||||||
|
|
||||||
import 'package:spotube/components/Category/CategoryCard.dart';
|
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/Shared/PageWindowTitleBar.dart';
|
||||||
import 'package:spotube/components/Player/Player.dart';
|
import 'package:spotube/components/Player/Player.dart';
|
||||||
import 'package:spotube/components/Library/UserLibrary.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/useBreakpointValue.dart';
|
||||||
import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
|
|
||||||
import 'package:spotube/hooks/useUpdateChecker.dart';
|
import 'package:spotube/hooks/useUpdateChecker.dart';
|
||||||
import 'package:spotube/models/Logger.dart';
|
import 'package:spotube/models/Logger.dart';
|
||||||
import 'package:spotube/provider/Downloader.dart';
|
import 'package:spotube/provider/Downloader.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
List<String> spotifyScopes = [
|
List<String> spotifyScopes = [
|
||||||
@ -144,38 +146,43 @@ class Home extends HookConsumerWidget {
|
|||||||
left: 8.0,
|
left: 8.0,
|
||||||
),
|
),
|
||||||
child: HookBuilder(builder: (context) {
|
child: HookBuilder(builder: (context) {
|
||||||
final pagingController = usePaginatedFutureProvider<
|
final spotify = ref.watch(spotifyProvider);
|
||||||
Page<Category>, int, Category>(
|
final recommendationMarket = ref.watch(
|
||||||
(pageKey) => categoriesQuery(pageKey),
|
userPreferencesProvider
|
||||||
ref: ref,
|
.select((s) => s.recommendationMarket),
|
||||||
firstPageKey: 0,
|
);
|
||||||
onData: (categories, pagingController, pageKey) {
|
|
||||||
final items = categories.items?.toList();
|
final categoriesQuery = useInfiniteQuery(
|
||||||
if (pageKey == 0) {
|
job: categoriesQueryJob,
|
||||||
Category category = Category();
|
externalData: {
|
||||||
category.id = "user-featured-playlists";
|
"spotify": spotify,
|
||||||
category.name = "Featured";
|
"recommendationMarket": recommendationMarket,
|
||||||
items?.insert(0, category);
|
|
||||||
}
|
|
||||||
if (categories.isLast && items != null) {
|
|
||||||
pagingController.appendLastPage(items);
|
|
||||||
} else if (categories.items != null) {
|
|
||||||
pagingController.appendPage(
|
|
||||||
items!, categories.nextOffset);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return PagedListView(
|
|
||||||
pagingController: pagingController,
|
final categories = categoriesQuery.pages
|
||||||
builderDelegate: PagedChildBuilderDelegate<Category>(
|
.expand<Category?>(
|
||||||
firstPageProgressIndicatorBuilder: (_) =>
|
(page) => page?.items ?? const Iterable.empty(),
|
||||||
const ShimmerCategories(),
|
)
|
||||||
newPageProgressIndicatorBuilder: (_) =>
|
.toList();
|
||||||
const ShimmerCategories(),
|
|
||||||
itemBuilder: (context, item, index) {
|
return ListView.builder(
|
||||||
return CategoryCard(item);
|
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);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Artist/ArtistCard.dart';
|
import 'package:spotube/components/Artist/ArtistCard.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';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
|
||||||
class UserArtists extends HookConsumerWidget {
|
class UserArtists extends HookConsumerWidget {
|
||||||
UserArtists({Key? key}) : super(key: key);
|
const UserArtists({Key? key}) : super(key: key);
|
||||||
final logger = getLogger(UserArtists);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final pagingController =
|
final artistQuery = useInfiniteQuery(
|
||||||
usePaginatedFutureProvider<CursorPage<Artist>, String, Artist>(
|
job: currentUserFollowingArtistsQueryJob,
|
||||||
(pageKey) => currentUserFollowingArtistsQuery(pageKey),
|
externalData: ref.watch(spotifyProvider),
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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(
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 200,
|
maxCrossAxisExtent: 200,
|
||||||
mainAxisExtent: 250,
|
mainAxisExtent: 250,
|
||||||
@ -37,12 +37,17 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
mainAxisSpacing: 20,
|
mainAxisSpacing: 20,
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
pagingController: pagingController,
|
itemBuilder: (context, index) {
|
||||||
builderDelegate: PagedChildBuilderDelegate<Artist>(
|
if (index == artists.length - 1 && hasNextPage) {
|
||||||
itemBuilder: (context, item, index) {
|
return Waypoint(
|
||||||
return ArtistCard(item);
|
onEnter: () {
|
||||||
|
artistQuery.fetchNextPage();
|
||||||
|
},
|
||||||
|
child: ArtistCard(artists[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ArtistCard(artists[index]);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import 'package:spotube/components/Library/UserDownloads.dart';
|
|||||||
import 'package:spotube/components/Library/UserLocalTracks.dart';
|
import 'package:spotube/components/Library/UserLocalTracks.dart';
|
||||||
import 'package:spotube/components/Library/UserPlaylists.dart';
|
import 'package:spotube/components/Library/UserPlaylists.dart';
|
||||||
import 'package:spotube/components/Shared/AnonymousFallback.dart';
|
import 'package:spotube/components/Shared/AnonymousFallback.dart';
|
||||||
|
import 'package:spotube/components/Shared/ColoredTabBar.dart';
|
||||||
|
|
||||||
class UserLibrary extends ConsumerWidget {
|
class UserLibrary extends ConsumerWidget {
|
||||||
const UserLibrary({Key? key}) : super(key: key);
|
const UserLibrary({Key? key}) : super(key: key);
|
||||||
@ -16,7 +17,9 @@ class UserLibrary extends ConsumerWidget {
|
|||||||
length: 5,
|
length: 5,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: const TabBar(
|
appBar: ColoredTabBar(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
child: const TabBar(
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: "Playlist"),
|
Tab(text: "Playlist"),
|
||||||
@ -26,12 +29,13 @@ class UserLibrary extends ConsumerWidget {
|
|||||||
Tab(text: "Album"),
|
Tab(text: "Album"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: TabBarView(children: [
|
),
|
||||||
const AnonymousFallback(child: UserPlaylists()),
|
body: const TabBarView(children: [
|
||||||
const UserDownloads(),
|
AnonymousFallback(child: UserPlaylists()),
|
||||||
const UserLocalTracks(),
|
UserDownloads(),
|
||||||
|
UserLocalTracks(),
|
||||||
AnonymousFallback(child: UserArtists()),
|
AnonymousFallback(child: UserArtists()),
|
||||||
const AnonymousFallback(child: UserAlbums()),
|
AnonymousFallback(child: UserAlbums()),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -60,6 +60,10 @@ class Search extends HookConsumerWidget {
|
|||||||
controller.value.text;
|
controller.value.text;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 7,
|
||||||
|
),
|
||||||
hintStyle: const TextStyle(height: 2),
|
hintStyle: const TextStyle(height: 2),
|
||||||
hintText: "Search...",
|
hintText: "Search...",
|
||||||
),
|
),
|
||||||
@ -93,7 +97,9 @@ class Search extends HookConsumerWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8, horizontal: 20),
|
vertical: 8,
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
14
lib/components/Shared/ColoredTabBar.dart
Normal file
14
lib/components/Shared/ColoredTabBar.dart
Normal 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;
|
||||||
|
}
|
29
lib/components/Shared/Waypoint.dart
Normal file
29
lib/components/Shared/Waypoint.dart
Normal 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.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/themes/light-theme.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
|
final bowl = QueryBowl();
|
||||||
void main() async {
|
void main() async {
|
||||||
await Hive.initFlutter();
|
await Hive.initFlutter();
|
||||||
Hive.registerAdapter(CacheTrackAdapter());
|
Hive.registerAdapter(CacheTrackAdapter());
|
||||||
@ -124,7 +126,10 @@ void main() async {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
child: QueryBowlScope(
|
||||||
|
bowl: bowl,
|
||||||
child: const Spotube(),
|
child: const Spotube(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Album/AlbumView.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/Artist/ArtistProfile.dart';
|
||||||
import 'package:spotube/components/Home/Home.dart';
|
import 'package:spotube/components/Home/Home.dart';
|
||||||
import 'package:spotube/components/Login/Login.dart';
|
import 'package:spotube/components/Login/Login.dart';
|
||||||
@ -49,19 +48,6 @@ GoRouter createGoRouter() => GoRouter(
|
|||||||
return SpotubePage(child: ArtistProfile(state.params["id"]!));
|
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(
|
GoRoute(
|
||||||
path: "/playlist/:id",
|
path: "/playlist/:id",
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotube/models/Logger.dart';
|
import 'package:spotube/models/Logger.dart';
|
||||||
import 'package:spotube/models/LyricsModels.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/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
final categoriesQuery = FutureProvider.family<Page<Category>, int>(
|
final categoriesQueryJob =
|
||||||
(ref, pageKey) {
|
InfiniteQueryJob<Page<Category>, Map<String, dynamic>, int>(
|
||||||
final spotify = ref.watch(spotifyProvider);
|
queryKey: "categories-query",
|
||||||
final recommendationMarket = ref.watch(
|
initialParam: 0,
|
||||||
userPreferencesProvider.select((s) => s.recommendationMarket),
|
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
|
||||||
);
|
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 16,
|
||||||
return spotify.categories
|
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)
|
.list(country: recommendationMarket)
|
||||||
.getPage(15, pageKey);
|
.getPage(15, pageParam);
|
||||||
|
|
||||||
|
return categories;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final categoryPlaylistsQuery =
|
final categoryPlaylistsQueryJob =
|
||||||
FutureProvider.family<Page<PlaylistSimple>, String>(
|
InfiniteQueryJob.withVariableKey<Page<PlaylistSimple>, SpotifyApi, int>(
|
||||||
(ref, value) {
|
preQueryKey: "category-playlists",
|
||||||
final spotify = ref.watch(spotifyProvider);
|
initialParam: 0,
|
||||||
final List data = value.split("/");
|
getNextPageParam: (lastPage, lastParam) => lastPage.nextOffset,
|
||||||
final id = data.first;
|
getPreviousPageParam: (lastPage, lastParam) => lastPage.nextOffset - 6,
|
||||||
final pageKey = data.last;
|
task: (queryKey, pageKey, spotify) {
|
||||||
|
final id = getVariable(queryKey);
|
||||||
return (id != "user-featured-playlists"
|
return (id != "user-featured-playlists"
|
||||||
? spotify.playlists.getByCategoryId(id)
|
? spotify.playlists.getByCategoryId(id)
|
||||||
: spotify.playlists.featured)
|
: 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>(
|
final artistProfileQuery = FutureProvider.family<Artist, String>(
|
||||||
(ref, id) {
|
(ref, id) {
|
||||||
final spotify = ref.watch(spotifyProvider);
|
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 =
|
final artistRelatedArtistsQuery =
|
||||||
FutureProvider.family<Iterable<Artist>, String>(
|
FutureProvider.family<Iterable<Artist>, String>(
|
||||||
(ref, id) {
|
(ref, id) {
|
||||||
|
@ -15,6 +15,7 @@ ThemeData darkTheme({
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
primaryColor: accentMaterialColor,
|
primaryColor: accentMaterialColor,
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
primarySwatch: accentMaterialColor,
|
primarySwatch: accentMaterialColor,
|
||||||
backgroundColor: backgroundMaterialColor[900],
|
backgroundColor: backgroundMaterialColor[900],
|
||||||
scaffoldBackgroundColor: backgroundMaterialColor[900],
|
scaffoldBackgroundColor: backgroundMaterialColor[900],
|
||||||
@ -56,7 +57,7 @@ ThemeData darkTheme({
|
|||||||
),
|
),
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
onPrimary: accentMaterialColor[300],
|
foregroundColor: accentMaterialColor[300],
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
@ -80,9 +80,10 @@ ThemeData lightTheme({
|
|||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
color: backgroundMaterialColor[50],
|
color: backgroundMaterialColor[50],
|
||||||
),
|
),
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
onPrimary: accentMaterialColor[800],
|
foregroundColor: accentMaterialColor[800],
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
@ -9,6 +9,7 @@ import audio_service
|
|||||||
import audio_session
|
import audio_session
|
||||||
import audioplayers_darwin
|
import audioplayers_darwin
|
||||||
import bitsdojo_window_macos
|
import bitsdojo_window_macos
|
||||||
|
import connectivity_plus_macos
|
||||||
import package_info_plus_macos
|
import package_info_plus_macos
|
||||||
import path_provider_macos
|
import path_provider_macos
|
||||||
import shared_preferences_macos
|
import shared_preferences_macos
|
||||||
@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
||||||
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
||||||
|
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
70
pubspec.lock
70
pubspec.lock
@ -346,6 +346,48 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
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:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -437,6 +479,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -732,6 +788,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
nm:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nm
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
oauth2:
|
oauth2:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1262,6 +1325,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
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:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -54,6 +54,9 @@ dependencies:
|
|||||||
badges: ^2.0.3
|
badges: ^2.0.3
|
||||||
mime: ^1.0.2
|
mime: ^1.0.2
|
||||||
metadata_god: ^0.2.0
|
metadata_god: ^0.2.0
|
||||||
|
visibility_detector: ^0.3.3
|
||||||
|
fl_query: ^0.3.0
|
||||||
|
fl_query_hooks: ^0.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
||||||
#include <bitsdojo_window_windows/bitsdojo_window_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 <metadata_god/metadata_god_plugin_c_api.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
||||||
BitsdojoWindowPluginRegisterWithRegistrar(
|
BitsdojoWindowPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||||
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
MetadataGodPluginCApiRegisterWithRegistrar(
|
MetadataGodPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("MetadataGodPluginCApi"));
|
registry->GetRegistrarForPlugin("MetadataGodPluginCApi"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_windows
|
audioplayers_windows
|
||||||
bitsdojo_window_windows
|
bitsdojo_window_windows
|
||||||
|
connectivity_plus_windows
|
||||||
metadata_god
|
metadata_god
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
|
Loading…
Reference in New Issue
Block a user