feat: amount string group separator based on locale and parse those formatted strings back to amounts based on localized decimal pattern

This commit is contained in:
julian 2023-06-16 10:43:20 -06:00
parent aac6c0fdb6
commit 7bcfc87f4d
3 changed files with 146 additions and 14 deletions

View file

@ -63,4 +63,13 @@ class AmountFormatter {
tokenContract: ethContract,
);
}
Amount? amountFrom(
String string, {
required String locale,
required Coin coin,
EthContract? ethContract,
}) {
return unit.tryParse(string, locale: locale, coin: coin);
}
}

View file

@ -164,6 +164,51 @@ extension AmountUnitExt on AmountUnit {
}
}
Amount? tryParse(
String value, {
required String locale,
required Coin coin,
EthContract? tokenContract,
}) {
final precisionLost = value.startsWith("~");
final parts = (precisionLost ? value.substring(1) : 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, ".");
final Decimal? decimal = Decimal.tryParse(decimalString);
if (decimal == null) {
return null;
}
final decimalPlaces = tokenContract?.decimals ?? coin.decimals;
final realShift = math.min(shift, decimalPlaces);
return decimal.shift(0 - realShift).toAmount(fractionDigits: decimalPlaces);
}
String displayAmount({
required Amount amount,
required String locale,
@ -191,6 +236,17 @@ extension AmountUnitExt on AmountUnit {
// start building the return value with just the whole value
String returnValue = wholeNumber.toString();
// get number symbols for decimal place and group separator
final numberSymbols = numberFormatSymbols[locale] as NumberSymbols? ??
numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?;
// insert group separator
final regex = RegExp(r'\B(?=(\d{3})+(?!\d))');
returnValue = returnValue.replaceAllMapped(
regex,
(m) => "${m.group(0)}${numberSymbols?.GROUP_SEP ?? ","}",
);
// if true and withUnitName is true, we will show "~" prepended on amount
bool didLosePrecision = false;
@ -239,11 +295,7 @@ extension AmountUnitExt on AmountUnit {
}
// get decimal separator based on locale
final String separator =
(numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ??
(numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?)
?.DECIMAL_SEP ??
".";
final String separator = numberSymbols?.DECIMAL_SEP ?? ".";
// append separator and fractional amount
returnValue += "$separator$remainder";

View file

@ -28,7 +28,7 @@ void main() {
coin: Coin.bitcoin,
maxDecimalPlaces: 8,
),
"10123.45678 mBTC",
"10,123.45678 mBTC",
);
expect(
@ -38,7 +38,7 @@ void main() {
coin: Coin.bitcoin,
maxDecimalPlaces: 8,
),
"10123456.78 µBTC",
"10,123,456.78 µBTC",
);
expect(
@ -48,7 +48,7 @@ void main() {
coin: Coin.bitcoin,
maxDecimalPlaces: 8,
),
"1012345678 sats",
"1,012,345,678 sats",
);
final dec = Decimal.parse("10.123456789123456789");
@ -98,7 +98,7 @@ void main() {
coin: Coin.ethereum,
maxDecimalPlaces: 9,
),
"~10123.456789123 mETH",
"~10,123.456789123 mETH",
);
expect(
@ -108,7 +108,7 @@ void main() {
coin: Coin.ethereum,
maxDecimalPlaces: 8,
),
"~10123456.78912345 µETH",
"~10,123,456.78912345 µETH",
);
expect(
@ -118,7 +118,7 @@ void main() {
coin: Coin.ethereum,
maxDecimalPlaces: 1,
),
"~10123456789.1 gwei",
"~10,123,456,789.1 gwei",
);
expect(
@ -128,7 +128,7 @@ void main() {
coin: Coin.ethereum,
maxDecimalPlaces: 18,
),
"10123456789123.456789 mwei",
"10,123,456,789,123.456789 mwei",
);
expect(
@ -138,7 +138,7 @@ void main() {
coin: Coin.ethereum,
maxDecimalPlaces: 4,
),
"10123456789123456.789 kwei",
"10,123,456,789,123,456.789 kwei",
);
expect(
@ -148,7 +148,78 @@ void main() {
coin: Coin.ethereum,
maxDecimalPlaces: 1,
),
"10123456789123456789 wei",
"10,123,456,789,123,456,789 wei",
);
});
test("parse eth string to amount", () {
final Amount amount = Amount.fromDecimal(
Decimal.parse("10.123456789123456789"),
fractionDigits: Coin.ethereum.decimals,
);
expect(
AmountUnit.nano.tryParse(
"~10,123,456,789.1 gwei",
locale: "en_US",
coin: Coin.ethereum,
),
Amount.fromDecimal(
Decimal.parse("10.1234567891"),
fractionDigits: Coin.ethereum.decimals,
),
);
expect(
AmountUnit.atto.tryParse(
"10,123,456,789,123,456,789 wei",
locale: "en_US",
coin: Coin.ethereum,
),
amount,
);
});
test("parse btc string to amount", () {
final Amount amount = Amount(
rawValue: BigInt.from(1012345678),
fractionDigits: 8,
);
expect(
AmountUnit.normal.tryParse(
"10.12345678 BTC",
locale: "en_US",
coin: Coin.bitcoin,
),
amount,
);
expect(
AmountUnit.milli.tryParse(
"10,123.45678 mBTC",
locale: "en_US",
coin: Coin.bitcoin,
),
amount,
);
expect(
AmountUnit.micro.tryParse(
"10,123,456.7822 µBTC",
locale: "en_US",
coin: Coin.bitcoin,
),
amount,
);
expect(
AmountUnit.nano.tryParse(
"1,012,345,678 sats",
locale: "en_US",
coin: Coin.bitcoin,
),
amount,
);
});
}