From b2434cec5c38ccc4622e1ed6f621b5a1cd16200a Mon Sep 17 00:00:00 2001 From: zaelgohary Date: Wed, 4 Jun 2025 09:54:25 +0300 Subject: [PATCH 1/3] Create formatSmallAmount and use it in total amount & amount in order details, buy_tft & order_card --- app/lib/helpers/transaction_helpers.dart | 26 +++++++++++++++++++++++ app/lib/screens/market/buy_tft.dart | 2 +- app/lib/screens/market/order_details.dart | 9 ++++---- app/lib/widgets/market/order_card.dart | 7 +++--- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/lib/helpers/transaction_helpers.dart b/app/lib/helpers/transaction_helpers.dart index d4bb3f17b..7abeb3493 100644 --- a/app/lib/helpers/transaction_helpers.dart +++ b/app/lib/helpers/transaction_helpers.dart @@ -12,3 +12,29 @@ Decimal roundAmount(String amount) { Decimal parsedAmount = Decimal.parse(amount).shift(2).floor().shift(-2); return parsedAmount; } + +/// Formats decimal amounts for display, preserving significant digits for small amounts +/// while keeping reasonable precision for larger amounts +String formatSmallAmount(String amount) { + try { + final Decimal decimalAmount = Decimal.parse(amount); + final double doubleAmount = decimalAmount.toDouble(); + + // For very small amounts (less than 0.01), show up to 8 significant digits + if (doubleAmount > 0 && doubleAmount < 0.01) { + // Remove trailing zeros and show significant digits + String formatted = doubleAmount.toStringAsFixed(8); + // Remove trailing zeros + formatted = formatted.replaceAll(RegExp(r'0+$'), ''); + // Remove trailing decimal point if all decimals were zeros + formatted = formatted.replaceAll(RegExp(r'\.$'), ''); + return formatted; + } + + // For amounts >= 0.01, use the standard 2 decimal places + return roundAmount(amount).toString(); + } catch (e) { + // If parsing fails, return the original amount + return amount; + } +} diff --git a/app/lib/screens/market/buy_tft.dart b/app/lib/screens/market/buy_tft.dart index 5e2a555b1..89a71fae6 100644 --- a/app/lib/screens/market/buy_tft.dart +++ b/app/lib/screens/market/buy_tft.dart @@ -184,7 +184,7 @@ class _BuyTFTWidgetState extends State { final amount = Decimal.parse(amountText); final price = Decimal.parse(priceText); final total = amount * price; - totalAmountController.text = roundAmount(total.toString()).toString(); + totalAmountController.text = formatSmallAmount(total.toString()); } catch (e) { totalAmountController.text = ''; } diff --git a/app/lib/screens/market/order_details.dart b/app/lib/screens/market/order_details.dart index a56e425a0..ec66a67b2 100644 --- a/app/lib/screens/market/order_details.dart +++ b/app/lib/screens/market/order_details.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stellar_client/models/exceptions.dart'; +import 'package:threebotlogin/helpers/transaction_helpers.dart'; import 'package:threebotlogin/models/offer.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/services/stellar_service.dart' as Stellar; @@ -248,7 +249,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${totalCost.toStringAsFixed(2)} TFT', + '${formatSmallAmount(totalCost.toString())} TFT', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.primary, ), @@ -267,7 +268,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${pricePerUSDC.toString()} USDC', + '${formatSmallAmount(pricePerUSDC.toString())} USDC', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onSurface, ), @@ -283,7 +284,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${pricePerTFT.toStringAsFixed(2)} TFT', + '${formatSmallAmount(pricePerTFT.toString())} TFT', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onSurface, ), @@ -299,7 +300,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${amount.toStringAsFixed(2)} USDC', + '${formatSmallAmount(amount.toString())} USDC', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.error, ), diff --git a/app/lib/widgets/market/order_card.dart b/app/lib/widgets/market/order_card.dart index f5556db6f..37e5671af 100644 --- a/app/lib/widgets/market/order_card.dart +++ b/app/lib/widgets/market/order_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/helpers/transaction_helpers.dart'; import 'package:threebotlogin/models/offer.dart'; import 'package:intl/intl.dart'; import 'package:threebotlogin/models/wallet.dart' as Wallet; @@ -49,13 +50,13 @@ class _OrderCardWidgetState extends ConsumerState { List cardContent = []; cardContent = [ - _buildInfoRow(context, 'Amount:', '- ${amount.toStringAsFixed(2)} USDC', + _buildInfoRow(context, 'Amount:', '- ${formatSmallAmount(amount.toString())} USDC', textColor: Theme.of(context).colorScheme.error), _buildInfoRow( - context, 'Price per TFT:', '${pricePerTFT.toStringAsFixed(4)} USDC', + context, 'Price per TFT:', '${formatSmallAmount(pricePerTFT.toString())} USDC', isHighlighted: true), _buildInfoRow(context, 'Total Received:', - '+ ${(totalCost).toStringAsFixed(4)} TFT', + '+ ${formatSmallAmount(totalCost.toString())} TFT', textColor: Theme.of(context).colorScheme.primary), ]; From ba691b457fe3c8268104a148c235da2d4e6bc728 Mon Sep 17 00:00:00 2001 From: zaelgohary Date: Wed, 4 Jun 2025 11:50:20 +0300 Subject: [PATCH 2/3] Fix trailling zeros in amounts, Add thousand seperator, update format amount and add approximation symbol --- app/lib/helpers/transaction_helpers.dart | 93 ++++++++++++++++++++--- app/lib/screens/market/buy_tft.dart | 16 ++-- app/lib/screens/market/order_book.dart | 11 +-- app/lib/screens/market/order_details.dart | 8 +- app/lib/screens/market/overview.dart | 23 +++--- app/lib/services/stellar_service.dart | 3 +- app/lib/widgets/market/order_card.dart | 6 +- app/test/transaction_helpers_test.dart | 56 ++++++++++++++ 8 files changed, 174 insertions(+), 42 deletions(-) create mode 100644 app/test/transaction_helpers_test.dart diff --git a/app/lib/helpers/transaction_helpers.dart b/app/lib/helpers/transaction_helpers.dart index 7abeb3493..5c34b6f13 100644 --- a/app/lib/helpers/transaction_helpers.dart +++ b/app/lib/helpers/transaction_helpers.dart @@ -13,26 +13,95 @@ Decimal roundAmount(String amount) { return parsedAmount; } -/// Formats decimal amounts for display, preserving significant digits for small amounts -/// while keeping reasonable precision for larger amounts -String formatSmallAmount(String amount) { +/// Adds thousand separators to large numbers for better readability +String _addThousandSeparators(String number) { + if (!number.contains('.')) { + // Whole number + final formatter = NumberFormat('#,###'); + return formatter.format(int.parse(number)); + } else { + // Decimal number + List parts = number.split('.'); + final formatter = NumberFormat('#,###'); + String wholePart = formatter.format(int.parse(parts[0])); + return '$wholePart.${parts[1]}'; + } +} + +/// Formats decimal amounts for display with user-friendly precision +/// following cryptocurrency app best practices for everyday users +String formatAmountDisplay(String amount) { try { final Decimal decimalAmount = Decimal.parse(amount); + + // Handle zero case + if (decimalAmount == Decimal.zero) { + return '0'; + } + final double doubleAmount = decimalAmount.toDouble(); + String formatted; + + // User-friendly formatting based on amount size + if (doubleAmount >= 1000) { + // Large amounts: 2 decimal places max with thousand separators (e.g., 1,234.57) + formatted = decimalAmount.toStringAsFixed(2); + formatted = _addThousandSeparators(formatted); + } else if (doubleAmount >= 1) { + // Medium amounts: 3 decimal places max (e.g., 123.456) + formatted = decimalAmount.toStringAsFixed(3); + } else if (doubleAmount >= 0.01) { + // Small amounts: 4 decimal places max (e.g., 0.1234) + formatted = decimalAmount.toStringAsFixed(4); + } + else { + // Small amounts: Show with approximation for clarity + String str = decimalAmount.toString(); + if (str.contains('.')) { + List parts = str.split('.'); + String decimals = parts[1]; + + // Find first non-zero digit + int firstNonZero = -1; + for (int i = 0; i < decimals.length; i++) { + if (decimals[i] != '0') { + firstNonZero = i; + break; + } + } + + if (firstNonZero != -1) { + // Show 2-3 significant digits with approximation + int precision = firstNonZero + 2; + if (precision > 8) precision = 8; // Max 8 decimal places + + String approximated = decimalAmount.toStringAsFixed(precision); + // Only add ~ if we're actually truncating/rounding + if (decimals.length > precision) { + formatted = '~$approximated'; + } else { + formatted = approximated; + } + } else { + formatted = decimalAmount.toString(); + } + } else { + formatted = decimalAmount.toString(); + } + } - // For very small amounts (less than 0.01), show up to 8 significant digits - if (doubleAmount > 0 && doubleAmount < 0.01) { - // Remove trailing zeros and show significant digits - String formatted = doubleAmount.toStringAsFixed(8); - // Remove trailing zeros + // Remove trailing zeros (but keep approximation symbol if present) + if (formatted.startsWith('~')) { + String number = formatted.substring(1); + number = number.replaceAll(RegExp(r'0+$'), ''); + number = number.replaceAll(RegExp(r'\.$'), ''); + formatted = '~$number'; + } else { formatted = formatted.replaceAll(RegExp(r'0+$'), ''); - // Remove trailing decimal point if all decimals were zeros formatted = formatted.replaceAll(RegExp(r'\.$'), ''); - return formatted; } - // For amounts >= 0.01, use the standard 2 decimal places - return roundAmount(amount).toString(); + return formatted; } catch (e) { // If parsing fails, return the original amount return amount; diff --git a/app/lib/screens/market/buy_tft.dart b/app/lib/screens/market/buy_tft.dart index 89a71fae6..32b25f942 100644 --- a/app/lib/screens/market/buy_tft.dart +++ b/app/lib/screens/market/buy_tft.dart @@ -39,9 +39,13 @@ class _BuyTFTWidgetState extends State { void initState() { super.initState(); amountController = TextEditingController( - text: widget.edit ? widget.offer?.amount.toString() ?? '' : ''); + text: widget.edit + ? formatAmountDisplay(widget.offer?.amount.toString() ?? '') + : ''); priceController = TextEditingController( - text: widget.edit ? widget.offer?.price.toString() ?? '' : ''); + text: widget.edit + ? formatAmountDisplay(widget.offer?.price.toString() ?? '') + : ''); amountController.addListener(_calculateTotal); priceController.addListener(_calculateTotal); if (widget.edit) _calculateTotal(); @@ -96,7 +100,7 @@ class _BuyTFTWidgetState extends State { currentMarketPrice = 1 / marketData.lastUsdPrice; if (!widget.edit && priceController.text.isEmpty) { - priceController.text = currentMarketPrice!.toStringAsFixed(7); + priceController.text = formatAmountDisplay(currentMarketPrice!.toString()); _calculateTotal(); } }); @@ -171,7 +175,7 @@ class _BuyTFTWidgetState extends State { calculateAmount(int percentage) { final amount = Decimal.parse(availableUSDC ?? '0') * (Decimal.fromInt(percentage).shift(-2)); - amountController.text = roundAmount(amount.toString()).toString(); + amountController.text = formatAmountDisplay(roundAmount(amount.toString()).toString()); _calculateTotal(); } @@ -184,7 +188,7 @@ class _BuyTFTWidgetState extends State { final amount = Decimal.parse(amountText); final price = Decimal.parse(priceText); final total = amount * price; - totalAmountController.text = formatSmallAmount(total.toString()); + totalAmountController.text = formatAmountDisplay(total.toString()); } catch (e) { totalAmountController.text = ''; } @@ -504,7 +508,7 @@ class _BuyTFTWidgetState extends State { ], ) : Text( - 'Available: $availableUSDC USDC', + 'Available: ${formatAmountDisplay(availableUSDC ?? '0')} USDC', style: Theme.of(context) .textTheme .bodySmall! diff --git a/app/lib/screens/market/order_book.dart b/app/lib/screens/market/order_book.dart index c805d32b2..0e10a3265 100644 --- a/app/lib/screens/market/order_book.dart +++ b/app/lib/screens/market/order_book.dart @@ -4,6 +4,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/helpers/transaction_helpers.dart'; import 'package:threebotlogin/models/order_book.dart'; import 'package:threebotlogin/services/stellar_service.dart'; @@ -256,9 +257,9 @@ class _OrderbookWidgetState extends State { bid != null ? (double.tryParse(bid.price) != null && double.parse(bid.price) > 0) - ? (double.parse(bid.amount) / + ? formatAmountDisplay((double.parse(bid.amount) / double.parse(bid.price)) - .toStringAsFixed(7) + .toString()) : '' : '', style: Theme.of(context) @@ -272,7 +273,7 @@ class _OrderbookWidgetState extends State { bid != null ? (double.tryParse(bid.price) != null && double.parse(bid.price) > 0) - ? bid.price.toString() + ? formatAmountDisplay(bid.price.toString()) : '' : '', style: Theme.of(context) @@ -296,14 +297,14 @@ class _OrderbookWidgetState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Text(ask != null ? ask.amount.toString() : '', + Text(ask != null ? formatAmountDisplay(ask.amount.toString()) : '', style: Theme.of(context) .textTheme .bodySmall! .copyWith( color: Theme.of(context).colorScheme.error)), - Text(ask != null ? ask.price.toString() : '', + Text(ask != null ? formatAmountDisplay(ask.price.toString()) : '', style: Theme.of(context) .textTheme .bodySmall! diff --git a/app/lib/screens/market/order_details.dart b/app/lib/screens/market/order_details.dart index ec66a67b2..e1163f2f4 100644 --- a/app/lib/screens/market/order_details.dart +++ b/app/lib/screens/market/order_details.dart @@ -249,7 +249,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${formatSmallAmount(totalCost.toString())} TFT', + '${formatAmountDisplay(totalCost.toString())} TFT', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.primary, ), @@ -268,7 +268,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${formatSmallAmount(pricePerUSDC.toString())} USDC', + '${formatAmountDisplay(pricePerUSDC.toString())} USDC', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onSurface, ), @@ -284,7 +284,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${formatSmallAmount(pricePerTFT.toString())} TFT', + '${formatAmountDisplay(pricePerTFT.toString())} TFT', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.onSurface, ), @@ -300,7 +300,7 @@ class _OrderDetailsWidgetState extends State { ), ), Text( - '${formatSmallAmount(amount.toString())} USDC', + '${formatAmountDisplay(amount.toString())} USDC', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.error, ), diff --git a/app/lib/screens/market/overview.dart b/app/lib/screens/market/overview.dart index 16bacb44f..a3b658797 100644 --- a/app/lib/screens/market/overview.dart +++ b/app/lib/screens/market/overview.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/helpers/transaction_helpers.dart'; import 'package:threebotlogin/models/market_data.dart'; import 'package:threebotlogin/models/wallet.dart'; import 'package:threebotlogin/providers/wallets_provider.dart'; @@ -294,7 +295,7 @@ class _OverviewWidgetState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( - marketData!.lastPrice.toStringAsFixed(7), + formatAmountDisplay(marketData!.lastPrice.toString()), style: Theme.of(context) .textTheme .headlineLarge! @@ -437,9 +438,9 @@ class _OverviewWidgetState extends ConsumerState { CrossAxisAlignment.start, children: [ _buildMarketColumn('Last Price', - '${marketData!.lastPrice.toStringAsFixed(7)} USDC'), + '${formatAmountDisplay(marketData!.lastPrice.toString())} USDC'), _buildMarketColumn('Last USD Price', - '\$${marketData!.lastUsdPrice.toStringAsFixed(7)}'), + '\$${formatAmountDisplay(marketData!.lastUsdPrice.toString())}'), ], ), ), @@ -450,9 +451,9 @@ class _OverviewWidgetState extends ConsumerState { CrossAxisAlignment.start, children: [ _buildMarketColumn('24H High', - '${marketData!.high24h.toStringAsFixed(7)} USDC'), + '${formatAmountDisplay(marketData!.high24h.toString())} USDC'), _buildMarketColumn('24H Low', - '${marketData!.low24h.toStringAsFixed(7)} USDC'), + '${formatAmountDisplay(marketData!.low24h.toString())} USDC'), ], ), ), @@ -497,16 +498,16 @@ class _OverviewWidgetState extends ConsumerState { children: [ _buildMarketColumn( 'TFT Balance', - _selectedWallet - ?.stellarBalances['TFT']!), + formatAmountDisplay(_selectedWallet + ?.stellarBalances['TFT']! ?? '0')), _buildMarketColumn( 'USDC Balance', - _selectedWallet - ?.stellarBalances['USDC']!), + formatAmountDisplay(_selectedWallet + ?.stellarBalances['USDC']! ?? '0')), _buildMarketColumn( 'XLM Balance', - _selectedWallet - ?.stellarBalances['XLM']!), + formatAmountDisplay(_selectedWallet + ?.stellarBalances['XLM']! ?? '0')), ], ), ], diff --git a/app/lib/services/stellar_service.dart b/app/lib/services/stellar_service.dart index 8bc07ba25..694201618 100644 --- a/app/lib/services/stellar_service.dart +++ b/app/lib/services/stellar_service.dart @@ -6,6 +6,7 @@ import 'package:stellar_client/models/vesting_account.dart'; import 'package:stellar_client/stellar_client.dart'; import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart'; import 'package:threebotlogin/helpers/logger.dart'; +import 'package:threebotlogin/helpers/transaction_helpers.dart'; import 'package:threebotlogin/models/market_data.dart'; import 'package:threebotlogin/models/offer.dart'; import 'package:threebotlogin/models/order_book.dart'; @@ -319,7 +320,7 @@ Future getAvailableUSDCBalance( return sum + double.parse(offer.amount); }); final balance = await getBalanceByClient(client); - return (double.parse(balance['USDC']!) - totalReserved).toStringAsFixed(7); + return formatAmountDisplay((double.parse(balance['USDC']!) - totalReserved).toString()); } String getAssetName(Asset asset) { diff --git a/app/lib/widgets/market/order_card.dart b/app/lib/widgets/market/order_card.dart index 37e5671af..85829291f 100644 --- a/app/lib/widgets/market/order_card.dart +++ b/app/lib/widgets/market/order_card.dart @@ -50,13 +50,13 @@ class _OrderCardWidgetState extends ConsumerState { List cardContent = []; cardContent = [ - _buildInfoRow(context, 'Amount:', '- ${formatSmallAmount(amount.toString())} USDC', + _buildInfoRow(context, 'Amount:', '- ${formatAmountDisplay(amount.toString())} USDC', textColor: Theme.of(context).colorScheme.error), _buildInfoRow( - context, 'Price per TFT:', '${formatSmallAmount(pricePerTFT.toString())} USDC', + context, 'Price per TFT:', '${formatAmountDisplay(pricePerTFT.toString())} USDC', isHighlighted: true), _buildInfoRow(context, 'Total Received:', - '+ ${formatSmallAmount(totalCost.toString())} TFT', + '+ ${formatAmountDisplay(totalCost.toString())} TFT', textColor: Theme.of(context).colorScheme.primary), ]; diff --git a/app/test/transaction_helpers_test.dart b/app/test/transaction_helpers_test.dart new file mode 100644 index 000000000..64ef3254d --- /dev/null +++ b/app/test/transaction_helpers_test.dart @@ -0,0 +1,56 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:threebotlogin/helpers/transaction_helpers.dart'; + +void main() { + group('formatAmountDisplay', () { + test('handles large amounts (user-friendly)', () { + expect(formatAmountDisplay('1234.567890'), equals('1,234.57')); // 2 decimals + thousand separators + expect(formatAmountDisplay('5000.00'), equals('5,000')); // Remove trailing zeros + separators + expect(formatAmountDisplay('10000.123'), equals('10,000.12')); // 2 decimals max + separators + expect(formatAmountDisplay('1000000.00'), equals('1,000,000')); // Large numbers with separators + }); + + test('handles medium amounts (user-friendly)', () { + expect(formatAmountDisplay('123.456789'), equals('123.457')); // 3 decimals for medium amounts + expect(formatAmountDisplay('1.50'), equals('1.5')); // Remove trailing zeros + expect(formatAmountDisplay('99.999'), equals('99.999')); // Keep significant decimals + }); + + test('handles small amounts (user-friendly)', () { + expect(formatAmountDisplay('0.12345678'), equals('0.1235')); // 4 decimals for small amounts + expect(formatAmountDisplay('0.1000'), equals('0.1')); // Remove trailing zeros + }); + + test('handles very small amounts with approximation', () { + expect(formatAmountDisplay('0.00670241'), equals('~0.0067')); // Approximated with ~ symbol + expect(formatAmountDisplay('0.001234567'), equals('~0.0012')); // Approximated with ~ symbol + expect(formatAmountDisplay('0.0005'), equals('0.0005')); // Keep as-is if short enough + expect(formatAmountDisplay('0.000100'), equals('0.0001')); // Remove trailing zeros + }); + + test('handles tiny amounts with approximation', () { + expect(formatAmountDisplay('0.000012345678'), equals('~0.000012')); // Approximate tiny amounts + expect(formatAmountDisplay('0.00000123456789'), equals('~0.0000012')); // Show 2 significant digits + expect(formatAmountDisplay('0.00000006'), equals('0.00000006')); // Keep if short enough + }); + + test('handles whole numbers', () { + expect(formatAmountDisplay('5'), equals('5')); + expect(formatAmountDisplay('100'), equals('100')); + expect(formatAmountDisplay('1000'), equals('1,000')); // Thousand separator added + }); + + test('handles edge cases', () { + expect(formatAmountDisplay('0'), equals('0')); + expect(formatAmountDisplay('0.0'), equals('0')); + expect(formatAmountDisplay('0.00'), equals('0')); + }); + + test('removes trailing zeros consistently', () { + expect(formatAmountDisplay('1.50000000'), equals('1.5')); + expect(formatAmountDisplay('0.12345600'), equals('0.1235')); + expect(formatAmountDisplay('1000.00'), equals('1,000')); // Thousand separator added + expect(formatAmountDisplay('0.001200'), equals('0.0012')); + }); + }); +} From 0171e003e5491c955086d12af7e68b5b767b71c8 Mon Sep 17 00:00:00 2001 From: zaelgohary Date: Mon, 23 Jun 2025 08:35:29 +0300 Subject: [PATCH 3/3] Replace thousand seperator func w NumberFormat, refactor formatAmountDisplay, --- app/lib/helpers/transaction_helpers.dart | 103 ++++++----------------- 1 file changed, 26 insertions(+), 77 deletions(-) diff --git a/app/lib/helpers/transaction_helpers.dart b/app/lib/helpers/transaction_helpers.dart index 5c34b6f13..3d0352773 100644 --- a/app/lib/helpers/transaction_helpers.dart +++ b/app/lib/helpers/transaction_helpers.dart @@ -13,97 +13,46 @@ Decimal roundAmount(String amount) { return parsedAmount; } -/// Adds thousand separators to large numbers for better readability -String _addThousandSeparators(String number) { - if (!number.contains('.')) { - // Whole number - final formatter = NumberFormat('#,###'); - return formatter.format(int.parse(number)); - } else { - // Decimal number - List parts = number.split('.'); - final formatter = NumberFormat('#,###'); - String wholePart = formatter.format(int.parse(parts[0])); - return '$wholePart.${parts[1]}'; - } -} - -/// Formats decimal amounts for display with user-friendly precision -/// following cryptocurrency app best practices for everyday users String formatAmountDisplay(String amount) { try { final Decimal decimalAmount = Decimal.parse(amount); + if (decimalAmount == Decimal.zero) return '0'; - // Handle zero case - if (decimalAmount == Decimal.zero) { - return '0'; - } - - final double doubleAmount = decimalAmount.toDouble(); + final Decimal absAmount = decimalAmount.abs(); + final double doubleAmount = absAmount.toDouble(); String formatted; - // User-friendly formatting based on amount size - if (doubleAmount >= 1000) { - // Large amounts: 2 decimal places max with thousand separators (e.g., 1,234.57) - formatted = decimalAmount.toStringAsFixed(2); - formatted = _addThousandSeparators(formatted); + if (absAmount % Decimal.one == Decimal.zero) { + formatted = NumberFormat('#,##0').format(doubleAmount); + } else if (doubleAmount >= 1000) { + formatted = NumberFormat('#,##0.00').format(doubleAmount); } else if (doubleAmount >= 1) { - // Medium amounts: 3 decimal places max (e.g., 123.456) - formatted = decimalAmount.toStringAsFixed(3); + formatted = NumberFormat('0.###').format(doubleAmount); } else if (doubleAmount >= 0.01) { - // Small amounts: 4 decimal places max (e.g., 0.1234) - formatted = decimalAmount.toStringAsFixed(4); - } - else { - // Small amounts: Show with approximation for clarity - String str = decimalAmount.toString(); - if (str.contains('.')) { - List parts = str.split('.'); - String decimals = parts[1]; - - // Find first non-zero digit - int firstNonZero = -1; - for (int i = 0; i < decimals.length; i++) { - if (decimals[i] != '0') { - firstNonZero = i; - break; - } - } - - if (firstNonZero != -1) { - // Show 2-3 significant digits with approximation - int precision = firstNonZero + 2; - if (precision > 8) precision = 8; // Max 8 decimal places - - String approximated = decimalAmount.toStringAsFixed(precision); - // Only add ~ if we're actually truncating/rounding - if (decimals.length > precision) { - formatted = '~$approximated'; - } else { - formatted = approximated; - } - } else { - formatted = decimalAmount.toString(); - } + formatted = NumberFormat('0.####').format(doubleAmount); + } else { + // Very small amounts: Show with approximation for clarity + String decimals = absAmount.toString().split('.').length > 1 + ? absAmount.toString().split('.')[1] + : ''; + int firstNonZero = decimals.indexOf(RegExp(r'[1-9]')); + if (firstNonZero != -1) { + int precision = (firstNonZero + 2).clamp(0, 8); + double approxValue = double.parse(absAmount.toStringAsFixed(precision)); + String approximated = NumberFormat('0.${'0' * (precision - 1)}#').format(approxValue); + formatted = decimals.length > precision ? '~$approximated' : approximated; } else { - formatted = decimalAmount.toString(); + formatted = NumberFormat('0.########').format(doubleAmount); } } - // Remove trailing zeros (but keep approximation symbol if present) - if (formatted.startsWith('~')) { - String number = formatted.substring(1); - number = number.replaceAll(RegExp(r'0+$'), ''); - number = number.replaceAll(RegExp(r'\.$'), ''); - formatted = '~$number'; - } else { - formatted = formatted.replaceAll(RegExp(r'0+$'), ''); - formatted = formatted.replaceAll(RegExp(r'\.$'), ''); + // Remove trailing zeros after decimal point only (if any), but keep approximation symbol if present + if (formatted.contains('.')) { + formatted = formatted.replaceFirst(RegExp(r'(\.\d*?[1-9])0+\u001b'), r'$1\u001b'); + formatted = formatted.replaceFirst(RegExp(r'\.$'), ''); } - return formatted; } catch (e) { - // If parsing fails, return the original amount return amount; } -} +} \ No newline at end of file