mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-23 20:36:44 +00:00
send controller initial, fee rates support
This commit is contained in:
parent
1d0b66c45a
commit
571c515a46
13 changed files with 462 additions and 17 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit 24cde9d073da636fbc2150b7abbd50b48342e040
|
||||
Subproject commit c4dd1cb9dd40a7a16829a00f45acbd55f63d9895
|
|
@ -88,10 +88,12 @@ public class AppController implements Initializable {
|
|||
|
||||
private ElectrumServer.ConnectionService connectionService;
|
||||
|
||||
public static Integer currentBlockHeight;
|
||||
private static Integer currentBlockHeight;
|
||||
|
||||
public static boolean showTxHexProperty;
|
||||
|
||||
private static Map<Integer, Double> targetBlockFeeRates;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
EventManager.get().register(this);
|
||||
|
@ -342,6 +344,10 @@ public class AppController implements Initializable {
|
|||
return currentBlockHeight;
|
||||
}
|
||||
|
||||
public static Map<Integer, Double> getTargetBlockFeeRates() {
|
||||
return targetBlockFeeRates;
|
||||
}
|
||||
|
||||
public static void showErrorDialog(String title, String content) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
|
@ -764,6 +770,7 @@ public class AppController implements Initializable {
|
|||
@Subscribe
|
||||
public void newConnection(ConnectionEvent event) {
|
||||
currentBlockHeight = event.getBlockHeight();
|
||||
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||
String banner = event.getServerBanner();
|
||||
String status = "Connected: " + (banner == null ? "Server" : banner.split(System.lineSeparator(), 2)[0]) + " at height " + event.getBlockHeight();
|
||||
EventManager.get().post(new StatusEvent(status));
|
||||
|
@ -783,6 +790,11 @@ public class AppController implements Initializable {
|
|||
EventManager.get().post(new StatusEvent(status));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void feesUpdated(FeeRatesUpdatedEvent event) {
|
||||
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void viewTransaction(ViewTransactionEvent event) {
|
||||
Tab tab = addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex());
|
||||
|
|
35
src/main/java/com/sparrowwallet/sparrow/BitcoinUnit.java
Normal file
35
src/main/java/com/sparrowwallet/sparrow/BitcoinUnit.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
package com.sparrowwallet.sparrow;
|
||||
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
|
||||
public enum BitcoinUnit {
|
||||
BTC("BTC") {
|
||||
@Override
|
||||
public long getSatsValue(double unitValue) {
|
||||
return (long)(unitValue * Transaction.SATOSHIS_PER_BITCOIN);
|
||||
}
|
||||
},
|
||||
SATOSHIS("sats") {
|
||||
@Override
|
||||
public long getSatsValue(double unitValue) {
|
||||
return (long)unitValue;
|
||||
}
|
||||
};
|
||||
|
||||
private final String label;
|
||||
|
||||
BitcoinUnit(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public abstract long getSatsValue(double unitValue);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class FeeRatesChart extends LineChart<String, Number> {
|
||||
private XYChart.Series<String, Number> feeRateSeries;
|
||||
private Integer selectedTargetBlocks;
|
||||
|
||||
public FeeRatesChart(@NamedArg("xAxis") Axis<String> xAxis, @NamedArg("yAxis") Axis<Number> yAxis) {
|
||||
super(xAxis, yAxis);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
feeRateSeries = new XYChart.Series<>();
|
||||
getData().add(feeRateSeries);
|
||||
}
|
||||
|
||||
public void update(Map<Integer, Double> targetBlocksFeeRates) {
|
||||
feeRateSeries.getData().clear();
|
||||
|
||||
for(Integer targetBlocks : targetBlocksFeeRates.keySet()) {
|
||||
XYChart.Data<String, Number> data = new XYChart.Data<>(Integer.toString(targetBlocks), targetBlocksFeeRates.get(targetBlocks));
|
||||
feeRateSeries.getData().add(data);
|
||||
}
|
||||
|
||||
if(selectedTargetBlocks != null) {
|
||||
select(selectedTargetBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
public void select(Integer targetBlocks) {
|
||||
Node selectedSymbol = lookup(".chart-line-symbol.selected");
|
||||
if(selectedSymbol != null) {
|
||||
selectedSymbol.getStyleClass().remove("selected");
|
||||
}
|
||||
|
||||
for(int i = 0; i < feeRateSeries.getData().size(); i++) {
|
||||
XYChart.Data<String, Number> data = feeRateSeries.getData().get(i);
|
||||
Node symbol = lookup(".chart-line-symbol.data" + i);
|
||||
if(symbol != null) {
|
||||
if(data.getXValue().equals(targetBlocks.toString())) {
|
||||
symbol.getStyleClass().add("selected");
|
||||
selectedTargetBlocks = targetBlocks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,6 +64,11 @@ public class UtxosChart extends BarChart<String, Number> {
|
|||
}
|
||||
|
||||
public void select(Entry entry) {
|
||||
Node selectedBar = lookup(".chart-bar.selected");
|
||||
if(selectedBar != null) {
|
||||
selectedBar.getStyleClass().remove("selected");
|
||||
}
|
||||
|
||||
for(int i = 0; i < utxoSeries.getData().size(); i++) {
|
||||
XYChart.Data<String, Number> data = utxoSeries.getData().get(i);
|
||||
Node bar = lookup(".data" + i);
|
||||
|
@ -71,8 +76,6 @@ public class UtxosChart extends BarChart<String, Number> {
|
|||
if(data.getExtraValue() != null && data.getExtraValue().equals(entry)) {
|
||||
bar.getStyleClass().add("selected");
|
||||
this.selectedEntry = entry;
|
||||
} else {
|
||||
bar.getStyleClass().remove("selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,16 @@ package com.sparrowwallet.sparrow.event;
|
|||
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ConnectionEvent {
|
||||
public class ConnectionEvent extends FeeRatesUpdatedEvent {
|
||||
private final List<String> serverVersion;
|
||||
private final String serverBanner;
|
||||
private final int blockHeight;
|
||||
private final BlockHeader blockHeader;
|
||||
|
||||
public ConnectionEvent(List<String> serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader) {
|
||||
public ConnectionEvent(List<String> serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader, Map<Integer, Double> targetBlockFeeRates) {
|
||||
super(targetBlockFeeRates);
|
||||
this.serverVersion = serverVersion;
|
||||
this.serverBanner = serverBanner;
|
||||
this.blockHeight = blockHeight;
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class FeeRatesUpdatedEvent {
|
||||
private final Map<Integer, Double> targetBlockFeeRates;
|
||||
|
||||
public FeeRatesUpdatedEvent(Map<Integer, Double> targetBlockFeeRates) {
|
||||
this.targetBlockFeeRates = targetBlockFeeRates;
|
||||
}
|
||||
|
||||
public Map<Integer, Double> getTargetBlockFeeRates() {
|
||||
return targetBlockFeeRates;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,9 @@ import com.sparrowwallet.drongo.wallet.*;
|
|||
import com.sparrowwallet.sparrow.AppController;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
||||
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||
import com.sparrowwallet.sparrow.event.NewBlockEvent;
|
||||
import com.sparrowwallet.sparrow.wallet.SendController;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Service;
|
||||
|
@ -505,6 +507,23 @@ public class ElectrumServer {
|
|||
return transactionMap;
|
||||
}
|
||||
|
||||
public Map<Integer, Double> getFeeEstimates(List<Integer> targetBlocks) throws ServerException {
|
||||
JsonRpcClient client = new JsonRpcClient(getTransport());
|
||||
BatchRequestBuilder<Integer, Double> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(Double.class);
|
||||
for(Integer targetBlock : targetBlocks) {
|
||||
batchRequest.add(targetBlock, "blockchain.estimatefee", targetBlock);
|
||||
}
|
||||
|
||||
Map<Integer, Double> targetBlocksFeeRatesBtcKb = batchRequest.execute();
|
||||
|
||||
Map<Integer, Double> targetBlocksFeeRatesSats = new TreeMap<>();
|
||||
for(Integer target : targetBlocksFeeRatesBtcKb.keySet()) {
|
||||
targetBlocksFeeRatesSats.put(target, targetBlocksFeeRatesBtcKb.get(target) * Transaction.SATOSHIS_PER_BITCOIN / 1024);
|
||||
}
|
||||
|
||||
return targetBlocksFeeRatesSats;
|
||||
}
|
||||
|
||||
private String getScriptHash(Wallet wallet, WalletNode node) {
|
||||
byte[] hash = Sha256Hash.hash(wallet.getOutputScript(node).getProgram());
|
||||
byte[] reversed = Utils.reverseBytes(hash);
|
||||
|
@ -802,15 +821,18 @@ public class ElectrumServer {
|
|||
}
|
||||
}
|
||||
|
||||
public static class ConnectionService extends ScheduledService<ConnectionEvent> implements Thread.UncaughtExceptionHandler {
|
||||
public static class ConnectionService extends ScheduledService<FeeRatesUpdatedEvent> implements Thread.UncaughtExceptionHandler {
|
||||
private static final int FEE_RATES_PERIOD = 5 * 60 * 1000;
|
||||
|
||||
private boolean firstCall = true;
|
||||
private Thread reader;
|
||||
private Throwable lastReaderException;
|
||||
private long feeRatesRetrievedAt;
|
||||
|
||||
@Override
|
||||
protected Task<ConnectionEvent> createTask() {
|
||||
protected Task<FeeRatesUpdatedEvent> createTask() {
|
||||
return new Task<>() {
|
||||
protected ConnectionEvent call() throws ServerException {
|
||||
protected FeeRatesUpdatedEvent call() throws ServerException {
|
||||
ElectrumServer electrumServer = new ElectrumServer();
|
||||
if(firstCall) {
|
||||
electrumServer.connect();
|
||||
|
@ -826,10 +848,20 @@ public class ElectrumServer {
|
|||
BlockHeaderTip tip = electrumServer.subscribeBlockHeaders();
|
||||
String banner = electrumServer.getServerBanner();
|
||||
|
||||
return new ConnectionEvent(serverVersion, banner, tip.height, tip.getBlockHeader());
|
||||
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE);
|
||||
feeRatesRetrievedAt = System.currentTimeMillis();
|
||||
|
||||
return new ConnectionEvent(serverVersion, banner, tip.height, tip.getBlockHeader(), blockTargetFeeRates);
|
||||
} else {
|
||||
if(reader.isAlive()) {
|
||||
electrumServer.ping();
|
||||
|
||||
long elapsed = System.currentTimeMillis() - feeRatesRetrievedAt;
|
||||
if(elapsed > FEE_RATES_PERIOD) {
|
||||
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE);
|
||||
feeRatesRetrievedAt = System.currentTimeMillis();
|
||||
return new FeeRatesUpdatedEvent(blockTargetFeeRates);
|
||||
}
|
||||
} else {
|
||||
firstCall = true;
|
||||
throw new ServerException("Connection to server failed", lastReaderException);
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package com.sparrowwallet.sparrow.wallet;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.sparrow.AppController;
|
||||
import com.sparrowwallet.sparrow.BitcoinUnit;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||
import com.sparrowwallet.sparrow.control.CopyableTextField;
|
||||
import com.sparrowwallet.sparrow.control.FeeRatesChart;
|
||||
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.util.StringConverter;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class SendController extends WalletFormController implements Initializable {
|
||||
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
|
||||
|
||||
@FXML
|
||||
private CopyableTextField address;
|
||||
|
||||
@FXML
|
||||
private TextField label;
|
||||
|
||||
@FXML
|
||||
private TextField amount;
|
||||
|
||||
@FXML
|
||||
private ComboBox<BitcoinUnit> amountUnit;
|
||||
|
||||
@FXML
|
||||
private Slider targetBlocks;
|
||||
|
||||
@FXML
|
||||
private CopyableLabel feeRate;
|
||||
|
||||
@FXML
|
||||
private TextField fee;
|
||||
|
||||
@FXML
|
||||
private ComboBox<BitcoinUnit> feeAmountUnit;
|
||||
|
||||
@FXML
|
||||
private FeeRatesChart feeRatesChart;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
EventManager.get().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeView() {
|
||||
ValidationSupport validationSupport = new ValidationSupport();
|
||||
validationSupport.registerValidator(address, Validator.combine(
|
||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !isValidAddress())
|
||||
));
|
||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||
|
||||
amountUnit.getSelectionModel().select(0);
|
||||
|
||||
targetBlocks.setMin(0);
|
||||
targetBlocks.setMax(TARGET_BLOCKS_RANGE.size() - 1);
|
||||
targetBlocks.setMajorTickUnit(1);
|
||||
targetBlocks.setMinorTickCount(0);
|
||||
targetBlocks.setLabelFormatter(new StringConverter<Double>() {
|
||||
@Override
|
||||
public String toString(Double object) {
|
||||
return Integer.toString(TARGET_BLOCKS_RANGE.get(object.intValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromString(String string) {
|
||||
return (double)TARGET_BLOCKS_RANGE.indexOf(Integer.valueOf(string));
|
||||
}
|
||||
});
|
||||
targetBlocks.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||
Integer target = getTargetBlocks();
|
||||
|
||||
if(targetBlocksFeeRates != null) {
|
||||
setFeeRate(targetBlocksFeeRates.get(target));
|
||||
feeRatesChart.select(target);
|
||||
} else {
|
||||
feeRate.setText("Unknown");
|
||||
}
|
||||
|
||||
Tooltip tooltip = new Tooltip("Target confirmation within " + target + " blocks");
|
||||
targetBlocks.setTooltip(tooltip);
|
||||
|
||||
//TODO: Set fee based on tx size
|
||||
});
|
||||
|
||||
feeAmountUnit.getSelectionModel().select(1);
|
||||
|
||||
feeRatesChart.initialize();
|
||||
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||
if(targetBlocksFeeRates != null) {
|
||||
feeRatesChart.update(targetBlocksFeeRates);
|
||||
} else {
|
||||
feeRate.setText("Unknown");
|
||||
}
|
||||
|
||||
setTargetBlocks(5);
|
||||
}
|
||||
|
||||
private boolean isValidAddress() {
|
||||
try {
|
||||
getAddress();
|
||||
} catch (InvalidAddressException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Address getAddress() throws InvalidAddressException {
|
||||
return Address.fromString(address.getText());
|
||||
}
|
||||
|
||||
private Integer getTargetBlocks() {
|
||||
int index = (int)targetBlocks.getValue();
|
||||
return TARGET_BLOCKS_RANGE.get(index);
|
||||
}
|
||||
|
||||
private void setTargetBlocks(Integer target) {
|
||||
int index = TARGET_BLOCKS_RANGE.indexOf(target);
|
||||
targetBlocks.setValue(index);
|
||||
feeRatesChart.select(target);
|
||||
}
|
||||
|
||||
private Map<Integer, Double> getTargetBlocksFeeRates() {
|
||||
return AppController.getTargetBlockFeeRates();
|
||||
}
|
||||
|
||||
private void setFeeRate(Double feeRateAmt) {
|
||||
feeRate.setText(String.format("%.2f", feeRateAmt) + " sats/vByte");
|
||||
}
|
||||
|
||||
public void setMaxInput(ActionEvent event) {
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
|
||||
feeRatesChart.update(event.getTargetBlockFeeRates());
|
||||
feeRatesChart.select(getTargetBlocks());
|
||||
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
.address-text-field {
|
||||
-fx-font-family: Courier;
|
||||
.form .fieldset:horizontal .field {
|
||||
-fx-pref-height: 40px;
|
||||
}
|
||||
|
||||
.receive-form .form .fieldset:horizontal .label-container {
|
||||
-fx-pref-width: 90px;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
|
@ -7,11 +11,6 @@
|
|||
-fx-padding: 20;
|
||||
}
|
||||
|
||||
.receive-form .form .fieldset:horizontal .label-container {
|
||||
-fx-pref-width: 90px;
|
||||
-fx-pref-height: 25px;
|
||||
}
|
||||
|
||||
#lastUsedField .input-container {
|
||||
-fx-alignment: center-left;
|
||||
}
|
33
src/main/resources/com/sparrowwallet/sparrow/wallet/send.css
Normal file
33
src/main/resources/com/sparrowwallet/sparrow/wallet/send.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.form .fieldset:horizontal .field {
|
||||
-fx-pref-height: 40px;
|
||||
}
|
||||
|
||||
.send-form .form .fieldset:horizontal .label-container {
|
||||
-fx-pref-width: 90px;
|
||||
}
|
||||
|
||||
.amount-field {
|
||||
-fx-max-width: 150px;
|
||||
}
|
||||
|
||||
#feeRatesChart {
|
||||
-fx-max-width: 350px;
|
||||
-fx-max-height: 130px;
|
||||
}
|
||||
|
||||
.default-color0.chart-series-line {
|
||||
-fx-stroke: rgba(105, 108, 119, 0.6);
|
||||
-fx-stroke-width: 1px;
|
||||
}
|
||||
|
||||
.chart-line-symbol {
|
||||
-fx-background-color: rgba(30, 136, 207, 0);
|
||||
}
|
||||
|
||||
.chart-line-symbol.selected {
|
||||
-fx-background-color: rgba(30, 136, 207, 0.6);
|
||||
}
|
||||
|
||||
#feeRateField .input-container {
|
||||
-fx-alignment: center-left;
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?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.Fieldset?>
|
||||
<?import tornadofx.control.Form?>
|
||||
<?import tornadofx.control.Field?>
|
||||
<?import com.sparrowwallet.sparrow.control.CopyableTextField?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import com.sparrowwallet.sparrow.control.CopyableLabel?>
|
||||
<?import javafx.collections.FXCollections?>
|
||||
<?import com.sparrowwallet.drongo.policy.PolicyType?>
|
||||
<?import com.sparrowwallet.sparrow.BitcoinUnit?>
|
||||
<?import com.sparrowwallet.sparrow.control.FeeRatesChart?>
|
||||
<?import javafx.scene.chart.CategoryAxis?>
|
||||
<?import javafx.scene.chart.NumberAxis?>
|
||||
<BorderPane stylesheets="@send.css, @wallet.css, @../script.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.SendController">
|
||||
<center>
|
||||
<GridPane styleClass="send-form" hgap="10.0" vgap="10.0">
|
||||
<padding>
|
||||
<Insets left="25.0" right="25.0" top="25.0" />
|
||||
</padding>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="50" />
|
||||
<ColumnConstraints percentWidth="30" />
|
||||
<ColumnConstraints percentWidth="20" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints />
|
||||
</rowConstraints>
|
||||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2">
|
||||
<Fieldset inputGrow="SOMETIMES" text="Send">
|
||||
<Field text="Pay to:">
|
||||
<CopyableTextField fx:id="address" styleClass="address-text-field"/>
|
||||
</Field>
|
||||
<Field text="Label:">
|
||||
<TextField fx:id="label" />
|
||||
</Field>
|
||||
<Field text="Amount:">
|
||||
<TextField fx:id="amount" styleClass="amount-field" />
|
||||
<ComboBox fx:id="amountUnit">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<BitcoinUnit fx:constant="BTC" />
|
||||
<BitcoinUnit fx:constant="SATOSHIS" />
|
||||
</FXCollections>
|
||||
</items>
|
||||
</ComboBox>
|
||||
<Region style="-fx-pref-width: 20" />
|
||||
<Button fx:id="maxButton" text="Max" onAction="#setMaxInput" />
|
||||
</Field>
|
||||
</Fieldset>
|
||||
</Form>
|
||||
<Form GridPane.columnIndex="0" GridPane.rowIndex="1">
|
||||
<Fieldset inputGrow="SOMETIMES" text="Fee">
|
||||
<Field text="Block Target:">
|
||||
<Slider fx:id="targetBlocks" snapToTicks="true" showTickLabels="true" showTickMarks="true" />
|
||||
</Field>
|
||||
<Field fx:id="feeRateField" text="Rate:">
|
||||
<CopyableLabel fx:id="feeRate" />
|
||||
</Field>
|
||||
<Field text="Fee:">
|
||||
<TextField fx:id="fee" styleClass="amount-field"/>
|
||||
<ComboBox fx:id="feeAmountUnit">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<BitcoinUnit fx:constant="BTC" />
|
||||
<BitcoinUnit fx:constant="SATOSHIS" />
|
||||
</FXCollections>
|
||||
</items>
|
||||
</ComboBox>
|
||||
</Field>
|
||||
</Fieldset>
|
||||
</Form>
|
||||
<AnchorPane GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.columnSpan="2">
|
||||
<FeeRatesChart fx:id="feeRatesChart" legendVisible="false" AnchorPane.topAnchor="10" AnchorPane.leftAnchor="20" animated="false">
|
||||
<xAxis>
|
||||
<CategoryAxis side="BOTTOM" />
|
||||
</xAxis>
|
||||
<yAxis>
|
||||
<NumberAxis side="LEFT" />
|
||||
</yAxis>
|
||||
</FeeRatesChart>
|
||||
</AnchorPane>
|
||||
</GridPane>
|
||||
</center>
|
||||
</BorderPane>
|
|
@ -108,4 +108,8 @@
|
|||
|
||||
.unused-check {
|
||||
-fx-text-fill: #50a14f;
|
||||
}
|
||||
|
||||
.address-text-field {
|
||||
-fx-font-family: Courier;
|
||||
}
|
Loading…
Reference in a new issue