From c4ac256896e0487bd78755dc4300149469a62f6c Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 6 Dec 2025 01:32:46 +0800 Subject: [PATCH] :recycle: Continued to migrate list pagination --- lib/screens/creators/poll/poll_list.dart | 89 +++--- lib/screens/creators/poll/poll_list.g.dart | 149 ---------- lib/screens/creators/sites/site_list.dart | 74 ++--- lib/screens/creators/sites/site_list.g.dart | 183 ------------ lib/screens/creators/stickers/stickers.dart | 164 +++++------ lib/screens/creators/stickers/stickers.g.dart | 149 ---------- lib/screens/discovery/articles.dart | 95 +++---- lib/screens/discovery/articles.freezed.dart | 268 ++++++++++++++++++ lib/screens/discovery/articles.g.dart | 196 ------------- lib/screens/discovery/feeds/feed_detail.dart | 100 ++----- .../discovery/feeds/feed_detail.g.dart | 164 ----------- .../discovery/feeds/feed_marketplace.dart | 184 ++++++------ .../discovery/feeds/feed_marketplace.g.dart | 180 ------------ lib/widgets/post/compose_poll.dart | 44 +-- 14 files changed, 561 insertions(+), 1478 deletions(-) delete mode 100644 lib/screens/creators/sites/site_list.g.dart create mode 100644 lib/screens/discovery/articles.freezed.dart delete mode 100644 lib/screens/discovery/feeds/feed_marketplace.g.dart diff --git a/lib/screens/creators/poll/poll_list.dart b/lib/screens/creators/poll/poll_list.dart index 16202e4c..2ee598b6 100644 --- a/lib/screens/creators/poll/poll_list.dart +++ b/lib/screens/creators/poll/poll_list.dart @@ -4,60 +4,51 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/poll.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/screens/poll/poll_editor.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/poll/poll_feedback.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:island/widgets/extended_refresh_indicator.dart'; import 'package:styled_widget/styled_widget.dart'; part 'poll_list.g.dart'; -@riverpod -class PollListNotifier extends _$PollListNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; +final pollListNotifierProvider = AsyncNotifierProvider.family.autoDispose( + PollListNotifier.new, +); + +class PollListNotifier + extends AutoDisposeFamilyAsyncNotifier, String?> + with FamilyAsyncPaginationController { + static const int pageSize = 20; @override - Future> build(String? pubName) { - // immediately load first page - return fetch(cursor: null); - } - - @override - Future> fetch({ - required String? cursor, - }) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); // read the current family argument passed to provider - final currentPub = pubName; final queryParams = { - 'offset': offset, - 'take': _pageSize, - if (currentPub != null) 'pub': currentPub, + 'offset': fetchedCount.toString(), + 'take': pageSize, + if (arg != null) 'pub': arg, }; final response = await client.get( '/sphere/polls/me', queryParameters: queryParams, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; - final items = data.map((json) => SnPollWithStats.fromJson(json)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final items = + response.data + .map((json) => SnPollWithStats.fromJson(json)) + .cast() + .toList(); - final hasMore = offset + items.length < total; - final nextCursor = hasMore ? (offset + items.length).toString() : null; - - return CursorPagingData( - items: items, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return items; } } @@ -97,31 +88,19 @@ class CreatorPollListScreen extends HookConsumerWidget { ), body: ExtendedRefreshIndicator( onRefresh: () => ref.refresh(pollListNotifierProvider(pubName).future), - child: CustomScrollView( - slivers: [ - PagingHelperSliverView( - provider: pollListNotifierProvider(pubName), - futureRefreshable: pollListNotifierProvider(pubName).future, - notifierRefreshable: pollListNotifierProvider(pubName).notifier, - contentBuilder: - (data, widgetCount, endItemView) => SliverList.builder( - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - final pollWithStats = data.items[index]; - return ConstrainedBox( - constraints: BoxConstraints(maxWidth: 640), - child: _CreatorPollItem( - pollWithStats: pollWithStats, - pubName: pubName, - ), - ).center(); - }, - ), - ), - ], + child: PaginationList( + provider: pollListNotifierProvider(pubName), + notifier: pollListNotifierProvider(pubName).notifier, + padding: const EdgeInsets.only(top: 12), + itemBuilder: (context, index, pollWithStats) { + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: 640), + child: _CreatorPollItem( + pollWithStats: pollWithStats, + pubName: pubName, + ), + ).center(); + }, ), ), ); diff --git a/lib/screens/creators/poll/poll_list.g.dart b/lib/screens/creators/poll/poll_list.g.dart index ef637cac..dcfb847a 100644 --- a/lib/screens/creators/poll/poll_list.g.dart +++ b/lib/screens/creators/poll/poll_list.g.dart @@ -148,154 +148,5 @@ class _PollWithStatsProviderElement String get id => (origin as PollWithStatsProvider).id; } -String _$pollListNotifierHash() => r'd5b822e737788be8982f5cb3b501d460441930c1'; - -abstract class _$PollListNotifier - extends - BuildlessAutoDisposeAsyncNotifier> { - late final String? pubName; - - FutureOr> build(String? pubName); -} - -/// See also [PollListNotifier]. -@ProviderFor(PollListNotifier) -const pollListNotifierProvider = PollListNotifierFamily(); - -/// See also [PollListNotifier]. -class PollListNotifierFamily - extends Family>> { - /// See also [PollListNotifier]. - const PollListNotifierFamily(); - - /// See also [PollListNotifier]. - PollListNotifierProvider call(String? pubName) { - return PollListNotifierProvider(pubName); - } - - @override - PollListNotifierProvider getProviderOverride( - covariant PollListNotifierProvider provider, - ) { - return call(provider.pubName); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'pollListNotifierProvider'; -} - -/// See also [PollListNotifier]. -class PollListNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - PollListNotifier, - CursorPagingData - > { - /// See also [PollListNotifier]. - PollListNotifierProvider(String? pubName) - : this._internal( - () => PollListNotifier()..pubName = pubName, - from: pollListNotifierProvider, - name: r'pollListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$pollListNotifierHash, - dependencies: PollListNotifierFamily._dependencies, - allTransitiveDependencies: - PollListNotifierFamily._allTransitiveDependencies, - pubName: pubName, - ); - - PollListNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.pubName, - }) : super.internal(); - - final String? pubName; - - @override - FutureOr> runNotifierBuild( - covariant PollListNotifier notifier, - ) { - return notifier.build(pubName); - } - - @override - Override overrideWith(PollListNotifier Function() create) { - return ProviderOverride( - origin: this, - override: PollListNotifierProvider._internal( - () => create()..pubName = pubName, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - pubName: pubName, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - PollListNotifier, - CursorPagingData - > - createElement() { - return _PollListNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is PollListNotifierProvider && other.pubName == pubName; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, pubName.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin PollListNotifierRef - on AutoDisposeAsyncNotifierProviderRef> { - /// The parameter `pubName` of this provider. - String? get pubName; -} - -class _PollListNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - PollListNotifier, - CursorPagingData - > - with PollListNotifierRef { - _PollListNotifierProviderElement(super.provider); - - @override - String? get pubName => (origin as PollListNotifierProvider).pubName; -} - // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/creators/sites/site_list.dart b/lib/screens/creators/sites/site_list.dart index 97a4a620..1c4151ed 100644 --- a/lib/screens/creators/sites/site_list.dart +++ b/lib/screens/creators/sites/site_list.dart @@ -6,54 +6,43 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/publication_site.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/screens/creators/sites/site_edit.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:island/widgets/extended_refresh_indicator.dart'; import 'package:styled_widget/styled_widget.dart'; -part 'site_list.g.dart'; +final siteListNotifierProvider = AsyncNotifierProvider.family.autoDispose( + SiteListNotifier.new, +); -@riverpod -class SiteListNotifier extends _$SiteListNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; +class SiteListNotifier + extends AutoDisposeFamilyAsyncNotifier, String> + with FamilyAsyncPaginationController { + static const int pageSize = 20; @override - Future> build(String? pubName) { - // immediately load first page - return fetch(cursor: null); - } - - @override - Future> fetch({ - required String? cursor, - }) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); // read the current family argument passed to provider - final queryParams = {'offset': offset, 'take': _pageSize}; + final queryParams = {'offset': fetchedCount.toString(), 'take': pageSize}; final response = await client.get( - '/zone/sites/$pubName', + '/zone/sites/$arg', queryParameters: queryParams, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; - final items = data.map((json) => SnPublicationSite.fromJson(json)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final items = + response.data + .map((json) => SnPublicationSite.fromJson(json)) + .cast() + .toList(); - final hasMore = offset + items.length < total; - final nextCursor = hasMore ? (offset + items.length).toString() : null; - - return CursorPagingData( - items: items, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return items; } } @@ -84,24 +73,15 @@ class CreatorSiteListScreen extends HookConsumerWidget { child: CustomScrollView( slivers: [ const SliverGap(8), - PagingHelperSliverView( + PaginationList( provider: siteListNotifierProvider(pubName), - futureRefreshable: siteListNotifierProvider(pubName).future, - notifierRefreshable: siteListNotifierProvider(pubName).notifier, - contentBuilder: - (data, widgetCount, endItemView) => SliverList.builder( - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - final site = data.items[index]; - return ConstrainedBox( - constraints: BoxConstraints(maxWidth: 640), - child: _CreatorSiteItem(site: site, pubName: pubName), - ).center(); - }, - ), + notifier: siteListNotifierProvider(pubName).notifier, + itemBuilder: (context, index, site) { + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: 640), + child: _CreatorSiteItem(site: site, pubName: pubName), + ).center(); + }, ), ], ), diff --git a/lib/screens/creators/sites/site_list.g.dart b/lib/screens/creators/sites/site_list.g.dart deleted file mode 100644 index 687c8b10..00000000 --- a/lib/screens/creators/sites/site_list.g.dart +++ /dev/null @@ -1,183 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'site_list.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$siteListNotifierHash() => r'1670cadcc0c7ccbd98bc33bbf5b4af21e9cb166c'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -abstract class _$SiteListNotifier - extends - BuildlessAutoDisposeAsyncNotifier> { - late final String? pubName; - - FutureOr> build(String? pubName); -} - -/// See also [SiteListNotifier]. -@ProviderFor(SiteListNotifier) -const siteListNotifierProvider = SiteListNotifierFamily(); - -/// See also [SiteListNotifier]. -class SiteListNotifierFamily - extends Family>> { - /// See also [SiteListNotifier]. - const SiteListNotifierFamily(); - - /// See also [SiteListNotifier]. - SiteListNotifierProvider call(String? pubName) { - return SiteListNotifierProvider(pubName); - } - - @override - SiteListNotifierProvider getProviderOverride( - covariant SiteListNotifierProvider provider, - ) { - return call(provider.pubName); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'siteListNotifierProvider'; -} - -/// See also [SiteListNotifier]. -class SiteListNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - SiteListNotifier, - CursorPagingData - > { - /// See also [SiteListNotifier]. - SiteListNotifierProvider(String? pubName) - : this._internal( - () => SiteListNotifier()..pubName = pubName, - from: siteListNotifierProvider, - name: r'siteListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$siteListNotifierHash, - dependencies: SiteListNotifierFamily._dependencies, - allTransitiveDependencies: - SiteListNotifierFamily._allTransitiveDependencies, - pubName: pubName, - ); - - SiteListNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.pubName, - }) : super.internal(); - - final String? pubName; - - @override - FutureOr> runNotifierBuild( - covariant SiteListNotifier notifier, - ) { - return notifier.build(pubName); - } - - @override - Override overrideWith(SiteListNotifier Function() create) { - return ProviderOverride( - origin: this, - override: SiteListNotifierProvider._internal( - () => create()..pubName = pubName, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - pubName: pubName, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - SiteListNotifier, - CursorPagingData - > - createElement() { - return _SiteListNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is SiteListNotifierProvider && other.pubName == pubName; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, pubName.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin SiteListNotifierRef - on - AutoDisposeAsyncNotifierProviderRef< - CursorPagingData - > { - /// The parameter `pubName` of this provider. - String? get pubName; -} - -class _SiteListNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - SiteListNotifier, - CursorPagingData - > - with SiteListNotifierRef { - _SiteListNotifierProviderElement(super.provider); - - @override - String? get pubName => (origin as SiteListNotifierProvider).pubName; -} - -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/creators/stickers/stickers.dart b/lib/screens/creators/stickers/stickers.dart index 47ea955e..1c5c8551 100644 --- a/lib/screens/creators/stickers/stickers.dart +++ b/lib/screens/creators/stickers/stickers.dart @@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/file.dart'; import 'package:island/models/sticker.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/services/responsive.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; @@ -14,10 +15,10 @@ import 'package:island/widgets/content/cloud_file_picker.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:island/screens/creators/stickers/pack_detail.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; part 'stickers.g.dart'; @@ -81,111 +82,94 @@ class SliverStickerPacksList extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return PagingHelperView( + return PaginationList( provider: stickerPacksNotifierProvider(pubName), - futureRefreshable: stickerPacksNotifierProvider(pubName).future, - notifierRefreshable: stickerPacksNotifierProvider(pubName).notifier, - contentBuilder: - (data, widgetCount, endItemView) => ListView.builder( - padding: EdgeInsets.zero, - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - - final sticker = data.items[index]; - return ListTile( - shape: RoundedRectangleBorder( - borderRadius: const BorderRadius.all(Radius.circular(8)), - ), - title: Text(sticker.name), - subtitle: Text(sticker.description), - trailing: const Icon(Symbols.chevron_right), - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: sticker.name, - actions: [ - IconButton( - icon: const Icon(Symbols.add_circle), - onPressed: () { - final id = sticker.id; - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'createSticker'.tr(), - child: StickerForm(packId: id), - ), - ).then((value) { - if (value != null) { - ref.invalidate( - stickerPackContentProvider(id), - ); - } - }); - }, - ), - StickerPackActionMenu( - pubName: pubName, - packId: sticker.id, - iconShadow: Shadow(), - ), - ], - child: StickerPackDetailContent( - id: sticker.id, - pubName: pubName, - ), - ), - ); - }, - ); - }, + notifier: stickerPacksNotifierProvider(pubName).notifier, + itemBuilder: (context, index, sticker) { + return ListTile( + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8)), ), + title: Text(sticker.name), + subtitle: Text(sticker.description), + trailing: const Icon(Symbols.chevron_right), + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => SheetScaffold( + titleText: sticker.name, + actions: [ + IconButton( + icon: const Icon(Symbols.add_circle), + onPressed: () { + final id = sticker.id; + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => SheetScaffold( + titleText: 'createSticker'.tr(), + child: StickerForm(packId: id), + ), + ).then((value) { + if (value != null) { + ref.invalidate(stickerPackContentProvider(id)); + } + }); + }, + ), + StickerPackActionMenu( + pubName: pubName, + packId: sticker.id, + iconShadow: Shadow(), + ), + ], + child: StickerPackDetailContent( + id: sticker.id, + pubName: pubName, + ), + ), + ); + }, + ); + }, ); } } -@riverpod -class StickerPacksNotifier extends _$StickerPacksNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; +final stickerPacksNotifierProvider = AsyncNotifierProvider.family.autoDispose( + StickerPacksNotifier.new, +); + +class StickerPacksNotifier + extends AutoDisposeFamilyAsyncNotifier, String> + with FamilyAsyncPaginationController { + static const int pageSize = 20; @override - Future> build(String pubName) { - return fetch(cursor: null); - } - - @override - Future> fetch({ - required String? cursor, - }) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); try { final response = await client.get( '/sphere/stickers', - queryParameters: {'offset': offset, 'take': _pageSize, 'pub': pubName}, + queryParameters: { + 'offset': fetchedCount.toString(), + 'take': pageSize, + 'pub': arg, + }, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; - final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final stickers = + response.data + .map((e) => SnStickerPack.fromJson(e)) + .cast() + .toList(); - final hasMore = offset + stickers.length < total; - final nextCursor = hasMore ? (offset + stickers.length).toString() : null; - - return CursorPagingData( - items: stickers, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return stickers; } catch (err) { rethrow; } diff --git a/lib/screens/creators/stickers/stickers.g.dart b/lib/screens/creators/stickers/stickers.g.dart index cd5a4d4b..d6c4c1a6 100644 --- a/lib/screens/creators/stickers/stickers.g.dart +++ b/lib/screens/creators/stickers/stickers.g.dart @@ -147,154 +147,5 @@ class _StickerPackProviderElement String? get packId => (origin as StickerPackProvider).packId; } -String _$stickerPacksNotifierHash() => - r'30024b35235f3085a5b1ec2204d0a974ee907e22'; - -abstract class _$StickerPacksNotifier - extends BuildlessAutoDisposeAsyncNotifier> { - late final String pubName; - - FutureOr> build(String pubName); -} - -/// See also [StickerPacksNotifier]. -@ProviderFor(StickerPacksNotifier) -const stickerPacksNotifierProvider = StickerPacksNotifierFamily(); - -/// See also [StickerPacksNotifier]. -class StickerPacksNotifierFamily - extends Family>> { - /// See also [StickerPacksNotifier]. - const StickerPacksNotifierFamily(); - - /// See also [StickerPacksNotifier]. - StickerPacksNotifierProvider call(String pubName) { - return StickerPacksNotifierProvider(pubName); - } - - @override - StickerPacksNotifierProvider getProviderOverride( - covariant StickerPacksNotifierProvider provider, - ) { - return call(provider.pubName); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'stickerPacksNotifierProvider'; -} - -/// See also [StickerPacksNotifier]. -class StickerPacksNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - StickerPacksNotifier, - CursorPagingData - > { - /// See also [StickerPacksNotifier]. - StickerPacksNotifierProvider(String pubName) - : this._internal( - () => StickerPacksNotifier()..pubName = pubName, - from: stickerPacksNotifierProvider, - name: r'stickerPacksNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$stickerPacksNotifierHash, - dependencies: StickerPacksNotifierFamily._dependencies, - allTransitiveDependencies: - StickerPacksNotifierFamily._allTransitiveDependencies, - pubName: pubName, - ); - - StickerPacksNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.pubName, - }) : super.internal(); - - final String pubName; - - @override - FutureOr> runNotifierBuild( - covariant StickerPacksNotifier notifier, - ) { - return notifier.build(pubName); - } - - @override - Override overrideWith(StickerPacksNotifier Function() create) { - return ProviderOverride( - origin: this, - override: StickerPacksNotifierProvider._internal( - () => create()..pubName = pubName, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - pubName: pubName, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - StickerPacksNotifier, - CursorPagingData - > - createElement() { - return _StickerPacksNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is StickerPacksNotifierProvider && other.pubName == pubName; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, pubName.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin StickerPacksNotifierRef - on AutoDisposeAsyncNotifierProviderRef> { - /// The parameter `pubName` of this provider. - String get pubName; -} - -class _StickerPacksNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - StickerPacksNotifier, - CursorPagingData - > - with StickerPacksNotifierRef { - _StickerPacksNotifierProviderElement(super.provider); - - @override - String get pubName => (origin as StickerPacksNotifierProvider).pubName; -} - // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/discovery/articles.dart b/lib/screens/discovery/articles.dart index 38a9508f..52469306 100644 --- a/lib/screens/discovery/articles.dart +++ b/lib/screens/discovery/articles.dart @@ -1,41 +1,37 @@ import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/webfeed.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/web_article_card.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; part 'articles.g.dart'; +part 'articles.freezed.dart'; -@riverpod -class ArticlesListNotifier extends _$ArticlesListNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; +@freezed +sealed class ArticleListQuery with _$ArticleListQuery { + const factory ArticleListQuery({String? feedId, String? publisherId}) = + _ArticleListQuery; +} - Map _params = {}; +final articlesListNotifierProvider = AsyncNotifierProvider.family.autoDispose( + ArticlesListNotifier.new, +); + +class ArticlesListNotifier + extends AutoDisposeFamilyAsyncNotifier, ArticleListQuery> + with FamilyAsyncPaginationController { + static const int pageSize = 20; @override - Future> build({ - String? feedId, - String? publisherId, - }) async { - _params = { - if (feedId != null) 'feedId': feedId, - if (publisherId != null) 'publisherId': publisherId, - }; - return fetch(cursor: null); - } - - @override - Future> fetch({ - required String? cursor, - }) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); - final queryParams = {'limit': _pageSize, 'offset': offset, ..._params}; + final queryParams = {'limit': pageSize, 'offset': fetchedCount.toString()}; try { final response = await client.get( @@ -43,23 +39,17 @@ class ArticlesListNotifier extends _$ArticlesListNotifier queryParameters: queryParams, ); - final List data = response.data; final articles = - data + response.data .map( (json) => SnWebArticle.fromJson(json as Map), ) + .cast() .toList(); - final total = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0; - final hasMore = offset + articles.length < total; - final nextCursor = hasMore ? (offset + articles.length).toString() : null; + totalCount = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0; - return CursorPagingData( - items: articles, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return articles; } catch (e) { debugPrint('Error fetching articles: $e'); rethrow; @@ -85,34 +75,17 @@ class SliverArticlesList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return PagingHelperSliverView( - provider: articlesListNotifierProvider( - feedId: feedId, - publisherId: publisherId, - ), - futureRefreshable: - articlesListNotifierProvider( - feedId: feedId, - publisherId: publisherId, - ).future, - notifierRefreshable: - articlesListNotifierProvider( - feedId: feedId, - publisherId: publisherId, - ).notifier, - contentBuilder: - (data, widgetCount, endItemView) => SliverList.separated( - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - - final article = data.items[index]; - return WebArticleCard(article: article, showDetails: true); - }, - separatorBuilder: (context, index) => const SizedBox(height: 12), - ), + final provider = articlesListNotifierProvider( + ArticleListQuery(feedId: feedId, publisherId: publisherId), + ); + return PaginationList( + provider: provider, + notifier: provider.notifier, + isRefreshable: false, + isSliver: true, + itemBuilder: (context, index, article) { + return WebArticleCard(article: article, showDetails: true); + }, ); } } diff --git a/lib/screens/discovery/articles.freezed.dart b/lib/screens/discovery/articles.freezed.dart new file mode 100644 index 00000000..83353961 --- /dev/null +++ b/lib/screens/discovery/articles.freezed.dart @@ -0,0 +1,268 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'articles.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ArticleListQuery { + + String? get feedId; String? get publisherId; +/// Create a copy of ArticleListQuery +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ArticleListQueryCopyWith get copyWith => _$ArticleListQueryCopyWithImpl(this as ArticleListQuery, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ArticleListQuery&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)); +} + + +@override +int get hashCode => Object.hash(runtimeType,feedId,publisherId); + +@override +String toString() { + return 'ArticleListQuery(feedId: $feedId, publisherId: $publisherId)'; +} + + +} + +/// @nodoc +abstract mixin class $ArticleListQueryCopyWith<$Res> { + factory $ArticleListQueryCopyWith(ArticleListQuery value, $Res Function(ArticleListQuery) _then) = _$ArticleListQueryCopyWithImpl; +@useResult +$Res call({ + String? feedId, String? publisherId +}); + + + + +} +/// @nodoc +class _$ArticleListQueryCopyWithImpl<$Res> + implements $ArticleListQueryCopyWith<$Res> { + _$ArticleListQueryCopyWithImpl(this._self, this._then); + + final ArticleListQuery _self; + final $Res Function(ArticleListQuery) _then; + +/// Create a copy of ArticleListQuery +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? feedId = freezed,Object? publisherId = freezed,}) { + return _then(_self.copyWith( +feedId: freezed == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable +as String?,publisherId: freezed == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ArticleListQuery]. +extension ArticleListQueryPatterns on ArticleListQuery { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ArticleListQuery value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ArticleListQuery() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ArticleListQuery value) $default,){ +final _that = this; +switch (_that) { +case _ArticleListQuery(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ArticleListQuery value)? $default,){ +final _that = this; +switch (_that) { +case _ArticleListQuery() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? feedId, String? publisherId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ArticleListQuery() when $default != null: +return $default(_that.feedId,_that.publisherId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? feedId, String? publisherId) $default,) {final _that = this; +switch (_that) { +case _ArticleListQuery(): +return $default(_that.feedId,_that.publisherId);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? feedId, String? publisherId)? $default,) {final _that = this; +switch (_that) { +case _ArticleListQuery() when $default != null: +return $default(_that.feedId,_that.publisherId);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _ArticleListQuery implements ArticleListQuery { + const _ArticleListQuery({this.feedId, this.publisherId}); + + +@override final String? feedId; +@override final String? publisherId; + +/// Create a copy of ArticleListQuery +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ArticleListQueryCopyWith<_ArticleListQuery> get copyWith => __$ArticleListQueryCopyWithImpl<_ArticleListQuery>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ArticleListQuery&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)); +} + + +@override +int get hashCode => Object.hash(runtimeType,feedId,publisherId); + +@override +String toString() { + return 'ArticleListQuery(feedId: $feedId, publisherId: $publisherId)'; +} + + +} + +/// @nodoc +abstract mixin class _$ArticleListQueryCopyWith<$Res> implements $ArticleListQueryCopyWith<$Res> { + factory _$ArticleListQueryCopyWith(_ArticleListQuery value, $Res Function(_ArticleListQuery) _then) = __$ArticleListQueryCopyWithImpl; +@override @useResult +$Res call({ + String? feedId, String? publisherId +}); + + + + +} +/// @nodoc +class __$ArticleListQueryCopyWithImpl<$Res> + implements _$ArticleListQueryCopyWith<$Res> { + __$ArticleListQueryCopyWithImpl(this._self, this._then); + + final _ArticleListQuery _self; + final $Res Function(_ArticleListQuery) _then; + +/// Create a copy of ArticleListQuery +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? feedId = freezed,Object? publisherId = freezed,}) { + return _then(_ArticleListQuery( +feedId: freezed == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable +as String?,publisherId: freezed == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/screens/discovery/articles.g.dart b/lib/screens/discovery/articles.g.dart index ae473949..1f041361 100644 --- a/lib/screens/discovery/articles.g.dart +++ b/lib/screens/discovery/articles.g.dart @@ -25,201 +25,5 @@ final subscribedFeedsProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef SubscribedFeedsRef = AutoDisposeFutureProviderRef>; -String _$articlesListNotifierHash() => - r'579741af4d90c7c81f2e2697e57c4895b7a9dabc'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -abstract class _$ArticlesListNotifier - extends BuildlessAutoDisposeAsyncNotifier> { - late final String? feedId; - late final String? publisherId; - - FutureOr> build({ - String? feedId, - String? publisherId, - }); -} - -/// See also [ArticlesListNotifier]. -@ProviderFor(ArticlesListNotifier) -const articlesListNotifierProvider = ArticlesListNotifierFamily(); - -/// See also [ArticlesListNotifier]. -class ArticlesListNotifierFamily - extends Family>> { - /// See also [ArticlesListNotifier]. - const ArticlesListNotifierFamily(); - - /// See also [ArticlesListNotifier]. - ArticlesListNotifierProvider call({String? feedId, String? publisherId}) { - return ArticlesListNotifierProvider( - feedId: feedId, - publisherId: publisherId, - ); - } - - @override - ArticlesListNotifierProvider getProviderOverride( - covariant ArticlesListNotifierProvider provider, - ) { - return call(feedId: provider.feedId, publisherId: provider.publisherId); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'articlesListNotifierProvider'; -} - -/// See also [ArticlesListNotifier]. -class ArticlesListNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - ArticlesListNotifier, - CursorPagingData - > { - /// See also [ArticlesListNotifier]. - ArticlesListNotifierProvider({String? feedId, String? publisherId}) - : this._internal( - () => - ArticlesListNotifier() - ..feedId = feedId - ..publisherId = publisherId, - from: articlesListNotifierProvider, - name: r'articlesListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$articlesListNotifierHash, - dependencies: ArticlesListNotifierFamily._dependencies, - allTransitiveDependencies: - ArticlesListNotifierFamily._allTransitiveDependencies, - feedId: feedId, - publisherId: publisherId, - ); - - ArticlesListNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.feedId, - required this.publisherId, - }) : super.internal(); - - final String? feedId; - final String? publisherId; - - @override - FutureOr> runNotifierBuild( - covariant ArticlesListNotifier notifier, - ) { - return notifier.build(feedId: feedId, publisherId: publisherId); - } - - @override - Override overrideWith(ArticlesListNotifier Function() create) { - return ProviderOverride( - origin: this, - override: ArticlesListNotifierProvider._internal( - () => - create() - ..feedId = feedId - ..publisherId = publisherId, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - feedId: feedId, - publisherId: publisherId, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - ArticlesListNotifier, - CursorPagingData - > - createElement() { - return _ArticlesListNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is ArticlesListNotifierProvider && - other.feedId == feedId && - other.publisherId == publisherId; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, feedId.hashCode); - hash = _SystemHash.combine(hash, publisherId.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin ArticlesListNotifierRef - on AutoDisposeAsyncNotifierProviderRef> { - /// The parameter `feedId` of this provider. - String? get feedId; - - /// The parameter `publisherId` of this provider. - String? get publisherId; -} - -class _ArticlesListNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - ArticlesListNotifier, - CursorPagingData - > - with ArticlesListNotifierRef { - _ArticlesListNotifierProviderElement(super.provider); - - @override - String? get feedId => (origin as ArticlesListNotifierProvider).feedId; - @override - String? get publisherId => - (origin as ArticlesListNotifierProvider).publisherId; -} - // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/discovery/feeds/feed_detail.dart b/lib/screens/discovery/feeds/feed_detail.dart index 3412e652..f48b6389 100644 --- a/lib/screens/discovery/feeds/feed_detail.dart +++ b/lib/screens/discovery/feeds/feed_detail.dart @@ -1,17 +1,16 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/webfeed.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:island/widgets/web_article_card.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:styled_widget/styled_widget.dart'; part 'feed_detail.g.dart'; @@ -23,52 +22,32 @@ Future marketplaceWebFeed(Ref ref, String feedId) async { return SnWebFeed.fromJson(resp.data); } -/// Provider for web feed articles content -@riverpod +final marketplaceWebFeedContentNotifierProvider = AsyncNotifierProvider.family + .autoDispose(MarketplaceWebFeedContentNotifier.new); + class MarketplaceWebFeedContentNotifier - extends _$MarketplaceWebFeedContentNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; + extends AutoDisposeFamilyAsyncNotifier, String> + with FamilyAsyncPaginationController { + static const int pageSize = 20; @override - Future> build(String feedId) async { - _feedId = feedId; - return fetch(cursor: null); - } - - late final String _feedId; - ValueNotifier totalCount = ValueNotifier(0); - - @override - Future> fetch({ - required String? cursor, - }) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); - final queryParams = {'offset': offset, 'take': _pageSize}; + final queryParams = {'offset': fetchedCount.toString(), 'take': pageSize}; final response = await client.get( - '/sphere/feeds/$_feedId/articles', + '/sphere/feeds/$arg/articles', queryParameters: queryParams, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - totalCount.value = total; - final List data = response.data; - final articles = data.map((json) => SnWebArticle.fromJson(json)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final articles = + response.data + .map((json) => SnWebArticle.fromJson(json)) + .cast() + .toList(); - final hasMore = offset + articles.length < total; - final nextCursor = hasMore ? (offset + articles.length).toString() : null; - - return CursorPagingData( - items: articles, - hasMore: hasMore, - nextCursor: nextCursor, - ); - } - - void dispose() { - totalCount.dispose(); + return articles; } } @@ -126,10 +105,6 @@ class MarketplaceWebFeedDetailScreen extends HookConsumerWidget { marketplaceWebFeedContentNotifierProvider(id).notifier, ); - useEffect(() { - return feedNotifier.dispose; - }, []); - return AppScaffold( appBar: AppBar(title: Text(feed.value?.title ?? 'loading'.tr())), body: Column( @@ -147,14 +122,10 @@ class MarketplaceWebFeedDetailScreen extends HookConsumerWidget { spacing: 4, children: [ const Icon(Symbols.rss_feed, size: 16), - ListenableBuilder( - listenable: feedNotifier.totalCount, - builder: - (context, _) => Text( - 'webFeedArticleCount'.plural( - feedNotifier.totalCount.value, - ), - ), + Text( + 'webFeedArticleCount'.plural( + feedNotifier.totalCount ?? 0, + ), ), ], ).opacity(0.85), @@ -174,29 +145,12 @@ class MarketplaceWebFeedDetailScreen extends HookConsumerWidget { const Divider(height: 1), // Articles list Expanded( - child: PagingHelperView( + child: PaginationList( provider: marketplaceWebFeedContentNotifierProvider(id), - futureRefreshable: - marketplaceWebFeedContentNotifierProvider(id).future, - notifierRefreshable: - marketplaceWebFeedContentNotifierProvider(id).notifier, - contentBuilder: - (data, widgetCount, endItemView) => ListView.separated( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 20, - ), - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - - final article = data.items[index]; - return WebArticleCard(article: article); - }, - separatorBuilder: (context, index) => const Gap(12), - ), + notifier: marketplaceWebFeedContentNotifierProvider(id).notifier, + itemBuilder: (context, index, article) { + return WebArticleCard(article: article); + }, ), ), Container( diff --git a/lib/screens/discovery/feeds/feed_detail.g.dart b/lib/screens/discovery/feeds/feed_detail.g.dart index e3bc890f..03905634 100644 --- a/lib/screens/discovery/feeds/feed_detail.g.dart +++ b/lib/screens/discovery/feeds/feed_detail.g.dart @@ -290,169 +290,5 @@ class _MarketplaceWebFeedSubscriptionProviderElement (origin as MarketplaceWebFeedSubscriptionProvider).feedId; } -String _$marketplaceWebFeedContentNotifierHash() => - r'25688082884cb824eeff300888ba38c9748295dc'; - -abstract class _$MarketplaceWebFeedContentNotifier - extends BuildlessAutoDisposeAsyncNotifier> { - late final String feedId; - - FutureOr> build(String feedId); -} - -/// Provider for web feed articles content -/// -/// Copied from [MarketplaceWebFeedContentNotifier]. -@ProviderFor(MarketplaceWebFeedContentNotifier) -const marketplaceWebFeedContentNotifierProvider = - MarketplaceWebFeedContentNotifierFamily(); - -/// Provider for web feed articles content -/// -/// Copied from [MarketplaceWebFeedContentNotifier]. -class MarketplaceWebFeedContentNotifierFamily - extends Family>> { - /// Provider for web feed articles content - /// - /// Copied from [MarketplaceWebFeedContentNotifier]. - const MarketplaceWebFeedContentNotifierFamily(); - - /// Provider for web feed articles content - /// - /// Copied from [MarketplaceWebFeedContentNotifier]. - MarketplaceWebFeedContentNotifierProvider call(String feedId) { - return MarketplaceWebFeedContentNotifierProvider(feedId); - } - - @override - MarketplaceWebFeedContentNotifierProvider getProviderOverride( - covariant MarketplaceWebFeedContentNotifierProvider provider, - ) { - return call(provider.feedId); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'marketplaceWebFeedContentNotifierProvider'; -} - -/// Provider for web feed articles content -/// -/// Copied from [MarketplaceWebFeedContentNotifier]. -class MarketplaceWebFeedContentNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - MarketplaceWebFeedContentNotifier, - CursorPagingData - > { - /// Provider for web feed articles content - /// - /// Copied from [MarketplaceWebFeedContentNotifier]. - MarketplaceWebFeedContentNotifierProvider(String feedId) - : this._internal( - () => MarketplaceWebFeedContentNotifier()..feedId = feedId, - from: marketplaceWebFeedContentNotifierProvider, - name: r'marketplaceWebFeedContentNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$marketplaceWebFeedContentNotifierHash, - dependencies: MarketplaceWebFeedContentNotifierFamily._dependencies, - allTransitiveDependencies: - MarketplaceWebFeedContentNotifierFamily._allTransitiveDependencies, - feedId: feedId, - ); - - MarketplaceWebFeedContentNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.feedId, - }) : super.internal(); - - final String feedId; - - @override - FutureOr> runNotifierBuild( - covariant MarketplaceWebFeedContentNotifier notifier, - ) { - return notifier.build(feedId); - } - - @override - Override overrideWith(MarketplaceWebFeedContentNotifier Function() create) { - return ProviderOverride( - origin: this, - override: MarketplaceWebFeedContentNotifierProvider._internal( - () => create()..feedId = feedId, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - feedId: feedId, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - MarketplaceWebFeedContentNotifier, - CursorPagingData - > - createElement() { - return _MarketplaceWebFeedContentNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is MarketplaceWebFeedContentNotifierProvider && - other.feedId == feedId; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, feedId.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin MarketplaceWebFeedContentNotifierRef - on AutoDisposeAsyncNotifierProviderRef> { - /// The parameter `feedId` of this provider. - String get feedId; -} - -class _MarketplaceWebFeedContentNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - MarketplaceWebFeedContentNotifier, - CursorPagingData - > - with MarketplaceWebFeedContentNotifierRef { - _MarketplaceWebFeedContentNotifierProviderElement(super.provider); - - @override - String get feedId => - (origin as MarketplaceWebFeedContentNotifierProvider).feedId; -} - // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/screens/discovery/feeds/feed_marketplace.dart b/lib/screens/discovery/feeds/feed_marketplace.dart index 42a9e481..9016c753 100644 --- a/lib/screens/discovery/feeds/feed_marketplace.dart +++ b/lib/screens/discovery/feeds/feed_marketplace.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -6,52 +7,44 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/webfeed.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/widgets/app_scaffold.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'dart:async'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; +final marketplaceWebFeedsNotifierProvider = AsyncNotifierProvider( + MarketplaceWebFeedsNotifier.new, +); -part 'feed_marketplace.g.dart'; - -@riverpod -class MarketplaceWebFeedsNotifier extends _$MarketplaceWebFeedsNotifier - with CursorPagingNotifierMixin { - String? _query; +class MarketplaceWebFeedsNotifier extends AsyncNotifier> + with + AsyncPaginationController, + AsyncPaginationFilter { + @override + String? currentFilter; @override - Future> build({required String? query}) { - _query = query; - return fetch(cursor: null); - } - - @override - Future> fetch({required String? cursor}) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); final response = await client.get( '/sphere/feeds/explore', queryParameters: { - 'offset': offset, + 'offset': fetchedCount.toString(), 'take': 20, - if (_query != null && _query!.isNotEmpty) 'query': _query, + if (currentFilter != null && currentFilter!.isNotEmpty) + 'query': currentFilter, }, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; - final feeds = data.map((e) => SnWebFeed.fromJson(e)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final feeds = + response.data + .map((e) => SnWebFeed.fromJson(e)) + .cast() + .toList(); - final hasMore = offset + feeds.length < total; - final nextCursor = hasMore ? (offset + feeds.length).toString() : null; - - return CursorPagingData( - items: feeds, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return feeds; } } @@ -86,83 +79,70 @@ class MarketplaceWebFeedsScreen extends HookConsumerWidget { title: const Text('webFeeds').tr(), actions: const [Gap(8)], ), - body: PagingHelperView( - provider: marketplaceWebFeedsNotifierProvider(query: query.value), - futureRefreshable: - marketplaceWebFeedsNotifierProvider(query: query.value).future, - notifierRefreshable: - marketplaceWebFeedsNotifierProvider(query: query.value).notifier, - contentBuilder: - (data, widgetCount, endItemView) => Column( - children: [ - // Search bar above the list - Padding( - padding: const EdgeInsets.all(16), - child: SearchBar( - elevation: WidgetStateProperty.all(4), - controller: searchController, - focusNode: focusNode, - hintText: 'search'.tr(), - leading: const Icon(Symbols.search), - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric(horizontal: 24), - ), - onTapOutside: - (_) => FocusManager.instance.primaryFocus?.unfocus(), - trailing: [ - if (query.value != null && query.value!.isNotEmpty) - IconButton( - icon: const Icon(Symbols.close), - onPressed: () { - query.value = null; - searchController.clear(); - focusNode.unfocus(); - }, - ), - ], - onChanged: (value) { - // Debounce search to avoid excessive API calls - debounceTimer.value?.cancel(); - debounceTimer.value = Timer( - const Duration(milliseconds: 500), - () { - query.value = value.isEmpty ? null : value; - }, - ); - }, - onSubmitted: (value) { - query.value = value.isEmpty ? null : value; + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: SearchBar( + elevation: WidgetStateProperty.all(4), + controller: searchController, + focusNode: focusNode, + hintText: 'search'.tr(), + leading: const Icon(Symbols.search), + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric(horizontal: 24), + ), + onTapOutside: + (_) => FocusManager.instance.primaryFocus?.unfocus(), + trailing: [ + if (query.value != null && query.value!.isNotEmpty) + IconButton( + icon: const Icon(Symbols.close), + onPressed: () { + query.value = null; + searchController.clear(); focusNode.unfocus(); }, ), - ), - Expanded( - child: ListView.builder( - padding: EdgeInsets.zero, - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - - final feed = data.items[index]; - return ListTile( - title: Text(feed.title), - subtitle: Text(feed.description ?? ''), - trailing: const Icon(Symbols.chevron_right), - onTap: () { - // Navigate to web feed detail page - context.pushNamed( - 'webFeedDetail', - pathParameters: {'feedId': feed.id}, - ); - }, - ); - }, - ), - ), ], + onChanged: (value) { + // Debounce search to avoid excessive API calls + debounceTimer.value?.cancel(); + debounceTimer.value = Timer( + const Duration(milliseconds: 500), + () { + query.value = value.isEmpty ? null : value; + }, + ); + }, + onSubmitted: (value) { + query.value = value.isEmpty ? null : value; + focusNode.unfocus(); + }, ), + ), + Expanded( + child: PaginationList( + provider: marketplaceWebFeedsNotifierProvider, + notifier: marketplaceWebFeedsNotifierProvider.notifier, + padding: EdgeInsets.zero, + itemBuilder: (context, index, feed) { + return ListTile( + title: Text(feed.title), + subtitle: Text(feed.description ?? ''), + trailing: const Icon(Symbols.chevron_right), + onTap: () { + // Navigate to web feed detail page + context.pushNamed( + 'webFeedDetail', + pathParameters: {'feedId': feed.id}, + ); + }, + ); + }, + ), + ), + ], ), ); } diff --git a/lib/screens/discovery/feeds/feed_marketplace.g.dart b/lib/screens/discovery/feeds/feed_marketplace.g.dart deleted file mode 100644 index ef3adffc..00000000 --- a/lib/screens/discovery/feeds/feed_marketplace.g.dart +++ /dev/null @@ -1,180 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'feed_marketplace.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$marketplaceWebFeedsNotifierHash() => - r'774b2985f2f7d61fe958f534f84e39f814327c4e'; - -/// Copied from Dart SDK -class _SystemHash { - _SystemHash._(); - - static int combine(int hash, int value) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + value); - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); - return hash ^ (hash >> 6); - } - - static int finish(int hash) { - // ignore: parameter_assignments - hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); - // ignore: parameter_assignments - hash = hash ^ (hash >> 11); - return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); - } -} - -abstract class _$MarketplaceWebFeedsNotifier - extends BuildlessAutoDisposeAsyncNotifier> { - late final String? query; - - FutureOr> build({required String? query}); -} - -/// See also [MarketplaceWebFeedsNotifier]. -@ProviderFor(MarketplaceWebFeedsNotifier) -const marketplaceWebFeedsNotifierProvider = MarketplaceWebFeedsNotifierFamily(); - -/// See also [MarketplaceWebFeedsNotifier]. -class MarketplaceWebFeedsNotifierFamily - extends Family>> { - /// See also [MarketplaceWebFeedsNotifier]. - const MarketplaceWebFeedsNotifierFamily(); - - /// See also [MarketplaceWebFeedsNotifier]. - MarketplaceWebFeedsNotifierProvider call({required String? query}) { - return MarketplaceWebFeedsNotifierProvider(query: query); - } - - @override - MarketplaceWebFeedsNotifierProvider getProviderOverride( - covariant MarketplaceWebFeedsNotifierProvider provider, - ) { - return call(query: provider.query); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'marketplaceWebFeedsNotifierProvider'; -} - -/// See also [MarketplaceWebFeedsNotifier]. -class MarketplaceWebFeedsNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - MarketplaceWebFeedsNotifier, - CursorPagingData - > { - /// See also [MarketplaceWebFeedsNotifier]. - MarketplaceWebFeedsNotifierProvider({required String? query}) - : this._internal( - () => MarketplaceWebFeedsNotifier()..query = query, - from: marketplaceWebFeedsNotifierProvider, - name: r'marketplaceWebFeedsNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$marketplaceWebFeedsNotifierHash, - dependencies: MarketplaceWebFeedsNotifierFamily._dependencies, - allTransitiveDependencies: - MarketplaceWebFeedsNotifierFamily._allTransitiveDependencies, - query: query, - ); - - MarketplaceWebFeedsNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.query, - }) : super.internal(); - - final String? query; - - @override - FutureOr> runNotifierBuild( - covariant MarketplaceWebFeedsNotifier notifier, - ) { - return notifier.build(query: query); - } - - @override - Override overrideWith(MarketplaceWebFeedsNotifier Function() create) { - return ProviderOverride( - origin: this, - override: MarketplaceWebFeedsNotifierProvider._internal( - () => create()..query = query, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - query: query, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - MarketplaceWebFeedsNotifier, - CursorPagingData - > - createElement() { - return _MarketplaceWebFeedsNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is MarketplaceWebFeedsNotifierProvider && other.query == query; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, query.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin MarketplaceWebFeedsNotifierRef - on AutoDisposeAsyncNotifierProviderRef> { - /// The parameter `query` of this provider. - String? get query; -} - -class _MarketplaceWebFeedsNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - MarketplaceWebFeedsNotifier, - CursorPagingData - > - with MarketplaceWebFeedsNotifierRef { - _MarketplaceWebFeedsNotifierProviderElement(super.provider); - - @override - String? get query => (origin as MarketplaceWebFeedsNotifierProvider).query; -} - -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/widgets/post/compose_poll.dart b/lib/widgets/post/compose_poll.dart index c5ed2cc2..cdb1a461 100644 --- a/lib/widgets/post/compose_poll.dart +++ b/lib/widgets/post/compose_poll.dart @@ -9,8 +9,8 @@ import 'package:island/screens/creators/poll/poll_list.dart'; import 'package:island/screens/poll/poll_editor.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/sheet.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:island/widgets/post/publishers_modal.dart'; @@ -45,35 +45,21 @@ class ComposePollSheet extends HookConsumerWidget { child: TabBarView( children: [ // Link/Select existing poll list - PagingHelperView( + PaginationList( provider: pollListNotifierProvider(pub?.name), - futureRefreshable: - pollListNotifierProvider(pub?.name).future, - notifierRefreshable: - pollListNotifierProvider(pub?.name).notifier, - contentBuilder: - (data, widgetCount, endItemView) => ListView.builder( - padding: EdgeInsets.zero, - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - - final poll = data.items[index]; - - return ListTile( - leading: const Icon(Symbols.how_to_vote, fill: 1), - title: Text(poll.title ?? 'untitled'.tr()), - subtitle: _buildPollSubtitle(poll), - onTap: () { - Navigator.of( - context, - ).pop(SnPoll.fromPollWithStats(poll)); - }, - ); - }, - ), + notifier: pollListNotifierProvider(pub?.name).notifier, + itemBuilder: (context, index, poll) { + return ListTile( + leading: const Icon(Symbols.how_to_vote, fill: 1), + title: Text(poll.title ?? 'untitled'.tr()), + subtitle: _buildPollSubtitle(poll), + onTap: () { + Navigator.of( + context, + ).pop(SnPoll.fromPollWithStats(poll)); + }, + ); + }, ), // Create new poll and return it