fix: horizontal infinite lists doesn't fill the screen

This commit is contained in:
Kingkor Roy Tirtho 2022-12-07 10:27:46 +06:00
parent 067e9ac53e
commit 69995bea1c
10 changed files with 212 additions and 175 deletions

View File

@ -50,24 +50,25 @@ class ArtistAlbumList extends HookConsumerWidget {
child: Scrollbar(
interactive: false,
controller: scrollController,
child: Waypoint(
controller: scrollController,
onTouchEdge: () {
albumsQuery.fetchNextPage();
},
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 const ShimmerPlaybuttonCard(count: 1);
}
return AlbumCard(albums[index]);
},
),
),
),
),
);
}
}

View File

@ -66,21 +66,22 @@ class CategoryCard extends HookConsumerWidget {
child: Scrollbar(
controller: scrollController,
interactive: false,
child: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: playlists.length,
itemBuilder: (context, index) {
if (index == playlists.length - 1 && hasNextPage) {
return Waypoint(
onEnter: () {
child: Waypoint(
controller: scrollController,
onTouchEdge: () {
playlistQuery.fetchNextPage();
},
child: const ShimmerPlaybuttonCard(count: 1),
);
}
return PlaylistCard(playlists[index]);
},
child: ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
controller: scrollController,
children: [
...playlists
.map((playlist) => PlaylistCard(playlist)),
if (hasNextPage)
const ShimmerPlaybuttonCard(count: 1),
],
),
),
),
),

View File

@ -18,6 +18,7 @@ class Genres extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final scrollController = useScrollController();
final spotify = ref.watch(spotifyProvider);
final recommendationMarket = ref.watch(
userPreferencesProvider.select((s) => s.recommendationMarket),
@ -45,24 +46,26 @@ class Genres extends HookConsumerWidget {
return PlatformScaffold(
appBar: kIsDesktop ? PageWindowTitleBar() : null,
body: ListView.builder(
body: Waypoint(
onTouchEdge: () {
if (categoriesQuery.hasNextPage) {
categoriesQuery.fetchNextPage();
}
},
controller: scrollController,
child: ListView.builder(
controller: scrollController,
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 const ShimmerCategories();
}
return CategoryCard(category);
},
),
),
);
}
}

View File

@ -49,15 +49,19 @@ class UserArtists extends HookConsumerWidget {
),
padding: const EdgeInsets.all(10),
itemBuilder: (context, index) {
return HookBuilder(builder: (context) {
if (index == artists.length - 1 && hasNextPage) {
return Waypoint(
onEnter: () {
controller: useScrollController(),
isGrid: true,
onTouchEdge: () {
artistQuery.fetchNextPage();
},
child: ArtistCard(artists[index]),
);
}
return ArtistCard(artists[index]);
});
},
),
);

View File

