add tab label icons, show loading and failure status there

This commit is contained in:
Craig Raw 2021-03-16 12:07:55 +02:00
parent 8b7d1e6888
commit 4078c61d6b
19 changed files with 241 additions and 39 deletions

View file

@ -21,6 +21,7 @@ import com.sparrowwallet.drongo.psbt.PSBTParseException;
import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.net.ServerType;
@ -53,6 +54,8 @@ import javafx.stage.*;
import javafx.util.Duration;
import org.controlsfx.control.Notifications;
import org.controlsfx.control.StatusBar;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -70,6 +73,8 @@ public class AppController implements Initializable {
private static final Logger log = LoggerFactory.getLogger(AppController.class);
public static final String DRAG_OVER_CLASS = "drag-over";
public static final double TAB_LABEL_GRAPHIC_OPACITY_INACTIVE = 0.8;
public static final double TAB_LABEL_GRAPHIC_OPACITY_ACTIVE = 0.95;
@FXML
private MenuItem saveTransaction;
@ -163,7 +168,11 @@ public class AppController implements Initializable {
});
tabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> {
tabs.getTabs().forEach(tab -> ((Label)tab.getGraphic()).getGraphic().setOpacity(TAB_LABEL_GRAPHIC_OPACITY_INACTIVE));
if(selectedTab != null) {
Label tabLabel = (Label)selectedTab.getGraphic();
tabLabel.getGraphic().setOpacity(TAB_LABEL_GRAPHIC_OPACITY_ACTIVE);
TabData tabData = (TabData)selectedTab.getUserData();
if(tabData.getType() == TabData.TabType.TRANSACTION) {
EventManager.get().post(new TransactionTabSelectedEvent(selectedTab));
@ -920,7 +929,14 @@ public class AppController implements Initializable {
if(!name.equals(wallet.getName())) {
wallet.setName(name);
}
Tab tab = new Tab(name);
Tab tab = new Tab("");
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
glyph.setFontSize(9.0);
glyph.setOpacity(TAB_LABEL_GRAPHIC_OPACITY_ACTIVE);
Label tabLabel = new Label(name);
tabLabel.setGraphic(glyph);
tabLabel.setGraphicTextGap(5.0);
tab.setGraphic(tabLabel);
tab.setContextMenu(getTabContextMenu(tab));
tab.setClosable(true);
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
@ -1057,7 +1073,14 @@ public class AppController implements Initializable {
tabName = "[" + transaction.getTxId().toString().substring(0, 6) + "]";
}
Tab tab = new Tab(tabName);
Tab tab = new Tab("");
Glyph glyph = new Glyph("FontAwesome", FontAwesome.Glyph.SEND);
glyph.setFontSize(9.0);
glyph.setOpacity(TAB_LABEL_GRAPHIC_OPACITY_ACTIVE);
Label tabLabel = new Label(tabName);
tabLabel.setGraphic(glyph);
tabLabel.setGraphicTextGap(5.0);
tab.setGraphic(tabLabel);
tab.setContextMenu(getTabContextMenu(tab));
tab.setClosable(true);
FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("transaction/transaction.fxml"));
@ -1138,6 +1161,57 @@ public class AppController implements Initializable {
EventManager.get().post(new ThemeChangedEvent(selectedTheme));
}
private void tabLabelStartAnimation(Wallet wallet) {
tabs.getTabs().stream().filter(tab -> tab.getUserData() instanceof WalletTabData && ((WalletTabData)tab.getUserData()).getWallet() == wallet).forEach(this::tabLabelStartAnimation);
}
private void tabLabelStartAnimation(Transaction transaction) {
tabs.getTabs().stream().filter(tab -> tab.getUserData() instanceof TransactionTabData && ((TransactionTabData)tab.getUserData()).getTransaction().getTxId().equals(transaction.getTxId())).forEach(this::tabLabelStartAnimation);
}
private void tabLabelStartAnimation(Tab tab) {
Label tabLabel = (Label) tab.getGraphic();
if(tabLabel.getUserData() == null) {
FadeTransition fadeTransition = new FadeTransition(Duration.millis(1000), tabLabel.getGraphic());
fadeTransition.setFromValue(tabLabel.getGraphic().getOpacity());
fadeTransition.setToValue(0.1);
fadeTransition.setAutoReverse(true);
fadeTransition.setCycleCount(Animation.INDEFINITE);
fadeTransition.play();
tabLabel.setUserData(fadeTransition);
}
}
private void tabLabelAddFailure(Tab tab) {
Label tabLabel = (Label)tab.getGraphic();
if(!tabLabel.getStyleClass().contains("failure")) {
tabLabel.getGraphic().getStyleClass().add("failure");
}
}
private void tabLabelStopAnimation(Wallet wallet) {
tabs.getTabs().stream().filter(tab -> tab.getUserData() instanceof WalletTabData && ((WalletTabData)tab.getUserData()).getWallet() == wallet).forEach(this::tabLabelStopAnimation);
}
private void tabLabelStopAnimation(Transaction transaction) {
tabs.getTabs().stream().filter(tab -> tab.getUserData() instanceof TransactionTabData && ((TransactionTabData)tab.getUserData()).getTransaction().getTxId().equals(transaction.getTxId())).forEach(this::tabLabelStopAnimation);
}
private void tabLabelStopAnimation(Tab tab) {
Label tabLabel = (Label) tab.getGraphic();
if(tabLabel.getUserData() != null) {
FadeTransition fadeTransition = (FadeTransition)tabLabel.getUserData();
fadeTransition.stop();
tabLabel.setUserData(null);
tabLabel.getGraphic().setOpacity(tab.isSelected() ? TAB_LABEL_GRAPHIC_OPACITY_ACTIVE : TAB_LABEL_GRAPHIC_OPACITY_INACTIVE);
}
}
private void tabLabelRemoveFailure(Tab tab) {
Label tabLabel = (Label)tab.getGraphic();
tabLabel.getGraphic().getStyleClass().remove("failure");
}
@Subscribe
public void themeChanged(ThemeChangedEvent event) {
String darkCss = getClass().getResource("darktheme.css").toExternalForm();
@ -1384,15 +1458,27 @@ public class AppController implements Initializable {
}
}
@Subscribe
public void transactionReferences(TransactionReferencesEvent event) {
if(AppServices.isConnected() && event instanceof TransactionReferencesStartedEvent) {
tabLabelStartAnimation(event.getTransaction());
} else {
tabLabelStopAnimation(event.getTransaction());
}
}
@Subscribe
public void walletHistoryStarted(WalletHistoryStartedEvent event) {
if(AppServices.isConnected() && getOpenWallets().containsKey(event.getWallet())) {
if(event.getWalletNode() == null && event.getWallet().getTransactions().isEmpty()) {
statusUpdated(new StatusEvent("Loading transactions...", 120));
if(statusTimeline == null || statusTimeline.getStatus() != Animation.Status.RUNNING) {
statusBar.setProgress(-1);
loadingWallets.add(event.getWallet());
}
}
tabLabelStartAnimation(event.getWallet());
}
}
@Subscribe
@ -1404,10 +1490,18 @@ public class AppController implements Initializable {
if(statusTimeline == null || statusTimeline.getStatus() != Animation.Status.RUNNING) {
statusBar.setProgress(0);
}
tabLabelStopAnimation(event.getWallet());
loadingWallets.remove(event.getWallet());
tabs.getTabs().stream().filter(tab -> tab.getUserData() instanceof WalletTabData && ((WalletTabData)tab.getUserData()).getWallet() == event.getWallet()).forEach(this::tabLabelRemoveFailure);
}
}
@Subscribe
public void walletHistoryFailed(WalletHistoryFailedEvent event) {
walletHistoryFinished(new WalletHistoryFinishedEvent(event.getWallet()));
tabs.getTabs().stream().filter(tab -> tab.getUserData() instanceof WalletTabData && ((WalletTabData) tab.getUserData()).getWallet() == event.getWallet()).forEach(this::tabLabelAddFailure);
}
@Subscribe
public void bwtBootStatus(BwtBootStatusEvent event) {
serverToggle.setDisable(true);

View file

@ -1,23 +1,18 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import java.util.Map;
public class BlockTransactionFetchedEvent extends PagedEvent {
public class BlockTransactionFetchedEvent extends TransactionReferencesFinishedEvent {
private final Sha256Hash txId;
private final BlockTransaction blockTransaction;
private final Map<Sha256Hash, BlockTransaction> inputTransactions;
public BlockTransactionFetchedEvent(Sha256Hash txId, BlockTransaction blockTransaction, Map<Sha256Hash, BlockTransaction> inputTransactions) {
this(txId, blockTransaction, inputTransactions, 0, blockTransaction.getTransaction().getInputs().size());
}
public BlockTransactionFetchedEvent(Sha256Hash txId, BlockTransaction blockTransaction, Map<Sha256Hash, BlockTransaction> inputTransactions, int pageStart, int pageEnd) {
super(pageStart, pageEnd);
this.txId = txId;
this.blockTransaction = blockTransaction;
public BlockTransactionFetchedEvent(Transaction transaction, BlockTransaction blockTransaction, Map<Sha256Hash, BlockTransaction> inputTransactions, int pageStart, int pageEnd) {
super(transaction, blockTransaction, pageStart, pageEnd);
this.txId = transaction.getTxId();
this.inputTransactions = inputTransactions;
}
@ -25,10 +20,6 @@ public class BlockTransactionFetchedEvent extends PagedEvent {
return txId;
}
public BlockTransaction getBlockTransaction() {
return blockTransaction;
}
public Map<Sha256Hash, BlockTransaction> getInputTransactions() {
return inputTransactions;
}

View file

@ -1,17 +1,18 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
import java.util.List;
public class BlockTransactionOutputsFetchedEvent extends PagedEvent {
public class BlockTransactionOutputsFetchedEvent extends TransactionReferencesEvent {
private final Sha256Hash txId;
private final List<BlockTransaction> outputTransactions;
public BlockTransactionOutputsFetchedEvent(Sha256Hash txId, List<BlockTransaction> outputTransactions, int pageStart, int pageEnd) {
super(pageStart, pageEnd);
this.txId = txId;
public BlockTransactionOutputsFetchedEvent(Transaction transaction, List<BlockTransaction> outputTransactions, int pageStart, int pageEnd) {
super(transaction, pageStart, pageEnd);
this.txId = transaction.getTxId();
this.outputTransactions = outputTransactions;
}

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.TabData;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
public class TabEvent {
@ -15,7 +16,7 @@ public class TabEvent {
}
public String getTabName() {
return tab.getText();
return ((Label)tab.getGraphic()).getText();
}
public TabData getTabData() {

View file

@ -0,0 +1,20 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Transaction;
public class TransactionReferencesEvent extends PagedEvent {
private final Transaction transaction;
public TransactionReferencesEvent(Transaction transaction) {
this(transaction, 0, 0);
}
public TransactionReferencesEvent(Transaction transaction, int pageStart, int pageEnd) {
super(pageStart, pageEnd);
this.transaction = transaction;
}
public Transaction getTransaction() {
return transaction;
}
}

View file

@ -0,0 +1,21 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Transaction;
public class TransactionReferencesFailedEvent extends TransactionReferencesEvent {
private final Throwable exception;
public TransactionReferencesFailedEvent(Transaction transaction, Throwable exception) {
super(transaction);
this.exception = exception;
}
public TransactionReferencesFailedEvent(Transaction transaction, Throwable exception, int pageStart, int pageEnd) {
super(transaction, pageStart, pageEnd);
this.exception = exception;
}
public Throwable getException() {
return exception;
}
}

View file

@ -0,0 +1,22 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.BlockTransaction;
public class TransactionReferencesFinishedEvent extends TransactionReferencesEvent {
private final BlockTransaction blockTransaction;
public TransactionReferencesFinishedEvent(Transaction transaction, BlockTransaction blockTransaction) {
super(transaction);
this.blockTransaction = blockTransaction;
}
public TransactionReferencesFinishedEvent(Transaction transaction, BlockTransaction blockTransaction, int pageStart, int pageEnd) {
super(transaction, pageStart, pageEnd);
this.blockTransaction = blockTransaction;
}
public BlockTransaction getBlockTransaction() {
return blockTransaction;
}
}

View file

@ -0,0 +1,13 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.protocol.Transaction;
public class TransactionReferencesStartedEvent extends TransactionReferencesEvent {
public TransactionReferencesStartedEvent(Transaction transaction) {
super(transaction);
}
public TransactionReferencesStartedEvent(Transaction transaction, int pageStart, int pageEnd) {
super(transaction, pageStart, pageEnd);
}
}

View file

@ -0,0 +1,16 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.Wallet;
public class WalletHistoryFailedEvent extends WalletHistoryStatusEvent {
private final Throwable exception;
public WalletHistoryFailedEvent(Wallet wallet, Throwable exception) {
super(wallet, exception.getCause() == null ? exception.getMessage() : exception.getCause().getMessage());
this.exception = exception;
}
public Throwable getException() {
return exception;
}
}

View file

@ -6,8 +6,4 @@ public class WalletHistoryFinishedEvent extends WalletHistoryStatusEvent {
public WalletHistoryFinishedEvent(Wallet wallet) {
super(wallet, false);
}
public WalletHistoryFinishedEvent(Wallet wallet, String errorMessage) {
super(wallet, errorMessage);
}
}

View file

@ -1,9 +1,17 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode;
public class WalletHistoryStartedEvent extends WalletHistoryStatusEvent {
public WalletHistoryStartedEvent(Wallet wallet) {
private final WalletNode walletNode;
public WalletHistoryStartedEvent(Wallet wallet, WalletNode walletNode) {
super(wallet, true);
this.walletNode = walletNode;
}
public WalletNode getWalletNode() {
return walletNode;
}
}

View file

@ -944,7 +944,7 @@ public class ElectrumServer {
});
disconnectionService.start();
} else if(subscribe) {
EventManager.get().post(new DisconnectionEvent());
Platform.runLater(() -> EventManager.get().post(new DisconnectionEvent()));
}
}

View file

@ -145,6 +145,9 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
String blockHeader = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
client.createRequest().returnAs(String.class).method("blockchain.block.header").id(idCounter.incrementAndGet()).params(blockHeight).execute());
result.put(blockHeight, blockHeader);
} catch(ServerException e) {
//If there is an error with the server connection, don't keep trying - this may take too long given many blocks
throw new ElectrumServerRpcException("Failed to retrieve block header for block height: " + blockHeight, e);
} catch(JsonRpcException e) {
log.warn("Failed to retrieve block header for block height: " + blockHeight + " (" + e.getErrorMessage() + ")");
} catch(Exception e) {

View file

@ -846,10 +846,13 @@ public class HeadersController extends TransactionFormController implements Init
headersForm.setBlockTransaction(blockTransaction);
updateBlockchainForm(blockTransaction, AppServices.getCurrentBlockHeight());
}
EventManager.get().post(new TransactionReferencesFinishedEvent(headersForm.getTransaction(), blockTransaction));
});
transactionReferenceService.setOnFailed(failedEvent -> {
log.error("Error fetching broadcasted transaction", failedEvent.getSource().getException());
EventManager.get().post(new TransactionReferencesFailedEvent(headersForm.getTransaction(), failedEvent.getSource().getException()));
});
EventManager.get().post(new TransactionReferencesStartedEvent(headersForm.getTransaction()));
transactionReferenceService.start();
}
});
@ -1132,10 +1135,13 @@ public class HeadersController extends TransactionFormController implements Init
headersForm.setBlockTransaction(blockTransaction);
updateBlockchainForm(blockTransaction, AppServices.getCurrentBlockHeight());
}
EventManager.get().post(new TransactionReferencesFinishedEvent(headersForm.getTransaction(), blockTransaction));
});
transactionReferenceService.setOnFailed(failEvent -> {
log.error("Could not update block transaction", failEvent.getSource().getException());
EventManager.get().post(new TransactionReferencesFailedEvent(headersForm.getTransaction(), failEvent.getSource().getException()));
});
EventManager.get().post(new TransactionReferencesStartedEvent(headersForm.getTransaction()));
transactionReferenceService.start();
}
}

