mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-26 00:15:18 -04:00
enable volume input when taking range offer
This commit is contained in:
parent
0cf34f3170
commit
8f505ab17b
4 changed files with 168 additions and 29 deletions
|
@ -207,7 +207,6 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter xmrFormatter,
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter xmrFormatter,
|
||||||
OfferUtil offerUtil) {
|
OfferUtil offerUtil) {
|
||||||
super(dataModel);
|
super(dataModel);
|
||||||
|
|
||||||
this.fiatVolumeValidator = fiatVolumeValidator;
|
this.fiatVolumeValidator = fiatVolumeValidator;
|
||||||
this.amountValidator4Decimals = amountValidator4Decimals;
|
this.amountValidator4Decimals = amountValidator4Decimals;
|
||||||
this.amountValidator8Decimals = amountValidator8Decimals;
|
this.amountValidator8Decimals = amountValidator8Decimals;
|
||||||
|
|
|
@ -41,6 +41,7 @@ import haveno.core.trade.handlers.TradeResultHandler;
|
||||||
import haveno.core.user.Preferences;
|
import haveno.core.user.Preferences;
|
||||||
import haveno.core.user.User;
|
import haveno.core.user.User;
|
||||||
import haveno.core.util.VolumeUtil;
|
import haveno.core.util.VolumeUtil;
|
||||||
|
import haveno.core.util.coin.CoinUtil;
|
||||||
import haveno.core.xmr.listeners.XmrBalanceListener;
|
import haveno.core.xmr.listeners.XmrBalanceListener;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
|
@ -59,6 +60,7 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
@ -91,7 +93,10 @@ class TakeOfferDataModel extends OfferDataModel {
|
||||||
private XmrBalanceListener balanceListener;
|
private XmrBalanceListener balanceListener;
|
||||||
private PaymentAccount paymentAccount;
|
private PaymentAccount paymentAccount;
|
||||||
private boolean isTabSelected;
|
private boolean isTabSelected;
|
||||||
|
protected boolean allowAmountUpdate = true;
|
||||||
Price tradePrice;
|
Price tradePrice;
|
||||||
|
private final Predicate<Price> isNonZeroPrice = (p) -> p != null && !p.isZero();
|
||||||
|
private final Predicate<ObjectProperty<Volume>> isNonZeroVolume = (v) -> v.get() != null && !v.get().isZero();
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -317,6 +322,10 @@ class TakeOfferDataModel extends OfferDataModel {
|
||||||
return offer;
|
return offer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReadOnlyObjectProperty<Volume> getVolume() {
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
ObservableList<PaymentAccount> getPossiblePaymentAccounts() {
|
ObservableList<PaymentAccount> getPossiblePaymentAccounts() {
|
||||||
Set<PaymentAccount> paymentAccounts = user.getPaymentAccounts();
|
Set<PaymentAccount> paymentAccounts = user.getPaymentAccounts();
|
||||||
checkNotNull(paymentAccounts, "paymentAccounts must not be null");
|
checkNotNull(paymentAccounts, "paymentAccounts must not be null");
|
||||||
|
@ -387,6 +396,32 @@ class TakeOfferDataModel extends OfferDataModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void calculateAmount() {
|
||||||
|
if (isNonZeroPrice.test(tradePrice) && isNonZeroVolume.test(volume) && allowAmountUpdate) {
|
||||||
|
try {
|
||||||
|
Volume volumeBefore = volume.get();
|
||||||
|
calculateVolume();
|
||||||
|
|
||||||
|
// if the volume != amount * price, we need to adjust the amount
|
||||||
|
if (amount.get() == null || !volumeBefore.equals(tradePrice.getVolumeByAmount(amount.get()))) {
|
||||||
|
BigInteger value = tradePrice.getAmountByVolume(volumeBefore);
|
||||||
|
value = value.min(offer.getAmount()); // adjust if above maximum
|
||||||
|
value = value.max(offer.getMinAmount()); // adjust if below minimum
|
||||||
|
value = CoinUtil.getRoundedAmount(value, tradePrice, offer.getMinAmount(), getMaxTradeLimit(), offer.getCounterCurrencyCode(), paymentAccount.getPaymentMethod().getId());
|
||||||
|
amount.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateTotalToPay();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error(t.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setVolume(Volume volume) {
|
||||||
|
this.volume.set(volume);
|
||||||
|
}
|
||||||
|
|
||||||
void maybeApplyAmount(BigInteger amount) {
|
void maybeApplyAmount(BigInteger amount) {
|
||||||
if (amount.compareTo(offer.getMinAmount()) >= 0 && amount.compareTo(getMaxTradeLimit()) <= 0) {
|
if (amount.compareTo(offer.getMinAmount()) >= 0 && amount.compareTo(getMaxTradeLimit()) <= 0) {
|
||||||
this.amount.set(amount);
|
this.amount.set(amount);
|
||||||
|
|
|
@ -144,9 +144,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
waitingForFundsLabel, offerAvailabilityLabel, priceAsPercentageDescription,
|
waitingForFundsLabel, offerAvailabilityLabel, priceAsPercentageDescription,
|
||||||
tradeFeeDescriptionLabel, resultLabel, tradeFeeInXmrLabel, xLabel,
|
tradeFeeDescriptionLabel, resultLabel, tradeFeeInXmrLabel, xLabel,
|
||||||
fakeXLabel, extraInfoLabel;
|
fakeXLabel, extraInfoLabel;
|
||||||
private InputTextField amountTextField;
|
private InputTextField amountTextField, volumeTextField;
|
||||||
private TextField paymentMethodTextField, currencyTextField, priceTextField, priceAsPercentageTextField,
|
private TextField paymentMethodTextField, currencyTextField, priceTextField, priceAsPercentageTextField,
|
||||||
volumeTextField, amountRangeTextField;
|
amountRangeTextField;
|
||||||
private FundsTextField totalToPayTextField;
|
private FundsTextField totalToPayTextField;
|
||||||
private AddressTextField addressTextField;
|
private AddressTextField addressTextField;
|
||||||
private BalanceTextField balanceTextField;
|
private BalanceTextField balanceTextField;
|
||||||
|
@ -159,7 +159,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
private Notification walletFundedNotification;
|
private Notification walletFundedNotification;
|
||||||
private OfferView.CloseHandler closeHandler;
|
private OfferView.CloseHandler closeHandler;
|
||||||
private Subscription balanceSubscription,
|
private Subscription balanceSubscription,
|
||||||
showTransactionPublishedScreenSubscription, showWarningInvalidBtcDecimalPlacesSubscription,
|
showTransactionPublishedScreenSubscription, showWarningInvalidXmrDecimalPlacesSubscription,
|
||||||
isWaitingForFundsSubscription, offerWarningSubscription, errorMessageSubscription,
|
isWaitingForFundsSubscription, offerWarningSubscription, errorMessageSubscription,
|
||||||
isOfferAvailableSubscription;
|
isOfferAvailableSubscription;
|
||||||
private ChangeListener<BigInteger> missingCoinListener;
|
private ChangeListener<BigInteger> missingCoinListener;
|
||||||
|
@ -170,7 +170,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
takeOfferFromUnsignedAccountWarningDisplayed, payByMailWarningDisplayed, cashAtAtmWarningDisplayed,
|
takeOfferFromUnsignedAccountWarningDisplayed, payByMailWarningDisplayed, cashAtAtmWarningDisplayed,
|
||||||
australiaPayidWarningDisplayed, paypalWarningDisplayed, cashAppWarningDisplayed, F2FWarningDisplayed;
|
australiaPayidWarningDisplayed, paypalWarningDisplayed, cashAppWarningDisplayed, F2FWarningDisplayed;
|
||||||
private SimpleBooleanProperty errorPopupDisplayed;
|
private SimpleBooleanProperty errorPopupDisplayed;
|
||||||
private ChangeListener<Boolean> amountFocusedListener, getShowWalletFundedNotificationListener;
|
private ChangeListener<Boolean> amountFocusedListener, volumeFocusedListener, getShowWalletFundedNotificationListener;
|
||||||
|
|
||||||
private InfoInputTextField volumeInfoTextField;
|
private InfoInputTextField volumeInfoTextField;
|
||||||
|
|
||||||
|
@ -208,21 +208,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
|
|
||||||
balanceTextField.setFormatter(model.getXmrFormatter());
|
balanceTextField.setFormatter(model.getXmrFormatter());
|
||||||
|
|
||||||
amountFocusedListener = (o, oldValue, newValue) -> {
|
|
||||||
model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
|
|
||||||
amountTextField.setText(model.amount.get());
|
|
||||||
};
|
|
||||||
|
|
||||||
getShowWalletFundedNotificationListener = (observable, oldValue, newValue) -> {
|
|
||||||
if (newValue) {
|
|
||||||
Notification walletFundedNotification = new Notification()
|
|
||||||
.headLine(Res.get("notification.walletUpdate.headline"))
|
|
||||||
.notification(Res.get("notification.walletUpdate.msg", HavenoUtils.formatXmr(model.dataModel.getTotalToPay().get(), true)))
|
|
||||||
.autoClose();
|
|
||||||
|
|
||||||
walletFundedNotification.show();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
GUIUtil.focusWhenAddedToScene(amountTextField);
|
GUIUtil.focusWhenAddedToScene(amountTextField);
|
||||||
}
|
}
|
||||||
|
@ -342,6 +327,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
amountRangeTextField.setText(model.getAmountRange());
|
amountRangeTextField.setText(model.getAmountRange());
|
||||||
amountRangeBox.setVisible(true);
|
amountRangeBox.setVisible(true);
|
||||||
amountRangeBox.setManaged(true);
|
amountRangeBox.setManaged(true);
|
||||||
|
volumeTextField.setDisable(false);
|
||||||
} else {
|
} else {
|
||||||
amountTextField.setDisable(true);
|
amountTextField.setDisable(true);
|
||||||
amountTextField.setManaged(true);
|
amountTextField.setManaged(true);
|
||||||
|
@ -604,6 +590,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
totalToPayTextField.textProperty().bind(model.totalToPay);
|
totalToPayTextField.textProperty().bind(model.totalToPay);
|
||||||
addressTextField.amountAsProperty().bind(model.dataModel.getMissingCoin());
|
addressTextField.amountAsProperty().bind(model.dataModel.getMissingCoin());
|
||||||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||||
|
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.getCounterCurrency(model.dataModel.getCurrencyCode())));
|
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.getCounterCurrency(model.dataModel.getCurrencyCode())));
|
||||||
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
|
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
|
||||||
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
||||||
|
@ -703,10 +690,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
waitingForFundsLabel.setManaged(isWaitingForFunds);
|
waitingForFundsLabel.setManaged(isWaitingForFunds);
|
||||||
});
|
});
|
||||||
|
|
||||||
showWarningInvalidBtcDecimalPlacesSubscription = EasyBind.subscribe(model.showWarningInvalidBtcDecimalPlaces, newValue -> {
|
showWarningInvalidXmrDecimalPlacesSubscription = EasyBind.subscribe(model.showWarningInvalidXmrDecimalPlaces, newValue -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
new Popup().warning(Res.get("takeOffer.amountPriceBox.warning.invalidXmrDecimalPlaces")).show();
|
new Popup().warning(Res.get("takeOffer.amountPriceBox.warning.invalidXmrDecimalPlaces")).show();
|
||||||
model.showWarningInvalidBtcDecimalPlaces.set(false);
|
model.showWarningInvalidXmrDecimalPlaces.set(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -742,13 +729,31 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
errorMessageSubscription.unsubscribe();
|
errorMessageSubscription.unsubscribe();
|
||||||
isOfferAvailableSubscription.unsubscribe();
|
isOfferAvailableSubscription.unsubscribe();
|
||||||
isWaitingForFundsSubscription.unsubscribe();
|
isWaitingForFundsSubscription.unsubscribe();
|
||||||
showWarningInvalidBtcDecimalPlacesSubscription.unsubscribe();
|
showWarningInvalidXmrDecimalPlacesSubscription.unsubscribe();
|
||||||
showTransactionPublishedScreenSubscription.unsubscribe();
|
showTransactionPublishedScreenSubscription.unsubscribe();
|
||||||
// noSufficientFeeSubscription.unsubscribe();
|
// noSufficientFeeSubscription.unsubscribe();
|
||||||
balanceSubscription.unsubscribe();
|
balanceSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createListeners() {
|
private void createListeners() {
|
||||||
|
amountFocusedListener = (o, oldValue, newValue) -> {
|
||||||
|
model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
|
||||||
|
amountTextField.setText(model.amount.get());
|
||||||
|
};
|
||||||
|
getShowWalletFundedNotificationListener = (observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
Notification walletFundedNotification = new Notification()
|
||||||
|
.headLine(Res.get("notification.walletUpdate.headline"))
|
||||||
|
.notification(Res.get("notification.walletUpdate.msg", HavenoUtils.formatXmr(model.dataModel.getTotalToPay().get(), true)))
|
||||||
|
.autoClose();
|
||||||
|
|
||||||
|
walletFundedNotification.show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
volumeFocusedListener = (o, oldValue, newValue) -> {
|
||||||
|
model.onFocusOutVolumeTextField(oldValue, newValue);
|
||||||
|
volumeTextField.setText(model.volume.get());
|
||||||
|
};
|
||||||
missingCoinListener = (observable, oldValue, newValue) -> {
|
missingCoinListener = (observable, oldValue, newValue) -> {
|
||||||
if (!newValue.toString().equals("")) {
|
if (!newValue.toString().equals("")) {
|
||||||
updateQrCode();
|
updateQrCode();
|
||||||
|
@ -758,12 +763,14 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
|
|
||||||
private void addListeners() {
|
private void addListeners() {
|
||||||
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
||||||
|
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
||||||
model.dataModel.getShowWalletFundedNotification().addListener(getShowWalletFundedNotificationListener);
|
model.dataModel.getShowWalletFundedNotification().addListener(getShowWalletFundedNotificationListener);
|
||||||
model.dataModel.getMissingCoin().addListener(missingCoinListener);
|
model.dataModel.getMissingCoin().addListener(missingCoinListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeListeners() {
|
private void removeListeners() {
|
||||||
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
||||||
|
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
||||||
model.dataModel.getShowWalletFundedNotification().removeListener(getShowWalletFundedNotificationListener);
|
model.dataModel.getShowWalletFundedNotification().removeListener(getShowWalletFundedNotificationListener);
|
||||||
model.dataModel.getMissingCoin().removeListener(missingCoinListener);
|
model.dataModel.getMissingCoin().removeListener(missingCoinListener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,17 @@ import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||||
|
import haveno.core.locale.CurrencyUtil;
|
||||||
import haveno.core.locale.Res;
|
import haveno.core.locale.Res;
|
||||||
import haveno.core.monetary.Price;
|
import haveno.core.monetary.Price;
|
||||||
|
import haveno.core.monetary.Volume;
|
||||||
import haveno.core.offer.Offer;
|
import haveno.core.offer.Offer;
|
||||||
import haveno.core.offer.OfferDirection;
|
import haveno.core.offer.OfferDirection;
|
||||||
import haveno.core.offer.OfferRestrictions;
|
import haveno.core.offer.OfferRestrictions;
|
||||||
import haveno.core.offer.OfferUtil;
|
import haveno.core.offer.OfferUtil;
|
||||||
import haveno.core.payment.PaymentAccount;
|
import haveno.core.payment.PaymentAccount;
|
||||||
import haveno.core.payment.payload.PaymentMethod;
|
import haveno.core.payment.payload.PaymentMethod;
|
||||||
|
import haveno.core.payment.validation.FiatVolumeValidator;
|
||||||
import haveno.core.payment.validation.XmrValidator;
|
import haveno.core.payment.validation.XmrValidator;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
|
@ -37,7 +40,10 @@ import haveno.core.util.FormattingUtils;
|
||||||
import haveno.core.util.VolumeUtil;
|
import haveno.core.util.VolumeUtil;
|
||||||
import haveno.core.util.coin.CoinFormatter;
|
import haveno.core.util.coin.CoinFormatter;
|
||||||
import haveno.core.util.coin.CoinUtil;
|
import haveno.core.util.coin.CoinUtil;
|
||||||
|
import haveno.core.util.validation.AmountValidator4Decimals;
|
||||||
|
import haveno.core.util.validation.AmountValidator8Decimals;
|
||||||
import haveno.core.util.validation.InputValidator;
|
import haveno.core.util.validation.InputValidator;
|
||||||
|
import haveno.core.util.validation.MonetaryValidator;
|
||||||
import haveno.desktop.Navigation;
|
import haveno.desktop.Navigation;
|
||||||
import haveno.desktop.common.model.ActivatableWithDataModel;
|
import haveno.desktop.common.model.ActivatableWithDataModel;
|
||||||
import haveno.desktop.common.model.ViewModel;
|
import haveno.desktop.common.model.ViewModel;
|
||||||
|
@ -76,12 +82,15 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||||
private final Navigation navigation;
|
private final Navigation navigation;
|
||||||
private final CoinFormatter xmrFormatter;
|
private final CoinFormatter xmrFormatter;
|
||||||
|
private final FiatVolumeValidator fiatVolumeValidator;
|
||||||
|
private final AmountValidator4Decimals amountValidator4Decimals;
|
||||||
|
private final AmountValidator8Decimals amountValidator8Decimals;
|
||||||
|
|
||||||
private String amountRange;
|
private String amountRange;
|
||||||
private String paymentLabel;
|
private String paymentLabel;
|
||||||
private boolean takeOfferRequested;
|
private boolean takeOfferRequested, ignoreVolumeStringListener;
|
||||||
private Trade trade;
|
private Trade trade;
|
||||||
private Offer offer;
|
protected Offer offer;
|
||||||
private String price;
|
private String price;
|
||||||
private String amountDescription;
|
private String amountDescription;
|
||||||
|
|
||||||
|
@ -101,15 +110,18 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
final BooleanProperty isTakeOfferButtonDisabled = new SimpleBooleanProperty(true);
|
final BooleanProperty isTakeOfferButtonDisabled = new SimpleBooleanProperty(true);
|
||||||
final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true);
|
final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true);
|
||||||
final BooleanProperty isWaitingForFunds = new SimpleBooleanProperty();
|
final BooleanProperty isWaitingForFunds = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
final BooleanProperty showWarningInvalidXmrDecimalPlaces = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
||||||
final BooleanProperty takeOfferCompleted = new SimpleBooleanProperty();
|
final BooleanProperty takeOfferCompleted = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
||||||
|
|
||||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||||
|
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
private ChangeListener<String> amountStrListener;
|
private ChangeListener<String> amountStrListener;
|
||||||
private ChangeListener<BigInteger> amountListener;
|
private ChangeListener<BigInteger> amountListener;
|
||||||
|
private ChangeListener<String> volumeStringListener;
|
||||||
|
private ChangeListener<Volume> volumeListener;
|
||||||
private ChangeListener<Boolean> isWalletFundedListener;
|
private ChangeListener<Boolean> isWalletFundedListener;
|
||||||
private ChangeListener<Trade.State> tradeStateListener;
|
private ChangeListener<Trade.State> tradeStateListener;
|
||||||
private ChangeListener<Offer.State> offerStateListener;
|
private ChangeListener<Offer.State> offerStateListener;
|
||||||
|
@ -124,6 +136,9 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TakeOfferViewModel(TakeOfferDataModel dataModel,
|
public TakeOfferViewModel(TakeOfferDataModel dataModel,
|
||||||
|
FiatVolumeValidator fiatVolumeValidator,
|
||||||
|
AmountValidator4Decimals amountValidator4Decimals,
|
||||||
|
AmountValidator8Decimals amountValidator8Decimals,
|
||||||
OfferUtil offerUtil,
|
OfferUtil offerUtil,
|
||||||
XmrValidator btcValidator,
|
XmrValidator btcValidator,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
|
@ -138,6 +153,9 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
this.navigation = navigation;
|
this.navigation = navigation;
|
||||||
this.xmrFormatter = btcFormatter;
|
this.xmrFormatter = btcFormatter;
|
||||||
|
this.fiatVolumeValidator = fiatVolumeValidator;
|
||||||
|
this.amountValidator4Decimals = amountValidator4Decimals;
|
||||||
|
this.amountValidator8Decimals = amountValidator8Decimals;
|
||||||
createListeners();
|
createListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +228,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
xmrValidator.setMaxValue(offer.getAmount());
|
xmrValidator.setMaxValue(offer.getAmount());
|
||||||
xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit());
|
xmrValidator.setMaxTradeLimit(dataModel.getMaxTradeLimit());
|
||||||
xmrValidator.setMinValue(offer.getMinAmount());
|
xmrValidator.setMinValue(offer.getMinAmount());
|
||||||
|
|
||||||
|
setVolumeToModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -288,7 +308,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
InputValidator.ValidationResult result = isXmrInputValid(amount.get());
|
InputValidator.ValidationResult result = isXmrInputValid(amount.get());
|
||||||
amountValidationResult.set(result);
|
amountValidationResult.set(result);
|
||||||
if (result.isValid) {
|
if (result.isValid) {
|
||||||
showWarningInvalidBtcDecimalPlaces.set(!DisplayUtils.hasBtcValidDecimals(userInput, xmrFormatter));
|
if (userInput != null) showWarningInvalidXmrDecimalPlaces.set(!DisplayUtils.hasBtcValidDecimals(userInput, xmrFormatter));
|
||||||
// only allow max 4 decimal places for xmr values
|
// only allow max 4 decimal places for xmr values
|
||||||
setAmountToModel();
|
setAmountToModel();
|
||||||
// reformat input
|
// reformat input
|
||||||
|
@ -338,6 +358,30 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFocusOutVolumeTextField(boolean oldValue, boolean newValue) {
|
||||||
|
if (oldValue && !newValue) {
|
||||||
|
InputValidator.ValidationResult result = isVolumeInputValid(volume.get());
|
||||||
|
volumeValidationResult.set(result);
|
||||||
|
if (result.isValid) {
|
||||||
|
setVolumeToModel();
|
||||||
|
ignoreVolumeStringListener = true;
|
||||||
|
|
||||||
|
Volume volume = dataModel.getVolume().get();
|
||||||
|
if (volume != null) {
|
||||||
|
volume = VolumeUtil.getAdjustedVolume(volume, offer.getPaymentMethod().getId());
|
||||||
|
this.volume.set(VolumeUtil.formatVolume(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreVolumeStringListener = false;
|
||||||
|
|
||||||
|
dataModel.calculateAmount();
|
||||||
|
|
||||||
|
if (amount.get() != null)
|
||||||
|
amountValidationResult.set(isXmrInputValid(amount.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// States
|
// States
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -454,14 +498,12 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private void addBindings() {
|
private void addBindings() {
|
||||||
volume.bind(createStringBinding(() -> VolumeUtil.formatVolume(dataModel.volume.get()), dataModel.volume));
|
|
||||||
totalToPay.bind(createStringBinding(() -> HavenoUtils.formatXmr(dataModel.getTotalToPay().get(), true), dataModel.getTotalToPay()));
|
totalToPay.bind(createStringBinding(() -> HavenoUtils.formatXmr(dataModel.getTotalToPay().get(), true), dataModel.getTotalToPay()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void removeBindings() {
|
private void removeBindings() {
|
||||||
volumeDescriptionLabel.unbind();
|
volumeDescriptionLabel.unbind();
|
||||||
volume.unbind();
|
|
||||||
totalToPay.unbind();
|
totalToPay.unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,10 +517,33 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
};
|
};
|
||||||
|
|
||||||
amountListener = (ov, oldValue, newValue) -> {
|
amountListener = (ov, oldValue, newValue) -> {
|
||||||
amount.set(HavenoUtils.formatXmr(newValue));
|
amount.set(HavenoUtils.formatXmr(newValue));
|
||||||
applyTakerFee();
|
applyTakerFee();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
volumeStringListener = (ov, oldValue, newValue) -> {
|
||||||
|
if (!ignoreVolumeStringListener) {
|
||||||
|
if (isVolumeInputValid(newValue).isValid) {
|
||||||
|
setVolumeToModel();
|
||||||
|
dataModel.calculateAmount();
|
||||||
|
dataModel.calculateTotalToPay();
|
||||||
|
}
|
||||||
|
updateButtonDisableState();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
volumeListener = (ov, oldValue, newValue) -> {
|
||||||
|
ignoreVolumeStringListener = true;
|
||||||
|
if (newValue != null)
|
||||||
|
volume.set(VolumeUtil.formatVolume(newValue));
|
||||||
|
else
|
||||||
|
volume.set("");
|
||||||
|
|
||||||
|
ignoreVolumeStringListener = false;
|
||||||
|
};
|
||||||
|
|
||||||
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
|
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
|
||||||
|
|
||||||
tradeStateListener = (ov, oldValue, newValue) -> applyTradeState();
|
tradeStateListener = (ov, oldValue, newValue) -> applyTradeState();
|
||||||
|
@ -527,9 +592,11 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
// Bidirectional bindings are used for all input fields: amount, price, volume and minAmount
|
// Bidirectional bindings are used for all input fields: amount, price, volume and minAmount
|
||||||
// We do volume/amount calculation during input, so user has immediate feedback
|
// We do volume/amount calculation during input, so user has immediate feedback
|
||||||
amount.addListener(amountStrListener);
|
amount.addListener(amountStrListener);
|
||||||
|
volume.addListener(volumeStringListener);
|
||||||
|
|
||||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||||
dataModel.getAmount().addListener(amountListener);
|
dataModel.getAmount().addListener(amountListener);
|
||||||
|
dataModel.getVolume().addListener(volumeListener);
|
||||||
|
|
||||||
dataModel.getIsXmrWalletFunded().addListener(isWalletFundedListener);
|
dataModel.getIsXmrWalletFunded().addListener(isWalletFundedListener);
|
||||||
p2PService.getNetworkNode().addConnectionListener(connectionListener);
|
p2PService.getNetworkNode().addConnectionListener(connectionListener);
|
||||||
|
@ -541,9 +608,11 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
|
|
||||||
private void removeListeners() {
|
private void removeListeners() {
|
||||||
amount.removeListener(amountStrListener);
|
amount.removeListener(amountStrListener);
|
||||||
|
volume.removeListener(volumeStringListener);
|
||||||
|
|
||||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||||
dataModel.getAmount().removeListener(amountListener);
|
dataModel.getAmount().removeListener(amountListener);
|
||||||
|
dataModel.getVolume().removeListener(volumeListener);
|
||||||
|
|
||||||
dataModel.getIsXmrWalletFunded().removeListener(isWalletFundedListener);
|
dataModel.getIsXmrWalletFunded().removeListener(isWalletFundedListener);
|
||||||
if (offer != null) {
|
if (offer != null) {
|
||||||
|
@ -583,6 +652,35 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setVolumeToModel() {
|
||||||
|
if (volume.get() != null && !volume.get().isEmpty()) {
|
||||||
|
try {
|
||||||
|
dataModel.setVolume(Volume.parse(volume.get(), offer.getCounterCurrencyCode()));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.debug(t.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataModel.setVolume(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputValidator.ValidationResult isVolumeInputValid(String input) {
|
||||||
|
return getVolumeValidator().validate(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: replace with VolumeUtils?
|
||||||
|
|
||||||
|
private MonetaryValidator getVolumeValidator() {
|
||||||
|
final String code = offer.getCounterCurrencyCode();
|
||||||
|
if (CurrencyUtil.isFiatCurrency(code)) {
|
||||||
|
return fiatVolumeValidator;
|
||||||
|
} else if (CurrencyUtil.isVolumeRoundedToNearestUnit(code)) {
|
||||||
|
return amountValidator4Decimals;
|
||||||
|
} else {
|
||||||
|
return amountValidator8Decimals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue