signatures progress bar and sign buttons

This commit is contained in:
Craig Raw 2020-07-20 17:05:08 +02:00
parent 6ef333ae2a
commit fca55779bf
8 changed files with 333 additions and 7 deletions

2
drongo

@ -1 +1 @@
Subproject commit dcd4218ba14ecea9113925b80af02fdc3287079a
Subproject commit f6dcdb6d26c3b40ae1c0f7502b3e526aa8959564

View file

@ -0,0 +1,150 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.wallet.Keystore;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
import org.controlsfx.control.SegmentedBar;
public class SignaturesProgressBar extends SegmentedBar<SignaturesProgressBar.SignatureProgressSegment> {
public SignaturesProgressBar() {
setOrientation(Orientation.HORIZONTAL);
setSegmentViewFactory(SignatureProgressSegmentView::new);
setInfoNodeFactory(segment -> segment.getKeystore() == null ? null : new SignatureProgressSegmentLabel(segment.getKeystore().getLabel()));
}
public void initialize(ObservableList<Keystore> signedKeystores, int threshold) {
getStyleClass().add("signatures-progress-bar");
getSegments().clear();
int numSegments = Math.max(threshold, signedKeystores.size());
double segmentSize = 100d / numSegments;
for(int i = 0; i < numSegments; i++) {
if(i < signedKeystores.size()) {
getSegments().add(new SignatureProgressSegment(segmentSize, i, signedKeystores.get(i)));
} else {
getSegments().add(new SignatureProgressSegment(segmentSize, i, null));
}
}
signedKeystores.addListener((ListChangeListener<Keystore>) c -> {
int numSegments1 = Math.max(threshold, c.getList().size());
double newSegmentSize = 100d / numSegments1;
for(int i = 0; i < numSegments1; i++) {
SignatureProgressSegment segment = null;
if(i < getSegments().size()) {
segment = getSegments().get(i);
}
Keystore signedKeystore = null;
if(i < signedKeystores.size()) {
signedKeystore = signedKeystores.get(i);
}
if(segment != null) {
//Animate new signature if changed
segment.setKeystore(signedKeystore);
} else {
//Add extra (unnecessary) signature
for(SignaturesProgressBar.SignatureProgressSegment existingSegment : getSegments()) {
existingSegment.setValue(newSegmentSize);
}
SignaturesProgressBar.SignatureProgressSegment newSegment = new SignatureProgressSegment(newSegmentSize, i, null);
getSegments().add(newSegment);
newSegment.setKeystore(signedKeystore);
}
}
});
}
public static class SignatureProgressSegment extends SegmentedBar.Segment {
private final SimpleObjectProperty<Keystore> keystoreProperty;
private final int index;
public SignatureProgressSegment(double value, int index, Keystore keystore) {
super(value);
this.index = index;
this.keystoreProperty = new SimpleObjectProperty<>(this, "keystore", null);
keystoreProperty.addListener((observable, oldValue, newValue) -> {
setText(newValue == null ? "No keystore" : newValue.getLabel());
});
setKeystore(keystore);
}
public int getIndex() {
return index;
}
public Keystore getKeystore() {
return keystoreProperty.get();
}
public SimpleObjectProperty<Keystore> keystoreProperty() {
return keystoreProperty;
}
public void setKeystore(Keystore keystore) {
keystoreProperty.set(keystore);
}
}
public static class SignatureProgressSegmentView extends StackPane {
private final ProgressBar progressBar;
private final Label label;
public SignatureProgressSegmentView(SignatureProgressSegment segment) {
getStyleClass().add("signature-progress-segment");
getStyleClass().add("segment" + segment.getIndex());
label = new Label();
label.textProperty().bind(segment.textProperty());
label.getStyleClass().add("signature-progress-segment-label");
StackPane.setAlignment(label, Pos.CENTER);
progressBar = new ProgressBar(segment.getKeystore() == null ? 0.0 : 1.0);
progressBar.setPrefWidth(Double.MAX_VALUE);
progressBar.setPrefHeight(30);
setPrefHeight(50);
getChildren().addAll(progressBar, label);
segment.keystoreProperty().addListener((observable, oldValue, newValue) -> {
if(oldValue == null && newValue != null) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(progressBar.progressProperty(), 0)),
new KeyFrame(Duration.millis(800), new KeyValue(progressBar.progressProperty(), 1))
);
timeline.setCycleCount(1);
timeline.play();
}
});
}
@Override
protected void layoutChildren() {
super.layoutChildren();
label.setVisible(label.prefWidth(-1) < getWidth() - getPadding().getLeft() - getPadding().getRight() - 8);
}
}
public static class SignatureProgressSegmentLabel extends Label {
public SignatureProgressSegmentLabel(String text) {
super(text);
setPadding(new Insets(10));
}
}
}