View file

@ -374,12 +374,14 @@ public class TransactionController implements Initializable {
final BlockTransaction blockTx = thisBlockTx;
Platform.runLater(() -> {
EventManager.get().post(new BlockTransactionFetchedEvent(getTransaction().getTxId(), blockTx, inputTransactions, indexStart, maxIndex));
EventManager.get().post(new BlockTransactionFetchedEvent(getTransaction(), blockTx, inputTransactions, indexStart, maxIndex));
});
});
transactionReferenceService.setOnFailed(failedEvent -> {
log.error("Error fetching transaction or input references", failedEvent.getSource().getException());
EventManager.get().post(new TransactionReferencesFailedEvent(getTransaction(), failedEvent.getSource().getException(), indexStart, maxIndex));
});
EventManager.get().post(new TransactionReferencesStartedEvent(getTransaction(), indexStart, maxIndex));
transactionReferenceService.start();
}
}
@ -391,12 +393,14 @@ public class TransactionController implements Initializable {
transactionOutputsReferenceService.setOnSucceeded(successEvent -> {
List<BlockTransaction> outputTransactions = transactionOutputsReferenceService.getValue();
Platform.runLater(() -> {
EventManager.get().post(new BlockTransactionOutputsFetchedEvent(getTransaction().getTxId(), outputTransactions, indexStart, maxIndex));
EventManager.get().post(new BlockTransactionOutputsFetchedEvent(getTransaction(), outputTransactions, indexStart, maxIndex));
});
});
transactionOutputsReferenceService.setOnFailed(failedEvent -> {
log.error("Error fetching transaction output references", failedEvent.getSource().getException());
EventManager.get().post(new TransactionReferencesFailedEvent(getTransaction(), failedEvent.getSource().getException(), indexStart, maxIndex));
});
EventManager.get().post(new TransactionReferencesStartedEvent(getTransaction(), indexStart, maxIndex));
transactionOutputsReferenceService.start();
}
}

