Newer
Older
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../data/models/cesium_card.dart';
import 'package:ndef/utilities.dart';
import 'qr_manager.dart';
import '../../../data/models/contact.dart';
import '../../../data/models/node_type.dart';
import '../../../data/models/payment_cubit.dart';
import '../../../data/models/transaction.dart';
import '../../../data/models/transaction_type.dart';
import '../../../g1/api.dart';
import '../data/models/bottom_nav_cubit.dart';
import '../data/models/multi_wallet_transaction_cubit.dart';
import '../data/models/node.dart';
import '../data/models/node_manager.dart';
import '../data/models/utxo.dart';
import '../data/models/utxo_cubit.dart';
import '../g1/astroid_helper.dart';
import 'contacts_cache.dart';
import 'logger.dart';
import 'ui_helpers.dart';
required double amount,
required String comment,
bool isRetry = false,
required double currentUd,
bool useBMA = false}) async {
if (!SharedPreferencesHelper().isG1nkgoCard() &&
!SharedPreferencesHelper().hasVolatile()) {
hasPass = await showImportCesiumWalletDialog(
context,
SharedPreferencesHelper().getPubKey(),
context.read<BottomNavCubit>().currentIndex) ??
false;
hasPass = true;
}
if (hasPass) {
if (context.mounted) {
final MultiWalletTransactionCubit txCubit =
context.read<MultiWalletTransactionCubit>();
final PaymentCubit paymentCubit = context.read<PaymentCubit>();
final AppCubit appCubit = context.read<AppCubit>();
paymentCubit.sending();
final String fromPubKey = SharedPreferencesHelper().getPubKey();
final bool? confirmed = await _confirmSend(context, amount.toString(),
fromPubKey, recipients, isRetry, appCubit.currency, isToMultiple);
final Contact fromContact = await ContactsCache().getContact(fromPubKey);
final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
if (!context.mounted) {
return false;
}
final UtxoCubit utxoCubit = context.read<UtxoCubit>();
final double convertedAmount = toG1(amount, isG1, currentUd);
if (confirmed == null || !confirmed) {
paymentCubit.sentFailed();
} else {
final Transaction tx = Transaction(
type: TransactionType.pending,
from: fromContact,
to: recipients[0],
recipients: recipients,
recipientsAmounts: List<double>.filled(recipients.length, amount),
amount: -toCG1(convertedAmount).toDouble() * recipients.length,
comment: comment,
time: DateTime.now());
final bool isConnected =
await ConnectivityWidgetWrapperWrapper.isConnected;
logger('isConnected: $isConnected');
if (isConnected != null && !isConnected && !isRetry) {
paymentCubit.sent();
if (!context.mounted) {
showAlertDialog(context, tr('payment_waiting_internet_title'),
tr('payment_waiting_internet_desc_beta'));
final Transaction pending =
tx.copyWith(type: TransactionType.waitingNetwork);
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// PAY!
PayResult result;
if (!useBMA) {
result = await payWithGVA(
to: recipients.map((Contact c) => c.pubKey).toList(),
comment: comment,
amount: convertedAmount);
} else {
await utxoCubit.fetchUtxos(fromPubKey);
final List<Utxo>? utxos = utxoCubit.consume(convertedAmount);
final Tuple2<Map<String, dynamic>?, Node> currentBlock =
await getCurrentBlockGVA();
if (currentBlock != null && utxos != null) {
result = await payWithBMA(
destPub: recipients[0].pubKey,
blockHash: '${currentBlock.item1!['hash']}',
blockNumber: '${currentBlock.item1!['number']}',
comment: comment,
wallet: wallet,
utxos: utxos,
amount: convertedAmount);
} else {
final Node triedNode = currentBlock.item2;
result = PayResult(
message: 'Error retrieving payment data', node: triedNode);
}
}
final Transaction pending = tx.copyWith(
debugInfo:
'Node used: ${result != null && result.node != null ? result.node!.url : 'unknown'}');
if (result.message == 'success') {
paymentCubit.sent();
if (!context.mounted) {
showAlertDialog(context, tr('payment_successful'),
tr('payment_successful_desc'));
if (!isRetry) {
// Add here the transaction to the pending list (so we can check it the tx is confirmed)
txCubit.addPendingTransaction(pending);
} else {
// Update the previously failed tx with an update time and type pending
txCubit.updatePendingTransaction(
pending.copyWith(type: TransactionType.pending));
} else {
paymentCubit.pendingPayment();
if (!context.mounted) {
final bool failedWithoutBalance =
result.message == 'insufficient balance' ||
result.message == 'Insufficient balance in your wallet';
desc: tr('payment_error_desc',
namedArgs: <String, String>{'error': tr(result.message)}),
increaseErrors: !failedWithoutBalance,
if (!isRetry) {
txCubit.insertPendingTransaction(
pending.copyWith(type: TransactionType.failed));
context.read<BottomNavCubit>().updateIndex(3);
} else {
// Update the previously failed tx with an update time and type pending
txCubit.updatePendingTransaction(
pending.copyWith(type: TransactionType.failed));
}
showPayError(
context: context,
desc: tr('payment_error_no_pass'),
increaseErrors: false);
}
bool weHaveBalance(BuildContext context, double amount) {
final double balance = getBalance(context);
return weHave;
}
double getBalance(BuildContext context) =>
context.read<MultiWalletTransactionCubit>().balance();
Future<bool?> _confirmSend(
BuildContext context,
String amount,
String fromPubKey,
List<Contact> recipients,
bool isRetry,
Currency currency,
bool isPayToMultiple) async {
return showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(tr('please_confirm_sent')),
content: isPayToMultiple
? Text(tr('please_confirm_sent_multi_desc',
namedArgs: <String, String>{
'amount': amount,
'currency': currency.name(),
'people': recipients.length.toString()
}))
: Text(tr(
isRetry
? 'please_confirm_retry_sent_desc'
: 'please_confirm_sent_desc',
namedArgs: <String, String>{
'amount': amount,
'to': humanizeContact(fromPubKey, recipients[0], true),
'currency': currency.name()
})),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(tr('cancel')),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(tr('yes_sent')),
),
],
);
},
);
}
void showPayError(
{required BuildContext context,
required String desc,
required bool increaseErrors,
Node? node}) {
NodeManager().increaseNodeErrors(NodeType.gva, node);
}
const Duration paymentTimeRange = Duration(minutes: 60);
Future<void> onKeyScanned(BuildContext context, String scannedKey) async {
final PaymentState? pay = parseScannedUri(scannedKey);
final PaymentCubit paymentCubit = context.read<PaymentCubit>();
if (pay != null) {
logger('Scanned $pay');
final String result = extractPublicKey(pay.contacts[0].pubKey);
final Contact contact = await ContactsCache().getContact(result);
final double? currentAmount = paymentCubit.state.amount;
paymentCubit.selectUser(contact);
if (pay.amount != null) {
paymentCubit.selectKeyAmount(contact, pay.amount);
} else {
paymentCubit.selectKeyAmount(contact, currentAmount);
}
if (pay.comment != null) {
paymentCubit.setComment(pay.comment);
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(tr('qr_invalid_payment'))));
}
}
Future<void> importAstroID(BuildContext context) async {
try {
// Scanner le QR code et extraire la valeur DISCO
final String? disco = await QrManager.qrScan(context);
if (disco == null) {
return;
}
// Demander à l'utilisateur de saisir le mot de passe unique ($UNIQID)
context: context,
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
builder: (BuildContext context) {
String passwordLocal = '';
return AlertDialog(
title: const Text('Saisir le mot de passe'),
content: TextField(
decoration: const InputDecoration(hintText: 'Mot de passe unique'),
onChanged: (String value) {
// Handle password input
passwordLocal = value;
},
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(null),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
// Handle password submission
Navigator.of(context).pop(passwordLocal);
},
child: const Text('OK'),
),
],
);
},
);
if (password == null || password.isEmpty) {
return;
}
// Déchiffrer l'AstroID
final Tuple2<String, String>? secrets =
await decryptAstroID(disco, password);
if (secrets == null) {
showAlertDialog(context, 'Erreur',
'Impossible de déchiffrer AstroID. Vérifiez le mot de passe.');
return;
}
// Initialiser un nouveau portefeuille CesiumWallet avec les secrets déchiffrés
final CesiumWallet wallet = CesiumWallet(secrets.item1, secrets.item2);
// Add the AstroID wallet to the storage
SharedPreferencesHelper().importAstroIDWallet(disco, password);
final CesiumCard card = SharedPreferencesHelper().buildCesiumCard(
seed: wallet.seed.toHexString(), pubKey: wallet.pubkey);
// Select the AstroID wallet as the current wallet
SharedPreferencesHelper().selectCurrentWallet(card);
// Rediriger vers l'écran principal
Navigator.of(context).pushReplacementNamed('/');
} catch (e, stacktrace) {
logger('Erreur importation AstroID: $e');
logger(stacktrace.toString());
showAlertDialog(context, 'Erreur',
'Une erreur est survenue lors de importation de AstroID.');
}
}