feat: use classic GraphQL pagination system for wallet history

This commit is contained in:
poka 2024-01-07 19:37:21 +01:00
parent ef9e7b30f1
commit bc415b56e8
9 changed files with 127 additions and 89 deletions

View File

@ -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}}}) {

View File

@ -112,7 +112,7 @@ class WalletData extends HiveObject {
final datapod = Provider.of<V2sDatapodProvider>(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;

View File

@ -11,11 +11,10 @@ import 'package:graphql_flutter/graphql_flutter.dart';
class DuniterIndexer with ChangeNotifier {
Map<String, String?> 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<dynamic>? blockchainTX =
(result.data['transaction_connection']['edges'] as List<dynamic>?);
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<dynamic> repos = [
...previousResultData!['transaction'] as List<dynamic>,
...fetchMoreResultData!['transaction'] as List<dynamic>
];
if (fetchMoreCursor != null) {
opts = FetchMoreOptions(
variables: {'cursor': fetchMoreCursor, 'number': nRepositories},
updateQuery: (previousResultData, fetchMoreResultData) {
final List<dynamic> repos = [
...previousResultData!['transaction_connection']['edges']
as List<dynamic>,
...fetchMoreResultData!['transaction_connection']['edges']
as List<dynamic>
];
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);
}

View File

@ -139,7 +139,7 @@ class V2sDatapodProvider with ChangeNotifier {
}
Future<Image> getRemoteAvatar(String address,
{double size = 20, bool saveOnDisk = false, String? uuid}) async {
{double size = 20, String? uuid}) async {
final variables = <String, dynamic>{
'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<File> 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<File>().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(

View File

@ -32,13 +32,13 @@ class _ActivityScreenState extends State<ActivityScreen> {
@override
Widget build(BuildContext context) {
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: true);
Provider.of<DuniterIndexer>(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,

View File

@ -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 =

View File

@ -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: <String, dynamic>{
'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: <Widget>[
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!);
}

View File

@ -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: <Widget>[
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: <Widget>[
ScaledSizedBox(height: 15),

View File

@ -36,7 +36,7 @@ class SearchIdentityQuery extends StatelessWidget {
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(
store: HiveStore()), // GraphQLCache(store: HiveStore())
store: HiveStore()),
link: httpLink,
),
);