refactor and cleanup importing

This commit is contained in:
Craig Raw 2020-05-13 18:15:46 +02:00
parent d516eaa9d6
commit 02258dea8d
13 changed files with 77 additions and 221 deletions

View file

@ -1,32 +0,0 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.io.Device;
import javafx.collections.ObservableList;
import javafx.scene.control.Accordion;
public class DeviceAccordion extends Accordion {
private ObservableList<Device> devices;
private DeviceOperation deviceOperation = DeviceOperation.IMPORT;
public void setDevices(Wallet wallet, ObservableList<Device> devices) {
this.devices = devices;
for(Device device : devices) {
DevicePane devicePane = new DevicePane(this, wallet, device);
this.getPanes().add(devicePane);
}
}
public DeviceOperation getDeviceOperation() {
return deviceOperation;
}
public void setDeviceOperation(DeviceOperation deviceOperation) {
this.deviceOperation = deviceOperation;
}
public enum DeviceOperation {
IMPORT;
}
}

View file

@ -12,14 +12,11 @@ import com.sparrowwallet.sparrow.io.Device;
import com.sparrowwallet.sparrow.io.Hwi;
import com.sparrowwallet.drongo.wallet.WalletModel;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import org.controlsfx.control.textfield.CustomPasswordField;
import org.controlsfx.control.textfield.CustomTextField;
@ -32,115 +29,28 @@ import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
import java.util.List;
public class DevicePane extends TitledPane {
private final DeviceAccordion deviceAccordion;
public class DevicePane extends TitledDescriptionPane {
private final DeviceOperation deviceOperation;
private final Wallet wallet;
private final Device device;
private Label mainLabel;
private Label statusLabel;
private Hyperlink showHideLink;
private CustomPasswordField pinField;
private CustomTextField passphraseField;
private Button unlockButton;
private Button enterPinButton;
private Button setPassphraseButton;
private SplitMenuButton importButton;
private final SimpleStringProperty status = new SimpleStringProperty("");
private final SimpleStringProperty passphrase = new SimpleStringProperty("");
public DevicePane(DeviceAccordion deviceAccordion, Wallet wallet, Device device) {
super();
this.deviceAccordion = deviceAccordion;
public DevicePane(DeviceOperation deviceOperation, Wallet wallet, Device device) {
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
this.deviceOperation = deviceOperation;
this.wallet = wallet;
this.device = device;
setPadding(Insets.EMPTY);
setGraphic(getTitle());
getStyleClass().add("titled-description-pane");
setDefaultStatus();
removeArrow();
}
private void removeArrow() {
Platform.runLater(() -> {
Node arrow = this.lookup(".arrow");
if (arrow != null) {
arrow.setVisible(false);
arrow.setManaged(false);
} else {
removeArrow();
}
});
}
private void setDefaultStatus() {
setStatus(device.getNeedsPinSent() ? "Locked" : device.getNeedsPassphraseSent() ? "Passphrase Required" : "Unlocked");
}
private Node getTitle() {
HBox listItem = new HBox();
listItem.setPadding(new Insets(10, 20, 10, 10));
listItem.setSpacing(10);
HBox imageBox = new HBox();
imageBox.setMinWidth(50);
imageBox.setMinHeight(50);
listItem.getChildren().add(imageBox);
Image image = new Image("image/" + device.getType() + ".png", 50, 50, true, true);
if (!image.isError()) {
ImageView imageView = new ImageView();
imageView.setImage(image);
imageBox.getChildren().add(imageView);
}
VBox labelsBox = new VBox();
labelsBox.setSpacing(5);
labelsBox.setAlignment(Pos.CENTER_LEFT);
this.mainLabel = new Label();
mainLabel.setText(device.getModel().toDisplayString());
mainLabel.getStyleClass().add("main-label");
labelsBox.getChildren().add(mainLabel);
HBox statusBox = new HBox();
statusBox.setSpacing(7);
this.statusLabel = new Label();
statusLabel.getStyleClass().add("status-label");
statusLabel.textProperty().bind(status);
statusBox.getChildren().add(statusLabel);
showHideLink = new Hyperlink("Show details...");
showHideLink.managedProperty().bind(showHideLink.visibleProperty());
showHideLink.setVisible(false);
showHideLink.setOnAction(event -> {
if(this.isExpanded()) {
setExpanded(false);
} else {
setExpanded(true);
}
});
this.expandedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue) {
showHideLink.setText(showHideLink.getText().replace("Show", "Hide"));
} else {
showHideLink.setText(showHideLink.getText().replace("Hide", "Show"));
}
});
statusBox.getChildren().add(showHideLink);
labelsBox.getChildren().add(statusBox);
listItem.getChildren().add(labelsBox);
HBox.setHgrow(labelsBox, Priority.ALWAYS);
HBox buttonBox = new HBox();
buttonBox.setAlignment(Pos.CENTER_RIGHT);
createUnlockButton();
createSetPassphraseButton();
createImportButton();
@ -152,15 +62,17 @@ public class DevicePane extends TitledPane {
showOperationButton();
}
buttonBox.getChildren().addAll(unlockButton, setPassphraseButton, importButton);
listItem.getChildren().add(buttonBox);
buttonBox.getChildren().addAll(setPassphraseButton, importButton);
}
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
//Hack to force listItem to expand to full available width less border
listItem.setPrefWidth(newValue.getWidth() - 2);
});
@Override
protected Control createButton() {
createUnlockButton();
return unlockButton;
}
return listItem;
private void setDefaultStatus() {
setDescription(device.getNeedsPinSent() ? "Locked" : device.getNeedsPassphraseSent() ? "Passphrase Required" : "Unlocked");
}
private void createUnlockButton() {
@ -256,7 +168,7 @@ public class DevicePane extends TitledPane {
}
private Node getPassphraseEntry() {
passphraseField = (CustomTextField)TextFields.createClearableTextField();
CustomTextField passphraseField = (CustomTextField)TextFields.createClearableTextField();
passphrase.bind(passphraseField.textProperty());
HBox.setHgrow(passphraseField, Priority.ALWAYS);
@ -284,12 +196,12 @@ public class DevicePane extends TitledPane {
setContent(getPinEntry());
setExpanded(true);
} else {
setErrorStatus("Could not request PIN");
setError("Could not request PIN", null);
unlockButton.setDisable(false);
}
});
promptPinService.setOnFailed(workerStateEvent -> {
setErrorStatus(promptPinService.getException().getMessage());
setError(promptPinService.getException().getMessage(), null);
unlockButton.setDisable(false);
});
promptPinService.start();
@ -314,7 +226,7 @@ public class DevicePane extends TitledPane {
showOperationButton();
}
} else {
setErrorStatus("Incorrect PIN");
setError("Incorrect PIN", null);
enterPinButton.setDisable(false);
if(pinField != null) {
pinField.setText("");
@ -322,7 +234,7 @@ public class DevicePane extends TitledPane {
}
});
sendPinService.setOnFailed(workerStateEvent -> {
setErrorStatus(sendPinService.getException().getMessage());
setError(sendPinService.getException().getMessage(), null);
enterPinButton.setDisable(false);
});
sendPinService.start();
@ -344,13 +256,13 @@ public class DevicePane extends TitledPane {
setDefaultStatus();
showOperationButton();
} else {
setErrorStatus("Passphrase send failed");
setError("Passphrase send failed", null);
setPassphraseButton.setDisable(false);
setPassphraseButton.setVisible(true);
}
});
enumerateService.setOnFailed(workerStateEvent -> {
setErrorStatus(enumerateService.getException().getMessage());
setError(enumerateService.getException().getMessage(), null);
setPassphraseButton.setDisable(false);
setPassphraseButton.setVisible(true);
});
@ -371,7 +283,7 @@ public class DevicePane extends TitledPane {
importXpub(derivation);
});
enumerateService.setOnFailed(workerStateEvent -> {
setErrorStatus(enumerateService.getException().getMessage());
setError(enumerateService.getException().getMessage(), null);
importButton.setDisable(false);
});
enumerateService.start();
@ -397,24 +309,14 @@ public class DevicePane extends TitledPane {
EventManager.get().post(new KeystoreImportEvent(keystore));
});
getXpubService.setOnFailed(workerStateEvent -> {
setErrorStatus(getXpubService.getException().getMessage());
setError(getXpubService.getException().getMessage(), null);
importButton.setDisable(false);
});
getXpubService.start();
}
private void setStatus(String statusMessage) {
statusLabel.getStyleClass().remove("status-error");
status.setValue(statusMessage);
}
private void setErrorStatus(String statusMessage) {
statusLabel.getStyleClass().add("status-error");
status.setValue(statusMessage);
}
private void showOperationButton() {
if(deviceAccordion.getDeviceOperation().equals(DeviceAccordion.DeviceOperation.IMPORT)) {
if(deviceOperation.equals(DeviceOperation.IMPORT)) {
importButton.setVisible(true);
showHideLink.setText("Show derivation...");
showHideLink.setVisible(true);
@ -459,4 +361,8 @@ public class DevicePane extends TitledPane {
return contentBox;
}
public enum DeviceOperation {
IMPORT;
}
}

View file

@ -1,32 +0,0 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.io.KeystoreFileImport;
import com.sparrowwallet.sparrow.io.KeystoreImport;
import com.sparrowwallet.sparrow.io.KeystoreMnemonicImport;
import javafx.collections.ObservableList;
import javafx.scene.control.Accordion;
import java.util.List;
public class KeystoreImportAccordion extends Accordion {
private List<KeystoreImport> importers;
public void setKeystoreImporters(Wallet wallet, ObservableList<KeystoreImport> importers) {
this.importers = importers;
for(KeystoreImport importer : importers) {
TitledDescriptionPane importPane = null;
if(importer instanceof KeystoreFileImport) {
importPane = new FileKeystoreImportPane(wallet, (KeystoreFileImport)importer);
} else if(importer instanceof KeystoreMnemonicImport) {
importPane = new MnemonicKeystoreImportPane(wallet, (KeystoreMnemonicImport)importer);
} else {
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
}
this.getPanes().add(importPane);
}
}
}

View file

@ -106,8 +106,10 @@ public class TitledDescriptionPane extends TitledPane {
descriptionLabel.getStyleClass().remove("description-label");
descriptionLabel.getStyleClass().add("description-error");
descriptionLabel.setText(title);
setContent(getContentBox(detail));
setExpanded(true);
if(detail != null && !detail.isEmpty()) {
setContent(getContentBox(detail));
setExpanded(true);
}
}
protected Node getContentBox(String message) {

View file

@ -26,8 +26,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
stackPane.getChildren().add(anchorPane);
ScrollPane scrollPane = new ScrollPane();
scrollPane.setPrefWidth(500);
scrollPane.setPrefHeight(500);
scrollPane.setPrefHeight(280);
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
anchorPane.getChildren().add(scrollPane);
scrollPane.setFitToWidth(true);
@ -44,8 +43,8 @@ public class WalletImportDialog extends Dialog<Wallet> {
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
dialogPane.getButtonTypes().addAll(cancelButtonType);
dialogPane.setPrefWidth(650);
dialogPane.setPrefHeight(600);
dialogPane.setPrefWidth(500);
dialogPane.setPrefHeight(360);
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallet : null);
}

View file

@ -46,7 +46,7 @@ public class ColdcardSinglesig implements KeystoreFileImport {
Map<String, JsonElement> map = gson.fromJson(new InputStreamReader(inputStream), stringStringMap);
if (map.get("xfp") == null) {
throw new ImportException("This is not a valid Coldcard wallet export");
throw new ImportException("File was not a valid Coldcard wallet export");
}
String masterFingerprint = map.get("xfp").getAsString();

View file

@ -43,7 +43,7 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
Wallet wallet = importWallet(inputStream, password);
if(!wallet.getPolicyType().equals(PolicyType.SINGLE) || wallet.getKeystores().size() != 1) {
throw new ImportException("Multisig wallet detected - import it using File > Import > Electrum");
throw new ImportException("Multisig wallet detected - import it using File > Import Wallet");
}
if(!wallet.getScriptType().equals(scriptType)) {
@ -71,7 +71,7 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
ElectrumJsonWallet ew = new ElectrumJsonWallet();
if(map.get("wallet_type") == null) {
throw new ImportException("This is not a valid Electrum wallet");
throw new ImportException("File was not a valid Electrum wallet");
}
ew.wallet_type = map.get("wallet_type").getAsString();

View file

@ -1,28 +1,29 @@
package com.sparrowwallet.sparrow.keystoreimport;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.sparrow.control.KeystoreImportAccordion;
import com.sparrowwallet.sparrow.io.ColdcardMultisig;
import com.sparrowwallet.sparrow.io.ColdcardSinglesig;
import com.sparrowwallet.sparrow.io.KeystoreImport;
import javafx.collections.FXCollections;
import com.sparrowwallet.sparrow.control.FileKeystoreImportPane;
import com.sparrowwallet.sparrow.io.*;
import javafx.fxml.FXML;
import javafx.scene.control.Accordion;
import java.util.Collections;
import java.util.List;
public class HwAirgappedController extends KeystoreImportDetailController {
@FXML
private KeystoreImportAccordion importAccordion;
private Accordion importAccordion;
public void initializeView() {
List<KeystoreImport> importers = Collections.emptyList();
List<KeystoreFileImport> importers = Collections.emptyList();
if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE)) {
importers = List.of(new ColdcardSinglesig());
} else if(getMasterController().getWallet().getPolicyType().equals(PolicyType.MULTI)) {
importers = List.of(new ColdcardMultisig());
}
importAccordion.setKeystoreImporters(getMasterController().getWallet(), FXCollections.observableList(importers));
for(KeystoreImport importer : importers) {
FileKeystoreImportPane importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer);;
importAccordion.getPanes().add(importPane);
}
}
}

View file

@ -1,18 +1,21 @@
package com.sparrowwallet.sparrow.keystoreimport;
import com.sparrowwallet.sparrow.control.DeviceAccordion;
import com.sparrowwallet.sparrow.control.DevicePane;
import com.sparrowwallet.sparrow.io.Device;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.Accordion;
import java.util.List;
public class HwUsbDevicesController extends KeystoreImportDetailController {
@FXML
private DeviceAccordion deviceAccordion;
private Accordion deviceAccordion;
public void initializeView(List<Device> devices) {
deviceAccordion.setDeviceOperation(DeviceAccordion.DeviceOperation.IMPORT);
deviceAccordion.setDevices(getMasterController().getWallet(), FXCollections.observableList(devices));
for(Device device : devices) {
DevicePane devicePane = new DevicePane(DevicePane.DeviceOperation.IMPORT, getMasterController().getWallet(), device);
deviceAccordion.getPanes().add(devicePane);
}
}
}

View file

@ -1,20 +1,33 @@
package com.sparrowwallet.sparrow.keystoreimport;
import com.sparrowwallet.sparrow.control.KeystoreImportAccordion;
import com.sparrowwallet.sparrow.io.Bip39;
import com.sparrowwallet.sparrow.io.Electrum;
import com.sparrowwallet.sparrow.io.KeystoreImport;
import javafx.collections.FXCollections;
import com.sparrowwallet.sparrow.control.FileKeystoreImportPane;
import com.sparrowwallet.sparrow.control.MnemonicKeystoreImportPane;
import com.sparrowwallet.sparrow.control.TitledDescriptionPane;
import com.sparrowwallet.sparrow.io.*;
import javafx.fxml.FXML;
import javafx.scene.control.Accordion;
import java.util.List;
public class SwController extends KeystoreImportDetailController {
@FXML
private KeystoreImportAccordion importAccordion;
private Accordion importAccordion;
public void initializeView() {
List<KeystoreImport> importers = List.of(new Bip39(), new Electrum());
importAccordion.setKeystoreImporters(getMasterController().getWallet(), FXCollections.observableList(importers));
for(KeystoreImport importer : importers) {
TitledDescriptionPane importPane = null;
if(importer instanceof KeystoreFileImport) {
importPane = new FileKeystoreImportPane(getMasterController().getWallet(), (KeystoreFileImport)importer);
} else if(importer instanceof KeystoreMnemonicImport) {
importPane = new MnemonicKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicImport)importer);
} else {
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
}
importAccordion.getPanes().add(importPane);
}
}
}

View file

@ -6,10 +6,8 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.sparrowwallet.sparrow.control.KeystoreImportAccordion?>
<AnchorPane stylesheets="@keystoreimport.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.keystoreimport.HwAirgappedController">
<ScrollPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" fitToWidth="true">
<KeystoreImportAccordion fx:id="importAccordion" />
<Accordion fx:id="importAccordion" />
</ScrollPane>
</AnchorPane>

View file

@ -5,10 +5,9 @@
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.sparrowwallet.sparrow.control.DeviceAccordion?>
<AnchorPane stylesheets="@keystoreimport.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.keystoreimport.HwUsbDevicesController">
<ScrollPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" fitToWidth="true">
<DeviceAccordion fx:id="deviceAccordion" />
<Accordion fx:id="deviceAccordion" />
</ScrollPane>
</AnchorPane>

View file

@ -5,10 +5,9 @@
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.sparrowwallet.sparrow.control.KeystoreImportAccordion?>
<AnchorPane stylesheets="@keystoreimport.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.keystoreimport.SwController">
<ScrollPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" fitToWidth="true">
<KeystoreImportAccordion fx:id="importAccordion" />
<Accordion fx:id="importAccordion" />
</ScrollPane>
</AnchorPane>