mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 00:27:00 +00:00
signatures progress bar and sign buttons
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user