mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
signatures progress bar and sign buttons
This commit is contained in:
parent
6ef333ae2a
commit
fca55779bf
8 changed files with 333 additions and 7 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit dcd4218ba14ecea9113925b80af02fdc3287079a
|
||||
Subproject commit f6dcdb6d26c3b40ae1c0f7502b3e526aa8959564
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue