diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java index e644dedd..ab18cfb7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java @@ -14,7 +14,7 @@ import java.util.Map; public class QRCodeDialog extends DialogWindow { public QRCodeDialog(String data) throws WriterException { - super(data); + super(data.length() > 70 ? data.substring(0, 70) + "..." : data); setHints(List.of(Hint.CENTERED)); diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java index acbe9745..07c1ad12 100644 --- a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java @@ -4,6 +4,7 @@ import com.google.common.eventbus.Subscribe; import com.googlecode.lanterna.gui2.*; import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyPurpose; +import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.WalletNode; @@ -38,19 +39,28 @@ public class ReceiveDialog extends WalletDialog { Panel mainPanel = new Panel(new GridLayout(2).setHorizontalSpacing(2).setVerticalSpacing(1).setTopMarginSize(1)); + boolean isSilentPayments = walletForm.getWallet().getPolicyType() == PolicyType.SINGLE_SP; + mainPanel.addComponent(new Label("Address")); address = new Label("").addTo(mainPanel); - mainPanel.addComponent(new Label("Derivation")); - derivation = new Label("").addTo(mainPanel); + if(!isSilentPayments) { + mainPanel.addComponent(new Label("Derivation")); + derivation = new Label("").addTo(mainPanel); - mainPanel.addComponent(new Label("Last Used")); - lastUsed = new Label("").addTo(mainPanel); + mainPanel.addComponent(new Label("Last Used")); + lastUsed = new Label("").addTo(mainPanel); + } else { + derivation = new Label(""); + lastUsed = new Label(""); + } Panel buttonPanel = new Panel(); buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); buttonPanel.addComponent(new Button("Back", () -> onBack(Function.RECEIVE))); - buttonPanel.addComponent(new Button("Get Fresh Address", this::refreshAddress).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); + if(!isSilentPayments) { + buttonPanel.addComponent(new Button("Get Fresh Address", this::refreshAddress).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); + } mainPanel.addComponent(new Button("Show QR", this::showQR)); @@ -61,12 +71,17 @@ public class ReceiveDialog extends WalletDialog { } public void showQR() { - if(currentEntry == null) { - return; - } - try { - QRCodeDialog qrCodeDialog = new QRCodeDialog(currentEntry.getAddress().toString()); + String qrAddress; + if(getWalletForm().getWallet().getPolicyType() == PolicyType.SINGLE_SP) { + qrAddress = getWalletForm().getWallet().getKeystores().getFirst().getSilentPaymentScanAddress().getAddress(); + } else if(currentEntry != null) { + qrAddress = currentEntry.getAddress().toString(); + } else { + return; + } + + QRCodeDialog qrCodeDialog = new QRCodeDialog(qrAddress); qrCodeDialog.showDialog(SparrowTerminal.get().getGui()); } catch(Exception e) { log.error("Error creating QR", e); @@ -76,6 +91,12 @@ public class ReceiveDialog extends WalletDialog { public void refreshAddress() { SparrowTerminal.get().getGuiThread().invokeLater(() -> { + if(getWalletForm().getWallet().getPolicyType() == PolicyType.SINGLE_SP) { + String silentPaymentAddress = getWalletForm().getWallet().getKeystores().getFirst().getSilentPaymentScanAddress().getAddress(); + address.setText(silentPaymentAddress); + return; + } + NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry); setNodeEntry(freshEntry); }); @@ -109,6 +130,10 @@ public class ReceiveDialog extends WalletDialog { } private void updateLastUsed() { + if(currentEntry == null) { + return; + } + SparrowTerminal.get().getGuiThread().invokeLater(() -> { Set currentOutputs = currentEntry.getNode().getTransactionOutputs(); if(AppServices.onlineProperty().get() && currentOutputs.isEmpty()) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java index 0e901a8c..5f10f51f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java @@ -9,6 +9,7 @@ import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.OutputDescriptor; +import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.KeystoreSource; @@ -20,6 +21,8 @@ import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.io.Device; import com.sparrowwallet.sparrow.io.Hwi; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -27,9 +30,14 @@ import javafx.fxml.Initializable; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.AnchorPane; +import javafx.util.Duration; import org.controlsfx.glyphfont.Glyph; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import tornadofx.control.Form; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -44,6 +52,9 @@ public class ReceiveController extends WalletFormController implements Initializ private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + @FXML + private Form receiveForm; + @FXML private CopyableTextField address; @@ -56,18 +67,45 @@ public class ReceiveController extends WalletFormController implements Initializ @FXML private Label lastUsed; + @FXML + private AnchorPane qrCodePane; + @FXML private ImageView qrCode; + @FXML + private Form spReceiveForm; + + @FXML + private AnchorPane spCopyPane; + + @FXML + private TextArea spAddress; + + @FXML + private Form scriptPubKeyForm; + @FXML private ScriptArea scriptPubKeyArea; + @FXML + private Form outputDescriptorForm; + @FXML private SelectableCodeArea outputDescriptor; + @FXML + private Form spQrForm; + + @FXML + private ImageView spQrCode; + @FXML private Button displayAddress; + @FXML + private Button nextAddress; + private NodeEntry currentEntry; private QRDisplayDialog addressQrDialog; @@ -80,12 +118,39 @@ public class ReceiveController extends WalletFormController implements Initializ @Override public void initializeView() { address.setSkin(new AddressTextFieldSkin(address)); + receiveForm.managedProperty().bind(receiveForm.visibleProperty()); + qrCodePane.managedProperty().bind(qrCodePane.visibleProperty()); + spReceiveForm.managedProperty().bind(spReceiveForm.visibleProperty()); + spCopyPane.managedProperty().bind(spCopyPane.visibleProperty()); + scriptPubKeyForm.managedProperty().bind(scriptPubKeyForm.visibleProperty()); + outputDescriptorForm.managedProperty().bind(outputDescriptorForm.visibleProperty()); + spQrForm.managedProperty().bind(spQrForm.visibleProperty()); + + qrCodePane.visibleProperty().bind(receiveForm.visibleProperty()); + spReceiveForm.visibleProperty().bind(receiveForm.visibleProperty().not()); + spCopyPane.visibleProperty().bind(receiveForm.visibleProperty().not()); + scriptPubKeyForm.visibleProperty().bind(receiveForm.visibleProperty()); + outputDescriptorForm.visibleProperty().bind(receiveForm.visibleProperty()); + spQrForm.visibleProperty().bind(receiveForm.visibleProperty().not()); + + updateFromWalletPolicy(); + initializeScriptField(scriptPubKeyArea); displayAddress.managedProperty().bind(displayAddress.visibleProperty()); displayAddress.setVisible(false); - qrCode.setOnMouseClicked(event -> { + nextAddress.managedProperty().bind(nextAddress.visibleProperty()); + + spAddress.setOnMouseClicked(event -> { + copySilentPaymentsAddress(null); + Tooltip tooltip = new Tooltip("Copied!"); + tooltip.show(spAddress, event.getScreenX(), event.getScreenY()); + Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> tooltip.hide())); + timeline.play(); + }); + + qrCode.setOnMouseClicked(_ -> { if(currentEntry != null && addressQrDialog == null) { addressQrDialog = new QRDisplayDialog(currentEntry.getAddress().toString()); addressQrDialog.initOwner(address.getScene().getWindow()); @@ -94,9 +159,16 @@ public class ReceiveController extends WalletFormController implements Initializ } }); + spQrCode.setOnMouseClicked(_ -> copySilentPaymentsAddress(null)); + refreshAddress(); } + public void updateFromWalletPolicy() { + receiveForm.setVisible(walletForm.getWallet().getPolicyType() != PolicyType.SINGLE_SP); + nextAddress.setVisible(walletForm.getWallet().getPolicyType() != PolicyType.SINGLE_SP); + } + public void setNodeEntry(NodeEntry nodeEntry) { if(currentEntry != null) { label.textProperty().unbindBidirectional(currentEntry.labelProperty()); @@ -128,6 +200,10 @@ public class ReceiveController extends WalletFormController implements Initializ } private void updateLastUsed() { + if(currentEntry == null) { + return; + } + Set currentOutputs = currentEntry.getNode().getTransactionOutputs(); if(AppServices.isConnected() && currentOutputs.isEmpty()) { lastUsed.setText("Never"); @@ -192,6 +268,31 @@ public class ReceiveController extends WalletFormController implements Initializ return null; } + public void copySilentPaymentsAddress(ActionEvent actionEvent) { + if(walletForm.getWallet().getPolicyType() == PolicyType.SINGLE_SP) { + ClipboardContent content = new ClipboardContent(); + content.putString(walletForm.getWallet().getKeystores().getFirst().getSilentPaymentScanAddress().getAddress()); + Clipboard.getSystemClipboard().setContent(content); + } + } + + private Image getSilentPaymentsQrCode(String address) { + try { + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix qrMatrix = qrCodeWriter.encode(address, BarcodeFormat.QR_CODE, 400, 400, Map.of(EncodeHintType.MARGIN, 2)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + MatrixToImageWriter.writeToStream(qrMatrix, "PNG", baos, new MatrixToImageConfig()); + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + return new Image(bais); + } catch(Exception e) { + log.error("Error generating QR", e); + } + + return null; + } + public void getNewAddress(ActionEvent event) { refreshAddress(); if(currentEntry != null) { @@ -200,6 +301,16 @@ public class ReceiveController extends WalletFormController implements Initializ } public void refreshAddress() { + if(walletForm.getWallet().getPolicyType() == PolicyType.SINGLE_SP) { + String silentPaymentAddress = walletForm.getWallet().getKeystores().getFirst().getSilentPaymentScanAddress().getAddress(); + spAddress.setText(silentPaymentAddress); + Image qrImage = getSilentPaymentsQrCode(silentPaymentAddress); + if(qrImage != null) { + spQrCode.setImage(qrImage); + } + return; + } + NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry); while(freshEntry.getLabel() != null && !freshEntry.getLabel().isEmpty()) { freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, freshEntry); @@ -315,6 +426,7 @@ public class ReceiveController extends WalletFormController implements Initializ @Subscribe public void walletAddressesChanged(WalletAddressesChangedEvent event) { + updateFromWalletPolicy(); displayAddress.setUserData(null); } diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.css b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.css index a9edfa92..b9efb46c 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.css +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.css @@ -16,6 +16,14 @@ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 10, 0, 0, 0); } +#spQrCode { + -fx-cursor: default; +} + +#spQrCode:hover { + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0); +} + #lastUsedField .input-container, #derivationField .input-container { -fx-alignment: center-left; } \ No newline at end of file diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml index d311c0d6..c37b3aa9 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml @@ -32,7 +32,7 @@ -
+
@@ -40,22 +40,44 @@ - + - +
- + +
+ + + +
+ +