mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-14 16:25:16 +00:00
fix: horizontal infinite lists doesn't fill the screen
This commit is contained in:
parent
067e9ac53e
commit
69995bea1c
@ -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]);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -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]),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user