feat: follow transactions status directly on activity screen
This commit is contained in:
parent
93c9b377ba
commit
b37b9ad804
|
@ -165,6 +165,7 @@
|
|||
"choiceOfSourceWallet": "Choose a source wallet",
|
||||
"extrinsicInProgress": "{} in progress",
|
||||
"extrinsicValidated": "{} validated !",
|
||||
"extrinsicFinalized": "{} finalized !",
|
||||
"fromMinus": "from",
|
||||
"toMinus": "to",
|
||||
"deleteThisWallet": "Delete this wallet",
|
||||
|
|
|
@ -166,6 +166,7 @@
|
|||
"choiceOfSourceWallet": "Elige un monedero de origen",
|
||||
"extrinsicInProgress": "{} en progreso",
|
||||
"extrinsicValidated": "¡ {} validado !",
|
||||
"extrinsicFinalized": "{} finalized !",
|
||||
"fromMinus": "de",
|
||||
"toMinus": "a",
|
||||
"deleteThisWallet": "Borrar este monedero",
|
||||
|
|
|
@ -165,6 +165,7 @@
|
|||
"choiceOfSourceWallet": "Choix du portefeuille source",
|
||||
"extrinsicInProgress": "{} en cours",
|
||||
"extrinsicValidated": "{} validée !",
|
||||
"extrinsicFinalized": "{} finalisé !",
|
||||
"fromMinus": "de",
|
||||
"toMinus": "vers",
|
||||
"deleteThisWallet": "Supprimer ce portefeuille",
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
[
|
||||
"https://hasura.gdev.coinduf.eu"
|
||||
"https://gdev-indexer.p2p.legal",
|
||||
"https://hasura.gdev.coinduf.eu",
|
||||
"https://gdev-hasura.cgeek.fr",
|
||||
"https://hasura-gdev.pini.fr"
|
||||
]
|
||||
|
|
|
@ -96,3 +96,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
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
|
@ -70,7 +70,7 @@ class WalletData extends HiveObject {
|
|||
|
||||
// creates the ':'-separated string from the WalletData
|
||||
String inLine() {
|
||||
return "$chest:$number:$name:$derivation:$imageDefaultPath";
|
||||
return "$chest:$number:$name:$derivation:$imageDefaultPath:$imageCustomPath:$identityStatus";
|
||||
}
|
||||
|
||||
bool hasIdentity() {
|
||||
|
@ -112,7 +112,7 @@ class WalletData extends HiveObject {
|
|||
final datapod = Provider.of<V2sDatapodProvider>(homeContext, listen: false);
|
||||
final avatarUuid = const Uuid().v4();
|
||||
|
||||
await datapod.getAvatar(address, saveOnDisk: true, uuid: avatarUuid);
|
||||
await datapod.getRemoteAvatar(address, saveOnDisk: true, uuid: avatarUuid);
|
||||
|
||||
final avatarPath = '${avatarsDirectory.path}/$address-$avatarUuid';
|
||||
if (!await File(avatarPath).exists()) return;
|
||||
|
|
|
@ -197,113 +197,135 @@ class DuniterIndexer with ChangeNotifier {
|
|||
String result = n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 2);
|
||||
return double.parse(result);
|
||||
}
|
||||
}
|
||||
|
||||
//// Manuals queries
|
||||
|
||||
Future<bool> isIdtyExist(String name) async {
|
||||
final variables = <String, dynamic>{
|
||||
'name': name,
|
||||
};
|
||||
final result = await _execQuery(isIdtyExistQ, variables);
|
||||
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);
|
||||
Future<bool> isIdtyExist(String name) async {
|
||||
final variables = <String, dynamic>{
|
||||
'name': name,
|
||||
};
|
||||
final result = await _execQuery(isIdtyExistQ, variables);
|
||||
return result.data!['identity']?.isNotEmpty ?? false;
|
||||
}
|
||||
|
||||
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 '';
|
||||
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);
|
||||
}
|
||||
|
||||
final dateDelimiter = getDateDelimiter();
|
||||
Future<QueryResult> _execQuery(
|
||||
String query, Map<String, dynamic> variables) async {
|
||||
final httpLink = HttpLink(
|
||||
'$indexerEndpoint/v1/graphql',
|
||||
);
|
||||
|
||||
amount = repository[4] == 'RECEIVED' ? repository[3] : repository[3] * -1;
|
||||
final client = GraphQLClient(
|
||||
cache: GraphQLCache(),
|
||||
link: httpLink,
|
||||
);
|
||||
|
||||
if (isUdUnit) {
|
||||
amount = round(amount / balanceRatio);
|
||||
finalAmount = 'ud'.tr(args: ['$amount ']);
|
||||
} else {
|
||||
finalAmount = '$amount $currencyName';
|
||||
final options = QueryOptions(document: gql(query), variables: variables);
|
||||
|
||||
return await client.query(options);
|
||||
}
|
||||
|
||||
bool isMigrationTime =
|
||||
startBlockchainInitialized && date.compareTo(startBlockchainTime) < 0;
|
||||
Stream<QueryResult> subscribeHistoryIssued(String address) {
|
||||
final wsLink = WebSocketLink(
|
||||
'${indexerEndpoint.replaceFirst('https', 'wss')}/v1/graphql',
|
||||
);
|
||||
|
||||
return {
|
||||
'finalAmount': finalAmount,
|
||||
'isMigrationTime': isMigrationTime,
|
||||
'dateDelimiter': dateDelimiter,
|
||||
'dateForm': dateForm,
|
||||
};
|
||||
}
|
||||
final variables = <String, dynamic>{
|
||||
'address': address,
|
||||
};
|
||||
|
||||
int weekNumber(DateTime date) {
|
||||
int dayOfYear = int.parse(DateFormat("D").format(date));
|
||||
return ((dayOfYear - date.weekday + 10) / 7).floor();
|
||||
final client = GraphQLClient(
|
||||
cache: GraphQLCache(),
|
||||
link: wsLink,
|
||||
);
|
||||
|
||||
final options = SubscriptionOptions(
|
||||
document: gql(subscribeHistoryIssuedQ),
|
||||
variables: variables,
|
||||
);
|
||||
|
||||
return client.subscribe(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() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ import 'package:gecko/globals.dart';
|
|||
import 'package:gecko/models/chest_data.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/providers/duniter_indexer.dart';
|
||||
import 'package:gecko/providers/home.dart';
|
||||
import 'package:gecko/providers/my_wallets.dart';
|
||||
import 'package:gecko/providers/wallet_options.dart';
|
||||
import 'package:gecko/providers/wallets_profiles.dart';
|
||||
import 'package:gecko/widgets/transaction_status.dart';
|
||||
import 'package:pinenacl/ed25519.dart';
|
||||
import 'package:polkawallet_sdk/api/apiKeyring.dart';
|
||||
import 'package:polkawallet_sdk/api/types/networkParams.dart';
|
||||
|
@ -37,7 +39,7 @@ class SubstrateSdk with ChangeNotifier {
|
|||
bool importIsLoading = false;
|
||||
int blocNumber = 0;
|
||||
bool isLoadingEndpoint = false;
|
||||
Map transactionStatus = {};
|
||||
Map<String, TransactionContent> transactionStatus = {};
|
||||
final int initSs58 = 42;
|
||||
Map<String, int> currencyParameters = {};
|
||||
final csSalt = TextEditingController();
|
||||
|
@ -54,21 +56,35 @@ class SubstrateSdk with ChangeNotifier {
|
|||
////////// 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,
|
||||
[String? rawParams]) async {
|
||||
final walletOptions =
|
||||
Provider.of<WalletOptionsProvider>(homeContext, listen: false);
|
||||
final walletProfiles =
|
||||
Provider.of<WalletsProfilesProvider>(homeContext, listen: false);
|
||||
transactionStatus.putIfAbsent(currentTransactionId, () => 'sending');
|
||||
final currentTransactionId = transcationContent.transactionId;
|
||||
transactionStatus.putIfAbsent(
|
||||
currentTransactionId, () => transcationContent);
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hash = await sdk.api.tx.signAndSend(txInfo, txOptions, password,
|
||||
rawParam: rawParams, onStatusChange: (p0) {
|
||||
transactionStatus.update(currentTransactionId, (_) => p0,
|
||||
ifAbsent: () => p0);
|
||||
rawParam: rawParams, onStatusChange: (newStatus) {
|
||||
transactionStatus.update(currentTransactionId, (trans) {
|
||||
trans.status = statusMap[newStatus]!;
|
||||
return trans;
|
||||
}, ifAbsent: () {
|
||||
transcationContent.status = statusMap[newStatus]!;
|
||||
return transcationContent;
|
||||
});
|
||||
notifyListeners();
|
||||
}).timeout(
|
||||
const Duration(seconds: 18),
|
||||
|
@ -77,25 +93,47 @@ class SubstrateSdk with ChangeNotifier {
|
|||
log.d(hash);
|
||||
if (hash.isEmpty) {
|
||||
transactionStatus.update(
|
||||
currentTransactionId, (_) => 'Exception: timeout');
|
||||
currentTransactionId,
|
||||
(trans) {
|
||||
trans.status = TransactionStatus.timeout;
|
||||
return trans;
|
||||
},
|
||||
ifAbsent: () {
|
||||
transcationContent.status = TransactionStatus.timeout;
|
||||
return transcationContent;
|
||||
},
|
||||
);
|
||||
notifyListeners();
|
||||
|
||||
return 'Exception: timeout';
|
||||
} else {
|
||||
// Success !
|
||||
transactionStatus.update(currentTransactionId, (_) => hash.toString(),
|
||||
ifAbsent: () => hash.toString());
|
||||
transactionStatus.update(currentTransactionId, (trans) {
|
||||
trans.status = TransactionStatus.success;
|
||||
return trans;
|
||||
}, ifAbsent: () {
|
||||
transcationContent.status = TransactionStatus.success;
|
||||
return transcationContent;
|
||||
});
|
||||
notifyListeners();
|
||||
walletOptions.reload();
|
||||
walletProfiles.reload();
|
||||
return hash.toString();
|
||||
}
|
||||
} catch (e) {
|
||||
transactionStatus.update(currentTransactionId, (_) => e.toString(),
|
||||
ifAbsent: () => e.toString());
|
||||
transactionStatus.update(
|
||||
currentTransactionId,
|
||||
(trans) {
|
||||
trans.status = TransactionStatus.failed;
|
||||
trans.error = e.toString();
|
||||
return trans;
|
||||
},
|
||||
ifAbsent: () {
|
||||
transcationContent.status = TransactionStatus.failed;
|
||||
transcationContent.error = e.toString();
|
||||
return transcationContent;
|
||||
},
|
||||
);
|
||||
notifyListeners();
|
||||
return e.toString();
|
||||
}
|
||||
transactionStatus.remove(currentTransactionId);
|
||||
}
|
||||
|
||||
Future _getStorage(String call) async {
|
||||
|
@ -604,6 +642,8 @@ class SubstrateSdk with ChangeNotifier {
|
|||
final homeProvider = Provider.of<HomeProvider>(homeContext, listen: false);
|
||||
final myWalletProvider =
|
||||
Provider.of<MyWalletsProvider>(homeContext, listen: false);
|
||||
final duniterIndexer =
|
||||
Provider.of<DuniterIndexer>(homeContext, listen: false);
|
||||
|
||||
homeProvider.changeMessage("connectionPending".tr(), 0);
|
||||
|
||||
|
@ -648,7 +688,7 @@ class SubstrateSdk with ChangeNotifier {
|
|||
await initCurrencyParameters();
|
||||
|
||||
// Indexer Blockchain start
|
||||
getBlockStart();
|
||||
duniterIndexer.getBlockStart();
|
||||
|
||||
notifyListeners();
|
||||
homeProvider.changeMessage(
|
||||
|
@ -985,7 +1025,14 @@ class SubstrateSdk with ChangeNotifier {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1053,7 +1100,14 @@ class SubstrateSdk with ChangeNotifier {
|
|||
|
||||
log.d('Cert action: ${txInfo.call!}');
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1069,8 +1123,14 @@ class SubstrateSdk with ChangeNotifier {
|
|||
final txOptions = [name];
|
||||
|
||||
final transactionId = const Uuid().v4();
|
||||
|
||||
_executeCall(transactionId, txInfo, txOptions, password);
|
||||
final transactionContent = TransactionContent(
|
||||
transactionId: const Uuid().v4(),
|
||||
status: TransactionStatus.sending,
|
||||
from: fromAddress,
|
||||
to: fromAddress,
|
||||
amount: -1,
|
||||
);
|
||||
_executeCall(transactionContent, txInfo, txOptions, password);
|
||||
|
||||
return transactionId;
|
||||
}
|
||||
|
@ -1140,7 +1200,15 @@ newKeySig: $newKeySigType""");
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1166,7 +1234,14 @@ newKeySig: $newKeySigType""");
|
|||
|
||||
final txOptions = [idtyIndex, address, revocationSigTyped];
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1241,10 +1316,6 @@ newKeySig: $newKeySigType""");
|
|||
void reload() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetTransactionStatus() {
|
||||
transactionStatus.clear();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:gecko/globals.dart';
|
||||
import 'package:gecko/models/queries_datapod.dart';
|
||||
import 'package:gecko/models/scale_functions.dart';
|
||||
import 'package:gecko/providers/my_wallets.dart';
|
||||
import 'package:gecko/providers/substrate_sdk.dart';
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -36,9 +35,6 @@ class V2sDatapodProvider with ChangeNotifier {
|
|||
List<Map<String, String>>? socials,
|
||||
Map<String, double>? geoloc}) async {
|
||||
final sub = Provider.of<SubstrateSdk>(homeContext, listen: false);
|
||||
final myWallets =
|
||||
Provider.of<MyWalletsProvider>(homeContext, listen: false);
|
||||
final walletData = myWallets.getWalletDataByAddress(address);
|
||||
|
||||
final messageToSign = jsonEncode({
|
||||
'address': address,
|
||||
|
@ -70,9 +66,6 @@ class V2sDatapodProvider with ChangeNotifier {
|
|||
return false;
|
||||
}
|
||||
log.d(result.data!['updateProfile']['message']);
|
||||
walletData!.profileUpdatedTime = DateTime.now();
|
||||
walletBox.put(address, walletData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -145,7 +138,7 @@ class V2sDatapodProvider with ChangeNotifier {
|
|||
return profileDate;
|
||||
}
|
||||
|
||||
Future<Image> getAvatar(String address,
|
||||
Future<Image> getRemoteAvatar(String address,
|
||||
{double size = 20, bool saveOnDisk = false, String? uuid}) async {
|
||||
final variables = <String, dynamic>{
|
||||
'address': address,
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:gecko/screens/transaction_in_progress.dart';
|
|||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:image_cropper/image_cropper.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class WalletOptionsProvider with ChangeNotifier {
|
||||
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) {
|
||||
await File(croppedFile.path).rename(newPath);
|
||||
} else {
|
||||
if (croppedFile == null) {
|
||||
log.w('No image selected.');
|
||||
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);
|
||||
|
||||
return newPath;
|
||||
} else {
|
||||
log.w('No image selected.');
|
||||
|
@ -139,6 +156,7 @@ class WalletOptionsProvider with ChangeNotifier {
|
|||
Provider.of<WalletOptionsProvider>(context, listen: false);
|
||||
final myWalletProvider =
|
||||
Provider.of<MyWalletsProvider>(context, listen: false);
|
||||
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
|
||||
|
||||
bool canValidate = false;
|
||||
bool idtyExist = false;
|
||||
|
@ -160,9 +178,9 @@ class WalletOptionsProvider with ChangeNotifier {
|
|||
TextField(
|
||||
key: keyEnterIdentityUsername,
|
||||
onChanged: (_) async {
|
||||
idtyExist = await isIdtyExist(idtyName.text);
|
||||
idtyExist = await duniterIndexer.isIdtyExist(idtyName.text);
|
||||
canValidate = !idtyExist &&
|
||||
!await isIdtyExist(idtyName.text) &&
|
||||
!await duniterIndexer.isIdtyExist(idtyName.text) &&
|
||||
idtyName.text.length >= 2 &&
|
||||
idtyName.text.length <= 32;
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import 'package:gecko/providers/duniter_indexer.dart';
|
|||
import 'package:gecko/providers/my_wallets.dart';
|
||||
import 'package:gecko/models/wallet_data.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/wallets_profiles.dart';
|
||||
import 'package:gecko/screens/certifications.dart';
|
||||
|
@ -50,6 +49,24 @@ class WalletOptions extends StatelessWidget {
|
|||
final isWalletNameIndexed =
|
||||
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(
|
||||
onPopInvoked: (_) {
|
||||
walletOptions.isEditing = false;
|
||||
|
@ -259,58 +276,48 @@ class WalletOptions extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget avatar(WalletOptionsProvider walletProvider) {
|
||||
return Consumer<V2sDatapodProvider>(builder: (context, datapod, _) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
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 {
|
||||
final newPath = await (walletProvider.changeAvatar());
|
||||
if (newPath != '') {
|
||||
wallet.imageCustomPath = newPath;
|
||||
walletBox.put(wallet.key, wallet);
|
||||
// Uncomment to enable Cs+ avatar storage
|
||||
// CesiumPlusProvider().setAvatar(wallet.address, newPath);
|
||||
}
|
||||
wallet.imageCustomPath = await (walletProvider.changeAvatar());
|
||||
walletProvider.reload();
|
||||
},
|
||||
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 {
|
||||
wallet.imageCustomPath = await (walletProvider.changeAvatar());
|
||||
walletProvider.reload();
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/walletOptions/camera.png',
|
||||
height: scaleSize(38),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/walletOptions/camera.png',
|
||||
height: scaleSize(38),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget confirmIdentityButton(WalletOptionsProvider walletProvider) {
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
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/models/widgets_keys.dart';
|
||||
import 'package:gecko/providers/my_wallets.dart';
|
||||
import 'package:gecko/providers/substrate_sdk.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:easy_localization/easy_localization.dart';
|
||||
|
||||
class TransactionInProgress extends StatelessWidget {
|
||||
class TransactionInProgress extends StatefulWidget {
|
||||
final String transactionId;
|
||||
final String transType;
|
||||
final String? fromAddress, toAddress, toUsername;
|
||||
|
@ -22,56 +25,69 @@ class TransactionInProgress extends StatelessWidget {
|
|||
this.toUsername,
|
||||
}) : 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
|
||||
Widget build(BuildContext context) {
|
||||
final sub = Provider.of<SubstrateSdk>(context, listen: true);
|
||||
|
||||
final transactionDetails = TransactionDetails(
|
||||
transactionId: 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();
|
||||
}
|
||||
if (sub.transactionStatus.containsKey(widget.transactionId)) {
|
||||
txContent = sub.transactionStatus[widget.transactionId]!;
|
||||
}
|
||||
|
||||
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(
|
||||
children: [
|
||||
getTransactionStatusIcon(details),
|
||||
TransactionStatusIcon(txContent.status),
|
||||
ScaledSizedBox(height: 7),
|
||||
if (details.txStatus != TransactionStatus.none)
|
||||
if (txContent.status != TransactionStatus.none)
|
||||
Text(
|
||||
transactionDetails.resultText,
|
||||
resultText,
|
||||
textAlign: TextAlign.center,
|
||||
style: scaledTextStyle(fontSize: 17),
|
||||
)
|
||||
|
@ -80,9 +96,6 @@ class TransactionInProgress extends StatelessWidget {
|
|||
}
|
||||
|
||||
return PopScope(
|
||||
onPopInvoked: (_) {
|
||||
sub.resetTransactionStatus();
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: backgroundColor,
|
||||
appBar: AppBar(
|
||||
|
@ -94,8 +107,7 @@ class TransactionInProgress extends StatelessWidget {
|
|||
children: <Widget>[
|
||||
Text(
|
||||
'extrinsicInProgress'.tr(args: [
|
||||
transactionDetails.actionMap[transType] ??
|
||||
'strangeTransaction'.tr()
|
||||
actionMap[widget.transType] ?? 'strangeTransaction'.tr()
|
||||
]),
|
||||
style: scaledTextStyle(fontSize: 20),
|
||||
)
|
||||
|
@ -117,30 +129,29 @@ class TransactionInProgress extends StatelessWidget {
|
|||
)),
|
||||
child: Column(children: <Widget>[
|
||||
ScaledSizedBox(height: 10),
|
||||
if (transType == 'pay')
|
||||
if (widget.transType == 'pay')
|
||||
Text(
|
||||
transactionDetails.isUdUnit
|
||||
? 'ud'.tr(args: ['${transactionDetails.amount} '])
|
||||
: '${transactionDetails.amount} $currencyName',
|
||||
isUdUnit
|
||||
? 'ud'.tr(args: ['$amount '])
|
||||
: '$amount $currencyName',
|
||||
textAlign: TextAlign.center,
|
||||
style: scaledTextStyle(
|
||||
fontSize: 17, fontWeight: FontWeight.w500),
|
||||
),
|
||||
if (transType == 'pay') ScaledSizedBox(height: 10),
|
||||
if (widget.transType == 'pay') ScaledSizedBox(height: 10),
|
||||
Text(
|
||||
'fromMinus'.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
style: scaledTextStyle(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
transactionDetails.fromAddress!,
|
||||
fromAddressFormat,
|
||||
textAlign: TextAlign.center,
|
||||
style: scaledTextStyle(
|
||||
fontSize: 17, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Visibility(
|
||||
visible: transactionDetails.fromAddress !=
|
||||
transactionDetails.toAddress,
|
||||
visible: fromAddressFormat != toAddressFormat,
|
||||
child: Column(
|
||||
children: [
|
||||
ScaledSizedBox(height: 10),
|
||||
|
@ -150,7 +161,7 @@ class TransactionInProgress extends StatelessWidget {
|
|||
style: scaledTextStyle(fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
transactionDetails.toUsername!,
|
||||
toUsernameFormat,
|
||||
textAlign: TextAlign.center,
|
||||
style: scaledTextStyle(
|
||||
fontSize: 17, fontWeight: FontWeight.w500),
|
||||
|
@ -162,7 +173,7 @@ class TransactionInProgress extends StatelessWidget {
|
|||
]),
|
||||
),
|
||||
const Spacer(),
|
||||
buildTransactionStatus(transactionDetails),
|
||||
buildTransactionStatus(),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
child: Align(
|
||||
|
@ -178,7 +189,6 @@ class TransactionInProgress extends StatelessWidget {
|
|||
backgroundColor: orangeC,
|
||||
),
|
||||
onPressed: () {
|
||||
sub.resetTransactionStatus();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
|
@ -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(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'package:gecko/globals.dart';
|
|||
import 'package:gecko/models/scale_functions.dart';
|
||||
import 'package:gecko/models/widgets_keys.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/widgets/datapod_avatar.dart';
|
||||
|
||||
class CertTile extends StatelessWidget {
|
||||
const CertTile({
|
||||
|
@ -28,9 +28,8 @@ class CertTile extends StatelessWidget {
|
|||
key: keyTransaction(keyID++),
|
||||
contentPadding: EdgeInsets.only(
|
||||
left: 10, right: 0, top: scaleSize(3), bottom: scaleSize(3)),
|
||||
leading: ClipOval(
|
||||
child: V2sDatapodProvider().defaultAvatar(avatarSize),
|
||||
),
|
||||
leading: DatapodAvatar(
|
||||
address: repository['address'], size: avatarSize),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gecko/globals.dart';
|
||||
import 'package:gecko/models/scale_functions.dart';
|
||||
|
||||
class Loading extends StatelessWidget {
|
||||
const Loading({
|
||||
|
@ -13,7 +14,7 @@ class Loading extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
return ScaledSizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: CircularProgressIndicator(
|
||||
|
|
|
@ -48,7 +48,7 @@ class ContactsList extends StatelessWidget {
|
|||
contentPadding: const EdgeInsets.all(5),
|
||||
dense: !isTall,
|
||||
leading: DatapodAvatar(
|
||||
address: g1Wallet.address, size: scaleSize(50)),
|
||||
address: g1Wallet.address, size: 47),
|
||||
title: Row(children: <Widget>[
|
||||
Text(getShortPubkey(g1Wallet.address),
|
||||
style: scaledTextStyle(
|
||||
|
|
|
@ -28,7 +28,7 @@ class HeaderProfile extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final avatarSize = scaleSize(110);
|
||||
const double avatarSize = 110;
|
||||
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
|
||||
final walletOptions =
|
||||
Provider.of<WalletOptionsProvider>(context, listen: false);
|
||||
|
|
|
@ -5,17 +5,21 @@ import 'package:gecko/models/queries_indexer.dart';
|
|||
import 'package:gecko/models/scale_functions.dart';
|
||||
import 'package:gecko/models/widgets_keys.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/transaction_in_progress_tile.dart';
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HistoryQuery extends StatelessWidget {
|
||||
const HistoryQuery({Key? key, required this.address}) : super(key: key);
|
||||
const HistoryQuery({Key? key, required this.address})
|
||||
: super(key: key);
|
||||
final String address;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final duniterIndexer = Provider.of<DuniterIndexer>(context, listen: false);
|
||||
final sub = Provider.of<SubstrateSdk>(context, listen: false);
|
||||
|
||||
final ScrollController scrollController = ScrollController();
|
||||
FetchMoreOptions? opts;
|
||||
|
@ -108,6 +112,15 @@ class HistoryQuery extends StatelessWidget {
|
|||
result, opts, address, nRepositories);
|
||||
}
|
||||
|
||||
// Get transaction in progress if exist
|
||||
String? transactionId;
|
||||
for (final entry in sub.transactionStatus.entries) {
|
||||
if (entry.value.from == address) {
|
||||
transactionId = entry.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build history list
|
||||
return NotificationListener(
|
||||
child: Builder(
|
||||
|
@ -116,6 +129,9 @@ class HistoryQuery extends StatelessWidget {
|
|||
key: keyListTransactions,
|
||||
controller: scrollController,
|
||||
children: <Widget>[
|
||||
if (transactionId != null)
|
||||
TransactionInProgressTule(
|
||||
address: address, transactionId: transactionId),
|
||||
HistoryView(
|
||||
result: result,
|
||||
address: address,
|
||||
|
|
|
@ -40,7 +40,8 @@ class HistoryView extends StatelessWidget {
|
|||
: Column(children: <Widget>[
|
||||
Column(
|
||||
children: duniterIndexer.transBC!.map((repository) {
|
||||
final answer = computeHistoryView(repository, address);
|
||||
final answer =
|
||||
duniterIndexer.computeHistoryView(repository, address);
|
||||
pastDelimiters.add(answer['dateDelimiter']);
|
||||
|
||||
bool isMigrationTime = false;
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:gecko/providers/my_wallets.dart';
|
|||
import 'package:gecko/providers/substrate_sdk.dart';
|
||||
import 'package:gecko/providers/wallet_options.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/transaction_in_progress.dart';
|
||||
import 'package:gecko/widgets/balance.dart';
|
||||
|
@ -54,13 +55,17 @@ void paymentPopup(BuildContext context, String toAddress, String? username) {
|
|||
destAddress: toAddress,
|
||||
amount: double.parse(walletViewProvider.payAmount.text),
|
||||
password: myWalletProvider.pinCode);
|
||||
|
||||
const legacyMonitor = false;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return TransactionInProgress(
|
||||
transactionId: transactionId,
|
||||
toAddress: toAddress,
|
||||
toUsername: username);
|
||||
return legacyMonitor
|
||||
? TransactionInProgress(
|
||||
transactionId: transactionId,
|
||||
toAddress: toAddress,
|
||||
toUsername: username)
|
||||
: ActivityScreen(address: acc.address!);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ class SearchIdentityQuery extends StatelessWidget {
|
|||
|
||||
searchProvider.resultLenght = identities.length;
|
||||
|
||||
final avatarSize = scaleSize(45);
|
||||
const double avatarSize = 45;
|
||||
return Expanded(
|
||||
child: ListView(children: <Widget>[
|
||||
for (Map profile in identities)
|
||||
|
|
|
@ -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: () => duniterIndexer.reload(),
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,9 +39,7 @@ class TransactionTile extends StatelessWidget {
|
|||
key: keyTransaction(newKey),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
||||
leading: ClipOval(
|
||||
child: DatapodAvatar(address: repository[1], size: avatarSize),
|
||||
),
|
||||
leading: DatapodAvatar(address: repository[1], size: avatarSize),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 5),
|
||||
child: Text(getShortPubkey(repository[1]),
|
||||
|
|
|
@ -417,6 +417,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -5,7 +5,7 @@ description: Pay with G1.
|
|||
# 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
|
||||
|
||||
version: 0.1.3+63
|
||||
version: 0.1.3+64
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
@ -59,6 +59,7 @@ dependencies:
|
|||
crypto: ^3.0.3
|
||||
screen_brightness: ^0.2.2+1
|
||||
uuid: ^3.0.7
|
||||
fade_and_translate: ^0.1.3
|
||||
|
||||
dev_dependencies:
|
||||
# flutter_launcher_icons: ^0.9.2
|
||||
|
|
Loading…
Reference in New Issue