fiat currency support

This commit is contained in:
Craig Raw 2020-07-10 17:13:32 +02:00
parent 539013919b
commit c477d31d3d
14 changed files with 619 additions and 15 deletions

View file

@ -52,6 +52,10 @@ public class AppController implements Initializable {
private static final int SERVER_PING_PERIOD = 10 * 1000; private static final int SERVER_PING_PERIOD = 10 * 1000;
private static final int ENUMERATE_HW_PERIOD = 30 * 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"; public static final String DRAG_OVER_CLASS = "drag-over";
private MainApp application; private MainApp application;
@ -90,6 +94,8 @@ public class AppController implements Initializable {
private Timeline statusTimeline; private Timeline statusTimeline;
private ExchangeSource.RatesService ratesService;
private ElectrumServer.ConnectionService connectionService; private ElectrumServer.ConnectionService connectionService;
private static Integer currentBlockHeight; private static Integer currentBlockHeight;
@ -98,6 +104,8 @@ public class AppController implements Initializable {
private static Map<Integer, Double> targetBlockFeeRates; private static Map<Integer, Double> targetBlockFeeRates;
private static Map<Currency, Double> fiatCurrencyExchangeRate;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
EventManager.get().register(this); EventManager.get().register(this);
@ -174,20 +182,36 @@ public class AppController implements Initializable {
if(!connectionService.isRunning()) { if(!connectionService.isRunning()) {
connectionService.start(); connectionService.start();
} }
if(ratesService.getState() == Worker.State.CANCELLED) {
ratesService.reset();
}
if(!ratesService.isRunning() && ratesService.getExchangeSource() != ExchangeSource.NONE) {
ratesService.start();
}
} else { } else {
connectionService.cancel(); connectionService.cancel();
ratesService.cancel();
} }
} }
}); });
onlineProperty.bindBidirectional(serverToggle.selectedProperty()); onlineProperty.bindBidirectional(serverToggle.selectedProperty());
connectionService = createConnectionService();
Config config = Config.get(); Config config = Config.get();
connectionService = createConnectionService();
if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) { if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) {
connectionService.start(); 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()); openTransactionIdItem.disableProperty().bind(onlineProperty.not());
openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json")); openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json"));
@ -219,6 +243,18 @@ public class AppController implements Initializable {
return connectionService; 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) { public void setApplication(MainApp application) {
this.application = application; this.application = application;
} }
@ -361,6 +397,10 @@ public class AppController implements Initializable {
return targetBlockFeeRates; return targetBlockFeeRates;
} }
public static Map<Currency, Double> getFiatCurrencyExchangeRate() {
return fiatCurrencyExchangeRate;
}
public static void showErrorDialog(String title, String content) { public static void showErrorDialog(String title, String content) {
Alert alert = new Alert(Alert.AlertType.ERROR); Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(title); alert.setTitle(title);
@ -820,4 +860,25 @@ public class AppController implements Initializable {
Tab tab = addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex()); Tab tab = addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex());
tabs.getSelectionModel().select(tab); tabs.getSelectionModel().select(tab);
} }
@Subscribe
public void bitcoinUnitChanged(BitcoinUnitChangedEvent event) {
Optional<Toggle> 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());
}
} }

View file

