From bc415b56e8572d12af2f268e0be9a7ca34369b3b Mon Sep 17 00:00:00 2001 From: poka Date: Sun, 7 Jan 2024 19:37:21 +0100 Subject: [PATCH] feat: use classic GraphQL pagination system for wallet history --- lib/models/queries_indexer.dart | 29 +++++++++- lib/models/wallet_data.dart | 2 +- lib/providers/duniter_indexer.dart | 73 ++++++++++++-------------- lib/providers/v2s_datapod.dart | 34 +++++++++--- lib/screens/activity.dart | 10 ++-- lib/widgets/datapod_avatar.dart | 7 ++- lib/widgets/history_query.dart | 48 ++++++++--------- lib/widgets/history_view.dart | 11 ++-- lib/widgets/search_identity_query.dart | 2 +- 9 files changed, 127 insertions(+), 89 deletions(-) diff --git a/lib/models/queries_indexer.dart b/lib/models/queries_indexer.dart index f7592f4..9e8b802 100644 --- a/lib/models/queries_indexer.dart +++ b/lib/models/queries_indexer.dart @@ -18,7 +18,7 @@ query ($name: String!) { } '''; -const String getHistoryByAddressQ = r''' +const String getHistoryByAddressRelayQ = r''' query ($address: String!, $number: Int!, $cursor: String) { transaction_connection(where: {_or: [ @@ -56,6 +56,33 @@ query ($address: String!, $number: Int!, $cursor: String) { } '''; +const String getHistoryByAddressQ = r''' +query ($address: String!, $number: Int!, $offset: Int!) { + transaction_aggregate(where: {_or: [{issuer_pubkey: {_eq: $address}}, {receiver_pubkey: {_eq: $address}}]}) { + aggregate { + count + } + } + transaction(where: {_or: [{issuer_pubkey: {_eq: $address}}, {receiver_pubkey: {_eq: $address}}]}, order_by: {created_at: desc}, limit: $number, offset: $offset) { + amount + comment + created_at + issuer { + pubkey + identity { + name + } + } + receiver { + pubkey + identity { + name + } + } + } +} +'''; + const String getCertsReceived = r''' query ($address: String!) { certification(where: {receiver: {pubkey: {_eq: $address}}}) { diff --git a/lib/models/wallet_data.dart b/lib/models/wallet_data.dart index 55aff1b..725e773 100644 --- a/lib/models/wallet_data.dart +++ b/lib/models/wallet_data.dart @@ -112,7 +112,7 @@ class WalletData extends HiveObject { final datapod = Provider.of(homeContext, listen: false); final avatarUuid = const Uuid().v4(); - await datapod.getRemoteAvatar(address, saveOnDisk: true, uuid: avatarUuid); + await datapod.getRemoteAvatar(address, uuid: avatarUuid); final avatarPath = '${avatarsDirectory.path}/$address-$avatarUuid'; if (!await File(avatarPath).exists()) return; diff --git a/lib/providers/duniter_indexer.dart b/lib/providers/duniter_indexer.dart index aaf8cd1..8076d26 100644 --- a/lib/providers/duniter_indexer.dart +++ b/lib/providers/duniter_indexer.dart @@ -11,11 +11,10 @@ import 'package:graphql_flutter/graphql_flutter.dart'; class DuniterIndexer with ChangeNotifier { Map walletNameIndexer = {}; - String? fetchMoreCursor; - Map? pageInfo; List? transBC; List listIndexerEndpoints = []; bool isLoadingIndexer = false; + bool hasNextPage = false; void reload() { notifyListeners(); @@ -128,14 +127,13 @@ class DuniterIndexer with ChangeNotifier { return indexerEndpoint; } - List parseHistory(blockchainTX, pubkey) { + List parseHistory(List blockchainTX, String address) { List transBC = []; int i = 0; - for (final trans in blockchainTX) { - final transaction = trans['node']; + for (final transaction in blockchainTX) { final direction = - transaction['issuer_pubkey'] != pubkey ? 'RECEIVED' : 'SENT'; + transaction['issuer']['pubkey'] != address ? 'RECEIVED' : 'SENT'; transBC.add(i); transBC[i] = []; @@ -143,10 +141,10 @@ class DuniterIndexer with ChangeNotifier { final amountBrut = transaction['amount']; final amount = removeDecimalZero(amountBrut / 100); if (direction == "RECEIVED") { - transBC[i].add(transaction['issuer_pubkey']); + transBC[i].add(transaction['issuer']['pubkey']); transBC[i].add(transaction['issuer']['identity']?['name'] ?? ''); } else if (direction == "SENT") { - transBC[i].add(transaction['receiver_pubkey']); + transBC[i].add(transaction['receiver']['pubkey']); transBC[i].add(transaction['receiver']['identity']?['name'] ?? ''); } transBC[i].add(amount); @@ -157,38 +155,35 @@ class DuniterIndexer with ChangeNotifier { return transBC; } - FetchMoreOptions? mergeQueryResult(result, opts, pubkey, nRepositories) { - final List? blockchainTX = - (result.data['transaction_connection']['edges'] as List?); + FetchMoreOptions? mergeQueryResult( + {required List transactions, + required FetchMoreOptions? opts, + required String address, + required int nRepositories, + required int offset}) { + // pageInfo = result.data!['transaction_connection']['pageInfo']; + // fetchMoreCursor = pageInfo!['endCursor']; + // final hasNextPage = pageInfo!['hasNextPage']; + // final hasPreviousPage = pageInfo!['hasPreviousPage']; + // log.d('endCursor: $fetchMoreCursor $hasNextPage $hasPreviousPage'); - pageInfo = result.data['transaction_connection']['pageInfo']; - fetchMoreCursor = pageInfo!['endCursor']; - final hasNextPage = pageInfo!['hasNextPage']; - final hasPreviousPage = pageInfo!['hasPreviousPage']; - log.d('endCursor: $fetchMoreCursor $hasNextPage $hasPreviousPage'); + // if (fetchMoreCursor != null) { + opts = FetchMoreOptions( + variables: {'offset': offset, 'number': nRepositories}, + updateQuery: (previousResultData, fetchMoreResultData) { + final List repos = [ + ...previousResultData!['transaction'] as List, + ...fetchMoreResultData!['transaction'] as List + ]; - if (fetchMoreCursor != null) { - opts = FetchMoreOptions( - variables: {'cursor': fetchMoreCursor, 'number': nRepositories}, - updateQuery: (previousResultData, fetchMoreResultData) { - final List repos = [ - ...previousResultData!['transaction_connection']['edges'] - as List, - ...fetchMoreResultData!['transaction_connection']['edges'] - as List - ]; - - fetchMoreResultData['transaction_connection']['edges'] = repos; - return fetchMoreResultData; - }, - ); - } - - if (fetchMoreCursor != null) { - transBC = parseHistory(blockchainTX, pubkey); - } else { - log.d("Activity start of $pubkey"); - } + fetchMoreResultData['transaction'] = repos; + return fetchMoreResultData; + }, + ); + transBC = parseHistory(transactions, address); + // } else { + // log.d("Activity start of $address"); + // } return opts; } @@ -232,6 +227,8 @@ class DuniterIndexer with ChangeNotifier { final options = QueryOptions(document: gql(query), variables: variables); + // 5GMyvKsTNk9wDBy9jwKaX6mhSzmFFtpdK9KNnmrLoSTSuJHv + return await client.query(options); } diff --git a/lib/providers/v2s_datapod.dart b/lib/providers/v2s_datapod.dart index ec85764..7d17621 100644 --- a/lib/providers/v2s_datapod.dart +++ b/lib/providers/v2s_datapod.dart @@ -139,7 +139,7 @@ class V2sDatapodProvider with ChangeNotifier { } Future getRemoteAvatar(String address, - {double size = 20, bool saveOnDisk = false, String? uuid}) async { + {double size = 20, String? uuid}) async { final variables = { 'address': address, }; @@ -157,12 +157,8 @@ class V2sDatapodProvider with ChangeNotifier { final sanitizedAvatar64 = avatar64.replaceAll('\n', '').replaceAll('\r', '').replaceAll(' ', ''); - if (saveOnDisk) { - log.d('We save avatar for $address'); - await saveAvatar(address, sanitizedAvatar64, uuid); - } else { - await cacheAvatar(address, sanitizedAvatar64); - } + log.d('We save avatar for $address'); + await saveAvatar(address, sanitizedAvatar64, uuid); return Image.memory( base64.decode(sanitizedAvatar64), @@ -182,6 +178,30 @@ class V2sDatapodProvider with ChangeNotifier { return await file.writeAsBytes(base64.decode(data)); } + // Future cacheAvatar(String address, String data) async { + // // Get the list of all files in the directory + // final dir = Directory(avatarsCacheDirectory.path); + // var filesList = dir.listSync().whereType().toList(); + + // // Sorting files by modified date, oldest first + // filesList + // .sort((a, b) => a.lastModifiedSync().compareTo(b.lastModifiedSync())); + + // // If there are more than 20 files, remove the oldest ones + // while (filesList.length > 20) { + // filesList.first.deleteSync(); + // filesList.removeAt(0); + // } + + // // Write the new avatar file + // final file = File('${avatarsCacheDirectory.path}/$address'); + // await file.writeAsBytes(base64.decode(data)); + + // log.d('cache files: ${filesList.length}'); + + // return file; + // } + Image getAvatarLocal(String address) { final avatarFile = File('${avatarsCacheDirectory.path}/$address'); return Image.file( diff --git a/lib/screens/activity.dart b/lib/screens/activity.dart index f462529..390fc99 100644 --- a/lib/screens/activity.dart +++ b/lib/screens/activity.dart @@ -32,13 +32,13 @@ class _ActivityScreenState extends State { @override Widget build(BuildContext context) { - final duniterIndexer = Provider.of(context, listen: true); + Provider.of(context, listen: true); return PopScope( - onPopInvoked: (_) { - duniterIndexer.fetchMoreCursor = - duniterIndexer.pageInfo = duniterIndexer.transBC = null; - }, + // onPopInvoked: (_) { + // duniterIndexer.fetchMoreCursor = + // duniterIndexer.pageInfo = duniterIndexer.transBC = null; + // }, child: Scaffold( appBar: AppBar( elevation: 0, diff --git a/lib/widgets/datapod_avatar.dart b/lib/widgets/datapod_avatar.dart index f591b6b..cab71d8 100644 --- a/lib/widgets/datapod_avatar.dart +++ b/lib/widgets/datapod_avatar.dart @@ -6,7 +6,6 @@ import 'package:gecko/globals.dart'; import 'package:gecko/models/queries_datapod.dart'; import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/providers/v2s_datapod.dart'; -import 'package:gecko/widgets/commons/loading.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:provider/provider.dart'; @@ -37,7 +36,7 @@ class DatapodAvatar extends StatelessWidget { final client = ValueNotifier( GraphQLClient( - cache: GraphQLCache(store: HiveStore()), + cache: GraphQLCache(), link: httpLink, ), ); @@ -55,8 +54,8 @@ class DatapodAvatar extends StatelessWidget { ), builder: (QueryResult result, {fetchMore, refetch}) { if (result.isLoading) { - return const Center( - child: Loading(), + return Center( + child: ClipOval(child: datapod.defaultAvatar(size)), ); } final String? avatar64 = diff --git a/lib/widgets/history_query.dart b/lib/widgets/history_query.dart index f2ce886..35b360c 100644 --- a/lib/widgets/history_query.dart +++ b/lib/widgets/history_query.dart @@ -12,8 +12,7 @@ import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:provider/provider.dart'; class HistoryQuery extends StatelessWidget { - const HistoryQuery({Key? key, required this.address}) - : super(key: key); + const HistoryQuery({Key? key, required this.address}) : super(key: key); final String address; @override @@ -24,7 +23,6 @@ class HistoryQuery extends StatelessWidget { final ScrollController scrollController = ScrollController(); FetchMoreOptions? opts; - int nPage = 1; int nRepositories = 20; if (indexerEndpoint == '') { @@ -39,7 +37,7 @@ class HistoryQuery extends StatelessWidget { } final httpLink = HttpLink( - '$indexerEndpoint/v1beta1/relay', + '$indexerEndpoint/v1/graphql', ); final client = ValueNotifier( @@ -61,8 +59,8 @@ class HistoryQuery extends StatelessWidget { document: gql(getHistoryByAddressQ), variables: { 'address': address, - 'number': 20, - 'cursor': null + 'number': nRepositories, + 'offset': 0 }, ), builder: (QueryResult result, {fetchMore, refetch}) { @@ -73,6 +71,7 @@ class HistoryQuery extends StatelessWidget { ), ); } + final List transactions = result.data?["transaction"]; if (result.hasException) { log.e('Error Indexer: ${result.exception}'); @@ -84,8 +83,7 @@ class HistoryQuery extends StatelessWidget { style: scaledTextStyle(fontSize: 18), ) ]); - } else if (result - .data?['transaction_connection']?['edges'].isEmpty) { + } else if (transactions.isEmpty) { return Column(children: [ ScaledSizedBox(height: 50), Text( @@ -95,22 +93,18 @@ class HistoryQuery extends StatelessWidget { ]); } - if (result.isNotLoading) { - if (duniterIndexer.fetchMoreCursor == null) nPage = 1; + final int totalTransactions = + result.data!["transaction_aggregate"]["aggregate"]["count"]; + duniterIndexer.hasNextPage = + !(transactions.length == totalTransactions); - if (nPage <= 3) { - nRepositories = 20; - } else if (nPage <= 6) { - nRepositories = 40; - } else if (nPage <= 12) { - nRepositories = 80; - } else { - nRepositories = 120; - } - nPage++; - opts = duniterIndexer.mergeQueryResult( - result, opts, address, nRepositories); - } + opts = duniterIndexer.mergeQueryResult( + transactions: transactions, + opts: opts, + address: address, + nRepositories: nRepositories, + offset: transactions.length, + ); // Get transaction in progress if exist String? transactionId; @@ -141,13 +135,13 @@ class HistoryQuery extends StatelessWidget { ), ), onNotification: (dynamic t) { - if (duniterIndexer.pageInfo == null) { - duniterIndexer.reload(); - } + // if (duniterIndexer.pageInfo == null) { + // duniterIndexer.reload(); + // } if (t is ScrollEndNotification && scrollController.position.pixels >= scrollController.position.maxScrollExtent * 0.7 && - duniterIndexer.pageInfo!['hasNextPage'] && + duniterIndexer.hasNextPage && result.isNotLoading) { fetchMore!(opts!); } diff --git a/lib/widgets/history_view.dart b/lib/widgets/history_view.dart index df966fb..4594d8c 100644 --- a/lib/widgets/history_view.dart +++ b/lib/widgets/history_view.dart @@ -5,6 +5,7 @@ import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/providers/duniter_indexer.dart'; import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/screens/wallet_view.dart'; +import 'package:gecko/widgets/commons/loading.dart'; import 'package:gecko/widgets/page_route_no_transition.dart'; import 'package:gecko/widgets/transaction_tile.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; @@ -97,14 +98,14 @@ class HistoryView extends StatelessWidget { context: context), ]); }).toList()), - if (result.isLoading && duniterIndexer.pageInfo!['hasPreviousPage']) + if (result.isLoading && duniterIndexer.hasNextPage) const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - CircularProgressIndicator(), + Loading(size: 30, stroke: 3), ], ), - if (!duniterIndexer.pageInfo!['hasNextPage'] && + if (!duniterIndexer.hasNextPage && sub.oldOwnerKeys[address]?[0] != null) Padding( padding: const EdgeInsets.symmetric(vertical: 30), @@ -128,7 +129,7 @@ class HistoryView extends StatelessWidget { ), Column(children: [ Text( - 'identityMigrated:'.tr(), + 'identityMigrated'.tr(), style: scaledTextStyle( fontSize: 19, color: Colors.green[700], @@ -150,7 +151,7 @@ class HistoryView extends StatelessWidget { ), ), ), - if (!duniterIndexer.pageInfo!['hasNextPage']) + if (!duniterIndexer.hasNextPage) Column( children: [ ScaledSizedBox(height: 15), diff --git a/lib/widgets/search_identity_query.dart b/lib/widgets/search_identity_query.dart index e9b0d8a..0f0c893 100644 --- a/lib/widgets/search_identity_query.dart +++ b/lib/widgets/search_identity_query.dart @@ -36,7 +36,7 @@ class SearchIdentityQuery extends StatelessWidget { final client = ValueNotifier( GraphQLClient( cache: GraphQLCache( - store: HiveStore()), // GraphQLCache(store: HiveStore()) + store: HiveStore()), link: httpLink, ), );