mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
refactor import accordion, add wallet import
This commit is contained in:
parent
bc5690346c
commit
d516eaa9d6
21 changed files with 361 additions and 237 deletions
|
@ -14,6 +14,7 @@ import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.control.TextAreaDialog;
|
import com.sparrowwallet.sparrow.control.TextAreaDialog;
|
||||||
|
import com.sparrowwallet.sparrow.control.WalletImportDialog;
|
||||||
import com.sparrowwallet.sparrow.control.WalletNameDialog;
|
import com.sparrowwallet.sparrow.control.WalletNameDialog;
|
||||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||||
import com.sparrowwallet.sparrow.event.TabEvent;
|
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<Wallet> 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) {
|
public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) {
|
||||||
try {
|
try {
|
||||||
String name = walletFile.getName();
|
String name = walletFile.getName();
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class DevicePane extends TitledPane {
|
||||||
setPadding(Insets.EMPTY);
|
setPadding(Insets.EMPTY);
|
||||||
|
|
||||||
setGraphic(getTitle());
|
setGraphic(getTitle());
|
||||||
getStyleClass().add("devicepane");
|
getStyleClass().add("titled-description-pane");
|
||||||
setDefaultStatus();
|
setDefaultStatus();
|
||||||
|
|
||||||
removeArrow();
|
removeArrow();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,121 +1,26 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
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.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.io.ImportException;
|
||||||
import com.sparrowwallet.sparrow.io.KeystoreFileImport;
|
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.*;
|
import java.io.*;
|
||||||
|
|
||||||
public class FileKeystoreImportPane extends KeystoreImportPane {
|
public class FileKeystoreImportPane extends FileImportPane {
|
||||||
|
protected final Wallet wallet;
|
||||||
private final KeystoreFileImport importer;
|
private final KeystoreFileImport importer;
|
||||||
private Button importButton;
|
|
||||||
private final SimpleStringProperty password = new SimpleStringProperty("");
|
|
||||||
|
|
||||||
public FileKeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreFileImport importer) {
|
public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer) {
|
||||||
super(importAccordion, wallet, importer);
|
super(importer, importer.getName(), "Keystore file import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
||||||
|
this.wallet = wallet;
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException {
|
||||||
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);
|
Keystore keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password);
|
||||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,12 @@ public class KeystoreImportAccordion extends Accordion {
|
||||||
this.importers = importers;
|
this.importers = importers;
|
||||||
|
|
||||||
for(KeystoreImport importer : importers) {
|
for(KeystoreImport importer : importers) {
|
||||||
KeystoreImportPane importPane = null;
|
TitledDescriptionPane importPane = null;
|
||||||
|
|
||||||
if(importer instanceof KeystoreFileImport) {
|
if(importer instanceof KeystoreFileImport) {
|
||||||
importPane = new FileKeystoreImportPane(this, wallet, (KeystoreFileImport)importer);
|
importPane = new FileKeystoreImportPane(wallet, (KeystoreFileImport)importer);
|
||||||
} else if(importer instanceof KeystoreMnemonicImport) {
|
} else if(importer instanceof KeystoreMnemonicImport) {
|
||||||
importPane = new MnemonicKeystoreImportPane(this, wallet, (KeystoreMnemonicImport)importer);
|
importPane = new MnemonicKeystoreImportPane(wallet, (KeystoreMnemonicImport)importer);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
|
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MnemonicKeystoreImportPane extends KeystoreImportPane {
|
public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
|
protected final Wallet wallet;
|
||||||
private final KeystoreMnemonicImport importer;
|
private final KeystoreMnemonicImport importer;
|
||||||
|
|
||||||
private SplitMenuButton enterMnemonicButton;
|
private SplitMenuButton enterMnemonicButton;
|
||||||
|
@ -48,21 +49,19 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane {
|
||||||
private SimpleListProperty<String> wordEntriesProperty;
|
private SimpleListProperty<String> wordEntriesProperty;
|
||||||
private final SimpleStringProperty passphraseProperty = new SimpleStringProperty();
|
private final SimpleStringProperty passphraseProperty = new SimpleStringProperty();
|
||||||
|
|
||||||
public MnemonicKeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreMnemonicImport importer) {
|
public MnemonicKeystoreImportPane(Wallet wallet, KeystoreMnemonicImport importer) {
|
||||||
super(importAccordion, wallet, importer);
|
super(importer.getName(), "Mnemonic import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
||||||
|
this.wallet = wallet;
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
|
|
||||||
|
createImportButton();
|
||||||
|
buttonBox.getChildren().add(importButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Node getTitle(KeystoreImport importer) {
|
protected Control createButton() {
|
||||||
Node title = super.getTitle(importer);
|
|
||||||
setDescription("Keystore file import");
|
|
||||||
|
|
||||||
createEnterMnemonicButton();
|
createEnterMnemonicButton();
|
||||||
createImportButton();
|
return enterMnemonicButton;
|
||||||
buttonBox.getChildren().addAll(enterMnemonicButton, importButton);
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createEnterMnemonicButton() {
|
private void createEnterMnemonicButton() {
|
||||||
|
@ -103,8 +102,6 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane {
|
||||||
importButton.getItems().add(item);
|
importButton.getItems().add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
importButton.managedProperty().bind(importButton.visibleProperty());
|
importButton.managedProperty().bind(importButton.visibleProperty());
|
||||||
importButton.setVisible(false);
|
importButton.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,33 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.sparrow.AppController;
|
||||||
import com.sparrowwallet.sparrow.io.KeystoreImport;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Hyperlink;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.TitledPane;
|
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
public abstract class KeystoreImportPane extends TitledPane {
|
public class TitledDescriptionPane extends TitledPane {
|
||||||
protected final KeystoreImportAccordion importAccordion;
|
|
||||||
protected final Wallet wallet;
|
|
||||||
|
|
||||||
private Label mainLabel;
|
|
||||||
private Label descriptionLabel;
|
private Label descriptionLabel;
|
||||||
protected Hyperlink showHideLink;
|
protected Hyperlink showHideLink;
|
||||||
protected HBox buttonBox;
|
protected HBox buttonBox;
|
||||||
|
|
||||||
public KeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreImport importer) {
|
public TitledDescriptionPane(String title, String description, String content, String imageUrl) {
|
||||||
this.importAccordion = importAccordion;
|
getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
|
||||||
this.wallet = wallet;
|
getStyleClass().add("titled-description-pane");
|
||||||
|
|
||||||
setPadding(Insets.EMPTY);
|
setPadding(Insets.EMPTY);
|
||||||
setGraphic(getTitle(importer));
|
setGraphic(getTitle(title, description, imageUrl));
|
||||||
getStyleClass().add("importpane");
|
setContent(getContentBox(content));
|
||||||
setContent(getContentBox(importer.getKeystoreImportDescription()));
|
|
||||||
removeArrow();
|
removeArrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeArrow() {
|
protected Node getTitle(String title, String description, String imageUrl) {
|
||||||
Platform.runLater(() -> {
|
|
||||||
Node arrow = this.lookup(".arrow");
|
|
||||||
if (arrow != null) {
|
|
||||||
arrow.setVisible(false);
|
|
||||||
arrow.setManaged(false);
|
|
||||||
} else {
|
|
||||||
removeArrow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Node getTitle(KeystoreImport importer) {
|
|
||||||
HBox listItem = new HBox();
|
HBox listItem = new HBox();
|
||||||
listItem.setPadding(new Insets(10, 20, 10, 10));
|
listItem.setPadding(new Insets(10, 20, 10, 10));
|
||||||
listItem.setSpacing(10);
|
listItem.setSpacing(10);
|
||||||
|
@ -57,7 +37,7 @@ public abstract class KeystoreImportPane extends TitledPane {
|
||||||
imageBox.setMinHeight(50);
|
imageBox.setMinHeight(50);
|
||||||
listItem.getChildren().add(imageBox);
|
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()) {
|
if (!image.isError()) {
|
||||||
ImageView imageView = new ImageView();
|
ImageView imageView = new ImageView();
|
||||||
imageView.setImage(image);
|
imageView.setImage(image);
|
||||||
|
@ -67,8 +47,8 @@ public abstract class KeystoreImportPane extends TitledPane {
|
||||||
VBox labelsBox = new VBox();
|
VBox labelsBox = new VBox();
|
||||||
labelsBox.setSpacing(5);
|
labelsBox.setSpacing(5);
|
||||||
labelsBox.setAlignment(Pos.CENTER_LEFT);
|
labelsBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
this.mainLabel = new Label();
|
Label mainLabel = new Label();
|
||||||
mainLabel.setText(importer.getName());
|
mainLabel.setText(title);
|
||||||
mainLabel.getStyleClass().add("main-label");
|
mainLabel.getStyleClass().add("main-label");
|
||||||
labelsBox.getChildren().add(mainLabel);
|
labelsBox.getChildren().add(mainLabel);
|
||||||
|
|
||||||
|
@ -76,16 +56,12 @@ public abstract class KeystoreImportPane extends TitledPane {
|
||||||
descriptionBox.setSpacing(7);
|
descriptionBox.setSpacing(7);
|
||||||
labelsBox.getChildren().add(descriptionBox);
|
labelsBox.getChildren().add(descriptionBox);
|
||||||
|
|
||||||
descriptionLabel = new Label("Keystore Import");
|
descriptionLabel = new Label(description);
|
||||||
descriptionLabel.getStyleClass().add("description-label");
|
descriptionLabel.getStyleClass().add("description-label");
|
||||||
showHideLink = new Hyperlink("Show Details...");
|
showHideLink = new Hyperlink("Show Details...");
|
||||||
showHideLink.managedProperty().bind(showHideLink.visibleProperty());
|
showHideLink.managedProperty().bind(showHideLink.visibleProperty());
|
||||||
showHideLink.setOnAction(event -> {
|
showHideLink.setOnAction(event -> {
|
||||||
if(this.isExpanded()) {
|
setExpanded(!this.isExpanded());
|
||||||
setExpanded(false);
|
|
||||||
} else {
|
|
||||||
setExpanded(true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.expandedProperty().addListener((observable, oldValue, newValue) -> {
|
this.expandedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if(newValue) {
|
if(newValue) {
|
||||||
|
@ -101,6 +77,10 @@ public abstract class KeystoreImportPane extends TitledPane {
|
||||||
|
|
||||||
buttonBox = new HBox();
|
buttonBox = new HBox();
|
||||||
buttonBox.setAlignment(Pos.CENTER_RIGHT);
|
buttonBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
Control button = createButton();
|
||||||
|
if(button != null) {
|
||||||
|
buttonBox.getChildren().add(button);
|
||||||
|
}
|
||||||
listItem.getChildren().add(buttonBox);
|
listItem.getChildren().add(buttonBox);
|
||||||
|
|
||||||
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
|
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -111,6 +91,11 @@ public abstract class KeystoreImportPane extends TitledPane {
|
||||||
return listItem;
|
return listItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Control createButton() {
|
||||||
|
//No buttons by default
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void setDescription(String text) {
|
protected void setDescription(String text) {
|
||||||
descriptionLabel.getStyleClass().remove("description-error");
|
descriptionLabel.getStyleClass().remove("description-error");
|
||||||
descriptionLabel.getStyleClass().add("description-label");
|
descriptionLabel.getStyleClass().add("description-label");
|
||||||
|
@ -141,4 +126,16 @@ public abstract class KeystoreImportPane extends TitledPane {
|
||||||
|
|
||||||
return contentBox;
|
return contentBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeArrow() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Node arrow = this.lookup(".arrow");
|
||||||
|
if (arrow != null) {
|
||||||
|
arrow.setVisible(false);
|
||||||
|
arrow.setManaged(false);
|
||||||
|
} else {
|
||||||
|
removeArrow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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<Wallet> {
|
||||||
|
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<WalletImport> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImport, WalletExport {
|
public class ColdcardMultisig implements WalletImport, KeystoreFileImport, WalletExport {
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -125,6 +125,10 @@ public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImpor
|
||||||
wallet.setDefaultPolicy(policy);
|
wallet.setDefaultPolicy(policy);
|
||||||
wallet.setScriptType(scriptType);
|
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;
|
return wallet;
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
throw new ImportException(e);
|
throw new ImportException(e);
|
||||||
|
|
|
@ -22,7 +22,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
public class Electrum implements KeystoreFileImport, SinglesigWalletImport, MultisigWalletImport, WalletExport {
|
public class Electrum implements KeystoreFileImport, WalletImport, WalletExport {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Electrum";
|
return "Electrum";
|
||||||
|
@ -131,7 +131,7 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!wallet.isValid()) {
|
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;
|
return wallet;
|
||||||
|
@ -145,15 +145,6 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult
|
||||||
return "Import an Electrum wallet";
|
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
|
@Override
|
||||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface FileImport extends Import {
|
||||||
|
boolean isEncrypted(File file);
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package com.sparrowwallet.sparrow.io;
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
|
||||||
public interface Import {
|
public interface Import {
|
||||||
String getName();
|
String getName();
|
||||||
|
WalletModel getWalletModel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public interface KeystoreFileImport extends KeystoreImport {
|
public interface KeystoreFileImport extends KeystoreImport, FileImport {
|
||||||
boolean isEncrypted(File file);
|
|
||||||
Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException;
|
Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,5 @@ import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
|
||||||
public interface KeystoreImport extends Import {
|
public interface KeystoreImport extends Import {
|
||||||
WalletModel getWalletModel();
|
|
||||||
String getKeystoreImportDescription();
|
String getKeystoreImportDescription();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -5,8 +5,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public interface MultisigWalletImport extends Import {
|
public interface WalletImport extends Import, FileImport {
|
||||||
String getWalletImportDescription();
|
String getWalletImportDescription();
|
||||||
Wallet importWallet(InputStream inputStream, String password) throws ImportException;
|
Wallet importWallet(InputStream inputStream, String password) throws ImportException;
|
||||||
boolean isEncrypted(File file);
|
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
<Menu mnemonicParsing="false" text="File">
|
<Menu mnemonicParsing="false" text="File">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem mnemonicParsing="false" text="New Wallet" onAction="#newWallet"/>
|
<MenuItem mnemonicParsing="false" text="New Wallet" onAction="#newWallet"/>
|
||||||
<MenuItem mnemonicParsing="false" text="Open Wallet" onAction="#openWallet"/>
|
<MenuItem mnemonicParsing="false" text="Open Wallet..." onAction="#openWallet"/>
|
||||||
<Menu mnemonicParsing="false" text="Open Transaction">
|
<Menu mnemonicParsing="false" text="Open Transaction">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem text="File..." onAction="#openFromFile"/>
|
<MenuItem text="File..." onAction="#openFromFile"/>
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
<MenuItem text="Examples" onAction="#openExamples"/>
|
<MenuItem text="Examples" onAction="#openExamples"/>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
<MenuItem mnemonicParsing="false" text="Import Wallet..." onAction="#importWallet"/>
|
||||||
<MenuItem mnemonicParsing="false" text="Close" onAction="#closeTab"/>
|
<MenuItem mnemonicParsing="false" text="Close" onAction="#closeTab"/>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
|
@ -50,3 +50,51 @@
|
||||||
-fx-padding: 20;
|
-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;
|
||||||
|
}
|
||||||
|
|
|
@ -33,52 +33,5 @@
|
||||||
-fx-background-color: transparent;
|
-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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue