Craig Raw
5 years ago
13 changed files with 462 additions and 17 deletions
@ -1 +1 @@ |
|||
Subproject commit 24cde9d073da636fbc2150b7abbd50b48342e040 |
|||
Subproject commit c4dd1cb9dd40a7a16829a00f45acbd55f63d9895 |
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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())); |
|||
} |
|||
} |
@ -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> |
Loading…
Reference in new issue