From c477d31d3d84dd6b6d7e73c242346aac1c737f72 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Fri, 10 Jul 2020 17:13:32 +0200 Subject: [PATCH] fiat currency support --- .../sparrowwallet/sparrow/AppController.java | 65 ++++++- .../sparrow/control/CoinLabel.java | 8 +- .../sparrow/control/FiatLabel.java | 120 ++++++++++++ .../event/ExchangeRatesUpdatedEvent.java | 21 +++ .../event/FiatCurrencySelectedEvent.java | 23 +++ .../sparrow/glyphfont/FontAwesome5.java | 1 + .../com/sparrowwallet/sparrow/io/Config.java | 21 +++ .../sparrow/io/ExchangeSource.java | 171 ++++++++++++++++++ .../GeneralPreferencesController.java | 94 ++++++++++ .../preferences/PreferencesDialog.java | 2 +- .../wallet/TransactionsController.java | 33 +++- .../sparrow/preferences/general.fxml | 58 ++++++ .../sparrow/preferences/preferences.fxml | 13 +- .../sparrow/wallet/transactions.fxml | 4 +- 14 files changed, 619 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/FiatLabel.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/ExchangeRatesUpdatedEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/FiatCurrencySelectedEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/ExchangeSource.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java create mode 100644 src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 6a29fbbe..789c7a20 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -52,6 +52,10 @@ public class AppController implements Initializable { private static final int SERVER_PING_PERIOD = 10 * 1000; private static final int ENUMERATE_HW_PERIOD = 30 * 1000; + private static final int RATES_PERIOD = 5 * 60 * 1000; + private static final ExchangeSource DEFAULT_EXCHANGE_SOURCE = ExchangeSource.COINGECKO; + private static final Currency DEFAULT_FIAT_CURRENCY = Currency.getInstance("USD"); + public static final String DRAG_OVER_CLASS = "drag-over"; private MainApp application; @@ -90,6 +94,8 @@ public class AppController implements Initializable { private Timeline statusTimeline; + private ExchangeSource.RatesService ratesService; + private ElectrumServer.ConnectionService connectionService; private static Integer currentBlockHeight; @@ -98,6 +104,8 @@ public class AppController implements Initializable { private static Map targetBlockFeeRates; + private static Map fiatCurrencyExchangeRate; + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); @@ -174,20 +182,36 @@ public class AppController implements Initializable { if(!connectionService.isRunning()) { connectionService.start(); } + + if(ratesService.getState() == Worker.State.CANCELLED) { + ratesService.reset(); + } + + if(!ratesService.isRunning() && ratesService.getExchangeSource() != ExchangeSource.NONE) { + ratesService.start(); + } } else { connectionService.cancel(); + ratesService.cancel(); } } }); onlineProperty.bindBidirectional(serverToggle.selectedProperty()); - connectionService = createConnectionService(); Config config = Config.get(); + connectionService = createConnectionService(); if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) { connectionService.start(); } + ExchangeSource source = config.getExchangeSource() != null ? config.getExchangeSource() : DEFAULT_EXCHANGE_SOURCE; + Currency currency = config.getFiatCurrency() != null ? config.getFiatCurrency() : DEFAULT_FIAT_CURRENCY; + ratesService = createRatesService(source, currency); + if (config.getMode() == Mode.ONLINE && source != ExchangeSource.NONE) { + ratesService.start(); + } + openTransactionIdItem.disableProperty().bind(onlineProperty.not()); openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json")); @@ -219,6 +243,18 @@ public class AppController implements Initializable { return connectionService; } + private ExchangeSource.RatesService createRatesService(ExchangeSource exchangeSource, Currency currency) { + ExchangeSource.RatesService ratesService = new ExchangeSource.RatesService(exchangeSource, currency); + ratesService.setPeriod(new Duration(RATES_PERIOD)); + ratesService.setRestartOnFailure(true); + + ratesService.setOnSucceeded(successEvent -> { + EventManager.get().post(ratesService.getValue()); + }); + + return ratesService; + } + public void setApplication(MainApp application) { this.application = application; } @@ -361,6 +397,10 @@ public class AppController implements Initializable { return targetBlockFeeRates; } + public static Map getFiatCurrencyExchangeRate() { + return fiatCurrencyExchangeRate; + } + public static void showErrorDialog(String title, String content) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle(title); @@ -820,4 +860,25 @@ public class AppController implements Initializable { Tab tab = addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex()); tabs.getSelectionModel().select(tab); } -} + + @Subscribe + public void bitcoinUnitChanged(BitcoinUnitChangedEvent event) { + Optional selectedToggle = bitcoinUnit.getToggles().stream().filter(toggle -> event.getBitcoinUnit().equals(toggle.getUserData())).findFirst(); + selectedToggle.ifPresent(toggle -> bitcoinUnit.selectToggle(toggle)); + } + + @Subscribe + public void fiatCurrencySelected(FiatCurrencySelectedEvent event) { + ratesService.cancel(); + + if (Config.get().getMode() != Mode.OFFLINE && event.getExchangeSource() != ExchangeSource.NONE) { + ratesService = createRatesService(event.getExchangeSource(), event.getCurrency()); + ratesService.start(); + } + } + + @Subscribe + public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) { + fiatCurrencyExchangeRate = Map.of(event.getSelectedCurrency(), event.getRate()); + } +} \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/control/CoinLabel.java b/src/main/java/com/sparrowwallet/sparrow/control/CoinLabel.java index 253db794..df2f0fae 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/CoinLabel.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/CoinLabel.java @@ -18,7 +18,7 @@ import java.util.Locale; public class CoinLabel extends CopyableLabel { public static final DecimalFormat BTC_FORMAT = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - private final LongProperty value = new SimpleLongProperty(-1); + private final LongProperty valueProperty = new SimpleLongProperty(-1); private final Tooltip tooltip; private final CoinContextMenu contextMenu; @@ -35,15 +35,15 @@ public class CoinLabel extends CopyableLabel { } public final LongProperty valueProperty() { - return value; + return valueProperty; } public final long getValue() { - return value.get(); + return valueProperty.get(); } public final void setValue(long value) { - this.value.set(value); + this.valueProperty.set(value); } public void refresh() { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FiatLabel.java b/src/main/java/com/sparrowwallet/sparrow/control/FiatLabel.java new file mode 100644 index 00000000..8f5971c1 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/FiatLabel.java @@ -0,0 +1,120 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.protocol.Transaction; +import javafx.beans.property.*; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tooltip; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.util.Currency; + +public class FiatLabel extends CopyableLabel { + private static final DecimalFormat CURRENCY_FORMAT = new DecimalFormat("#,##0.00"); + + private final LongProperty valueProperty = new SimpleLongProperty(-1); + private final DoubleProperty btcRateProperty = new SimpleDoubleProperty(0.0); + private final ObjectProperty currencyProperty = new SimpleObjectProperty<>(null); + private final Tooltip tooltip; + private final FiatContextMenu contextMenu; + + public FiatLabel() { + this(""); + } + + public FiatLabel(String text) { + super(text); + valueProperty().addListener((observable, oldValue, newValue) -> setValueAsText((Long)newValue)); + btcRateProperty().addListener((observable, oldValue, newValue) -> setValueAsText(getValue())); + currencyProperty().addListener((observable, oldValue, newValue) -> setValueAsText(getValue())); + tooltip = new Tooltip(); + contextMenu = new FiatContextMenu(); + } + + public final LongProperty valueProperty() { + return valueProperty; + } + + public final long getValue() { + return valueProperty.get(); + } + + public final void setValue(long value) { + this.valueProperty.set(value); + } + + public final DoubleProperty btcRateProperty() { + return btcRateProperty; + } + + public final double getBtcRate() { + return btcRateProperty.get(); + } + + public final void setBtcRate(double btcRate) { + this.btcRateProperty.set(btcRate); + } + + public final ObjectProperty currencyProperty() { + return currencyProperty; + } + + public final Currency getCurrency() { + return currencyProperty.get(); + } + + public final void setCurrency(Currency currency) { + this.currencyProperty.set(currency); + } + + public final void set(Currency currency, double btcRate, long value) { + setValue(value); + setBtcRate(btcRate); + setCurrency(currency); + } + + private void setValueAsText(long balance) { + if(getCurrency() != null && getBtcRate() > 0.0) { + BigDecimal satsBalance = BigDecimal.valueOf(balance); + BigDecimal btcBalance = satsBalance.divide(BigDecimal.valueOf(Transaction.SATOSHIS_PER_BITCOIN)); + BigDecimal fiatBalance = btcBalance.multiply(BigDecimal.valueOf(getBtcRate())); + + DecimalFormat currencyFormat = new DecimalFormat("#,##0.00"); + String label = getCurrency().getSymbol() + " " + currencyFormat.format(fiatBalance.doubleValue()); + tooltip.setText("1 BTC = " + getCurrency().getSymbol() + " " + currencyFormat.format(getBtcRate())); + + setText(label); + setTooltip(tooltip); + setContextMenu(contextMenu); + } else { + setText(""); + setTooltip(null); + setContextMenu(null); + } + } + + private class FiatContextMenu extends ContextMenu { + public FiatContextMenu() { + MenuItem copyValue = new MenuItem("Copy Value"); + copyValue.setOnAction(AE -> { + hide(); + ClipboardContent content = new ClipboardContent(); + content.putString(getText()); + Clipboard.getSystemClipboard().setContent(content); + }); + + MenuItem copyRate = new MenuItem("Copy Rate"); + copyRate.setOnAction(AE -> { + hide(); + ClipboardContent content = new ClipboardContent(); + content.putString(getTooltip().getText()); + Clipboard.getSystemClipboard().setContent(content); + }); + + getItems().addAll(copyValue, copyRate); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ExchangeRatesUpdatedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ExchangeRatesUpdatedEvent.java new file mode 100644 index 00000000..33174dc5 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/ExchangeRatesUpdatedEvent.java @@ -0,0 +1,21 @@ +package com.sparrowwallet.sparrow.event; + +import java.util.Currency; + +public class ExchangeRatesUpdatedEvent { + private final Currency selectedCurrency; + private final Double rate; + + public ExchangeRatesUpdatedEvent(Currency selectedCurrency, Double rate) { + this.selectedCurrency = selectedCurrency; + this.rate = rate; + } + + public Currency getSelectedCurrency() { + return selectedCurrency; + } + + public Double getRate() { + return rate; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/FiatCurrencySelectedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/FiatCurrencySelectedEvent.java new file mode 100644 index 00000000..b0822837 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/FiatCurrencySelectedEvent.java @@ -0,0 +1,23 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.sparrow.io.ExchangeSource; + +import java.util.Currency; + +public class FiatCurrencySelectedEvent { + private final ExchangeSource exchangeSource; + private final Currency currency; + + public FiatCurrencySelectedEvent(ExchangeSource exchangeSource, Currency currency) { + this.exchangeSource = exchangeSource; + this.currency = currency; + } + + public ExchangeSource getExchangeSource() { + return exchangeSource; + } + + public Currency getCurrency() { + return currency; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index fea5d907..6ab15b3d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -29,6 +29,7 @@ public class FontAwesome5 extends GlyphFont { QUESTION_CIRCLE('\uf059'), SD_CARD('\uf7c2'), SEARCH('\uf002'), + TOOLS('\uf7d9'), WALLET('\uf555'); private final char ch; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index dd0e599c..622ece0c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -6,12 +6,15 @@ import com.sparrowwallet.sparrow.Mode; import java.io.*; import java.lang.reflect.Type; +import java.util.Currency; public class Config { public static final String CONFIG_FILENAME = ".config"; private Mode mode; private BitcoinUnit bitcoinUnit; + private Currency fiatCurrency; + private ExchangeSource exchangeSource; private Integer keyDerivationPeriod; private File hwi; private String electrumServer; @@ -78,6 +81,24 @@ public class Config { flush(); } + public Currency getFiatCurrency() { + return fiatCurrency; + } + + public void setFiatCurrency(Currency fiatCurrency) { + this.fiatCurrency = fiatCurrency; + flush(); + } + + public ExchangeSource getExchangeSource() { + return exchangeSource; + } + + public void setExchangeSource(ExchangeSource exchangeSource) { + this.exchangeSource = exchangeSource; + flush(); + } + public Integer getKeyDerivationPeriod() { return keyDerivationPeriod; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ExchangeSource.java b/src/main/java/com/sparrowwallet/sparrow/io/ExchangeSource.java new file mode 100644 index 00000000..6d20c11e --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/ExchangeSource.java @@ -0,0 +1,171 @@ +package com.sparrowwallet.sparrow.io; + +import com.google.gson.Gson; +import com.sparrowwallet.sparrow.event.ExchangeRatesUpdatedEvent; +import javafx.concurrent.ScheduledService; +import javafx.concurrent.Service; +import javafx.concurrent.Task; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +public enum ExchangeSource { + NONE("None") { + @Override + public List getSupportedCurrencies() { + return Collections.emptyList(); + } + + @Override + public Double getExchangeRate(Currency currency) { + return null; + } + }, + COINBASE("Coinbase") { + @Override + public List getSupportedCurrencies() { + return getRates().data.rates.keySet().stream().filter(code -> isValidISO4217Code(code.toUpperCase())) + .map(code -> Currency.getInstance(code.toUpperCase())).collect(Collectors.toList()); + } + + @Override + public Double getExchangeRate(Currency currency) { + String currencyCode = currency.getCurrencyCode(); + OptionalDouble optRate = getRates().data.rates.entrySet().stream().filter(rate -> currencyCode.equalsIgnoreCase(rate.getKey())).mapToDouble(Map.Entry::getValue).findFirst(); + if(optRate.isPresent()) { + return optRate.getAsDouble(); + } + + return null; + } + + private CoinbaseRates getRates() { + String url = "https://api.coinbase.com/v2/exchange-rates?currency=BTC"; + + try(InputStream is = new URL(url).openStream(); Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + Gson gson = new Gson(); + return gson.fromJson(reader, CoinbaseRates.class); + } catch (Exception e) { + return new CoinbaseRates(); + } + } + }, + COINGECKO("Coingecko") { + @Override + public List getSupportedCurrencies() { + return getRates().rates.entrySet().stream().filter(rate -> "fiat".equals(rate.getValue().type) && isValidISO4217Code(rate.getKey().toUpperCase())) + .map(rate -> Currency.getInstance(rate.getKey().toUpperCase())).collect(Collectors.toList()); + } + + @Override + public Double getExchangeRate(Currency currency) { + String currencyCode = currency.getCurrencyCode(); + OptionalDouble optRate = getRates().rates.entrySet().stream().filter(rate -> currencyCode.equalsIgnoreCase(rate.getKey())).mapToDouble(rate -> rate.getValue().value).findFirst(); + if(optRate.isPresent()) { + return optRate.getAsDouble(); + } + + return null; + } + + private CoinGeckoRates getRates() { + String url = "https://api.coingecko.com/api/v3/exchange_rates"; + + try(InputStream is = new URL(url).openStream(); Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + Gson gson = new Gson(); + return gson.fromJson(reader, CoinGeckoRates.class); + } catch (Exception e) { + return new CoinGeckoRates(); + } + } + }; + + private final String name; + + ExchangeSource(String name) { + this.name = name; + } + + public abstract List getSupportedCurrencies(); + + public abstract Double getExchangeRate(Currency currency); + + private static boolean isValidISO4217Code(String code) { + try { + Currency currency = Currency.getInstance(code); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + public String toString() { + return name; + } + + public static class CurrenciesService extends Service> { + private final ExchangeSource exchangeSource; + + public CurrenciesService(ExchangeSource exchangeSource) { + this.exchangeSource = exchangeSource; + } + + @Override + protected Task> createTask() { + return new Task<>() { + protected List call() { + return exchangeSource.getSupportedCurrencies(); + } + }; + } + } + + public static class RatesService extends ScheduledService { + private final ExchangeSource exchangeSource; + private final Currency selectedCurrency; + + public RatesService(ExchangeSource exchangeSource, Currency selectedCurrency) { + this.exchangeSource = exchangeSource; + this.selectedCurrency = selectedCurrency; + } + + protected Task createTask() { + return new Task<>() { + protected ExchangeRatesUpdatedEvent call() { + Double rate = exchangeSource.getExchangeRate(selectedCurrency); + return new ExchangeRatesUpdatedEvent(selectedCurrency, rate); + } + }; + } + + public ExchangeSource getExchangeSource() { + return exchangeSource; + } + } + + private static class CoinbaseRates { + CoinbaseData data; + } + + private static class CoinbaseData { + String currency; + Map rates; + } + + private static class CoinGeckoRates { + Map rates = new LinkedHashMap<>(); + } + + private static class CoinGeckoRate { + String name; + String unit; + Double value; + String type; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java b/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java new file mode 100644 index 00000000..0b7f41c6 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java @@ -0,0 +1,94 @@ +package com.sparrowwallet.sparrow.preferences; + +import com.sparrowwallet.drongo.BitcoinUnit; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent; +import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent; +import com.sparrowwallet.sparrow.io.Config; +import com.sparrowwallet.sparrow.io.ExchangeSource; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; + +import java.util.Currency; +import java.util.List; + +public class GeneralPreferencesController extends PreferencesDetailController { + @FXML + private ComboBox bitcoinUnit; + + @FXML + private ComboBox fiatCurrency; + + @FXML + private ComboBox exchangeSource; + + private final ChangeListener fiatCurrencyListener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Currency oldValue, Currency newValue) { + if (newValue != null) { + Config.get().setFiatCurrency(newValue); + EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getValue(), newValue)); + } + } + }; + + @Override + public void initializeView(Config config) { + if(config.getBitcoinUnit() != null) { + bitcoinUnit.setValue(config.getBitcoinUnit()); + } + + bitcoinUnit.valueProperty().addListener((observable, oldValue, newValue) -> { + config.setBitcoinUnit(newValue); + EventManager.get().post(new BitcoinUnitChangedEvent(newValue)); + }); + + if(config.getExchangeSource() != null) { + exchangeSource.setValue(config.getExchangeSource()); + } else { + exchangeSource.getSelectionModel().select(2); + config.setExchangeSource(exchangeSource.getValue()); + } + + exchangeSource.valueProperty().addListener((observable, oldValue, source) -> { + config.setExchangeSource(source); + updateCurrencies(source); + }); + + updateCurrencies(exchangeSource.getSelectionModel().getSelectedItem()); + } + + private void updateCurrencies(ExchangeSource exchangeSource) { + ExchangeSource.CurrenciesService currenciesService = new ExchangeSource.CurrenciesService(exchangeSource); + currenciesService.setOnSucceeded(event -> { + updateCurrencies(currenciesService.getValue()); + }); + currenciesService.start(); + } + + private void updateCurrencies(List currencies) { + fiatCurrency.valueProperty().removeListener(fiatCurrencyListener); + + fiatCurrency.getItems().clear(); + fiatCurrency.getItems().addAll(currencies); + + Currency configCurrency = Config.get().getFiatCurrency(); + if(configCurrency != null && currencies.contains(configCurrency)) { + fiatCurrency.setDisable(false); + fiatCurrency.setValue(configCurrency); + } else if(!currencies.isEmpty()) { + fiatCurrency.setDisable(false); + fiatCurrency.getSelectionModel().select(0); + Config.get().setFiatCurrency(fiatCurrency.getValue()); + } else { + fiatCurrency.setDisable(true); + } + + //Always fire event regardless of previous selection to update rates + EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getValue(), fiatCurrency.getValue())); + + fiatCurrency.valueProperty().addListener(fiatCurrencyListener); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java b/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java index 605ff8ad..8464b8b0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/PreferencesDialog.java @@ -27,7 +27,7 @@ public class PreferencesDialog extends Dialog { if(initialGroup != null) { preferencesController.selectGroup(initialGroup); } else { - preferencesController.selectGroup(PreferenceGroup.SERVER); + preferencesController.selectGroup(PreferenceGroup.GENERAL); } final ButtonType closeButtonType = new javafx.scene.control.ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java index ac00fb2b..f705f1c9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java @@ -1,12 +1,14 @@ package com.sparrowwallet.sparrow.wallet; import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.BalanceChart; import com.sparrowwallet.sparrow.control.CoinLabel; -import com.sparrowwallet.sparrow.control.CopyableLabel; +import com.sparrowwallet.sparrow.control.FiatLabel; import com.sparrowwallet.sparrow.control.TransactionsTreeTable; import com.sparrowwallet.sparrow.event.*; +import com.sparrowwallet.sparrow.io.ExchangeSource; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -14,6 +16,8 @@ import javafx.scene.control.TreeItem; import javafx.scene.input.MouseEvent; import java.net.URL; +import java.util.Currency; +import java.util.Map; import java.util.ResourceBundle; public class TransactionsController extends WalletFormController implements Initializable { @@ -22,7 +26,7 @@ public class TransactionsController extends WalletFormController implements Init private CoinLabel balance; @FXML - private CopyableLabel fiatBalance; + private FiatLabel fiatBalance; @FXML private CoinLabel mempoolBalance; @@ -45,6 +49,7 @@ public class TransactionsController extends WalletFormController implements Init transactionsTable.initialize(walletTransactionsEntry); balance.setValue(walletTransactionsEntry.getBalance()); + setFiatBalance(AppController.getFiatCurrencyExchangeRate(), walletTransactionsEntry.getBalance()); mempoolBalance.setValue(walletTransactionsEntry.getMempoolBalance()); balanceChart.initialize(walletTransactionsEntry); @@ -56,6 +61,16 @@ public class TransactionsController extends WalletFormController implements Init }); } + private void setFiatBalance(Map fiatCurrencyExchangeRate, long balance) { + if(fiatCurrencyExchangeRate != null && !fiatCurrencyExchangeRate.isEmpty()) { + Currency currency = fiatCurrencyExchangeRate.keySet().iterator().next(); + Double rate = fiatCurrencyExchangeRate.get(currency); + if(rate != null) { + fiatBalance.set(currency, rate, balance); + } + } + } + @Subscribe public void walletNodesChanged(WalletNodesChangedEvent event) { if(event.getWallet().equals(walletForm.getWallet())) { @@ -99,6 +114,20 @@ public class TransactionsController extends WalletFormController implements Init mempoolBalance.refresh(event.getBitcoinUnit()); } + @Subscribe + public void fiatCurrencySelected(FiatCurrencySelectedEvent event) { + if(event.getExchangeSource() == ExchangeSource.NONE) { + fiatBalance.setCurrency(null); + fiatBalance.setBtcRate(0.0); + } + } + + @Subscribe + public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) { + Map fiatRate = Map.of(event.getSelectedCurrency(), event.getRate()); + setFiatBalance(fiatRate, getWalletForm().getWalletTransactionsEntry().getBalance()); + } + //TODO: Remove public void advanceBlock(MouseEvent event) { Integer currentBlock = getWalletForm().getWallet().getStoredBlockHeight(); diff --git a/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml b/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml new file mode 100644 index 00000000..f5312970 --- /dev/null +++ b/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + +
+
+
diff --git a/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.fxml b/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.fxml index 1234188b..3d747e5d 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/preferences/preferences.fxml @@ -10,15 +10,20 @@ - - - - + + + + + + + + + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml index c212e794..edfe5a1c 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml @@ -13,7 +13,7 @@ - +
@@ -35,7 +35,7 @@ - +