Merge branch 'dev'

This commit is contained in:
poka 2024-01-08 15:45:31 +01:00
commit 6cb085851f
54 changed files with 1290 additions and 831 deletions

View File

@ -165,6 +165,7 @@
"choiceOfSourceWallet": "Choose a source wallet", "choiceOfSourceWallet": "Choose a source wallet",
"extrinsicInProgress": "{} in progress", "extrinsicInProgress": "{} in progress",
"extrinsicValidated": "{} validated !", "extrinsicValidated": "{} validated !",
"extrinsicFinalized": "{} finalized !",
"fromMinus": "from", "fromMinus": "from",
"toMinus": "to", "toMinus": "to",
"deleteThisWallet": "Delete this wallet", "deleteThisWallet": "Delete this wallet",
@ -229,5 +230,10 @@
"gotit": "Got it", "gotit": "Got it",
"moreInfo": "More information", "moreInfo": "More information",
"keepThisPaperSafe": "Keep this sheet safe from prying lizards.\nIt will allow you to restore all your wallets at any time.", "keepThisPaperSafe": "Keep this sheet safe from prying lizards.\nIt will allow you to restore all your wallets at any time.",
"fundsUnavailable": "Insufficient funds" "fundsUnavailable": "Insufficient funds",
"addressNotBelongToMnemonic": "The address you provided does not belong to this recovery sentence",
"enterYourNewMnemonic": "Enter your new recovery sentence",
"enterYourNewAddress": "Enter your new address {}",
"youCanMigrateThisIdentity": "You can migrate this identity !",
"identityMigrated": "Identity migrated"
} }

View File

@ -166,6 +166,7 @@
"choiceOfSourceWallet": "Elige un monedero de origen", "choiceOfSourceWallet": "Elige un monedero de origen",
"extrinsicInProgress": "{} en progreso", "extrinsicInProgress": "{} en progreso",
"extrinsicValidated": "¡ {} validado !", "extrinsicValidated": "¡ {} validado !",
"extrinsicFinalized": "{} finalized !",
"fromMinus": "de", "fromMinus": "de",
"toMinus": "a", "toMinus": "a",
"deleteThisWallet": "Borrar este monedero", "deleteThisWallet": "Borrar este monedero",
@ -230,5 +231,10 @@
"gotit": "Got it", "gotit": "Got it",
"moreInfo": "More information", "moreInfo": "More information",
"keepThisPaperSafe": "Keep this sheet safe from prying lizards.\nIt will allow you to restore all your wallets at any time.", "keepThisPaperSafe": "Keep this sheet safe from prying lizards.\nIt will allow you to restore all your wallets at any time.",
"fundsUnavailable": "Insufficient funds" "fundsUnavailable": "Insufficient funds",
"addressNotBelongToMnemonic": "The address you provided does not belong to this recovery sentence",
"enterYourNewMnemonic": "Enter your new recovery sentence",
"enterYourNewAddress": "Enter your new address {}",
"youCanMigrateThisIdentity": "You can migrate this identity !",
"identityMigrated": "Identity migrated"
} }

View File

@ -165,6 +165,7 @@
"choiceOfSourceWallet": "Choix du portefeuille source", "choiceOfSourceWallet": "Choix du portefeuille source",
"extrinsicInProgress": "{} en cours", "extrinsicInProgress": "{} en cours",
"extrinsicValidated": "{} validée !", "extrinsicValidated": "{} validée !",
"extrinsicFinalized": "{} finalisé !",
"fromMinus": "de", "fromMinus": "de",
"toMinus": "vers", "toMinus": "vers",
"deleteThisWallet": "Supprimer ce portefeuille", "deleteThisWallet": "Supprimer ce portefeuille",
@ -229,5 +230,10 @@
"gotit": "J'ai compris", "gotit": "J'ai compris",
"moreInfo": "Plus d'info", "moreInfo": "Plus d'info",
"keepThisPaperSafe": "Gardez cette feuille précieusement, à labri des lézards indiscrets.\nElle vous permettra de restaurer tous vos portefeuilles à tout moment.", "keepThisPaperSafe": "Gardez cette feuille précieusement, à labri des lézards indiscrets.\nElle vous permettra de restaurer tous vos portefeuilles à tout moment.",
"fundsUnavailable": "Fonds insuffisants" "fundsUnavailable": "Fonds insuffisants",
"addressNotBelongToMnemonic": "L'adresse que vous avez fournit n'appartient pas à cette phrase de restauration",
"enterYourNewMnemonic": "Entrez votre nouvelle phrase de restauration",
"enterYourNewAddress": "Entrez votre nouvelle adresse {}",
"youCanMigrateThisIdentity": "Vous pouvez migrer vers cette identité !",
"identityMigrated": "Identité migré"
} }

View File

@ -1,3 +1,5 @@
[ [
"https://hasura.gdev.coinduf.eu" "gdev-indexer.p2p.legal",
"gdev-hasura.cgeek.fr",
"hasura-gdev.pini.fr"
] ]

View File

@ -1,5 +1,5 @@
{ {
"initial_monetary_mass": 50100, "initial_monetary_mass": 60100,
"identities": { "identities": {
"test1": { "test1": {
"index": 0, "index": 0,
@ -49,7 +49,7 @@
}, },
"owner_pubkey": "5LqbvutJtRTHvnforyndwPbkC4Kf5cJtdRQaDcHoMi8S" "owner_pubkey": "5LqbvutJtRTHvnforyndwPbkC4Kf5cJtdRQaDcHoMi8S"
}, },
"testCesium1": { "test5": {
"index": 4, "index": 4,
"balance": 10000, "balance": 10000,
"membership_expire_on": 1705509948, "membership_expire_on": 1705509948,
@ -59,6 +59,18 @@
"test2": 1727758466, "test2": 1727758466,
"test3": 1727758466 "test3": 1727758466
}, },
"owner_pubkey": "6FgzG8NwatTWHo7rM7sPP6P4Q95R2ZQNqYiHCs38RT21"
},
"testCesium1": {
"index": 5,
"balance": 10000,
"membership_expire_on": 1705509948,
"next_cert_issuable_on": 1668347505,
"certs_received": {
"test1": 1727758466,
"test2": 1727758466,
"test3": 1727758466
},
"owner_pubkey": "DCovzCEnQm9GUWe6mr8u42JR1JAuoj3HbQUGdCkfTzSr" "owner_pubkey": "DCovzCEnQm9GUWe6mr8u42JR1JAuoj3HbQUGdCkfTzSr"
} }
} }

View File

@ -29,7 +29,7 @@ const cesiumPod = "https://g1.data.le-sou.org";
// String cesiumPod = "https://g1.data.presles.fr"; // String cesiumPod = "https://g1.data.presles.fr";
// String cesiumPod = "https://g1.data.e-is.pro"; // String cesiumPod = "https://g1.data.e-is.pro";
const datapodEndpoint = 'https://gdev-datapod.p2p.legal'; const datapodEndpoint = 'gdev-datapod.p2p.legal';
// const v2sDatapod = 'http://10.0.2.2:8080'; // const v2sDatapod = 'http://10.0.2.2:8080';
// Contexts // Contexts

View File

@ -3,14 +3,30 @@ import 'package:gecko/models/wallet_data.dart';
class MigrateWalletChecks { class MigrateWalletChecks {
final Map balance; final Map balance;
final IdtyStatus idtyStatus; final IdtyStatus idtyStatus;
final bool isSmith;
final String validationStatus; final String validationStatus;
final bool canValidate; final bool canValidate;
const MigrateWalletChecks( const MigrateWalletChecks({
{required this.balance, required this.balance,
required this.idtyStatus, required this.idtyStatus,
required this.isSmith, required this.validationStatus,
required this.validationStatus, required this.canValidate,
required this.canValidate}); });
const MigrateWalletChecks.defaultValues({
this.balance = const {'transferableBalance': 0},
this.idtyStatus = IdtyStatus.none,
this.validationStatus = '',
this.canValidate = false,
});
@override
String toString() {
return {
'balance': balance,
'idtyStatus': idtyStatus,
'validationStatus': validationStatus,
'canValidate': canValidate,
}.toString();
}
} }

View File

