mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-02 19:26:37 +00:00
mobile paynym send flow implemented
This commit is contained in:
parent
a41c903a96
commit
828c301af7
3 changed files with 252 additions and 259 deletions
|
@ -4,6 +4,7 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart';
|
||||
|
@ -94,8 +95,7 @@ class _ConfirmTransactionViewState
|
|||
txid = await (manager.wallet as PaynymWalletInterface)
|
||||
.broadcastNotificationTx(preparedTx: transactionInfo);
|
||||
} else if (widget.isPaynymTransaction) {
|
||||
//
|
||||
throw UnimplementedError("paynym send not implemented yet");
|
||||
txid = await manager.confirmSend(txData: transactionInfo);
|
||||
} else {
|
||||
final coin = manager.coin;
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
|
@ -333,14 +333,20 @@ class _ConfirmTransactionViewState
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
"Recipient",
|
||||
widget.isPaynymTransaction
|
||||
? "PayNym recipient"
|
||||
: "Recipient",
|
||||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
"${transactionInfo["address"] ?? "ERROR"}",
|
||||
widget.isPaynymTransaction
|
||||
? (transactionInfo["paynymAccountLite"]
|
||||
as PaynymAccountLite)
|
||||
.nymName
|
||||
: "${transactionInfo["address"] ?? "ERROR"}",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bip47/bip47.dart';
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -20,6 +21,7 @@ import 'package:stackwallet/providers/wallet/public_private_balance_state_provid
|
|||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/address_utils.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
|
||||
|
@ -162,12 +164,17 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
}
|
||||
|
||||
void _updatePreviewButtonState(String? address, Decimal? amount) {
|
||||
final isValidAddress = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.validateAddress(address ?? "");
|
||||
ref.read(previewTxButtonStateProvider.state).state =
|
||||
(isValidAddress && amount != null && amount > Decimal.zero);
|
||||
if (isPaynymSend) {
|
||||
ref.read(previewTxButtonStateProvider.state).state =
|
||||
(amount != null && amount > Decimal.zero);
|
||||
} else {
|
||||
final isValidAddress = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.validateAddress(address ?? "");
|
||||
ref.read(previewTxButtonStateProvider.state).state =
|
||||
(isValidAddress && amount != null && amount > Decimal.zero);
|
||||
}
|
||||
}
|
||||
|
||||
late Future<String> _calculateFeesFuture;
|
||||
|
@ -281,6 +288,226 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
return null;
|
||||
}
|
||||
|
||||
Future<void> _previewTransaction() async {
|
||||
// wait for keyboard to disappear
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
// // TODO: remove the need for this!!
|
||||
// final bool isOwnAddress =
|
||||
// await manager.isOwnAddress(_address!);
|
||||
// if (isOwnAddress && coin != Coin.dogecoinTestNet) {
|
||||
// await showDialog<dynamic>(
|
||||
// context: context,
|
||||
// useSafeArea: false,
|
||||
// barrierDismissible: true,
|
||||
// builder: (context) {
|
||||
// return StackDialog(
|
||||
// title: "Transaction failed",
|
||||
// message:
|
||||
// "Sending to self is currently disabled",
|
||||
// rightButton: TextButton(
|
||||
// style: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .getSecondaryEnabledButtonColor(
|
||||
// context),
|
||||
// child: Text(
|
||||
// "Ok",
|
||||
// style: STextStyles.button(
|
||||
// context)
|
||||
// .copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<
|
||||
// StackColors>()!
|
||||
// .accentColorDark),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pop();
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
final amount = Format.decimalAmountToSatoshis(_amountToSend!, coin);
|
||||
int availableBalance;
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet)) {
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
availableBalance = Format.decimalAmountToSatoshis(
|
||||
(manager.wallet as FiroWallet).availablePrivateBalance(), coin);
|
||||
} else {
|
||||
availableBalance = Format.decimalAmountToSatoshis(
|
||||
(manager.wallet as FiroWallet).availablePublicBalance(), coin);
|
||||
}
|
||||
} else {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(manager.balance.getSpendable(), coin);
|
||||
}
|
||||
|
||||
// confirm send all
|
||||
if (amount == availableBalance) {
|
||||
final bool? shouldSendAll = await showDialog<bool>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return StackDialog(
|
||||
title: "Confirm send all",
|
||||
message:
|
||||
"You are about to send your entire balance. Would you like to continue?",
|
||||
leftButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Yes",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (shouldSendAll == null || shouldSendAll == false) {
|
||||
// cancel preview
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
bool wasCancelled = false;
|
||||
|
||||
unawaited(
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return BuildingTransactionDialog(
|
||||
onCancel: () {
|
||||
wasCancelled = true;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> txData;
|
||||
|
||||
if (isPaynymSend) {
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
final paymentCode = PaymentCode.fromPaymentCode(
|
||||
widget.accountLite!.code,
|
||||
wallet.networkType,
|
||||
);
|
||||
final feeRate = ref.read(feeRateTypeStateProvider);
|
||||
txData = await wallet.preparePaymentCodeSend(
|
||||
paymentCode: paymentCode,
|
||||
satoshiAmount: amount,
|
||||
args: {"feeRate": feeRate},
|
||||
);
|
||||
} else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
txData = await (manager.wallet as FiroWallet).prepareSendPublic(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {"feeRate": ref.read(feeRateTypeStateProvider)},
|
||||
);
|
||||
} else {
|
||||
txData = await manager.prepareSend(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {"feeRate": ref.read(feeRateTypeStateProvider)},
|
||||
);
|
||||
}
|
||||
|
||||
if (!wasCancelled && mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
txData["note"] = noteController.text;
|
||||
if (isPaynymSend) {
|
||||
txData["paynymAccountLite"] = widget.accountLite!;
|
||||
} else {
|
||||
txData["address"] = _address;
|
||||
}
|
||||
|
||||
unawaited(Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => ConfirmTransactionView(
|
||||
transactionInfo: txData,
|
||||
walletId: walletId,
|
||||
isPaynymTransaction: isPaynymSend,
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: ConfirmTransactionView.routeName,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
|
||||
unawaited(showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return StackDialog(
|
||||
title: "Transaction failed",
|
||||
message: e.toString(),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool get isPaynymSend => widget.accountLite != null;
|
||||
|
||||
@override
|
||||
|
@ -1532,253 +1759,7 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
onPressed: ref
|
||||
.watch(previewTxButtonStateProvider.state)
|
||||
.state
|
||||
? () async {
|
||||
// wait for keyboard to disappear
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId);
|
||||
|
||||
// // TODO: remove the need for this!!
|
||||
// final bool isOwnAddress =
|
||||
// await manager.isOwnAddress(_address!);
|
||||
// if (isOwnAddress && coin != Coin.dogecoinTestNet) {
|
||||
// await showDialog<dynamic>(
|
||||
// context: context,
|
||||
// useSafeArea: false,
|
||||
// barrierDismissible: true,
|
||||
// builder: (context) {
|
||||
// return StackDialog(
|
||||
// title: "Transaction failed",
|
||||
// message:
|
||||
// "Sending to self is currently disabled",
|
||||
// rightButton: TextButton(
|
||||
// style: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .getSecondaryEnabledButtonColor(
|
||||
// context),
|
||||
// child: Text(
|
||||
// "Ok",
|
||||
// style: STextStyles.button(
|
||||
// context)
|
||||
// .copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<
|
||||
// StackColors>()!
|
||||
// .accentColorDark),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pop();
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
final amount =
|
||||
Format.decimalAmountToSatoshis(
|
||||
_amountToSend!, coin);
|
||||
int availableBalance;
|
||||
if ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet)) {
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private") {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
(manager.wallet as FiroWallet)
|
||||
.availablePrivateBalance(),
|
||||
coin);
|
||||
} else {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
(manager.wallet as FiroWallet)
|
||||
.availablePublicBalance(),
|
||||
coin);
|
||||
}
|
||||
} else {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
manager.balance.getSpendable(),
|
||||
coin);
|
||||
}
|
||||
|
||||
// confirm send all
|
||||
if (amount == availableBalance) {
|
||||
final bool? shouldSendAll =
|
||||
await showDialog<bool>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return StackDialog(
|
||||
title: "Confirm send all",
|
||||
message:
|
||||
"You are about to send your entire balance. Would you like to continue?",
|
||||
leftButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(
|
||||
context),
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pop(false);
|
||||
},
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(
|
||||
context),
|
||||
child: Text(
|
||||
"Yes",
|
||||
style:
|
||||
STextStyles.button(context),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (shouldSendAll == null ||
|
||||
shouldSendAll == false) {
|
||||
// cancel preview
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
bool wasCancelled = false;
|
||||
|
||||
unawaited(showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return BuildingTransactionDialog(
|
||||
onCancel: () {
|
||||
wasCancelled = true;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
|
||||
Map<String, dynamic> txData;
|
||||
|
||||
if ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) &&
|
||||
ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state !=
|
||||
"Private") {
|
||||
txData =
|
||||
await (manager.wallet as FiroWallet)
|
||||
.prepareSendPublic(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {
|
||||
"feeRate": ref
|
||||
.read(feeRateTypeStateProvider)
|
||||
},
|
||||
);
|
||||
} else {
|
||||
txData = await manager.prepareSend(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {
|
||||
"feeRate": ref
|
||||
.read(feeRateTypeStateProvider)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!wasCancelled && mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
txData["note"] = noteController.text;
|
||||
txData["address"] = _address;
|
||||
|
||||
unawaited(Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) =>
|
||||
ConfirmTransactionView(
|
||||
transactionInfo: txData,
|
||||
walletId: walletId,
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: ConfirmTransactionView
|
||||
.routeName,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(context).pop();
|
||||
|
||||
unawaited(showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return StackDialog(
|
||||
title: "Transaction failed",
|
||||
message: e.toString(),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(
|
||||
context),
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(
|
||||
context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
? _previewTransaction
|
||||
: null,
|
||||
style: ref
|
||||
.watch(previewTxButtonStateProvider.state)
|
||||
|
|
|
@ -105,6 +105,9 @@ mixin PaynymWalletInterface {
|
|||
_checkChangeAddressForTransactions = checkChangeAddressForTransactions;
|
||||
}
|
||||
|
||||
// convenience getter
|
||||
btc_dart.NetworkType get networkType => _network;
|
||||
|
||||
// generate bip32 payment code root
|
||||
Future<bip32.BIP32> getRootNode({
|
||||
required List<String> mnemonic,
|
||||
|
@ -147,7 +150,7 @@ mixin PaynymWalletInterface {
|
|||
return Format.uint8listToString(bytes);
|
||||
}
|
||||
|
||||
Future<Future<Map<String, dynamic>>> preparePaymentCodeSend(
|
||||
Future<Map<String, dynamic>> preparePaymentCodeSend(
|
||||
{required PaymentCode paymentCode,
|
||||
required int satoshiAmount,
|
||||
Map<String, dynamic>? args}) async {
|
||||
|
@ -163,7 +166,10 @@ mixin PaynymWalletInterface {
|
|||
);
|
||||
|
||||
return _prepareSend(
|
||||
address: sendToAddress.value, satoshiAmount: satoshiAmount);
|
||||
address: sendToAddress.value,
|
||||
satoshiAmount: satoshiAmount,
|
||||
args: args,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue