Skip to content

Format amount in Orders #1091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions app/lib/helpers/transaction_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,47 @@ Decimal roundAmount(String amount) {
Decimal parsedAmount = Decimal.parse(amount).shift(2).floor().shift(-2);
return parsedAmount;
}

String formatAmountDisplay(String amount) {
try {
final Decimal decimalAmount = Decimal.parse(amount);
if (decimalAmount == Decimal.zero) return '0';

final Decimal absAmount = decimalAmount.abs();
final double doubleAmount = absAmount.toDouble();
String 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) {
formatted = NumberFormat('0.###').format(doubleAmount);
} else if (doubleAmount >= 0.01) {
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 = NumberFormat('0.########').format(doubleAmount);
}
}

// 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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\u001b is a Esc character, i don't understand its benefit to the regex.

formatted = formatted.replaceFirst(RegExp(r'\.$'), '');
}
return formatted;
} catch (e) {
return amount;
}
}
16 changes: 10 additions & 6 deletions app/lib/screens/market/buy_tft.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ class _BuyTFTWidgetState extends State<BuyTFTWidget> {
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();
Expand Down Expand Up @@ -96,7 +100,7 @@ class _BuyTFTWidgetState extends State<BuyTFTWidget> {
currentMarketPrice = 1 / marketData.lastUsdPrice;

if (!widget.edit && priceController.text.isEmpty) {
priceController.text = currentMarketPrice!.toStringAsFixed(7);
priceController.text = formatAmountDisplay(currentMarketPrice!.toString());
_calculateTotal();
}
});
Expand Down Expand Up @@ -171,7 +175,7 @@ class _BuyTFTWidgetState extends State<BuyTFTWidget> {
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();
}

Expand All @@ -184,7 +188,7 @@ class _BuyTFTWidgetState extends State<BuyTFTWidget> {
final amount = Decimal.parse(amountText);
final price = Decimal.parse(priceText);
final total = amount * price;
totalAmountController.text = roundAmount(total.toString()).toString();
totalAmountController.text = formatAmountDisplay(total.toString());
} catch (e) {
totalAmountController.text = '';
}
Expand Down Expand Up @@ -504,7 +508,7 @@ class _BuyTFTWidgetState extends State<BuyTFTWidget> {
],
)
: Text(
'Available: $availableUSDC USDC',
'Available: ${formatAmountDisplay(availableUSDC ?? '0')} USDC',
style: Theme.of(context)
.textTheme
.bodySmall!
Expand Down
11 changes: 6 additions & 5 deletions app/lib/screens/market/order_book.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -256,9 +257,9 @@ class _OrderbookWidgetState extends State<OrderbookWidget> {
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)
Expand All @@ -272,7 +273,7 @@ class _OrderbookWidgetState extends State<OrderbookWidget> {
bid != null
? (double.tryParse(bid.price) != null &&
double.parse(bid.price) > 0)
? bid.price.toString()
? formatAmountDisplay(bid.price.toString())
: ''
: '',
style: Theme.of(context)
Expand All @@ -296,14 +297,14 @@ class _OrderbookWidgetState extends State<OrderbookWidget> {
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!
Expand Down
9 changes: 5 additions & 4 deletions app/lib/screens/market/order_details.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -248,7 +249,7 @@ class _OrderDetailsWidgetState extends State<OrderDetailsWidget> {
),
),
Text(
'${totalCost.toStringAsFixed(2)} TFT',
'${formatAmountDisplay(totalCost.toString())} TFT',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
),
Expand All @@ -267,7 +268,7 @@ class _OrderDetailsWidgetState extends State<OrderDetailsWidget> {
),
),
Text(
'${pricePerUSDC.toString()} USDC',
'${formatAmountDisplay(pricePerUSDC.toString())} USDC',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
Expand All @@ -283,7 +284,7 @@ class _OrderDetailsWidgetState extends State<OrderDetailsWidget> {
),
),
Text(
'${pricePerTFT.toStringAsFixed(2)} TFT',
'${formatAmountDisplay(pricePerTFT.toString())} TFT',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
Expand All @@ -299,7 +300,7 @@ class _OrderDetailsWidgetState extends State<OrderDetailsWidget> {
),
),
Text(
'${amount.toStringAsFixed(2)} USDC',
'${formatAmountDisplay(amount.toString())} USDC',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.error,
),
Expand Down
23 changes: 12 additions & 11 deletions app/lib/screens/market/overview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -294,7 +295,7 @@ class _OverviewWidgetState extends ConsumerState<OverviewWidget> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
marketData!.lastPrice.toStringAsFixed(7),
formatAmountDisplay(marketData!.lastPrice.toString()),
style: Theme.of(context)
.textTheme
.headlineLarge!
Expand Down Expand Up @@ -437,9 +438,9 @@ class _OverviewWidgetState extends ConsumerState<OverviewWidget> {
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())}'),
],
),
),
Expand All @@ -450,9 +451,9 @@ class _OverviewWidgetState extends ConsumerState<OverviewWidget> {
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'),
],
),
),
Expand Down Expand Up @@ -497,16 +498,16 @@ class _OverviewWidgetState extends ConsumerState<OverviewWidget> {
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')),
],
),
],
Expand Down
3 changes: 2 additions & 1 deletion app/lib/services/stellar_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -319,7 +320,7 @@ Future<String> 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) {
Expand Down
7 changes: 4 additions & 3 deletions app/lib/widgets/market/order_card.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -49,13 +50,13 @@ class _OrderCardWidgetState extends ConsumerState<OrderCardWidget> {
List<Widget> cardContent = [];

cardContent = [
_buildInfoRow(context, 'Amount:', '- ${amount.toStringAsFixed(2)} USDC',
_buildInfoRow(context, 'Amount:', '- ${formatAmountDisplay(amount.toString())} USDC',
textColor: Theme.of(context).colorScheme.error),
_buildInfoRow(
context, 'Price per TFT:', '${pricePerTFT.toStringAsFixed(4)} USDC',
context, 'Price per TFT:', '${formatAmountDisplay(pricePerTFT.toString())} USDC',
isHighlighted: true),
_buildInfoRow(context, 'Total Received:',
'+ ${(totalCost).toStringAsFixed(4)} TFT',
'+ ${formatAmountDisplay(totalCost.toString())} TFT',
textColor: Theme.of(context).colorScheme.primary),
];

Expand Down
56 changes: 56 additions & 0 deletions app/test/transaction_helpers_test.dart
Original file line number Diff line number Diff line change
@@ -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'));
});
});
}