diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java index a63cb53f..71ec1212 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java @@ -49,6 +49,30 @@ public class TransactionDiagram extends GridPane { } } + public void update(String message) { + setMinHeight(getDiagramHeight()); + setMaxHeight(getDiagramHeight()); + + getChildren().clear(); + + VBox messagePane = new VBox(); + messagePane.setPrefHeight(getDiagramHeight()); + messagePane.setPadding(new Insets(0, 10, 0, 280)); + messagePane.setAlignment(Pos.CENTER); + messagePane.getChildren().add(createSpacer()); + + Label messageLabel = new Label(message); + messagePane.getChildren().add(messageLabel); + messagePane.getChildren().add(createSpacer()); + + GridPane.setConstraints(messagePane, 3, 0); + getChildren().add(messagePane); + } + + public void clear() { + getChildren().clear(); + } + public void update() { Map displayedUtxos = getDisplayedUtxos(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java index c1d76064..5f60e206 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/PaymentController.java @@ -235,8 +235,10 @@ public class PaymentController extends WalletFormController implements Initializ private void revalidate(TextField field, ChangeListener listener) { field.textProperty().removeListener(listener); String amt = field.getText(); + int caret = field.getCaretPosition(); field.setText(amt + "0"); field.setText(amt); + field.positionCaret(caret); field.textProperty().addListener(listener); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index e1f2dad3..f222924f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -13,15 +13,16 @@ import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.io.Config; -import com.sparrowwallet.sparrow.net.ExchangeSource; -import com.sparrowwallet.sparrow.net.ElectrumServer; -import com.sparrowwallet.sparrow.net.FeeRatesSource; -import com.sparrowwallet.sparrow.net.MempoolRateSize; +import com.sparrowwallet.sparrow.net.*; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.application.Platform; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; +import javafx.concurrent.Service; +import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -29,6 +30,7 @@ import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.layout.StackPane; +import javafx.util.Duration; import javafx.util.StringConverter; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.validation.ValidationResult; @@ -187,6 +189,8 @@ public class SendController extends WalletFormController implements Initializabl private ValidationSupport validationSupport; + private WalletTransactionService walletTransactionService; + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); @@ -484,19 +488,39 @@ public class SendController extends WalletFormController implements Initializabl boolean groupByAddress = Config.get().isGroupByAddress(); boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs(); boolean includeSpentMempoolOutputs = includeSpentMempoolOutputsProperty.get(); - WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), getUtxoFilters(), payments, feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs); - walletTransactionProperty.setValue(walletTransaction); - insufficientInputsProperty.set(false); - return; + if(walletTransactionService != null && walletTransactionService.isRunning()) { + walletTransactionService.cancel(); + } + + walletTransactionService = new WalletTransactionService(wallet, getUtxoSelectors(), getUtxoFilters(), payments, feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs); + walletTransactionService.setOnSucceeded(event -> { + walletTransactionProperty.setValue(walletTransactionService.getValue()); + insufficientInputsProperty.set(false); + }); + walletTransactionService.setOnFailed(event -> { + transactionDiagram.clear(); + walletTransactionProperty.setValue(null); + if(event.getSource().getException() instanceof InsufficientFundsException) { + insufficientInputsProperty.set(true); + } + }); + + final WalletTransactionService currentWalletTransactionService = walletTransactionService; + final KeyFrame delay = new KeyFrame(Duration.millis(200), e -> { + if(currentWalletTransactionService.isRunning()) { + transactionDiagram.update("Selecting UTXOs..."); + createButton.setDisable(true); + } + }); + final Timeline timeline = new Timeline(delay); + timeline.play(); + + walletTransactionService.start(); } - } catch(InvalidAddressException | IllegalStateException | IllegalArgumentException e) { - //ignore - } catch(InsufficientFundsException e) { - insufficientInputsProperty.set(true); + } catch(InvalidAddressException | IllegalStateException e) { + walletTransactionProperty.setValue(null); } - - walletTransactionProperty.setValue(null); } private List getUtxoSelectors() throws InvalidAddressException { @@ -511,6 +535,43 @@ public class SendController extends WalletFormController implements Initializabl return List.of(new BnBUtxoSelector(noInputsFee, costOfChange), new KnapsackUtxoSelector(noInputsFee)); } + private static class WalletTransactionService extends Service { + private final Wallet wallet; + private final List utxoSelectors; + private final List utxoFilters; + private final List payments; + private final double feeRate; + private final double longTermFeeRate; + private final Long fee; + private final Integer currentBlockHeight; + private final boolean groupByAddress; + private final boolean includeMempoolOutputs; + private final boolean includeSpentMempoolOutputs; + + public WalletTransactionService(Wallet wallet, List utxoSelectors, List utxoFilters, List payments, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs, boolean includeSpentMempoolOutputs) { + this.wallet = wallet; + this.utxoSelectors = utxoSelectors; + this.utxoFilters = utxoFilters; + this.payments = payments; + this.feeRate = feeRate; + this.longTermFeeRate = longTermFeeRate; + this.fee = fee; + this.currentBlockHeight = currentBlockHeight; + this.groupByAddress = groupByAddress; + this.includeMempoolOutputs = includeMempoolOutputs; + this.includeSpentMempoolOutputs = includeSpentMempoolOutputs; + } + + @Override + protected Task createTask() { + return new Task<>() { + protected WalletTransaction call() throws InsufficientFundsException { + return wallet.createWalletTransaction(utxoSelectors, utxoFilters, payments, feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs); + } + }; + } + } + private List getUtxoFilters() { UtxoFilter utxoFilter = utxoFilterProperty.get(); if(utxoFilter != null) { @@ -872,8 +933,10 @@ public class SendController extends WalletFormController implements Initializabl private void revalidate(TextField field, ChangeListener listener) { field.textProperty().removeListener(listener); String amt = field.getText(); + int caret = field.getCaretPosition(); field.setText(amt + "0"); field.setText(amt); + field.positionCaret(caret); field.textProperty().addListener(listener); }