From d516eaa9d6e656d82bac71fdb5f32face0267600 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 13 May 2020 17:16:52 +0200 Subject: [PATCH] refactor import accordion, add wallet import --- .../sparrowwallet/sparrow/AppController.java | 12 ++ .../sparrow/control/DevicePane.java | 2 +- .../sparrow/control/FileImportPane.java | 121 ++++++++++++++++++ .../control/FileKeystoreImportPane.java | 113 ++-------------- .../sparrow/control/FileWalletImportPane.java | 27 ++++ .../control/KeystoreImportAccordion.java | 6 +- .../control/MnemonicKeystoreImportPane.java | 23 ++-- ...rtPane.java => TitledDescriptionPane.java} | 73 +++++------ .../sparrow/control/WalletImportDialog.java | 59 +++++++++ .../sparrow/event/WalletImportEvent.java | 15 +++ .../sparrow/io/ColdcardMultisig.java | 6 +- .../sparrowwallet/sparrow/io/Electrum.java | 13 +- .../sparrowwallet/sparrow/io/FileImport.java | 7 + .../com/sparrowwallet/sparrow/io/Import.java | 3 + .../sparrow/io/KeystoreFileImport.java | 3 +- .../sparrow/io/KeystoreImport.java | 1 - .../sparrow/io/SinglesigWalletImport.java | 13 -- ...sigWalletImport.java => WalletImport.java} | 3 +- .../com/sparrowwallet/sparrow/app.fxml | 3 +- .../com/sparrowwallet/sparrow/general.css | 48 +++++++ .../sparrow/keystoreimport/keystoreimport.css | 47 ------- 21 files changed, 361 insertions(+), 237 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java rename src/main/java/com/sparrowwallet/sparrow/control/{KeystoreImportPane.java => TitledDescriptionPane.java} (76%) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/FileImport.java delete mode 100644 src/main/java/com/sparrowwallet/sparrow/io/SinglesigWalletImport.java rename src/main/java/com/sparrowwallet/sparrow/io/{MultisigWalletImport.java => WalletImport.java} (74%) diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 90615d17..609dff12 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -14,6 +14,7 @@ import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBTParseException; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.control.TextAreaDialog; +import com.sparrowwallet.sparrow.control.WalletImportDialog; import com.sparrowwallet.sparrow.control.WalletNameDialog; import com.sparrowwallet.sparrow.control.WalletPasswordDialog; import com.sparrowwallet.sparrow.event.TabEvent; @@ -257,6 +258,17 @@ public class AppController implements Initializable { } } + public void importWallet(ActionEvent event) { + WalletImportDialog dlg = new WalletImportDialog(); + Optional optionalWallet = dlg.showAndWait(); + if(optionalWallet.isPresent()) { + Wallet wallet = optionalWallet.get(); + File walletFile = Storage.getStorage().getWalletFile(wallet.getName()); + Tab tab = addWalletTab(walletFile, null, wallet); + tabs.getSelectionModel().select(tab); + } + } + public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) { try { String name = walletFile.getName(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java index 21f73f74..3ed5fdeb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java @@ -59,7 +59,7 @@ public class DevicePane extends TitledPane { setPadding(Insets.EMPTY); setGraphic(getTitle()); - getStyleClass().add("devicepane"); + getStyleClass().add("titled-description-pane"); setDefaultStatus(); removeArrow(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java new file mode 100644 index 00000000..dd95e0e6 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java @@ -0,0 +1,121 @@ +package com.sparrowwallet.sparrow.control; + +import com.google.gson.JsonParseException; +import com.sparrowwallet.drongo.crypto.InvalidPasswordException; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.KeystoreImportEvent; +import com.sparrowwallet.sparrow.io.FileImport; +import com.sparrowwallet.sparrow.io.ImportException; +import com.sparrowwallet.sparrow.io.KeystoreFileImport; +import javafx.beans.property.SimpleStringProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Control; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.control.textfield.TextFields; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +public abstract class FileImportPane extends TitledDescriptionPane { + private final FileImport importer; + private Button importButton; + private final SimpleStringProperty password = new SimpleStringProperty(""); + + public FileImportPane(FileImport importer, String title, String description, String content, String imageUrl) { + super(title, description, content, imageUrl); + this.importer = importer; + } + + @Override + protected Control createButton() { + importButton = new Button("Import File..."); + importButton.setAlignment(Pos.CENTER_RIGHT); + importButton.setOnAction(event -> { + importFile(); + }); + return importButton; + } + + private void importFile() { + Stage window = new Stage(); + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("All Files", "*.*"), + new FileChooser.ExtensionFilter("JSON", "*.json") + ); + + File file = fileChooser.showOpenDialog(window); + if(file != null) { + importFile(file, null); + } + } + + private void importFile(File file, String password) { + if(file.exists()) { + try { + if(importer.isEncrypted(file) && password == null) { + setDescription("Password Required"); + showHideLink.setVisible(false); + setContent(getPasswordEntry(file)); + importButton.setDisable(true); + setExpanded(true); + } else { + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + importFile(file.getName(), inputStream, password); + } + } catch (Exception e) { + String errorMessage = e.getMessage(); + if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) { + errorMessage = e.getCause().getMessage(); + } + if(e instanceof InvalidPasswordException || e.getCause() instanceof InvalidPasswordException) { + errorMessage = "Invalid wallet password"; + } + if(e instanceof JsonParseException || e.getCause() instanceof JsonParseException) { + errorMessage = "File was not in JSON format"; + } + setError("Import Error", errorMessage); + importButton.setDisable(false); + } + } + } + + protected abstract void importFile(String fileName, InputStream inputStream, String password) throws ImportException; + + private Node getPasswordEntry(File file) { + CustomPasswordField passwordField = (CustomPasswordField) TextFields.createClearablePasswordField(); + passwordField.setPromptText("Wallet password"); + password.bind(passwordField.textProperty()); + HBox.setHgrow(passwordField, Priority.ALWAYS); + + Button importEncryptedButton = new Button("Import"); + importEncryptedButton.setOnAction(event -> { + showHideLink.setVisible(true); + setExpanded(false); + importFile(file, password.get()); + }); + + HBox contentBox = new HBox(); + contentBox.setAlignment(Pos.TOP_RIGHT); + contentBox.setSpacing(20); + contentBox.getChildren().add(passwordField); + contentBox.getChildren().add(importEncryptedButton); + contentBox.setPadding(new Insets(10, 30, 10, 30)); + contentBox.setPrefHeight(60); + + return contentBox; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java index fc2ce087..2b55b755 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java @@ -1,121 +1,26 @@ package com.sparrowwallet.sparrow.control; -import com.google.gson.JsonParseException; -import com.sparrowwallet.drongo.crypto.InvalidPasswordException; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.KeystoreImportEvent; +import com.sparrowwallet.sparrow.io.ImportException; import com.sparrowwallet.sparrow.io.KeystoreFileImport; -import com.sparrowwallet.sparrow.io.KeystoreImport; -import javafx.beans.property.SimpleStringProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.stage.FileChooser; -import javafx.stage.Stage; -import org.controlsfx.control.textfield.CustomPasswordField; -import org.controlsfx.control.textfield.TextFields; import java.io.*; -public class FileKeystoreImportPane extends KeystoreImportPane { +public class FileKeystoreImportPane extends FileImportPane { + protected final Wallet wallet; private final KeystoreFileImport importer; - private Button importButton; - private final SimpleStringProperty password = new SimpleStringProperty(""); - public FileKeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreFileImport importer) { - super(importAccordion, wallet, importer); + public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer) { + super(importer, importer.getName(), "Keystore file import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png"); + this.wallet = wallet; this.importer = importer; } - @Override - protected Node getTitle(KeystoreImport importer) { - Node title = super.getTitle(importer); - - setDescription("Keystore file import"); - - importButton = new Button("Import File..."); - importButton.setAlignment(Pos.CENTER_RIGHT); - importButton.setOnAction(event -> { - importFile(); - }); - buttonBox.getChildren().add(importButton); - - return title; - } - - private void importFile() { - Stage window = new Stage(); - - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore"); - fileChooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("All Files", "*.*"), - new FileChooser.ExtensionFilter("JSON", "*.json") - ); - - File file = fileChooser.showOpenDialog(window); - if(file != null) { - importFile(file, null); - } - } - - private void importFile(File file, String password) { - if(file.exists()) { - try { - if(importer.isEncrypted(file) && password == null) { - setDescription("Password Required"); - showHideLink.setVisible(false); - setContent(getPasswordEntry(file)); - importButton.setDisable(true); - setExpanded(true); - } else { - InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); - Keystore keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password); - EventManager.get().post(new KeystoreImportEvent(keystore)); - } - } catch (Exception e) { - String errorMessage = e.getMessage(); - if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) { - errorMessage = e.getCause().getMessage(); - } - if(e instanceof InvalidPasswordException || e.getCause() instanceof InvalidPasswordException) { - errorMessage = "Invalid wallet password"; - } - if(e instanceof JsonParseException || e.getCause() instanceof JsonParseException) { - errorMessage = "File was not in JSON format"; - } - setError("Import Error", errorMessage); - importButton.setDisable(false); - } - } - } - - private Node getPasswordEntry(File file) { - CustomPasswordField passwordField = (CustomPasswordField) TextFields.createClearablePasswordField(); - passwordField.setPromptText("Wallet password"); - password.bind(passwordField.textProperty()); - HBox.setHgrow(passwordField, Priority.ALWAYS); - - Button importEncryptedButton = new Button("Import"); - importEncryptedButton.setOnAction(event -> { - showHideLink.setVisible(true); - setExpanded(false); - importFile(file, password.get()); - }); - - HBox contentBox = new HBox(); - contentBox.setAlignment(Pos.TOP_RIGHT); - contentBox.setSpacing(20); - contentBox.getChildren().add(passwordField); - contentBox.getChildren().add(importEncryptedButton); - contentBox.setPadding(new Insets(10, 30, 10, 30)); - contentBox.setPrefHeight(60); - - return contentBox; + protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException { + Keystore keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password); + EventManager.get().post(new KeystoreImportEvent(keystore)); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java new file mode 100644 index 00000000..3c356dcf --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java @@ -0,0 +1,27 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletImportEvent; +import com.sparrowwallet.sparrow.io.ImportException; +import com.sparrowwallet.sparrow.io.WalletImport; + +import java.io.InputStream; + +public class FileWalletImportPane extends FileImportPane { + private final WalletImport importer; + + public FileWalletImportPane(WalletImport importer) { + super(importer, importer.getName(), "Wallet file import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png"); + this.importer = importer; + } + + @Override + protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException { + Wallet wallet = importer.importWallet(inputStream, password); + if(wallet.getName() == null) { + wallet.setName(fileName); + } + EventManager.get().post(new WalletImportEvent(wallet)); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java b/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java index b8ef94ff..9e800f02 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportAccordion.java @@ -16,12 +16,12 @@ public class KeystoreImportAccordion extends Accordion { this.importers = importers; for(KeystoreImport importer : importers) { - KeystoreImportPane importPane = null; + TitledDescriptionPane importPane = null; if(importer instanceof KeystoreFileImport) { - importPane = new FileKeystoreImportPane(this, wallet, (KeystoreFileImport)importer); + importPane = new FileKeystoreImportPane(wallet, (KeystoreFileImport)importer); } else if(importer instanceof KeystoreMnemonicImport) { - importPane = new MnemonicKeystoreImportPane(this, wallet, (KeystoreMnemonicImport)importer); + importPane = new MnemonicKeystoreImportPane(wallet, (KeystoreMnemonicImport)importer); } else { throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass()); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java index 95e307be..b3f73bd3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java @@ -34,7 +34,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -public class MnemonicKeystoreImportPane extends KeystoreImportPane { +public class MnemonicKeystoreImportPane extends TitledDescriptionPane { + protected final Wallet wallet; private final KeystoreMnemonicImport importer; private SplitMenuButton enterMnemonicButton; @@ -48,21 +49,19 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { private SimpleListProperty wordEntriesProperty; private final SimpleStringProperty passphraseProperty = new SimpleStringProperty(); - public MnemonicKeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreMnemonicImport importer) { - super(importAccordion, wallet, importer); + public MnemonicKeystoreImportPane(Wallet wallet, KeystoreMnemonicImport importer) { + super(importer.getName(), "Mnemonic import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png"); + this.wallet = wallet; this.importer = importer; + + createImportButton(); + buttonBox.getChildren().add(importButton); } @Override - protected Node getTitle(KeystoreImport importer) { - Node title = super.getTitle(importer); - setDescription("Keystore file import"); - + protected Control createButton() { createEnterMnemonicButton(); - createImportButton(); - buttonBox.getChildren().addAll(enterMnemonicButton, importButton); - - return title; + return enterMnemonicButton; } private void createEnterMnemonicButton() { @@ -103,8 +102,6 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { importButton.getItems().add(item); } - - importButton.managedProperty().bind(importButton.visibleProperty()); importButton.setVisible(false); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/TitledDescriptionPane.java similarity index 76% rename from src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportPane.java rename to src/main/java/com/sparrowwallet/sparrow/control/TitledDescriptionPane.java index 73e5e6f4..0a27f893 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/KeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TitledDescriptionPane.java @@ -1,53 +1,33 @@ package com.sparrowwallet.sparrow.control; -import com.sparrowwallet.drongo.wallet.Wallet; -import com.sparrowwallet.sparrow.io.KeystoreImport; +import com.sparrowwallet.sparrow.AppController; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.Hyperlink; -import javafx.scene.control.Label; -import javafx.scene.control.TitledPane; +import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -public abstract class KeystoreImportPane extends TitledPane { - protected final KeystoreImportAccordion importAccordion; - protected final Wallet wallet; - - private Label mainLabel; +public class TitledDescriptionPane extends TitledPane { private Label descriptionLabel; protected Hyperlink showHideLink; protected HBox buttonBox; - public KeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreImport importer) { - this.importAccordion = importAccordion; - this.wallet = wallet; + public TitledDescriptionPane(String title, String description, String content, String imageUrl) { + getStylesheets().add(AppController.class.getResource("general.css").toExternalForm()); + getStyleClass().add("titled-description-pane"); setPadding(Insets.EMPTY); - setGraphic(getTitle(importer)); - getStyleClass().add("importpane"); - setContent(getContentBox(importer.getKeystoreImportDescription())); + setGraphic(getTitle(title, description, imageUrl)); + setContent(getContentBox(content)); removeArrow(); } - private void removeArrow() { - Platform.runLater(() -> { - Node arrow = this.lookup(".arrow"); - if (arrow != null) { - arrow.setVisible(false); - arrow.setManaged(false); - } else { - removeArrow(); - } - }); - } - - protected Node getTitle(KeystoreImport importer) { + protected Node getTitle(String title, String description, String imageUrl) { HBox listItem = new HBox(); listItem.setPadding(new Insets(10, 20, 10, 10)); listItem.setSpacing(10); @@ -57,7 +37,7 @@ public abstract class KeystoreImportPane extends TitledPane { imageBox.setMinHeight(50); listItem.getChildren().add(imageBox); - Image image = new Image("image/" + importer.getWalletModel().getType() + ".png", 50, 50, true, true); + Image image = new Image(imageUrl, 50, 50, true, true); if (!image.isError()) { ImageView imageView = new ImageView(); imageView.setImage(image); @@ -67,8 +47,8 @@ public abstract class KeystoreImportPane extends TitledPane { VBox labelsBox = new VBox(); labelsBox.setSpacing(5); labelsBox.setAlignment(Pos.CENTER_LEFT); - this.mainLabel = new Label(); - mainLabel.setText(importer.getName()); + Label mainLabel = new Label(); + mainLabel.setText(title); mainLabel.getStyleClass().add("main-label"); labelsBox.getChildren().add(mainLabel); @@ -76,16 +56,12 @@ public abstract class KeystoreImportPane extends TitledPane { descriptionBox.setSpacing(7); labelsBox.getChildren().add(descriptionBox); - descriptionLabel = new Label("Keystore Import"); + descriptionLabel = new Label(description); descriptionLabel.getStyleClass().add("description-label"); showHideLink = new Hyperlink("Show Details..."); showHideLink.managedProperty().bind(showHideLink.visibleProperty()); showHideLink.setOnAction(event -> { - if(this.isExpanded()) { - setExpanded(false); - } else { - setExpanded(true); - } + setExpanded(!this.isExpanded()); }); this.expandedProperty().addListener((observable, oldValue, newValue) -> { if(newValue) { @@ -101,6 +77,10 @@ public abstract class KeystoreImportPane extends TitledPane { buttonBox = new HBox(); buttonBox.setAlignment(Pos.CENTER_RIGHT); + Control button = createButton(); + if(button != null) { + buttonBox.getChildren().add(button); + } listItem.getChildren().add(buttonBox); this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> { @@ -111,6 +91,11 @@ public abstract class KeystoreImportPane extends TitledPane { return listItem; } + protected Control createButton() { + //No buttons by default + return null; + } + protected void setDescription(String text) { descriptionLabel.getStyleClass().remove("description-error"); descriptionLabel.getStyleClass().add("description-label"); @@ -141,4 +126,16 @@ public abstract class KeystoreImportPane extends TitledPane { return contentBox; } + + private void removeArrow() { + Platform.runLater(() -> { + Node arrow = this.lookup(".arrow"); + if (arrow != null) { + arrow.setVisible(false); + arrow.setManaged(false); + } else { + removeArrow(); + } + }); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java new file mode 100644 index 00000000..20d6ec13 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -0,0 +1,59 @@ +package com.sparrowwallet.sparrow.control; + +import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletImportEvent; +import com.sparrowwallet.sparrow.io.*; +import javafx.scene.control.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.StackPane; + +import java.util.List; + +public class WalletImportDialog extends Dialog { + private Wallet wallet; + + public WalletImportDialog() { + EventManager.get().register(this); + + final DialogPane dialogPane = getDialogPane(); + + StackPane stackPane = new StackPane(); + dialogPane.setContent(stackPane); + + AnchorPane anchorPane = new AnchorPane(); + stackPane.getChildren().add(anchorPane); + + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setPrefWidth(500); + scrollPane.setPrefHeight(500); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + anchorPane.getChildren().add(scrollPane); + scrollPane.setFitToWidth(true); + AnchorPane.setLeftAnchor(scrollPane, 0.0); + AnchorPane.setRightAnchor(scrollPane, 0.0); + + List importers = List.of(new ColdcardMultisig(), new Electrum()); + Accordion importAccordion = new Accordion(); + for(WalletImport importer : importers) { + FileWalletImportPane importPane = new FileWalletImportPane(importer); + importAccordion.getPanes().add(importPane); + } + scrollPane.setContent(importAccordion); + + final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + dialogPane.getButtonTypes().addAll(cancelButtonType); + dialogPane.setPrefWidth(650); + dialogPane.setPrefHeight(600); + + setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallet : null); + } + + @Subscribe + public void walletImported(WalletImportEvent event) { + wallet = event.getWallet(); + setResult(wallet); + this.close(); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java new file mode 100644 index 00000000..6dfc7aee --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletImportEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; + +public class WalletImportEvent { + private Wallet wallet; + + public WalletImportEvent(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java index 8d4c4d11..c09c6797 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java @@ -18,7 +18,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImport, WalletExport { +public class ColdcardMultisig implements WalletImport, KeystoreFileImport, WalletExport { private final Gson gson = new Gson(); @Override @@ -125,6 +125,10 @@ public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImpor wallet.setDefaultPolicy(policy); wallet.setScriptType(scriptType); + if(!wallet.isValid()) { + throw new IllegalStateException("This file does not describe a valid wallet. Please use the Settings > Multisig Wallets > Export XPUB feature on your Coldcard."); + } + return wallet; } catch(Exception e) { throw new ImportException(e); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java index f8690795..c17df8d0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java @@ -22,7 +22,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.zip.InflaterInputStream; -public class Electrum implements KeystoreFileImport, SinglesigWalletImport, MultisigWalletImport, WalletExport { +public class Electrum implements KeystoreFileImport, WalletImport, WalletExport { @Override public String getName() { return "Electrum"; @@ -131,7 +131,7 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult } if(!wallet.isValid()) { - throw new IllegalStateException("Electrum wallet is in an inconsistent state"); + throw new IllegalStateException("Electrum wallet is in an inconsistent state."); } return wallet; @@ -145,15 +145,6 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult return "Import an Electrum wallet"; } - @Override - public Wallet importWallet(ScriptType scriptType, InputStream inputStream, String password) throws ImportException { - Wallet wallet = importWallet(inputStream, password); - wallet.setScriptType(scriptType); - //TODO: Check this usage results in a valid wallet - - return wallet; - } - @Override public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { try { diff --git a/src/main/java/com/sparrowwallet/sparrow/io/FileImport.java b/src/main/java/com/sparrowwallet/sparrow/io/FileImport.java new file mode 100644 index 00000000..ef2a6114 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/FileImport.java @@ -0,0 +1,7 @@ +package com.sparrowwallet.sparrow.io; + +import java.io.File; + +public interface FileImport extends Import { + boolean isEncrypted(File file); +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Import.java b/src/main/java/com/sparrowwallet/sparrow/io/Import.java index a6aa364c..bd0e53cf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Import.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Import.java @@ -1,5 +1,8 @@ package com.sparrowwallet.sparrow.io; +import com.sparrowwallet.drongo.wallet.WalletModel; + public interface Import { String getName(); + WalletModel getWalletModel(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java b/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java index b31a56dc..6d7db71e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java @@ -6,7 +6,6 @@ import com.sparrowwallet.drongo.wallet.Keystore; import java.io.File; import java.io.InputStream; -public interface KeystoreFileImport extends KeystoreImport { - boolean isEncrypted(File file); +public interface KeystoreFileImport extends KeystoreImport, FileImport { Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/KeystoreImport.java b/src/main/java/com/sparrowwallet/sparrow/io/KeystoreImport.java index d9b95bc5..985caa05 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/KeystoreImport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/KeystoreImport.java @@ -4,6 +4,5 @@ import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.wallet.WalletModel; public interface KeystoreImport extends Import { - WalletModel getWalletModel(); String getKeystoreImportDescription(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SinglesigWalletImport.java b/src/main/java/com/sparrowwallet/sparrow/io/SinglesigWalletImport.java deleted file mode 100644 index 9d88703f..00000000 --- a/src/main/java/com/sparrowwallet/sparrow/io/SinglesigWalletImport.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sparrowwallet.sparrow.io; - -import com.sparrowwallet.drongo.protocol.ScriptType; -import com.sparrowwallet.drongo.wallet.Wallet; - -import java.io.File; -import java.io.InputStream; - -public interface SinglesigWalletImport extends Import { - String getWalletImportDescription(); - Wallet importWallet(ScriptType scriptType, InputStream inputStream, String password) throws ImportException; - boolean isEncrypted(File file); -} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/MultisigWalletImport.java b/src/main/java/com/sparrowwallet/sparrow/io/WalletImport.java similarity index 74% rename from src/main/java/com/sparrowwallet/sparrow/io/MultisigWalletImport.java rename to src/main/java/com/sparrowwallet/sparrow/io/WalletImport.java index e12aeb8e..bb516927 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/MultisigWalletImport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/WalletImport.java @@ -5,8 +5,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import java.io.File; import java.io.InputStream; -public interface MultisigWalletImport extends Import { +public interface WalletImport extends Import, FileImport { String getWalletImportDescription(); Wallet importWallet(InputStream inputStream, String password) throws ImportException; - boolean isEncrypted(File file); } diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.fxml b/src/main/resources/com/sparrowwallet/sparrow/app.fxml index 4891faa4..1f264fad 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/app.fxml @@ -13,7 +13,7 @@ - + @@ -21,6 +21,7 @@ + diff --git a/src/main/resources/com/sparrowwallet/sparrow/general.css b/src/main/resources/com/sparrowwallet/sparrow/general.css index 86e4fdd7..e8aaf2d4 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/general.css +++ b/src/main/resources/com/sparrowwallet/sparrow/general.css @@ -50,3 +50,51 @@ -fx-padding: 20; } +.titled-description-pane > .title { + -fx-background-color: white; + -fx-padding: 0; + -fx-border-color: #e5e5e6; + /*-fx-border-width: 1;*/ +} + +.titled-description-pane > .title > .arrow-button { + -fx-padding: 0; +} + +.titled-description-pane > .title > .arrow-button > .arrow { + visibility: hidden; + -fx-translate-x: -1000; + -fx-padding: 0; +} + +.titled-description-pane .main-label .text { + +} + +.titled-description-pane .status-label .text, .titled-description-pane .description-label .text { + -fx-fill: #a0a1a7; +} + +.titled-description-pane .status-error .text, .titled-description-pane .description-error .text { + -fx-fill: #ca1243; +} + +.titled-description-pane .description-label, .titled-description-pane .description-error { + -fx-border-width: 0px; + -fx-border-color: transparent; +} + +.titled-description-pane .hyperlink { + -fx-padding: 0; + -fx-border-width: 0; + -fx-fill: #1e88cf; +} + +.titled-description-pane .hyperlink:visited { + -fx-text-fill: #1e88cf; + -fx-underline: false; +} + +.titled-description-pane .hyperlink:hover:visited { + -fx-underline: true; +} diff --git a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css index 54dece77..921d74ad 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css +++ b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css @@ -33,52 +33,5 @@ -fx-background-color: transparent; } -.titled-pane > .title { - -fx-background-color: white; - -fx-padding: 0; - -fx-border-color: #e5e5e6; - /*-fx-border-width: 1;*/ -} -.titled-pane > .title > .arrow-button { - -fx-padding: 0; -} - -.titled-pane > .title > .arrow-button > .arrow { - visibility: hidden; - -fx-translate-x: -1000; - -fx-padding: 0; -} - -.main-label .text { - -} - -.status-label .text, .description-label .text { - -fx-fill: #a0a1a7; -} - -.status-error .text, .description-error .text { - -fx-fill: #ca1243; -} - -.description-label, .description-error { - -fx-border-width: 0px; - -fx-border-color: transparent; -} - -.hyperlink { - -fx-padding: 0; - -fx-border-width: 0; - -fx-fill: #1e88cf; -} - -.hyperlink:visited { - -fx-text-fill: #1e88cf; - -fx-underline: false; -} - -.hyperlink:hover:visited { - -fx-underline: true; -}