mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
improve new tx notifications
This commit is contained in:
parent
f0b7409c4a
commit
b2f48a1b05
8 changed files with 91 additions and 46 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit d2582c041479704d609c20ed13195c3f92ced999
|
Subproject commit fff658a3ab33a3f63f5a1cd03c2b7cc1f20bec4a
|
|
@ -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()));
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue