Skip to content

Commit da94d0d

Browse files
committed
fix: use locale-aware parsing for buy amount and eth fee inputs
Parse the buy form fiat/crypto amount and the eth fee gwei and gas limit fields with the active locale's decimal and group separators instead of hardcoded comma stripping, which mis-scaled values in comma-decimal locales.
1 parent a50e5bc commit da94d0d

2 files changed

Lines changed: 70 additions & 12 deletions

File tree

lib/pages/buy_view/buy_form.dart

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import '../../services/buy/buy_response.dart';
3131
import '../../services/buy/simplex/simplex_api.dart';
3232
import '../../themes/stack_colors.dart';
3333
import '../../utilities/address_utils.dart';
34+
import '../../utilities/amount/amount.dart';
35+
import '../../utilities/amount/amount_formatter.dart';
3436
import '../../utilities/assets.dart';
3537
import '../../utilities/barcode_scanner_interface.dart';
3638
import '../../utilities/clipboard_interface.dart';
@@ -124,6 +126,30 @@ class _BuyFormState extends ConsumerState<BuyForm> {
124126
// static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
125127
// static String boundedCryptoTicker = '';
126128

129+
/// Parse a buy amount string using the active locale's decimal and group
130+
/// separators. Fiat amounts are parsed via [Amount.tryParseFiatString];
131+
/// crypto amounts reuse the locale-aware [pAmountFormatter] parser so that
132+
/// full crypto precision is preserved.
133+
Decimal? _tryParseBuyAmount(String value) {
134+
if (buyWithFiat) {
135+
return Amount.tryParseFiatString(
136+
value,
137+
locale: ref.read(localeServiceChangeNotifierProvider).locale,
138+
)?.decimal;
139+
}
140+
141+
final cc = coin;
142+
if (cc != null) {
143+
return ref.read(pAmountFormatter(cc)).tryParse(value)?.decimal;
144+
}
145+
146+
// Fall back to fiat-style locale parsing when no coin is available.
147+
return Amount.tryParseFiatString(
148+
value,
149+
locale: ref.read(localeServiceChangeNotifierProvider).locale,
150+
)?.decimal;
151+
}
152+
127153
String _amountOutOfRangeErrorString = "";
128154
void validateAmount() {
129155
if (_buyAmountController.text.isEmpty) {
@@ -133,7 +159,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
133159
return;
134160
}
135161

136-
final value = Decimal.tryParse(_buyAmountController.text);
162+
final value = _tryParseBuyAmount(_buyAmountController.text);
137163
if (value == null) {
138164
setState(() {
139165
_amountOutOfRangeErrorString = "Invalid amount";
@@ -414,11 +440,12 @@ class _BuyFormState extends ConsumerState<BuyForm> {
414440
crypto: selectedCrypto!,
415441
fiat: selectedFiat!,
416442
youPayFiatPrice: buyWithFiat
417-
? Decimal.parse(_buyAmountController.text)
443+
? (_tryParseBuyAmount(_buyAmountController.text) ?? Decimal.zero)
418444
: Decimal.parse("100"), // dummy value
419445
youReceiveCryptoAmount: buyWithFiat
420446
? Decimal.parse("0.000420282") // dummy value
421-
: Decimal.parse(_buyAmountController.text), // Ternary for this
447+
: (_tryParseBuyAmount(_buyAmountController.text) ??
448+
Decimal.zero), // Ternary for this
422449
id: "id", // anything; we get an ID back
423450
receivingAddress: _receiveAddressController.text,
424451
buyWithFiat: buyWithFiat,
@@ -1123,12 +1150,23 @@ class _BuyFormState extends ConsumerState<BuyForm> {
11231150
final ClipboardData? data = await clipboard
11241151
.getData(Clipboard.kTextPlain);
11251152

1126-
final amountString = Decimal.tryParse(
1153+
final amountString = _tryParseBuyAmount(
11271154
data?.text ?? "",
11281155
);
11291156
if (amountString != null) {
1157+
final locale = ref
1158+
.read(
1159+
localeServiceChangeNotifierProvider,
1160+
)
1161+
.locale;
1162+
final decimalSeparator =
1163+
Util.getSymbolsFor(
1164+
locale: locale,
1165+
)?.DECIMAL_SEP ??
1166+
".";
11301167
_buyAmountController.text = amountString
1131-
.toString();
1168+
.toString()
1169+
.replaceFirst(".", decimalSeparator);
11321170

11331171
validateAmount();
11341172
}

lib/widgets/eth_fee_form.dart

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import 'dart:async';
22

33
import 'package:decimal/decimal.dart';
44
import 'package:flutter/material.dart';
5+
import 'package:flutter_riverpod/flutter_riverpod.dart';
56

7+
import '../providers/global/locale_provider.dart';
68
import '../services/ethereum/ethereum_api.dart';
79
import '../themes/stack_colors.dart';
810
import '../utilities/constants.dart';
@@ -35,7 +37,7 @@ class EthEIP1559Fee {
3537
"gasLimit: $gasLimit)";
3638
}
3739

38-
class EthFeeForm extends StatefulWidget {
40+
class EthFeeForm extends ConsumerStatefulWidget {
3941
EthFeeForm({
4042
super.key,
4143
this.minGasLimit = 21000,
@@ -56,10 +58,10 @@ class EthFeeForm extends StatefulWidget {
5658
final void Function(EthEIP1559Fee) stateChanged;
5759

5860
@override
59-
State<EthFeeForm> createState() => _EthFeeFormState();
61+
ConsumerState<EthFeeForm> createState() => _EthFeeFormState();
6062
}
6163

62-
class _EthFeeFormState extends State<EthFeeForm> {
64+
class _EthFeeFormState extends ConsumerState<EthFeeForm> {
6365
static const _textFadeDuration = Duration(milliseconds: 300);
6466

6567
final maxBaseController = TextEditingController();
@@ -71,11 +73,29 @@ class _EthFeeFormState extends State<EthFeeForm> {
7173

7274
late int _gasLimitCache;
7375

76+
/// Normalize a numeric input string using the active locale's decimal and
77+
/// group separators so that it can be parsed by [Decimal]/[int], which only
78+
/// understand a "." decimal point and no group separators. This preserves
79+
/// full precision (gwei needs up to 9 decimal places).
80+
String _normalizeForParsing(String value) {
81+
final locale = ref.read(localeServiceChangeNotifierProvider).locale;
82+
final numberSymbols = Util.getSymbolsFor(locale: locale);
83+
final groupSeparator = numberSymbols?.GROUP_SEP ?? ",";
84+
final decimalSeparator = numberSymbols?.DECIMAL_SEP ?? ".";
85+
86+
return value
87+
.replaceAll(groupSeparator, "")
88+
.replaceFirst(decimalSeparator, ".");
89+
}
90+
7491
EthEIP1559Fee get _current => EthEIP1559Fee(
75-
maxBaseFeeGwei: Decimal.tryParse(maxBaseController.text) ?? Decimal.zero,
92+
maxBaseFeeGwei:
93+
Decimal.tryParse(_normalizeForParsing(maxBaseController.text)) ??
94+
Decimal.zero,
7695
priorityFeeGwei:
77-
Decimal.tryParse(priorityFeeController.text) ?? Decimal.zero,
78-
gasLimit: int.parse(gasLimitController.text),
96+
Decimal.tryParse(_normalizeForParsing(priorityFeeController.text)) ??
97+
Decimal.zero,
98+
gasLimit: int.parse(_normalizeForParsing(gasLimitController.text)),
7999
);
80100

81101
String _currentBase = "Current: ";
@@ -267,7 +287,7 @@ class _EthFeeFormState extends State<EthFeeForm> {
267287
keyboardType: const TextInputType.numberWithOptions(decimal: true),
268288
focusNode: gasLimitFocus,
269289
onChanged: (value) {
270-
final intValue = int.tryParse(value);
290+
final intValue = int.tryParse(_normalizeForParsing(value));
271291
if (intValue == null ||
272292
intValue < widget.minGasLimit ||
273293
intValue > widget.maxGasLimit) {

0 commit comments

Comments
 (0)