mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 19:39:22 +00:00
refactor send screen address validation to take into account not being able to send from lelantus to spark directly
This commit is contained in:
parent
97ff9ecf8b
commit
02cb79c6a3
4 changed files with 376 additions and 341 deletions
|
@ -121,38 +121,173 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
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<UTXO> selectedUTXOs = {};
|
||||
|
||||
Future<void> _scanQr() async {
|
||||
try {
|
||||
// ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = false;
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
// Future<void>.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<SendView> {
|
|||
);
|
||||
}
|
||||
} 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<SendView> {
|
|||
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<SendView> {
|
|||
if (_data != null && _data!.contactLabel == address) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (address.isNotEmpty &&
|
||||
!ref
|
||||
.read(pWallets)
|
||||
|
@ -241,24 +377,22 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
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<SendView> {
|
|||
);
|
||||
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<SendView> {
|
|||
} 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<SendView> {
|
|||
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<SendView> {
|
|||
|
||||
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<SendView> {
|
|||
} else {
|
||||
setState(() {
|
||||
_calculateFeesFuture = calculateFees(
|
||||
_amountToSend!,
|
||||
ref.read(pSendAmount)!,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -1077,8 +1211,7 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
),
|
||||
onChanged: (newValue) {
|
||||
_address = newValue.trim();
|
||||
_updatePreviewButtonState(
|
||||
_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = newValue.isNotEmpty;
|
||||
|
@ -1115,9 +1248,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
onTap: () {
|
||||
sendToController.text = "";
|
||||
_address = "";
|
||||
_updatePreviewButtonState(
|
||||
_address,
|
||||
_amountToSend);
|
||||
_setValidAddressProviders(
|
||||
_address);
|
||||
setState(() {
|
||||
_addressToggleFlag =
|
||||
false;
|
||||
|
@ -1159,9 +1291,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
content.trim();
|
||||
_address = content.trim();
|
||||
|
||||
_updatePreviewButtonState(
|
||||
_address,
|
||||
_amountToSend);
|
||||
_setValidAddressProviders(
|
||||
_address);
|
||||
setState(() {
|
||||
_addressToggleFlag =
|
||||
sendToController
|
||||
|
@ -1195,139 +1326,9 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
"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<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult =
|
||||
await scanner.scan();
|
||||
|
||||
// Future<void>.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<SendView> {
|
|||
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<SendView> {
|
|||
),
|
||||
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<SendView> {
|
|||
// ? 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<SendView> {
|
|||
.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<SendView> {
|
|||
amount: (Decimal.tryParse(
|
||||
cryptoAmountController
|
||||
.text) ??
|
||||
_amountToSend
|
||||
ref
|
||||
.watch(pSendAmount)
|
||||
?.decimal ??
|
||||
Decimal.zero)
|
||||
.toAmount(
|
||||
|
@ -2239,14 +2228,10 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
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<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context)
|
||||
|
|
|
@ -348,7 +348,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
.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<TokenSendView> {
|
|||
),
|
||||
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<StackColors>()!
|
||||
|
|
|
@ -114,7 +114,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
String? _note;
|
||||
String? _onChainNote;
|
||||
|
||||
Amount? _amountToSend;
|
||||
Amount? _cachedAmountToSend;
|
||||
String? _address;
|
||||
|
||||
|
@ -125,8 +124,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
|
||||
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<DesktopSend> {
|
|||
Future<void> 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<DesktopSend> {
|
|||
} 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<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
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<void> scanQr() async {
|
||||
try {
|
||||
|
@ -639,10 +613,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
_address = qrResult.rawContent;
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
@ -670,6 +643,25 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
}
|
||||
|
||||
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<void> 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<DesktopSend> {
|
|||
sendToController.text = content;
|
||||
_address = content;
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
@ -715,28 +707,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
// Format.decimalAmountToSatoshis(
|
||||
// _amountToSend!));
|
||||
// });
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
ref.read(pSendAmount.notifier).state = amount;
|
||||
}
|
||||
|
||||
Future<void> sendAllTapped() async {
|
||||
|
@ -784,11 +777,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
|
||||
void _showDesktopCoinControl() async {
|
||||
final amount = ref.read(pSendAmount);
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => DesktopCoinControlUseDialog(
|
||||
walletId: widget.walletId,
|
||||
amountToSend: _amountToSend,
|
||||
amountToSend: amount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -797,7 +791,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
|
||||
_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<DesktopSend> {
|
|||
),
|
||||
onChanged: (newValue) {
|
||||
_address = newValue;
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = newValue.isNotEmpty;
|
||||
|
@ -1303,8 +1298,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
onTap: () {
|
||||
sendToController.text = "";
|
||||
_address = "";
|
||||
_updatePreviewButtonState(
|
||||
_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = false;
|
||||
});
|
||||
|
@ -1365,10 +1359,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
|
||||
_address = entry.address;
|
||||
|
||||
_updatePreviewButtonState(
|
||||
_address,
|
||||
_amountToSend,
|
||||
);
|
||||
_setValidAddressProviders(_address);
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = true;
|
||||
|
@ -1393,9 +1384,44 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
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<DesktopSend> {
|
|||
},
|
||||
),
|
||||
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<DesktopSend> {
|
|||
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,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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<bool>((_) {
|
||||
return false;
|
||||
final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
|
||||
final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false);
|
||||
final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false);
|
||||
|
||||
final pPreviewTxButtonEnabled =
|
||||
Provider.autoDispose.family<bool, Coin>((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<bool>((_) {
|
||||
|
|
Loading…
Reference in a new issue