View file

@ -27,6 +27,7 @@ public class FontAwesome5 extends GlyphFont {
LOCK('\uf023'),
LOCK_OPEN('\uf3c1'),
PEN_FANCY('\uf5ac'),
QRCODE('\uf029'),
QUESTION_CIRCLE('\uf059'),
SD_CARD('\uf7c2'),
SEARCH('\uf002'),

View file

@ -4,13 +4,17 @@ import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.CoinLabel;
import com.sparrowwallet.sparrow.control.IdLabel;
import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.control.SignaturesProgressBar;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@ -18,6 +22,7 @@ import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import org.controlsfx.glyphfont.Glyph;
import tornadofx.control.DateTimePicker;
import tornadofx.control.Field;
import tornadofx.control.Fieldset;
@ -27,9 +32,7 @@ import tornadofx.control.Form;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.*;
import java.util.stream.Collectors;
public class HeadersController extends TransactionFormController implements Initializable {
@ -128,6 +131,12 @@ public class HeadersController extends TransactionFormController implements Init
@FXML
private Form signaturesForm;
@FXML
private SignaturesProgressBar signaturesProgressBar;
@FXML
private Button signButton;
@FXML
private Form broadcastForm;
@ -303,6 +312,17 @@ public class HeadersController extends TransactionFormController implements Init
psbtInput.setSigHash(newValue);
}
});
headersForm.signingWalletProperty().addListener((observable, oldValue, signingWallet) -> {
initializeSignButton(signingWallet);
Map<PSBTInput, List<Keystore>> signedKeystoresMap = signingWallet.getSignedKeystores(headersForm.getPsbt());
Optional<List<Keystore>> optSignedKeystores = signedKeystoresMap.values().stream().filter(list -> !list.isEmpty()).min(Comparator.comparingInt(List::size));
optSignedKeystores.ifPresent(keystores -> headersForm.getSignedKeystores().setAll(keystores));
int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired();
signaturesProgressBar.initialize(headersForm.getSignedKeystores(), threshold);
});
}
}
@ -370,6 +390,18 @@ public class HeadersController extends TransactionFormController implements Init
}
}
private void initializeSignButton(Wallet signingWallet) {
Optional<Keystore> softwareKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.SW_SEED)).findAny();
Optional<Keystore> usbKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB)).findAny();
if(softwareKeystore.isEmpty() && usbKeystore.isEmpty()) {
signButton.setDisable(true);
} else if(softwareKeystore.isEmpty()) {
Glyph usbGlyph = new Glyph(FontAwesome5Brands.FONT_NAME, FontAwesome5Brands.Glyph.USB);
usbGlyph.setFontSize(20);
signButton.setGraphic(usbGlyph);
}
}
private static class BlockHeightContextMenu extends ContextMenu {
public BlockHeightContextMenu(Sha256Hash blockHash) {
MenuItem copyBlockHash = new MenuItem("Copy Block Hash");
@ -404,6 +436,18 @@ public class HeadersController extends TransactionFormController implements Init
EventManager.get().post(new FinalizePSBTEvent(headersForm.getPsbt(), signingWallet.getValue()));
}
public void showPSBT(ActionEvent event) {
}
public void savePSBT(ActionEvent event) {
}
public void signPSBT(ActionEvent event) {
headersForm.getSignedKeystores().add(headersForm.getSigningWallet().getKeystores().get(0));
}
@Subscribe
public void transactionChanged(TransactionChangedEvent event) {
if(headersForm.getTransaction().equals(event.getTransaction())) {

View file

@ -4,7 +4,11 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.List;
import java.util.Map;
@ -21,7 +25,8 @@ public class TransactionData {
private int minOutputFetched;
private int maxOutputFetched;
private Wallet signingWallet;
private final SimpleObjectProperty<Wallet> signingWallet = new SimpleObjectProperty<>(this, "signingWallet", null);
private final ObservableList<Keystore> signedKeystores = FXCollections.observableArrayList();
public TransactionData(PSBT psbt) {
this.transaction = psbt.getTransaction();
@ -114,10 +119,18 @@ public class TransactionData {
}
public Wallet getSigningWallet() {
return signingWallet.get();
}
public SimpleObjectProperty<Wallet> signingWalletProperty() {
return signingWallet;
}
public void setSigningWallet(Wallet signingWallet) {
this.signingWallet = signingWallet;
public void setSigningWallet(Wallet wallet) {
this.signingWallet.set(wallet);
}
public ObservableList<Keystore> getSignedKeystores() {
return signedKeystores;
}
}

View file

@ -4,7 +4,10 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import java.io.IOException;
@ -58,10 +61,18 @@ public abstract class TransactionForm {
return txdata.getSigningWallet();
}
public SimpleObjectProperty<Wallet> signingWalletProperty() {
return txdata.signingWalletProperty();
}
public void setSigningWallet(Wallet signingWallet) {
txdata.setSigningWallet(signingWallet);
}
public ObservableList<Keystore> getSignedKeystores() {
return txdata.getSignedKeystores();
}
public boolean isEditable() {
if(getBlockTransaction() != null) {
return false;

View file

@ -37,3 +37,87 @@
-fx-text-fill: rgb(202, 18, 67);
}
.signatures-buttons {
-fx-padding: 10 20 10 20;
}
.signatures-buttons .button {
-fx-pref-height: 75px;
-fx-max-width: Infinity;
}
.signatures-progress-bar {
-fx-padding: 10 0 10 0;
}
.signatures-progress-bar {
-fx-padding: 10 0 10 0;
}
.signatures-progress-bar > .segment {
-fx-background-color: transparent;
}
.signature-progress-segment-label {
-fx-text-fill: white;
}
.signatures-progress-bar > .only-segment {
}
.signatures-progress-bar > .first-segment {
-fx-padding: 0 3 0 0;
}
.signatures-progress-bar > .middle-segment {
-fx-padding: 0 3 0 3;
}
.signatures-progress-bar > .last-segment {
-fx-padding: 0 0 0 3;
}
.segment0 .progress-bar {
-fx-accent: CHART_COLOR_3;
}
.segment1 .progress-bar {
-fx-accent: CHART_COLOR_4;
}
.segment2 .progress-bar {
-fx-accent: CHART_COLOR_7;
}
.segment3 .progress-bar {
-fx-accent: CHART_COLOR_5;
}
.segment4 .progress-bar {
-fx-accent: CHART_COLOR_6;
}
.segment5 .progress-bar {
-fx-accent: CHART_COLOR_2;
}
.segment6 .progress-bar {
-fx-accent: CHART_COLOR_1;
}
.segment7 .progress-bar {
-fx-accent: CHART_COLOR_8;
}
.segment8 .progress-bar {
-fx-accent: #3DA0E3;
}
.segment9 .progress-bar {
-fx-accent: #f9c74f;
}
.segment10 .progress-bar {
-fx-accent: #f94144;
}

View file

@ -21,6 +21,7 @@
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Hyperlink?>
<?import com.sparrowwallet.sparrow.control.SignaturesProgressBar?>
<GridPane hgap="10.0" vgap="10.0" styleClass="tx-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.HeadersController" stylesheets="@headers.css, @transaction.css, @../general.css">
<padding>
@ -165,6 +166,28 @@
<Form fx:id="signaturesForm" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="6">
<Fieldset text="Signatures" inputGrow="SOMETIMES">
<VBox>
<SignaturesProgressBar fx:id="signaturesProgressBar" />
</VBox>
<VBox>
<HBox styleClass="signatures-buttons" spacing="20">
<Button HBox.hgrow="ALWAYS" text="Show QR" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#showPSBT">
<graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="QRCODE" />
</graphic>
</Button>
<Button HBox.hgrow="ALWAYS" text="Save PSBT" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#savePSBT">
<graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="SD_CARD" />
</graphic>
</Button>
<Button fx:id="signButton" defaultButton="true" HBox.hgrow="ALWAYS" text="Sign" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#signPSBT">
<graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="PEN_FANCY" />
</graphic>
</Button>
</HBox>
</VBox>
</Fieldset>
</Form>