diff --git a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java index 0d1e15e6..71bf0fc9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java @@ -123,15 +123,18 @@ public class EntryCell extends TreeTableCell { HBox actionBox = new HBox(); actionBox.getStyleClass().add("cell-actions"); - Button receiveButton = new Button(""); - receiveButton.setGraphic(getReceiveGlyph()); - receiveButton.setOnAction(event -> { - EventManager.get().post(new ReceiveActionEvent(nodeEntry)); - Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry))); - }); - actionBox.getChildren().add(receiveButton); - if(canSignMessage(nodeEntry.getNode().getWallet())) { + if(!nodeEntry.getNode().getWallet().isBip47()) { + Button receiveButton = new Button(""); + receiveButton.setGraphic(getReceiveGlyph()); + receiveButton.setOnAction(event -> { + EventManager.get().post(new ReceiveActionEvent(nodeEntry)); + Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry))); + }); + actionBox.getChildren().add(receiveButton); + } + + if(canSignMessage(nodeEntry.getNode())) { Button signMessageButton = new Button(""); signMessageButton.setGraphic(getSignMessageGlyph()); signMessageButton.setOnAction(event -> { @@ -151,7 +154,7 @@ public class EntryCell extends TreeTableCell { } else if(entry instanceof HashIndexEntry) { HashIndexEntry hashIndexEntry = (HashIndexEntry)entry; setText(hashIndexEntry.getDescription()); - setContextMenu(new HashIndexEntryContextMenu(getTreeTableView(), hashIndexEntry)); + setContextMenu(getTreeTableView().getStyleClass().contains("bip47") ? null : new HashIndexEntryContextMenu(getTreeTableView(), hashIndexEntry)); Tooltip tooltip = new Tooltip(); tooltip.setShowDelay(Duration.millis(250)); tooltip.setText(hashIndexEntry.getHashIndex().toString()); @@ -175,7 +178,7 @@ public class EntryCell extends TreeTableCell { actionBox.getChildren().add(spendUtxoButton); } - setGraphic(actionBox); + setGraphic(getTreeTableView().getStyleClass().contains("bip47") ? null : actionBox); } } } @@ -287,9 +290,11 @@ public class EntryCell extends TreeTableCell { Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), List.of(utxo), List.of(payment), blockTransaction.getFee(), false))); } - private static boolean canSignMessage(Wallet wallet) { + private static boolean canSignMessage(WalletNode walletNode) { + Wallet wallet = walletNode.getWallet(); return wallet.getKeystores().size() == 1 && wallet.getScriptType() != ScriptType.P2TR && - (wallet.getKeystores().get(0).hasPrivateKey() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB); + (wallet.getKeystores().get(0).hasPrivateKey() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB) && + (!wallet.isBip47() || walletNode.getKeyPurpose() == KeyPurpose.RECEIVE); } private static boolean containsWalletOutputs(TransactionEntry transactionEntry) { @@ -502,16 +507,18 @@ public class EntryCell extends TreeTableCell { public static class AddressContextMenu extends ContextMenu { public AddressContextMenu(Address address, String outputDescriptor, NodeEntry nodeEntry) { - MenuItem receiveToAddress = new MenuItem("Receive To"); - receiveToAddress.setGraphic(getReceiveGlyph()); - receiveToAddress.setOnAction(event -> { - hide(); - EventManager.get().post(new ReceiveActionEvent(nodeEntry)); - Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry))); - }); - getItems().add(receiveToAddress); + if(nodeEntry == null || !nodeEntry.getWallet().isBip47()) { + MenuItem receiveToAddress = new MenuItem("Receive To"); + receiveToAddress.setGraphic(getReceiveGlyph()); + receiveToAddress.setOnAction(event -> { + hide(); + EventManager.get().post(new ReceiveActionEvent(nodeEntry)); + Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry))); + }); + getItems().add(receiveToAddress); + } - if(nodeEntry != null && canSignMessage(nodeEntry.getNode().getWallet())) { + if(nodeEntry != null && canSignMessage(nodeEntry.getNode())) { MenuItem signVerifyMessage = new MenuItem("Sign/Verify Message"); signVerifyMessage.setGraphic(getSignMessageGlyph()); signVerifyMessage.setOnAction(AE -> { diff --git a/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymAddressesController.java b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymAddressesController.java new file mode 100644 index 00000000..4e8d62c0 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymAddressesController.java @@ -0,0 +1,85 @@ +package com.sparrowwallet.sparrow.paynym; + +import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.drongo.KeyPurpose; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.WalletNode; +import com.sparrowwallet.sparrow.control.AddressTreeTable; +import com.sparrowwallet.sparrow.event.WalletEntryLabelsChangedEvent; +import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; +import com.sparrowwallet.sparrow.wallet.Entry; +import com.sparrowwallet.sparrow.wallet.WalletForm; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.util.StringConverter; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class PayNymAddressesController { + + @FXML + private ComboBox payNymWalletForms; + + @FXML + private AddressTreeTable receiveTable; + + @FXML + private AddressTreeTable sendTable; + + public void initializeView(WalletForm walletForm) { + payNymWalletForms.setItems(FXCollections.observableList(walletForm.getNestedWalletForms().stream().filter(nested -> nested.getWallet().isBip47()).collect(Collectors.toList()))); + payNymWalletForms.setConverter(new StringConverter<>() { + @Override + public String toString(WalletForm nested) { + return nested == null ? "" : nested.getWallet().getDisplayName(); + } + + @Override + public WalletForm fromString(String string) { + return null; + } + }); + + Optional optInitial = walletForm.getNestedWalletForms().stream().filter(nested -> nested.getWallet().isBip47() && nested.getWallet().getScriptType() == ScriptType.P2WPKH).findFirst(); + if(optInitial.isPresent()) { + optInitial.get().getAccountEntries().clear(); + receiveTable.initialize(optInitial.get().getNodeEntry(KeyPurpose.RECEIVE)); + sendTable.initialize(optInitial.get().getNodeEntry(KeyPurpose.SEND)); + payNymWalletForms.getSelectionModel().select(optInitial.get()); + } + + payNymWalletForms.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, selected) -> { + selected.getAccountEntries().clear(); + receiveTable.updateAll(selected.getNodeEntry(KeyPurpose.RECEIVE)); + sendTable.updateAll(selected.getNodeEntry(KeyPurpose.SEND)); + }); + } + + @Subscribe + public void walletHistoryChanged(WalletHistoryChangedEvent event) { + if(event.getWallet().equals(payNymWalletForms.getValue().getWallet())) { + List receiveNodes = event.getReceiveNodes(); + if(!receiveNodes.isEmpty()) { + receiveTable.updateHistory(receiveNodes); + } + + List sendNodes = event.getChangeNodes(); + if(!sendNodes.isEmpty()) { + sendTable.updateHistory(sendNodes); + } + } + } + + @Subscribe + public void walletEntryLabelChanged(WalletEntryLabelsChangedEvent event) { + if(event.getWallet().equals(payNymWalletForms.getValue().getWallet())) { + for(Entry entry : event.getEntries()) { + receiveTable.updateLabel(entry); + sendTable.updateLabel(entry); + } + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymAddressesDialog.java b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymAddressesDialog.java new file mode 100644 index 00000000..04202c42 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymAddressesDialog.java @@ -0,0 +1,45 @@ +package com.sparrowwallet.sparrow.paynym; + +import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.wallet.WalletForm; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; + +import java.io.IOException; + +public class PayNymAddressesDialog extends Dialog { + public PayNymAddressesDialog(WalletForm walletForm) { + final DialogPane dialogPane = getDialogPane(); + AppServices.setStageIcon(dialogPane.getScene().getWindow()); + AppServices.onEscapePressed(dialogPane.getScene(), this::close); + + try { + FXMLLoader payNymLoader = new FXMLLoader(AppServices.class.getResource("paynym/paynymaddresses.fxml")); + dialogPane.setContent(payNymLoader.load()); + PayNymAddressesController controller = payNymLoader.getController(); + controller.initializeView(walletForm); + + EventManager.get().register(controller); + + final ButtonType doneButtonType = new javafx.scene.control.ButtonType("Done", ButtonBar.ButtonData.OK_DONE); + dialogPane.getButtonTypes().add(doneButtonType); + + setOnCloseRequest(event -> { + EventManager.get().unregister(controller); + }); + + setResultConverter(dialogButton -> dialogButton == doneButtonType ? Boolean.TRUE : Boolean.FALSE); + + dialogPane.setPrefWidth(800); + dialogPane.setPrefHeight(600); + + setResizable(true); + } catch(IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymDialog.java b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymDialog.java index 4d0b5ab6..743e4a04 100644 --- a/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/paynym/PayNymDialog.java @@ -18,7 +18,7 @@ public class PayNymDialog extends Dialog { AppServices.onEscapePressed(dialogPane.getScene(), this::close); try { - FXMLLoader payNymLoader = new FXMLLoader(AppServices.class.getResource("soroban/paynym.fxml")); + FXMLLoader payNymLoader = new FXMLLoader(AppServices.class.getResource("paynym/paynym.fxml")); dialogPane.setContent(payNymLoader.load()); PayNymController payNymController = payNymLoader.getController(); payNymController.initializeView(walletId, selectLinkedOnly); @@ -30,7 +30,7 @@ public class PayNymDialog extends Dialog { AppServices.moveToActiveWindowScreen(this); dialogPane.getStylesheets().add(AppServices.class.getResource("app.css").toExternalForm()); - dialogPane.getStylesheets().add(AppServices.class.getResource("soroban/paynym.css").toExternalForm()); + dialogPane.getStylesheets().add(AppServices.class.getResource("paynym/paynym.css").toExternalForm()); final ButtonType selectButtonType = new javafx.scene.control.ButtonType("Select Contact", ButtonBar.ButtonData.APPLY); final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 8bb2c991..c38fb83a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -1351,7 +1351,7 @@ public class HeadersController extends TransactionFormController implements Init for(BlockTransactionHashIndex output : walletNode.getTransactionOutputs()) { if(output.getHash().equals(txid) && output.getLabel() == null) { //If we send to ourselves, usually change String label = outputIndexLabels.containsKey((int)output.getIndex()) ? outputIndexLabels.get((int)output.getIndex()) : headersForm.getName(); - output.setLabel(label + (walletNode.getKeyPurpose() == KeyPurpose.CHANGE ? " (change)" : " (received)")); + output.setLabel(label + (walletNode.getKeyPurpose() == KeyPurpose.CHANGE ? (walletNode.getWallet().isBip47() ? " (sent)" : " (change)") : " (received)")); changedLabelEntries.add(new HashIndexEntry(event.getWallet(), output, HashIndexEntry.Type.OUTPUT, walletNode.getKeyPurpose())); } if(output.getSpentBy() != null && output.getSpentBy().getHash().equals(txid) && output.getSpentBy().getLabel() == null) { //The norm - sending out diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java index ce274099..80abbbc4 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/AddressesController.java @@ -9,9 +9,11 @@ import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.AddressTreeTable; import com.sparrowwallet.sparrow.event.*; +import com.sparrowwallet.sparrow.paynym.PayNymAddressesDialog; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.scene.control.Button; import javafx.stage.FileChooser; import javafx.stage.Stage; import org.slf4j.Logger; @@ -34,6 +36,9 @@ public class AddressesController extends WalletFormController implements Initial @FXML private AddressTreeTable changeTable; + @FXML + private Button showPayNymAddresses; + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); @@ -43,6 +48,9 @@ public class AddressesController extends WalletFormController implements Initial public void initializeView() { receiveTable.initialize(getWalletForm().getNodeEntry(KeyPurpose.RECEIVE)); changeTable.initialize(getWalletForm().getNodeEntry(KeyPurpose.CHANGE)); + + showPayNymAddresses.managedProperty().bind(showPayNymAddresses.visibleProperty()); + showPayNymAddresses.setVisible(getWalletForm().getWallet().getChildWallets().stream().anyMatch(Wallet::isBip47)); } @Subscribe @@ -110,6 +118,13 @@ public class AddressesController extends WalletFormController implements Initial } } + @Subscribe + public void childWalletsAdded(ChildWalletsAddedEvent event) { + if(event.getWallet().equals(getWalletForm().getWallet())) { + showPayNymAddresses.setVisible(getWalletForm().getWallet().getChildWallets().stream().anyMatch(Wallet::isBip47)); + } + } + public void exportReceiveAddresses(ActionEvent event) { exportAddresses(KeyPurpose.RECEIVE); } @@ -151,4 +166,9 @@ public class AddressesController extends WalletFormController implements Initial } } } + + public void showPayNymAddresses(ActionEvent event) { + PayNymAddressesDialog payNymAddressesDialog = new PayNymAddressesDialog(getWalletForm()); + payNymAddressesDialog.showAndWait(); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 1767ddf4..8ba450d3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -377,6 +377,10 @@ public class WalletForm { this.lockedProperty.set(locked); } + public List getAccountEntries() { + return accountEntries; + } + @Subscribe public void walletDataChanged(WalletDataChangedEvent event) { if(event.getWallet().equals(wallet)) { @@ -471,7 +475,7 @@ public class WalletForm { } if((receivedRef.getLabel() == null || receivedRef.getLabel().isEmpty()) && wallet.getStandardAccountType() != StandardAccount.WHIRLPOOL_PREMIX) { - receivedRef.setLabel(changedNode.getLabel() + (changedNode.getKeyPurpose() == KeyPurpose.CHANGE ? " (change)" : " (received)")); + receivedRef.setLabel(changedNode.getLabel() + (changedNode.getKeyPurpose() == KeyPurpose.CHANGE ? (changedNode.getWallet().isBip47() ? " (sent)" : " (change)") : " (received)")); changedLabelEntries.add(new HashIndexEntry(event.getWallet(), receivedRef, HashIndexEntry.Type.OUTPUT, changedNode.getKeyPurpose())); } } @@ -496,7 +500,7 @@ public class WalletForm { for(BlockTransactionHashIndex receivedRef : childNode.getTransactionOutputs()) { if(receivedRef.getHash().equals(transactionEntry.getBlockTransaction().getHash())) { if((receivedRef.getLabel() == null || receivedRef.getLabel().isEmpty()) && wallet.getStandardAccountType() != StandardAccount.WHIRLPOOL_PREMIX) { - receivedRef.setLabel(entry.getLabel() + (keyPurpose == KeyPurpose.CHANGE ? " (change)" : " (received)")); + receivedRef.setLabel(entry.getLabel() + (keyPurpose == KeyPurpose.CHANGE ? (event.getWallet().isBip47() ? " (sent)" : " (change)") : " (received)")); labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef, HashIndexEntry.Type.OUTPUT, keyPurpose), entry); } if((childNode.getLabel() == null || childNode.getLabel().isEmpty())) { diff --git a/src/main/resources/com/sparrowwallet/sparrow/soroban/paynym.css b/src/main/resources/com/sparrowwallet/sparrow/paynym/paynym.css similarity index 100% rename from src/main/resources/com/sparrowwallet/sparrow/soroban/paynym.css rename to src/main/resources/com/sparrowwallet/sparrow/paynym/paynym.css diff --git a/src/main/resources/com/sparrowwallet/sparrow/soroban/paynym.fxml b/src/main/resources/com/sparrowwallet/sparrow/paynym/paynym.fxml similarity index 100% rename from src/main/resources/com/sparrowwallet/sparrow/soroban/paynym.fxml rename to src/main/resources/com/sparrowwallet/sparrow/paynym/paynym.fxml diff --git a/src/main/resources/com/sparrowwallet/sparrow/paynym/paynymaddresses.fxml b/src/main/resources/com/sparrowwallet/sparrow/paynym/paynymaddresses.fxml new file mode 100644 index 00000000..231e29ba --- /dev/null +++ b/src/main/resources/com/sparrowwallet/sparrow/paynym/paynymaddresses.fxml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + +
+
+ + + + + +
+ +
+
+ + + + + +
+ +
+
+
+
+
+
+
diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.fxml index c9cf57dc..fbf4887b 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.fxml @@ -33,6 +33,15 @@ + +