Skip to content

Commit

Permalink
Merge pull request #2173 from get10101/feat/currency-switch
Browse files Browse the repository at this point in the history
feat(webapp): add currency toggle to navbar to display balances in Sats/Btc or USD
  • Loading branch information
bonomat authored Mar 6, 2024
2 parents 484faf6 + 8192a65 commit 78c95bf
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 28 deletions.
14 changes: 11 additions & 3 deletions webapp/frontend/lib/common/amount_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,17 @@ String formatSats(Amount amount) {
return "${formatter.format(amount.sats)} sats";
}

String formatUsd(Usd usd) {
final formatter = NumberFormat("\$ #,###,###,###,###", "en");
return formatter.format(usd.usd);
String formatUsd(Usd usd, {int decimalPlaces = 0}) {
String formatString;
if (decimalPlaces > 0) {
formatString = '\$ #,###,###,###,##0.${'0' * decimalPlaces}';
} else {
formatString = '\$ #,###,###,###,##0';
}

final formatter = NumberFormat(formatString, "en");

return formatter.format(usd.asDouble);
}

String formatPrice(Price price) {
Expand Down
31 changes: 31 additions & 0 deletions webapp/frontend/lib/common/currency_change_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';

enum Currency { usd, btc, sats }

extension CurrencyExtension on Currency {
String get name {
switch (this) {
case Currency.sats:
return 'Sats';
case Currency.btc:
return 'BTC';
case Currency.usd:
return 'USD';
default:
throw Exception('Unknown currency');
}
}
}

class CurrencyChangeNotifier extends ChangeNotifier {
Currency _currency;

CurrencyChangeNotifier(this._currency);

Currency get currency => _currency;

set currency(Currency value) {
_currency = value;
notifyListeners();
}
}
40 changes: 40 additions & 0 deletions webapp/frontend/lib/common/currency_selection_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:bitcoin_icons/bitcoin_icons.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get_10101/common/currency_change_notifier.dart';
import 'package:provider/provider.dart';

class CurrencySelectionScreen extends StatelessWidget {
const CurrencySelectionScreen({super.key});

@override
Widget build(BuildContext context) {
CurrencyChangeNotifier changeNotifier = context.watch<CurrencyChangeNotifier>();
final Currency currency = changeNotifier.currency;

return SegmentedButton<Currency>(
style: SegmentedButton.styleFrom(
backgroundColor: Colors.grey[100],
),
segments: <ButtonSegment<Currency>>[
ButtonSegment<Currency>(
value: Currency.sats,
label: Text(Currency.sats.name),
icon: const Icon(BitcoinIcons.satoshi_v2)),
ButtonSegment<Currency>(
value: Currency.btc,
label: Text(Currency.btc.name),
icon: const Icon(BitcoinIcons.bitcoin)),
ButtonSegment<Currency>(
value: Currency.usd,
label: Text(Currency.usd.name),
icon: const Icon(FontAwesomeIcons.dollarSign),
),
],
selected: <Currency>{currency},
onSelectionChanged: (Set<Currency> newSelection) {
changeNotifier.currency = newSelection.first;
},
);
}
}
23 changes: 18 additions & 5 deletions webapp/frontend/lib/common/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ class Amount implements Formattable {
}
}

Usd operator *(Price multiplier) {
Usd result = Usd.zero();
result._usd = Decimal.parse((btc * multiplier.asDouble).toString());
return result;
}

@override
String formatted() {
final formatter = NumberFormat("#,###,###,###,###", "en");
Expand All @@ -69,11 +75,6 @@ class Amount implements Formattable {
String toString() {
return formatSats(this);
}

String formatSats(Amount amount) {
final formatter = NumberFormat("#,###,###,###,###", "en");
return "${formatter.format(amount.sats)} sats";
}
}

class Usd implements Formattable {
Expand Down Expand Up @@ -157,6 +158,18 @@ class Price implements Formattable {
}
}

Price operator +(Price other) {
Price result = Price.zero();
result._usd = _usd + other._usd;
return result;
}

Price operator /(Decimal divisor) {
Price result = Price.zero();
result._usd = (_usd / divisor).toDecimal();
return result;
}

@override
String formatted() {
final formatter = NumberFormat("#,##0.00", "en_US");
Expand Down
64 changes: 49 additions & 15 deletions webapp/frontend/lib/common/scaffold_with_nav.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:async';

import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/auth/auth_service.dart';
import 'package:get_10101/auth/login_screen.dart';
import 'package:get_10101/common/amount_text.dart';
import 'package:get_10101/common/balance.dart';
import 'package:get_10101/common/currency_change_notifier.dart';
import 'package:get_10101/common/currency_selection_widget.dart';
import 'package:get_10101/common/model.dart';
import 'package:get_10101/common/snack_bar.dart';
import 'package:get_10101/common/version_service.dart';
Expand Down Expand Up @@ -175,6 +179,14 @@ class ScaffoldWithNavigationRail extends StatelessWidget {

@override
Widget build(BuildContext context) {
final quoteChangeNotifier = context.watch<QuoteChangeNotifier>();
final quote = quoteChangeNotifier.getBestQuote();
final Price midMarket =
((quote?.ask ?? Price.zero()) + (quote?.bid ?? Price.zero())) / Decimal.fromInt(2);

final currencyChangeNotifier = context.watch<CurrencyChangeNotifier>();
final currency = currencyChangeNotifier.currency;

return Scaffold(
body: Row(
children: [
Expand All @@ -185,7 +197,18 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
trailing: Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [Text("v$version"), const SizedBox(height: 50)],
children: [
Row(
children: [
Text("v$version"),
],
),
const SizedBox(height: 10),
const Row(
children: [CurrencySelectionScreen()],
),
const SizedBox(height: 20),
],
),
),
leading: showAsDrawer
Expand Down Expand Up @@ -241,34 +264,27 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
value: balance == null
? []
: [
TextSpan(
text: balance?.offChain.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
formatAmountAsCurrency(
balance?.offChain, currency, midMarket),
]),
const SizedBox(width: 30),
TopBarItem(
label: 'On-chain: ',
value: balance == null
? []
: [
TextSpan(
text: balance?.onChain.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
formatAmountAsCurrency(balance?.onChain, currency, midMarket),
]),
const SizedBox(width: 30),
TopBarItem(
label: 'Total: ',
value: balance == null
? []
: [
TextSpan(
text: balance?.onChain
.add(balance?.offChain ?? Amount.zero())
.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
formatAmountAsCurrency(
balance?.onChain.add(balance?.offChain ?? Amount.zero()),
currency,
midMarket),
]),
],
),
Expand Down Expand Up @@ -333,3 +349,21 @@ class TopBarItem extends StatelessWidget {
);
}
}

TextSpan formatAmountAsCurrency(Amount? amount, Currency currency, Price midMarket) {
if (amount == null) {
return const TextSpan();
}

String formatted = "";
switch (currency) {
case Currency.usd:
formatted = formatUsd(amount * midMarket, decimalPlaces: 2);
case Currency.btc:
formatted = formatBtc(amount);
case Currency.sats:
formatted = formatSats(amount);
}

return TextSpan(text: formatted, style: const TextStyle(fontWeight: FontWeight.bold));
}
2 changes: 2 additions & 0 deletions webapp/frontend/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get_10101/auth/auth_service.dart';
import 'package:get_10101/common/currency_change_notifier.dart';
import 'package:get_10101/common/version_service.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/routes.dart';
Expand Down Expand Up @@ -33,6 +34,7 @@ void main() {
ChangeNotifierProvider(create: (context) => PositionChangeNotifier(const PositionService())),
ChangeNotifierProvider(create: (context) => OrderChangeNotifier(const OrderService())),
ChangeNotifierProvider(create: (context) => ChannelChangeNotifier(channelService)),
ChangeNotifierProvider(create: (context) => CurrencyChangeNotifier(Currency.sats)),
Provider(create: (context) => const SettingsService()),
Provider(create: (context) => channelService),
Provider(create: (context) => AuthService()),
Expand Down
34 changes: 29 additions & 5 deletions webapp/frontend/lib/trade/position_table.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/common/amount_text.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/common/currency_change_notifier.dart';
import 'package:get_10101/common/direction.dart';
import 'package:get_10101/common/model.dart';
import 'package:get_10101/settings/channel_change_notifier.dart';
Expand All @@ -25,8 +28,14 @@ class OpenPositionTable extends StatelessWidget {

final positionChangeNotifier = context.watch<PositionChangeNotifier>();
final positions = positionChangeNotifier.getPositions();

final currencyChangeNotifier = context.watch<CurrencyChangeNotifier>();
final currency = currencyChangeNotifier.currency;

final quoteChangeNotifier = context.watch<QuoteChangeNotifier>();
final quote = quoteChangeNotifier.getBestQuote();
final Price midMarket =
((quote?.ask ?? Price.zero()) + (quote?.bid ?? Price.zero())) / Decimal.fromInt(2);

if (positions == null) {
return const Center(child: CircularProgressIndicator());
Expand All @@ -35,12 +44,12 @@ class OpenPositionTable extends StatelessWidget {
if (positions.isEmpty) {
return const Center(child: Text('No data available'));
} else {
return buildTable(positions, quote, context, channel);
return buildTable(positions, quote, context, channel, midMarket, currency);
}
}

Widget buildTable(
List<Position> positions, BestQuote? bestQuote, BuildContext context, DlcChannel? channel) {
Widget buildTable(List<Position> positions, BestQuote? bestQuote, BuildContext context,
DlcChannel? channel, Price midMarket, Currency currency) {
Widget actionReplacementLabel = createActionReplacementLabel(channel);
return Table(
border: const TableBorder(verticalInside: BorderSide(width: 0.5, color: Colors.black)),
Expand Down Expand Up @@ -80,9 +89,9 @@ class OpenPositionTable extends StatelessWidget {
: "+${position.quantity}")),
buildTableCell(Text(position.averageEntryPrice.toString())),
buildTableCell(Text(position.liquidationPrice.toString())),
buildTableCell(Text(position.collateral.toString())),
buildAmountTableCell(position.collateral, currency, midMarket),
buildTableCell(Text(position.leverage.formatted())),
buildTableCell(Text(position.pnlSats.toString())),
buildAmountTableCell(position.pnlSats, currency, midMarket),
buildTableCell(
Text("${DateFormat('dd-MM-yyyy – HH:mm').format(position.expiry)} UTC")),
buildTableCell(Center(
Expand Down Expand Up @@ -208,4 +217,19 @@ class OpenPositionTable extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(10), alignment: Alignment.center, child: child)),
));

TableCell buildAmountTableCell(Amount? child, Currency currency, Price midMarket) {
if (child == null) {
return buildTableCell(const Text(""));
}

switch (currency) {
case Currency.usd:
return buildTableCell(Text(formatUsd(child * midMarket, decimalPlaces: 2)));
case Currency.btc:
return buildTableCell(Text(formatBtc(child)));
case Currency.sats:
return buildTableCell(Text(formatSats(child)));
}
}
}

0 comments on commit 78c95bf

Please sign in to comment.