From c1fc8ea3fe84c3977f94a41f77e3dfeb005f9565 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 6 Dec 2025 00:33:06 +0800 Subject: [PATCH] :recycle: Continued to move riverpod paging utils to own pagination utils --- lib/pods/paging.dart | 50 +++- lib/screens/account/relationship.dart | 73 +++--- lib/screens/account/relationship.g.dart | 21 -- lib/screens/chat/room_detail.dart | 173 +++++++------- lib/screens/chat/room_detail.g.dart | 149 ------------ lib/screens/creators/hub.dart | 217 ++++++++--------- lib/screens/creators/hub.g.dart | 153 ------------ lib/screens/notification.dart | 256 +++++++++----------- lib/screens/notification.g.dart | 21 -- lib/screens/wallet.dart | 221 +++++++++-------- lib/screens/wallet.g.dart | 301 +----------------------- lib/widgets/post/compose_fund.dart | 271 ++++++++++----------- 12 files changed, 602 insertions(+), 1304 deletions(-) diff --git a/lib/pods/paging.dart b/lib/pods/paging.dart index 97479736..ab337600 100644 --- a/lib/pods/paging.dart +++ b/lib/pods/paging.dart @@ -27,7 +27,7 @@ mixin AsyncPaginationController on AsyncNotifier> int? totalCount; @override - int fetchedCount = 0; + int get fetchedCount => state.value?.length ?? 0; @override bool get fetchedAll => totalCount != null && fetchedCount >= totalCount!; @@ -38,14 +38,12 @@ mixin AsyncPaginationController on AsyncNotifier> @override Future refresh() async { totalCount = null; - fetchedCount = 0; state = AsyncData>([]); final newState = await AsyncValue.guard>(() async { return await fetch(); }); state = newState; - fetchedCount = newState.value?.length ?? 0; } @override @@ -60,7 +58,47 @@ mixin AsyncPaginationController on AsyncNotifier> }); state = newState; - fetchedCount = newState.value?.length ?? 0; + } +} + +mixin FamilyAsyncPaginationController + on AutoDisposeFamilyAsyncNotifier, Arg> + implements PaginationController { + @override + int? totalCount; + + @override + int get fetchedCount => state.value?.length ?? 0; + + @override + bool get fetchedAll => totalCount != null && fetchedCount >= totalCount!; + + @override + FutureOr> build(Arg arg) async => fetch(); + + @override + Future refresh() async { + totalCount = null; + state = AsyncData>([]); + + final newState = await AsyncValue.guard>(() async { + return await fetch(); + }); + state = newState; + } + + @override + Future fetchFurther() async { + if (fetchedAll) return; + + state = AsyncLoading>(); + + final newState = await AsyncValue.guard>(() async { + final elements = await fetch(); + return [...?state.valueOrNull, ...elements]; + }); + + state = newState; } } @@ -71,10 +109,8 @@ mixin AsyncPaginationFilter on AsyncPaginationController if (currentFilter == filter) return; // Reset the data totalCount = null; - fetchedCount = 0; - currentFilter = filter; - state = AsyncData>([]); + currentFilter = filter; final newState = await AsyncValue.guard>(() async { return await fetch(); diff --git a/lib/screens/account/relationship.dart b/lib/screens/account/relationship.dart index dcdaabf6..ee0f8f07 100644 --- a/lib/screens/account/relationship.dart +++ b/lib/screens/account/relationship.dart @@ -3,16 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:relative_time/relative_time.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:island/models/relationship.dart'; import 'package:island/pods/network.dart'; @@ -28,39 +29,31 @@ Future> sentFriendRequest(Ref ref) async { .toList(); } -@riverpod -class RelationshipListNotifier extends _$RelationshipListNotifier - with CursorPagingNotifierMixin { - @override - Future> build() => fetch(cursor: null); +final relationshipListNotifierProvider = AsyncNotifierProvider( + RelationshipListNotifier.new, +); +class RelationshipListNotifier extends AsyncNotifier> + with AsyncPaginationController { @override - Future> fetch({ - required String? cursor, - }) async { + Future> fetch() async { final client = ref.read(apiClientProvider); - final offset = cursor == null ? 0 : int.parse(cursor); final take = 20; final response = await client.get( '/pass/relationships', - queryParameters: {'offset': offset, 'take': take}, + queryParameters: {'offset': fetchedCount.toString(), 'take': take}, ); final List items = (response.data as List) .map((e) => SnRelationship.fromJson(e as Map)) + .cast() .toList(); - final total = int.tryParse(response.headers['x-total']?.first ?? '') ?? 0; - final hasMore = offset + items.length < total; - final nextCursor = hasMore ? (offset + items.length).toString() : null; + totalCount = int.tryParse(response.headers['x-total']?.first ?? '') ?? 0; - return CursorPagingData( - items: items, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return items; } } @@ -242,7 +235,7 @@ class RelationshipScreen extends HookConsumerWidget { await client.post( '/pass/relationships/${relationship.accountId}/friends/${isAccept ? 'accept' : 'decline'}', ); - relationshipNotifier.forceRefresh(); + relationshipNotifier.refresh(); if (!context.mounted) return; if (isAccept) { showSnackBar( @@ -270,7 +263,7 @@ class RelationshipScreen extends HookConsumerWidget { '/pass/relationships/${relationship.accountId}', data: {'status': newStatus}, ); - relationshipNotifier.forceRefresh(); + relationshipNotifier.refresh(); } final user = ref.watch(userInfoProvider); @@ -305,32 +298,20 @@ class RelationshipScreen extends HookConsumerWidget { ), const Divider(height: 1), Expanded( - child: PagingHelperView( + child: PaginationList( provider: relationshipListNotifierProvider, - futureRefreshable: relationshipListNotifierProvider.future, - notifierRefreshable: relationshipListNotifierProvider.notifier, - contentBuilder: - (data, widgetCount, endItemView) => ListView.builder( - padding: EdgeInsets.zero, - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } - - final relationship = data.items[index]; - return RelationshipListTile( - relationship: relationship, - submitting: submitting.value, - onAccept: () => handleFriendRequest(relationship, true), - onDecline: - () => handleFriendRequest(relationship, false), - currentUserId: user.value?.id, - showRelatedAccount: false, - onUpdateStatus: updateRelationship, - ); - }, - ), + notifier: relationshipListNotifierProvider.notifier, + itemBuilder: (context, index, relationship) { + return RelationshipListTile( + relationship: relationship, + submitting: submitting.value, + onAccept: () => handleFriendRequest(relationship, true), + onDecline: () => handleFriendRequest(relationship, false), + currentUserId: user.value?.id, + showRelatedAccount: false, + onUpdateStatus: updateRelationship, + ); + }, ), ), ], diff --git a/lib/screens/account/relationship.g.dart b/lib/screens/account/relationship.g.dart index c08e463b..825ca804 100644 --- a/lib/screens/account/relationship.g.dart +++ b/lib/screens/account/relationship.g.dart @@ -26,26 +26,5 @@ final sentFriendRequestProvider = // ignore: unused_element typedef SentFriendRequestRef = AutoDisposeFutureProviderRef>; -String _$relationshipListNotifierHash() => - r'fc46920256f7c48445c00652165e879890f2c9a3'; - -/// See also [RelationshipListNotifier]. -@ProviderFor(RelationshipListNotifier) -final relationshipListNotifierProvider = AutoDisposeAsyncNotifierProvider< - RelationshipListNotifier, - CursorPagingData ->.internal( - RelationshipListNotifier.new, - name: r'relationshipListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$relationshipListNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$RelationshipListNotifier = - AutoDisposeAsyncNotifier>; // 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/chat/room_detail.dart b/lib/screens/chat/room_detail.dart index 6061a756..a93308e8 100644 --- a/lib/screens/chat/room_detail.dart +++ b/lib/screens/chat/room_detail.dart @@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/chat.dart'; import 'package:island/pods/chat/chat_room.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/account/status.dart'; @@ -17,9 +18,9 @@ import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:island/screens/chat/chat_form.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:styled_widget/styled_widget.dart'; import 'package:island/pods/database.dart'; import 'package:island/screens/chat/search_messages.dart'; @@ -600,39 +601,36 @@ class ChatMemberNotifier extends StateNotifier { } } -@riverpod -class ChatMemberListNotifier extends _$ChatMemberListNotifier - with CursorPagingNotifierMixin { - @override - Future> build(String roomId) { - return fetch(); - } +final chatMemberListNotifierProvider = AsyncNotifierProvider.autoDispose + .family, String>( + ChatMemberListNotifier.new, + ); + +class ChatMemberListNotifier + extends AutoDisposeFamilyAsyncNotifier, String> + with FamilyAsyncPaginationController { + static const pageSize = 20; @override - Future> fetch({String? cursor}) async { - final offset = cursor == null ? 0 : int.parse(cursor); - final take = 20; - + Future> fetch() async { final apiClient = ref.watch(apiClientProvider); final response = await apiClient.get( - '/sphere/chat/$roomId/members', - queryParameters: {'offset': offset, 'take': take, 'withStatus': true}, + '/sphere/chat/$arg/members', + queryParameters: { + 'offset': fetchedCount.toString(), + 'take': pageSize, + 'withStatus': true, + }, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; - final members = data.map((e) => SnChatMember.fromJson(e)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final members = + response.data + .map((e) => SnChatMember.fromJson(e)) + .cast() + .toList(); - // Calculate next cursor based on total count - final nextOffset = offset + members.length; - final String? nextCursor = - nextOffset < total ? nextOffset.toString() : null; - - return CursorPagingData( - items: members, - nextCursor: nextCursor, - hasMore: members.length < total, - ); + return members; } } @@ -727,75 +725,62 @@ class _ChatMemberListSheet extends HookConsumerWidget { ), const Divider(height: 1), Expanded( - child: PagingHelperView( + child: PaginationList( provider: memberListProvider, - futureRefreshable: memberListProvider.future, - notifierRefreshable: memberListProvider.notifier, - contentBuilder: (data, widgetCount, endItemView) { - return ListView.builder( - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == data.items.length) { - return endItemView; - } - - final member = data.items[index]; - return ListTile( - contentPadding: EdgeInsets.only(left: 16, right: 12), - leading: AccountPfcGestureDetector( - uname: member.account.name, - child: ProfilePictureWidget( - fileId: member.account.profile.picture?.id, + notifier: memberListProvider.notifier, + itemBuilder: (context, idx, member) { + return ListTile( + contentPadding: EdgeInsets.only(left: 16, right: 12), + leading: AccountPfcGestureDetector( + uname: member.account.name, + child: ProfilePictureWidget( + fileId: member.account.profile.picture?.id, + ), + ), + title: Row( + spacing: 6, + children: [ + Flexible(child: Text(member.account.nick)), + if (member.status != null) + AccountStatusLabel( + status: member.status!, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - title: Row( - spacing: 6, - children: [ - Flexible(child: Text(member.account.nick)), - if (member.status != null) - AccountStatusLabel( - status: member.status!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - if (member.joinedAt == null) - const Icon(Symbols.pending_actions, size: 20), - ], - ), - subtitle: Text("@${member.account.name}"), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (isManagable) - IconButton( - icon: const Icon(Symbols.delete), - onPressed: () { - showConfirmAlert( - 'removeChatMemberHint'.tr(), - 'removeChatMember'.tr(), - ).then((confirm) async { - if (confirm != true) return; - try { - final apiClient = ref.watch( - apiClientProvider, - ); - await apiClient.delete( - '/sphere/chat/$roomId/members/${member.accountId}', - ); - // Refresh both providers - memberNotifier.reset(); - memberNotifier.loadMore(); - ref.invalidate(memberListProvider); - } catch (err) { - showErrorAlert(err); - } - }); - }, - ), - ], - ), - ); - }, + if (member.joinedAt == null) + const Icon(Symbols.pending_actions, size: 20), + ], + ), + subtitle: Text("@${member.account.name}"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isManagable) + IconButton( + icon: const Icon(Symbols.delete), + onPressed: () { + showConfirmAlert( + 'removeChatMemberHint'.tr(), + 'removeChatMember'.tr(), + ).then((confirm) async { + if (confirm != true) return; + try { + final apiClient = ref.watch(apiClientProvider); + await apiClient.delete( + '/sphere/chat/$roomId/members/${member.accountId}', + ); + // Refresh both providers + memberNotifier.reset(); + memberNotifier.loadMore(); + ref.invalidate(memberListProvider); + } catch (err) { + showErrorAlert(err); + } + }); + }, + ), + ], + ), ); }, ), diff --git a/lib/screens/chat/room_detail.g.dart b/lib/screens/chat/room_detail.g.dart index 3187515b..7f7d7406 100644 --- a/lib/screens/chat/room_detail.g.dart +++ b/lib/screens/chat/room_detail.g.dart @@ -149,154 +149,5 @@ class _TotalMessagesCountProviderElement String get roomId => (origin as TotalMessagesCountProvider).roomId; } -String _$chatMemberListNotifierHash() => - r'3ea30150278523e9f6b23f9200ea9a9fbae9c973'; - -abstract class _$ChatMemberListNotifier - extends BuildlessAutoDisposeAsyncNotifier> { - late final String roomId; - - FutureOr> build(String roomId); -} - -/// See also [ChatMemberListNotifier]. -@ProviderFor(ChatMemberListNotifier) -const chatMemberListNotifierProvider = ChatMemberListNotifierFamily(); - -/// See also [ChatMemberListNotifier]. -class ChatMemberListNotifierFamily - extends Family>> { - /// See also [ChatMemberListNotifier]. - const ChatMemberListNotifierFamily(); - - /// See also [ChatMemberListNotifier]. - ChatMemberListNotifierProvider call(String roomId) { - return ChatMemberListNotifierProvider(roomId); - } - - @override - ChatMemberListNotifierProvider getProviderOverride( - covariant ChatMemberListNotifierProvider provider, - ) { - return call(provider.roomId); - } - - 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'chatMemberListNotifierProvider'; -} - -/// See also [ChatMemberListNotifier]. -class ChatMemberListNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - ChatMemberListNotifier, - CursorPagingData - > { - /// See also [ChatMemberListNotifier]. - ChatMemberListNotifierProvider(String roomId) - : this._internal( - () => ChatMemberListNotifier()..roomId = roomId, - from: chatMemberListNotifierProvider, - name: r'chatMemberListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatMemberListNotifierHash, - dependencies: ChatMemberListNotifierFamily._dependencies, - allTransitiveDependencies: - ChatMemberListNotifierFamily._allTransitiveDependencies, - roomId: roomId, - ); - - ChatMemberListNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.roomId, - }) : super.internal(); - - final String roomId; - - @override - FutureOr> runNotifierBuild( - covariant ChatMemberListNotifier notifier, - ) { - return notifier.build(roomId); - } - - @override - Override overrideWith(ChatMemberListNotifier Function() create) { - return ProviderOverride( - origin: this, - override: ChatMemberListNotifierProvider._internal( - () => create()..roomId = roomId, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - roomId: roomId, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - ChatMemberListNotifier, - CursorPagingData - > - createElement() { - return _ChatMemberListNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is ChatMemberListNotifierProvider && other.roomId == roomId; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, roomId.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin ChatMemberListNotifierRef - on AutoDisposeAsyncNotifierProviderRef> { - /// The parameter `roomId` of this provider. - String get roomId; -} - -class _ChatMemberListNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - ChatMemberListNotifier, - CursorPagingData - > - with ChatMemberListNotifierRef { - _ChatMemberListNotifierProviderElement(super.provider); - - @override - String get roomId => (origin as ChatMemberListNotifierProvider).roomId; -} - // 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/hub.dart b/lib/screens/creators/hub.dart index 389f7e46..258c4628 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -10,6 +10,7 @@ import 'package:island/models/post.dart'; import 'package:island/models/publisher.dart'; import 'package:island/models/heatmap.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/screens/creators/publishers_form.dart'; import 'package:island/services/responsive.dart'; import 'package:island/utils/text.dart'; @@ -18,11 +19,11 @@ import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.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:island/widgets/response.dart'; import 'package:island/widgets/activity_heatmap.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 'hub.g.dart'; @@ -77,38 +78,31 @@ Future> publisherInvites(Ref ref) async { .toList(); } -@riverpod -class PublisherMemberListNotifier extends _$PublisherMemberListNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; +final publisherMemberListNotifierProvider = AsyncNotifierProvider.family + .autoDispose(PublisherMemberListNotifier.new); + +class PublisherMemberListNotifier + extends AutoDisposeFamilyAsyncNotifier, String> + with FamilyAsyncPaginationController { + static const int pageSize = 20; @override - Future> build(String uname) async { - return fetch(); - } - - @override - Future> fetch({String? cursor}) async { + Future> fetch() async { final apiClient = ref.read(apiClientProvider); - final offset = cursor != null ? int.parse(cursor) : 0; final response = await apiClient.get( - '/sphere/publishers/$uname/members', - queryParameters: {'offset': offset, 'take': _pageSize}, + '/sphere/publishers/$arg/members', + queryParameters: {'offset': fetchedCount.toString(), 'take': pageSize}, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; - final members = data.map((e) => SnPublisherMember.fromJson(e)).toList(); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); + final members = + response.data + .map((e) => SnPublisherMember.fromJson(e)) + .cast() + .toList(); - final hasMore = offset + members.length < total; - final nextCursor = hasMore ? (offset + members.length).toString() : null; - - return CursorPagingData( - items: members, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return members; } } @@ -902,100 +896,87 @@ class _PublisherMemberListSheet extends HookConsumerWidget { ), const Divider(height: 1), Expanded( - child: PagingHelperView( + child: PaginationList( provider: memberListProvider, - futureRefreshable: memberListProvider.future, - notifierRefreshable: memberListProvider.notifier, - contentBuilder: (data, widgetCount, endItemView) { - return ListView.builder( - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == data.items.length) { - return endItemView; - } - - final member = data.items[index]; - return ListTile( - contentPadding: EdgeInsets.only(left: 16, right: 12), - leading: ProfilePictureWidget( - fileId: member.account!.profile.picture?.id, - ), - title: Row( - spacing: 6, - children: [ - Flexible(child: Text(member.account!.nick)), - if (member.joinedAt == null) - const Icon(Symbols.pending_actions, size: 20), - ], - ), - subtitle: Row( - children: [ - Text( - member.role >= 100 - ? 'permissionOwner' - : member.role >= 50 - ? 'permissionModerator' - : 'permissionMember', - ).tr(), - Text('·').bold().padding(horizontal: 6), - Expanded(child: Text("@${member.account!.name}")), - ], - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if ((publisherIdentity.value?.role ?? 0) >= 50) - IconButton( - icon: const Icon(Symbols.edit), - onPressed: () { - showModalBottomSheet( - isScrollControlled: true, - context: context, - builder: - (context) => _PublisherMemberRoleSheet( - publisherUname: publisherUname, - member: member, - ), - ).then((value) { - if (value != null) { - // Refresh both providers - memberNotifier.reset(); - memberNotifier.loadMore(); - ref.invalidate(memberListProvider); - } - }); - }, - ), - if ((publisherIdentity.value?.role ?? 0) >= 50) - IconButton( - icon: const Icon(Symbols.delete), - onPressed: () { - showConfirmAlert( - 'removePublisherMemberHint'.tr(), - 'removePublisherMember'.tr(), - ).then((confirm) async { - if (confirm != true) return; - try { - final apiClient = ref.watch( - apiClientProvider, - ); - await apiClient.delete( - '/sphere/publishers/$publisherUname/members/${member.accountId}', - ); - // Refresh both providers - memberNotifier.reset(); - memberNotifier.loadMore(); - ref.invalidate(memberListProvider); - } catch (err) { - showErrorAlert(err); - } - }); - }, - ), - ], - ), - ); - }, + notifier: memberListProvider.notifier, + itemBuilder: (context, index, member) { + return ListTile( + contentPadding: EdgeInsets.only(left: 16, right: 12), + leading: ProfilePictureWidget( + fileId: member.account!.profile.picture?.id, + ), + title: Row( + spacing: 6, + children: [ + Flexible(child: Text(member.account!.nick)), + if (member.joinedAt == null) + const Icon(Symbols.pending_actions, size: 20), + ], + ), + subtitle: Row( + children: [ + Text( + member.role >= 100 + ? 'permissionOwner' + : member.role >= 50 + ? 'permissionModerator' + : 'permissionMember', + ).tr(), + Text('·').bold().padding(horizontal: 6), + Expanded(child: Text("@${member.account!.name}")), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if ((publisherIdentity.value?.role ?? 0) >= 50) + IconButton( + icon: const Icon(Symbols.edit), + onPressed: () { + showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: + (context) => _PublisherMemberRoleSheet( + publisherUname: publisherUname, + member: member, + ), + ).then((value) { + if (value != null) { + // Refresh both providers + memberNotifier.reset(); + memberNotifier.loadMore(); + ref.invalidate(memberListProvider); + } + }); + }, + ), + if ((publisherIdentity.value?.role ?? 0) >= 50) + IconButton( + icon: const Icon(Symbols.delete), + onPressed: () { + showConfirmAlert( + 'removePublisherMemberHint'.tr(), + 'removePublisherMember'.tr(), + ).then((confirm) async { + if (confirm != true) return; + try { + final apiClient = ref.watch(apiClientProvider); + await apiClient.delete( + '/sphere/publishers/$publisherUname/members/${member.accountId}', + ); + // Refresh both providers + memberNotifier.reset(); + memberNotifier.loadMore(); + ref.invalidate(memberListProvider); + } catch (err) { + showErrorAlert(err); + } + }); + }, + ), + ], + ), ); }, ), diff --git a/lib/screens/creators/hub.g.dart b/lib/screens/creators/hub.g.dart index 3540469f..77b0d13a 100644 --- a/lib/screens/creators/hub.g.dart +++ b/lib/screens/creators/hub.g.dart @@ -534,158 +534,5 @@ final publisherInvitesProvider = // ignore: unused_element typedef PublisherInvitesRef = AutoDisposeFutureProviderRef>; -String _$publisherMemberListNotifierHash() => - r'b4afd5d591a6f3d29f1b45fb1b6d17cb34f3f11b'; - -abstract class _$PublisherMemberListNotifier - extends - BuildlessAutoDisposeAsyncNotifier> { - late final String uname; - - FutureOr> build(String uname); -} - -/// See also [PublisherMemberListNotifier]. -@ProviderFor(PublisherMemberListNotifier) -const publisherMemberListNotifierProvider = PublisherMemberListNotifierFamily(); - -/// See also [PublisherMemberListNotifier]. -class PublisherMemberListNotifierFamily - extends Family>> { - /// See also [PublisherMemberListNotifier]. - const PublisherMemberListNotifierFamily(); - - /// See also [PublisherMemberListNotifier]. - PublisherMemberListNotifierProvider call(String uname) { - return PublisherMemberListNotifierProvider(uname); - } - - @override - PublisherMemberListNotifierProvider getProviderOverride( - covariant PublisherMemberListNotifierProvider provider, - ) { - return call(provider.uname); - } - - 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'publisherMemberListNotifierProvider'; -} - -/// See also [PublisherMemberListNotifier]. -class PublisherMemberListNotifierProvider - extends - AutoDisposeAsyncNotifierProviderImpl< - PublisherMemberListNotifier, - CursorPagingData - > { - /// See also [PublisherMemberListNotifier]. - PublisherMemberListNotifierProvider(String uname) - : this._internal( - () => PublisherMemberListNotifier()..uname = uname, - from: publisherMemberListNotifierProvider, - name: r'publisherMemberListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$publisherMemberListNotifierHash, - dependencies: PublisherMemberListNotifierFamily._dependencies, - allTransitiveDependencies: - PublisherMemberListNotifierFamily._allTransitiveDependencies, - uname: uname, - ); - - PublisherMemberListNotifierProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.uname, - }) : super.internal(); - - final String uname; - - @override - FutureOr> runNotifierBuild( - covariant PublisherMemberListNotifier notifier, - ) { - return notifier.build(uname); - } - - @override - Override overrideWith(PublisherMemberListNotifier Function() create) { - return ProviderOverride( - origin: this, - override: PublisherMemberListNotifierProvider._internal( - () => create()..uname = uname, - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - uname: uname, - ), - ); - } - - @override - AutoDisposeAsyncNotifierProviderElement< - PublisherMemberListNotifier, - CursorPagingData - > - createElement() { - return _PublisherMemberListNotifierProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is PublisherMemberListNotifierProvider && other.uname == uname; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, uname.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin PublisherMemberListNotifierRef - on - AutoDisposeAsyncNotifierProviderRef< - CursorPagingData - > { - /// The parameter `uname` of this provider. - String get uname; -} - -class _PublisherMemberListNotifierProviderElement - extends - AutoDisposeAsyncNotifierProviderElement< - PublisherMemberListNotifier, - CursorPagingData - > - with PublisherMemberListNotifierRef { - _PublisherMemberListNotifierProviderElement(super.provider); - - @override - String get uname => (origin as PublisherMemberListNotifierProvider).uname; -} - // 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/notification.dart b/lib/screens/notification.dart index 624f7329..3fa5f5e5 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -7,16 +7,17 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/account.dart'; import 'package:island/pods/network.dart'; +import 'package:island/pods/paging.dart'; import 'package:island/pods/websocket.dart'; import 'package:island/route.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/content/sheet.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:relative_time/relative_time.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:url_launcher/url_launcher_string.dart'; @@ -81,45 +82,37 @@ class NotificationUnreadCountNotifier } } -@riverpod -class NotificationListNotifier extends _$NotificationListNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 5; +final notificationListNotifierProvider = AsyncNotifierProvider( + NotificationListNotifier.new, +); + +class NotificationListNotifier extends AsyncNotifier> + with AsyncPaginationController { + static const int pageSize = 5; @override - Future> build() => 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 = {'offset': offset, 'take': _pageSize}; + final queryParams = {'offset': fetchedCount.toString(), 'take': pageSize}; final response = await client.get( '/ring/notifications', queryParameters: queryParams, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); - final List data = response.data; + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); final notifications = - data.map((json) => SnNotification.fromJson(json)).toList(); + response.data + .map((json) => SnNotification.fromJson(json)) + .cast() + .toList(); - final hasMore = offset + notifications.length < total; - final nextCursor = - hasMore ? (offset + notifications.length).toString() : null; final unreadCount = notifications.where((n) => n.viewedAt == null).length; ref .read(notificationUnreadCountNotifierProvider.notifier) .decrement(unreadCount); - return CursorPagingData( - items: notifications, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return notifications; } } @@ -174,129 +167,108 @@ class NotificationSheet extends HookConsumerWidget { icon: const Icon(Symbols.mark_as_unread), ), ], - child: PagingHelperView( + child: PaginationList( provider: notificationListNotifierProvider, - futureRefreshable: notificationListNotifierProvider.future, - notifierRefreshable: notificationListNotifierProvider.notifier, - contentBuilder: - (data, widgetCount, endItemView) => ListView.builder( - padding: EdgeInsets.zero, - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } + notifier: notificationListNotifierProvider.notifier, + itemBuilder: (context, index, notification) { + final pfp = notification.meta['pfp'] as String?; + final images = notification.meta['images'] as List?; + final imageIds = images?.cast() ?? []; - final notification = data.items[index]; - final pfp = notification.meta['pfp'] as String?; - final images = notification.meta['images'] as List?; - final imageIds = images?.cast() ?? []; - - return ListTile( - isThreeLine: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - leading: - pfp != null - ? ProfilePictureWidget(fileId: pfp, radius: 20) - : CircleAvatar( - backgroundColor: - Theme.of(context).colorScheme.primaryContainer, - child: Icon( - _getNotificationIcon(notification.topic), - color: - Theme.of( - context, - ).colorScheme.onPrimaryContainer, - ), - ), - title: Text(notification.title), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (notification.subtitle.isNotEmpty) - Text(notification.subtitle).bold(), - Row( - spacing: 6, - children: [ - Text( - DateFormat().format( - notification.createdAt.toLocal(), - ), - ).fontSize(11), - Text('·').fontSize(11).bold(), - Text( - RelativeTime( - context, - ).format(notification.createdAt.toLocal()), - ).fontSize(11), - ], - ).opacity(0.75).padding(bottom: 4), - MarkdownTextContent( - content: notification.content, - textStyle: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurface.withOpacity(0.8), - ), + return ListTile( + isThreeLine: true, + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + leading: + pfp != null + ? ProfilePictureWidget(fileId: pfp, radius: 20) + : CircleAvatar( + backgroundColor: + Theme.of(context).colorScheme.primaryContainer, + child: Icon( + _getNotificationIcon(notification.topic), + color: Theme.of(context).colorScheme.onPrimaryContainer, ), - if (imageIds.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: - imageIds.map((imageId) { - return SizedBox( - width: 80, - height: 80, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: CloudImageWidget( - fileId: imageId, - aspectRatio: 1, - fit: BoxFit.cover, - ), - ), - ); - }).toList(), - ), - ), - ], + ), + title: Text(notification.title), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (notification.subtitle.isNotEmpty) + Text(notification.subtitle).bold(), + Row( + spacing: 6, + children: [ + Text( + DateFormat().format(notification.createdAt.toLocal()), + ).fontSize(11), + Text('·').fontSize(11).bold(), + Text( + RelativeTime( + context, + ).format(notification.createdAt.toLocal()), + ).fontSize(11), + ], + ).opacity(0.75).padding(bottom: 4), + MarkdownTextContent( + content: notification.content, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurface.withOpacity(0.8), ), - trailing: - notification.viewedAt != null - ? null - : Container( - width: 12, - height: 12, - decoration: const BoxDecoration( - color: Colors.blue, - shape: BoxShape.circle, - ), - ), - onTap: () { - if (notification.meta['action_uri'] != null) { - var uri = notification.meta['action_uri'] as String; - if (uri.startsWith('/')) { - // In-app routes - rootNavigatorKey.currentContext?.push( - notification.meta['action_uri'], - ); - } else { - // External URLs - launchUrlString(uri); - } - } - }, - ); - }, + ), + if (imageIds.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: + imageIds.map((imageId) { + return SizedBox( + width: 80, + height: 80, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CloudImageWidget( + fileId: imageId, + aspectRatio: 1, + fit: BoxFit.cover, + ), + ), + ); + }).toList(), + ), + ), + ], ), + trailing: + notification.viewedAt != null + ? null + : Container( + width: 12, + height: 12, + decoration: const BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + ), + onTap: () { + if (notification.meta['action_uri'] != null) { + var uri = notification.meta['action_uri'] as String; + if (uri.startsWith('/')) { + // In-app routes + rootNavigatorKey.currentContext?.push( + notification.meta['action_uri'], + ); + } else { + // External URLs + launchUrlString(uri); + } + } + }, + ); + }, ), ); } diff --git a/lib/screens/notification.g.dart b/lib/screens/notification.g.dart index a0ea5b6b..2b01609d 100644 --- a/lib/screens/notification.g.dart +++ b/lib/screens/notification.g.dart @@ -27,26 +27,5 @@ final notificationUnreadCountNotifierProvider = ); typedef _$NotificationUnreadCountNotifier = AutoDisposeAsyncNotifier; -String _$notificationListNotifierHash() => - r'260046e11f45b0d67ab25bcbdc8604890d71ccc7'; - -/// See also [NotificationListNotifier]. -@ProviderFor(NotificationListNotifier) -final notificationListNotifierProvider = AutoDisposeAsyncNotifierProvider< - NotificationListNotifier, - CursorPagingData ->.internal( - NotificationListNotifier.new, - name: r'notificationListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$notificationListNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$NotificationListNotifier = - AutoDisposeAsyncNotifier>; // 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/wallet.dart b/lib/screens/wallet.dart index efd145b7..d10a63d5 100644 --- a/lib/screens/wallet.dart +++ b/lib/screens/wallet.dart @@ -19,7 +19,8 @@ import 'package:island/widgets/payment/payment_overlay.dart'; import 'package:island/widgets/response.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/pods/paging.dart'; +import 'package:island/widgets/paging/pagination_list.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; @@ -990,70 +991,87 @@ class _CreateTransferSheetState extends State { } } -@riverpod -class TransactionListNotifier extends _$TransactionListNotifier - with CursorPagingNotifierMixin { - static const int _pageSize = 20; +final transactionListNotifierProvider = AsyncNotifierProvider( + TransactionListNotifier.new, +); + +class TransactionListNotifier extends AsyncNotifier> + with AsyncPaginationController { + static const int pageSize = 20; @override - Future> build() => 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 offset = fetchedCount; - final queryParams = {'offset': offset, 'take': _pageSize}; + final queryParams = {'offset': offset, 'take': pageSize}; final response = await client.get( '/pass/wallets/transactions', queryParameters: queryParams, ); - final total = int.parse(response.headers.value('X-Total') ?? '0'); + totalCount = int.parse(response.headers.value('X-Total') ?? '0'); final List data = response.data; final transactions = data.map((json) => SnTransaction.fromJson(json)).toList(); - final hasMore = offset + transactions.length < total; - final nextCursor = - hasMore ? (offset + transactions.length).toString() : null; - - return CursorPagingData( - items: transactions, - hasMore: hasMore, - nextCursor: nextCursor, - ); + return transactions; } } -@riverpod -Future> walletFunds( - Ref ref, { - int offset = 0, - int take = 20, -}) async { - final client = ref.watch(apiClientProvider); - final resp = await client.get( - '/pass/wallets/funds?offset=$offset&take=$take', - ); - return (resp.data as List).map((e) => SnWalletFund.fromJson(e)).toList(); +final walletFundsNotifierProvider = AsyncNotifierProvider( + WalletFundsNotifier.new, +); + +class WalletFundsNotifier extends AsyncNotifier> + with AsyncPaginationController { + static const int pageSize = 20; + + @override + Future> fetch() async { + final client = ref.read(apiClientProvider); + final offset = fetchedCount; + + final response = await client.get( + '/pass/wallets/funds?offset=$offset&take=$pageSize', + ); + // Assuming total count header is present or we just check if list is empty + final list = + (response.data as List).map((e) => SnWalletFund.fromJson(e)).toList(); + if (list.length < pageSize) { + totalCount = fetchedCount + list.length; + } + return list; + } } -@riverpod -Future> walletFundRecipients( - Ref ref, { - int offset = 0, - int take = 20, -}) async { - final client = ref.watch(apiClientProvider); - final resp = await client.get( - '/pass/wallets/funds/recipients?offset=$offset&take=$take', - ); - return (resp.data as List) - .map((e) => SnWalletFundRecipient.fromJson(e)) - .toList(); +final walletFundRecipientsNotifierProvider = AsyncNotifierProvider( + WalletFundRecipientsNotifier.new, +); + +class WalletFundRecipientsNotifier + extends AsyncNotifier> + with AsyncPaginationController { + static const int _pageSize = 20; + + @override + Future> fetch() async { + final client = ref.read(apiClientProvider); + final offset = fetchedCount; + + final response = await client.get( + '/pass/wallets/funds/recipients?offset=$offset&take=$_pageSize', + ); + final list = + (response.data as List) + .map((e) => SnWalletFundRecipient.fromJson(e)) + .toList(); + + if (list.length < _pageSize) { + totalCount = fetchedCount + list.length; + } + return list; + } } @riverpod @@ -1408,71 +1426,50 @@ class WalletScreen extends HookConsumerWidget { controller: tabController, children: [ // Transactions Tab - CustomScrollView( - slivers: [ - PagingHelperSliverView( - provider: transactionListNotifierProvider, - futureRefreshable: transactionListNotifierProvider.future, - notifierRefreshable: - transactionListNotifierProvider.notifier, - contentBuilder: - ( - data, - widgetCount, - endItemView, - ) => SliverList.builder( - itemCount: widgetCount, - itemBuilder: (context, index) { - if (index == widgetCount - 1) { - return endItemView; - } + PaginationList( + padding: EdgeInsets.zero, + provider: transactionListNotifierProvider, + notifier: transactionListNotifierProvider.notifier, + itemBuilder: (context, index, transaction) { + final isIncome = + transaction.payeeWalletId == wallet.value?.id; - final transaction = data.items[index]; - final isIncome = - transaction.payeeWalletId == wallet.value?.id; - - return InkWell( - onTap: () { - showModalBottomSheet( - context: context, - useRootNavigator: true, - isScrollControlled: true, - builder: - (context) => TransactionDetailSheet( - transaction: transaction, - ), - ); - }, - child: ListTile( - key: ValueKey(transaction.id), - leading: Icon( - isIncome - ? Symbols.payment_arrow_down - : Symbols.paid, - ), - title: Text( - transaction.remarks ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - DateFormat.yMd().add_Hm().format( - transaction.createdAt, - ), - ), - trailing: Text( - '${isIncome ? '+' : '-'}${transaction.amount.toStringAsFixed(2)} ${transaction.currency}', - style: TextStyle( - color: - isIncome ? Colors.green : Colors.red, - ), - ), - ), - ); - }, + return InkWell( + onTap: () { + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: + (context) => TransactionDetailSheet( + transaction: transaction, + ), + ); + }, + child: ListTile( + key: ValueKey(transaction.id), + leading: Icon( + isIncome ? Symbols.payment_arrow_down : Symbols.paid, + ), + title: Text( + transaction.remarks ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + DateFormat.yMd().add_Hm().format( + transaction.createdAt, ), - ), - ], + ), + trailing: Text( + '${isIncome ? '+' : '-'}${transaction.amount.toStringAsFixed(2)} ${transaction.currency}', + style: TextStyle( + color: isIncome ? Colors.green : Colors.red, + ), + ), + ), + ); + }, ), // My Funds Tab @@ -1522,7 +1519,7 @@ class WalletScreen extends HookConsumerWidget { } Widget _buildFundsList(BuildContext context, WidgetRef ref) { - final funds = ref.watch(walletFundsProvider()); + final funds = ref.watch(walletFundsNotifierProvider); return funds.when( data: (fundList) { @@ -1784,7 +1781,7 @@ class WalletScreen extends HookConsumerWidget { if (paidOrder != null) { // Wait for server to handle order await Future.delayed(const Duration(seconds: 1)); - ref.invalidate(walletFundsProvider); + ref.invalidate(walletFundsNotifierProvider); ref.invalidate(walletCurrentProvider); if (context.mounted) { showSnackBar('fundCreatedSuccessfully'.tr()); diff --git a/lib/screens/wallet.g.dart b/lib/screens/wallet.g.dart index ce0eedc7..40360d50 100644 --- a/lib/screens/wallet.g.dart +++ b/lib/screens/wallet.g.dart @@ -40,7 +40,7 @@ final walletStatsProvider = AutoDisposeFutureProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef WalletStatsRef = AutoDisposeFutureProviderRef; -String _$walletFundsHash() => r'f60718c01ca5b7618a02682a0417669f750644a3'; +String _$walletFundHash() => r'459efdee5e2775eedaa4312e0d317c218fa7e1fa'; /// Copied from Dart SDK class _SystemHash { @@ -63,284 +63,6 @@ class _SystemHash { } } -/// See also [walletFunds]. -@ProviderFor(walletFunds) -const walletFundsProvider = WalletFundsFamily(); - -/// See also [walletFunds]. -class WalletFundsFamily extends Family>> { - /// See also [walletFunds]. - const WalletFundsFamily(); - - /// See also [walletFunds]. - WalletFundsProvider call({int offset = 0, int take = 20}) { - return WalletFundsProvider(offset: offset, take: take); - } - - @override - WalletFundsProvider getProviderOverride( - covariant WalletFundsProvider provider, - ) { - return call(offset: provider.offset, take: provider.take); - } - - 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'walletFundsProvider'; -} - -/// See also [walletFunds]. -class WalletFundsProvider - extends AutoDisposeFutureProvider> { - /// See also [walletFunds]. - WalletFundsProvider({int offset = 0, int take = 20}) - : this._internal( - (ref) => walletFunds(ref as WalletFundsRef, offset: offset, take: take), - from: walletFundsProvider, - name: r'walletFundsProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$walletFundsHash, - dependencies: WalletFundsFamily._dependencies, - allTransitiveDependencies: WalletFundsFamily._allTransitiveDependencies, - offset: offset, - take: take, - ); - - WalletFundsProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.offset, - required this.take, - }) : super.internal(); - - final int offset; - final int take; - - @override - Override overrideWith( - FutureOr> Function(WalletFundsRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: WalletFundsProvider._internal( - (ref) => create(ref as WalletFundsRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - offset: offset, - take: take, - ), - ); - } - - @override - AutoDisposeFutureProviderElement> createElement() { - return _WalletFundsProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is WalletFundsProvider && - other.offset == offset && - other.take == take; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, offset.hashCode); - hash = _SystemHash.combine(hash, take.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin WalletFundsRef on AutoDisposeFutureProviderRef> { - /// The parameter `offset` of this provider. - int get offset; - - /// The parameter `take` of this provider. - int get take; -} - -class _WalletFundsProviderElement - extends AutoDisposeFutureProviderElement> - with WalletFundsRef { - _WalletFundsProviderElement(super.provider); - - @override - int get offset => (origin as WalletFundsProvider).offset; - @override - int get take => (origin as WalletFundsProvider).take; -} - -String _$walletFundRecipientsHash() => - r'3a5e32b2d20700edd5944885693aff127b58adb1'; - -/// See also [walletFundRecipients]. -@ProviderFor(walletFundRecipients) -const walletFundRecipientsProvider = WalletFundRecipientsFamily(); - -/// See also [walletFundRecipients]. -class WalletFundRecipientsFamily - extends Family>> { - /// See also [walletFundRecipients]. - const WalletFundRecipientsFamily(); - - /// See also [walletFundRecipients]. - WalletFundRecipientsProvider call({int offset = 0, int take = 20}) { - return WalletFundRecipientsProvider(offset: offset, take: take); - } - - @override - WalletFundRecipientsProvider getProviderOverride( - covariant WalletFundRecipientsProvider provider, - ) { - return call(offset: provider.offset, take: provider.take); - } - - 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'walletFundRecipientsProvider'; -} - -/// See also [walletFundRecipients]. -class WalletFundRecipientsProvider - extends AutoDisposeFutureProvider> { - /// See also [walletFundRecipients]. - WalletFundRecipientsProvider({int offset = 0, int take = 20}) - : this._internal( - (ref) => walletFundRecipients( - ref as WalletFundRecipientsRef, - offset: offset, - take: take, - ), - from: walletFundRecipientsProvider, - name: r'walletFundRecipientsProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$walletFundRecipientsHash, - dependencies: WalletFundRecipientsFamily._dependencies, - allTransitiveDependencies: - WalletFundRecipientsFamily._allTransitiveDependencies, - offset: offset, - take: take, - ); - - WalletFundRecipientsProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.offset, - required this.take, - }) : super.internal(); - - final int offset; - final int take; - - @override - Override overrideWith( - FutureOr> Function( - WalletFundRecipientsRef provider, - ) - create, - ) { - return ProviderOverride( - origin: this, - override: WalletFundRecipientsProvider._internal( - (ref) => create(ref as WalletFundRecipientsRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - offset: offset, - take: take, - ), - ); - } - - @override - AutoDisposeFutureProviderElement> - createElement() { - return _WalletFundRecipientsProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is WalletFundRecipientsProvider && - other.offset == offset && - other.take == take; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, offset.hashCode); - hash = _SystemHash.combine(hash, take.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin WalletFundRecipientsRef - on AutoDisposeFutureProviderRef> { - /// The parameter `offset` of this provider. - int get offset; - - /// The parameter `take` of this provider. - int get take; -} - -class _WalletFundRecipientsProviderElement - extends AutoDisposeFutureProviderElement> - with WalletFundRecipientsRef { - _WalletFundRecipientsProviderElement(super.provider); - - @override - int get offset => (origin as WalletFundRecipientsProvider).offset; - @override - int get take => (origin as WalletFundRecipientsProvider).take; -} - -String _$walletFundHash() => r'459efdee5e2775eedaa4312e0d317c218fa7e1fa'; - /// See also [walletFund]. @ProviderFor(walletFund) const walletFundProvider = WalletFundFamily(); @@ -459,26 +181,5 @@ class _WalletFundProviderElement String get fundId => (origin as WalletFundProvider).fundId; } -String _$transactionListNotifierHash() => - r'74d3c15f45a6e55b36150ab38e98475a508fc932'; - -/// See also [TransactionListNotifier]. -@ProviderFor(TransactionListNotifier) -final transactionListNotifierProvider = AutoDisposeAsyncNotifierProvider< - TransactionListNotifier, - CursorPagingData ->.internal( - TransactionListNotifier.new, - name: r'transactionListNotifierProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$transactionListNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$TransactionListNotifier = - AutoDisposeAsyncNotifier>; // 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_fund.dart b/lib/widgets/post/compose_fund.dart index 45d1e5c7..06ecc87e 100644 --- a/lib/widgets/post/compose_fund.dart +++ b/lib/widgets/post/compose_fund.dart @@ -22,6 +22,8 @@ class ComposeFundSheet extends HookConsumerWidget { final isPushing = useState(false); final errorText = useState(null); + final fundsData = ref.watch(walletFundsNotifierProvider); + return SheetScaffold( heightFactor: 0.6, titleText: 'fund'.tr(), @@ -41,161 +43,146 @@ class ComposeFundSheet extends HookConsumerWidget { child: TabBarView( children: [ // Link/Select existing fund list - ref - .watch(walletFundsProvider()) - .when( - data: - (funds) => - funds.isEmpty - ? Center( - child: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Symbols.money_bag, - size: 48, - color: - Theme.of( - context, - ).colorScheme.outline, - ), - const Gap(16), - Text( - 'noFundsCreated'.tr(), - style: - Theme.of( - context, - ).textTheme.titleMedium, - ), - ], + fundsData.when( + data: + (funds) => + funds.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Symbols.money_bag, + size: 48, + color: + Theme.of( + context, + ).colorScheme.outline, ), - ) - : ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: funds.length, - itemBuilder: (context, index) { - final fund = funds[index]; + const Gap(16), + Text( + 'noFundsCreated'.tr(), + style: + Theme.of( + context, + ).textTheme.titleMedium, + ), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: funds.length, + itemBuilder: (context, index) { + final fund = funds[index]; - return Card( - margin: const EdgeInsets.only( - bottom: 8, - ), - child: InkWell( - onTap: - () => Navigator.of( - context, - ).pop(fund), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: InkWell( + onTap: + () => + Navigator.of(context).pop(fund), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( children: [ - Row( - children: [ - Icon( - Symbols.money_bag, + Icon( + Symbols.money_bag, + color: + Theme.of( + context, + ).colorScheme.primary, + fill: 1, + ), + const Gap(8), + Expanded( + child: Text( + '${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}', + style: TextStyle( + fontSize: 18, + fontWeight: + FontWeight.bold, color: Theme.of(context) .colorScheme .primary, - fill: 1, ), - const Gap(8), - Expanded( - child: Text( - '${fund.totalAmount.toStringAsFixed(2)} ${fund.currency}', - style: TextStyle( - fontSize: 18, - fontWeight: - FontWeight.bold, - color: - Theme.of( - context, - ) - .colorScheme - .primary, - ), - ), - ), - Container( - padding: - const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: - _getFundStatusColor( - context, - fund.status, - ).withOpacity( - 0.1, - ), - borderRadius: - BorderRadius.circular( - 12, - ), - ), - child: Text( - _getFundStatusText( - fund.status, - ), - style: TextStyle( - color: - _getFundStatusColor( - context, - fund.status, - ), - fontSize: 12, - fontWeight: - FontWeight.w600, - ), - ), - ), - ], - ), - if (fund.message != null && - fund - .message! - .isNotEmpty) ...[ - const Gap(8), - Text( - fund.message!, - style: - Theme.of(context) - .textTheme - .bodyMedium, ), - ], - const Gap(8), - Text( - '${'recipients'.tr()}: ${fund.recipients.where((r) => r.isReceived).length}/${fund.recipients.length}', - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith( + ), + Container( + padding: + const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( color: - Theme.of(context) - .colorScheme - .onSurfaceVariant, + _getFundStatusColor( + context, + fund.status, + ).withOpacity(0.1), + borderRadius: + BorderRadius.circular( + 12, + ), + ), + child: Text( + _getFundStatusText( + fund.status, + ), + style: TextStyle( + color: + _getFundStatusColor( + context, + fund.status, + ), + fontSize: 12, + fontWeight: + FontWeight.w600, + ), ), ), ], ), - ), + if (fund.message != null && + fund.message!.isNotEmpty) ...[ + const Gap(8), + Text( + fund.message!, + style: + Theme.of( + context, + ).textTheme.bodyMedium, + ), + ], + const Gap(8), + Text( + '${'recipients'.tr()}: ${fund.recipients.where((r) => r.isReceived).length}/${fund.recipients.length}', + style: Theme.of( + context, + ).textTheme.bodySmall?.copyWith( + color: + Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ], ), - ); - }, - ), - loading: - () => const Center( - child: CircularProgressIndicator(), - ), - error: - (error, stack) => - Center(child: Text('Error: $error')), - ), + ), + ), + ); + }, + ), + loading: + () => const Center(child: CircularProgressIndicator()), + error: + (error, stack) => Center(child: Text('Error: $error')), + ), // Create new fund and return it SingleChildScrollView( @@ -314,7 +301,9 @@ class ComposeFundSheet extends HookConsumerWidget { await Future.delayed( const Duration(seconds: 1), ); - ref.invalidate(walletFundsProvider); + ref.invalidate( + walletFundsNotifierProvider, + ); // Return the created fund final updatedResp = await client.get(