diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index b9878e5cd..ea82e5b65 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -121,38 +121,173 @@ class _SendViewState extends ConsumerState { late final bool isStellar; late final bool isFiro; - Amount? _amountToSend; Amount? _cachedAmountToSend; String? _address; bool _addressToggleFlag = false; - bool _isSparkAddress = false; bool _cryptoAmountChangeLock = false; late VoidCallback onCryptoAmountChanged; Set selectedUTXOs = {}; + Future _scanQr() async { + try { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + final qrResult = await scanner.scan(); + + // Future.delayed( + // const Duration(seconds: 2), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + + Logging.instance.log("qrResult content: ${qrResult.rawContent}", + level: LogLevel.Info); + + final results = AddressUtils.parseUri(qrResult.rawContent); + + Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info); + + if (results.isNotEmpty && results["scheme"] == coin.uriScheme) { + // auto fill address + _address = (results["address"] ?? "").trim(); + sendToController.text = _address!; + + // autofill notes field + if (results["message"] != null) { + noteController.text = results["message"]!; + } else if (results["label"] != null) { + noteController.text = results["label"]!; + } + + // autofill amount field + if (results["amount"] != null) { + final Amount amount = Decimal.parse(results["amount"]!).toAmount( + fractionDigits: coin.decimals, + ); + cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( + amount, + withUnitName: false, + ); + ref.read(pSendAmount.notifier).state = amount; + } + + _setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + + // now check for non standard encoded basic address + } else if (ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .validateAddress(qrResult.rawContent)) { + _address = qrResult.rawContent.trim(); + sendToController.text = _address ?? ""; + + _setValidAddressProviders(_address); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } + } on PlatformException catch (e, s) { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true; + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning); + } + } + + void _fiatFieldChanged(String baseAmountString) { + final baseAmount = Amount.tryParseFiatString( + baseAmountString, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + final Amount? amount; + if (baseAmount != null) { + final Decimal _price = + ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + + if (_price == Decimal.zero) { + amount = 0.toAmountAsRaw(fractionDigits: coin.decimals); + } else { + amount = baseAmount <= Amount.zero + ? 0.toAmountAsRaw(fractionDigits: coin.decimals) + : (baseAmount.decimal / _price) + .toDecimal( + scaleOnInfinitePrecision: coin.decimals, + ) + .toAmount(fractionDigits: coin.decimals); + } + if (_cachedAmountToSend != null && _cachedAmountToSend == amount) { + return; + } + _cachedAmountToSend = amount; + Logging.instance + .log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info); + + final amountString = ref.read(pAmountFormatter(coin)).format( + amount, + withUnitName: false, + ); + + _cryptoAmountChangeLock = true; + cryptoAmountController.text = amountString; + _cryptoAmountChangeLock = false; + } else { + amount = 0.toAmountAsRaw(fractionDigits: coin.decimals); + _cryptoAmountChangeLock = true; + cryptoAmountController.text = ""; + _cryptoAmountChangeLock = false; + } + // setState(() { + // _calculateFeesFuture = calculateFees( + // Format.decimalAmountToSatoshis( + // _amountToSend!)); + // }); + ref.read(pSendAmount.notifier).state = amount; + } + void _cryptoAmountChanged() async { if (!_cryptoAmountChangeLock) { final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( cryptoAmountController.text, ); + final Amount? amount; if (cryptoAmount != null) { - _amountToSend = cryptoAmount; - if (_cachedAmountToSend != null && - _cachedAmountToSend == _amountToSend) { + amount = cryptoAmount; + if (_cachedAmountToSend != null && _cachedAmountToSend == amount) { return; } - _cachedAmountToSend = _amountToSend; - Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", + _cachedAmountToSend = amount; + Logging.instance.log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info); final price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (price > Decimal.zero) { - baseAmountController.text = (_amountToSend!.decimal * price) + baseAmountController.text = (amount!.decimal * price) .toAmount( fractionDigits: 2, ) @@ -161,20 +296,20 @@ class _SendViewState extends ConsumerState { ); } } else { - _amountToSend = null; + amount = null; baseAmountController.text = ""; } - _updatePreviewButtonState(_address, _amountToSend); + ref.read(pSendAmount.notifier).state = amount; _cryptoAmountChangedFeeUpdateTimer?.cancel(); _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { if (coin != Coin.epicCash && !_baseFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( - _amountToSend == null + amount == null ? 0.toAmountAsRaw(fractionDigits: coin.decimals) - : _amountToSend!, + : amount!, ); }); } @@ -193,9 +328,9 @@ class _SendViewState extends ConsumerState { if (coin != Coin.epicCash && !_cryptoFocus.hasFocus) { setState(() { _calculateFeesFuture = calculateFees( - _amountToSend == null + ref.read(pSendAmount) == null ? 0.toAmountAsRaw(fractionDigits: coin.decimals) - : _amountToSend!, + : ref.read(pSendAmount)!, ); }); } @@ -230,6 +365,7 @@ class _SendViewState extends ConsumerState { if (_data != null && _data!.contactLabel == address) { return null; } + if (address.isNotEmpty && !ref .read(pWallets) @@ -241,24 +377,22 @@ class _SendViewState extends ConsumerState { return null; } - void _updatePreviewButtonState(String? address, Amount? amount) { + void _setValidAddressProviders(String? address) { if (isPaynymSend) { - ref.read(previewTxButtonStateProvider.state).state = - (amount != null && amount > Amount.zero); + ref.read(pValidSendToAddress.notifier).state = true; } else { - final walletCurrency = - ref.read(pWallets).getWallet(walletId).cryptoCurrency; - final isValidAddress = walletCurrency.validateAddress(address ?? ""); + final wallet = ref.read(pWallets).getWallet(walletId); + if (wallet is SparkInterface) { + ref.read(pValidSparkSendToAddress.notifier).state = + SparkInterface.validateSparkAddress( + address: address ?? "", + isTestNet: + wallet.cryptoCurrency.network == CryptoCurrencyNetwork.test, + ); + } - _isSparkAddress = isValidAddress - ? SparkInterface.validateSparkAddress( - address: address!, - isTestNet: walletCurrency.network == CryptoCurrencyNetwork.test, - ) - : false; - - ref.read(previewTxButtonStateProvider.state).state = - (isValidAddress && amount != null && amount > Amount.zero); + ref.read(pValidSendToAddress.notifier).state = + wallet.cryptoCurrency.validateAddress(address ?? ""); } } @@ -392,7 +526,7 @@ class _SendViewState extends ConsumerState { ); final wallet = ref.read(pWallets).getWallet(walletId); - final Amount amount = _amountToSend!; + final Amount amount = ref.read(pSendAmount)!; final Amount availableBalance; if (isFiro) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) { @@ -524,7 +658,7 @@ class _SendViewState extends ConsumerState { } else if (wallet is FiroWallet) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.public: - if (_isSparkAddress) { + if (ref.read(pValidSparkSendToAddress)) { txDataFuture = wallet.prepareSparkMintTransaction( txData: TxData( sparkRecipients: [ @@ -570,10 +704,10 @@ class _SendViewState extends ConsumerState { case FiroType.spark: txDataFuture = wallet.prepareSendSpark( txData: TxData( - recipients: _isSparkAddress + recipients: ref.read(pValidSparkSendToAddress) ? null : [(address: _address!, amount: amount)], - sparkRecipients: _isSparkAddress + sparkRecipients: ref.read(pValidSparkSendToAddress) ? [ ( address: _address!, @@ -807,7 +941,7 @@ class _SendViewState extends ConsumerState { if (isFiro) { ref.listen(publicPrivateBalanceStateProvider, (previous, next) { - if (_amountToSend == null) { + if (ref.read(pSendAmount) == null) { setState(() { _calculateFeesFuture = calculateFees(0.toAmountAsRaw(fractionDigits: coin.decimals)); @@ -815,7 +949,7 @@ class _SendViewState extends ConsumerState { } else { setState(() { _calculateFeesFuture = calculateFees( - _amountToSend!, + ref.read(pSendAmount)!, ); }); } @@ -1077,8 +1211,7 @@ class _SendViewState extends ConsumerState { ), onChanged: (newValue) { _address = newValue.trim(); - _updatePreviewButtonState( - _address, _amountToSend); + _setValidAddressProviders(_address); setState(() { _addressToggleFlag = newValue.isNotEmpty; @@ -1115,9 +1248,8 @@ class _SendViewState extends ConsumerState { onTap: () { sendToController.text = ""; _address = ""; - _updatePreviewButtonState( - _address, - _amountToSend); + _setValidAddressProviders( + _address); setState(() { _addressToggleFlag = false; @@ -1159,9 +1291,8 @@ class _SendViewState extends ConsumerState { content.trim(); _address = content.trim(); - _updatePreviewButtonState( - _address, - _amountToSend); + _setValidAddressProviders( + _address); setState(() { _addressToggleFlag = sendToController @@ -1195,139 +1326,9 @@ class _SendViewState extends ConsumerState { "Scan QR Button. Opens Camera For Scanning QR Code.", key: const Key( "sendViewScanQrButtonKey"), - onTap: () async { - try { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); - } - - final qrResult = - await scanner.scan(); - - // Future.delayed( - // const Duration(seconds: 2), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info); - - final results = - AddressUtils.parseUri( - qrResult.rawContent); - - Logging.instance.log( - "qrResult parsed: $results", - level: LogLevel.Info); - - if (results.isNotEmpty && - results["scheme"] == - coin.uriScheme) { - // auto fill address - _address = - (results["address"] ?? - "") - .trim(); - sendToController.text = - _address!; - - // autofill notes field - if (results["message"] != - null) { - noteController.text = - results["message"]!; - } else if (results[ - "label"] != - null) { - noteController.text = - results["label"]!; - } - - // autofill amount field - if (results["amount"] != - null) { - final Amount amount = - Decimal.parse(results[ - "amount"]!) - .toAmount( - fractionDigits: - coin.decimals, - ); - cryptoAmountController - .text = - ref - .read( - pAmountFormatter( - coin)) - .format( - amount, - withUnitName: - false, - ); - _amountToSend = amount; - } - - _updatePreviewButtonState( - _address, - _amountToSend); - setState(() { - _addressToggleFlag = - sendToController - .text.isNotEmpty; - }); - - // now check for non standard encoded basic address - } else if (ref - .read(pWallets) - .getWallet(walletId) - .cryptoCurrency - .validateAddress(qrResult - .rawContent)) { - _address = qrResult - .rawContent - .trim(); - sendToController.text = - _address ?? ""; - - _updatePreviewButtonState( - _address, - _amountToSend); - setState(() { - _addressToggleFlag = - sendToController - .text.isNotEmpty; - }); - } - } on PlatformException catch (e, s) { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true; - // here we ignore the exception caused by not giving permission - // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning); - } - }, + onTap: _scanQr, child: const QrCodeIcon(), - ) + ), ], ), ), @@ -1338,7 +1339,11 @@ class _SendViewState extends ConsumerState { const SizedBox( height: 10, ), - if (isStellar || _isSparkAddress) + if (isStellar || + (ref.watch(pValidSparkSendToAddress) && + ref.watch( + publicPrivateBalanceStateProvider) != + FiroType.lelantus)) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1419,9 +1424,50 @@ class _SendViewState extends ConsumerState { ), Builder( builder: (_) { - final error = _updateInvalidAddressText( - _address ?? "", - ); + final String? error; + + if (_address == null || _address!.isEmpty) { + error = null; + } else if (isFiro) { + if (ref.watch( + publicPrivateBalanceStateProvider) == + FiroType.lelantus) { + if (_data != null && + _data!.contactLabel == _address) { + error = SparkInterface.validateSparkAddress( + address: _data!.address, + isTestNet: coin.isTestNet) + ? "Unsupported" + : null; + } else if (ref + .watch(pValidSparkSendToAddress)) { + error = "Unsupported"; + } else { + error = ref.watch(pValidSendToAddress) + ? null + : "Invalid address"; + } + } else { + if (_data != null && + _data!.contactLabel == _address) { + error = null; + } else if (!ref.watch(pValidSendToAddress) && + !ref.watch(pValidSparkSendToAddress)) { + error = "Invalid address"; + } else { + error = null; + } + } + } else { + if (_data != null && + _data!.contactLabel == _address) { + error = null; + } else if (!ref.watch(pValidSendToAddress)) { + error = "Invalid address"; + } else { + error = null; + } + } if (error == null || error.isEmpty) { return Container(); @@ -1737,65 +1783,7 @@ class _SendViewState extends ConsumerState { // ? newValue // : oldValue), ], - onChanged: (baseAmountString) { - final baseAmount = Amount.tryParseFiatString( - baseAmountString, - locale: locale, - ); - if (baseAmount != null) { - final Decimal _price = ref - .read(priceAnd24hChangeNotifierProvider) - .getPrice(coin) - .item1; - - if (_price == Decimal.zero) { - _amountToSend = 0.toAmountAsRaw( - fractionDigits: coin.decimals); - } else { - _amountToSend = baseAmount <= Amount.zero - ? 0.toAmountAsRaw( - fractionDigits: coin.decimals) - : (baseAmount.decimal / _price) - .toDecimal( - scaleOnInfinitePrecision: - coin.decimals, - ) - .toAmount( - fractionDigits: coin.decimals); - } - if (_cachedAmountToSend != null && - _cachedAmountToSend == _amountToSend) { - return; - } - _cachedAmountToSend = _amountToSend; - Logging.instance.log( - "it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info); - - final amountString = - ref.read(pAmountFormatter(coin)).format( - _amountToSend!, - withUnitName: false, - ); - - _cryptoAmountChangeLock = true; - cryptoAmountController.text = amountString; - _cryptoAmountChangeLock = false; - } else { - _amountToSend = 0.toAmountAsRaw( - fractionDigits: coin.decimals); - _cryptoAmountChangeLock = true; - cryptoAmountController.text = ""; - _cryptoAmountChangeLock = false; - } - // setState(() { - // _calculateFeesFuture = calculateFees( - // Format.decimalAmountToSatoshis( - // _amountToSend!)); - // }); - _updatePreviewButtonState( - _address, _amountToSend); - }, + onChanged: _fiatFieldChanged, decoration: InputDecoration( contentPadding: const EdgeInsets.only( top: 12, @@ -1860,8 +1848,8 @@ class _SendViewState extends ConsumerState { .spendable; Amount? amount; - if (_amountToSend != null) { - amount = _amountToSend!; + if (ref.read(pSendAmount) != null) { + amount = ref.read(pSendAmount)!; if (spendable == amount) { // this is now a send all @@ -2075,7 +2063,8 @@ class _SendViewState extends ConsumerState { amount: (Decimal.tryParse( cryptoAmountController .text) ?? - _amountToSend + ref + .watch(pSendAmount) ?.decimal ?? Decimal.zero) .toAmount( @@ -2239,14 +2228,10 @@ class _SendViewState extends ConsumerState { height: 12, ), TextButton( - onPressed: ref - .watch(previewTxButtonStateProvider.state) - .state + onPressed: ref.watch(pPreviewTxButtonEnabled(coin)) ? _previewTransaction : null, - style: ref - .watch(previewTxButtonStateProvider.state) - .state + style: ref.watch(pPreviewTxButtonEnabled(coin)) ? Theme.of(context) .extension()! .getPrimaryEnabledButtonStyle(context) diff --git a/lib/pages/send_view/token_send_view.dart b/lib/pages/send_view/token_send_view.dart index b05beeb25..c1ea39345 100644 --- a/lib/pages/send_view/token_send_view.dart +++ b/lib/pages/send_view/token_send_view.dart @@ -348,7 +348,7 @@ class _TokenSendViewState extends ConsumerState { .getWallet(walletId) .cryptoCurrency .validateAddress(address ?? ""); - ref.read(previewTxButtonStateProvider.state).state = + ref.read(previewTokenTxButtonStateProvider.state).state = (isValidAddress && amount != null && amount > Amount.zero); } @@ -1227,12 +1227,14 @@ class _TokenSendViewState extends ConsumerState { ), TextButton( onPressed: ref - .watch(previewTxButtonStateProvider.state) + .watch( + previewTokenTxButtonStateProvider.state) .state ? _previewTransaction : null, style: ref - .watch(previewTxButtonStateProvider.state) + .watch( + previewTokenTxButtonStateProvider.state) .state ? Theme.of(context) .extension()! diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index dd596037e..d638c8690 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -114,7 +114,6 @@ class _DesktopSendState extends ConsumerState { String? _note; String? _onChainNote; - Amount? _amountToSend; Amount? _cachedAmountToSend; String? _address; @@ -125,8 +124,6 @@ class _DesktopSendState extends ConsumerState { bool get isPaynymSend => widget.accountLite != null; - bool _isSparkAddress = false; - bool isCustomFee = false; int customFeeRate = 1; (FeeRateType, String?, String?)? feeSelectionResult; @@ -141,7 +138,7 @@ class _DesktopSendState extends ConsumerState { Future previewSend() async { final wallet = ref.read(pWallets).getWallet(walletId); - final Amount amount = _amountToSend!; + final Amount amount = ref.read(pSendAmount)!; final Amount availableBalance; if ((coin == Coin.firo || coin == Coin.firoTestNet)) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) { @@ -321,7 +318,7 @@ class _DesktopSendState extends ConsumerState { } else if (wallet is FiroWallet) { switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.public: - if (_isSparkAddress) { + if (ref.read(pValidSparkSendToAddress)) { txDataFuture = wallet.prepareSparkMintTransaction( txData: TxData( sparkRecipients: [ @@ -367,10 +364,10 @@ class _DesktopSendState extends ConsumerState { case FiroType.spark: txDataFuture = wallet.prepareSendSpark( txData: TxData( - recipients: _isSparkAddress + recipients: ref.read(pValidSparkSendToAddress) ? null : [(address: _address!, amount: amount)], - sparkRecipients: _isSparkAddress + sparkRecipients: ref.read(pValidSparkSendToAddress) ? [ ( address: _address!, @@ -533,21 +530,21 @@ class _DesktopSendState extends ConsumerState { final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( cryptoAmountController.text, ); + final Amount? amount; if (cryptoAmount != null) { - _amountToSend = cryptoAmount; - if (_cachedAmountToSend != null && - _cachedAmountToSend == _amountToSend) { + amount = cryptoAmount; + if (_cachedAmountToSend != null && _cachedAmountToSend == amount) { return; } - Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", + Logging.instance.log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info); - _cachedAmountToSend = _amountToSend; + _cachedAmountToSend = amount; final price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (price > Decimal.zero) { - final String fiatAmountString = (_amountToSend!.decimal * price) + final String fiatAmountString = (amount!.decimal * price) .toAmount(fractionDigits: 2) .fiatString( locale: ref.read(localeServiceChangeNotifierProvider).locale, @@ -556,52 +553,29 @@ class _DesktopSendState extends ConsumerState { baseAmountController.text = fiatAmountString; } } else { - _amountToSend = null; + amount = null; _cachedAmountToSend = null; baseAmountController.text = ""; } - _updatePreviewButtonState(_address, _amountToSend); + ref.read(pSendAmount.notifier).state = amount; } } - String? _updateInvalidAddressText(String address) { - if (_data != null && _data!.contactLabel == address) { - return null; - } - if (address.isNotEmpty && - !ref - .read(pWallets) - .getWallet(walletId) - .cryptoCurrency - .validateAddress(address)) { - return "Invalid address"; - } - return null; - } - - void _updatePreviewButtonState(String? address, Amount? amount) { - if (isPaynymSend) { - ref.read(previewTxButtonStateProvider.state).state = - (amount != null && amount > Amount.zero); - } else { - final walletCurrency = - ref.read(pWallets).getWallet(walletId).cryptoCurrency; - final isValidAddress = walletCurrency.validateAddress(address ?? ""); - - _isSparkAddress = isValidAddress && - ref.read(publicPrivateBalanceStateProvider.state).state != - FiroType.lelantus - ? SparkInterface.validateSparkAddress( - address: address!, - isTestNet: walletCurrency.network == CryptoCurrencyNetwork.test, - ) - : false; - - ref.read(previewTxButtonStateProvider.state).state = - (isValidAddress && amount != null && amount > Amount.zero); - } - } + // String? _updateInvalidAddressText(String address) { + // if (_data != null && _data!.contactLabel == address) { + // return null; + // } + // if (address.isNotEmpty && + // !ref + // .read(pWallets) + // .getWallet(walletId) + // .cryptoCurrency + // .validateAddress(address)) { + // return "Invalid address"; + // } + // return null; + // } Future scanQr() async { try { @@ -639,10 +613,9 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController.text = ref .read(pAmountFormatter(coin)) .format(amount, withUnitName: false); - _amountToSend = amount; + ref.read(pSendAmount.notifier).state = amount; } - _updatePreviewButtonState(_address, _amountToSend); setState(() { _addressToggleFlag = sendToController.text.isNotEmpty; }); @@ -656,7 +629,7 @@ class _DesktopSendState extends ConsumerState { _address = qrResult.rawContent; sendToController.text = _address ?? ""; - _updatePreviewButtonState(_address, _amountToSend); + _setValidAddressProviders(_address); setState(() { _addressToggleFlag = sendToController.text.isNotEmpty; }); @@ -670,6 +643,25 @@ class _DesktopSendState extends ConsumerState { } } + void _setValidAddressProviders(String? address) { + if (isPaynymSend) { + ref.read(pValidSendToAddress.notifier).state = true; + } else { + final wallet = ref.read(pWallets).getWallet(walletId); + if (wallet is SparkInterface) { + ref.read(pValidSparkSendToAddress.notifier).state = + SparkInterface.validateSparkAddress( + address: address ?? "", + isTestNet: + wallet.cryptoCurrency.network == CryptoCurrencyNetwork.test, + ); + } + + ref.read(pValidSendToAddress.notifier).state = + wallet.cryptoCurrency.validateAddress(address ?? ""); + } + } + Future pasteAddress() async { final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { @@ -686,7 +678,7 @@ class _DesktopSendState extends ConsumerState { sendToController.text = content; _address = content; - _updatePreviewButtonState(_address, _amountToSend); + _setValidAddressProviders(_address); setState(() { _addressToggleFlag = sendToController.text.isNotEmpty; }); @@ -715,28 +707,29 @@ class _DesktopSendState extends ConsumerState { baseAmountString, locale: ref.read(localeServiceChangeNotifierProvider).locale, ); + final Amount? amount; if (baseAmount != null) { final _price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (_price == Decimal.zero) { - _amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals); + amount = Decimal.zero.toAmount(fractionDigits: coin.decimals); } else { - _amountToSend = baseAmount <= Amount.zero + amount = baseAmount <= Amount.zero ? Decimal.zero.toAmount(fractionDigits: coin.decimals) : (baseAmount.decimal / _price) .toDecimal(scaleOnInfinitePrecision: coin.decimals) .toAmount(fractionDigits: coin.decimals); } - if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { + if (_cachedAmountToSend != null && _cachedAmountToSend == amount) { return; } - _cachedAmountToSend = _amountToSend; - Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", - level: LogLevel.Info); + _cachedAmountToSend = amount; + Logging.instance + .log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info); final amountString = ref.read(pAmountFormatter(coin)).format( - _amountToSend!, + amount!, withUnitName: false, ); @@ -744,7 +737,7 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController.text = amountString; _cryptoAmountChangeLock = false; } else { - _amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals); + amount = Decimal.zero.toAmount(fractionDigits: coin.decimals); _cryptoAmountChangeLock = true; cryptoAmountController.text = ""; _cryptoAmountChangeLock = false; @@ -754,7 +747,7 @@ class _DesktopSendState extends ConsumerState { // Format.decimalAmountToSatoshis( // _amountToSend!)); // }); - _updatePreviewButtonState(_address, _amountToSend); + ref.read(pSendAmount.notifier).state = amount; } Future sendAllTapped() async { @@ -784,11 +777,12 @@ class _DesktopSendState extends ConsumerState { } void _showDesktopCoinControl() async { + final amount = ref.read(pSendAmount); await showDialog( context: context, builder: (context) => DesktopCoinControlUseDialog( walletId: widget.walletId, - amountToSend: _amountToSend, + amountToSend: amount, ), ); } @@ -797,7 +791,8 @@ class _DesktopSendState extends ConsumerState { void initState() { WidgetsBinding.instance.addPostFrameCallback((_) { ref.refresh(feeSheetSessionCacheProvider); - ref.read(previewTxButtonStateProvider.state).state = false; + ref.read(pValidSendToAddress.state).state = false; + ref.read(pValidSparkSendToAddress.state).state = false; }); // _calculateFeesFuture = calculateFees(0); @@ -832,20 +827,20 @@ class _DesktopSendState extends ConsumerState { _cryptoFocus.addListener(() { if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { - if (_amountToSend == null) { + if (ref.read(pSendAmount) == null) { ref.refresh(sendAmountProvider); } else { - ref.read(sendAmountProvider.state).state = _amountToSend!; + ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!; } } }); _baseFocus.addListener(() { if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { - if (_amountToSend == null) { + if (ref.read(pSendAmount) == null) { ref.refresh(sendAmountProvider); } else { - ref.read(sendAmountProvider.state).state = _amountToSend!; + ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!; } } }); @@ -1263,7 +1258,7 @@ class _DesktopSendState extends ConsumerState { ), onChanged: (newValue) { _address = newValue; - _updatePreviewButtonState(_address, _amountToSend); + _setValidAddressProviders(_address); setState(() { _addressToggleFlag = newValue.isNotEmpty; @@ -1303,8 +1298,7 @@ class _DesktopSendState extends ConsumerState { onTap: () { sendToController.text = ""; _address = ""; - _updatePreviewButtonState( - _address, _amountToSend); + _setValidAddressProviders(_address); setState(() { _addressToggleFlag = false; }); @@ -1365,10 +1359,7 @@ class _DesktopSendState extends ConsumerState { _address = entry.address; - _updatePreviewButtonState( - _address, - _amountToSend, - ); + _setValidAddressProviders(_address); setState(() { _addressToggleFlag = true; @@ -1393,9 +1384,44 @@ class _DesktopSendState extends ConsumerState { if (!isPaynymSend) Builder( builder: (_) { - final error = _updateInvalidAddressText( - _address ?? "", - ); + final String? error; + + if (_address == null || _address!.isEmpty) { + error = null; + } else if (coin == Coin.firo || coin == Coin.firoTestNet) { + if (ref.watch(publicPrivateBalanceStateProvider) == + FiroType.lelantus) { + if (_data != null && _data!.contactLabel == _address) { + error = SparkInterface.validateSparkAddress( + address: _data!.address, isTestNet: coin.isTestNet) + ? "Lelantus to Spark not supported" + : null; + } else if (ref.watch(pValidSparkSendToAddress)) { + error = "Lelantus to Spark not supported"; + } else { + error = ref.watch(pValidSendToAddress) + ? null + : "Invalid address"; + } + } else { + if (_data != null && _data!.contactLabel == _address) { + error = null; + } else if (!ref.watch(pValidSendToAddress) && + !ref.watch(pValidSparkSendToAddress)) { + error = "Invalid address"; + } else { + error = null; + } + } + } else { + if (_data != null && _data!.contactLabel == _address) { + error = null; + } else if (!ref.watch(pValidSendToAddress)) { + error = "Invalid address"; + } else { + error = null; + } + } if (error == null || error.isEmpty) { return Container(); @@ -1422,16 +1448,16 @@ class _DesktopSendState extends ConsumerState { }, ), if (isStellar || - (_isSparkAddress && + (ref.watch(pValidSparkSendToAddress) && ref.watch(publicPrivateBalanceStateProvider) != - FiroType.public)) + FiroType.lelantus)) const SizedBox( height: 10, ), if (isStellar || - (_isSparkAddress && + (ref.watch(pValidSparkSendToAddress) && ref.watch(publicPrivateBalanceStateProvider) != - FiroType.public)) + FiroType.lelantus)) ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1727,10 +1753,9 @@ class _DesktopSendState extends ConsumerState { PrimaryButton( buttonHeight: ButtonHeight.l, label: "Preview send", - enabled: ref.watch(previewTxButtonStateProvider.state).state, - onPressed: ref.watch(previewTxButtonStateProvider.state).state - ? previewSend - : null, + enabled: ref.watch(pPreviewTxButtonEnabled(coin)), + onPressed: + ref.watch(pPreviewTxButtonEnabled(coin)) ? previewSend : null, ) ], ); diff --git a/lib/providers/ui/preview_tx_button_state_provider.dart b/lib/providers/ui/preview_tx_button_state_provider.dart index 842ac5658..768edf301 100644 --- a/lib/providers/ui/preview_tx_button_state_provider.dart +++ b/lib/providers/ui/preview_tx_button_state_provider.dart @@ -9,9 +9,32 @@ */ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; -final previewTxButtonStateProvider = StateProvider.autoDispose((_) { - return false; +final pSendAmount = StateProvider.autoDispose((_) => null); +final pValidSendToAddress = StateProvider.autoDispose((_) => false); +final pValidSparkSendToAddress = StateProvider.autoDispose((_) => false); + +final pPreviewTxButtonEnabled = + Provider.autoDispose.family((ref, coin) { + final amount = ref.watch(pSendAmount) ?? Amount.zero; + + // TODO [prio=low]: move away from Coin + if (coin == Coin.firo || coin == Coin.firoTestNet) { + if (ref.watch(publicPrivateBalanceStateProvider) == FiroType.lelantus) { + return ref.watch(pValidSendToAddress) && + !ref.watch(pValidSparkSendToAddress) && + amount > Amount.zero; + } else { + return (ref.watch(pValidSendToAddress) || + ref.watch(pValidSparkSendToAddress)) && + amount > Amount.zero; + } + } else { + return ref.watch(pValidSendToAddress) && amount > Amount.zero; + } }); final previewTokenTxButtonStateProvider = StateProvider.autoDispose((_) {