From dfe1f16495ab04c029761ec5018cb6bacdd36706 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 28 Feb 2023 13:19:24 +0200 Subject: [PATCH] configure a block explorer url, and open a txid in the configured block explorer --- drongo | 2 +- .../sparrowwallet/sparrow/AppServices.java | 14 +++ .../sparrow/control/EntryCell.java | 15 ++- .../sparrow/glyphfont/FontAwesome5.java | 1 + .../com/sparrowwallet/sparrow/io/Config.java | 10 ++ .../sparrow/net/BlockExplorer.java | 18 ++++ .../GeneralPreferencesController.java | 102 +++++++++++++++--- .../transaction/HeadersController.java | 15 +++ .../sparrow/preferences/general.fxml | 17 +-- .../sparrow/transaction/headers.fxml | 25 ++++- 10 files changed, 185 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/net/BlockExplorer.java diff --git a/drongo b/drongo index d0da764a..0f78efc3 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit d0da764aadfc9edc2a7fb35540eca0ee4c20ba0f +Subproject commit 0f78efc373e99cf48a749ced25cd112ca083b88f diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 3e9c2340..58b348df 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -811,6 +811,20 @@ public class AppServices { } } + public static void openBlockExplorer(String txid) { + Server blockExplorer = Config.get().getBlockExplorer() == null ? BlockExplorer.MEMPOOL_SPACE.getServer() : Config.get().getBlockExplorer(); + String url = blockExplorer.getUrl(); + if(url.contains("{0}")) { + url = url.replace("{0}", txid); + } else { + if(Network.get() != Network.MAINNET) { + url += "/" + Network.get().getName(); + } + url += "/tx/" + txid; + } + AppServices.get().getApplication().getHostServices().showDocument(url); + } + static void parseFileUriArguments(List fileUriArguments) { for(String fileUri : fileUriArguments) { try { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java index f25a2c01..2e0e6842 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/EntryCell.java @@ -537,6 +537,13 @@ public class EntryCell extends TreeTableCell implements Confirmati getItems().add(createCpfp); } + MenuItem openBlockExplorer = new MenuItem("Open in Block Explorer"); + openBlockExplorer.setOnAction(AE -> { + hide(); + AppServices.openBlockExplorer(blockTransaction.getHashAsString()); + }); + getItems().add(openBlockExplorer); + MenuItem copyTxid = new MenuItem("Copy Transaction ID"); copyTxid.setOnAction(AE -> { hide(); @@ -558,6 +565,12 @@ public class EntryCell extends TreeTableCell implements Confirmati EventManager.get().post(new ViewTransactionEvent(this.getOwnerWindow(), blockTransaction)); }); + MenuItem openBlockExplorer = new MenuItem("Open in Block Explorer"); + openBlockExplorer.setOnAction(AE -> { + hide(); + AppServices.openBlockExplorer(blockTransaction.getHashAsString()); + }); + MenuItem copyDate = new MenuItem("Copy Date"); copyDate.setOnAction(AE -> { hide(); @@ -582,7 +595,7 @@ public class EntryCell extends TreeTableCell implements Confirmati Clipboard.getSystemClipboard().setContent(content); }); - getItems().addAll(viewTransaction, copyDate, copyTxid, copyHeight); + getItems().addAll(viewTransaction, openBlockExplorer, copyDate, copyTxid, copyHeight); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 76410f75..ae33d192 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -76,6 +76,7 @@ public class FontAwesome5 extends GlyphFont { TOGGLE_OFF('\uf204'), TOGGLE_ON('\uf205'), TOOLS('\uf7d9'), + UP_RIGHT_FROM_SQUARE('\uf35d'), UNDO('\uf0e2'), USER('\uf007'), USER_FRIENDS('\uf500'), diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index 766d17eb..b545972f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -30,6 +30,7 @@ public class Config { private Mode mode; private BitcoinUnit bitcoinUnit; private UnitFormat unitFormat; + private Server blockExplorer; private FeeRatesSource feeRatesSource; private FeeRatesSelection feeRatesSelection; private OptimizationStrategy sendOptimizationStrategy; @@ -150,6 +151,15 @@ public class Config { flush(); } + public Server getBlockExplorer() { + return blockExplorer; + } + + public void setBlockExplorer(Server blockExplorer) { + this.blockExplorer = blockExplorer; + flush(); + } + public FeeRatesSource getFeeRatesSource() { return feeRatesSource; } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/BlockExplorer.java b/src/main/java/com/sparrowwallet/sparrow/net/BlockExplorer.java new file mode 100644 index 00000000..4fe263bc --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/BlockExplorer.java @@ -0,0 +1,18 @@ +package com.sparrowwallet.sparrow.net; + +import com.sparrowwallet.sparrow.io.Server; + +public enum BlockExplorer { + MEMPOOL_SPACE("https://mempool.space"), + BLOCKSTREAM_INFO("https://blockstream.info"); + + private final Server server; + + BlockExplorer(String url) { + this.server = new Server(url); + } + + public Server getServer() { + return server; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java b/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java index e4593cd6..c6109a0d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/GeneralPreferencesController.java @@ -1,32 +1,44 @@ package com.sparrowwallet.sparrow.preferences; -import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.Config; +import com.sparrowwallet.sparrow.io.Server; +import com.sparrowwallet.sparrow.net.BlockExplorer; import com.sparrowwallet.sparrow.net.ExchangeSource; import com.sparrowwallet.sparrow.net.FeeRatesSource; +import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; +import javafx.scene.control.TextInputDialog; +import javafx.util.StringConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; import java.util.Currency; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; public class GeneralPreferencesController extends PreferencesDetailController { private static final Logger log = LoggerFactory.getLogger(GeneralPreferencesController.class); - @FXML - private ComboBox bitcoinUnit; + private static final Server CUSTOM_BLOCK_EXPLORER = new Server("http://custom.block.explorer"); @FXML private ComboBox feeRatesSource; + @FXML + private ComboBox blockExplorers; + @FXML private ComboBox fiatCurrency; @@ -63,17 +75,6 @@ public class GeneralPreferencesController extends PreferencesDetailController { @Override public void initializeView(Config config) { - if(config.getBitcoinUnit() != null) { - bitcoinUnit.setValue(config.getBitcoinUnit()); - } else { - bitcoinUnit.setValue(BitcoinUnit.AUTO); - } - - bitcoinUnit.valueProperty().addListener((observable, oldValue, newValue) -> { - config.setBitcoinUnit(newValue); - EventManager.get().post(new BitcoinUnitChangedEvent(newValue)); - }); - if(config.getFeeRatesSource() != null) { feeRatesSource.setValue(config.getFeeRatesSource()); } else { @@ -86,6 +87,62 @@ public class GeneralPreferencesController extends PreferencesDetailController { EventManager.get().post(new FeeRatesSourceChangedEvent(newValue)); }); + blockExplorers.setItems(getBlockExplorerList()); + blockExplorers.setConverter(new StringConverter<>() { + @Override + public String toString(Server server) { + if(server == null) { + return "None Available"; + } + + if(server == CUSTOM_BLOCK_EXPLORER) { + return "Custom..."; + } + + return server.getHost(); + } + + @Override + public Server fromString(String string) { + return null; + } + }); + blockExplorers.valueProperty().addListener((observable, oldValue, newValue) -> { + if(newValue != null) { + if(newValue == CUSTOM_BLOCK_EXPLORER) { + TextInputDialog textInputDialog = new TextInputDialog(); + textInputDialog.setTitle("Enter Block Explorer URL"); + textInputDialog.setHeaderText("Enter the URL of the block explorer.\n\nIf present, the characters {0} will be replaced with the txid.\nFor example, https://localhost or https://localhost/tx/{0}\n"); + textInputDialog.getEditor().setPromptText("https://localhost"); + Optional optUrl = textInputDialog.showAndWait(); + if(optUrl.isPresent() && !optUrl.get().isEmpty()) { + try { + Server server = getBlockExplorer(optUrl.get()); + config.setBlockExplorer(server); + Platform.runLater(() -> { + blockExplorers.getSelectionModel().select(-1); + blockExplorers.setItems(getBlockExplorerList()); + blockExplorers.setValue(Config.get().getBlockExplorer()); + }); + } catch(Exception e) { + AppServices.showErrorDialog("Invalid URL", "The URL " + optUrl.get() + " is not valid."); + blockExplorers.setValue(oldValue); + } + } else { + blockExplorers.setValue(oldValue); + } + } else { + Config.get().setBlockExplorer(newValue); + } + } + }); + + if(config.getBlockExplorer() != null) { + blockExplorers.setValue(config.getBlockExplorer()); + } else { + blockExplorers.getSelectionModel().select(0); + } + if(config.getExchangeSource() != null) { exchangeSource.setValue(config.getExchangeSource()); } else { @@ -134,6 +191,23 @@ public class GeneralPreferencesController extends PreferencesDetailController { }); } + private static Server getBlockExplorer(String serverUrl) { + String url = serverUrl.trim(); + if(url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + return new Server(url); + } + + private ObservableList getBlockExplorerList() { + List servers = Arrays.stream(BlockExplorer.values()).map(BlockExplorer::getServer).collect(Collectors.toList()); + if(Config.get().getBlockExplorer() != null && !servers.contains(Config.get().getBlockExplorer())) { + servers.add(Config.get().getBlockExplorer()); + } + servers.add(CUSTOM_BLOCK_EXPLORER); + return FXCollections.observableList(servers); + } + private void updateCurrencies(ExchangeSource exchangeSource) { ExchangeSource.CurrenciesService currenciesService = new ExchangeSource.CurrenciesService(exchangeSource); currenciesService.setOnSucceeded(event -> { diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 7e2a5d03..52ae5a00 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -78,6 +78,12 @@ public class HeadersController extends TransactionFormController implements Init @FXML private IdLabel id; + @FXML + private ToggleButton copyTxid; + + @FXML + private ToggleButton openBlockExplorer; + @FXML private TransactionDiagram transactionDiagram; @@ -806,11 +812,13 @@ public class HeadersController extends TransactionFormController implements Init addStyleClass(size, UNFINALIZED_TXID_CLASS); addStyleClass(virtualSize, UNFINALIZED_TXID_CLASS); addStyleClass(feeRate, UNFINALIZED_TXID_CLASS); + openBlockExplorer.setDisable(true); } else { id.getStyleClass().remove(UNFINALIZED_TXID_CLASS); size.getStyleClass().remove(UNFINALIZED_TXID_CLASS); virtualSize.getStyleClass().remove(UNFINALIZED_TXID_CLASS); feeRate.getStyleClass().remove(UNFINALIZED_TXID_CLASS); + openBlockExplorer.setDisable(false); } } @@ -821,11 +829,18 @@ public class HeadersController extends TransactionFormController implements Init } public void copyId(ActionEvent event) { + copyTxid.setSelected(false); ClipboardContent content = new ClipboardContent(); content.putString(headersForm.getTransaction().calculateTxId(false).toString()); Clipboard.getSystemClipboard().setContent(content); } + public void openInBlockExplorer(ActionEvent event) { + openBlockExplorer.setSelected(false); + String txid = headersForm.getTransaction().calculateTxId(false).toString(); + AppServices.openBlockExplorer(txid); + } + public void setLocktimeToCurrentHeight(ActionEvent event) { if(AppServices.getCurrentBlockHeight() != null && locktimeBlock.isEditable()) { locktimeBlock.getValueFactory().setValue(AppServices.getCurrentBlockHeight()); diff --git a/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml b/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml index b008a55d..d837e883 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/preferences/general.fxml @@ -11,7 +11,6 @@ - @@ -30,18 +29,6 @@
- - - - - - - - - - - - @@ -54,6 +41,10 @@ + + + +
diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml index 1dc7c5e7..c74cbda4 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml @@ -45,11 +45,26 @@
- + + + + + + + + + + + + + + + + + + + +