From c585522c355eeb38dbea8b3516dcd145818dcaea Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 5 Dec 2025 00:10:25 +0800 Subject: [PATCH] :bug: Fixes in new pagination list --- lib/pods/paging.dart | 4 +- lib/pods/timeline.dart | 6 +- lib/screens/explore.dart | 156 ++++++++++-------------- lib/widgets/paging/pagination_list.dart | 23 +++- 4 files changed, 95 insertions(+), 94 deletions(-) diff --git a/lib/pods/paging.dart b/lib/pods/paging.dart index 0a5becee..3945c652 100644 --- a/lib/pods/paging.dart +++ b/lib/pods/paging.dart @@ -45,11 +45,12 @@ mixin AsyncPaginationController on AsyncNotifier> return await fetch(); }); state = newState; + fetchedCount = newState.value?.length ?? 0; } @override Future fetchFurther() async { - if (!fetchedAll) return; + if (fetchedAll) return; state = AsyncLoading>(); @@ -67,6 +68,7 @@ mixin AsyncPaginationFilter on AsyncPaginationController implements PaginationFiltered { @override Future applyFilter(F filter) async { + if (currentFilter == filter) return; // Reset the data totalCount = null; fetchedCount = 0; diff --git a/lib/pods/timeline.dart b/lib/pods/timeline.dart index d1abd4eb..16b73775 100644 --- a/lib/pods/timeline.dart +++ b/lib/pods/timeline.dart @@ -23,7 +23,11 @@ class ActivityListNotifier extends AsyncNotifier> final client = ref.read(apiClientProvider); final cursor = - state.valueOrNull?.lastOrNull?.createdAt.toUtc().toIso8601String(); + state.isLoading + ? null + : state.valueOrNull?.lastOrNull?.createdAt + .toUtc() + .toIso8601String(); final queryParameters = { if (cursor != null) 'cursor': cursor, diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 50a51af7..018601f5 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -72,8 +72,8 @@ class ExploreScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final tabController = useTabController(initialLength: 3); final currentFilter = useState(null); + final notifier = ref.watch(activityListNotifierProvider.notifier); useEffect(() { // Set FAB type to chat @@ -91,33 +91,10 @@ class ExploreScreen extends HookConsumerWidget { }; }, []); - useEffect(() { - void listener() { - switch (tabController.index) { - 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]); + void handleFilterChange(String? filter) { + currentFilter.value = filter; + notifier.applyFilter(filter); + } // Listen for post creation events to refresh activities useEffect(() { @@ -149,35 +126,42 @@ class ExploreScreen extends HookConsumerWidget { margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top), child: Row( children: [ - Expanded( - child: TabBar( - controller: tabController, - tabAlignment: TabAlignment.start, - isScrollable: true, - dividerColor: Colors.transparent, - labelPadding: const EdgeInsets.symmetric(horizontal: 12), - tabs: [ - Tab( - icon: Tooltip( - message: 'explore'.tr(), - child: Icon(Symbols.explore), - ), - ), - Tab( - icon: Tooltip( - message: 'exploreFilterSubscriptions'.tr(), - child: Icon(Symbols.subscriptions), - ), - ), - Tab( - icon: Tooltip( - message: 'exploreFilterFriends'.tr(), - child: Icon(Symbols.people), - ), - ), - ], - ), + Row( + spacing: 4, + children: [ + IconButton( + onPressed: () => handleFilterChange(null), + icon: Icon(Symbols.explore), + tooltip: 'explore'.tr(), + isSelected: currentFilter.value == null, + color: + currentFilter.value == null + ? Theme.of(context).colorScheme.primary + : 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, + ), + IconButton( + onPressed: () => handleFilterChange('friends'), + icon: Icon(Symbols.people), + tooltip: 'exploreFilterFriends'.tr(), + isSelected: currentFilter.value == 'friends', + color: + currentFilter.value == 'friends' + ? Theme.of(context).colorScheme.primary + : null, + ), + ], ), + const Spacer(), IconButton( onPressed: () { context.pushNamed('articles'); @@ -241,10 +225,13 @@ class ExploreScreen extends HookConsumerWidget { 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); @@ -324,6 +311,7 @@ class ExploreScreen extends HookConsumerWidget { notifier: activityListNotifierProvider.notifier, // Sliver list cannot provide refresh handled by the pagination list isRefreshable: false, + isSliver: true, contentBuilder: (data) => _ActivityListView(data: data, isWide: isWide), ); } @@ -433,7 +421,8 @@ class ExploreScreen extends HookConsumerWidget { } PreferredSizeWidget _buildAppBar( - TabController tabController, + String? currentFilter, + void Function(String?) handleFilterChange, BuildContext context, ) { final foregroundColor = Theme.of(context).appBarTheme.foregroundColor; @@ -449,38 +438,25 @@ class ExploreScreen extends HookConsumerWidget { ), child: Row( children: [ - Expanded( - child: TabBar( - controller: tabController, - tabAlignment: TabAlignment.start, - isScrollable: true, - dividerColor: Colors.transparent, - labelPadding: const EdgeInsets.symmetric(horizontal: 12), - tabs: [ - Tab( - icon: Tooltip( - message: 'explore'.tr(), - child: Icon(Symbols.explore, color: foregroundColor), - ), - ), - Tab( - icon: Tooltip( - message: 'exploreFilterSubscriptions'.tr(), - child: Icon( - Symbols.subscriptions, - color: foregroundColor, - ), - ), - ), - Tab( - icon: Tooltip( - message: 'exploreFilterFriends'.tr(), - child: Icon(Symbols.people, color: foregroundColor), - ), - ), - ], - ), + IconButton( + onPressed: () => handleFilterChange(null), + icon: Icon(Symbols.explore, color: foregroundColor), + tooltip: 'explore'.tr(), + isSelected: currentFilter == null, ), + IconButton( + onPressed: () => handleFilterChange('subscriptions'), + icon: Icon(Symbols.subscriptions, color: foregroundColor), + tooltip: 'exploreFilterSubscriptions'.tr(), + isSelected: currentFilter == 'subscriptions', + ), + IconButton( + onPressed: () => handleFilterChange('friends'), + icon: Icon(Symbols.people, color: foregroundColor), + tooltip: 'exploreFilterFriends'.tr(), + isSelected: currentFilter == 'friends', + ), + const Spacer(), IconButton( onPressed: () { context.pushNamed('articles'); diff --git a/lib/widgets/paging/pagination_list.dart b/lib/widgets/paging/pagination_list.dart index 4ddd0ea3..b397ebff 100644 --- a/lib/widgets/paging/pagination_list.dart +++ b/lib/widgets/paging/pagination_list.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/pods/paging.dart'; import 'package:island/widgets/extended_refresh_indicator.dart'; +import 'package:island/widgets/response.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class PaginationList extends HookConsumerWidget { @@ -34,7 +35,7 @@ class PaginationList extends HookConsumerWidget { if (scrollInfo is ScrollEndNotification && scrollInfo.metrics.axisDirection == AxisDirection.down && scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) { - if (!noti.fetchedAll) { + if (!noti.fetchedAll && !data.isLoading && !data.hasError) { noti.fetchFurther(); } } @@ -54,24 +55,42 @@ class PaginationWidget extends HookConsumerWidget { final Refreshable> notifier; final Widget Function(List) contentBuilder; final bool isRefreshable; + final bool isSliver; + final bool showDefaultWidgets; const PaginationWidget({ super.key, required this.provider, required this.notifier, required this.contentBuilder, this.isRefreshable = true, + this.isSliver = false, + this.showDefaultWidgets = true, }); @override Widget build(BuildContext context, WidgetRef ref) { final data = ref.watch(provider); 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( onNotification: (ScrollNotification scrollInfo) { if (scrollInfo is ScrollEndNotification && scrollInfo.metrics.axisDirection == AxisDirection.down && scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent) { - if (!noti.fetchedAll) { + if (!noti.fetchedAll && !data.isLoading && !data.hasError) { noti.fetchFurther(); } }