@ -18,7 +18,7 @@ query ($name: String!) {
} }
'''; ''';
const String getHistoryByAddressQ = r''' const String getHistoryByAddressRelayQ = r'''
query ($address: String!, $number: Int!, $cursor: String) { query ($address: String!, $number: Int!, $cursor: String) {
transaction_connection(where: transaction_connection(where:
{_or: [ {_or: [
@ -56,9 +56,36 @@ 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''' const String getCertsReceived = r'''
query ($address: String!) { query ($address: String!) {
certification(where: {receiver: {pubkey: {_eq: $address}}}) { certification(where: {receiver: {pubkey: {_eq: $address}}}, order_by: {created_at: desc}) {
issuer { issuer {
pubkey pubkey
name name
@ -70,7 +97,7 @@ query ($address: String!) {
const String getCertsSent = r''' const String getCertsSent = r'''
query ($address: String!) { query ($address: String!) {
certification(where: {issuer: {pubkey: {_eq: $address}}}) { certification(where: {issuer: {pubkey: {_eq: $address}}}, order_by: {created_at: desc}) {
receiver { receiver {
pubkey pubkey
name name
@ -96,3 +123,15 @@ query {
} }
} }
'''; ''';
const String subscribeHistoryIssuedQ = r'''
subscription ($address: String!) {
account_by_pk(pubkey: $address) {
transactions_issued(limit: 1, order_by: {created_at: desc}) {
receiver_pubkey
amount
created_at
}
}
}
''';

View File

@ -0,0 +1,19 @@
import 'package:gecko/widgets/transaction_status.dart';
class TransactionContent {
final String transactionId;
TransactionStatus status;
final String from;
final String to;
final double amount;
String? error;
TransactionContent({
required this.transactionId,
required this.status,
required this.from,
required this.to,
required this.amount,
this.error,
});
}

View File

@ -70,7 +70,7 @@ class WalletData extends HiveObject {
// creates the ':'-separated string from the WalletData // creates the ':'-separated string from the WalletData
String inLine() { String inLine() {
return "$chest:$number:$name:$derivation:$imageDefaultPath"; return "$chest:$number:$name:$derivation:$imageDefaultPath:$imageCustomPath:$identityStatus";
} }
bool hasIdentity() { bool hasIdentity() {
@ -112,7 +112,7 @@ class WalletData extends HiveObject {
final datapod = Provider.of<V2sDatapodProvider>(homeContext, listen: false); final datapod = Provider.of<V2sDatapodProvider>(homeContext, listen: false);
final avatarUuid = const Uuid().v4(); final avatarUuid = const Uuid().v4();
await datapod.getAvatar(address, saveOnDisk: true, uuid: avatarUuid); await datapod.getRemoteAvatar(address, uuid: avatarUuid);
final avatarPath = '${avatarsDirectory.path}/$address-$avatarUuid'; final avatarPath = '${avatarsDirectory.path}/$address-$avatarUuid';
if (!await File(avatarPath).exists()) return; if (!await File(avatarPath).exists()) return;

View File

@ -11,11 +11,12 @@ import 'package:graphql_flutter/graphql_flutter.dart';
class DuniterIndexer with ChangeNotifier { class DuniterIndexer with ChangeNotifier {
Map<String, String?> walletNameIndexer = {}; Map<String, String?> walletNameIndexer = {};
String? fetchMoreCursor;
Map? pageInfo;
List? transBC; List? transBC;
List listIndexerEndpoints = []; List listIndexerEndpoints = [];
bool isLoadingIndexer = false; bool isLoadingIndexer = false;
bool hasNextPage = false;
Future<QueryResult<Object?>?> Function()? refetch;
late GraphQLClient indexerClient;
void reload() { void reload() {
notifyListeners(); notifyListeners();
@ -27,10 +28,11 @@ class DuniterIndexer with ChangeNotifier {
final client = HttpClient(); final client = HttpClient();
client.connectionTimeout = const Duration(milliseconds: 4000); client.connectionTimeout = const Duration(milliseconds: 4000);
try { try {
final request = await client.postUrl(Uri.parse('$endpoint/v1/graphql')); final request =
await client.postUrl(Uri.parse('https://$endpoint/v1/graphql'));
final response = await request.close(); final response = await request.close();
if (response.statusCode != 200) { if (response.statusCode != 200) {
log.w('INDEXER IS OFFLINE'); log.w('Indexer $endpoint is offline');
indexerEndpoint = ''; indexerEndpoint = '';
isLoadingIndexer = false; isLoadingIndexer = false;
notifyListeners(); notifyListeners();
@ -46,7 +48,7 @@ class DuniterIndexer with ChangeNotifier {
return true; return true;
} }
} catch (e) { } catch (e) {
log.w('INDEXER IS OFFLINE'); log.w('Indexer $endpoint is offline');
indexerEndpoint = ''; indexerEndpoint = '';
isLoadingIndexer = false; isLoadingIndexer = false;
notifyListeners(); notifyListeners();
@ -96,7 +98,7 @@ class DuniterIndexer with ChangeNotifier {
} }
try { try {
final endpointPath = '${listIndexerEndpoints[i]}/v1/graphql'; final endpointPath = 'https://${listIndexerEndpoints[i]}/v1/graphql';
final request = await client.postUrl(Uri.parse(endpointPath)); final request = await client.postUrl(Uri.parse(endpointPath));
final response = await request.close(); final response = await request.close();
@ -128,14 +130,13 @@ class DuniterIndexer with ChangeNotifier {
return indexerEndpoint; return indexerEndpoint;
} }
List parseHistory(blockchainTX, pubkey) { List parseHistory(List blockchainTX, String address) {
List transBC = []; List transBC = [];
int i = 0; int i = 0;
for (final trans in blockchainTX) { for (final transaction in blockchainTX) {
final transaction = trans['node'];
final direction = final direction =
transaction['issuer_pubkey'] != pubkey ? 'RECEIVED' : 'SENT'; transaction['issuer']['pubkey'] != address ? 'RECEIVED' : 'SENT';
transBC.add(i); transBC.add(i);
transBC[i] = []; transBC[i] = [];
@ -143,10 +144,10 @@ class DuniterIndexer with ChangeNotifier {
final amountBrut = transaction['amount']; final amountBrut = transaction['amount'];
final amount = removeDecimalZero(amountBrut / 100); final amount = removeDecimalZero(amountBrut / 100);
if (direction == "RECEIVED") { if (direction == "RECEIVED") {
transBC[i].add(transaction['issuer_pubkey']); transBC[i].add(transaction['issuer']['pubkey']);
transBC[i].add(transaction['issuer']['identity']?['name'] ?? ''); transBC[i].add(transaction['issuer']['identity']?['name'] ?? '');
} else if (direction == "SENT") { } 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(transaction['receiver']['identity']?['name'] ?? '');
} }
transBC[i].add(amount); transBC[i].add(amount);
@ -157,38 +158,35 @@ class DuniterIndexer with ChangeNotifier {
return transBC; return transBC;
} }
FetchMoreOptions? mergeQueryResult(result, opts, pubkey, nRepositories) { FetchMoreOptions? mergeQueryResult(
final List<dynamic>? blockchainTX = {required List transactions,
(result.data['transaction_connection']['edges'] as List<dynamic>?); 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']; // if (fetchMoreCursor != null) {
fetchMoreCursor = pageInfo!['endCursor']; opts = FetchMoreOptions(
final hasNextPage = pageInfo!['hasNextPage']; variables: {'offset': offset, 'number': nRepositories},
final hasPreviousPage = pageInfo!['hasPreviousPage']; updateQuery: (previousResultData, fetchMoreResultData) {
log.d('endCursor: $fetchMoreCursor $hasNextPage $hasPreviousPage'); final List<dynamic> repos = [
...previousResultData!['transaction'] as List<dynamic>,
...fetchMoreResultData!['transaction'] as List<dynamic>
];
if (fetchMoreCursor != null) { fetchMoreResultData['transaction'] = repos;
opts = FetchMoreOptions( return fetchMoreResultData;
variables: {'cursor': fetchMoreCursor, 'number': nRepositories}, },
updateQuery: (previousResultData, fetchMoreResultData) { );
final List<dynamic> repos = [ transBC = parseHistory(transactions, address);
...previousResultData!['transaction_connection']['edges'] // } else {
as List<dynamic>, // log.d("Activity start of $address");
...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");
}
return opts; return opts;
} }
@ -197,113 +195,119 @@ class DuniterIndexer with ChangeNotifier {
String result = n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 2); String result = n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 2);
return double.parse(result); return double.parse(result);
} }
}
//// Manuals queries //// Manuals queries
Future<bool> isIdtyExist(String name) async { Future<bool> isIdtyExist(String name) async {
final variables = <String, dynamic>{ final variables = <String, dynamic>{
'name': name, 'name': name,
}; };
final result = await _execQuery(isIdtyExistQ, variables); final result = await _execQuery(isIdtyExistQ, variables);
return result.data!['identity']?.isNotEmpty ?? false; return result.data!['identity']?.isNotEmpty ?? false;
}
Future<DateTime> getBlockStart() async {
final result = await _execQuery(getBlockchainStartQ, {});
if (!result.hasException) {
startBlockchainTime =
DateTime.parse(result.data!['block'][0]['created_at']);
startBlockchainInitialized = true;
return startBlockchainTime;
}
return DateTime(0, 0, 0, 0, 0);
}
Future<QueryResult> _execQuery(
String query, Map<String, dynamic> variables) async {
final httpLink = HttpLink(
'$indexerEndpoint/v1/graphql',
);
final GraphQLClient client = GraphQLClient(
cache: GraphQLCache(),
link: httpLink,
);
final QueryOptions options =
QueryOptions(document: gql(query), variables: variables);
return await client.query(options);
}
Map computeHistoryView(repository, String address) {
final bool isUdUnit = configBox.get('isUdUnit') ?? false;
late double amount;
late String finalAmount;
final DateTime date = repository[0];
final dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, {
1,
2,
7,
9
}.contains(date.month) ? 4 : 3)}";
DateTime normalizeDate(DateTime inputDate) {
return DateTime(inputDate.year, inputDate.month, inputDate.day);
} }
String getDateDelimiter() { Future<DateTime> getBlockStart() async {
DateTime now = DateTime.now(); final result = await _execQuery(getBlockchainStartQ, {});
final transactionDate = normalizeDate(date.toLocal()); if (!result.hasException) {
final todayDate = normalizeDate(now); startBlockchainTime =
final yesterdayDate = normalizeDate(now.subtract(const Duration(days: 1))); DateTime.parse(result.data!['block'][0]['created_at']);
final isSameWeek = weekNumber(transactionDate) == weekNumber(now) && startBlockchainInitialized = true;
transactionDate.year == now.year; return startBlockchainTime;
final isTodayOrYesterday =
transactionDate == todayDate || transactionDate == yesterdayDate;
if (transactionDate == todayDate) {
return "today".tr();
} else if (transactionDate == yesterdayDate) {
return "yesterday".tr();
} else if (isSameWeek && !isTodayOrYesterday) {
return "thisWeek".tr();
} else if (!isSameWeek && !isTodayOrYesterday) {
if (transactionDate.year == now.year) {
return monthsInYear[transactionDate.month]!;
} else {
return "${monthsInYear[transactionDate.month]} ${transactionDate.year}";
}
} else {
return '';
} }
return DateTime(0, 0, 0, 0, 0);
} }
final dateDelimiter = getDateDelimiter(); Future<QueryResult> _execQuery(
String query, Map<String, dynamic> variables) async {
final options = QueryOptions(document: gql(query), variables: variables);
amount = repository[4] == 'RECEIVED' ? repository[3] : repository[3] * -1; // 5GMyvKsTNk9wDBy9jwKaX6mhSzmFFtpdK9KNnmrLoSTSuJHv
if (isUdUnit) { return await indexerClient.query(options);
amount = round(amount / balanceRatio);
finalAmount = 'ud'.tr(args: ['$amount ']);
} else {
finalAmount = '$amount $currencyName';
} }
bool isMigrationTime = Stream<QueryResult> subscribeHistoryIssued(String address) {
startBlockchainInitialized && date.compareTo(startBlockchainTime) < 0; final variables = <String, dynamic>{
'address': address,
};
return { final options = SubscriptionOptions(
'finalAmount': finalAmount, document: gql(subscribeHistoryIssuedQ),
'isMigrationTime': isMigrationTime, variables: variables,
'dateDelimiter': dateDelimiter, );
'dateForm': dateForm,
};
}
int weekNumber(DateTime date) { return indexerClient.subscribe(options);
int dayOfYear = int.parse(DateFormat("D").format(date)); }
return ((dayOfYear - date.weekday + 10) / 7).floor();
Map computeHistoryView(repository, String address) {
final bool isUdUnit = configBox.get('isUdUnit') ?? false;
late double amount;
late String finalAmount;
final DateTime date = repository[0];
final dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, {
1,
2,
7,
9
}.contains(date.month) ? 4 : 3)}";
DateTime normalizeDate(DateTime inputDate) {
return DateTime(inputDate.year, inputDate.month, inputDate.day);
}
String getDateDelimiter() {
DateTime now = DateTime.now();
final transactionDate = normalizeDate(date.toLocal());
final todayDate = normalizeDate(now);
final yesterdayDate =
normalizeDate(now.subtract(const Duration(days: 1)));
final isSameWeek = weekNumber(transactionDate) == weekNumber(now) &&
transactionDate.year == now.year;
final isTodayOrYesterday =
transactionDate == todayDate || transactionDate == yesterdayDate;
if (transactionDate == todayDate) {
return "today".tr();
} else if (transactionDate == yesterdayDate) {
return "yesterday".tr();
} else if (isSameWeek && !isTodayOrYesterday) {
return "thisWeek".tr();
} else if (!isSameWeek && !isTodayOrYesterday) {
if (transactionDate.year == now.year) {
return monthsInYear[transactionDate.month]!;
} else {
return "${monthsInYear[transactionDate.month]} ${transactionDate.year}";
}
} else {
return '';
}
}
final dateDelimiter = getDateDelimiter();
amount = repository[4] == 'RECEIVED' ? repository[3] : repository[3] * -1;
if (isUdUnit) {
amount = round(amount / balanceRatio);
finalAmount = 'ud'.tr(args: ['$amount ']);
} else {
finalAmount = '$amount $currencyName';
}
bool isMigrationTime =
startBlockchainInitialized && date.compareTo(startBlockchainTime) < 0;
return {
'finalAmount': finalAmount,
'isMigrationTime': isMigrationTime,
'dateDelimiter': dateDelimiter,
'dateForm': dateForm,
};
}
int weekNumber(DateTime date) {
int dayOfYear = int.parse(DateFormat("D").format(date));
return ((dayOfYear - date.weekday + 10) / 7).floor();
}
} }

View File

@ -119,6 +119,7 @@ class MyWalletsProvider with ChangeNotifier {
final avatarFolder = Directory('${directory.path}/avatars/'); final avatarFolder = Directory('${directory.path}/avatars/');
if (await avatarFolder.exists()) { if (await avatarFolder.exists()) {
await avatarFolder.delete(recursive: true); await avatarFolder.delete(recursive: true);
await avatarFolder.create();
} }
myWalletProvider.pinCode = ''; myWalletProvider.pinCode = '';

View File

@ -12,7 +12,7 @@ class SearchProvider with ChangeNotifier {
} }
Future<List<G1WalletsList>> searchAddress() async { Future<List<G1WalletsList>> searchAddress() async {
if (isAddress(searchController.text)) { if (await isAddress(searchController.text)) {
G1WalletsList wallet = G1WalletsList(address: searchController.text); G1WalletsList wallet = G1WalletsList(address: searchController.text);
return [wallet]; return [wallet];
} else { } else {

View File

@ -6,12 +6,15 @@ import 'package:flutter/material.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/chest_data.dart'; import 'package:gecko/models/chest_data.dart';
import 'package:gecko/models/migrate_wallet_checks.dart'; import 'package:gecko/models/migrate_wallet_checks.dart';
import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/transaction_content.dart';
import 'package:gecko/models/wallet_data.dart'; import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/duniter_indexer.dart'; import 'package:gecko/providers/duniter_indexer.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';
import 'package:gecko/providers/wallet_options.dart'; import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/providers/wallets_profiles.dart'; import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/widgets/transaction_status.dart';
import 'package:pinenacl/ed25519.dart'; import 'package:pinenacl/ed25519.dart';
import 'package:polkawallet_sdk/api/apiKeyring.dart'; import 'package:polkawallet_sdk/api/apiKeyring.dart';
import 'package:polkawallet_sdk/api/types/networkParams.dart'; import 'package:polkawallet_sdk/api/types/networkParams.dart';
@ -36,8 +39,8 @@ class SubstrateSdk with ChangeNotifier {
bool importIsLoading = false; bool importIsLoading = false;
int blocNumber = 0; int blocNumber = 0;
bool isLoadingEndpoint = false; bool isLoadingEndpoint = false;
Map transactionStatus = {}; Map<String, TransactionContent> transactionStatus = {};
static const int initSs58 = 42; final int initSs58 = 42;
Map<String, int> currencyParameters = {}; Map<String, int> currencyParameters = {};
final csSalt = TextEditingController(); final csSalt = TextEditingController();
final csPassword = TextEditingController(); final csPassword = TextEditingController();
@ -53,21 +56,35 @@ class SubstrateSdk with ChangeNotifier {
////////// 1: API METHODS /////////// ////////// 1: API METHODS ///////////
///////////////////////////////////// /////////////////////////////////////
Future<String> _executeCall(String currentTransactionId, TxInfoData txInfo, Map<String, TransactionStatus> statusMap = {
'sending': TransactionStatus.sending,
'Ready': TransactionStatus.propagation,
'Broadcast': TransactionStatus.validating,
'Finalized': TransactionStatus.finalized
};
Future _executeCall(TransactionContent transcationContent, TxInfoData txInfo,
txOptions, String password, txOptions, String password,
[String? rawParams]) async { [String? rawParams]) async {
final walletOptions = final walletOptions =
Provider.of<WalletOptionsProvider>(homeContext, listen: false); Provider.of<WalletOptionsProvider>(homeContext, listen: false);
final walletProfiles = final walletProfiles =
Provider.of<WalletsProfilesProvider>(homeContext, listen: false); Provider.of<WalletsProfilesProvider>(homeContext, listen: false);
transactionStatus.putIfAbsent(currentTransactionId, () => 'sending'); final currentTransactionId = transcationContent.transactionId;
transactionStatus.putIfAbsent(
currentTransactionId, () => transcationContent);
notifyListeners(); notifyListeners();
try { try {
final hash = await sdk.api.tx.signAndSend(txInfo, txOptions, password, final hash = await sdk.api.tx.signAndSend(txInfo, txOptions, password,
rawParam: rawParams, onStatusChange: (p0) { rawParam: rawParams, onStatusChange: (newStatus) {
transactionStatus.update(currentTransactionId, (_) => p0, transactionStatus.update(currentTransactionId, (trans) {
ifAbsent: () => p0); trans.status = statusMap[newStatus]!;
return trans;
}, ifAbsent: () {
transcationContent.status = statusMap[newStatus]!;
return transcationContent;
});
notifyListeners(); notifyListeners();
}).timeout( }).timeout(
const Duration(seconds: 18), const Duration(seconds: 18),
@ -76,25 +93,47 @@ class SubstrateSdk with ChangeNotifier {
log.d(hash); log.d(hash);
if (hash.isEmpty) { if (hash.isEmpty) {
transactionStatus.update( transactionStatus.update(
currentTransactionId, (_) => 'Exception: timeout'); currentTransactionId,
(trans) {
trans.status = TransactionStatus.timeout;
return trans;
},
ifAbsent: () {
transcationContent.status = TransactionStatus.timeout;
return transcationContent;
},
);
notifyListeners(); notifyListeners();
return 'Exception: timeout';
} else { } else {
// Success ! // Success !
transactionStatus.update(currentTransactionId, (_) => hash.toString(), transactionStatus.update(currentTransactionId, (trans) {
ifAbsent: () => hash.toString()); trans.status = TransactionStatus.success;
return trans;
}, ifAbsent: () {
transcationContent.status = TransactionStatus.success;
return transcationContent;
});
notifyListeners(); notifyListeners();
walletOptions.reload(); walletOptions.reload();
walletProfiles.reload(); walletProfiles.reload();
return hash.toString();
} }
} catch (e) { } catch (e) {
transactionStatus.update(currentTransactionId, (_) => e.toString(), transactionStatus.update(
ifAbsent: () => e.toString()); currentTransactionId,
(trans) {
trans.status = TransactionStatus.failed;
trans.error = e.toString();
return trans;
},
ifAbsent: () {
transcationContent.status = TransactionStatus.failed;
transcationContent.error = e.toString();
return transcationContent;
},
);
notifyListeners(); notifyListeners();
return e.toString();
} }
transactionStatus.remove(currentTransactionId);
} }
Future _getStorage(String call) async { Future _getStorage(String call) async {
@ -603,6 +642,8 @@ class SubstrateSdk with ChangeNotifier {
final homeProvider = Provider.of<HomeProvider>(homeContext, listen: false); final homeProvider = Provider.of<HomeProvider>(homeContext, listen: false);
final myWalletProvider = final myWalletProvider =
Provider.of<MyWalletsProvider>(homeContext, listen: false); Provider.of<MyWalletsProvider>(homeContext, listen: false);
final duniterIndexer =
Provider.of<DuniterIndexer>(homeContext, listen: false);
homeProvider.changeMessage("connectionPending".tr(), 0); homeProvider.changeMessage("connectionPending".tr(), 0);
@ -647,7 +688,7 @@ class SubstrateSdk with ChangeNotifier {
await initCurrencyParameters(); await initCurrencyParameters();
// Indexer Blockchain start // Indexer Blockchain start
getBlockStart(); duniterIndexer.getBlockStart();
notifyListeners(); notifyListeners();
homeProvider.changeMessage( homeProvider.changeMessage(
@ -889,7 +930,6 @@ class SubstrateSdk with ChangeNotifier {
Future<MigrateWalletChecks> getBalanceAndIdtyStatus( Future<MigrateWalletChecks> getBalanceAndIdtyStatus(
String fromAddress, String toAddress) async { String fromAddress, String toAddress) async {
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
bool canValidate = false; bool canValidate = false;
String validationStatus = ''; String validationStatus = '';
@ -907,7 +947,9 @@ class SubstrateSdk with ChangeNotifier {
final isSmithData = await isSmith(fromAddress); final isSmithData = await isSmith(fromAddress);
// Check conditions to set 'canValidate' and 'validationStatus' // Check conditions to set 'canValidate' and 'validationStatus'
if (transferableBalance != 0 && !fromHasConsumer) { if (transferableBalance != 0 &&
!fromHasConsumer &&
await isAddress(toAddress)) {
canValidate = true; canValidate = true;
} else if (toIdtyStatus != IdtyStatus.none && } else if (toIdtyStatus != IdtyStatus.none &&
fromIdtyStatus != IdtyStatus.none) { fromIdtyStatus != IdtyStatus.none) {
@ -920,14 +962,9 @@ class SubstrateSdk with ChangeNotifier {
validationStatus = 'thisAccountIsEmpty'.tr(); validationStatus = 'thisAccountIsEmpty'.tr();
} }
if (sub.g1V1NewAddress == '') {
validationStatus = '';
}
return MigrateWalletChecks( return MigrateWalletChecks(
balance: fromBalance, balance: fromBalance,
idtyStatus: toIdtyStatus, idtyStatus: toIdtyStatus,
isSmith: isSmithData,
validationStatus: validationStatus, validationStatus: validationStatus,
canValidate: canValidate, canValidate: canValidate,
); );
@ -988,7 +1025,14 @@ class SubstrateSdk with ChangeNotifier {
} }
final transactionId = const Uuid().v4(); final transactionId = const Uuid().v4();
_executeCall(transactionId, txInfo, txOptions, password, rawParams); final transactionContent = TransactionContent(
transactionId: transactionId,
status: TransactionStatus.sending,
from: fromAddress,
to: destAddress,
amount: amount,
);
_executeCall(transactionContent, txInfo, txOptions, password, rawParams);
return transactionId; return transactionId;
} }
@ -1056,7 +1100,14 @@ class SubstrateSdk with ChangeNotifier {
log.d('Cert action: ${txInfo.call!}'); log.d('Cert action: ${txInfo.call!}');
final transactionId = const Uuid().v4(); final transactionId = const Uuid().v4();
_executeCall(transactionId, txInfo, txOptions, password, rawParams); final transactionContent = TransactionContent(
transactionId: const Uuid().v4(),
status: TransactionStatus.sending,
from: fromAddress,
to: destAddress,
amount: -1,
);
_executeCall(transactionContent, txInfo, txOptions, password, rawParams);
return transactionId; return transactionId;
} }
@ -1072,8 +1123,14 @@ class SubstrateSdk with ChangeNotifier {
final txOptions = [name]; final txOptions = [name];
final transactionId = const Uuid().v4(); final transactionId = const Uuid().v4();
final transactionContent = TransactionContent(
_executeCall(transactionId, txInfo, txOptions, password); transactionId: const Uuid().v4(),
status: TransactionStatus.sending,
from: fromAddress,
to: fromAddress,
amount: -1,
);
_executeCall(transactionContent, txInfo, txOptions, password);
return transactionId; return transactionId;
} }
@ -1143,7 +1200,15 @@ newKeySig: $newKeySigType""");
} }
final transactionId = const Uuid().v4(); final transactionId = const Uuid().v4();
_executeCall(transactionId, txInfo, txOptions, fromPassword, rawParams); final transactionContent = TransactionContent(
transactionId: const Uuid().v4(),
status: TransactionStatus.sending,
from: fromAddress,
to: fromAddress,
amount: -1,
);
_executeCall(
transactionContent, txInfo, txOptions, fromPassword, rawParams);
return transactionId; return transactionId;
} }
@ -1169,7 +1234,14 @@ newKeySig: $newKeySigType""");
final txOptions = [idtyIndex, address, revocationSigTyped]; final txOptions = [idtyIndex, address, revocationSigTyped];
final transactionId = const Uuid().v4(); final transactionId = const Uuid().v4();
_executeCall(transactionId, txInfo, txOptions, password); final transactionContent = TransactionContent(
transactionId: const Uuid().v4(),
status: TransactionStatus.sending,
from: address,
to: address,
amount: -1,
);
_executeCall(transactionContent, txInfo, txOptions, password);
return transactionId; return transactionId;
} }
@ -1244,10 +1316,6 @@ newKeySig: $newKeySigType""");
void reload() { void reload() {
notifyListeners(); notifyListeners();
} }
void resetTransactionStatus() {
transactionStatus.clear();
}
} }
//////////////////////////////////////////// ////////////////////////////////////////////
@ -1267,7 +1335,7 @@ void snackNode(bool isConnected) {
final snackBar = SnackBar( final snackBar = SnackBar(
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
content: Text(message, style: const TextStyle(fontSize: 16)), content: Text(message, style: scaledTextStyle(fontSize: 14)),
duration: const Duration(seconds: 4)); duration: const Duration(seconds: 4));
ScaffoldMessenger.of(homeContext).showSnackBar(snackBar); ScaffoldMessenger.of(homeContext).showSnackBar(snackBar);
} }

View File

@ -5,26 +5,20 @@ import 'package:flutter/material.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/queries_datapod.dart'; import 'package:gecko/models/queries_datapod.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class V2sDatapodProvider with ChangeNotifier { class V2sDatapodProvider with ChangeNotifier {
late GraphQLClient datapodClient;
Future<QueryResult> _execQuery( Future<QueryResult> _execQuery(
String query, Map<String, dynamic> variables) async { String query, Map<String, dynamic> variables) async {
final httpLink = HttpLink('$datapodEndpoint/v1/graphql');
final GraphQLClient client = GraphQLClient(
cache: GraphQLCache(),
link: httpLink,
);
final QueryOptions options = final QueryOptions options =
QueryOptions(document: gql(query), variables: variables); QueryOptions(document: gql(query), variables: variables);
return await client.query(options); return await datapodClient.query(options);
} }
Future<bool> updateProfile( Future<bool> updateProfile(
@ -36,9 +30,6 @@ class V2sDatapodProvider with ChangeNotifier {
List<Map<String, String>>? socials, List<Map<String, String>>? socials,
Map<String, double>? geoloc}) async { Map<String, double>? geoloc}) async {
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false); final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
final myWallets =
Provider.of<MyWalletsProvider>(homeContext, listen: false);
final walletData = myWallets.getWalletDataByAddress(address);
final messageToSign = jsonEncode({ final messageToSign = jsonEncode({
'address': address, 'address': address,
@ -70,9 +61,6 @@ class V2sDatapodProvider with ChangeNotifier {
return false; return false;
} }
log.d(result.data!['updateProfile']['message']); log.d(result.data!['updateProfile']['message']);
walletData!.profileUpdatedTime = DateTime.now();
walletBox.put(address, walletData);
return true; return true;
} }
@ -145,8 +133,8 @@ class V2sDatapodProvider with ChangeNotifier {
return profileDate; return profileDate;
} }
Future<Image> getAvatar(String address, Future<Image> getRemoteAvatar(String address,
{double size = 20, bool saveOnDisk = false, String? uuid}) async { {double size = 20, String? uuid}) async {
final variables = <String, dynamic>{ final variables = <String, dynamic>{
'address': address, 'address': address,
}; };
@ -164,12 +152,8 @@ class V2sDatapodProvider with ChangeNotifier {
final sanitizedAvatar64 = final sanitizedAvatar64 =
avatar64.replaceAll('\n', '').replaceAll('\r', '').replaceAll(' ', ''); avatar64.replaceAll('\n', '').replaceAll('\r', '').replaceAll(' ', '');
if (saveOnDisk) { log.d('We save avatar for $address');
log.d('We save avatar for $address'); await saveAvatar(address, sanitizedAvatar64, uuid);
await saveAvatar(address, sanitizedAvatar64, uuid);
} else {
await cacheAvatar(address, sanitizedAvatar64);
}
return Image.memory( return Image.memory(
base64.decode(sanitizedAvatar64), base64.decode(sanitizedAvatar64),
@ -185,16 +169,26 @@ class V2sDatapodProvider with ChangeNotifier {
} }
Future<File> cacheAvatar(String address, String data) async { Future<File> cacheAvatar(String address, String data) async {
final file = File('${avatarsCacheDirectory.path}/$address'); final uuid = const Uuid().v4();
return await file.writeAsBytes(base64.decode(data)); final tempFile = File('${avatarsCacheDirectory.path}/$uuid$address');
final targetFile = File('${avatarsCacheDirectory.path}/$address');
try {
// Write to a temporary file first to prevent data race
await tempFile.writeAsBytes(base64.decode(data));
log.d('Caching avatar of $address');
return await tempFile.rename(targetFile.path);
} catch (e) {
log.e("An error occurred while caching avatar: $e");
rethrow;
}
} }
Image getAvatarLocal(String address, double size) { Image getAvatarLocal(String address) {
final avatarFile = File('${avatarsCacheDirectory.path}/$address'); final avatarFile = File('${avatarsCacheDirectory.path}/$address');
return Image.file( return Image.file(
avatarFile, avatarFile,
height: size, fit: BoxFit.cover,
fit: BoxFit.fitWidth,
); );
} }
@ -207,13 +201,12 @@ class V2sDatapodProvider with ChangeNotifier {
} }
} }
Future deleteAvatarsDirectory() async { Future deleteAvatarsDirectory() async {
if (await avatarsDirectory.exists()) { if (await avatarsDirectory.exists()) {
await avatarsDirectory.delete(recursive: true); await avatarsDirectory.delete(recursive: true);
} }
} }
reload() { reload() {
notifyListeners(); notifyListeners();
} }

View File

@ -18,6 +18,7 @@ import 'package:gecko/screens/transaction_in_progress.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:image_cropper/image_cropper.dart'; import 'package:image_cropper/image_cropper.dart';
import 'package:uuid/uuid.dart';
class WalletOptionsProvider with ChangeNotifier { class WalletOptionsProvider with ChangeNotifier {
final address = TextEditingController(); final address = TextEditingController();
@ -116,15 +117,31 @@ class WalletOptionsProvider with ChangeNotifier {
], ],
); );
final newPath = "${avatarsDirectory.path}/${address.text}"; final avatarUuid = const Uuid().v4();
final newPath = "${avatarsDirectory.path}/${address.text}-$avatarUuid";
if (croppedFile != null) { if (croppedFile == null) {
await File(croppedFile.path).rename(newPath);
} else {
log.w('No image selected.'); log.w('No image selected.');
return ''; return '';
} }
await File(croppedFile.path).rename(newPath);
final walletData =
MyWalletsProvider().getWalletDataByAddress(address.text);
if (walletData!.imageCustomPath != null) {
final avatarFile = File(walletData.imageCustomPath!);
await avatarFile.delete();
}
walletData.profileUpdatedTime = DateTime.now();
walletData.imageCustomPath = newPath;
await walletBox.put(address.text, walletData);
notifyListeners();
datapod.setAvatar(address.text, newPath); datapod.setAvatar(address.text, newPath);
return newPath; return newPath;
} else { } else {
log.w('No image selected.'); log.w('No image selected.');
@ -139,6 +156,7 @@ class WalletOptionsProvider with ChangeNotifier {
Provider.of<WalletOptionsProvider>(context, listen: false); Provider.of<WalletOptionsProvider>(context, listen: false);
final myWalletProvider = final myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false); Provider.of<MyWalletsProvider>(context, listen: false);
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
bool canValidate = false; bool canValidate = false;
bool idtyExist = false; bool idtyExist = false;
@ -151,7 +169,7 @@ class WalletOptionsProvider with ChangeNotifier {
title: Text( title: Text(
'confirmYourIdentity'.tr(), 'confirmYourIdentity'.tr(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
), ),
content: SizedBox( content: SizedBox(
height: 100, height: 100,
@ -160,9 +178,9 @@ class WalletOptionsProvider with ChangeNotifier {
TextField( TextField(
key: keyEnterIdentityUsername, key: keyEnterIdentityUsername,
onChanged: (_) async { onChanged: (_) async {
idtyExist = await isIdtyExist(idtyName.text); idtyExist = await duniterIndexer.isIdtyExist(idtyName.text);
canValidate = !idtyExist && canValidate = !idtyExist &&
!await isIdtyExist(idtyName.text) && !await duniterIndexer.isIdtyExist(idtyName.text) &&
idtyName.text.length >= 2 && idtyName.text.length >= 2 &&
idtyName.text.length <= 32; idtyName.text.length <= 32;
@ -176,7 +194,7 @@ class WalletOptionsProvider with ChangeNotifier {
textAlign: TextAlign.center, textAlign: TextAlign.center,
autofocus: true, autofocus: true,
controller: idtyName, controller: idtyName,
style: const TextStyle(fontSize: 19), style: const TextStyle(fontSize: 17),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Consumer<WalletOptionsProvider>(builder: (context, wOptions, _) { Consumer<WalletOptionsProvider>(builder: (context, wOptions, _) {

View File

@ -6,10 +6,12 @@ import 'package:flutter/material.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/g1_wallets_list.dart'; import 'package:gecko/models/g1_wallets_list.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/screens/wallet_view.dart'; import 'package:gecko/screens/wallet_view.dart';
import 'package:jdenticon_dart/jdenticon_dart.dart'; import 'package:jdenticon_dart/jdenticon_dart.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:barcode_scan2/barcode_scan2.dart';
import 'package:provider/provider.dart';
class WalletsProfilesProvider with ChangeNotifier { class WalletsProfilesProvider with ChangeNotifier {
WalletsProfilesProvider(this.address); WalletsProfilesProvider(this.address);
@ -35,7 +37,7 @@ class WalletsProfilesProvider with ChangeNotifier {
log.e("BarcodeScanner ERR: $e"); log.e("BarcodeScanner ERR: $e");
return 'false'; return 'false';
} }
if (isAddress(barcode.rawContent)) { if (await isAddress(barcode.rawContent)) {
address = barcode.rawContent; address = barcode.rawContent;
Navigator.popUntil( Navigator.popUntil(
context, context,
@ -93,24 +95,30 @@ class WalletsProfilesProvider with ChangeNotifier {
} }
} }
bool isAddress(address) { // bool isAddress(address) {
final RegExp regExp = RegExp( // final RegExp regExp = RegExp(
r'^[a-zA-Z0-9]+$', // r'^[a-zA-Z0-9]+$',
caseSensitive: false, // caseSensitive: false,
multiLine: false, // multiLine: false,
); // );
if (regExp.hasMatch(address) == true && // if (regExp.hasMatch(address) == true &&
address.length > 45 && // address.length > 45 &&
address.length < 52) { // address.length < 52) {
return true; // return true;
} else { // } else {
return false; // return false;
} // }
// }
Future<bool> isAddress(String address) async {
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
return await sub.sdk.api.account.checkAddressFormat(address, sub.initSs58) ??
false;
} }
snackMessage(context, snackMessage(context,
{required String message, int duration = 2, double fontSize = 16}) { {required String message, int duration = 2, double fontSize = 14}) {
final snackBar = SnackBar( final snackBar = SnackBar(
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
padding: EdgeInsets.all(scaleSize(19)), padding: EdgeInsets.all(scaleSize(19)),
@ -124,7 +132,7 @@ snackCopyKey(context) {
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
padding: EdgeInsets.all(scaleSize(19)), padding: EdgeInsets.all(scaleSize(19)),
content: Text("thisAddressHasBeenCopiedToClipboard".tr(), content: Text("thisAddressHasBeenCopiedToClipboard".tr(),
style: scaledTextStyle(fontSize: 16)), style: scaledTextStyle(fontSize: 14)),
duration: const Duration(seconds: 2)); duration: const Duration(seconds: 2));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
@ -134,7 +142,7 @@ snackCopySeed(context) {
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
padding: EdgeInsets.all(scaleSize(19)), padding: EdgeInsets.all(scaleSize(19)),
content: Text("thisMnemonicHasBeenCopiedToClipboard".tr(), content: Text("thisMnemonicHasBeenCopiedToClipboard".tr(),
style: scaledTextStyle(fontSize: 16)), style: scaledTextStyle(fontSize: 14)),
duration: const Duration(seconds: 4)); duration: const Duration(seconds: 4));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }

View File

@ -36,8 +36,7 @@ class _ActivityScreenState extends State<ActivityScreen> {
return PopScope( return PopScope(
onPopInvoked: (_) { onPopInvoked: (_) {
duniterIndexer.fetchMoreCursor = duniterIndexer.refetch = duniterIndexer.transBC = null;
duniterIndexer.pageInfo = duniterIndexer.transBC = null;
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
@ -45,7 +44,7 @@ class _ActivityScreenState extends State<ActivityScreen> {
toolbarHeight: scaleSize(57), toolbarHeight: scaleSize(57),
title: Text( title: Text(
'accountActivity'.tr(), 'accountActivity'.tr(),
style: scaledTextStyle(fontSize: 20), style: scaledTextStyle(fontSize: 18),
), ),
), ),
bottomNavigationBar: const GeckoBottomAppBar(), bottomNavigationBar: const GeckoBottomAppBar(),

View File

@ -51,7 +51,7 @@ class CertificationsScreen extends StatelessWidget {
CertsCounter(address: address) CertsCounter(address: address)
]), ]),
content: CertsList( content: CertsList(
address: address, direction: CertDirection.sent), address: address, direction: CertDirection.received),
contentHorizontalPadding: 0, contentHorizontalPadding: 0,
contentBorderWidth: 1, contentBorderWidth: 1,
), ),

View File

@ -35,8 +35,8 @@ class DebugScreen extends StatelessWidget {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
SizedBox( SizedBox(
height: 60, height: 50,
width: 250, width: 210,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
foregroundColor: Colors.white, foregroundColor: Colors.white,
@ -47,7 +47,7 @@ class DebugScreen extends StatelessWidget {
child: const Text( child: const Text(
'Spawn a bloc', 'Spawn a bloc',
style: TextStyle( style: TextStyle(
fontSize: 20, fontWeight: FontWeight.w600), fontSize: 17, fontWeight: FontWeight.w600),
), ),
), ),
), ),

View File

@ -20,6 +20,7 @@ import 'package:gecko/screens/myWallets/restore_chest.dart';
import 'package:gecko/screens/onBoarding/1.dart'; import 'package:gecko/screens/onBoarding/1.dart';
import 'package:gecko/widgets/drawer.dart'; import 'package:gecko/widgets/drawer.dart';
import 'package:gecko/widgets/buttons/home_buttons.dart'; import 'package:gecko/widgets/buttons/home_buttons.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -76,7 +77,25 @@ class _HomeScreenState extends State<HomeScreen> {
homeProvider.isWalletBoxInit = true; homeProvider.isWalletBoxInit = true;
myWalletProvider.reload(); myWalletProvider.reload();
duniterIndexer.getValidIndexerEndpoint(); duniterIndexer.getValidIndexerEndpoint().then((validIndexerEndpoint) {
final wsLinkIndexer = WebSocketLink(
'wss://$validIndexerEndpoint/v1/graphql',
);
final wsLinkDatapod = WebSocketLink(
'wss://$datapodEndpoint/v1/graphql',
);
duniterIndexer.indexerClient = GraphQLClient(
cache: GraphQLCache(),
link: wsLinkIndexer,
);
datapod.datapodClient = GraphQLClient(
cache: GraphQLCache(),
link: wsLinkDatapod,
);
});
await homeProvider.getValidEndpoints(); await homeProvider.getValidEndpoints();
if (configBox.get('isCacheChecked') == null) { if (configBox.get('isCacheChecked') == null) {
@ -101,7 +120,6 @@ class _HomeScreenState extends State<HomeScreen> {
} }
}); });
} }
// _duniterIndexer.checkIndexerEndpointBackground();
}); });
super.initState(); super.initState();
} }

View File

@ -4,11 +4,11 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:durt/durt.dart'; import 'package:durt/durt.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/wallet_data.dart'; import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/my_wallets.dart'; import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/screens/myWallets/unlocking_wallet.dart'; import 'package:gecko/screens/myWallets/unlocking_wallet.dart';
import 'package:gecko/widgets/commons/top_appbar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class ChangePinScreen extends StatefulWidget with ChangeNotifier { class ChangePinScreen extends StatefulWidget with ChangeNotifier {
@ -45,11 +45,7 @@ class _ChangePinScreenState extends State<ChangePinScreen> {
}, },
child: Scaffold( child: Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: AppBar( appBar: GeckoAppBar(widget.walletName!),
elevation: 1,
toolbarHeight: scaleSize(57),
title: Text(widget.walletName!),
),
body: Center( body: Center(
child: SafeArea( child: SafeArea(
child: Column(children: <Widget>[ child: Column(children: <Widget>[

View File

@ -91,9 +91,10 @@ class ImportG1v1 extends StatelessWidget {
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
controller: sub.csSalt, controller: sub.csSalt,
obscureText: !sub.isCesiumIDVisible, obscureText: !sub.isCesiumIDVisible,
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 14),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'enterCesiumId'.tr(), hintText: 'enterCesiumId'.tr(),
hintStyle: scaledTextStyle(fontSize: 14),
suffixIcon: IconButton( suffixIcon: IconButton(
key: keyCesiumIdVisible, key: keyCesiumIdVisible,
icon: Icon( icon: Icon(
@ -132,9 +133,10 @@ class ImportG1v1 extends StatelessWidget {
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
controller: sub.csPassword, controller: sub.csPassword,
obscureText: !sub.isCesiumIDVisible, obscureText: !sub.isCesiumIDVisible,
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 14),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'enterCesiumPassword'.tr(), hintText: 'enterCesiumPassword'.tr(),
hintStyle: scaledTextStyle(fontSize: 14),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
sub.isCesiumIDVisible sub.isCesiumIDVisible
@ -167,7 +169,7 @@ class ImportG1v1 extends StatelessWidget {
child: Text( child: Text(
'v1: ${getShortPubkey(sub.g1V1OldPubkey)}', 'v1: ${getShortPubkey(sub.g1V1OldPubkey)}',
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 17, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontFamily: 'Monospace'), fontFamily: 'Monospace'),
), ),
@ -183,7 +185,7 @@ class ImportG1v1 extends StatelessWidget {
child: Text( child: Text(
'v2: ${getShortPubkey(sub.g1V1NewAddress)}', 'v2: ${getShortPubkey(sub.g1V1NewAddress)}',
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 17, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontFamily: 'Monospace'), fontFamily: 'Monospace'),
), ),
@ -212,7 +214,7 @@ class ImportG1v1 extends StatelessWidget {
ScaledSizedBox(height: 20), ScaledSizedBox(height: 20),
Text( Text(
'migrateToThisWallet'.tr(), 'migrateToThisWallet'.tr(),
style: scaledTextStyle(fontSize: 17), style: scaledTextStyle(fontSize: 16),
), ),
ScaledSizedBox(height: 5), ScaledSizedBox(height: 5),
DropdownButtonHideUnderline( DropdownButtonHideUnderline(
@ -226,7 +228,7 @@ class ImportG1v1 extends StatelessWidget {
value: wallet, value: wallet,
child: Text( child: Text(
wallet.name!, wallet.name!,
style: scaledTextStyle(fontSize: 17), style: scaledTextStyle(fontSize: 16),
), ),
); );
}).toList(), }).toList(),
@ -292,7 +294,7 @@ class ImportG1v1 extends StatelessWidget {
child: Text( child: Text(
'migrateAccount'.tr(), 'migrateAccount'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontWeight: FontWeight.w600), fontSize: 19, fontWeight: FontWeight.w600),
), ),
), ),
), ),
@ -301,7 +303,7 @@ class ImportG1v1 extends StatelessWidget {
statusData.validationStatus, statusData.validationStatus,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 14, color: Colors.grey[600]), fontSize: 12, color: Colors.grey[600]),
) )
]); ]);
}); });

View File

@ -9,12 +9,15 @@ import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/wallet_data.dart'; import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/models/widgets_keys.dart'; import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/duniter_indexer.dart'; import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/providers/generate_wallets.dart';
import 'package:gecko/providers/my_wallets.dart'; import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallet_options.dart'; import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/providers/wallets_profiles.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';
import 'package:gecko/widgets/commons/top_appbar.dart'; import 'package:gecko/widgets/commons/top_appbar.dart';
import 'package:polkawallet_sdk/api/apiKeyring.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class MigrateIdentityScreen extends StatelessWidget { class MigrateIdentityScreen extends StatelessWidget {
@ -22,190 +25,251 @@ class MigrateIdentityScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// final _homeProvider = Provider.of<HomeProvider>(context);
final walletOptions = final walletOptions =
Provider.of<WalletOptionsProvider>(context, listen: false); Provider.of<WalletOptionsProvider>(context, listen: false);
final myWalletProvider = final myWalletProvider =
Provider.of<MyWalletsProvider>(context, listen: false); Provider.of<MyWalletsProvider>(context, listen: false);
final generatedWalletsProvider =
Provider.of<GenerateWalletsProvider>(context, listen: false);
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false); final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
final sub = Provider.of<SubstrateSdk>(context, listen: false);
final fromAddress = walletOptions.address.text; final fromAddress = walletOptions.address.text;
final defaultWallet = myWalletProvider.getDefaultWallet(); final newMnemonicSentence = TextEditingController();
final walletsList = myWalletProvider.listWallets.toList(); final newWalletAddress = TextEditingController();
late WalletData selectedWallet;
if (fromAddress == defaultWallet.address) {
selectedWallet =
walletsList[fromAddress == walletsList[0].address ? 1 : 0];
} else {
selectedWallet = defaultWallet;
}
final mdStyle = MarkdownStyleSheet( final mdStyle = MarkdownStyleSheet(
p: scaledTextStyle(fontSize: 17, color: Colors.black, letterSpacing: 0.3), p: scaledTextStyle(fontSize: 16, color: Colors.black, letterSpacing: 0.3),
textAlign: WrapAlignment.center, textAlign: WrapAlignment.center,
); );
final bool isUdUnit = configBox.get('isUdUnit') ?? false;
final unit = isUdUnit ? 'ud'.tr(args: ['']) : currencyName;
if (walletsList.length < 2) { var statusData = const MigrateWalletChecks.defaultValues();
return Column( var mnemonicIsValid = false;
children: [ int? matchDerivationNbr;
ScaledSizedBox(height: 80), String matchInfo = '';
Row(
mainAxisAlignment: MainAxisAlignment.center, Future scanDerivations() async {
children: [ if (!await isAddress(newWalletAddress.text) ||
Text( !await sub.isMnemonicValid(newMnemonicSentence.text) ||
'Vous devez avoir au moins 2 portefeuilles\npour effecter cette opération', !statusData.canValidate) {
style: scaledTextStyle(fontSize: 17), mnemonicIsValid = false;
) matchInfo = '';
], walletOptions.reload();
) return;
], }
log.d('Scan derivations to find a match');
//Scan root wallet
final addressData = await sub.sdk.api.keyring.addressFromMnemonic(
sub.currencyParameters['ss58']!,
cryptoType: CryptoType.sr25519,
mnemonic: newMnemonicSentence.text,
); );
if (addressData.address == newWalletAddress.text) {
matchDerivationNbr = -1;
mnemonicIsValid = true;
walletOptions.reload();
return;
}
//Scan derivations
for (int derivationNbr in [
for (var i = 0; i < generatedWalletsProvider.numberScan; i += 1) i
]) {
final addressData = await sub.sdk.api.keyring.addressFromMnemonic(
sub.currencyParameters['ss58']!,
cryptoType: CryptoType.sr25519,
mnemonic: newMnemonicSentence.text,
derivePath: '//$derivationNbr');
if (addressData.address == newWalletAddress.text) {
matchDerivationNbr = derivationNbr;
mnemonicIsValid = true;
matchInfo = "youCanMigrateThisIdentity".tr();
break;
} else {
mnemonicIsValid = false;
}
}
if (!mnemonicIsValid) {
matchInfo = "addressNotBelongToMnemonic".tr();
}
walletOptions.reload();
} }
return Scaffold( return Scaffold(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
appBar: GeckoAppBar('migrateIdentity'.tr()), appBar: GeckoAppBar('migrateIdentity'.tr()),
body: SafeArea( body: SafeArea(
child: Consumer<SubstrateSdk>(builder: (context, sub, _) { child: Column(children: <Widget>[
return FutureBuilder( const Row(children: []),
future: sub.getBalanceAndIdtyStatus( ScaledSizedBox(height: 18),
fromAddress, selectedWallet.address), ScaledSizedBox(
builder: (BuildContext context, width: 320,
AsyncSnapshot<MigrateWalletChecks> status) { child: MarkdownBody(
if (status.data == null) { data: 'areYouSureMigrateIdentity'.tr(args: [
return Column(children: [ duniterIndexer.walletNameIndexer[fromAddress] ?? '???',
ScaledSizedBox(height: 80), '${walletOptions.balanceCache[fromAddress]} $unit'
Row(mainAxisAlignment: MainAxisAlignment.center, children: [ ]),
ScaledSizedBox( styleSheet: mdStyle),
height: scaleSize(32), ),
width: scaleSize(32), ScaledSizedBox(height: 55),
child: CircularProgressIndicator( Text('migrateToThisWallet'.tr(),
color: orangeC, style: scaledTextStyle(fontSize: 16)),
strokeWidth: scaleSize(4), ScaledSizedBox(height: 5),
), ScaledSizedBox(
), width: 320,
]), child: TextField(
]); controller: newMnemonicSentence,
autofocus: true,
minLines: 2,
maxLines: 2,
style: scaledTextStyle(fontSize: 14),
decoration: InputDecoration(
icon: Image.asset(
'assets/onBoarding/phrase_de_restauration_flou.png',
width: scaleSize(30),
),
hintText: 'enterYourNewMnemonic'.tr(),
hintStyle: scaledTextStyle(fontSize: 14),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: orangeC),
),
),
onChanged: (newMnemonic) async {
await scanDerivations();
},
),
),
ScaledSizedBox(height: 5),
ScaledSizedBox(
width: 320,
child: TextField(
controller: newWalletAddress,
style: scaledTextStyle(fontSize: 14),
decoration: InputDecoration(
icon: Image.asset(
'assets/walletOptions/key.png',
height: scaleSize(30),
),
hintText: 'enterYourNewAddress'.tr(args: [currencyName]),
hintStyle: scaledTextStyle(fontSize: 14),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: orangeC),
),
),
onChanged: (newAddress) async {
if (await isAddress(newAddress)) {
statusData = await sub.getBalanceAndIdtyStatus(
fromAddress, newAddress);
await scanDerivations();
} else {
statusData = const MigrateWalletChecks.defaultValues();
matchInfo = '';
walletOptions.reload();
} }
},
),
),
const Spacer(flex: 2),
Consumer<WalletOptionsProvider>(builder: (context, _, __) {
return ScaledSizedBox(
width: 320,
height: 55,
child: ElevatedButton(
key: keyConfirm,
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
elevation: 4,
backgroundColor: orangeC,
),
onPressed: statusData.canValidate && mnemonicIsValid
? () async {
WalletData? defaultWallet =
myWalletProvider.getDefaultWallet();
final statusData = status.data!; String? pin;
final walletsList = myWalletProvider.listWallets.toList(); if (myWalletProvider.pinCode == '') {
pin = await Navigator.push(
context,
MaterialPageRoute(
builder: (homeContext) {
return UnlockingWallet(wallet: defaultWallet);
},
),
);
}
if (myWalletProvider.pinCode == '') return;
walletsList await sub.importAccount(
.removeWhere((element) => element.address == fromAddress); mnemonic: newMnemonicSentence.text,
derivePath: matchDerivationNbr == -1
? ''
: "//$matchDerivationNbr",
password: 'password');
final bool isUdUnit = configBox.get('isUdUnit') ?? false; final transactionId = await sub.migrateIdentity(
final unit = isUdUnit ? 'ud'.tr(args: ['']) : currencyName; fromAddress: fromAddress,
destAddress: newWalletAddress.text,
fromPassword: pin ?? myWalletProvider.pinCode,
destPassword: 'password',
withBalance: true,
fromBalance: statusData.balance);
return Column(children: <Widget>[ sub.deleteAccounts([newWalletAddress.text]);
const Row(children: []), Navigator.pop(context);
ScaledSizedBox(height: 18), Navigator.push(
ScaledSizedBox( context,
width: 320, MaterialPageRoute(builder: (context) {
child: MarkdownBody( return TransactionInProgress(
data: 'areYouSureMigrateIdentity'.tr(args: [ transactionId: transactionId,
duniterIndexer.walletNameIndexer[fromAddress] ?? transType: 'identityMigration',
'???', fromAddress: getShortPubkey(fromAddress),
'${statusData.balance['transferableBalance']} $unit' toAddress:
]), getShortPubkey(newWalletAddress.text));
styleSheet: mdStyle), }),
),
ScaledSizedBox(height: 55),
Text('migrateToThisWallet'.tr(),
style: scaledTextStyle(fontSize: 17)),
ScaledSizedBox(height: 5),
DropdownButtonHideUnderline(
key: keySelectWallet,
child: DropdownButton(
value: selectedWallet,
icon: const Icon(Icons.keyboard_arrow_down),
items: walletsList.map((wallet) {
return DropdownMenuItem(
key: keySelectThisWallet(wallet.address),
value: wallet,
child: Text(
wallet.name!,
style: scaledTextStyle(fontSize: 17),
),
); );
}).toList(), }
onChanged: (WalletData? newSelectedWallet) { : null,
selectedWallet = newSelectedWallet!; child: Text(
sub.reload(); 'migrateIdentity'.tr(),
}, style: scaledTextStyle(
), fontSize: 19,
), fontWeight: FontWeight.w600,
const Spacer(flex: 2), color: Colors.white),
ScaledSizedBox( ),
width: 320, ),
height: 55, );
child: ElevatedButton( }),
key: keyConfirm, Consumer<WalletOptionsProvider>(builder: (context, _, __) {
style: ElevatedButton.styleFrom( return ScaledSizedBox(
foregroundColor: Colors.white, width: 320,
elevation: 4, child: Column(
backgroundColor: orangeC, children: [
),
onPressed: statusData.canValidate
? () async {
WalletData? defaultWallet =
myWalletProvider.getDefaultWallet();
String? pin;
if (myWalletProvider.pinCode == '') {
pin = await Navigator.push(
context,
MaterialPageRoute(
builder: (homeContext) {
return UnlockingWallet(
wallet: defaultWallet);
},
),
);
}
if (myWalletProvider.pinCode == '') return;
final transactionId = await sub.migrateIdentity(
fromAddress: fromAddress,
destAddress: selectedWallet.address,
fromPassword: pin ?? myWalletProvider.pinCode,
destPassword: pin ?? myWalletProvider.pinCode,
withBalance: true,
fromBalance: statusData.balance);
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return TransactionInProgress(
transactionId: transactionId,
transType: 'identityMigration',
fromAddress: getShortPubkey(fromAddress),
toAddress: getShortPubkey(
selectedWallet.address));
}),
);
}
: null,
child: Text(
'migrateIdentity'.tr(),
style: scaledTextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white),
),
),
),
ScaledSizedBox(height: 10), ScaledSizedBox(height: 10),
Text( Text(
statusData.validationStatus, statusData.validationStatus,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style:
scaledTextStyle(fontSize: 15, color: Colors.grey[600]), scaledTextStyle(fontSize: 12, color: Colors.grey[600]),
), ),
const Spacer(), ScaledSizedBox(height: 5),
]); Text(
}); matchInfo,
}), textAlign: TextAlign.center,
style:
scaledTextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
);
}),
const Spacer(),
]),
), ),
); );
} }

View File

@ -108,7 +108,7 @@ class RestoreChest extends StatelessWidget {
child: Text( child: Text(
'restoreThisChest'.tr(), 'restoreThisChest'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontSize: 19,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.white), color: Colors.white),
), ),

View File

@ -10,7 +10,6 @@ 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/providers/wallet_options.dart'; import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/providers/wallets_profiles.dart'; import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/screens/certifications.dart'; import 'package:gecko/screens/certifications.dart';
@ -50,6 +49,24 @@ class WalletOptions extends StatelessWidget {
final isWalletNameIndexed = final isWalletNameIndexed =
duniterIndexer.walletNameIndexer[walletOptions.address.text] != null; duniterIndexer.walletNameIndexer[walletOptions.address.text] != null;
// StreamSubscription<QueryResult>? subscription;
// final stream = duniterIndexer.subscribeHistoryIssued(wallet.address);
// subscription = stream.listen((result) {
// if (result.hasException) {
// log.e(result.exception);
// } else {
// final Map transData =
// result.data?['account_by_pk']['transactions_issued'].first;
// final String receiver = transData['receiver_pubkey'];
// final double amount = transData['amount'] / 100;
// final createdAt = DateTime.parse(transData['created_at']);
// log.d('$receiver --- $amount --- $createdAt');
// subscription?.cancel();
// }
// });
return PopScope( return PopScope(
onPopInvoked: (_) { onPopInvoked: (_) {
walletOptions.isEditing = false; walletOptions.isEditing = false;
@ -69,7 +86,7 @@ class WalletOptions extends StatelessWidget {
? duniterIndexer ? duniterIndexer
.walletNameIndexer[walletOptions.address.text]! .walletNameIndexer[walletOptions.address.text]!
: wallet.name!, : wallet.name!,
style: scaledTextStyle(fontSize: 20), style: scaledTextStyle(fontSize: 19),
); );
}), }),
actions: [ actions: [
@ -259,58 +276,48 @@ class WalletOptions extends StatelessWidget {
} }
Widget avatar(WalletOptionsProvider walletProvider) { Widget avatar(WalletOptionsProvider walletProvider) {
return Consumer<V2sDatapodProvider>(builder: (context, datapod, _) { return Stack(
return Stack( children: <Widget>[
children: <Widget>[ InkWell(
InkWell( onTap: () async {
await (walletProvider.changeAvatar());
},
child: wallet.imageCustomPath == null || wallet.imageCustomPath == ''
? Image.asset(
'assets/avatars/${wallet.imageDefaultPath}',
width: scaleSize(122),
)
: Container(
width: scaleSize(122),
height: scaleSize(122),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
image: DecorationImage(
fit: BoxFit.cover,
image: FileImage(
File(wallet.imageCustomPath!),
),
),
),
),
),
Positioned(
right: 0,
top: 0,
child: InkWell(
onTap: () async { onTap: () async {
final newPath = await (walletProvider.changeAvatar()); wallet.imageCustomPath = await (walletProvider.changeAvatar());
if (newPath != '') {
wallet.imageCustomPath = newPath;
walletBox.put(wallet.key, wallet);
// Uncomment to enable Cs+ avatar storage
// CesiumPlusProvider().setAvatar(wallet.address, newPath);
}
walletProvider.reload(); walletProvider.reload();
}, },
child: child: Image.asset(
wallet.imageCustomPath == null || wallet.imageCustomPath == '' 'assets/walletOptions/camera.png',
? Image.asset( height: scaleSize(38),
'assets/avatars/${wallet.imageDefaultPath}',
width: scaleSize(122),
)
: Container(
width: scaleSize(122),
height: scaleSize(122),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
image: DecorationImage(
fit: BoxFit.cover,
image: FileImage(
File(wallet.imageCustomPath!),
),
),
),
),
),
Positioned(
right: 0,
top: 0,
child: InkWell(
onTap: () async {
wallet.imageCustomPath = await (walletProvider.changeAvatar());
walletProvider.reload();
},
child: Image.asset(
'assets/walletOptions/camera.png',
height: scaleSize(38),
),
), ),
), ),
], ),
); ],
}); );
} }
Widget confirmIdentityButton(WalletOptionsProvider walletProvider) { Widget confirmIdentityButton(WalletOptionsProvider walletProvider) {

View File

@ -44,7 +44,7 @@ class _WalletsHomeState extends State<WalletsHome> {
return Scaffold( return Scaffold(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
appBar: AppBar( appBar: AppBar(
elevation: 1, backgroundColor: yellowC,
toolbarHeight: scaleSize(57), toolbarHeight: scaleSize(57),
title: Row( title: Row(
children: [ children: [
@ -62,7 +62,6 @@ class _WalletsHomeState extends State<WalletsHome> {
), ),
], ],
), ),
backgroundColor: const Color(0xffFFD58D),
), ),
bottomNavigationBar: bottomNavigationBar:
Consumer<MyWalletsProvider>(builder: (context, _, __) { Consumer<MyWalletsProvider>(builder: (context, _, __) {
@ -161,7 +160,7 @@ class _WalletsHomeState extends State<WalletsHome> {
'explainDraggableWallet'.tr(), 'explainDraggableWallet'.tr(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontWeight: FontWeight.w500), fontSize: 17, fontWeight: FontWeight.w500),
), ),
], ],
)) ))

View File

@ -83,7 +83,7 @@ class OnboardingStepTen extends StatelessWidget {
Text( Text(
"youHaveToBeConnectedToValidateChest".tr(), "youHaveToBeConnectedToValidateChest".tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontSize: 17,
color: Colors.redAccent, color: Colors.redAccent,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),

View File

@ -63,7 +63,7 @@ class _QrCodeFullscreenState extends State<QrCodeFullscreen> {
}), }),
title: Text( title: Text(
'QR Code de ${getShortPubkey(widget.address)}', 'QR Code de ${getShortPubkey(widget.address)}',
style: scaledTextStyle(color: orangeC, fontSize: 20), style: scaledTextStyle(color: orangeC, fontSize: 18),
)), )),
body: SafeArea( body: SafeArea(
child: SizedBox.expand( child: SizedBox.expand(

View File

@ -32,7 +32,7 @@ class _SearchScreenState extends State<SearchScreen> {
final searchProvider = Provider.of<SearchProvider>(context, listen: false); final searchProvider = Provider.of<SearchProvider>(context, listen: false);
final clipboard = await Clipboard.getData('text/plain'); final clipboard = await Clipboard.getData('text/plain');
pastedAddress = clipboard?.text ?? ''; pastedAddress = clipboard?.text ?? '';
canPasteAddress = isAddress(pastedAddress); canPasteAddress = await isAddress(pastedAddress);
searchProvider.reload(); searchProvider.reload();
} }

View File

@ -9,6 +9,7 @@ import 'package:gecko/providers/settings_provider.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/widgets/commons/loading.dart'; import 'package:gecko/widgets/commons/loading.dart';
import 'package:gecko/widgets/commons/top_appbar.dart';
import 'package:polkawallet_sdk/api/types/networkParams.dart'; import 'package:polkawallet_sdk/api/types/networkParams.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -21,17 +22,12 @@ class SettingsScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
appBar: AppBar( appBar: GeckoAppBar('parameters'.tr()),
toolbarHeight: scaleSize(57),
title: Text(
'parameters'.tr(),
style: scaledTextStyle(fontSize: 21),
)),
body: Column(children: <Widget>[ body: Column(children: <Widget>[
ScaledSizedBox(height: 30), ScaledSizedBox(height: 30),
Text( Text(
'networkSettings'.tr(), 'networkSettings'.tr(),
style: scaledTextStyle(color: Colors.grey[500]!, fontSize: 20), style: scaledTextStyle(color: Colors.grey[500]!, fontSize: 19),
), ),
ScaledSizedBox(height: 20), ScaledSizedBox(height: 20),
duniterEndpointSelection(context), duniterEndpointSelection(context),
@ -40,7 +36,7 @@ class SettingsScreen extends StatelessWidget {
ScaledSizedBox(height: 35), ScaledSizedBox(height: 35),
Text( Text(
'displaySettings'.tr(), 'displaySettings'.tr(),
style: scaledTextStyle(color: Colors.grey[500]!, fontSize: 20), style: scaledTextStyle(color: Colors.grey[500]!, fontSize: 19),
), ),
ScaledSizedBox(height: 20), ScaledSizedBox(height: 20),
chooseCurrencyUnit(context), chooseCurrencyUnit(context),
@ -60,7 +56,7 @@ class SettingsScreen extends StatelessWidget {
child: Text( child: Text(
'forgetAllMyChests'.tr(), 'forgetAllMyChests'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 18, fontSize: 17,
color: const Color(0xffD80000), color: const Color(0xffD80000),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@ -87,14 +83,14 @@ class SettingsScreen extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
ScaledSizedBox(width: 12), ScaledSizedBox(width: 12),
Text('showUdAmounts'.tr(), style: scaledTextStyle(fontSize: 16)), Text('showUdAmounts'.tr(), style: scaledTextStyle(fontSize: 15)),
const Spacer(), const Spacer(),
Consumer<HomeProvider>(builder: (context, homeProvider, _) { Consumer<HomeProvider>(builder: (context, homeProvider, _) {
final bool isUdUnit = configBox.get('isUdUnit') ?? false; final bool isUdUnit = configBox.get('isUdUnit') ?? false;
return Icon( return Icon(
isUdUnit ? Icons.check_box : Icons.check_box_outline_blank, isUdUnit ? Icons.check_box : Icons.check_box_outline_blank,
color: orangeC, color: orangeC,
size: scaleSize(30), size: scaleSize(27),
); );
}), }),
ScaledSizedBox(width: 30), ScaledSizedBox(width: 30),
@ -145,7 +141,7 @@ class SettingsScreen extends StatelessWidget {
width: 55, width: 55,
child: Text( child: Text(
'currencyNode'.tr(), 'currencyNode'.tr(),
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 15),
), ),
), ),
const Spacer(), const Spacer(),
@ -165,7 +161,7 @@ class SettingsScreen extends StatelessWidget {
return DropdownButtonHideUnderline( return DropdownButtonHideUnderline(
key: keySelectDuniterNodeDropDown, key: keySelectDuniterNodeDropDown,
child: DropdownButton( child: DropdownButton(
style: scaledTextStyle(fontSize: 16, color: Colors.black), style: scaledTextStyle(fontSize: 15, color: Colors.black),
value: selectedDuniterEndpoint, value: selectedDuniterEndpoint,
icon: const Icon(Icons.keyboard_arrow_down), icon: const Icon(Icons.keyboard_arrow_down),
items: duniterBootstrapNodes items: duniterBootstrapNodes
@ -233,7 +229,7 @@ class SettingsScreen extends StatelessWidget {
key: keyCustomDuniterEndpoint, key: keyCustomDuniterEndpoint,
controller: endpointController, controller: endpointController,
autocorrect: false, autocorrect: false,
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 15),
), ),
), ),
); );
@ -297,7 +293,7 @@ class SettingsScreen extends StatelessWidget {
ScaledSizedBox(width: 5), ScaledSizedBox(width: 5),
ScaledSizedBox( ScaledSizedBox(
width: 55, width: 55,
child: Text('Indexer', style: scaledTextStyle(fontSize: 16)), child: Text('Indexer', style: scaledTextStyle(fontSize: 15)),
), ),
const Spacer(), const Spacer(),
Icon(indexerEndpoint != '' ? Icons.check : Icons.close), Icon(indexerEndpoint != '' ? Icons.check : Icons.close),
@ -307,7 +303,7 @@ class SettingsScreen extends StatelessWidget {
child: Consumer<SettingsProvider>(builder: (context, set, _) { child: Consumer<SettingsProvider>(builder: (context, set, _) {
return DropdownButtonHideUnderline( return DropdownButtonHideUnderline(
child: DropdownButton( child: DropdownButton(
style: scaledTextStyle(fontSize: 16, color: Colors.black), style: scaledTextStyle(fontSize: 15, color: Colors.black),
value: selectedIndexerEndpoint, value: selectedIndexerEndpoint,
icon: const Icon(Icons.keyboard_arrow_down), icon: const Icon(Icons.keyboard_arrow_down),
items: items:
@ -370,7 +366,7 @@ class SettingsScreen extends StatelessWidget {
child: TextField( child: TextField(
controller: indexerEndpointController, controller: indexerEndpointController,
autocorrect: false, autocorrect: false,
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 15),
), ),
), ),
); );

View File

@ -1,6 +1,7 @@
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/widgets/commons/top_appbar.dart';
class TemplateScreen extends StatelessWidget { class TemplateScreen extends StatelessWidget {
const TemplateScreen({Key? key}) : super(key: key); const TemplateScreen({Key? key}) : super(key: key);
@ -11,13 +12,12 @@ class TemplateScreen extends StatelessWidget {
return Scaffold( return Scaffold(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
appBar: AppBar( appBar: const GeckoAppBar('Template screen'),
toolbarHeight: scaleSize(57), title: const Text('Template screen')), body: SafeArea(
body: const SafeArea(
child: Column(children: <Widget>[ child: Column(children: <Widget>[
SizedBox(height: 20), ScaledSizedBox(height: 20),
Text('data'), const Text('data'),
SizedBox(height: 20), ScaledSizedBox(height: 20),
]), ]),
)); ));
} }

View File

@ -1,14 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/transaction_content.dart';
import 'package:gecko/models/widgets_keys.dart'; import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/my_wallets.dart'; import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallets_profiles.dart'; import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/widgets/transaction_status.dart';
import 'package:gecko/widgets/transaction_status_icon.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
class TransactionInProgress extends StatelessWidget { class TransactionInProgress extends StatefulWidget {
final String transactionId; final String transactionId;
final String transType; final String transType;
final String? fromAddress, toAddress, toUsername; final String? fromAddress, toAddress, toUsername;
@ -22,56 +25,69 @@ class TransactionInProgress extends StatelessWidget {
this.toUsername, this.toUsername,
}) : super(key: key); }) : super(key: key);
@override
State<TransactionInProgress> createState() => _TransactionInProgressState();
}
class _TransactionInProgressState extends State<TransactionInProgress> {
String resultText = '';
late String fromAddressFormat;
late String toAddressFormat;
late String toUsernameFormat;
late String amount;
late bool isUdUnit;
late TransactionContent txContent;
@override
void initState() {
final walletProfiles =
Provider.of<WalletsProfilesProvider>(homeContext, listen: false);
final myWalletProvider =
Provider.of<MyWalletsProvider>(homeContext, listen: false);
String defaultWalletAddress = myWalletProvider.getDefaultWallet().address;
String defaultWalletName = myWalletProvider.getDefaultWallet().name!;
String? walletDataName =
myWalletProvider.getWalletDataByAddress(widget.toAddress ?? '')?.name;
fromAddressFormat = widget.fromAddress ??
g1WalletsBox.get(defaultWalletAddress)?.username ??
defaultWalletName;
toAddressFormat = widget.toAddress ?? walletProfiles.address;
toUsernameFormat =
widget.toUsername ?? walletDataName ?? getShortPubkey(toAddressFormat);
amount = walletProfiles.payAmount.text;
isUdUnit = configBox.get('isUdUnit') ?? false;
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sub = Provider.of<SubstrateSdk>(context, listen: true); final sub = Provider.of<SubstrateSdk>(context, listen: true);
final transactionDetails = TransactionDetails( if (sub.transactionStatus.containsKey(widget.transactionId)) {
transactionId: transactionId, txContent = sub.transactionStatus[widget.transactionId]!;
fromAddress: fromAddress,
toAddress: toAddress,
toUsername: toUsername,
sub: sub,
transType: transType,
);
Widget getTransactionStatusIcon(TransactionDetails details) {
switch (details.txStatus) {
case TransactionStatus.loading:
return ScaledSizedBox(
height: 17,
width: 17,
child: const CircularProgressIndicator(
color: orangeC,
strokeWidth: 2,
),
);
case TransactionStatus.success:
return Icon(
Icons.done_all,
size: scaleSize(32),
color: Colors.greenAccent,
);
case TransactionStatus.failed:
return Icon(
Icons.close,
size: scaleSize(32),
color: Colors.redAccent,
);
case TransactionStatus.none:
default:
return const SizedBox.shrink();
}
} }
Widget buildTransactionStatus(TransactionDetails details) { if (txContent.status == TransactionStatus.success) {
resultText = 'extrinsicValidated'
.tr(args: [actionMap[widget.transType] ?? 'strangeTransaction'.tr()]);
} else if (txContent.status == TransactionStatus.failed) {
resultText = errorTransactionMap[txContent.error] ?? txContent.error!;
} else {
resultText = statusStatusMap[txContent.status] ??
'Unknown status: ${txContent.status}';
}
Widget buildTransactionStatus() {
return Column( return Column(
children: [ children: [
getTransactionStatusIcon(details), TransactionStatusIcon(txContent.status),
ScaledSizedBox(height: 7), ScaledSizedBox(height: 7),
if (details.txStatus != TransactionStatus.none) if (txContent.status != TransactionStatus.none)
Text( Text(
transactionDetails.resultText, resultText,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle(fontSize: 17), style: scaledTextStyle(fontSize: 17),
) )
@ -80,9 +96,6 @@ class TransactionInProgress extends StatelessWidget {
} }
return PopScope( return PopScope(
onPopInvoked: (_) {
sub.resetTransactionStatus();
},
child: Scaffold( child: Scaffold(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
appBar: AppBar( appBar: AppBar(
@ -94,8 +107,7 @@ class TransactionInProgress extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
'extrinsicInProgress'.tr(args: [ 'extrinsicInProgress'.tr(args: [
transactionDetails.actionMap[transType] ?? actionMap[widget.transType] ?? 'strangeTransaction'.tr()
'strangeTransaction'.tr()
]), ]),
style: scaledTextStyle(fontSize: 20), style: scaledTextStyle(fontSize: 20),
) )
@ -117,30 +129,29 @@ class TransactionInProgress extends StatelessWidget {
)), )),
child: Column(children: <Widget>[ child: Column(children: <Widget>[
ScaledSizedBox(height: 10), ScaledSizedBox(height: 10),
if (transType == 'pay') if (widget.transType == 'pay')
Text( Text(
transactionDetails.isUdUnit isUdUnit
? 'ud'.tr(args: ['${transactionDetails.amount} ']) ? 'ud'.tr(args: ['$amount '])
: '${transactionDetails.amount} $currencyName', : '$amount $currencyName',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 17, fontWeight: FontWeight.w500), fontSize: 17, fontWeight: FontWeight.w500),
), ),
if (transType == 'pay') ScaledSizedBox(height: 10), if (widget.transType == 'pay') ScaledSizedBox(height: 10),
Text( Text(
'fromMinus'.tr(), 'fromMinus'.tr(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 16),
), ),
Text( Text(
transactionDetails.fromAddress!, fromAddressFormat,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 17, fontWeight: FontWeight.w500), fontSize: 17, fontWeight: FontWeight.w500),
), ),
Visibility( Visibility(
visible: transactionDetails.fromAddress != visible: fromAddressFormat != toAddressFormat,
transactionDetails.toAddress,
child: Column( child: Column(
children: [ children: [
ScaledSizedBox(height: 10), ScaledSizedBox(height: 10),
@ -150,7 +161,7 @@ class TransactionInProgress extends StatelessWidget {
style: scaledTextStyle(fontSize: 16), style: scaledTextStyle(fontSize: 16),
), ),
Text( Text(
transactionDetails.toUsername!, toUsernameFormat,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 17, fontWeight: FontWeight.w500), fontSize: 17, fontWeight: FontWeight.w500),
@ -162,7 +173,7 @@ class TransactionInProgress extends StatelessWidget {
]), ]),
), ),
const Spacer(), const Spacer(),
buildTransactionStatus(transactionDetails), buildTransactionStatus(),
const Spacer(), const Spacer(),
Expanded( Expanded(
child: Align( child: Align(
@ -178,13 +189,12 @@ class TransactionInProgress extends StatelessWidget {
backgroundColor: orangeC, backgroundColor: orangeC,
), ),
onPressed: () { onPressed: () {
sub.resetTransactionStatus();
Navigator.pop(context); Navigator.pop(context);
}, },
child: Text( child: Text(
'close'.tr(), 'close'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontWeight: FontWeight.w600), fontSize: 19, fontWeight: FontWeight.w600),
), ),
), ),
), ),
@ -198,84 +208,3 @@ class TransactionInProgress extends StatelessWidget {
); );
} }
} }
enum TransactionStatus { loading, failed, success, none }
class TransactionDetails {
String? fromAddress, toAddress, toUsername, amount;
bool isUdUnit = false;
String resultText = '';
TransactionStatus txStatus = TransactionStatus.none;
TransactionDetails({
required transactionId,
required this.fromAddress,
required this.toAddress,
required this.toUsername,
required SubstrateSdk sub,
required String transType,
}) {
final walletProfiles =
Provider.of<WalletsProfilesProvider>(homeContext, listen: false);
final myWalletProvider =
Provider.of<MyWalletsProvider>(homeContext, listen: false);
String defaultWalletAddress = myWalletProvider.getDefaultWallet().address;
String defaultWalletName = myWalletProvider.getDefaultWallet().name!;
String? walletDataName =
myWalletProvider.getWalletDataByAddress(toAddress ?? '')?.name;
fromAddress = fromAddress ??
g1WalletsBox.get(defaultWalletAddress)?.username ??
defaultWalletName;
toAddress = toAddress ?? walletProfiles.address;
toUsername = toUsername ?? walletDataName ?? getShortPubkey(toAddress!);
amount = walletProfiles.payAmount.text;
isUdUnit = configBox.get('isUdUnit') ?? false;
if (sub.transactionStatus.containsKey(transactionId)) {
calculateTransactionStatus(
sub.transactionStatus[transactionId], transType);
}
}
void calculateTransactionStatus(String? result, String transType) {
if (result == null) {
txStatus = TransactionStatus.none;
} else if (result.contains('blockHash: ')) {
txStatus = TransactionStatus.success;
resultText = 'extrinsicValidated'
.tr(args: [actionMap[transType] ?? 'strangeTransaction'.tr()]);
} else if (result.contains('Exception: ')) {
txStatus = TransactionStatus.failed;
String exception = result.split('Exception: ')[1];
resultText = resultMap[exception] ?? exception;
} else {
txStatus = TransactionStatus.loading;
resultText = resultMap[result] ?? 'Unknown status: $result';
}
}
Map<String, String> actionMap = {
'pay': 'transaction'.tr(),
'cert': 'certification'.tr(),
'comfirmIdty': 'identityConfirm'.tr(),
'revokeIdty': 'revokeAdhesion'.tr(),
'identityMigration': 'identityMigration'.tr(),
};
Map<String, String> resultMap = {
'sending': 'sending'.tr(),
'Ready': 'propagating'.tr(),
'Broadcast': 'validating'.tr(),
'cert.NotRespectCertPeriod': '24hbetweenCerts'.tr(),
'identity.CreatorNotAllowedToCreateIdty': '24hbetweenCerts'.tr(),
'cert.CannotCertifySelf': 'canNotCertifySelf'.tr(),
'identity.IdtyNameAlreadyExist': 'nameAlreadyExist'.tr(),
'balances.KeepAlive': '2GDtoKeepAlive'.tr(),
'1010: Invalid Transaction: Inability to pay some fees , e.g. account balance too low':
'youHaveToFeedThisAccountBeforeUsing'.tr(),
'Token.FundsUnavailable': 'fundsUnavailable'.tr(),
'Exception: timeout': 'execTimeoutOver'.tr(),
};
}

View File

@ -104,16 +104,15 @@ class GeckoBottomAppBar extends StatelessWidget {
}, },
), ),
); );
if (myWalletProvider.pinCode == '') return;
Navigator.popUntil(
context, ModalRoute.withName('/'));
//FIXME: Should not have to wait 300 milliseconds when /mywallets exist in navigator...
sleep(const Duration(milliseconds: 300));
Navigator.pushNamed(context, '/mywallets');
// Navigator.pushNamedAndRemoveUntil(
// context, '/mywallets', ModalRoute.withName('/'));
} }
if (myWalletProvider.pinCode == '') return;
Navigator.popUntil(context, ModalRoute.withName('/'));
//FIXME: Should not have to wait 300 milliseconds when /mywallets exist in navigator...
sleep(const Duration(milliseconds: 300));
Navigator.pushNamed(context, '/mywallets');
// Navigator.pushNamedAndRemoveUntil(
// context, '/mywallets', ModalRoute.withName('/'));
}, },
), ),
), ),

View File

@ -79,7 +79,7 @@ class ChestOptionsButtons extends StatelessWidget {
], ],
), ),
), ),
ScaledSizedBox(height: 20), // ScaledSizedBox(height: 20),
// InkWell( // InkWell(
// key: keyChangeChest, // key: keyChangeChest,
// onTap: () { // onTap: () {

View File

@ -3,8 +3,8 @@ import 'package:gecko/globals.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/widgets_keys.dart'; import 'package:gecko/models/widgets_keys.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/screens/wallet_view.dart'; import 'package:gecko/screens/wallet_view.dart';
import 'package:gecko/widgets/datapod_avatar.dart';
class CertTile extends StatelessWidget { class CertTile extends StatelessWidget {
const CertTile({ const CertTile({
@ -28,9 +28,8 @@ class CertTile extends StatelessWidget {
key: keyTransaction(keyID++), key: keyTransaction(keyID++),
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
left: 10, right: 0, top: scaleSize(3), bottom: scaleSize(3)), left: 10, right: 0, top: scaleSize(3), bottom: scaleSize(3)),
leading: ClipOval( leading: DatapodAvatar(
child: V2sDatapodProvider().defaultAvatar(avatarSize), address: repository['address'], size: avatarSize),
),
title: Padding( title: Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
child: Text( child: Text(

View File

@ -4,8 +4,10 @@ import 'package:gecko/globals.dart';
import 'package:gecko/models/queries_indexer.dart'; import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/widgets_keys.dart'; import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/widgets/cert_tile.dart'; import 'package:gecko/widgets/cert_tile.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart';
class CertsList extends StatelessWidget { class CertsList extends StatelessWidget {
const CertsList( const CertsList(
@ -18,14 +20,11 @@ class CertsList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final indexerProvider = Provider.of<DuniterIndexer>(context, listen: false);
final screenHeight = MediaQuery.of(context).size.height; final screenHeight = MediaQuery.of(context).size.height;
final appBarHeight = AppBar().preferredSize.height; final appBarHeight = AppBar().preferredSize.height;
final windowHeight = screenHeight - appBarHeight - (isTall ? 170 : 140); final windowHeight = screenHeight - appBarHeight - (isTall ? 170 : 140);
final httpLink = HttpLink(
'$indexerEndpoint/v1/graphql',
);
late String gertCertsReq; late String gertCertsReq;
late String certFrom; late String certFrom;
@ -37,14 +36,8 @@ class CertsList extends StatelessWidget {
certFrom = 'receiver'; certFrom = 'receiver';
} }
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: httpLink,
),
);
return GraphQLProvider( return GraphQLProvider(
client: client, client: ValueNotifier(indexerProvider.indexerClient),
child: Query( child: Query(
options: QueryOptions( options: QueryOptions(
document: gql(gertCertsReq), document: gql(gertCertsReq),
@ -53,7 +46,7 @@ class CertsList extends StatelessWidget {
}, },
), ),
builder: (QueryResult result, {fetchMore, refetch}) { builder: (QueryResult result, {fetchMore, refetch}) {
if (result.isLoading && result.data == null) { if (result.isLoading || result.data == null) {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
@ -87,31 +80,39 @@ class CertsList extends StatelessWidget {
final date = DateTime.parse(cert['created_at']); final date = DateTime.parse(cert['created_at']);
final dp = DateTime(date.year, date.month, date.day); final dp = DateTime(date.year, date.month, date.day);
final dateForm = '${dp.day}-${dp.month}-${dp.year}'; final dateForm = '${dp.day}-${dp.month}-${dp.year}';
listCerts.add({
'address': issuerAddress, // Check if we have a more recent certification, we skip
'name': issuerName, if (!listCerts.any((cert) => cert['address'] == issuerAddress)) {
'date': dateForm listCerts.add({
}); 'address': issuerAddress,
'name': issuerName,
'date': dateForm
});
}
} }
// Build history list // Build history list
return SizedBox( return SizedBox(
height: windowHeight, height: windowHeight,
child: ListView( child: RefreshIndicator(
key: keyListTransactions, color: orangeC,
children: <Widget>[ onRefresh: () async => refetch!.call(),
result.data == null child: ListView(
? Column(children: <Widget>[ key: keyListTransactions,
ScaledSizedBox(height: 50), children: <Widget>[
Text( result.data == null
"noTransactionToDisplay".tr(), ? Column(children: <Widget>[
style: scaledTextStyle(fontSize: 18), ScaledSizedBox(height: 50),
) Text(
]) "noTransactionToDisplay".tr(),
: Column(children: <Widget>[ style: scaledTextStyle(fontSize: 18),
CertTile(listCerts: listCerts), )
]) ])
], : Column(children: <Widget>[
CertTile(listCerts: listCerts),
])
],
),
), ),
); );
}, },

View File

@ -112,7 +112,7 @@ Future<bool?> confirmPopupCertification(BuildContext context, String question1,
child: Text( child: Text(
"yes".tr(), "yes".tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontSize: 19,
color: const Color(0xffD80000), color: const Color(0xffD80000),
), ),
), ),
@ -124,7 +124,7 @@ Future<bool?> confirmPopupCertification(BuildContext context, String question1,
TextButton( TextButton(
child: Text( child: Text(
"no".tr(), "no".tr(),
style: scaledTextStyle(fontSize: 20), style: scaledTextStyle(fontSize: 19),
), ),
onPressed: () { onPressed: () {
Navigator.pop(context, false); Navigator.pop(context, false);

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/scale_functions.dart';
class Loading extends StatelessWidget { class Loading extends StatelessWidget {
const Loading({ const Loading({
@ -13,7 +14,7 @@ class Loading extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return ScaledSizedBox(
height: size, height: size,
width: size, width: size,
child: CircularProgressIndicator( child: CircularProgressIndicator(

View File

@ -48,7 +48,7 @@ class ContactsList extends StatelessWidget {
contentPadding: const EdgeInsets.all(5), contentPadding: const EdgeInsets.all(5),
dense: !isTall, dense: !isTall,
leading: DatapodAvatar( leading: DatapodAvatar(
address: g1Wallet.address, size: scaleSize(50)), address: g1Wallet.address, size: 47),
title: Row(children: <Widget>[ title: Row(children: <Widget>[
Text(getShortPubkey(g1Wallet.address), Text(getShortPubkey(g1Wallet.address),
style: scaledTextStyle( style: scaledTextStyle(

View File

@ -6,7 +6,6 @@ import 'package:gecko/globals.dart';
import 'package:gecko/models/queries_datapod.dart'; import 'package:gecko/models/queries_datapod.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/providers/v2s_datapod.dart'; import 'package:gecko/providers/v2s_datapod.dart';
import 'package:gecko/widgets/commons/loading.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -24,27 +23,17 @@ class DatapodAvatar extends StatelessWidget {
if (cachedImage.existsSync()) { if (cachedImage.existsSync()) {
return ScaledSizedBox( return ScaledSizedBox(
width: size, width: size,
height: size,
child: ClipOval( child: ClipOval(
child: datapod.getAvatarLocal(address, size), child: datapod.getAvatarLocal(address),
), ),
); );
} }
final httpLink = HttpLink(
'$datapodEndpoint/v1/graphql',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: httpLink,
),
);
return ScaledSizedBox( return ScaledSizedBox(
width: size, width: size,
child: GraphQLProvider( child: GraphQLProvider(
client: client, client: ValueNotifier(datapod.datapodClient),
child: Query( child: Query(
options: QueryOptions( options: QueryOptions(
document: gql(getAvatarQ), document: gql(getAvatarQ),
@ -53,9 +42,9 @@ class DatapodAvatar extends StatelessWidget {
}, },
), ),
builder: (QueryResult result, {fetchMore, refetch}) { builder: (QueryResult result, {fetchMore, refetch}) {
if (result.isLoading) { if (result.isLoading || result.data == null) {
return const Center( return Center(
child: Loading(), child: ClipOval(child: datapod.defaultAvatar(size)),
); );
} }
final String? avatar64 = final String? avatar64 =
@ -75,8 +64,7 @@ class DatapodAvatar extends StatelessWidget {
return ClipOval( return ClipOval(
child: Image.memory( child: Image.memory(
base64.decode(sanitizedAvatar64), base64.decode(sanitizedAvatar64),
height: size, fit: BoxFit.cover,
fit: BoxFit.fitWidth,
), ),
); );
}), }),

View File

@ -28,7 +28,7 @@ class HeaderProfile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final avatarSize = scaleSize(110); const double avatarSize = 110;
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false); final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
final walletOptions = final walletOptions =
Provider.of<WalletOptionsProvider>(context, listen: false); Provider.of<WalletOptionsProvider>(context, listen: false);
@ -79,30 +79,37 @@ class HeaderProfile extends StatelessWidget {
]), ]),
ScaledSizedBox(height: 15), ScaledSizedBox(height: 15),
Balance(address: address, size: 20), Balance(address: address, size: 20),
ScaledSizedBox(height: 5), ScaledSizedBox(
InkWell( height: 60,
onTap: () => sub.certsCounterCache[address] != null
? {
Navigator.push(
context,
PageNoTransit(builder: (context) {
return CertificationsScreen(
address: address,
username: duniterIndexer
.walletNameIndexer[address] ??
'');
}),
),
}
: null,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
IdentityStatus( ScaledSizedBox(height: 5),
address: address, InkWell(
isOwner: false, onTap: () => sub.certsCounterCache[address] != null
color: Colors.black), ? {
Certifications(address: address, size: 18) Navigator.push(
context,
PageNoTransit(builder: (context) {
return CertificationsScreen(
address: address,
username: duniterIndexer
.walletNameIndexer[address] ??
'');
}),
),
}
: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IdentityStatus(
address: address,
isOwner: false,
color: Colors.black),
Certifications(address: address, size: 18)
],
),
),
], ],
), ),
), ),

View File

@ -5,7 +5,9 @@ import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/widgets_keys.dart'; import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/duniter_indexer.dart'; import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/widgets/history_view.dart'; import 'package:gecko/widgets/history_view.dart';
import 'package:gecko/widgets/transaction_in_progress_tile.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -16,11 +18,11 @@ class HistoryQuery extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false); final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
final sub = Provider.of<SubstrateSdk>(context, listen: false);
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
FetchMoreOptions? opts; FetchMoreOptions? opts;
int nPage = 1;
int nRepositories = 20; int nRepositories = 20;
if (indexerEndpoint == '') { if (indexerEndpoint == '') {
@ -34,19 +36,8 @@ class HistoryQuery extends StatelessWidget {
]); ]);
} }
final httpLink = HttpLink(
'$indexerEndpoint/v1beta1/relay',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(),
link: httpLink,
),
);
return GraphQLProvider( return GraphQLProvider(
client: client, client: ValueNotifier(duniterIndexer.indexerClient),
child: Expanded( child: Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -57,11 +48,12 @@ class HistoryQuery extends StatelessWidget {
document: gql(getHistoryByAddressQ), document: gql(getHistoryByAddressQ),
variables: <String, dynamic>{ variables: <String, dynamic>{
'address': address, 'address': address,
'number': 20, 'number': nRepositories,
'cursor': null 'offset': 0
}, },
), ),
builder: (QueryResult result, {fetchMore, refetch}) { builder: (QueryResult result, {fetchMore, refetch}) {
duniterIndexer.refetch = refetch;
if (result.isLoading && result.data == null) { if (result.isLoading && result.data == null) {
return const Center( return const Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
@ -69,6 +61,7 @@ class HistoryQuery extends StatelessWidget {
), ),
); );
} }
final List transactions = result.data?["transaction"];
if (result.hasException) { if (result.hasException) {
log.e('Error Indexer: ${result.exception}'); log.e('Error Indexer: ${result.exception}');
@ -80,8 +73,7 @@ class HistoryQuery extends StatelessWidget {
style: scaledTextStyle(fontSize: 18), style: scaledTextStyle(fontSize: 18),
) )
]); ]);
} else if (result } else if (transactions.isEmpty) {
.data?['transaction_connection']?['edges'].isEmpty) {
return Column(children: <Widget>[ return Column(children: <Widget>[
ScaledSizedBox(height: 50), ScaledSizedBox(height: 50),
Text( Text(
@ -91,47 +83,57 @@ class HistoryQuery extends StatelessWidget {
]); ]);
} }
if (result.isNotLoading) { final int totalTransactions =
if (duniterIndexer.fetchMoreCursor == null) nPage = 1; result.data!["transaction_aggregate"]["aggregate"]["count"];
duniterIndexer.hasNextPage =
!(transactions.length == totalTransactions);
if (nPage <= 3) { opts = duniterIndexer.mergeQueryResult(
nRepositories = 20; transactions: transactions,
} else if (nPage <= 6) { opts: opts,
nRepositories = 40; address: address,
} else if (nPage <= 12) { nRepositories: nRepositories,
nRepositories = 80; offset: transactions.length,
} else { );
nRepositories = 120;
// Get transaction in progress if exist
String? transactionId;
for (final entry in sub.transactionStatus.entries) {
if (entry.value.from == address) {
transactionId = entry.key;
break;
} }
nPage++;
opts = duniterIndexer.mergeQueryResult(
result, opts, address, nRepositories);
} }
// Build history list // Build history list
return NotificationListener( return NotificationListener(
child: Builder( child: Builder(
builder: (context) => Expanded( builder: (context) => Expanded(
child: ListView( child: RefreshIndicator(
key: keyListTransactions, color: orangeC,
controller: scrollController, onRefresh: () async => refetch!.call(),
children: <Widget>[ child: ListView(
HistoryView( key: keyListTransactions,
result: result, controller: scrollController,
address: address, children: <Widget>[
) if (transactionId != null)
], TransactionInProgressTule(
address: address,
transactionId: transactionId),
HistoryView(
result: result,
address: address,
)
],
),
), ),
), ),
), ),
onNotification: (dynamic t) { onNotification: (dynamic t) {
if (duniterIndexer.pageInfo == null) {
duniterIndexer.reload();
}
if (t is ScrollEndNotification && if (t is ScrollEndNotification &&
scrollController.position.pixels >= scrollController.position.pixels >=
scrollController.position.maxScrollExtent * 0.7 && scrollController.position.maxScrollExtent * 0.7 &&
duniterIndexer.pageInfo!['hasNextPage'] && duniterIndexer.hasNextPage &&
result.isNotLoading) { result.isNotLoading) {
fetchMore!(opts!); 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/duniter_indexer.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/screens/wallet_view.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/page_route_no_transition.dart';
import 'package:gecko/widgets/transaction_tile.dart'; import 'package:gecko/widgets/transaction_tile.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
@ -34,13 +35,14 @@ class HistoryView extends StatelessWidget {
ScaledSizedBox(height: 50), ScaledSizedBox(height: 50),
Text( Text(
"noTransactionToDisplay".tr(), "noTransactionToDisplay".tr(),
style: scaledTextStyle(fontSize: 18), style: scaledTextStyle(fontSize: 17),
) )
]) ])
: Column(children: <Widget>[ : Column(children: <Widget>[
Column( Column(
children: duniterIndexer.transBC!.map((repository) { children: duniterIndexer.transBC!.map((repository) {
final answer = computeHistoryView(repository, address); final answer =
duniterIndexer.computeHistoryView(repository, address);
pastDelimiters.add(answer['dateDelimiter']); pastDelimiters.add(answer['dateDelimiter']);
bool isMigrationTime = false; bool isMigrationTime = false;
@ -52,23 +54,24 @@ class HistoryView extends StatelessWidget {
return Column(children: <Widget>[ return Column(children: <Widget>[
if (isMigrationTime) if (isMigrationTime)
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: scaleSize(23)), padding: EdgeInsets.only(
top: scaleSize(25), bottom: scaleSize(15)),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Image( Image(
image: const AssetImage('assets/party.png'), image: const AssetImage('assets/party.png'),
height: scaleSize(32)), height: scaleSize(31)),
Text( Text(
'blockchainStart'.tr(), 'blockchainStart'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontSize: 20,
color: Colors.blueAccent, color: Colors.blueAccent,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w400),
), ),
Image( Image(
image: const AssetImage('assets/party.png'), image: const AssetImage('assets/party.png'),
height: scaleSize(32)), height: scaleSize(31)),
], ],
), ),
), ),
@ -96,14 +99,14 @@ class HistoryView extends StatelessWidget {
context: context), context: context),
]); ]);
}).toList()), }).toList()),
if (result.isLoading && duniterIndexer.pageInfo!['hasPreviousPage']) if (result.isLoading && duniterIndexer.hasNextPage)
const Row( const Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
CircularProgressIndicator(), Loading(size: 30, stroke: 3),
], ],
), ),
if (!duniterIndexer.pageInfo!['hasNextPage'] && if (!duniterIndexer.hasNextPage &&
sub.oldOwnerKeys[address]?[0] != null) sub.oldOwnerKeys[address]?[0] != null)
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 30), padding: const EdgeInsets.symmetric(vertical: 30),
@ -127,7 +130,7 @@ class HistoryView extends StatelessWidget {
), ),
Column(children: [ Column(children: [
Text( Text(
'Identité migré:'.tr(), 'identityMigrated'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontSize: 20,
color: Colors.green[700], color: Colors.green[700],
@ -149,14 +152,22 @@ class HistoryView extends StatelessWidget {
), ),
), ),
), ),
if (!duniterIndexer.pageInfo!['hasNextPage']) if (!duniterIndexer.hasNextPage)
Column( Column(
children: <Widget>[ children: <Widget>[
ScaledSizedBox(height: 15), ScaledSizedBox(height: 15),
Text("historyStart".tr(), Row(
textAlign: TextAlign.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
style: scaledTextStyle(fontSize: 20)), children: [
ScaledSizedBox(height: 15) Icon(Icons.blur_on_outlined, size: scaleSize(31)),
Text("historyStart".tr(),
textAlign: TextAlign.center,
style: scaledTextStyle(
fontSize: 20, fontWeight: FontWeight.w300)),
Icon(Icons.blur_on_outlined, size: scaleSize(31)),
],
),
ScaledSizedBox(height: 30)
], ],
) )
]); ]);

View File

@ -5,6 +5,7 @@ import 'package:gecko/models/scale_functions.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/widgets/commons/animated_text.dart'; import 'package:gecko/widgets/commons/animated_text.dart';
import 'package:gecko/widgets/commons/loading.dart';
import 'package:gecko/widgets/name_by_address.dart'; import 'package:gecko/widgets/name_by_address.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -28,6 +29,13 @@ class IdentityStatus extends StatelessWidget {
future: sub.idtyStatus([address]), future: sub.idtyStatus([address]),
initialData: [walletData.identityStatus], initialData: [walletData.identityStatus],
builder: (context, AsyncSnapshot<List<IdtyStatus>> snapshot) { builder: (context, AsyncSnapshot<List<IdtyStatus>> snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Loading(size: 18);
} else if (snapshot.hasError || snapshot.data == null) {
log.e(snapshot.error);
return const Icon(Icons.close, color: Colors.red);
}
final resStatus = snapshot.data!.first; final resStatus = snapshot.data!.first;
walletData.identityStatus = resStatus; walletData.identityStatus = resStatus;
walletBox.put(address, walletData); walletBox.put(address, walletData);
@ -51,7 +59,7 @@ class IdentityStatus extends StatelessWidget {
} }
final Map<IdtyStatus, String> statusText = { final Map<IdtyStatus, String> statusText = {
IdtyStatus.none: 'noIdentity'.tr(), IdtyStatus.none: '',
IdtyStatus.created: 'identityCreated'.tr(), IdtyStatus.created: 'identityCreated'.tr(),
IdtyStatus.confirmed: 'identityConfirmed'.tr(), IdtyStatus.confirmed: 'identityConfirmed'.tr(),
IdtyStatus.validated: 'memberValidated'.tr(), IdtyStatus.validated: 'memberValidated'.tr(),

View File

@ -6,6 +6,7 @@ import 'package:gecko/models/queries_indexer.dart';
import 'package:gecko/models/scale_functions.dart'; import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/wallet_data.dart'; import 'package:gecko/models/wallet_data.dart';
import 'package:gecko/providers/duniter_indexer.dart'; import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/widgets/commons/loading.dart';
import 'package:gecko/widgets/wallet_name.dart'; import 'package:gecko/widgets/wallet_name.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -34,22 +35,8 @@ class NameByAddress extends StatelessWidget {
return WalletName(wallet: wallet, size: size, color: color); return WalletName(wallet: wallet, size: size, color: color);
} }
// if (g1WalletsBox.get(wallet.address)?.username != null) {
// return Text(g1WalletsBox.get(wallet.address)!.username!);
// }
final httpLink = HttpLink(
'$indexerEndpoint/v1/graphql',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: httpLink,
),
);
return GraphQLProvider( return GraphQLProvider(
client: client, client: ValueNotifier(duniterIndexer.indexerClient),
child: Query( child: Query(
options: QueryOptions( options: QueryOptions(
document: gql(getNameByAddressQ), document: gql(getNameByAddressQ),
@ -66,7 +53,7 @@ class NameByAddress extends StatelessWidget {
} }
if (result.isLoading) { if (result.isLoading) {
return const Text('Loading'); return const Loading();
} }
duniterIndexer.walletNameIndexer[wallet.address] = duniterIndexer.walletNameIndexer[wallet.address] =

View File

@ -11,6 +11,7 @@ import 'package:gecko/providers/my_wallets.dart';
import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/providers/wallet_options.dart'; import 'package:gecko/providers/wallet_options.dart';
import 'package:gecko/providers/wallets_profiles.dart'; import 'package:gecko/providers/wallets_profiles.dart';
import 'package:gecko/screens/activity.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';
import 'package:gecko/widgets/balance.dart'; import 'package:gecko/widgets/balance.dart';
@ -54,13 +55,17 @@ void paymentPopup(BuildContext context, String toAddress, String? username) {
destAddress: toAddress, destAddress: toAddress,
amount: double.parse(walletViewProvider.payAmount.text), amount: double.parse(walletViewProvider.payAmount.text),
password: myWalletProvider.pinCode); password: myWalletProvider.pinCode);
const legacyMonitor = false;
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
return TransactionInProgress( return legacyMonitor
transactionId: transactionId, ? TransactionInProgress(
toAddress: toAddress, transactionId: transactionId,
toUsername: username); toAddress: toAddress,
toUsername: username)
: ActivityScreen(address: acc.address!);
}), }),
); );
} }
@ -131,7 +136,7 @@ void paymentPopup(BuildContext context, String toAddress, String? username) {
Text( Text(
'executeATransfer'.tr(), 'executeATransfer'.tr(),
style: scaledTextStyle( style: scaledTextStyle(
fontSize: 20, fontWeight: FontWeight.w700), fontSize: 19, fontWeight: FontWeight.w700),
), ),
IconButton( IconButton(
key: keyPopButton, key: keyPopButton,

View File

@ -29,19 +29,8 @@ class SearchIdentityQuery extends StatelessWidget {
return Text('noResult'.tr()); return Text('noResult'.tr());
} }
final httpLink = HttpLink(
'$indexerEndpoint/v1/graphql',
);
final client = ValueNotifier(
GraphQLClient(
cache: GraphQLCache(
store: HiveStore()), // GraphQLCache(store: HiveStore())
link: httpLink,
),
);
return GraphQLProvider( return GraphQLProvider(
client: client, client: ValueNotifier(duniterIndexer.indexerClient),
child: Query( child: Query(
options: QueryOptions( options: QueryOptions(
document: gql(searchAddressByNameQ), document: gql(searchAddressByNameQ),
@ -74,7 +63,7 @@ class SearchIdentityQuery extends StatelessWidget {
searchProvider.resultLenght = identities.length; searchProvider.resultLenght = identities.length;
final avatarSize = scaleSize(45); const double avatarSize = 45;
return Expanded( return Expanded(
child: ListView(children: <Widget>[ child: ListView(children: <Widget>[
for (Map profile in identities) for (Map profile in identities)

View File

@ -0,0 +1,160 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gecko/globals.dart';
import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/transaction_content.dart';
import 'package:gecko/providers/duniter_indexer.dart';
import 'package:gecko/providers/substrate_sdk.dart';
import 'package:gecko/widgets/datapod_avatar.dart';
import 'package:gecko/widgets/transaction_status.dart';
import 'package:gecko/widgets/transaction_status_icon.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart';
import 'package:fade_and_translate/fade_and_translate.dart';
class TransactionInProgressTule extends StatefulWidget {
const TransactionInProgressTule(
{Key? key, required this.address, this.transactionId})
: super(key: key);
final String address;
final String? transactionId;
@override
State<TransactionInProgressTule> createState() =>
_TransactionInProgressTuleState();
}
class _TransactionInProgressTuleState extends State<TransactionInProgressTule> {
late bool isVisible;
late TransactionContent txContent;
@override
void initState() {
isVisible = true;
StreamSubscription<QueryResult>? subscription;
final sub = Provider.of<SubstrateSdk>(context, listen: false);
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
final stream = duniterIndexer.subscribeHistoryIssued(widget.address);
txContent = sub.transactionStatus[widget.transactionId]!;
subscription = stream.listen((result) {
if (result.hasException) {
log.e(result.exception);
isVisible = true;
} else {
final Map transData =
result.data?['account_by_pk']['transactions_issued'].first;
final String receiver = transData['receiver_pubkey'];
final double amount = transData['amount'] / 100;
final createdAt = DateTime.parse(transData['created_at']);
final difference = createdAt.difference(DateTime.now());
if (receiver == txContent.to &&
amount == txContent.amount &&
difference.inSeconds.abs() < 30) {
isVisible = false;
txContent.status = TransactionStatus.finalized;
sub.reload();
subscription?.cancel();
} else {
isVisible = true;
}
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
return Consumer<SubstrateSdk>(builder: (context, sub, _) {
final statusIcon =
TransactionStatusIcon(txContent.status, size: 21, stroke: 2);
String humanStatus = '';
final finalAmount = txContent.amount * -1;
if (txContent.status == TransactionStatus.success) {
humanStatus = 'extrinsicValidated'.tr(args: [actionMap['pay']!]);
} else if (txContent.status == TransactionStatus.failed) {
humanStatus = errorTransactionMap[txContent.error] ?? txContent.error!;
} else {
humanStatus = statusStatusMap[txContent.status] ??
'Unknown status: ${txContent.status}';
}
return FadeAndTranslate(
visible: isVisible,
translate: const Offset(0, -40),
delay: const Duration(seconds: 2),
duration: const Duration(milliseconds: 700),
onCompleted: () async => duniterIndexer.refetch?.call(),
child: Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: orangeC,
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 10),
Text(
'Transaction en cours',
style: scaledTextStyle(
fontSize: 20,
color: Colors.blueAccent,
fontWeight: FontWeight.w400),
),
ListTile(
key: const Key('transactionInProgress'),
contentPadding:
const EdgeInsets.symmetric(horizontal: 5, vertical: 15),
leading: DatapodAvatar(address: txContent.to, size: 50),
title: Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Text(getShortPubkey(txContent.to),
style: scaledTextStyle(
fontSize: 17, fontFamily: 'Monospace')),
),
subtitle: Row(
children: [
statusIcon,
ScaledSizedBox(width: 10),
ScaledSizedBox(
width: 160,
child: Text(
humanStatus,
style: scaledTextStyle(
fontStyle: FontStyle.italic,
color: Theme.of(context)
.textTheme
.titleLarge!
.color,
fontSize: 14),
),
),
],
),
trailing: Text("$finalAmount $currencyName",
style: scaledTextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.blue[700]),
textAlign: TextAlign.justify),
dense: !isTall,
isThreeLine: false),
],
),
),
),
);
});
}
}

View File

@ -0,0 +1,42 @@
import 'package:easy_localization/easy_localization.dart';
enum TransactionStatus {
sending,
propagation,
validating,
failed,
success,
timeout,
finalized,
none
}
Map<String, String> actionMap = {
'pay': 'transaction'.tr(),
'cert': 'certification'.tr(),
'comfirmIdty': 'identityConfirm'.tr(),
'revokeIdty': 'revokeAdhesion'.tr(),
'identityMigration': 'identityMigration'.tr(),
};
Map<TransactionStatus, String> statusStatusMap = {
TransactionStatus.none: 'noTransaction'.tr(),
TransactionStatus.sending: 'sending'.tr(),
TransactionStatus.propagation: 'propagating'.tr(),
TransactionStatus.validating: 'validating'.tr(),
TransactionStatus.success: 'extrinsicValidated'.tr(args: [actionMap['pay']!]),
TransactionStatus.finalized:
'extrinsicFinalized'.tr(args: [actionMap['pay']!]),
TransactionStatus.timeout: 'execTimeoutOver'.tr(),
};
Map<String, String> errorTransactionMap = {
'cert.NotRespectCertPeriod': '24hbetweenCerts'.tr(),
'identity.CreatorNotAllowedToCreateIdty': '24hbetweenCerts'.tr(),
'cert.CannotCertifySelf': 'canNotCertifySelf'.tr(),
'identity.IdtyNameAlreadyExist': 'nameAlreadyExist'.tr(),
'balances.KeepAlive': '2GDtoKeepAlive'.tr(),
'1010: Invalid Transaction: Inability to pay some fees , e.g. account balance too low':
'youHaveToFeedThisAccountBeforeUsing'.tr(),
'Token.FundsUnavailable': 'fundsUnavailable'.tr(),
};

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/widgets/commons/loading.dart';
import 'package:gecko/widgets/transaction_status.dart';
class TransactionStatusIcon extends StatelessWidget {
const TransactionStatusIcon(this.status,
{Key? key, this.size = 32, this.stroke = 3})
: super(key: key);
final TransactionStatus status;
final double size;
final double stroke;
@override
Widget build(BuildContext context) {
switch (status) {
case TransactionStatus.sending:
case TransactionStatus.propagation:
case TransactionStatus.validating:
return Loading(size: size, stroke: stroke);
case TransactionStatus.success:
return Icon(
Icons.done,
size: scaleSize(size),
color: Colors.green,
);
case TransactionStatus.finalized:
return Icon(
Icons.done_all,
size: scaleSize(size),
color: Colors.green,
);
case TransactionStatus.failed:
case TransactionStatus.timeout:
return Icon(
Icons.close,
size: scaleSize(size),
color: Colors.red,
);
case TransactionStatus.none:
default:
return ScaledSizedBox(height: size, width: size);
}
}
}

View File

@ -4,8 +4,8 @@ import 'package:gecko/models/scale_functions.dart';
import 'package:gecko/models/widgets_keys.dart'; import 'package:gecko/models/widgets_keys.dart';
import 'package:gecko/providers/duniter_indexer.dart'; import 'package:gecko/providers/duniter_indexer.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/screens/wallet_view.dart'; import 'package:gecko/screens/wallet_view.dart';
import 'package:gecko/widgets/datapod_avatar.dart';
import 'package:gecko/widgets/page_route_no_transition.dart'; import 'package:gecko/widgets/page_route_no_transition.dart';
class TransactionTile extends StatelessWidget { class TransactionTile extends StatelessWidget {
@ -39,9 +39,7 @@ class TransactionTile extends StatelessWidget {
key: keyTransaction(newKey), key: keyTransaction(newKey),
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 5), const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
leading: ClipOval( leading: DatapodAvatar(address: repository[1], size: avatarSize),
child: V2sDatapodProvider().defaultAvatar(avatarSize),
),
title: Padding( title: Padding(
padding: const EdgeInsets.only(bottom: 5), padding: const EdgeInsets.only(bottom: 5),
child: Text(getShortPubkey(repository[1]), child: Text(getShortPubkey(repository[1]),

View File

@ -417,6 +417,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.1+3" version: "0.0.1+3"
fade_and_translate:
dependency: "direct main"
description:
name: fade_and_translate
sha256: d5ebb7279e00cbef6da9391e15976f0348d3deceb2aee0ed172c23a736fa5dd4
url: "https://pub.dev"
source: hosted
version: "0.1.3"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:

View File

@ -5,7 +5,7 @@ description: Pay with G1.
# pub.dev using `pub publish`. This is preferred for private packages. # pub.dev using `pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 0.1.3+63 version: 0.1.4+66
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
@ -59,6 +59,7 @@ dependencies:
crypto: ^3.0.3 crypto: ^3.0.3
screen_brightness: ^0.2.2+1 screen_brightness: ^0.2.2+1
uuid: ^3.0.7 uuid: ^3.0.7
fade_and_translate: ^0.1.3
dev_dependencies: dev_dependencies:
# flutter_launcher_icons: ^0.9.2 # flutter_launcher_icons: ^0.9.2