improve new tx notifications

This commit is contained in:
Craig Raw 2020-08-09 14:13:10 +02:00
parent f0b7409c4a
commit b2f48a1b05
8 changed files with 91 additions and 46 deletions

2
drongo

@ -1 +1 @@
Subproject commit d2582c041479704d609c20ed13195c3f92ced999 Subproject commit fff658a3ab33a3f63f5a1cd03c2b7cc1f20bec4a

View file

@ -44,6 +44,7 @@ import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.util.Duration; import javafx.util.Duration;
import org.controlsfx.control.Notifications; import org.controlsfx.control.Notifications;
import org.controlsfx.control.StatusBar; import org.controlsfx.control.StatusBar;
@ -161,9 +162,6 @@ public class AppController implements Initializable {
rootStack.getStyleClass().removeAll(DRAG_OVER_CLASS); rootStack.getStyleClass().removeAll(DRAG_OVER_CLASS);
}); });
Stage tabStage = (Stage)tabs.getScene().getWindow();
tabStage.getScene().getStylesheets().add(AppController.class.getResource("notificationpopup.css").toExternalForm());
tabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> { tabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> {
if(selectedTab != null) { if(selectedTab != null) {
TabData tabData = (TabData)selectedTab.getUserData(); TabData tabData = (TabData)selectedTab.getUserData();
@ -241,15 +239,6 @@ public class AppController implements Initializable {
} }
openTransactionIdItem.disableProperty().bind(onlineProperty.not()); openTransactionIdItem.disableProperty().bind(onlineProperty.not());
List<File> recentWalletFiles = Config.get().getRecentWalletFiles();
if(recentWalletFiles != null) {
for(File walletFile : recentWalletFiles) {
if(walletFile.exists()) {
openWalletFile(walletFile);
}
}
}
} }
private ElectrumServer.ConnectionService createConnectionService() { private ElectrumServer.ConnectionService createConnectionService() {
@ -618,7 +607,7 @@ public class AppController implements Initializable {
} }
} }
private void openWalletFile(File file) { public void openWalletFile(File file) {
try { try {
Storage storage = new Storage(file); Storage storage = new Storage(file);
FileType fileType = IOUtils.getFileType(file); FileType fileType = IOUtils.getFileType(file);
@ -959,25 +948,46 @@ public class AppController implements Initializable {
@Subscribe @Subscribe
public void newWalletTransactions(NewWalletTransactionsEvent event) { public void newWalletTransactions(NewWalletTransactionsEvent event) {
if(Config.get().isNotifyNewTransactions()) { if(Config.get().isNotifyNewTransactions()) {
String text = "New " + (event.getBlockTransactions().size() > 1 ? "transactions: " : "transaction: "); String text;
if(event.getBlockTransactions().size() == 1) {
BlockTransaction blockTransaction = event.getBlockTransactions().get(0);
if(blockTransaction.getHeight() == 0) {
text = "New mempool transaction: ";
} else {
int confirmations = blockTransaction.getConfirmations(getCurrentBlockHeight());
if(confirmations == 1) {
text = "First transaction confirmation: ";
} else if(confirmations <= BlockTransactionHash.BLOCKS_TO_CONFIRM) {
text = "Confirming transaction: ";
} else {
text = "Confirmed transaction: ";
}
}
BitcoinUnit unit = Config.get().getBitcoinUnit(); text += event.getValueAsText(event.getTotalValue());
if(unit == null || unit.equals(BitcoinUnit.AUTO)) {
unit = (event.getTotalValue() >= BitcoinUnit.getAutoThreshold() ? BitcoinUnit.BTC : BitcoinUnit.SATOSHIS);
}
if(unit == BitcoinUnit.BTC) {
text += CoinLabel.getBTCFormat().format((double)event.getTotalValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC";
} else { } else {
text += String.format(Locale.ENGLISH, "%,d", event.getTotalValue()) + " sats"; if(event.getTotalBlockchainValue() > 0 && event.getTotalMempoolValue() > 0) {
text = "New transactions: " + event.getValueAsText(event.getTotalValue()) + " (" + event.getValueAsText(event.getTotalMempoolValue()) + " in mempool)";
} else if(event.getTotalMempoolValue() > 0) {
text = "New mempool transactions: " + event.getValueAsText(event.getTotalMempoolValue());
} else {
text = "New transactions: " + event.getValueAsText(event.getTotalValue());
}
} }
Window.getWindows().forEach(window -> {
String notificationStyles = AppController.class.getResource("notificationpopup.css").toExternalForm();
if(!window.getScene().getStylesheets().contains(notificationStyles)) {
window.getScene().getStylesheets().add(notificationStyles);
}
});
Image image = new Image("image/sparrow-small.png", 50, 50, false, false); Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
Notifications notificationBuilder = Notifications.create() Notifications notificationBuilder = Notifications.create()
.title("Sparrow - " + event.getWallet().getName()) .title("Sparrow - " + event.getWallet().getName())
.text(text) .text(text)
.graphic(new ImageView(image)) .graphic(new ImageView(image))
.hideAfter(Duration.seconds(180)) .hideAfter(Duration.seconds(5))
.position(Pos.TOP_RIGHT) .position(Pos.TOP_RIGHT)
.threshold(5, Notifications.create().title("Sparrow").text("Multiple new wallet transactions").graphic(new ImageView(image))) .threshold(5, Notifications.create().title("Sparrow").text("Multiple new wallet transactions").graphic(new ImageView(image)))
.onAction(e -> selectTab(event.getWallet())); .onAction(e -> selectTab(event.getWallet()));

View file

@ -7,6 +7,7 @@ import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.preferences.PreferenceGroup; import com.sparrowwallet.sparrow.preferences.PreferenceGroup;
import com.sparrowwallet.sparrow.preferences.PreferencesDialog; import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
@ -14,6 +15,8 @@ import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.controlsfx.glyphfont.GlyphFontRegistry; import org.controlsfx.glyphfont.GlyphFontRegistry;
import java.io.File;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public class MainApp extends Application { public class MainApp extends Application {
@ -60,6 +63,15 @@ public class MainApp extends Application {
appController.initializeView(); appController.initializeView();
stage.show(); stage.show();
List<File> recentWalletFiles = Config.get().getRecentWalletFiles();
if(recentWalletFiles != null) {
for(File walletFile : recentWalletFiles) {
if(walletFile.exists()) {
Platform.runLater(() -> appController.openWalletFile(walletFile));
}
}
}
} }
@Override @Override

View file

@ -1,6 +1,6 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.wallet.TransactionEntry; import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
import javafx.animation.*; import javafx.animation.*;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -54,7 +54,7 @@ public class ConfirmationProgressIndicator extends StackPane {
arcLengthTimeline.getKeyFrames().add(arcLengthFrame); arcLengthTimeline.getKeyFrames().add(arcLengthFrame);
sequence.getChildren().add(arcLengthTimeline); sequence.getChildren().add(arcLengthTimeline);
if(newValue.intValue() == TransactionEntry.BLOCKS_TO_CONFIRM) { if(newValue.intValue() == BlockTransactionHash.BLOCKS_TO_CONFIRM) {
Timeline arcRadiusTimeline = new Timeline(); Timeline arcRadiusTimeline = new Timeline();
KeyValue arcRadiusXValue = new KeyValue(arc.radiusXProperty(), 0.0); KeyValue arcRadiusXValue = new KeyValue(arc.radiusXProperty(), 0.0);
KeyValue arcRadiusYValue = new KeyValue(arc.radiusYProperty(), 0.0); KeyValue arcRadiusYValue = new KeyValue(arc.radiusYProperty(), 0.0);
@ -98,7 +98,7 @@ public class ConfirmationProgressIndicator extends StackPane {
} }
private static double getDegrees(int confirmations) { private static double getDegrees(int confirmations) {
int requiredConfirmations = TransactionEntry.BLOCKS_TO_CONFIRM; int requiredConfirmations = BlockTransactionHash.BLOCKS_TO_CONFIRM;
return ((double)Math.min(confirmations, requiredConfirmations)/ requiredConfirmations) * -360d; return ((double)Math.min(confirmations, requiredConfirmations)/ requiredConfirmations) * -360d;
} }

View file

@ -1,19 +1,26 @@
package com.sparrowwallet.sparrow.event; package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.BitcoinUnit;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.CoinLabel;
import com.sparrowwallet.sparrow.io.Config;
import java.util.List; import java.util.List;
import java.util.Locale;
public class NewWalletTransactionsEvent { public class NewWalletTransactionsEvent {
private final Wallet wallet; private final Wallet wallet;
private final List<BlockTransaction> blockTransactions; private final List<BlockTransaction> blockTransactions;
private final long totalValue; private final long totalBlockchainValue;
private final long totalMempoolValue;
public NewWalletTransactionsEvent(Wallet wallet, List<BlockTransaction> blockTransactions, long totalValue) { public NewWalletTransactionsEvent(Wallet wallet, List<BlockTransaction> blockTransactions, long totalBlockchainValue, long totalMempoolValue) {
this.wallet = wallet; this.wallet = wallet;
this.blockTransactions = blockTransactions; this.blockTransactions = blockTransactions;
this.totalValue = totalValue; this.totalBlockchainValue = totalBlockchainValue;
this.totalMempoolValue = totalMempoolValue;
} }
public Wallet getWallet() { public Wallet getWallet() {
@ -25,6 +32,27 @@ public class NewWalletTransactionsEvent {
} }
public long getTotalValue() { public long getTotalValue() {
return totalValue; return totalBlockchainValue + totalMempoolValue;
}
public long getTotalBlockchainValue() {
return totalBlockchainValue;
}
public long getTotalMempoolValue() {
return totalMempoolValue;
}
public String getValueAsText(long value) {
BitcoinUnit unit = Config.get().getBitcoinUnit();
if(unit == null || unit.equals(BitcoinUnit.AUTO)) {
unit = (value >= BitcoinUnit.getAutoThreshold() ? BitcoinUnit.BTC : BitcoinUnit.SATOSHIS);
}
if(unit == BitcoinUnit.BTC) {
return CoinLabel.getBTCFormat().format((double) value / Transaction.SATOSHIS_PER_BITCOIN) + " BTC";
}
return String.format(Locale.ENGLISH, "%,d", value) + " sats";
} }
} }

View file

@ -449,7 +449,7 @@ public class HeadersController extends TransactionFormController implements Init
blockStatus.setText(confirmations + " Confirmations"); blockStatus.setText(confirmations + " Confirmations");
} }
if(confirmations <= TransactionEntry.BLOCKS_TO_CONFIRM) { if(confirmations <= BlockTransactionHash.BLOCKS_TO_CONFIRM) {
ConfirmationProgressIndicator indicator; ConfirmationProgressIndicator indicator;
if(blockStatus.getGraphic() == null) { if(blockStatus.getGraphic() == null) {
indicator = new ConfirmationProgressIndicator(confirmations); indicator = new ConfirmationProgressIndicator(confirmations);

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.wallet;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.BlockTransaction;
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
@ -17,9 +18,6 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class TransactionEntry extends Entry implements Comparable<TransactionEntry> { public class TransactionEntry extends Entry implements Comparable<TransactionEntry> {
public static final int BLOCKS_TO_CONFIRM = 6;
public static final int BLOCKS_TO_FULLY_CONFIRM = 100;
private final Wallet wallet; private final Wallet wallet;
private final BlockTransaction blockTransaction; private final BlockTransaction blockTransaction;
@ -63,29 +61,25 @@ public class TransactionEntry extends Entry implements Comparable<TransactionEnt
} }
public boolean isConfirming() { public boolean isConfirming() {
return getConfirmations() < BLOCKS_TO_CONFIRM; return getConfirmations() < BlockTransactionHash.BLOCKS_TO_CONFIRM;
} }
public boolean isFullyConfirming() { public boolean isFullyConfirming() {
return getConfirmations() < BLOCKS_TO_FULLY_CONFIRM; return getConfirmations() < BlockTransactionHash.BLOCKS_TO_FULLY_CONFIRM;
} }
public int calculateConfirmations() { public int calculateConfirmations() {
if(blockTransaction.getHeight() <= 0) { return blockTransaction.getConfirmations(wallet.getStoredBlockHeight());
return 0;
}
return wallet.getStoredBlockHeight() - blockTransaction.getHeight() + 1;
} }
public String getConfirmationsDescription() { public String getConfirmationsDescription() {
int confirmations = getConfirmations(); int confirmations = getConfirmations();
if(confirmations == 0) { if(confirmations == 0) {
return "Unconfirmed in mempool"; return "Unconfirmed in mempool";
} else if(confirmations < BLOCKS_TO_FULLY_CONFIRM) { } else if(confirmations < BlockTransactionHash.BLOCKS_TO_FULLY_CONFIRM) {
return confirmations + " confirmation" + (confirmations == 1 ? "" : "s"); return confirmations + " confirmation" + (confirmations == 1 ? "" : "s");
} else { } else {
return BLOCKS_TO_FULLY_CONFIRM + "+ confirmations"; return BlockTransactionHash.BLOCKS_TO_FULLY_CONFIRM + "+ confirmations";
} }
} }

View file

@ -69,8 +69,9 @@ public class WalletTransactionsEntry extends Entry {
if(!entriesAdded.isEmpty()) { if(!entriesAdded.isEmpty()) {
List<BlockTransaction> blockTransactions = entriesAdded.stream().map(txEntry -> ((TransactionEntry)txEntry).getBlockTransaction()).collect(Collectors.toList()); List<BlockTransaction> blockTransactions = entriesAdded.stream().map(txEntry -> ((TransactionEntry)txEntry).getBlockTransaction()).collect(Collectors.toList());
long totalValue = entriesAdded.stream().mapToLong(Entry::getValue).sum(); long totalBlockchainValue = entriesAdded.stream().filter(txEntry -> ((TransactionEntry)txEntry).getConfirmations() > 0).mapToLong(Entry::getValue).sum();
EventManager.get().post(new NewWalletTransactionsEvent(wallet, blockTransactions, totalValue)); long totalMempoolValue = entriesAdded.stream().filter(txEntry -> ((TransactionEntry)txEntry).getConfirmations() == 0).mapToLong(Entry::getValue).sum();
EventManager.get().post(new NewWalletTransactionsEvent(wallet, blockTransactions, totalBlockchainValue, totalMempoolValue));
} }
} }