@ -200,16 +200,9 @@ class Search extends HookConsumerWidget {
if (playlists.isNotEmpty)
PlatformText.headline("Playlists"),
const SizedBox(height: 10),
if (searchPlaylist.isLoading &&
!searchPlaylist.isFetchingNextPage)
const PlatformCircularProgressIndicator()
else if (searchPlaylist.hasError)
PlatformText(searchPlaylist
.error?[searchPlaylist.pageParams.last])
else
ScrollConfiguration(
behavior: ScrollConfiguration.of(context)
.copyWith(
behavior:
ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
@ -221,6 +214,11 @@ class Search extends HookConsumerWidget {
? ScrollbarOrientation.bottom
: ScrollbarOrientation.top,
controller: playlistController,
child: Waypoint(
onTouchEdge: () {
searchPlaylist.fetchNextPage();
},
controller: playlistController,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: playlistController,
@ -231,15 +229,8 @@ class Search extends HookConsumerWidget {
if (i == playlists.length - 1 &&
searchPlaylist
.hasNextPage) {
return Waypoint(
onEnter: () {
searchPlaylist
.fetchNextPage();
},
child:
const ShimmerPlaybuttonCard(
count: 1),
);
return const ShimmerPlaybuttonCard(
count: 1);
}
return PlaylistCard(playlist);
},
@ -249,20 +240,20 @@ class Search extends HookConsumerWidget {
),
),
),
),
if (searchPlaylist.isLoading &&
!searchPlaylist.isFetchingNextPage)
const PlatformCircularProgressIndicator(),
if (searchPlaylist.hasError)
PlatformText(searchPlaylist
.error?[searchPlaylist.pageParams.last]),
const SizedBox(height: 20),
if (artists.isNotEmpty)
PlatformText.headline("Artists"),
const SizedBox(height: 10),
if (searchArtist.isLoading &&
!searchArtist.isFetchingNextPage)
const PlatformCircularProgressIndicator()
else if (searchArtist.hasError)
PlatformText(searchArtist
.error?[searchArtist.pageParams.last])
else
ScrollConfiguration(
behavior: ScrollConfiguration.of(context)
.copyWith(
behavior:
ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
@ -270,6 +261,11 @@ class Search extends HookConsumerWidget {
),
child: Scrollbar(
controller: artistController,
child: Waypoint(
controller: artistController,
onTouchEdge: () {
searchArtist.fetchNextPage();
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: artistController,
@ -279,15 +275,8 @@ class Search extends HookConsumerWidget {
(i, artist) {
if (i == artists.length - 1 &&
searchArtist.hasNextPage) {
return Waypoint(
onEnter: () {
searchArtist
.fetchNextPage();
},
child:
const ShimmerPlaybuttonCard(
count: 1),
);
return const ShimmerPlaybuttonCard(
count: 1);
}
return Container(
margin: const EdgeInsets
@ -302,6 +291,13 @@ class Search extends HookConsumerWidget {
),
),
),
),
if (searchArtist.isLoading &&
!searchArtist.isFetchingNextPage)
const PlatformCircularProgressIndicator(),
if (searchArtist.hasError)
PlatformText(searchArtist
.error?[searchArtist.pageParams.last]),
const SizedBox(height: 20),
if (albums.isNotEmpty)
PlatformText(
@ -310,16 +306,9 @@ class Search extends HookConsumerWidget {
Theme.of(context).textTheme.headline5,
),
const SizedBox(height: 10),
if (searchAlbum.isLoading &&
!searchAlbum.isFetchingNextPage)
const PlatformCircularProgressIndicator()
else if (searchAlbum.hasError)
PlatformText(searchAlbum
.error?[searchAlbum.pageParams.last])
else
ScrollConfiguration(
behavior: ScrollConfiguration.of(context)
.copyWith(
behavior:
ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
@ -327,6 +316,11 @@ class Search extends HookConsumerWidget {
),
child: Scrollbar(
controller: albumController,
child: Waypoint(
controller: albumController,
onTouchEdge: () {
searchAlbum.fetchNextPage();
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: albumController,
@ -335,14 +329,8 @@ class Search extends HookConsumerWidget {
...albums.mapIndexed((i, album) {
if (i == albums.length - 1 &&
searchAlbum.hasNextPage) {
return Waypoint(
onEnter: () {
searchAlbum.fetchNextPage();
},
child:
const ShimmerPlaybuttonCard(
count: 1),
);
return const ShimmerPlaybuttonCard(
count: 1);
}
return AlbumCard(
TypeConversionUtils
@ -356,6 +344,13 @@ class Search extends HookConsumerWidget {
),
),
),
),
if (searchAlbum.isLoading &&
!searchAlbum.isFetchingNextPage)
const PlatformCircularProgressIndicator(),
if (searchAlbum.hasError)
PlatformText(searchAlbum
.error?[searchAlbum.pageParams.last]),
],
),
),

View File

@ -1,18 +1,27 @@
import 'package:flutter/material.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:go_router/go_router.dart';
import 'package:platform_ui/platform_ui.dart';
class SpotubePageRoute extends PageRouteBuilder {
final Widget child;
SpotubePageRoute({required this.child})
: super(
pageBuilder: (context, animation, secondaryAnimation) => child,
settings: RouteSettings(
name: child.key.toString(),
),
class SpotubePage extends CustomTransitionPage {
SpotubePage({
required super.child,
}) : super(
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child;
},
);
@override
Route createRoute(BuildContext context) {
if (platform == TargetPlatform.windows) {
return FluentPageRoute(
builder: (context) => child,
settings: this,
maintainState: maintainState,
barrierLabel: barrierLabel,
fullscreenDialog: fullscreenDialog,
);
}
class SpotubePage extends MaterialPage {
const SpotubePage({
required Widget child,
}) : super(child: child);
return super.createRoute(context);
}
}

View File

@ -1,29 +1,57 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:visibility_detector/visibility_detector.dart';
class Waypoint extends StatelessWidget {
final void Function()? onEnter;
final void Function()? onLeave;
class Waypoint extends HookWidget {
final void Function()? onTouchEdge;
final Widget? child;
final ScrollController controller;
final bool isGrid;
const Waypoint({
Key? key,
this.onEnter,
this.onLeave,
required this.controller,
this.isGrid = false,
this.onTouchEdge,
this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
useEffect(() {
if (isGrid) {
return null;
}
listener() {
// nextPageTrigger will have a value equivalent to 80% of the list size.
final nextPageTrigger = 0.8 * controller.position.maxScrollExtent;
// scrollController fetches the next paginated data when the current postion of the user on the screen has surpassed
if (controller.position.pixels >= nextPageTrigger) {
onTouchEdge?.call();
}
}
if (controller.hasClients) {
listener();
}
controller.addListener(listener);
return () => controller.removeListener(listener);
}, [controller, onTouchEdge]);
if (isGrid) {
return VisibilityDetector(
key: const Key("waypoint"),
onVisibilityChanged: (info) {
if (info.visibleFraction == 0) {
onLeave?.call();
} else if (info.visibleFraction > 0) {
onEnter?.call();
if (info.visibleFraction > 0) {
onTouchEdge?.call();
}
},
child: child ?? Container(),
);
}
return child ?? Container();
}
}

View File

@ -28,28 +28,28 @@ final router = GoRouter(
routes: [
GoRoute(
path: "/",
pageBuilder: (context, state) => const SpotubePage(child: Genres()),
pageBuilder: (context, state) => SpotubePage(child: const Genres()),
),
GoRoute(
path: "/search",
name: "Search",
pageBuilder: (context, state) => const SpotubePage(child: Search()),
pageBuilder: (context, state) => SpotubePage(child: const Search()),
),
GoRoute(
path: "/library",
name: "Library",
pageBuilder: (context, state) =>
const SpotubePage(child: UserLibrary()),
SpotubePage(child: const UserLibrary()),
),
GoRoute(
path: "/lyrics",
name: "Lyrics",
pageBuilder: (context, state) => const SpotubePage(child: Lyrics()),
pageBuilder: (context, state) => SpotubePage(child: const Lyrics()),
),
GoRoute(
path: "/settings",
pageBuilder: (context, state) => const SpotubePage(
child: Settings(),
pageBuilder: (context, state) => SpotubePage(
child: const Settings(),
),
),
GoRoute(
@ -87,16 +87,16 @@ final router = GoRouter(
GoRoute(
path: "/login-tutorial",
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => const SpotubePage(
child: LoginTutorial(),
pageBuilder: (context, state) => SpotubePage(
child: const LoginTutorial(),
),
),
GoRoute(
path: "/player",
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) {
return const SpotubePage(
child: PlayerView(),
return SpotubePage(
child: const PlayerView(),
);
},
),

View File

@ -1050,11 +1050,9 @@ packages:
platform_ui:
dependency: "direct main"
description:
path: "."
ref: bf42bc4caf9cb382f5215ea2db711adbf2a99f4b
resolved-ref: bf42bc4caf9cb382f5215ea2db711adbf2a99f4b
url: "https://github.com/KRTirtho/platform_ui.git"
source: git
path: "../platform_ui"
relative: true
source: path
version: "0.1.0"
plugin_platform_interface:
dependency: transitive

View File

@ -63,9 +63,7 @@ dependencies:
tuple: ^2.0.1
uuid: ^3.0.6
platform_ui:
git:
url: https://github.com/KRTirtho/platform_ui.git
ref: bf42bc4caf9cb382f5215ea2db711adbf2a99f4b
path: ../platform_ui
fluent_ui: ^4.0.3
macos_ui: ^1.7.5
libadwaita: ^1.2.5