feat: use classic GraphQL pagination system for wallet history
This commit is contained in:
parent
ef9e7b30f1
commit
bc415b56e8
|
@ -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}}}) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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!);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -36,7 +36,7 @@ class SearchIdentityQuery extends StatelessWidget {
|
|||
final client = ValueNotifier(
|
||||
GraphQLClient(
|
||||
cache: GraphQLCache(
|
||||
store: HiveStore()), // GraphQLCache(store: HiveStore())
|
||||
store: HiveStore()),
|
||||
link: httpLink,
|
||||
),
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue