mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-09 08:14:43 +00:00
634 lines
26 KiB
Java
634 lines
26 KiB
Java
package com.sparrowwallet.sparrow.wallet;
|
|
|
|
import com.google.common.eventbus.Subscribe;
|
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
|
import com.sparrowwallet.drongo.KeyPurpose;
|
|
import com.sparrowwallet.drongo.address.Address;
|
|
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
|
import com.sparrowwallet.drongo.address.P2PKHAddress;
|
|
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException;
|
|
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
|
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
|
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
|
import com.sparrowwallet.drongo.wallet.*;
|
|
import com.sparrowwallet.sparrow.AppServices;
|
|
import com.sparrowwallet.sparrow.CurrencyRate;
|
|
import com.sparrowwallet.sparrow.EventManager;
|
|
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.paynym.PayNym;
|
|
import com.sparrowwallet.sparrow.paynym.PayNymAddress;
|
|
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
|
import com.sparrowwallet.sparrow.soroban.*;
|
|
import javafx.application.Platform;
|
|
import javafx.beans.property.BooleanProperty;
|
|
import javafx.beans.property.ObjectProperty;
|
|
import javafx.beans.property.SimpleBooleanProperty;
|
|
import javafx.beans.property.SimpleObjectProperty;
|
|
import javafx.beans.value.ChangeListener;
|
|
import javafx.beans.value.ObservableValue;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.collections.ListChangeListener;
|
|
import javafx.event.ActionEvent;
|
|
import javafx.fxml.FXML;
|
|
import javafx.fxml.Initializable;
|
|
import javafx.scene.control.*;
|
|
import org.controlsfx.glyphfont.Glyph;
|
|
import org.controlsfx.validation.ValidationResult;
|
|
import org.controlsfx.validation.ValidationSupport;
|
|
import org.controlsfx.validation.Validator;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.net.URL;
|
|
import java.text.DecimalFormat;
|
|
import java.text.DecimalFormatSymbols;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
|
|
|
public class PaymentController extends WalletFormController implements Initializable {
|
|
private static final Logger log = LoggerFactory.getLogger(PaymentController.class);
|
|
|
|
public static final long MINIMUM_P2PKH_OUTPUT_SATS = 546L;
|
|
|
|
private SendController sendController;
|
|
|
|
private ValidationSupport validationSupport;
|
|
|
|
@FXML
|
|
private ComboBox<Wallet> openWallets;
|
|
|
|
@FXML
|
|
private ComboBoxTextField address;
|
|
|
|
@FXML
|
|
private TextField label;
|
|
|
|
@FXML
|
|
private TextField amount;
|
|
|
|
@FXML
|
|
private ComboBox<BitcoinUnit> amountUnit;
|
|
|
|
@FXML
|
|
private FiatLabel fiatAmount;
|
|
|
|
@FXML
|
|
private Label amountStatus;
|
|
|
|
@FXML
|
|
private Label dustStatus;
|
|
|
|
@FXML
|
|
private ToggleButton maxButton;
|
|
|
|
@FXML
|
|
private Button scanQrButton;
|
|
|
|
@FXML
|
|
private Button addPaymentButton;
|
|
|
|
private final BooleanProperty emptyAmountProperty = new SimpleBooleanProperty(true);
|
|
|
|
private final BooleanProperty dustAmountProperty = new SimpleBooleanProperty();
|
|
|
|
private final ChangeListener<String> amountListener = new ChangeListener<>() {
|
|
@Override
|
|
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
|
|
if(sendController.getUtxoSelector() instanceof MaxUtxoSelector) {
|
|
sendController.utxoSelectorProperty().setValue(null);
|
|
}
|
|
|
|
for(Tab tab : sendController.getPaymentTabs().getTabs()) {
|
|
PaymentController controller = (PaymentController) tab.getUserData();
|
|
controller.setSendMax(false);
|
|
}
|
|
|
|
Long recipientValueSats = getRecipientValueSats();
|
|
if(recipientValueSats != null) {
|
|
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), recipientValueSats);
|
|
dustAmountProperty.set(recipientValueSats < getRecipientDustThreshold());
|
|
emptyAmountProperty.set(false);
|
|
} else {
|
|
fiatAmount.setText("");
|
|
dustAmountProperty.set(false);
|
|
emptyAmountProperty.set(true);
|
|
}
|
|
|
|
updateMixOnlyStatus();
|
|
|
|
sendController.updateTransaction();
|
|
}
|
|
};
|
|
|
|
private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>();
|
|
|
|
private static final Wallet payNymWallet = new Wallet() {
|
|
@Override
|
|
public String getFullDisplayName() {
|
|
return "PayNym or Payment code...";
|
|
}
|
|
};
|
|
|
|
@Override
|
|
public void initialize(URL location, ResourceBundle resources) {
|
|
EventManager.get().register(this);
|
|
}
|
|
|
|
public void setSendController(SendController sendController) {
|
|
this.sendController = sendController;
|
|
this.validationSupport = sendController.getValidationSupport();
|
|
}
|
|
|
|
@Override
|
|
public void initializeView() {
|
|
updateOpenWallets();
|
|
openWallets.prefWidthProperty().bind(address.widthProperty());
|
|
openWallets.valueProperty().addListener((observable, oldValue, newValue) -> {
|
|
if(newValue == payNymWallet) {
|
|
boolean selectLinkedOnly = sendController.getPaymentTabs().getTabs().size() > 1 || !SorobanServices.canWalletMix(sendController.getWalletForm().getWallet());
|
|
PayNymDialog payNymDialog = new PayNymDialog(sendController.getWalletForm().getWalletId(), PayNymDialog.Operation.SEND, selectLinkedOnly);
|
|
Optional<PayNym> optPayNym = payNymDialog.showAndWait();
|
|
optPayNym.ifPresent(this::setPayNym);
|
|
} else if(newValue != null) {
|
|
WalletNode freshNode = newValue.getFreshNode(KeyPurpose.RECEIVE);
|
|
Address freshAddress = freshNode.getAddress();
|
|
address.setText(freshAddress.toString());
|
|
label.requestFocus();
|
|
}
|
|
});
|
|
openWallets.setCellFactory(c -> new ListCell<>() {
|
|
@Override
|
|
protected void updateItem(Wallet wallet, boolean empty) {
|
|
super.updateItem(wallet, empty);
|
|
if(empty || wallet == null) {
|
|
setText(null);
|
|
setGraphic(null);
|
|
} else {
|
|
setText(wallet.getFullDisplayName() + (wallet == sendController.getWalletForm().getWallet() ? " (Consolidation)" : ""));
|
|
setGraphic(wallet == payNymWallet ? getPayNymGlyph() : null);
|
|
}
|
|
}
|
|
});
|
|
|
|
payNymProperty.addListener((observable, oldValue, payNym) -> {
|
|
updateMixOnlyStatus(payNym);
|
|
revalidateAmount();
|
|
});
|
|
|
|
address.textProperty().addListener((observable, oldValue, newValue) -> {
|
|
address.leftProperty().set(null);
|
|
|
|
if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) {
|
|
payNymProperty.set(null);
|
|
}
|
|
|
|
try {
|
|
BitcoinURI bitcoinURI = new BitcoinURI(newValue);
|
|
Platform.runLater(() -> updateFromURI(bitcoinURI));
|
|
return;
|
|
} catch(Exception e) {
|
|
//ignore, not a URI
|
|
}
|
|
|
|
if(sendController.getWalletForm().getWallet().hasPaymentCode()) {
|
|
try {
|
|
PaymentCode paymentCode = new PaymentCode(newValue);
|
|
Wallet recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, sendController.getWalletForm().getWallet().getScriptType());
|
|
if(recipientBip47Wallet == null && sendController.getWalletForm().getWallet().getScriptType() != ScriptType.P2PKH) {
|
|
recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, ScriptType.P2PKH);
|
|
}
|
|
|
|
if(recipientBip47Wallet != null) {
|
|
PayNym payNym = PayNym.fromWallet(recipientBip47Wallet);
|
|
Platform.runLater(() -> setPayNym(payNym));
|
|
} else if(!paymentCode.equals(sendController.getWalletForm().getWallet().getPaymentCode())) {
|
|
ButtonType previewType = new ButtonType("Preview Transaction", ButtonBar.ButtonData.YES);
|
|
Optional<ButtonType> optButton = AppServices.showAlertDialog("Send notification transaction?", "This payment code is not yet linked with a notification transaction. Send a notification transaction?", Alert.AlertType.CONFIRMATION, ButtonType.CANCEL, previewType);
|
|
if(optButton.isPresent() && optButton.get() == previewType) {
|
|
Payment payment = new Payment(paymentCode.getNotificationAddress(), "Link " + paymentCode.toAbbreviatedString(), MINIMUM_P2PKH_OUTPUT_SATS, false);
|
|
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(sendController.getWalletForm().getWallet(), List.of(payment), List.of(new byte[80]), paymentCode)));
|
|
} else {
|
|
Platform.runLater(() -> address.setText(""));
|
|
}
|
|
}
|
|
} catch(Exception e) {
|
|
//ignore, not a payment code
|
|
}
|
|
}
|
|
|
|
revalidateAmount();
|
|
maxButton.setDisable(!isMaxButtonEnabled());
|
|
sendController.updateTransaction();
|
|
|
|
if(validationSupport != null) {
|
|
validationSupport.setErrorDecorationEnabled(true);
|
|
}
|
|
});
|
|
|
|
label.textProperty().addListener((observable, oldValue, newValue) -> {
|
|
maxButton.setDisable(!isMaxButtonEnabled());
|
|
sendController.getCreateButton().setDisable(sendController.getWalletTransaction() == null || newValue == null || newValue.isEmpty() || sendController.isInsufficientFeeRate());
|
|
sendController.updateTransaction();
|
|
});
|
|
|
|
amount.setTextFormatter(new CoinTextFormatter());
|
|
amount.textProperty().addListener(amountListener);
|
|
|
|
amountUnit.getSelectionModel().select(BitcoinUnit.BTC.equals(sendController.getBitcoinUnit(Config.get().getBitcoinUnit())) ? 0 : 1);
|
|
amountUnit.valueProperty().addListener((observable, oldValue, newValue) -> {
|
|
Long value = getRecipientValueSats(oldValue);
|
|
if(value != null) {
|
|
DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
|
df.setMaximumFractionDigits(8);
|
|
amount.setText(df.format(newValue.getValue(value)));
|
|
}
|
|
});
|
|
|
|
maxButton.setDisable(!isMaxButtonEnabled());
|
|
sendController.utxoSelectorProperty().addListener((observable, oldValue, newValue) -> {
|
|
maxButton.setDisable(!isMaxButtonEnabled());
|
|
});
|
|
sendController.getPaymentTabs().getTabs().addListener((ListChangeListener<Tab>) c -> {
|
|
maxButton.setDisable(!isMaxButtonEnabled());
|
|
});
|
|
sendController.utxoLabelSelectionProperty().addListener((observable, oldValue, newValue) -> {
|
|
maxButton.setText("Max" + newValue);
|
|
});
|
|
amountStatus.managedProperty().bind(amountStatus.visibleProperty());
|
|
amountStatus.visibleProperty().bind(sendController.insufficientInputsProperty().and(dustAmountProperty.not()).and(emptyAmountProperty.not()));
|
|
dustStatus.managedProperty().bind(dustStatus.visibleProperty());
|
|
dustStatus.visibleProperty().bind(dustAmountProperty);
|
|
|
|
Optional<Tab> firstTab = sendController.getPaymentTabs().getTabs().stream().findFirst();
|
|
if(firstTab.isPresent()) {
|
|
PaymentController controller = (PaymentController)firstTab.get().getUserData();
|
|
String firstLabel = controller.label.getText();
|
|
label.setText(firstLabel);
|
|
}
|
|
|
|
addValidation(validationSupport);
|
|
}
|
|
|
|
public void setPayNym(PayNym payNym) {
|
|
PayNym existingPayNym = payNymProperty.get();
|
|
payNymProperty.set(payNym);
|
|
address.setText(payNym.nymName());
|
|
address.leftProperty().set(getPayNymGlyph());
|
|
label.requestFocus();
|
|
if(existingPayNym != null && payNym.nymName().equals(existingPayNym.nymName()) && payNym.isCollaborativeSend() != existingPayNym.isCollaborativeSend()) {
|
|
sendController.updateTransaction();
|
|
}
|
|
}
|
|
|
|
public void updateMixOnlyStatus() {
|
|
updateMixOnlyStatus(payNymProperty.get());
|
|
}
|
|
|
|
public void updateMixOnlyStatus(PayNym payNym) {
|
|
boolean mixOnly = false;
|
|
try {
|
|
mixOnly = payNym != null && getRecipientAddress() instanceof PayNymAddress;
|
|
} catch(InvalidAddressException e) {
|
|
log.error("Error creating payment code from PayNym", e);
|
|
}
|
|
|
|
addPaymentButton.setDisable(mixOnly);
|
|
if(mixOnly) {
|
|
sendController.setPayNymMixOnlyPayment();
|
|
}
|
|
}
|
|
|
|
private void updateOpenWallets() {
|
|
updateOpenWallets(AppServices.get().getOpenWallets().keySet());
|
|
}
|
|
|
|
private void updateOpenWallets(Collection<Wallet> wallets) {
|
|
List<Wallet> openWalletList = wallets.stream().filter(wallet -> wallet.isValid() && !wallet.isWhirlpoolChildWallet() && !wallet.isBip47()).collect(Collectors.toList());
|
|
|
|
if(sendController.getWalletForm().getWallet().hasPaymentCode()) {
|
|
openWalletList.add(payNymWallet);
|
|
}
|
|
|
|
openWallets.setItems(FXCollections.observableList(openWalletList));
|
|
}
|
|
|
|
private void addValidation(ValidationSupport validationSupport) {
|
|
this.validationSupport = validationSupport;
|
|
|
|
validationSupport.registerValidator(address, Validator.combine(
|
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !newValue.isEmpty() && !isValidRecipientAddress())
|
|
));
|
|
validationSupport.registerValidator(label, Validator.combine(
|
|
Validator.createEmptyValidator("Label is required")
|
|
));
|
|
validationSupport.registerValidator(amount, Validator.combine(
|
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", getRecipientValueSats() != null && sendController.isInsufficientInputs()),
|
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Value", getRecipientValueSats() != null && getRecipientValueSats() < getRecipientDustThreshold())
|
|
));
|
|
}
|
|
|
|
private boolean isValidRecipientAddress() {
|
|
try {
|
|
getRecipientAddress();
|
|
return true;
|
|
} catch (InvalidAddressException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean isValidAddressAndLabel() {
|
|
return isValidRecipientAddress() && !label.getText().isEmpty();
|
|
}
|
|
|
|
private boolean isMaxButtonEnabled() {
|
|
return isValidAddressAndLabel() || (sendController.utxoSelectorProperty().get() instanceof PresetUtxoSelector && sendController.getPaymentTabs().getTabs().size() == 1);
|
|
}
|
|
|
|
private Address getRecipientAddress() throws InvalidAddressException {
|
|
PayNym payNym = payNymProperty.get();
|
|
if(payNym == null) {
|
|
return Address.fromString(address.getText());
|
|
}
|
|
|
|
if(!payNym.isCollaborativeSend()) {
|
|
try {
|
|
Wallet recipientBip47Wallet = getWalletForPayNym(payNym);
|
|
if(recipientBip47Wallet != null) {
|
|
WalletNode sendNode = recipientBip47Wallet.getFreshNode(KeyPurpose.SEND);
|
|
ECKey pubKey = sendNode.getPubKey();
|
|
Address address = recipientBip47Wallet.getScriptType().getAddress(pubKey);
|
|
if(sendController.getPaymentTabs().getTabs().size() > 1 || (getRecipientValueSats() != null && getRecipientValueSats() > getRecipientDustThreshold(address)) || maxButton.isSelected()) {
|
|
return address;
|
|
}
|
|
}
|
|
} catch(InvalidPaymentCodeException e) {
|
|
log.error("Error creating payment code from PayNym", e);
|
|
}
|
|
}
|
|
|
|
return new PayNymAddress(payNym);
|
|
}
|
|
|
|
private Wallet getWalletForPayNym(PayNym payNym) throws InvalidPaymentCodeException {
|
|
Wallet masterWallet = sendController.getWalletForm().getMasterWallet();
|
|
return masterWallet.getChildWallet(new PaymentCode(payNym.paymentCode().toString()), payNym.segwit() ? ScriptType.P2WPKH : ScriptType.P2PKH);
|
|
}
|
|
|
|
private Long getRecipientValueSats() {
|
|
return getRecipientValueSats(amountUnit.getSelectionModel().getSelectedItem());
|
|
}
|
|
|
|
private Long getRecipientValueSats(BitcoinUnit bitcoinUnit) {
|
|
if(amount.getText() != null && !amount.getText().isEmpty()) {
|
|
double fieldValue = Double.parseDouble(amount.getText().replaceAll(",", ""));
|
|
return bitcoinUnit.getSatsValue(fieldValue);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void setRecipientValueSats(long recipientValue) {
|
|
amount.textProperty().removeListener(amountListener);
|
|
DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
|
df.setMaximumFractionDigits(8);
|
|
amount.setText(df.format(amountUnit.getValue().getValue(recipientValue)));
|
|
amount.textProperty().addListener(amountListener);
|
|
}
|
|
|
|
private long getRecipientDustThreshold() {
|
|
Address address;
|
|
try {
|
|
address = getRecipientAddress();
|
|
} catch(InvalidAddressException e) {
|
|
address = new P2PKHAddress(new byte[20]);
|
|
}
|
|
|
|
if(address instanceof PayNymAddress && SorobanServices.canWalletMix(sendController.getWalletForm().getWallet())) {
|
|
return 0;
|
|
}
|
|
|
|
return getRecipientDustThreshold(address);
|
|
}
|
|
|
|
private long getRecipientDustThreshold(Address address) {
|
|
TransactionOutput txOutput = new TransactionOutput(new Transaction(), 1L, address.getOutputScript());
|
|
return address.getScriptType().getDustThreshold(txOutput, Transaction.DUST_RELAY_TX_FEE);
|
|
}
|
|
|
|
private void setFiatAmount(CurrencyRate currencyRate, Long amount) {
|
|
if(amount != null && currencyRate != null && currencyRate.isAvailable()) {
|
|
fiatAmount.set(currencyRate, amount);
|
|
}
|
|
}
|
|
|
|
public void revalidateAmount() {
|
|
revalidate(amount, amountListener);
|
|
Long recipientValueSats = getRecipientValueSats();
|
|
dustAmountProperty.set(recipientValueSats != null && recipientValueSats < getRecipientDustThreshold());
|
|
emptyAmountProperty.set(recipientValueSats == null);
|
|
}
|
|
|
|
private void revalidate(TextField field, ChangeListener<String> 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);
|
|
}
|
|
|
|
public boolean isValidPayment() {
|
|
try {
|
|
getPayment();
|
|
return true;
|
|
} catch(IllegalStateException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public Payment getPayment() {
|
|
return getPayment(isSendMax());
|
|
}
|
|
|
|
public Payment getPayment(boolean sendAll) {
|
|
try {
|
|
Address recipientAddress = getRecipientAddress();
|
|
Long value = sendAll ? Long.valueOf(getRecipientDustThreshold() + 1) : getRecipientValueSats();
|
|
|
|
if(!label.getText().isEmpty() && value != null && value >= getRecipientDustThreshold()) {
|
|
Payment payment = new Payment(recipientAddress, label.getText(), value, sendAll);
|
|
if(address.getUserData() != null) {
|
|
payment.setType((Payment.Type)address.getUserData());
|
|
}
|
|
|
|
return payment;
|
|
}
|
|
} catch(InvalidAddressException e) {
|
|
//ignore
|
|
}
|
|
|
|
throw new IllegalStateException("Invalid payment specified");
|
|
}
|
|
|
|
public void setPayment(Payment payment) {
|
|
if(getRecipientValueSats() == null || payment.getAmount() != getRecipientValueSats()) {
|
|
if(payment.getAddress() != null) {
|
|
address.setText(payment.getAddress().toString());
|
|
address.setUserData(payment.getType());
|
|
}
|
|
if(payment.getLabel() != null && !label.getText().equals(payment.getLabel())) {
|
|
label.setText(payment.getLabel());
|
|
}
|
|
if(payment.getAmount() >= 0) {
|
|
setRecipientValueSats(payment.getAmount());
|
|
}
|
|
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), payment.getAmount());
|
|
}
|
|
}
|
|
|
|
public void clear() {
|
|
address.setText("");
|
|
label.setText("");
|
|
|
|
amount.textProperty().removeListener(amountListener);
|
|
amount.setText("");
|
|
amount.textProperty().addListener(amountListener);
|
|
|
|
fiatAmount.setText("");
|
|
setSendMax(false);
|
|
|
|
dustAmountProperty.set(false);
|
|
payNymProperty.set(null);
|
|
}
|
|
|
|
public void setMaxInput(ActionEvent event) {
|
|
UtxoSelector utxoSelector = sendController.getUtxoSelector();
|
|
if(utxoSelector == null) {
|
|
MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector();
|
|
sendController.utxoSelectorProperty().set(maxUtxoSelector);
|
|
} else if(utxoSelector instanceof PresetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) {
|
|
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
|
|
Payment payment = new Payment(null, null, presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(), true);
|
|
setPayment(payment);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
List<Payment> payments = new ArrayList<>();
|
|
for(Tab tab : sendController.getPaymentTabs().getTabs()) {
|
|
PaymentController controller = (PaymentController)tab.getUserData();
|
|
if(controller != this) {
|
|
controller.setSendMax(false);
|
|
payments.add(controller.getPayment());
|
|
} else {
|
|
setSendMax(true);
|
|
payments.add(getPayment());
|
|
}
|
|
}
|
|
sendController.updateTransaction(payments);
|
|
} catch(IllegalStateException e) {
|
|
//ignore, validation errors
|
|
}
|
|
}
|
|
|
|
public void scanQrAddress(ActionEvent event) {
|
|
QRScanDialog qrScanDialog = new QRScanDialog();
|
|
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
|
|
if(optionalResult.isPresent()) {
|
|
QRScanDialog.Result result = optionalResult.get();
|
|
if(result.uri != null) {
|
|
updateFromURI(result.uri);
|
|
} else if(result.payload != null) {
|
|
address.setText(result.payload);
|
|
} else if(result.exception != null) {
|
|
log.error("Error scanning QR", result.exception);
|
|
showErrorDialog("Error scanning QR", result.exception.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void updateFromURI(BitcoinURI bitcoinURI) {
|
|
if(bitcoinURI.getAddress() != null) {
|
|
address.setText(bitcoinURI.getAddress().toString());
|
|
}
|
|
if(bitcoinURI.getLabel() != null) {
|
|
label.setText(bitcoinURI.getLabel());
|
|
}
|
|
if(bitcoinURI.getAmount() != null) {
|
|
setRecipientValueSats(bitcoinURI.getAmount());
|
|
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), bitcoinURI.getAmount());
|
|
}
|
|
if(bitcoinURI.getPayjoinUrl() != null) {
|
|
AppServices.addPayjoinURI(bitcoinURI);
|
|
}
|
|
sendController.updateTransaction();
|
|
}
|
|
|
|
public void addPayment(ActionEvent event) {
|
|
sendController.addPaymentTab();
|
|
}
|
|
|
|
public Button getAddPaymentButton() {
|
|
return addPaymentButton;
|
|
}
|
|
|
|
public boolean isSendMax() {
|
|
return maxButton.isSelected();
|
|
}
|
|
|
|
public void setSendMax(boolean sendMax) {
|
|
maxButton.setSelected(sendMax);
|
|
}
|
|
|
|
public void setInputFieldsDisabled(boolean disable) {
|
|
address.setDisable(disable);
|
|
label.setDisable(disable);
|
|
amount.setDisable(disable);
|
|
amountUnit.setDisable(disable);
|
|
scanQrButton.setDisable(disable);
|
|
addPaymentButton.setDisable(disable);
|
|
maxButton.setDisable(disable);
|
|
}
|
|
|
|
public static Glyph getPayNymGlyph() {
|
|
Glyph payNymGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ROBOT);
|
|
payNymGlyph.getStyleClass().add("paynym-icon");
|
|
payNymGlyph.setFontSize(12);
|
|
return payNymGlyph;
|
|
}
|
|
|
|
@Subscribe
|
|
public void bitcoinUnitChanged(BitcoinUnitChangedEvent event) {
|
|
BitcoinUnit unit = sendController.getBitcoinUnit(event.getBitcoinUnit());
|
|
amountUnit.getSelectionModel().select(BitcoinUnit.BTC.equals(unit) ? 0 : 1);
|
|
}
|
|
|
|
@Subscribe
|
|
public void fiatCurrencySelected(FiatCurrencySelectedEvent event) {
|
|
if(event.getExchangeSource() == ExchangeSource.NONE) {
|
|
fiatAmount.setCurrency(null);
|
|
fiatAmount.setBtcRate(0.0);
|
|
}
|
|
}
|
|
|
|
@Subscribe
|
|
public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) {
|
|
setFiatAmount(event.getCurrencyRate(), getRecipientValueSats());
|
|
}
|
|
|
|
@Subscribe
|
|
public void openWallets(OpenWalletsEvent event) {
|
|
updateOpenWallets(event.getWallets());
|
|
}
|
|
}
|