🐛 Fixes in new pagination list

This commit is contained in:
2025-12-05 00:10:25 +08:00
parent 6aba84e506
commit c585522c35
4 changed files with 95 additions and 94 deletions

View File

@@ -45,11 +45,12 @@ mixin AsyncPaginationController<T> on AsyncNotifier<List<T>>
return await fetch(); return await fetch();
}); });
state = newState; state = newState;
fetchedCount = newState.value?.length ?? 0;
} }
@override @override
Future<void> fetchFurther() async { Future<void> fetchFurther() async {
if (!fetchedAll) return; if (fetchedAll) return;
state = AsyncLoading<List<T>>(); state = AsyncLoading<List<T>>();
@@ -67,6 +68,7 @@ mixin AsyncPaginationFilter<F, T> on AsyncPaginationController<T>
implements PaginationFiltered<F> { implements PaginationFiltered<F> {
@override @override
Future<void> applyFilter(F filter) async { Future<void> applyFilter(F filter) async {
if (currentFilter == filter) return;
// Reset the data // Reset the data
totalCount = null; totalCount = null;
fetchedCount = 0; fetchedCount = 0;

View File

@@ -23,7 +23,11 @@ class ActivityListNotifier extends AsyncNotifier<List<SnTimelineEvent>>
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final cursor = final cursor =
state.valueOrNull?.lastOrNull?.createdAt.toUtc().toIso8601String(); state.isLoading
? null
: state.valueOrNull?.lastOrNull?.createdAt
.toUtc()
.toIso8601String();
final queryParameters = { final queryParameters = {
if (cursor != null) 'cursor': cursor, if (cursor != null) 'cursor': cursor,

View File

@@ -72,8 +72,8 @@ class ExploreScreen extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 3);
final currentFilter = useState<String?>(null); final currentFilter = useState<String?>(null);
final notifier = ref.watch(activityListNotifierProvider.notifier);
useEffect(() { useEffect(() {
// Set FAB type to chat // Set FAB type to chat
@@ -91,33 +91,10 @@ class ExploreScreen extends HookConsumerWidget {
}; };
}, []); }, []);
useEffect(() { void handleFilterChange(String? filter) {
void listener() { currentFilter.value = filter;
switch (tabController.index) { notifier.applyFilter(filter);
case 0:
currentFilter.value = null;
break;
case 1:
currentFilter.value = 'subscriptions';
break;
case 2:
currentFilter.value = 'friends';
break;
} }
}
tabController.addListener(listener);
return () => tabController.removeListener(listener);
}, [tabController]);
final notifier = ref.watch(activityListNotifierProvider.notifier);
useEffect(() {
Future(() {
notifier.applyFilter(currentFilter.value);
});
return null;
}, [currentFilter.value]);
// Listen for post creation events to refresh activities // Listen for post creation events to refresh activities
useEffect(() { useEffect(() {
@@ -149,35 +126,42 @@ class ExploreScreen extends HookConsumerWidget {
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Row( child: Row(
children: [ children: [
Expanded( Row(
child: TabBar( spacing: 4,
controller: tabController, children: [
tabAlignment: TabAlignment.start, IconButton(
isScrollable: true, onPressed: () => handleFilterChange(null),
dividerColor: Colors.transparent, icon: Icon(Symbols.explore),
labelPadding: const EdgeInsets.symmetric(horizontal: 12), tooltip: 'explore'.tr(),
tabs: [ isSelected: currentFilter.value == null,
Tab( color:
icon: Tooltip( currentFilter.value == null
message: 'explore'.tr(), ? Theme.of(context).colorScheme.primary
child: Icon(Symbols.explore), : null,
), ),
IconButton(
onPressed: () => handleFilterChange('subscriptions'),
icon: Icon(Symbols.subscriptions),
tooltip: 'exploreFilterSubscriptions'.tr(),
isSelected: currentFilter.value == 'subscriptions',
color:
currentFilter.value == 'subscriptions'
? Theme.of(context).colorScheme.primary
: null,
), ),
Tab( IconButton(
icon: Tooltip( onPressed: () => handleFilterChange('friends'),
message: 'exploreFilterSubscriptions'.tr(), icon: Icon(Symbols.people),
child: Icon(Symbols.subscriptions), tooltip: 'exploreFilterFriends'.tr(),
), isSelected: currentFilter.value == 'friends',
), color:
Tab( currentFilter.value == 'friends'
icon: Tooltip( ? Theme.of(context).colorScheme.primary
message: 'exploreFilterFriends'.tr(), : null,
child: Icon(Symbols.people),
),
), ),
], ],
), ),
), const Spacer(),
IconButton( IconButton(
onPressed: () { onPressed: () {
context.pushNamed('articles'); context.pushNamed('articles');
@@ -241,10 +225,13 @@ class ExploreScreen extends HookConsumerWidget {
tooltip: 'search'.tr(), tooltip: 'search'.tr(),
), ),
], ],
).padding(horizontal: 8), ).padding(horizontal: 8, vertical: 4),
); );
final appBar = isWide ? null : _buildAppBar(tabController, context); final appBar =
isWide
? null
: _buildAppBar(currentFilter.value, handleFilterChange, context);
final dragging = useState(false); final dragging = useState(false);
@@ -324,6 +311,7 @@ class ExploreScreen extends HookConsumerWidget {
notifier: activityListNotifierProvider.notifier, notifier: activityListNotifierProvider.notifier,
// Sliver list cannot provide refresh handled by the pagination list // Sliver list cannot provide refresh handled by the pagination list
isRefreshable: false, isRefreshable: false,
isSliver: true,
contentBuilder: (data) => _ActivityListView(data: data, isWide: isWide), contentBuilder: (data) => _ActivityListView(data: data, isWide: isWide),
); );
} }
@@ -433,7 +421,8 @@ class ExploreScreen extends HookConsumerWidget {
} }
PreferredSizeWidget _buildAppBar( PreferredSizeWidget _buildAppBar(
TabController tabController, String? currentFilter,
void Function(String?) handleFilterChange,
BuildContext context, BuildContext context,
) { ) {
final foregroundColor = Theme.of(context).appBarTheme.foregroundColor; final foregroundColor = Theme.of(context).appBarTheme.foregroundColor;
@@ -449,38 +438,25 @@ class ExploreScreen extends HookConsumerWidget {
), ),
child: Row( child: Row(
children: [ children: [
Expanded( IconButton(
child: TabBar( onPressed: () => handleFilterChange(null),
controller: tabController, icon: Icon(Symbols.explore, color: foregroundColor),
tabAlignment: TabAlignment.start, tooltip: 'explore'.tr(),
isScrollable: true, isSelected: currentFilter == null,
dividerColor: Colors.transparent,
labelPadding: const EdgeInsets.symmetric(horizontal: 12),
tabs: [
Tab(
icon: Tooltip(
message: 'explore'.tr(),
child: Icon(Symbols.explore, color: foregroundColor),
), ),
IconButton(
onPressed: () => handleFilterChange('subscriptions'),
icon: Icon(Symbols.subscriptions, color: foregroundColor),
tooltip: 'exploreFilterSubscriptions'.tr(),
isSelected: currentFilter == 'subscriptions',
), ),
Tab( IconButton(
icon: Tooltip( onPressed: () => handleFilterChange('friends'),
message: 'exploreFilterSubscriptions'.tr(), icon: Icon(Symbols.people, color: foregroundColor),
child: Icon( tooltip: 'exploreFilterFriends'.tr(),
Symbols.subscriptions, isSelected: currentFilter == 'friends',
color: foregroundColor,
),
),
),
Tab(
icon: Tooltip(
message: 'exploreFilterFriends'.tr(),
child: Icon(Symbols.people, color: foregroundColor),
),
),
],
),
), ),
const Spacer(),
IconButton( IconButton(
onPressed: () { onPressed: () {
context.pushNamed('articles'); context.pushNamed('articles');

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/paging.dart'; import 'package:island/pods/paging.dart';
import 'package:island/widgets/extended_refresh_indicator.dart'; import 'package:island/widgets/extended_refresh_indicator.dart';
import 'package:island/widgets/response.dart';
import 'package:super_sliver_list/super_sliver_list.dart'; import 'package:super_sliver_list/super_sliver_list.dart';
class PaginationList<T> extends HookConsumerWidget { class PaginationList<T> extends HookConsumerWidget {
@@ -34,7 +35,7 @@ class PaginationList<T> extends HookConsumerWidget {
if (scrollInfo is ScrollEndNotification && if (scrollInfo is ScrollEndNotification &&
scrollInfo.metrics.axisDirection == AxisDirection.down && scrollInfo.metrics.axisDirection == AxisDirection.down &&
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) { scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
if (!noti.fetchedAll) { if (!noti.fetchedAll && !data.isLoading && !data.hasError) {
noti.fetchFurther(); noti.fetchFurther();
} }
} }
@@ -54,24 +55,42 @@ class PaginationWidget<T> extends HookConsumerWidget {
final Refreshable<PaginationController<T>> notifier; final Refreshable<PaginationController<T>> notifier;
final Widget Function(List<T>) contentBuilder; final Widget Function(List<T>) contentBuilder;
final bool isRefreshable; final bool isRefreshable;
final bool isSliver;
final bool showDefaultWidgets;
const PaginationWidget({ const PaginationWidget({
super.key, super.key,
required this.provider, required this.provider,
required this.notifier, required this.notifier,
required this.contentBuilder, required this.contentBuilder,
this.isRefreshable = true, this.isRefreshable = true,
this.isSliver = false,
this.showDefaultWidgets = true,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(provider); final data = ref.watch(provider);
final noti = ref.watch(notifier); final noti = ref.watch(notifier);
if (data.isLoading) {
final content = ResponseLoadingWidget();
return isSliver ? SliverFillRemaining(child: content) : content;
}
if (data.hasError) {
final content = ResponseErrorWidget(
error: data.error,
onRetry: noti.refresh,
);
return isSliver ? SliverFillRemaining(child: content) : content;
}
final content = NotificationListener( final content = NotificationListener(
onNotification: (ScrollNotification scrollInfo) { onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo is ScrollEndNotification && if (scrollInfo is ScrollEndNotification &&
scrollInfo.metrics.axisDirection == AxisDirection.down && scrollInfo.metrics.axisDirection == AxisDirection.down &&
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) { scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) {
if (!noti.fetchedAll) { if (!noti.fetchedAll && !data.isLoading && !data.hasError) {
noti.fetchFurther(); noti.fetchFurther();
} }
} }