diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 5ce6ccbb0..941c2cb5a 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -10,7 +10,6 @@ import 'dart:async'; import 'dart:io'; -import 'dart:math'; import 'package:bip47/bip47.dart'; import 'package:cw_core/monero_transaction_priority.dart'; @@ -127,27 +126,13 @@ class _SendViewState extends ConsumerState { void _cryptoAmountChanged() async { if (!_cryptoAmountChangeLock) { - String cryptoAmount = cryptoAmountController.text; - if (cryptoAmount.isNotEmpty && - cryptoAmount != "." && - cryptoAmount != ",") { - if (cryptoAmount.startsWith("~")) { - cryptoAmount = cryptoAmount.substring(1); - } - if (cryptoAmount.contains(" ")) { - cryptoAmount = cryptoAmount.split(" ").first; - } - - // ensure we don't shift past minimum atomic value - final shift = min(ref.read(pAmountUnit(coin)).shift, coin.decimals); - - _amountToSend = cryptoAmount.contains(",") - ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")) - .shift(0 - shift) - .toAmount(fractionDigits: coin.decimals) - : Decimal.parse(cryptoAmount) - .shift(0 - shift) - .toAmount(fractionDigits: coin.decimals); + final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( + cryptoAmountController.text, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + coin: coin, + ); + if (cryptoAmount != null) { + _amountToSend = cryptoAmount; if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; @@ -1623,17 +1608,11 @@ class _SendViewState extends ConsumerState { : oldValue), ], onChanged: (baseAmountString) { - if (baseAmountString.isNotEmpty && - baseAmountString != "." && - baseAmountString != ",") { - final Amount baseAmount = - baseAmountString.contains(",") - ? Decimal.parse(baseAmountString - .replaceFirst(",", ".")) - .toAmount(fractionDigits: 2) - : Decimal.parse(baseAmountString) - .toAmount(fractionDigits: 2); - + final baseAmount = Amount.tryParseFiatString( + baseAmountString, + locale: locale, + ); + if (baseAmount != null) { final Decimal _price = ref .read(priceAnd24hChangeNotifierProvider) .getPrice(coin) diff --git a/lib/pages/send_view/token_send_view.dart b/lib/pages/send_view/token_send_view.dart index 4aa3a2225..ad845e9db 100644 --- a/lib/pages/send_view/token_send_view.dart +++ b/lib/pages/send_view/token_send_view.dart @@ -218,16 +218,11 @@ class _TokenSendViewState extends ConsumerState { } void _onFiatAmountFieldChanged(String baseAmountString) { - if (baseAmountString.isNotEmpty && - baseAmountString != "." && - baseAmountString != ",") { - final baseAmount = Amount.fromDecimal( - baseAmountString.contains(",") - ? Decimal.parse(baseAmountString.replaceFirst(",", ".")) - : Decimal.parse(baseAmountString), - fractionDigits: tokenContract.decimals, - ); - + final baseAmount = Amount.tryParseFiatString( + baseAmountString, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + if (baseAmount != null) { final _price = ref .read(priceAnd24hChangeNotifierProvider) .getTokenPrice(tokenContract.address) @@ -272,22 +267,14 @@ class _TokenSendViewState extends ConsumerState { void _cryptoAmountChanged() async { if (!_cryptoAmountChangeLock) { - String cryptoAmount = cryptoAmountController.text; - if (cryptoAmount.isNotEmpty && - cryptoAmount != "." && - cryptoAmount != ",") { - if (cryptoAmount.startsWith("~")) { - cryptoAmount = cryptoAmount.substring(1); - } - if (cryptoAmount.contains(" ")) { - cryptoAmount = cryptoAmount.split(" ").first; - } - - _amountToSend = Amount.fromDecimal( - cryptoAmount.contains(",") - ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")) - : Decimal.parse(cryptoAmount), - fractionDigits: tokenContract.decimals); + final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( + cryptoAmountController.text, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + coin: coin, + ethContract: tokenContract, + ); + if (cryptoAmount != null) { + _amountToSend = cryptoAmount; if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; @@ -1185,7 +1172,7 @@ class _TokenSendViewState extends ConsumerState { ConnectionState.done && snapshot.hasData) { return Text( - "~${snapshot.data! as String}", + "~${snapshot.data!}", style: STextStyles.itemSubtitle( context), diff --git a/lib/utilities/amount/amount.dart b/lib/utilities/amount/amount.dart index e0225d34f..0e8790064 100644 --- a/lib/utilities/amount/amount.dart +++ b/lib/utilities/amount/amount.dart @@ -32,6 +32,39 @@ class Amount { : assert(fractionDigits >= 0), _value = amount.shift(fractionDigits).toBigInt(); + static Amount? tryParseFiatString( + String value, { + required String locale, + }) { + final parts = value.split(" "); + + if (parts.first.isEmpty) { + return null; + } + + String str = parts.first; + if (str.startsWith(RegExp(r'[+-]'))) { + str = str.substring(1); + } + + if (str.isEmpty) { + return null; + } + + // get number symbols for decimal place and group separator + final numberSymbols = numberFormatSymbols[locale] as NumberSymbols? ?? + numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?; + + final groupSeparator = numberSymbols?.GROUP_SEP ?? ","; + final decimalSeparator = numberSymbols?.DECIMAL_SEP ?? "."; + + str = str.replaceAll(groupSeparator, ""); + + final decimalString = str.replaceFirst(decimalSeparator, "."); + + return Decimal.tryParse(decimalString)?.toAmount(fractionDigits: 2); + } + // =========================================================================== // ======= Instance properties =============================================== @@ -67,15 +100,23 @@ class Amount { }) { final wholeNumber = decimal.truncate(); - final String separator = - (numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ?? - (numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?) - ?.DECIMAL_SEP ?? - "."; + // get number symbols for decimal place and group separator + final numberSymbols = numberFormatSymbols[locale] as NumberSymbols? ?? + numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?; + + final String separator = numberSymbols?.DECIMAL_SEP ?? "."; final fraction = decimal - wholeNumber; - return "${wholeNumber.toStringAsFixed(0)}$separator${fraction.toStringAsFixed(2).substring(2)}"; + String wholeNumberString = wholeNumber.toStringAsFixed(0); + // insert group separator + final regex = RegExp(r'\B(?=(\d{3})+(?!\d))'); + wholeNumberString = wholeNumberString.replaceAllMapped( + regex, + (m) => "${m.group(0)}${numberSymbols?.GROUP_SEP ?? ","}", + ); + + return "$wholeNumberString$separator${fraction.toStringAsFixed(2).substring(2)}"; } // String localizedStringAsFixed({ // required String locale, diff --git a/lib/utilities/amount/amount_formatter.dart b/lib/utilities/amount/amount_formatter.dart index 5f8f5c99b..f9cd3f94d 100644 --- a/lib/utilities/amount/amount_formatter.dart +++ b/lib/utilities/amount/amount_formatter.dart @@ -64,7 +64,7 @@ class AmountFormatter { ); } - Amount? amountFrom( + Amount? tryParse( String string, { required String locale, required Coin coin,