@ -18,7 +18,7 @@ import java.util.Locale;
public class CoinLabel extends CopyableLabel { public class CoinLabel extends CopyableLabel {
public static final DecimalFormat BTC_FORMAT = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); 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 Tooltip tooltip;
private final CoinContextMenu contextMenu; private final CoinContextMenu contextMenu;
@ -35,15 +35,15 @@ public class CoinLabel extends CopyableLabel {
} }
public final LongProperty valueProperty() { public final LongProperty valueProperty() {
return value; return valueProperty;
} }
public final long getValue() { public final long getValue() {
return value.get(); return valueProperty.get();
} }
public final void setValue(long value) { public final void setValue(long value) {
this.value.set(value); this.valueProperty.set(value);
} }
public void refresh() { public void refresh() {

View file

@ -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<Currency> 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<Currency> 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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -29,6 +29,7 @@ public class FontAwesome5 extends GlyphFont {
QUESTION_CIRCLE('\uf059'), QUESTION_CIRCLE('\uf059'),
SD_CARD('\uf7c2'), SD_CARD('\uf7c2'),
SEARCH('\uf002'), SEARCH('\uf002'),
TOOLS('\uf7d9'),
WALLET('\uf555'); WALLET('\uf555');
private final char ch; private final char ch;

View file

@ -6,12 +6,15 @@ import com.sparrowwallet.sparrow.Mode;
import java.io.*; import java.io.*;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Currency;
public class Config { public class Config {
public static final String CONFIG_FILENAME = ".config"; public static final String CONFIG_FILENAME = ".config";
private Mode mode; private Mode mode;
private BitcoinUnit bitcoinUnit; private BitcoinUnit bitcoinUnit;
private Currency fiatCurrency;
private ExchangeSource exchangeSource;
private Integer keyDerivationPeriod; private Integer keyDerivationPeriod;
private File hwi; private File hwi;
private String electrumServer; private String electrumServer;
@ -78,6 +81,24 @@ public class Config {
flush(); 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() { public Integer getKeyDerivationPeriod() {
return keyDerivationPeriod; return keyDerivationPeriod;
} }

View file

@ -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<Currency> getSupportedCurrencies() {
return Collections.emptyList();
}
@Override
public Double getExchangeRate(Currency currency) {
return null;
}
},
COINBASE("Coinbase") {
@Override
public List<Currency> 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<Currency> 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<Currency> 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<List<Currency>> {
private final ExchangeSource exchangeSource;
public CurrenciesService(ExchangeSource exchangeSource) {
this.exchangeSource = exchangeSource;
}
@Override
protected Task<List<Currency>> createTask() {
return new Task<>() {
protected List<Currency> call() {
return exchangeSource.getSupportedCurrencies();
}
};
}
}
public static class RatesService extends ScheduledService<ExchangeRatesUpdatedEvent> {
private final ExchangeSource exchangeSource;
private final Currency selectedCurrency;
public RatesService(ExchangeSource exchangeSource, Currency selectedCurrency) {
this.exchangeSource = exchangeSource;
this.selectedCurrency = selectedCurrency;
}
protected Task<ExchangeRatesUpdatedEvent> 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<String, Double> rates;
}
private static class CoinGeckoRates {
Map<String, CoinGeckoRate> rates = new LinkedHashMap<>();
}
private static class CoinGeckoRate {
String name;
String unit;
Double value;
String type;
}
}

View file

@ -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> bitcoinUnit;
@FXML
private ComboBox<Currency> fiatCurrency;
@FXML
private ComboBox<ExchangeSource> exchangeSource;
private final ChangeListener<Currency> fiatCurrencyListener = new ChangeListener<Currency>() {
@Override
public void changed(ObservableValue<? extends Currency> 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<Currency> 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);
}
}

View file

@ -27,7 +27,7 @@ public class PreferencesDialog extends Dialog<Void> {
if(initialGroup != null) { if(initialGroup != null) {
preferencesController.selectGroup(initialGroup); preferencesController.selectGroup(initialGroup);
} else { } else {
preferencesController.selectGroup(PreferenceGroup.SERVER); preferencesController.selectGroup(PreferenceGroup.GENERAL);
} }
final ButtonType closeButtonType = new javafx.scene.control.ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE); final ButtonType closeButtonType = new javafx.scene.control.ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE);

View file

@ -1,12 +1,14 @@
package com.sparrowwallet.sparrow.wallet; package com.sparrowwallet.sparrow.wallet;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.BalanceChart; import com.sparrowwallet.sparrow.control.BalanceChart;
import com.sparrowwallet.sparrow.control.CoinLabel; 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.control.TransactionsTreeTable;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.ExchangeSource;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -14,6 +16,8 @@ import javafx.scene.control.TreeItem;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import java.net.URL; import java.net.URL;
import java.util.Currency;
import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
public class TransactionsController extends WalletFormController implements Initializable { public class TransactionsController extends WalletFormController implements Initializable {
@ -22,7 +26,7 @@ public class TransactionsController extends WalletFormController implements Init
private CoinLabel balance; private CoinLabel balance;
@FXML @FXML
private CopyableLabel fiatBalance; private FiatLabel fiatBalance;
@FXML @FXML
private CoinLabel mempoolBalance; private CoinLabel mempoolBalance;
@ -45,6 +49,7 @@ public class TransactionsController extends WalletFormController implements Init
transactionsTable.initialize(walletTransactionsEntry); transactionsTable.initialize(walletTransactionsEntry);
balance.setValue(walletTransactionsEntry.getBalance()); balance.setValue(walletTransactionsEntry.getBalance());
setFiatBalance(AppController.getFiatCurrencyExchangeRate(), walletTransactionsEntry.getBalance());
mempoolBalance.setValue(walletTransactionsEntry.getMempoolBalance()); mempoolBalance.setValue(walletTransactionsEntry.getMempoolBalance());
balanceChart.initialize(walletTransactionsEntry); balanceChart.initialize(walletTransactionsEntry);
@ -56,6 +61,16 @@ public class TransactionsController extends WalletFormController implements Init
}); });
} }
private void setFiatBalance(Map<Currency, Double> 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 @Subscribe
public void walletNodesChanged(WalletNodesChangedEvent event) { public void walletNodesChanged(WalletNodesChangedEvent event) {
if(event.getWallet().equals(walletForm.getWallet())) { if(event.getWallet().equals(walletForm.getWallet())) {
@ -99,6 +114,20 @@ public class TransactionsController extends WalletFormController implements Init
mempoolBalance.refresh(event.getBitcoinUnit()); 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<Currency, Double> fiatRate = Map.of(event.getSelectedCurrency(), event.getRate());
setFiatBalance(fiatRate, getWalletForm().getWalletTransactionsEntry().getBalance());
}
//TODO: Remove //TODO: Remove
public void advanceBlock(MouseEvent event) { public void advanceBlock(MouseEvent event) {
Integer currentBlock = getWalletForm().getWallet().getStoredBlockHeight(); Integer currentBlock = getWalletForm().getWallet().getStoredBlockHeight();

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import tornadofx.control.Form?>
<?import tornadofx.control.Fieldset?>
<?import tornadofx.control.Field?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.Insets?>
<?import com.sparrowwallet.drongo.BitcoinUnit?>
<?import com.sparrowwallet.sparrow.io.ExchangeSource?>
<GridPane hgap="10.0" vgap="10.0" stylesheets="@preferences.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.preferences.GeneralPreferencesController">
<padding>
<Insets left="25.0" right="25.0" top="25.0" />
</padding>
<columnConstraints>
<ColumnConstraints percentWidth="100" />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<Form GridPane.columnIndex="0" GridPane.rowIndex="0">
<Fieldset inputGrow="SOMETIMES" text="Bitcoin">
<Field text="Unit:">
<ComboBox fx:id="bitcoinUnit">
<items>
<FXCollections fx:factory="observableArrayList">
<BitcoinUnit fx:constant="AUTO" />
<BitcoinUnit fx:constant="BTC" />
<BitcoinUnit fx:constant="SATOSHIS" />
</FXCollections>
</items>
</ComboBox>
</Field>
</Fieldset>
<Fieldset inputGrow="SOMETIMES" text="Fiat">
<Field text="Currency:">
<ComboBox fx:id="fiatCurrency" />
</Field>
<Field text="Source:">
<ComboBox fx:id="exchangeSource">
<items>
<FXCollections fx:factory="observableArrayList">
<ExchangeSource fx:constant="NONE" />
<ExchangeSource fx:constant="COINBASE" />
<ExchangeSource fx:constant="COINGECKO" />
</FXCollections>
</items>
</ComboBox>
</Field>
</Fieldset>
</Form>
</GridPane>

View file

@ -10,15 +10,20 @@
<?import com.sparrowwallet.sparrow.preferences.PreferenceGroup?> <?import com.sparrowwallet.sparrow.preferences.PreferenceGroup?>
<?import org.controlsfx.glyphfont.Glyph?> <?import org.controlsfx.glyphfont.Glyph?>
<BorderPane stylesheets="@../general.css, @preferences.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.preferences.PreferencesController"> <BorderPane stylesheets="@../general.css, @preferences.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.preferences.PreferencesController">
<padding>
<Insets top="0" left="0" right="0" bottom="0" />
</padding>
<left> <left>
<VBox styleClass="list-menu"> <VBox styleClass="list-menu">
<ToggleButton VBox.vgrow="ALWAYS" text="Server" wrapText="true" textAlignment="CENTER" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$preferencesMenu"> <ToggleButton VBox.vgrow="ALWAYS" text="General" wrapText="true" textAlignment="CENTER" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$preferencesMenu">
<toggleGroup> <toggleGroup>
<ToggleGroup fx:id="preferencesMenu" /> <ToggleGroup fx:id="preferencesMenu" />
</toggleGroup> </toggleGroup>
<graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOOLS" />
</graphic>
<userData>
<PreferenceGroup fx:constant="GENERAL"/>
</userData>
</ToggleButton>
<ToggleButton VBox.vgrow="ALWAYS" text="Server" wrapText="true" textAlignment="CENTER" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$preferencesMenu">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="SERVER" /> <Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="SERVER" />
</graphic> </graphic>

View file

@ -13,7 +13,7 @@
<?import tornadofx.control.Fieldset?> <?import tornadofx.control.Fieldset?>
<?import tornadofx.control.Field?> <?import tornadofx.control.Field?>
<?import com.sparrowwallet.sparrow.control.CoinLabel?> <?import com.sparrowwallet.sparrow.control.CoinLabel?>
<?import com.sparrowwallet.sparrow.control.CopyableLabel?> <?import com.sparrowwallet.sparrow.control.FiatLabel?>
<BorderPane stylesheets="@transactions.css, @wallet.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.TransactionsController"> <BorderPane stylesheets="@transactions.css, @wallet.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.TransactionsController">
<center> <center>
@ -35,7 +35,7 @@
<CoinLabel fx:id="balance"/> <CoinLabel fx:id="balance"/>
</Field> </Field>
<Field text="Fiat balance:"> <Field text="Fiat balance:">
<CopyableLabel fx:id="fiatBalance" /> <FiatLabel fx:id="fiatBalance" />
</Field> </Field>
<Field text="Mempool:"> <Field text="Mempool:">
<CoinLabel fx:id="mempoolBalance" /> <CoinLabel fx:id="mempoolBalance" />