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