add search all open wallets functionality, include matches on transaction output addresses

This commit is contained in:
Craig Raw 2023-11-07 16:38:33 +02:00
parent ea3e0ca34a
commit e6eea67c4b
3 changed files with 72 additions and 24 deletions

View file

@ -1516,6 +1516,31 @@ public class AppController implements Initializable {
TabPane subTabs = (TabPane) selectedTab.getContent(); TabPane subTabs = (TabPane) selectedTab.getContent();
List<WalletForm> walletForms = subTabs.getTabs().stream().map(subTab -> ((WalletTabData)subTab.getUserData()).getWalletForm()).collect(Collectors.toList()); List<WalletForm> walletForms = subTabs.getTabs().stream().map(subTab -> ((WalletTabData)subTab.getUserData()).getWalletForm()).collect(Collectors.toList());
if(!walletForms.isEmpty()) { if(!walletForms.isEmpty()) {
searchWallets(walletForms);
}
}
}
}
public void searchAllWallets(ActionEvent event) {
List<WalletForm> allWalletForms = new ArrayList<>();
for(Tab tab : tabs.getTabs()) {
TabData tabData = (TabData)tab.getUserData();
if(tabData instanceof WalletTabData) {
TabPane subTabs = (TabPane)tab.getContent();
allWalletForms.addAll(subTabs.getTabs().stream().map(subTab -> ((WalletTabData)subTab.getUserData()).getWalletForm())
.filter(walletForm -> walletForm.getWallet().isValid() && !walletForm.isLocked()).collect(Collectors.toList()));
}
}
if(allWalletForms.isEmpty()) {
showErrorDialog("No wallets", "There are no open and unlocked wallets to search.");
} else {
searchWallets(allWalletForms);
}
}
private void searchWallets(List<WalletForm> walletForms) {
SearchWalletDialog searchWalletDialog = new SearchWalletDialog(walletForms); SearchWalletDialog searchWalletDialog = new SearchWalletDialog(walletForms);
searchWalletDialog.initOwner(rootStack.getScene().getWindow()); searchWalletDialog.initOwner(rootStack.getScene().getWindow());
Optional<Entry> optEntry = searchWalletDialog.showAndWait(); Optional<Entry> optEntry = searchWalletDialog.showAndWait();
@ -1525,9 +1550,6 @@ public class AppController implements Initializable {
Platform.runLater(() -> EventManager.get().post(new SelectEntryEvent(entry))); Platform.runLater(() -> EventManager.get().post(new SelectEntryEvent(entry)));
} }
} }
}
}
}
public void showWalletSummary(ActionEvent event) { public void showWalletSummary(ActionEvent event) {
Tab selectedTab = tabs.getSelectionModel().getSelectedItem(); Tab selectedTab = tabs.getSelectionModel().getSelectedItem();

View file

@ -1,6 +1,8 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.InvalidAddressException;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.wallet.*; import com.sparrowwallet.sparrow.wallet.*;
@ -21,6 +23,7 @@ import tornadofx.control.Form;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
public class SearchWalletDialog extends Dialog<Entry> { public class SearchWalletDialog extends Dialog<Entry> {
private static final Logger log = LoggerFactory.getLogger(SearchWalletDialog.class); private static final Logger log = LoggerFactory.getLogger(SearchWalletDialog.class);
@ -36,13 +39,16 @@ public class SearchWalletDialog extends Dialog<Entry> {
throw new IllegalArgumentException("No wallets selected to search"); throw new IllegalArgumentException("No wallets selected to search");
} }
boolean showWallet = walletForms.stream().map(WalletForm::getMasterWallet).distinct().limit(2).count() > 1;
boolean showAccount = walletForms.stream().anyMatch(walletForm -> !walletForm.getWallet().isMasterWallet() || !walletForm.getNestedWalletForms().isEmpty());
final DialogPane dialogPane = getDialogPane(); final DialogPane dialogPane = getDialogPane();
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm()); dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
dialogPane.getStylesheets().add(AppServices.class.getResource("dialog.css").toExternalForm()); dialogPane.getStylesheets().add(AppServices.class.getResource("dialog.css").toExternalForm());
dialogPane.getStylesheets().add(AppServices.class.getResource("wallet/wallet.css").toExternalForm()); dialogPane.getStylesheets().add(AppServices.class.getResource("wallet/wallet.css").toExternalForm());
dialogPane.getStylesheets().add(AppServices.class.getResource("search.css").toExternalForm()); dialogPane.getStylesheets().add(AppServices.class.getResource("search.css").toExternalForm());
AppServices.setStageIcon(dialogPane.getScene().getWindow()); AppServices.setStageIcon(dialogPane.getScene().getWindow());
dialogPane.setHeaderText("Search Wallet"); dialogPane.setHeaderText(showWallet ? "Search All Wallets" : "Search Wallet");
Image image = new Image("image/sparrow-small.png", 50, 50, false, false); Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
if(!image.isError()) { if(!image.isError()) {
@ -69,11 +75,9 @@ public class SearchWalletDialog extends Dialog<Entry> {
fieldset.getChildren().addAll(searchField); fieldset.getChildren().addAll(searchField);
form.getChildren().add(fieldset); form.getChildren().add(fieldset);
boolean showWallet = walletForms.size() > 1 || walletForms.stream().anyMatch(walletForm -> !walletForm.getNestedWalletForms().isEmpty());
results = new CoinTreeTable(); results = new CoinTreeTable();
results.setShowRoot(false); results.setShowRoot(false);
results.setPrefWidth(showWallet ? 950 : 850); results.setPrefWidth(showWallet || showAccount ? 950 : 850);
results.setUnitFormat(walletForms.iterator().next().getWallet()); results.setUnitFormat(walletForms.iterator().next().getWallet());
results.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY); results.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
results.setPlaceholder(new Label("No results")); results.setPlaceholder(new Label("No results"));
@ -81,11 +85,19 @@ public class SearchWalletDialog extends Dialog<Entry> {
if(showWallet) { if(showWallet) {
TreeTableColumn<Entry, String> walletColumn = new TreeTableColumn<>("Wallet"); TreeTableColumn<Entry, String> walletColumn = new TreeTableColumn<>("Wallet");
walletColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> { walletColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getWallet().getDisplayName()); return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getWallet().getMasterName());
}); });
results.getColumns().add(walletColumn); results.getColumns().add(walletColumn);
} }
if(showAccount) {
TreeTableColumn<Entry, String> accountColumn = new TreeTableColumn<>("Account");
accountColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getWallet().getDisplayName());
});
results.getColumns().add(accountColumn);
}
TreeTableColumn<Entry, String> typeColumn = new TreeTableColumn<>("Type"); TreeTableColumn<Entry, String> typeColumn = new TreeTableColumn<>("Type");
typeColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> { typeColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getEntryType()); return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getEntryType());
@ -135,7 +147,7 @@ public class SearchWalletDialog extends Dialog<Entry> {
}); });
search.textProperty().addListener((observable, oldValue, newValue) -> { search.textProperty().addListener((observable, oldValue, newValue) -> {
searchWallet(newValue.toLowerCase(Locale.ROOT)); searchWallets(newValue.toLowerCase(Locale.ROOT));
}); });
setResizable(true); setResizable(true);
@ -145,17 +157,12 @@ public class SearchWalletDialog extends Dialog<Entry> {
Platform.runLater(search::requestFocus); Platform.runLater(search::requestFocus);
} }
private void searchWallet(String searchText) { private void searchWallets(String searchText) {
List<Entry> matchingEntries = new ArrayList<>(); List<Entry> matchingEntries = new ArrayList<>();
if(!searchText.isEmpty()) { if(!searchText.isEmpty()) {
Long searchValue = null; Long searchValue = getSearchValue(searchText);
Address searchAddress = getSearchAddress(searchText);
try {
searchValue = Math.abs(Long.parseLong(searchText));
} catch(NumberFormatException e) {
//ignore
}
for(WalletForm walletForm : walletForms) { for(WalletForm walletForm : walletForms) {
WalletTransactionsEntry walletTransactionsEntry = walletForm.getWalletTransactionsEntry(); WalletTransactionsEntry walletTransactionsEntry = walletForm.getWalletTransactionsEntry();
@ -163,7 +170,8 @@ public class SearchWalletDialog extends Dialog<Entry> {
if(entry instanceof TransactionEntry transactionEntry) { if(entry instanceof TransactionEntry transactionEntry) {
if(transactionEntry.getBlockTransaction().getHash().toString().equals(searchText) || if(transactionEntry.getBlockTransaction().getHash().toString().equals(searchText) ||
(transactionEntry.getLabel() != null && transactionEntry.getLabel().toLowerCase(Locale.ROOT).contains(searchText)) || (transactionEntry.getLabel() != null && transactionEntry.getLabel().toLowerCase(Locale.ROOT).contains(searchText)) ||
(transactionEntry.getValue() != null && searchValue != null && Math.abs(transactionEntry.getValue()) == searchValue)) { (transactionEntry.getValue() != null && searchValue != null && Math.abs(transactionEntry.getValue()) == searchValue) ||
(searchAddress != null && transactionEntry.getBlockTransaction().getTransaction().getOutputs().stream().map(output -> output.getScript().getToAddress()).filter(Objects::nonNull).anyMatch(address -> address.equals(searchAddress)))) {
matchingEntries.add(entry); matchingEntries.add(entry);
} }
} }
@ -215,6 +223,22 @@ public class SearchWalletDialog extends Dialog<Entry> {
results.setRoot(rootItem); results.setRoot(rootItem);
} }
private Long getSearchValue(String searchText) {
try {
return Math.abs(Long.parseLong(searchText));
} catch(NumberFormatException e) {
return null;
}
}
private Address getSearchAddress(String searchText) {
try {
return Address.fromString(searchText);
} catch(InvalidAddressException e) {
return null;
}
}
private static class SearchWalletEntry extends Entry { private static class SearchWalletEntry extends Entry {
public SearchWalletEntry(Wallet wallet, List<Entry> entries) { public SearchWalletEntry(Wallet wallet, List<Entry> entries) {
super(wallet, wallet.getName(), entries); super(wallet, wallet.getName(), entries);

View file

@ -124,8 +124,10 @@
<MenuItem fx:id="lockWallet" mnemonicParsing="false" text="Lock Wallet" accelerator="Shortcut+L" onAction="#lockWallet"/> <MenuItem fx:id="lockWallet" mnemonicParsing="false" text="Lock Wallet" accelerator="Shortcut+L" onAction="#lockWallet"/>
<MenuItem fx:id="lockAllWallets" mnemonicParsing="false" text="Lock All Wallets" accelerator="Shortcut+Shift+L" onAction="#lockWallets"/> <MenuItem fx:id="lockAllWallets" mnemonicParsing="false" text="Lock All Wallets" accelerator="Shortcut+Shift+L" onAction="#lockWallets"/>
<SeparatorMenuItem /> <SeparatorMenuItem />
<MenuItem fx:id="showWalletSummary" mnemonicParsing="false" text="Show Wallet Summary" onAction="#showWalletSummary"/>
<MenuItem fx:id="searchWallet" mnemonicParsing="false" text="Search Wallet" accelerator="Shortcut+Shift+S" onAction="#searchWallet"/> <MenuItem fx:id="searchWallet" mnemonicParsing="false" text="Search Wallet" accelerator="Shortcut+Shift+S" onAction="#searchWallet"/>
<MenuItem fx:id="searchAllWallets" mnemonicParsing="false" text="Search All Wallets" accelerator="Shortcut+Shift+Alt+S" onAction="#searchAllWallets"/>
<SeparatorMenuItem />
<MenuItem fx:id="showWalletSummary" mnemonicParsing="false" text="Show Wallet Summary" onAction="#showWalletSummary"/>
<MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/> <MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
</items> </items>
</Menu> </Menu>