preferences update

This commit is contained in:
Craig Raw 2020-07-16 10:25:24 +02:00
parent 3cee45223e
commit bd721be1a2
6 changed files with 168 additions and 38 deletions

View file

@ -0,0 +1,45 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import org.controlsfx.glyphfont.Glyph;
public class HelpLabel extends Label {
private final Tooltip tooltip;
public HelpLabel() {
super("", getHelpGlyph());
tooltip = new Tooltip();
tooltip.textProperty().bind(helpTextProperty());
setTooltip(tooltip);
getStyleClass().add("help-label");
}
private static Glyph getHelpGlyph() {
Glyph lockGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.QUESTION_CIRCLE);
lockGlyph.getStyleClass().add("help-icon");
lockGlyph.setFontSize(12);
return lockGlyph;
}
public final StringProperty helpTextProperty() {
if(helpText == null) {
helpText = new SimpleStringProperty(this, "helpText", "");
}
return helpText;
}
private StringProperty helpText;
public final void setHelpText(String value) {
helpTextProperty().setValue(value);
}
public final String getHelpText() {
return helpText == null ? "" : helpText.getValue();
}
}

View file

@ -116,6 +116,15 @@ public class ElectrumServer {
return client.createRequest().returnAs(BlockHeaderTip.class).method("blockchain.headers.subscribe").id(1).execute(); return client.createRequest().returnAs(BlockHeaderTip.class).method("blockchain.headers.subscribe").id(1).execute();
} }
public static synchronized boolean isConnected() {
if(transport != null) {
TcpTransport tcpTransport = (TcpTransport)transport;
return tcpTransport.isConnected();
}
return false;
}
public static synchronized void closeActiveConnection() throws ServerException { public static synchronized void closeActiveConnection() throws ServerException {
try { try {
if(transport != null) { if(transport != null) {
@ -572,6 +581,10 @@ public class ElectrumServer {
public String hex; public String hex;
public BlockHeader getBlockHeader() { public BlockHeader getBlockHeader() {
if(hex == null) {
return null;
}
byte[] blockHeaderBytes = Utils.hexToBytes(hex); byte[] blockHeaderBytes = Utils.hexToBytes(hex);
return new BlockHeader(blockHeaderBytes); return new BlockHeader(blockHeaderBytes);
} }
@ -715,6 +728,10 @@ public class ElectrumServer {
} }
} }
public boolean isConnected() {
return socket != null && running;
}
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
return socketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT)); return socketFactory.createSocket(server.getHost(), server.getPortOrDefault(DEFAULT_PORT));
} }
@ -832,11 +849,20 @@ public class ElectrumServer {
public static class ConnectionService extends ScheduledService<FeeRatesUpdatedEvent> implements Thread.UncaughtExceptionHandler { public static class ConnectionService extends ScheduledService<FeeRatesUpdatedEvent> implements Thread.UncaughtExceptionHandler {
private static final int FEE_RATES_PERIOD = 5 * 60 * 1000; private static final int FEE_RATES_PERIOD = 5 * 60 * 1000;
private final boolean subscribe;
private boolean firstCall = true; private boolean firstCall = true;
private Thread reader; private Thread reader;
private Throwable lastReaderException; private Throwable lastReaderException;
private long feeRatesRetrievedAt; private long feeRatesRetrievedAt;
public ConnectionService() {
this(true);
}
public ConnectionService(boolean subscribe) {
this.subscribe = subscribe;
}
@Override @Override
protected Task<FeeRatesUpdatedEvent> createTask() { protected Task<FeeRatesUpdatedEvent> createTask() {
return new Task<>() { return new Task<>() {
@ -853,7 +879,13 @@ public class ElectrumServer {
List<String> serverVersion = electrumServer.getServerVersion(); List<String> serverVersion = electrumServer.getServerVersion();
firstCall = false; firstCall = false;
BlockHeaderTip tip = electrumServer.subscribeBlockHeaders(); BlockHeaderTip tip;
if(subscribe) {
tip = electrumServer.subscribeBlockHeaders();
} else {
tip = new BlockHeaderTip();
}
String banner = electrumServer.getServerBanner(); String banner = electrumServer.getServerBanner();
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE); Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE);

View file

@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.preferences;
import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.drongo.BitcoinUnit;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent; import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent;
import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent; import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
@ -24,6 +25,12 @@ public class GeneralPreferencesController extends PreferencesDetailController {
@FXML @FXML
private ComboBox<ExchangeSource> exchangeSource; private ComboBox<ExchangeSource> exchangeSource;
@FXML
private UnlabeledToggleSwitch groupByAddress;
@FXML
private UnlabeledToggleSwitch includeMempoolChange;
private final ChangeListener<Currency> fiatCurrencyListener = new ChangeListener<Currency>() { private final ChangeListener<Currency> fiatCurrencyListener = new ChangeListener<Currency>() {
@Override @Override
public void changed(ObservableValue<? extends Currency> observable, Currency oldValue, Currency newValue) { public void changed(ObservableValue<? extends Currency> observable, Currency oldValue, Currency newValue) {
@ -58,6 +65,15 @@ public class GeneralPreferencesController extends PreferencesDetailController {
}); });
updateCurrencies(exchangeSource.getSelectionModel().getSelectedItem()); updateCurrencies(exchangeSource.getSelectionModel().getSelectedItem());
groupByAddress.setSelected(config.isGroupByAddress());
includeMempoolChange.setSelected(config.isIncludeMempoolChange());
groupByAddress.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
config.setGroupByAddress(newValue);
});
includeMempoolChange.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
config.setIncludeMempoolChange(newValue);
});
} }
private void updateCurrencies(ExchangeSource exchangeSource) { private void updateCurrencies(ExchangeSource exchangeSource) {

View file

@ -3,12 +3,13 @@ package com.sparrowwallet.sparrow.preferences;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.control.TextFieldValidator; import com.sparrowwallet.sparrow.control.TextFieldValidator;
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch; import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
import com.sparrowwallet.sparrow.event.ConnectionEvent;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.ElectrumServer; import com.sparrowwallet.sparrow.io.ElectrumServer;
import com.sparrowwallet.sparrow.io.ServerException;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.concurrent.WorkerStateEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Control; import javafx.scene.control.Control;
@ -17,6 +18,7 @@ import javafx.scene.control.TextField;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.Duration;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.validation.ValidationResult; import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.ValidationSupport;
@ -123,40 +125,31 @@ public class ServerPreferencesController extends PreferencesDetailController {
}); });
testConnection.setOnAction(event -> { testConnection.setOnAction(event -> {
try {
ElectrumServer.closeActiveConnection();
} catch (ServerException e) {
testResults.setText("Failed to disconnect:\n" + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
}
ElectrumServer.ServerVersionService serverVersionService = new ElectrumServer.ServerVersionService();
serverVersionService.setOnSucceeded(successEvent -> {
List<String> serverVersion = serverVersionService.getValue();
testResults.setText("Connected to " + serverVersion.get(0) + " on protocol version " + serverVersion.get(1));
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE, Color.rgb(80, 161, 79)));
ElectrumServer.ServerBannerService serverBannerService = new ElectrumServer.ServerBannerService();
serverBannerService.setOnSucceeded(bannerSuccessEvent -> {
testResults.setText(testResults.getText() + "\nServer Banner: " + serverBannerService.getValue());
});
serverBannerService.setOnFailed(bannerFailEvent -> {
testResults.setText(testResults.getText() + "\nServer Banner: None");
});
serverBannerService.start();
});
serverVersionService.setOnFailed(failEvent -> {
Throwable e = failEvent.getSource().getException();
String reason = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
if(e.getCause() != null && e.getCause() instanceof SSLHandshakeException) {
reason = "SSL Handshake Error\n" + reason;
}
testResults.setText("Could not connect:\n\n" + reason);
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.EXCLAMATION_CIRCLE, Color.rgb(202, 18, 67)));
});
testResults.setText("Connecting to " + config.getElectrumServer() + "..."); testResults.setText("Connecting to " + config.getElectrumServer() + "...");
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null)); testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null));
serverVersionService.start();
boolean existingConnection = ElectrumServer.isConnected();
if(existingConnection) {
ElectrumServer.ServerBannerService serverBannerService = new ElectrumServer.ServerBannerService();
serverBannerService.setOnSucceeded(successEvent -> {
showConnectionSuccess(null, serverBannerService.getValue());
});
serverBannerService.setOnFailed(this::showConnectionFailure);
serverBannerService.start();
} else {
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(false);
connectionService.setPeriod(Duration.minutes(1));
connectionService.setOnSucceeded(successEvent -> {
ConnectionEvent connectionEvent = (ConnectionEvent)connectionService.getValue();
showConnectionSuccess(connectionEvent.getServerVersion(), connectionEvent.getServerBanner());
connectionService.cancel();
});
connectionService.setOnFailed(workerStateEvent -> {
showConnectionFailure(workerStateEvent);
connectionService.cancel();
});
connectionService.start();
}
}); });
String electrumServer = config.getElectrumServer(); String electrumServer = config.getElectrumServer();
@ -201,6 +194,27 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
} }
private void showConnectionSuccess(List<String> serverVersion, String serverBanner) {
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE, Color.rgb(80, 161, 79)));
if(serverVersion != null) {
testResults.setText("Connected to " + serverVersion.get(0) + " on protocol version " + serverVersion.get(1));
}
if(serverBanner != null) {
testResults.setText(testResults.getText() + "\nServer Banner: " + serverBanner);
}
}
private void showConnectionFailure(WorkerStateEvent failEvent) {
Throwable e = failEvent.getSource().getException();
String reason = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
if(e.getCause() != null && e.getCause() instanceof SSLHandshakeException) {
reason = "SSL Handshake Error\n" + reason;
}
testResults.setText("Could not connect:\n\n" + reason);
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.EXCLAMATION_CIRCLE, Color.rgb(202, 18, 67)));
}
private void setupValidation() { private void setupValidation() {
validationSupport.registerValidator(host, Validator.combine( validationSupport.registerValidator(host, Validator.combine(
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid host name", getHost(newValue) == null) (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid host name", getHost(newValue) == null)

View file

@ -5,6 +5,11 @@
-fx-padding: 5 0 5 0; -fx-padding: 5 0 5 0;
} }
.form .wideLabelFieldSet.fieldset:horizontal .label-container {
-fx-pref-width: 160px;
-fx-pref-height: 25px;
}
.form .fieldset:horizontal .label-container { .form .fieldset:horizontal .label-container {
-fx-pref-width: 110px; -fx-pref-width: 110px;
-fx-pref-height: 25px; -fx-pref-height: 25px;
@ -113,3 +118,7 @@
.copyable-text-field .copy-button:pressed > .graphic { .copyable-text-field .copy-button:pressed > .graphic {
-fx-background-color: #116a8d; -fx-background-color: #116a8d;
} }
.help-label {
-fx-padding: 0 0 0 10;
}

View file

@ -13,6 +13,9 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import com.sparrowwallet.drongo.BitcoinUnit?> <?import com.sparrowwallet.drongo.BitcoinUnit?>
<?import com.sparrowwallet.sparrow.io.ExchangeSource?> <?import com.sparrowwallet.sparrow.io.ExchangeSource?>
<?import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch?>
<?import com.sparrowwallet.sparrow.control.HelpLabel?>
<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"> <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> <padding>
<Insets left="25.0" right="25.0" top="25.0" /> <Insets left="25.0" right="25.0" top="25.0" />
@ -25,8 +28,8 @@
</rowConstraints> </rowConstraints>
<Form GridPane.columnIndex="0" GridPane.rowIndex="0"> <Form GridPane.columnIndex="0" GridPane.rowIndex="0">
<Fieldset inputGrow="SOMETIMES" text="Bitcoin"> <Fieldset inputGrow="SOMETIMES" text="Bitcoin" styleClass="wideLabelFieldSet">
<Field text="Unit:"> <Field text="Display unit:">
<ComboBox fx:id="bitcoinUnit"> <ComboBox fx:id="bitcoinUnit">
<items> <items>
<FXCollections fx:factory="observableArrayList"> <FXCollections fx:factory="observableArrayList">
@ -36,13 +39,14 @@
</FXCollections> </FXCollections>
</items> </items>
</ComboBox> </ComboBox>
<HelpLabel helpText="Display unit for bitcoin amounts. Auto displays amounts over 1 BTC in BTC, and amounts under in satoshis"/>
</Field> </Field>
</Fieldset> </Fieldset>
<Fieldset inputGrow="SOMETIMES" text="Fiat"> <Fieldset inputGrow="SOMETIMES" text="Fiat" styleClass="wideLabelFieldSet">
<Field text="Currency:"> <Field text="Currency:">
<ComboBox fx:id="fiatCurrency" /> <ComboBox fx:id="fiatCurrency" />
</Field> </Field>
<Field text="Source:"> <Field text="Exchange rate source:">
<ComboBox fx:id="exchangeSource"> <ComboBox fx:id="exchangeSource">
<items> <items>
<FXCollections fx:factory="observableArrayList"> <FXCollections fx:factory="observableArrayList">
@ -54,5 +58,15 @@
</ComboBox> </ComboBox>
</Field> </Field>
</Fieldset> </Fieldset>
<Fieldset inputGrow="SOMETIMES" text="Coin Selection" styleClass="wideLabelFieldSet">
<Field text="Group by address:">
<UnlabeledToggleSwitch fx:id="groupByAddress" />
<HelpLabel helpText="Group UTXOs by address when sending to improve privacy by only sending from an address once"/>
</Field>
<Field text="Include mempool change:">
<UnlabeledToggleSwitch fx:id="includeMempoolChange" />
<HelpLabel helpText="Allow a wallet to spend UTXOs that are still in the mempool where all their inputs are from that wallet"/>
</Field>
</Fieldset>
</Form> </Form>
</GridPane> </GridPane>