diff --git a/lib/pages/paynym/dialogs/paynym_details_popup.dart b/lib/pages/paynym/dialogs/paynym_details_popup.dart index 1e50723e6..a26905e7c 100644 --- a/lib/pages/paynym/dialogs/paynym_details_popup.dart +++ b/lib/pages/paynym/dialogs/paynym_details_popup.dart @@ -2,11 +2,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart'; import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/services/coins/coin_paynym_extension.dart'; +import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; @@ -14,8 +21,9 @@ import 'package:stackwallet/widgets/custom_buttons/paynym_follow_toggle_button.d import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; -class PaynymDetailsPopup extends StatefulWidget { +class PaynymDetailsPopup extends ConsumerStatefulWidget { const PaynymDetailsPopup({ Key? key, required this.walletId, @@ -26,10 +34,103 @@ class PaynymDetailsPopup extends StatefulWidget { final PaynymAccountLite accountLite; @override - State createState() => _PaynymDetailsPopupState(); + ConsumerState createState() => _PaynymDetailsPopupState(); } -class _PaynymDetailsPopupState extends State { +class _PaynymDetailsPopupState extends ConsumerState { + Future _onConnectPressed() async { + bool canPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + onWillPop: () async => canPop, + child: const LoadingIndicator( + width: 200, + ), + ), + ), + ); + + final wallet = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as DogecoinWallet; + + // sanity check to prevent second notifcation tx + if (wallet.hasConnectedConfirmed(widget.accountLite.code)) { + canPop = true; + Navigator.of(context).pop(); + // TODO show info popup + return; + } else if (wallet.hasConnected(widget.accountLite.code)) { + canPop = true; + Navigator.of(context).pop(); + // TODO show info popup + return; + } + + final rates = await wallet.fees; + + Map preparedTx; + + try { + preparedTx = await wallet.buildNotificationTx( + selectedTxFeeRate: rates.medium, + targetPaymentCodeString: widget.accountLite.code, + ); + } on InsufficientBalanceException catch (e) { + if (mounted) { + canPop = true; + Navigator.of(context).pop(); + } + // TODO show info popup + print(e); + return; + } + + if (mounted) { + // We have enough balance and prepared tx should be good to go. + + canPop = true; + // close loading + Navigator.of(context).pop(); + + // Close details + Navigator.of(context).pop(); + + // show info pop up + await showDialog( + context: context, + builder: (context) => ConfirmPaynymConnectDialog( + nymName: widget.accountLite.nymName, + onConfirmPressed: () { + // + print("CONFIRM NOTIF TX: $preparedTx"); + + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => ConfirmTransactionView( + walletId: wallet.walletId, + transactionInfo: { + "hex": preparedTx["hex"], + "recipient": preparedTx["recipientPaynym"], + "recipientAmt": preparedTx["amount"], + "fee": preparedTx["fee"], + "vSize": preparedTx["vSize"], + "note": "PayNym connect" + }, + ), + ), + ); + }, + amount: (preparedTx["amount"] as int) + (preparedTx["fee"] as int), + coin: wallet.coin, + ), + ); + } + } + @override Widget build(BuildContext context) { return DesktopDialog( @@ -75,9 +176,7 @@ class _PaynymDetailsPopupState extends State { ), iconSpacing: 4, width: 86, - onPressed: () { - // todo notification tx - }, + onPressed: _onConnectPressed, ), ], ), diff --git a/lib/services/coins/coin_paynym_extension.dart b/lib/services/coins/coin_paynym_extension.dart index 743e1336f..732e6d454 100644 --- a/lib/services/coins/coin_paynym_extension.dart +++ b/lib/services/coins/coin_paynym_extension.dart @@ -15,6 +15,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:tuple/tuple.dart'; +import '../../utilities/logger.dart'; + class SWException with Exception { SWException(this.message); @@ -180,7 +182,7 @@ extension PayNym on DogecoinWallet { Map transactionObject = { "hex": txn.item1, "recipientPaynym": targetPaymentCodeString, - // "recipientAmt": recipientsAmtArray[0], + "amount": amountToSend, "fee": feeBeingPaid, "vSize": txn.item2, }; @@ -200,7 +202,7 @@ extension PayNym on DogecoinWallet { Map transactionObject = { "hex": txn.item1, "recipientPaynym": targetPaymentCodeString, - // "recipientAmt": recipientsAmtArray[0], + "amount": amountToSend, "fee": feeBeingPaid, "vSize": txn.item2, }; @@ -221,7 +223,7 @@ extension PayNym on DogecoinWallet { Map transactionObject = { "hex": txn.item1, "recipientPaynym": targetPaymentCodeString, - // "recipientAmt": recipientsAmtArray[0], + "amount": amountToSend, "fee": feeBeingPaid, "vSize": txn.item2, }; @@ -290,7 +292,7 @@ extension PayNym on DogecoinWallet { final notificationScript = bscript.compile([bobP2PKH.output]); // build a notification tx - final txb = TransactionBuilder(); + final txb = TransactionBuilder(network: network); txb.setVersion(1); txb.addInput( @@ -330,20 +332,56 @@ extension PayNym on DogecoinWallet { return Tuple2(builtTx.toHex(), builtTx.virtualSize()); } - Future hasConfirmedNotificationTxSentTo( - String paymentCodeString) async { - final targetPaymentCode = - PaymentCode.fromPaymentCode(paymentCodeString, network); - final targetNotificationAddress = targetPaymentCode.notificationAddress(); + Future confirmNotificationTx( + {required Map preparedTx}) async { + try { + Logging.instance.log("confirmNotificationTx txData: $preparedTx", + level: LogLevel.Info); + final txHash = await electrumXClient.broadcastTransaction( + rawTx: preparedTx["hex"] as String); + Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); - final myTxHistory = (await transactionData) - .getAllTransactions() - .entries - .map((e) => e.value) + await updatePaynymNotificationInfo( + txid: txHash, + confirmed: false, + paymentCodeString: preparedTx["paymentCodeString"] as String, + ); + return txHash; + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + // Future hasConfirmedNotificationTxSentTo( + // String paymentCodeString) async { + // final targetPaymentCode = + // PaymentCode.fromPaymentCode(paymentCodeString, network); + // final targetNotificationAddress = targetPaymentCode.notificationAddress(); + // + // final myTxHistory = (await transactionData) + // .getAllTransactions() + // .entries + // .map((e) => e.value) + // .where((e) => + // e.txType == "Sent" && e.address == targetNotificationAddress); + // + // return myTxHistory.isNotEmpty; + // } + + bool hasConnected(String paymentCodeString) { + return getPaynymNotificationTxInfo() + .where((e) => e["paymentCodeString"] == paymentCodeString) + .isNotEmpty; + } + + bool hasConnectedConfirmed(String paymentCodeString) { + return getPaynymNotificationTxInfo() .where((e) => - e.txType == "Sent" && e.address == targetNotificationAddress); - - return myTxHistory.isNotEmpty; + e["paymentCodeString"] == paymentCodeString && + e["confirmed"] == true) + .isNotEmpty; } // fetch paynym notification tx meta data