diff --git a/lib/utilities/amount/amount_formatter.dart b/lib/utilities/amount/amount_formatter.dart index 3e556c95f..5f8f5c99b 100644 --- a/lib/utilities/amount/amount_formatter.dart +++ b/lib/utilities/amount/amount_formatter.dart @@ -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); + } } diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart index ee54f6d1a..79b36130f 100644 --- a/lib/utilities/amount/amount_unit.dart +++ b/lib/utilities/amount/amount_unit.dart @@ -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"; diff --git a/test/utilities/amount/amount_unit_test.dart b/test/utilities/amount/amount_unit_test.dart index 7af988def..7591473b5 100644 --- a/test/utilities/amount/amount_unit_test.dart +++ b/test/utilities/amount/amount_unit_test.dart @@ -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, ); }); }