feat: add datapod provider and mutation for avatar
This commit is contained in:
parent
2830a9d005
commit
8ca0b00a60
|
@ -26,6 +26,7 @@ import 'package:gecko/providers/duniter_indexer.dart';
|
||||||
import 'package:gecko/providers/generate_wallets.dart';
|
import 'package:gecko/providers/generate_wallets.dart';
|
||||||
import 'package:gecko/providers/settings_provider.dart';
|
import 'package:gecko/providers/settings_provider.dart';
|
||||||
import 'package:gecko/providers/substrate_sdk.dart';
|
import 'package:gecko/providers/substrate_sdk.dart';
|
||||||
|
import 'package:gecko/providers/v2s_datapod.dart';
|
||||||
import 'package:gecko/providers/wallets_profiles.dart';
|
import 'package:gecko/providers/wallets_profiles.dart';
|
||||||
import 'package:gecko/providers/home.dart';
|
import 'package:gecko/providers/home.dart';
|
||||||
import 'package:gecko/providers/my_wallets.dart';
|
import 'package:gecko/providers/my_wallets.dart';
|
||||||
|
@ -133,7 +134,8 @@ class Gecko extends StatelessWidget {
|
||||||
ChangeNotifierProvider(create: (_) => CesiumPlusProvider()),
|
ChangeNotifierProvider(create: (_) => CesiumPlusProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => SubstrateSdk()),
|
ChangeNotifierProvider(create: (_) => SubstrateSdk()),
|
||||||
ChangeNotifierProvider(create: (_) => DuniterIndexer()),
|
ChangeNotifierProvider(create: (_) => DuniterIndexer()),
|
||||||
ChangeNotifierProvider(create: (_) => SettingsProvider())
|
ChangeNotifierProvider(create: (_) => SettingsProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => V2sDatapodProvider())
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
localizationsDelegates: context.localizationDelegates,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
const String updateProfileQ = r'''
|
||||||
|
mutation ($address: String!, $hash: String!, $signature: String!, $title: String, $description: String, $avatar: String, $geoloc: GeolocInput, $city: String, $socials: [SocialInput!]) {
|
||||||
|
updateProfile(address: $address, hash: $hash, signature: $signature, title: $title, description: $description, avatarBase64: $avatar, geoloc: $geoloc, city: $city, socials: $socials) {
|
||||||
|
message
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String deleteProfileQ = r'''
|
||||||
|
mutation ($address: String!, $hash: String!, $signature: String!) {
|
||||||
|
deleteProfile(address: $address, hash: $hash, signature: $signature) {
|
||||||
|
message
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String migrateProfileQ = r'''
|
||||||
|
mutation ($addressOld: String!, $addressNew: String!, $hash: String!, $signature: String!) {
|
||||||
|
migrateProfile(addressOld: $addressOld, addressNew: $addressNew, hash: $hash, signature: $signature) {
|
||||||
|
message
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
|
@ -78,7 +78,7 @@ class CesiumPlusProvider with ChangeNotifier {
|
||||||
final hashDocHex = hashDoc.toString().toUpperCase();
|
final hashDocHex = hashDoc.toString().toUpperCase();
|
||||||
|
|
||||||
// Generate signature of document
|
// Generate signature of document
|
||||||
final signature = await sub.signCsPlusDocument(hashDocHex, address);
|
final signature = await sub.signDatapod(hashDocHex, address);
|
||||||
|
|
||||||
// Build final document
|
// Build final document
|
||||||
final Map<String, dynamic> data = {
|
final Map<String, dynamic> data = {
|
||||||
|
|
|
@ -136,7 +136,7 @@ class SubstrateSdk with ChangeNotifier {
|
||||||
return res?.signature ?? '';
|
return res?.signature ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> signCsPlusDocument(String document, String address) async {
|
Future<String> signDatapod(String document, String address) async {
|
||||||
final myWallets =
|
final myWallets =
|
||||||
Provider.of<MyWalletsProvider>(homeContext, listen: false);
|
Provider.of<MyWalletsProvider>(homeContext, listen: false);
|
||||||
final messageToSign = Uint8List.fromList(document.codeUnits);
|
final messageToSign = Uint8List.fromList(document.codeUnits);
|
||||||
|
@ -232,7 +232,7 @@ class SubstrateSdk with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get onchain storage values
|
// Get onchain storage values
|
||||||
final List<Map> balanceGlobalMulti =
|
final List<Map> accountMulti =
|
||||||
(await _getStorage('system.account.multi($stringifyAddresses)') as List)
|
(await _getStorage('system.account.multi($stringifyAddresses)') as List)
|
||||||
.map((dynamic e) => e as Map<String, dynamic>)
|
.map((dynamic e) => e as Map<String, dynamic>)
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -253,14 +253,10 @@ class SubstrateSdk with ChangeNotifier {
|
||||||
.map((dynamic e) => e as Map<String, dynamic>?)
|
.map((dynamic e) => e as Map<String, dynamic>?)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final List pastReevals =
|
|
||||||
await _getStorage('universalDividend.pastReevals()');
|
|
||||||
|
|
||||||
int nbr = 0;
|
int nbr = 0;
|
||||||
Map<String, Map<String, double>> finalBalancesList = {};
|
Map<String, Map<String, double>> finalBalancesList = {};
|
||||||
for (Map balanceGlobal in balanceGlobalMulti) {
|
for (Map account in accountMulti) {
|
||||||
final computedBalance =
|
final computedBalance = await _computeBalance(idtyDataList[nbr], account);
|
||||||
await _computeBalance(idtyDataList[nbr], pastReevals, balanceGlobal);
|
|
||||||
finalBalancesList.putIfAbsent(addresses[nbr], () => computedBalance);
|
finalBalancesList.putIfAbsent(addresses[nbr], () => computedBalance);
|
||||||
nbr++;
|
nbr++;
|
||||||
}
|
}
|
||||||
|
@ -279,35 +275,33 @@ class SubstrateSdk with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get onchain storage values
|
// Get onchain storage values
|
||||||
final Map balanceGlobal = await _getStorage('system.account("$address")');
|
final Map account = await _getStorage('system.account("$address")');
|
||||||
final int? idtyIndex =
|
final int? idtyIndex =
|
||||||
await _getStorage('identity.identityIndexOf("$address")');
|
await _getStorage('identity.identityIndexOf("$address")');
|
||||||
final Map? idtyData = idtyIndex == null
|
final Map? idtyData = idtyIndex == null
|
||||||
? null
|
? null
|
||||||
: await _getStorage('identity.identities($idtyIndex)');
|
: await _getStorage('identity.identities($idtyIndex)');
|
||||||
final List pastReevals =
|
|
||||||
await _getStorage('universalDividend.pastReevals()');
|
|
||||||
|
|
||||||
return _computeBalance(idtyData, pastReevals, balanceGlobal);
|
return _computeBalance(idtyData, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, double>> _computeBalance(
|
Future<Map<String, double>> _computeBalance(
|
||||||
Map? idtyData, List pastReevals, Map balanceGlobal) async {
|
Map? idtyData, Map account) async {
|
||||||
|
final List pastReevals =
|
||||||
|
await _getStorage('universalDividend.pastReevals()');
|
||||||
// Compute amount of claimable UDs
|
// Compute amount of claimable UDs
|
||||||
currentUdIndex = await getCurrentUdIndex();
|
currentUdIndex = await getCurrentUdIndex();
|
||||||
final int unclaimedUds = _computeUnclaimUds(
|
final int unclaimedUds = _computeUnclaimUds(
|
||||||
idtyData?['data']?['firstEligibleUd'] ?? 0, pastReevals);
|
idtyData?['data']?['firstEligibleUd'] ?? 0, pastReevals);
|
||||||
|
|
||||||
// Calculate transferable and potential balance
|
// Calculate transferable and potential balance
|
||||||
final int transferableBalance =
|
final int transferableBalance = (account['data']['free'] + unclaimedUds);
|
||||||
(balanceGlobal['data']['free'] + unclaimedUds);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'transferableBalance': round((transferableBalance / balanceRatio) / 100),
|
'transferableBalance': round((transferableBalance / balanceRatio) / 100),
|
||||||
'free': round((balanceGlobal['data']['free'] / balanceRatio) / 100),
|
'free': round((account['data']['free'] / balanceRatio) / 100),
|
||||||
'unclaimedUds': round((unclaimedUds / balanceRatio) / 100),
|
'unclaimedUds': round((unclaimedUds / balanceRatio) / 100),
|
||||||
'reserved':
|
'reserved': round((account['data']['reserved'] / balanceRatio) / 100),
|
||||||
round((balanceGlobal['data']['reserved'] / balanceRatio) / 100),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gecko/globals.dart';
|
||||||
|
import 'package:gecko/models/queries_datapod.dart';
|
||||||
|
import 'package:gecko/providers/substrate_sdk.dart';
|
||||||
|
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class V2sDatapodProvider with ChangeNotifier {
|
||||||
|
Future<QueryResult> _execQuery(
|
||||||
|
String query, Map<String, dynamic> variables) async {
|
||||||
|
final httpLink = HttpLink(
|
||||||
|
// 'http://10.0.2.2:8080/v1/graphql',
|
||||||
|
'https://gdev-datapod.p2p.legal/v1/graphql',
|
||||||
|
);
|
||||||
|
|
||||||
|
final GraphQLClient client = GraphQLClient(
|
||||||
|
cache: GraphQLCache(),
|
||||||
|
link: httpLink,
|
||||||
|
);
|
||||||
|
|
||||||
|
final QueryOptions options =
|
||||||
|
QueryOptions(document: gql(query), variables: variables);
|
||||||
|
|
||||||
|
return await client.query(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> updateProfile(
|
||||||
|
{required String address,
|
||||||
|
String? title,
|
||||||
|
String? description,
|
||||||
|
String? avatar,
|
||||||
|
String? city,
|
||||||
|
List<Map<String, String>>? socials,
|
||||||
|
Map<String, double>? geoloc}) async {
|
||||||
|
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
|
||||||
|
|
||||||
|
final messageToSign = jsonEncode({
|
||||||
|
'address': address,
|
||||||
|
'description': description,
|
||||||
|
'avatarBase64': avatar,
|
||||||
|
'geoloc': geoloc,
|
||||||
|
'title': title,
|
||||||
|
'city': city,
|
||||||
|
'socials': socials
|
||||||
|
});
|
||||||
|
final hashDocBytes = utf8.encode(messageToSign);
|
||||||
|
final hashDoc = sha256.convert(hashDocBytes).toString().toUpperCase();
|
||||||
|
final signature = await sub.signDatapod(hashDoc, address);
|
||||||
|
|
||||||
|
final variables = <String, dynamic>{
|
||||||
|
'address': address,
|
||||||
|
'hash': hashDoc,
|
||||||
|
'signature': signature,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'avatar': avatar,
|
||||||
|
'city': city,
|
||||||
|
'socials': socials,
|
||||||
|
'geoloc': geoloc,
|
||||||
|
};
|
||||||
|
final result = await _execQuery(updateProfileQ, variables);
|
||||||
|
if (result.hasException) {
|
||||||
|
log.e(result.exception.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.d(result.data!['updateProfile']['message']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteProfile({required String address}) async {
|
||||||
|
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
|
||||||
|
|
||||||
|
final messageToSign = jsonEncode({'address': address});
|
||||||
|
final hashDocBytes = utf8.encode(messageToSign);
|
||||||
|
final hashDoc = sha256.convert(hashDocBytes).toString().toUpperCase();
|
||||||
|
final signature = await sub.signDatapod(hashDoc, address);
|
||||||
|
|
||||||
|
final variables = <String, dynamic>{
|
||||||
|
'address': address,
|
||||||
|
'hash': hashDoc,
|
||||||
|
'signature': signature
|
||||||
|
};
|
||||||
|
final result = await _execQuery(deleteProfileQ, variables);
|
||||||
|
if (result.hasException) {
|
||||||
|
log.e(result.exception.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.d(result.data!['deleteProfile']['message']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> migrateProfile(
|
||||||
|
{required String addressOld, required String addressNew}) async {
|
||||||
|
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
|
||||||
|
|
||||||
|
final messageToSign =
|
||||||
|
jsonEncode({'addressOld': addressOld, 'addressNew': addressNew});
|
||||||
|
final hashDocBytes = utf8.encode(messageToSign);
|
||||||
|
final hashDoc = sha256.convert(hashDocBytes).toString().toUpperCase();
|
||||||
|
final signature = await sub.signDatapod(hashDoc, addressOld);
|
||||||
|
|
||||||
|
final variables = <String, dynamic>{
|
||||||
|
'addressOld': addressOld,
|
||||||
|
'addressNew': addressNew,
|
||||||
|
'hash': hashDoc,
|
||||||
|
'signature': signature
|
||||||
|
};
|
||||||
|
final result = await _execQuery(migrateProfileQ, variables);
|
||||||
|
if (result.hasException) {
|
||||||
|
log.e(result.exception.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.d(result.data!['migrateProfile']['message']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> setAvatar(String address, String avatarPath) async {
|
||||||
|
final avatarBytes = await File(avatarPath).readAsBytes();
|
||||||
|
final avatarString = base64Encode(avatarBytes);
|
||||||
|
return await updateProfile(address: address, avatar: avatarString);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import 'package:gecko/providers/duniter_indexer.dart';
|
||||||
import 'package:gecko/providers/my_wallets.dart';
|
import 'package:gecko/providers/my_wallets.dart';
|
||||||
import 'package:gecko/models/wallet_data.dart';
|
import 'package:gecko/models/wallet_data.dart';
|
||||||
import 'package:gecko/providers/substrate_sdk.dart';
|
import 'package:gecko/providers/substrate_sdk.dart';
|
||||||
|
import 'package:gecko/providers/v2s_datapod.dart';
|
||||||
import 'package:gecko/widgets/commons/common_elements.dart';
|
import 'package:gecko/widgets/commons/common_elements.dart';
|
||||||
import 'package:gecko/screens/myWallets/unlocking_wallet.dart';
|
import 'package:gecko/screens/myWallets/unlocking_wallet.dart';
|
||||||
import 'package:gecko/screens/transaction_in_progress.dart';
|
import 'package:gecko/screens/transaction_in_progress.dart';
|
||||||
|
@ -109,6 +110,9 @@ class WalletOptionsProvider with ChangeNotifier {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final datapod =
|
||||||
|
Provider.of<V2sDatapodProvider>(homeContext, listen: false);
|
||||||
|
|
||||||
final newPath = "${imageDirectory.path}/${pickedFile.name}";
|
final newPath = "${imageDirectory.path}/${pickedFile.name}";
|
||||||
|
|
||||||
if (croppedFile != null) {
|
if (croppedFile != null) {
|
||||||
|
@ -117,6 +121,7 @@ class WalletOptionsProvider with ChangeNotifier {
|
||||||
log.w('No image selected.');
|
log.w('No image selected.');
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
datapod.setAvatar(address.text, newPath);
|
||||||
return newPath;
|
return newPath;
|
||||||
} else {
|
} else {
|
||||||
log.w('No image selected.');
|
log.w('No image selected.');
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gecko/globals.dart';
|
import 'package:gecko/globals.dart';
|
||||||
|
|
Loading…
Reference in New Issue