View file

@ -105,14 +105,10 @@ public class WalletForm {
});
historyService.setOnFailed(workerStateEvent -> {
log.error("Error retrieving wallet history", workerStateEvent.getSource().getException());
EventManager.get().post(new WalletHistoryFinishedEvent(wallet, workerStateEvent.getSource().getException().getMessage()));
EventManager.get().post(new WalletHistoryFailedEvent(wallet, workerStateEvent.getSource().getException()));
});
if(node == null && wallet.getTransactions().isEmpty()) {
EventManager.get().post(new WalletHistoryStartedEvent(wallet));
} else {
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true));
}
EventManager.get().post(new WalletHistoryStartedEvent(wallet, node));
historyService.start();
}
}

View file

@ -20,6 +20,10 @@
-fx-fill: #383a42;
}
.tab-label .failure {
-fx-text-fill: rgb(202, 18, 67);
}
.status-bar .status-label {
-fx-alignment: center-left;
}

View file

@ -82,6 +82,10 @@
-fx-bar-fill: rgba(135, 138, 149, 0.5);
}
.tab-label .failure {
-fx-fill: #e06c75;
}
.root .titled-description-pane .status-error .text, .root .titled-description-pane .description-error .text {
-fx-fill: #e06c75;
}

View file

@ -5,6 +5,8 @@
<logger name="com.github.sarxos.webcam.ds.cgt.WebcamOpenTask" level="OFF"/>
<logger name="com.github.sarxos.webcam.ds.cgt.WebcamCloseTask" level="OFF"/>
<logger name="javafx.css" level="ERROR"/>
<logger name="javafx.scene.focus" level="INFO"/>
<logger name="sun.net.www.protocol.http.HttpURLConnection" level="INFO" />
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>