🐛 Fixes and optimzation on post
♻️ Replace freezed abstract classes with sealed
			
			
This commit is contained in:
		| @@ -5,7 +5,7 @@ part 'activity.freezed.dart'; | ||||
| part 'activity.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnActivity with _$SnActivity { | ||||
| sealed class SnActivity with _$SnActivity { | ||||
|   const factory SnActivity({ | ||||
|     required String id, | ||||
|     required String type, | ||||
| @@ -24,7 +24,7 @@ abstract class SnActivity with _$SnActivity { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnCheckInResult with _$SnCheckInResult { | ||||
| sealed class SnCheckInResult with _$SnCheckInResult { | ||||
|   const factory SnCheckInResult({ | ||||
|     required String id, | ||||
|     required int level, | ||||
| @@ -41,7 +41,7 @@ abstract class SnCheckInResult with _$SnCheckInResult { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnFortuneTip with _$SnFortuneTip { | ||||
| sealed class SnFortuneTip with _$SnFortuneTip { | ||||
|   const factory SnFortuneTip({ | ||||
|     required bool isPositive, | ||||
|     required String title, | ||||
| @@ -53,7 +53,7 @@ abstract class SnFortuneTip with _$SnFortuneTip { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnEventCalendarEntry with _$SnEventCalendarEntry { | ||||
| sealed class SnEventCalendarEntry with _$SnEventCalendarEntry { | ||||
|   const factory SnEventCalendarEntry({ | ||||
|     required DateTime date, | ||||
|     required SnCheckInResult? checkInResult, | ||||
|   | ||||
| @@ -4,7 +4,7 @@ part 'auth.freezed.dart'; | ||||
| part 'auth.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class AppTokenPair with _$AppTokenPair { | ||||
| sealed class AppTokenPair with _$AppTokenPair { | ||||
|   const factory AppTokenPair({ | ||||
|     required String accessToken, | ||||
|     required String refreshToken, | ||||
| @@ -15,7 +15,7 @@ abstract class AppTokenPair with _$AppTokenPair { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAuthChallenge with _$SnAuthChallenge { | ||||
| sealed class SnAuthChallenge with _$SnAuthChallenge { | ||||
|   const factory SnAuthChallenge({ | ||||
|     required String id, | ||||
|     required DateTime expiredAt, | ||||
| @@ -38,7 +38,7 @@ abstract class SnAuthChallenge with _$SnAuthChallenge { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAuthFactor with _$SnAuthFactor { | ||||
| sealed class SnAuthFactor with _$SnAuthFactor { | ||||
|   const factory SnAuthFactor({ | ||||
|     required String id, | ||||
|     required int type, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ part 'chat.freezed.dart'; | ||||
| part 'chat.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatRoom with _$SnChatRoom { | ||||
| sealed class SnChatRoom with _$SnChatRoom { | ||||
|   const factory SnChatRoom({ | ||||
|     required String id, | ||||
|     required String? name, | ||||
| @@ -31,7 +31,7 @@ abstract class SnChatRoom with _$SnChatRoom { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatMessage with _$SnChatMessage { | ||||
| sealed class SnChatMessage with _$SnChatMessage { | ||||
|   const factory SnChatMessage({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
| @@ -56,7 +56,7 @@ abstract class SnChatMessage with _$SnChatMessage { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatReaction with _$SnChatReaction { | ||||
| sealed class SnChatReaction with _$SnChatReaction { | ||||
|   const factory SnChatReaction({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
| @@ -74,7 +74,7 @@ abstract class SnChatReaction with _$SnChatReaction { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatMember with _$SnChatMember { | ||||
| sealed class SnChatMember with _$SnChatMember { | ||||
|   const factory SnChatMember({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
| @@ -96,7 +96,7 @@ abstract class SnChatMember with _$SnChatMember { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnChatSummary with _$SnChatSummary { | ||||
| sealed class SnChatSummary with _$SnChatSummary { | ||||
|   const factory SnChatSummary({ | ||||
|     required int unreadCount, | ||||
|     required SnChatMessage lastMessage, | ||||
| @@ -113,7 +113,7 @@ class MessageChangeAction { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class MessageChange with _$MessageChange { | ||||
| sealed class MessageChange with _$MessageChange { | ||||
|   const factory MessageChange({ | ||||
|     required String messageId, | ||||
|     required String action, | ||||
| @@ -126,7 +126,7 @@ abstract class MessageChange with _$MessageChange { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class MessageSyncResponse with _$MessageSyncResponse { | ||||
| sealed class MessageSyncResponse with _$MessageSyncResponse { | ||||
|   const factory MessageSyncResponse({ | ||||
|     @Default([]) List<MessageChange> changes, | ||||
|     required DateTime currentTimestamp, | ||||
| @@ -137,7 +137,7 @@ abstract class MessageSyncResponse with _$MessageSyncResponse { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class ChatRealtimeJoinResponse with _$ChatRealtimeJoinResponse { | ||||
| sealed class ChatRealtimeJoinResponse with _$ChatRealtimeJoinResponse { | ||||
|   const factory ChatRealtimeJoinResponse({ | ||||
|     required String token, | ||||
|     required Map<String, dynamic> config, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'file.g.dart'; | ||||
| enum UniversalFileType { image, video, audio, file } | ||||
|  | ||||
| @freezed | ||||
| abstract class UniversalFile with _$UniversalFile { | ||||
| sealed class UniversalFile with _$UniversalFile { | ||||
|   const UniversalFile._(); | ||||
|  | ||||
|   const factory UniversalFile({ | ||||
| @@ -31,7 +31,7 @@ abstract class UniversalFile with _$UniversalFile { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnCloudFile with _$SnCloudFile { | ||||
| sealed class SnCloudFile with _$SnCloudFile { | ||||
|   const factory SnCloudFile({ | ||||
|     required String id, | ||||
|     required String name, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ part 'post.freezed.dart'; | ||||
| part 'post.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnPost with _$SnPost { | ||||
| sealed class SnPost with _$SnPost { | ||||
|   const factory SnPost({ | ||||
|     required String id, | ||||
|     required String? title, | ||||
| @@ -43,7 +43,7 @@ abstract class SnPost with _$SnPost { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnPublisher with _$SnPublisher { | ||||
| sealed class SnPublisher with _$SnPublisher { | ||||
|   const factory SnPublisher({ | ||||
|     required String id, | ||||
|     required int type, | ||||
| @@ -66,7 +66,7 @@ abstract class SnPublisher with _$SnPublisher { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnPublisherStats with _$SnPublisherStats { | ||||
| sealed class SnPublisherStats with _$SnPublisherStats { | ||||
|   const factory SnPublisherStats({ | ||||
|     required int postsCreated, | ||||
|     required int stickerPacksCreated, | ||||
| @@ -80,7 +80,7 @@ abstract class SnPublisherStats with _$SnPublisherStats { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnSubscriptionStatus with _$SnSubscriptionStatus { | ||||
| sealed class SnSubscriptionStatus with _$SnSubscriptionStatus { | ||||
|   const factory SnSubscriptionStatus({ | ||||
|     required bool isSubscribed, | ||||
|     required int publisherId, | ||||
| @@ -92,7 +92,7 @@ abstract class SnSubscriptionStatus with _$SnSubscriptionStatus { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class ReactInfo with _$ReactInfo { | ||||
| sealed class ReactInfo with _$ReactInfo { | ||||
|   const factory ReactInfo({required String icon, required int attitude}) = | ||||
|       _ReactInfo; | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'realm.freezed.dart'; | ||||
| part 'realm.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnRealm with _$SnRealm { | ||||
| sealed class SnRealm with _$SnRealm { | ||||
|   const factory SnRealm({ | ||||
|     required String id, | ||||
|     required String slug, | ||||
| @@ -31,7 +31,7 @@ abstract class SnRealm with _$SnRealm { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnRealmMember with _$SnRealmMember { | ||||
| sealed class SnRealmMember with _$SnRealmMember { | ||||
|   const factory SnRealmMember({ | ||||
|     required String realmId, | ||||
|     required SnRealm? realm, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'relationship.freezed.dart'; | ||||
| part 'relationship.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnRelationship with _$SnRelationship { | ||||
| sealed class SnRelationship with _$SnRelationship { | ||||
|   const factory SnRelationship({ | ||||
|     required DateTime? createdAt, | ||||
|     required DateTime? updatedAt, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part 'sticker.freezed.dart'; | ||||
| part 'sticker.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnSticker with _$SnSticker { | ||||
| sealed class SnSticker with _$SnSticker { | ||||
|   const factory SnSticker({ | ||||
|     required String id, | ||||
|     required String slug, | ||||
| @@ -24,7 +24,7 @@ abstract class SnSticker with _$SnSticker { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnStickerPack with _$SnStickerPack { | ||||
| sealed class SnStickerPack with _$SnStickerPack { | ||||
|   const factory SnStickerPack({ | ||||
|     required String id, | ||||
|     required String name, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ part 'user.freezed.dart'; | ||||
| part 'user.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccount with _$SnAccount { | ||||
| sealed class SnAccount with _$SnAccount { | ||||
|   const factory SnAccount({ | ||||
|     required String id, | ||||
|     required String name, | ||||
| @@ -24,7 +24,7 @@ abstract class SnAccount with _$SnAccount { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccountProfile with _$SnAccountProfile { | ||||
| sealed class SnAccountProfile with _$SnAccountProfile { | ||||
|   const factory SnAccountProfile({ | ||||
|     required String id, | ||||
|     required String? firstName, | ||||
| @@ -48,7 +48,7 @@ abstract class SnAccountProfile with _$SnAccountProfile { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccountStatus with _$SnAccountStatus { | ||||
| sealed class SnAccountStatus with _$SnAccountStatus { | ||||
|   const factory SnAccountStatus({ | ||||
|     required String id, | ||||
|     required int attitude, | ||||
| @@ -69,7 +69,7 @@ abstract class SnAccountStatus with _$SnAccountStatus { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnAccountBadge with _$SnAccountBadge { | ||||
| sealed class SnAccountBadge with _$SnAccountBadge { | ||||
|   const factory SnAccountBadge({ | ||||
|     required String id, | ||||
|     required String type, | ||||
| @@ -88,7 +88,7 @@ abstract class SnAccountBadge with _$SnAccountBadge { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnNotification with _$SnNotification { | ||||
| sealed class SnNotification with _$SnNotification { | ||||
|   const factory SnNotification({ | ||||
|     required DateTime createdAt, | ||||
|     required DateTime updatedAt, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ part 'wallet.freezed.dart'; | ||||
| part 'wallet.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class SnWallet with _$SnWallet { | ||||
| sealed class SnWallet with _$SnWallet { | ||||
|   const factory SnWallet({ | ||||
|     required String id, | ||||
|     required List<SnWalletPocket> pockets, | ||||
| @@ -21,7 +21,7 @@ abstract class SnWallet with _$SnWallet { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnWalletPocket with _$SnWalletPocket { | ||||
| sealed class SnWalletPocket with _$SnWalletPocket { | ||||
|   const factory SnWalletPocket({ | ||||
|     required String id, | ||||
|     required String currency, | ||||
| @@ -37,7 +37,7 @@ abstract class SnWalletPocket with _$SnWalletPocket { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class SnTransaction with _$SnTransaction { | ||||
| sealed class SnTransaction with _$SnTransaction { | ||||
|   const factory SnTransaction({ | ||||
|     required String id, | ||||
|     required String currency, | ||||
|   | ||||
| @@ -51,7 +51,7 @@ final serverUrlProvider = Provider<String>((ref) { | ||||
| }); | ||||
|  | ||||
| @freezed | ||||
| abstract class AppSettings with _$AppSettings { | ||||
| sealed class AppSettings with _$AppSettings { | ||||
|   const factory AppSettings({ | ||||
|     required bool autoTranslate, | ||||
|     required bool soundEffects, | ||||
|   | ||||
| @@ -14,7 +14,7 @@ part 'websocket.freezed.dart'; | ||||
| part 'websocket.g.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class WebSocketState with _$WebSocketState { | ||||
| sealed class WebSocketState with _$WebSocketState { | ||||
|   const factory WebSocketState.connected() = _Connected; | ||||
|   const factory WebSocketState.connecting() = _Connecting; | ||||
|   const factory WebSocketState.disconnected() = _Disconnected; | ||||
| @@ -22,7 +22,7 @@ abstract class WebSocketState with _$WebSocketState { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class WebSocketPacket with _$WebSocketPacket { | ||||
| sealed class WebSocketPacket with _$WebSocketPacket { | ||||
|   const factory WebSocketPacket({ | ||||
|     required String type, | ||||
|     required Map<String, dynamic>? data, | ||||
|   | ||||
| @@ -17,7 +17,7 @@ part 'event_calendar.g.dart'; | ||||
| part 'event_calendar.freezed.dart'; | ||||
|  | ||||
| @freezed | ||||
| abstract class EventCalendarQuery with _$EventCalendarQuery { | ||||
| sealed class EventCalendarQuery with _$EventCalendarQuery { | ||||
|   const factory EventCalendarQuery({ | ||||
|     required String? uname, | ||||
|     required int year, | ||||
|   | ||||
| @@ -229,7 +229,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class ChatRoomMemberState with _$ChatRoomMemberState { | ||||
| sealed class ChatRoomMemberState with _$ChatRoomMemberState { | ||||
|   const factory ChatRoomMemberState({ | ||||
|     required List<SnChatMember> members, | ||||
|     required bool isLoading, | ||||
|   | ||||
| @@ -310,7 +310,7 @@ class _StickerPackActionMenu extends HookConsumerWidget { | ||||
| } | ||||
|  | ||||
| @freezed | ||||
| abstract class StickerWithPackQuery with _$StickerWithPackQuery { | ||||
| sealed class StickerWithPackQuery with _$StickerWithPackQuery { | ||||
|   const factory StickerWithPackQuery({ | ||||
|     required String packId, | ||||
|     required String id, | ||||
|   | ||||
| @@ -64,7 +64,12 @@ class PostDetailScreen extends HookConsumerWidget { | ||||
|                 child: Material( | ||||
|                   elevation: 2, | ||||
|                   color: Colors.transparent, | ||||
|                   child: PostQuickReply(parent: post).padding( | ||||
|                   child: PostQuickReply( | ||||
|                     parent: post, | ||||
|                     onPosted: () { | ||||
|                       ref.invalidate(postRepliesNotifierProvider(id)); | ||||
|                     }, | ||||
|                   ).padding( | ||||
|                     bottom: MediaQuery.of(context).padding.bottom + 16, | ||||
|                     top: 16, | ||||
|                     horizontal: 16, | ||||
|   | ||||
| @@ -16,7 +16,7 @@ const List<Tour> kAllTours = [ | ||||
| ]; | ||||
|  | ||||
| @freezed | ||||
| abstract class Tour with _$Tour { | ||||
| sealed class Tour with _$Tour { | ||||
|   const Tour._(); | ||||
|  | ||||
|   const factory Tour({required String id, required bool isStartup}) = _Tour; | ||||
|   | ||||
| @@ -1,13 +1,59 @@ | ||||
| import 'package:dio/dio.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:island/models/post.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/content/paging_helper_ext.dart'; | ||||
| import 'package:island/widgets/post/post_item.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:very_good_infinite_list/very_good_infinite_list.dart'; | ||||
|  | ||||
| part 'post_replies.g.dart'; | ||||
|  | ||||
| @riverpod | ||||
| class PostRepliesNotifier extends _$PostRepliesNotifier | ||||
|     with CursorPagingNotifierMixin<SnPost> { | ||||
|   static const int _pageSize = 20; | ||||
|  | ||||
|   PostRepliesNotifier(); | ||||
|  | ||||
|   String? _postId; | ||||
|  | ||||
|   @override | ||||
|   Future<CursorPagingData<SnPost>> build(String postId) { | ||||
|     _postId = postId; | ||||
|     return fetch(cursor: null); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<CursorPagingData<SnPost>> fetch({required String? cursor}) async { | ||||
|     if (_postId == null) { | ||||
|       throw StateError('PostRepliesNotifier must be initialized with postId'); | ||||
|     } | ||||
|  | ||||
|     final client = ref.read(apiClientProvider); | ||||
|     final offset = cursor == null ? 0 : int.parse(cursor); | ||||
|  | ||||
|     final response = await client.get( | ||||
|       '/posts/$_postId/replies', | ||||
|       queryParameters: {'offset': offset, 'take': _pageSize}, | ||||
|     ); | ||||
|  | ||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||
|     final List<dynamic> data = response.data; | ||||
|     final posts = data.map((json) => SnPost.fromJson(json)).toList(); | ||||
|  | ||||
|     final hasMore = offset + posts.length < total; | ||||
|     final nextCursor = hasMore ? (offset + posts.length).toString() : null; | ||||
|  | ||||
|     return CursorPagingData( | ||||
|       items: posts, | ||||
|       hasMore: hasMore, | ||||
|       nextCursor: nextCursor, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostRepliesList extends HookConsumerWidget { | ||||
|   final String postId; | ||||
| @@ -15,25 +61,14 @@ class PostRepliesList extends HookConsumerWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final postAsync = ref.watch(postRepliesProvider(postId)); | ||||
|     final isWide = isWideScreen(context); | ||||
|  | ||||
|     return postAsync.when( | ||||
|       data: | ||||
|           (controller) => SliverInfiniteList( | ||||
|             itemCount: controller.posts.length, | ||||
|             isLoading: controller.isLoading, | ||||
|             hasReachedMax: controller.hasReachedMax, | ||||
|             onFetchData: controller.fetchMore, | ||||
|             itemBuilder: (context, index) { | ||||
|               final post = controller.posts[index]; | ||||
|               return PostItem( | ||||
|                 item: post, | ||||
|                 backgroundColor: isWide ? Colors.transparent : null, | ||||
|               ); | ||||
|             }, | ||||
|             separatorBuilder: (_, __) => const Divider(height: 1), | ||||
|             emptyBuilder: (context) { | ||||
|     return PagingHelperSliverView( | ||||
|       provider: postRepliesNotifierProvider(postId), | ||||
|       futureRefreshable: postRepliesNotifierProvider(postId).future, | ||||
|       notifierRefreshable: postRepliesNotifierProvider(postId).notifier, | ||||
|       contentBuilder: (data, widgetCount, endItemView) { | ||||
|         if (data.items.isEmpty) { | ||||
|           return SliverToBoxAdapter( | ||||
|             child: Column( | ||||
|               children: [ | ||||
| @@ -41,70 +76,31 @@ class PostRepliesList extends HookConsumerWidget { | ||||
|                   'No replies', | ||||
|                   textAlign: TextAlign.center, | ||||
|                 ).fontSize(18).bold(), | ||||
|                     Text('Why not start a discussion?'), | ||||
|                 const Text('Why not start a discussion?'), | ||||
|               ], | ||||
|             ).padding(vertical: 16), | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         return SliverList.builder( | ||||
|           itemCount: widgetCount, | ||||
|           itemBuilder: (context, index) { | ||||
|             if (index == widgetCount - 1) { | ||||
|               return endItemView; | ||||
|             } | ||||
|  | ||||
|             return Column( | ||||
|               children: [ | ||||
|                 PostItem( | ||||
|                   item: data.items[index], | ||||
|                   backgroundColor: isWide ? Colors.transparent : null, | ||||
|                 ), | ||||
|                 const Divider(height: 1), | ||||
|               ], | ||||
|             ); | ||||
|           }, | ||||
|           ), | ||||
|       loading: | ||||
|           () => SliverFillRemaining( | ||||
|             child: const Center(child: CircularProgressIndicator()), | ||||
|           ), | ||||
|       error: | ||||
|           (e, _) => SliverFillRemaining( | ||||
|             child: ResponseErrorWidget( | ||||
|               error: e, | ||||
|               onRetry: () { | ||||
|                 ref.invalidate(postRepliesProvider(postId)); | ||||
|         ); | ||||
|       }, | ||||
|             ), | ||||
|           ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final postRepliesProvider = | ||||
|     FutureProviderFamily<_PostRepliesController, String>((ref, postId) async { | ||||
|       final client = ref.watch(apiClientProvider); | ||||
|       final controller = _PostRepliesController(client, postId); | ||||
|       await controller.fetchMore(); | ||||
|       return controller; | ||||
|     }); | ||||
|  | ||||
| class _PostRepliesController { | ||||
|   _PostRepliesController(this._dio, this.parentId); | ||||
|  | ||||
|   final Dio _dio; | ||||
|   final String parentId; | ||||
|   final List<SnPost> posts = []; | ||||
|   bool isLoading = false; | ||||
|   bool hasReachedMax = false; | ||||
|   int offset = 0; | ||||
|   final int take = 20; | ||||
|   int total = 0; | ||||
|  | ||||
|   Future<void> fetchMore() async { | ||||
|     if (isLoading || hasReachedMax) return; | ||||
|     isLoading = true; | ||||
|  | ||||
|     final response = await _dio.get( | ||||
|       '/posts/$parentId/replies', | ||||
|       queryParameters: {'offset': offset, 'take': take}, | ||||
|     ); | ||||
|  | ||||
|     final List<SnPost> fetched = | ||||
|         (response.data as List) | ||||
|             .map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||
|             .toList(); | ||||
|  | ||||
|     final headerTotal = int.tryParse(response.headers['x-total']?.first ?? ''); | ||||
|     if (headerTotal != null) total = headerTotal; | ||||
|  | ||||
|     posts.addAll(fetched); | ||||
|     offset += fetched.length; | ||||
|     if (posts.length >= total) hasReachedMax = true; | ||||
|  | ||||
|     isLoading = false; | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										180
									
								
								lib/widgets/post/post_replies.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								lib/widgets/post/post_replies.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
|  | ||||
| part of 'post_replies.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$postRepliesNotifierHash() => | ||||
|     r'49c178102ec0a4136974a0e9a8f090f511abd542'; | ||||
|  | ||||
| /// 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 _$PostRepliesNotifier | ||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPost>> { | ||||
|   late final String postId; | ||||
|  | ||||
|   FutureOr<CursorPagingData<SnPost>> build(String postId); | ||||
| } | ||||
|  | ||||
| /// See also [PostRepliesNotifier]. | ||||
| @ProviderFor(PostRepliesNotifier) | ||||
| const postRepliesNotifierProvider = PostRepliesNotifierFamily(); | ||||
|  | ||||
| /// See also [PostRepliesNotifier]. | ||||
| class PostRepliesNotifierFamily | ||||
|     extends Family<AsyncValue<CursorPagingData<SnPost>>> { | ||||
|   /// See also [PostRepliesNotifier]. | ||||
|   const PostRepliesNotifierFamily(); | ||||
|  | ||||
|   /// See also [PostRepliesNotifier]. | ||||
|   PostRepliesNotifierProvider call(String postId) { | ||||
|     return PostRepliesNotifierProvider(postId); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   PostRepliesNotifierProvider getProviderOverride( | ||||
|     covariant PostRepliesNotifierProvider provider, | ||||
|   ) { | ||||
|     return call(provider.postId); | ||||
|   } | ||||
|  | ||||
|   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||
|  | ||||
|   @override | ||||
|   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||
|  | ||||
|   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||
|  | ||||
|   @override | ||||
|   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||
|       _allTransitiveDependencies; | ||||
|  | ||||
|   @override | ||||
|   String? get name => r'postRepliesNotifierProvider'; | ||||
| } | ||||
|  | ||||
| /// See also [PostRepliesNotifier]. | ||||
| class PostRepliesNotifierProvider | ||||
|     extends | ||||
|         AutoDisposeAsyncNotifierProviderImpl< | ||||
|           PostRepliesNotifier, | ||||
|           CursorPagingData<SnPost> | ||||
|         > { | ||||
|   /// See also [PostRepliesNotifier]. | ||||
|   PostRepliesNotifierProvider(String postId) | ||||
|     : this._internal( | ||||
|         () => PostRepliesNotifier()..postId = postId, | ||||
|         from: postRepliesNotifierProvider, | ||||
|         name: r'postRepliesNotifierProvider', | ||||
|         debugGetCreateSourceHash: | ||||
|             const bool.fromEnvironment('dart.vm.product') | ||||
|                 ? null | ||||
|                 : _$postRepliesNotifierHash, | ||||
|         dependencies: PostRepliesNotifierFamily._dependencies, | ||||
|         allTransitiveDependencies: | ||||
|             PostRepliesNotifierFamily._allTransitiveDependencies, | ||||
|         postId: postId, | ||||
|       ); | ||||
|  | ||||
|   PostRepliesNotifierProvider._internal( | ||||
|     super._createNotifier, { | ||||
|     required super.name, | ||||
|     required super.dependencies, | ||||
|     required super.allTransitiveDependencies, | ||||
|     required super.debugGetCreateSourceHash, | ||||
|     required super.from, | ||||
|     required this.postId, | ||||
|   }) : super.internal(); | ||||
|  | ||||
|   final String postId; | ||||
|  | ||||
|   @override | ||||
|   FutureOr<CursorPagingData<SnPost>> runNotifierBuild( | ||||
|     covariant PostRepliesNotifier notifier, | ||||
|   ) { | ||||
|     return notifier.build(postId); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Override overrideWith(PostRepliesNotifier Function() create) { | ||||
|     return ProviderOverride( | ||||
|       origin: this, | ||||
|       override: PostRepliesNotifierProvider._internal( | ||||
|         () => create()..postId = postId, | ||||
|         from: from, | ||||
|         name: null, | ||||
|         dependencies: null, | ||||
|         allTransitiveDependencies: null, | ||||
|         debugGetCreateSourceHash: null, | ||||
|         postId: postId, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   AutoDisposeAsyncNotifierProviderElement< | ||||
|     PostRepliesNotifier, | ||||
|     CursorPagingData<SnPost> | ||||
|   > | ||||
|   createElement() { | ||||
|     return _PostRepliesNotifierProviderElement(this); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     return other is PostRepliesNotifierProvider && other.postId == postId; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode { | ||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||
|     hash = _SystemHash.combine(hash, postId.hashCode); | ||||
|  | ||||
|     return _SystemHash.finish(hash); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||
| // ignore: unused_element | ||||
| mixin PostRepliesNotifierRef | ||||
|     on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnPost>> { | ||||
|   /// The parameter `postId` of this provider. | ||||
|   String get postId; | ||||
| } | ||||
|  | ||||
| class _PostRepliesNotifierProviderElement | ||||
|     extends | ||||
|         AutoDisposeAsyncNotifierProviderElement< | ||||
|           PostRepliesNotifier, | ||||
|           CursorPagingData<SnPost> | ||||
|         > | ||||
|     with PostRepliesNotifierRef { | ||||
|   _PostRepliesNotifierProviderElement(super.provider); | ||||
|  | ||||
|   @override | ||||
|   String get postId => (origin as PostRepliesNotifierProvider).postId; | ||||
| } | ||||
|  | ||||
| // 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 | ||||
		Reference in New Issue
	
	Block a user