perc. price UI (WIP)

This commit is contained in:
Manfred Karrer 2016-04-14 21:59:14 +02:00
parent 517ee371db
commit d5118b048b
7 changed files with 176 additions and 38 deletions

View file

@ -106,7 +106,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
private final long date; private final long date;
private final long protocolVersion; private final long protocolVersion;
private final long fiatPrice; private final long fiatPrice;
private final double percentagePrice; private final double percentageBasedPrice;
private final boolean usePercentageBasedPrice; private final boolean usePercentageBasedPrice;
private final long amount; private final long amount;
private final long minAmount; private final long minAmount;
@ -140,7 +140,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
PubKeyRing pubKeyRing, PubKeyRing pubKeyRing,
Direction direction, Direction direction,
long fiatPrice, long fiatPrice,
double percentagePrice, double percentageBasedPrice,
boolean usePercentageBasedPrice, boolean usePercentageBasedPrice,
long amount, long amount,
long minAmount, long minAmount,
@ -157,7 +157,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
this.pubKeyRing = pubKeyRing; this.pubKeyRing = pubKeyRing;
this.direction = direction; this.direction = direction;
this.fiatPrice = fiatPrice; this.fiatPrice = fiatPrice;
this.percentagePrice = percentagePrice; this.percentageBasedPrice = percentageBasedPrice;
this.usePercentageBasedPrice = usePercentageBasedPrice; this.usePercentageBasedPrice = usePercentageBasedPrice;
this.amount = amount; this.amount = amount;
this.minAmount = minAmount; this.minAmount = minAmount;
@ -321,8 +321,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
return Fiat.valueOf(currencyCode, fiatPrice); return Fiat.valueOf(currencyCode, fiatPrice);
} }
public double getPercentagePrice() { public double getPercentageBasedPrice() {
return percentagePrice; return percentageBasedPrice;
} }
public boolean isUsePercentageBasedPrice() { public boolean isUsePercentageBasedPrice() {
@ -408,7 +408,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
Offer offer = (Offer) o; Offer offer = (Offer) o;
if (date != offer.date) return false; if (date != offer.date) return false;
if (fiatPrice != offer.fiatPrice) return false; if (fiatPrice != offer.fiatPrice) return false;
if (Double.compare(offer.percentagePrice, percentagePrice) != 0) return false; if (Double.compare(offer.percentageBasedPrice, percentageBasedPrice) != 0) return false;
if (usePercentageBasedPrice != offer.usePercentageBasedPrice) return false; if (usePercentageBasedPrice != offer.usePercentageBasedPrice) return false;
if (amount != offer.amount) return false; if (amount != offer.amount) return false;
if (minAmount != offer.minAmount) return false; if (minAmount != offer.minAmount) return false;
@ -441,7 +441,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0); result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0);
result = 31 * result + (int) (date ^ (date >>> 32)); result = 31 * result + (int) (date ^ (date >>> 32));
result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32)); result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32));
long temp = Double.doubleToLongBits(percentagePrice); long temp = Double.doubleToLongBits(percentageBasedPrice);
result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (usePercentageBasedPrice ? 1 : 0); result = 31 * result + (usePercentageBasedPrice ? 1 : 0);
result = 31 * result + (int) (amount ^ (amount >>> 32)); result = 31 * result + (int) (amount ^ (amount >>> 32));
@ -467,7 +467,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
"\n\tcurrencyCode='" + currencyCode + '\'' + "\n\tcurrencyCode='" + currencyCode + '\'' +
"\n\tdate=" + date + "\n\tdate=" + date +
"\n\tfiatPrice=" + fiatPrice + "\n\tfiatPrice=" + fiatPrice +
"\n\tpercentagePrice=" + percentagePrice + "\n\tpercentagePrice=" + percentageBasedPrice +
"\n\tusePercentageBasedPrice=" + usePercentageBasedPrice + "\n\tusePercentageBasedPrice=" + usePercentageBasedPrice +
"\n\tamount=" + amount + "\n\tamount=" + amount +
"\n\tminAmount=" + minAmount + "\n\tminAmount=" + minAmount +

View file

@ -404,7 +404,7 @@ public class MainViewModel implements ViewModel {
result = numPeersString + " / synchronized with " + btcNetworkAsString; result = numPeersString + " / synchronized with " + btcNetworkAsString;
btcSplashSyncIconId.set("image-connection-synced"); btcSplashSyncIconId.set("image-connection-synced");
} else if (percentage > 0.0) { } else if (percentage > 0.0) {
result = numPeersString + " / synchronizing with " + btcNetworkAsString + ": " + formatter.formatToPercent(percentage); result = numPeersString + " / synchronizing with " + btcNetworkAsString + ": " + formatter.formatToPercentWithSymbol(percentage);
} else { } else {
result = numPeersString + " / connecting to " + btcNetworkAsString; result = numPeersString + " / connecting to " + btcNetworkAsString;
} }

View file

@ -97,7 +97,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> minAmountAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> minAmountAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>(); final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Double> priceAsPercentage = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>(); final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> missingCoin = new SimpleObjectProperty<>(Coin.ZERO); final ObjectProperty<Coin> missingCoin = new SimpleObjectProperty<>(Coin.ZERO);
@ -110,6 +109,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
private Notification walletFundedNotification; private Notification walletFundedNotification;
boolean useSavingsWallet; boolean useSavingsWallet;
Coin totalAvailableBalance; Coin totalAvailableBalance;
private double percentageBasedPrice = 0;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -256,7 +256,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
Offer createAndGetOffer() { Offer createAndGetOffer() {
long fiatPrice = priceAsFiat.get() != null ? priceAsFiat.get().getValue() : 0L; long fiatPrice = priceAsFiat.get() != null ? priceAsFiat.get().getValue() : 0L;
double percentagePrice = 0;
long amount = amountAsCoin.get() != null ? amountAsCoin.get().getValue() : 0L; long amount = amountAsCoin.get() != null ? amountAsCoin.get().getValue() : 0L;
long minAmount = minAmountAsCoin.get() != null ? minAmountAsCoin.get().getValue() : 0L; long minAmount = minAmountAsCoin.get() != null ? minAmountAsCoin.get().getValue() : 0L;
@ -283,12 +282,15 @@ class CreateOfferDataModel extends ActivatableDataModel {
String countryCode = paymentAccount instanceof CountryBasedPaymentAccount ? ((CountryBasedPaymentAccount) paymentAccount).getCountry().code : null; String countryCode = paymentAccount instanceof CountryBasedPaymentAccount ? ((CountryBasedPaymentAccount) paymentAccount).getCountry().code : null;
checkNotNull(p2PService.getAddress(), "Address must not be null"); checkNotNull(p2PService.getAddress(), "Address must not be null");
log.error("fiatPrice " + fiatPrice);
log.error("percentageBasedPrice " + percentageBasedPrice);
log.error("usePercentageBasedPrice " + usePercentageBasedPrice.get());
return new Offer(offerId, return new Offer(offerId,
p2PService.getAddress(), p2PService.getAddress(),
keyRing.getPubKeyRing(), keyRing.getPubKeyRing(),
direction, direction,
fiatPrice, fiatPrice,
percentagePrice, percentageBasedPrice,
usePercentageBasedPrice.get(), usePercentageBasedPrice.get(),
amount, amount,
minAmount, minAmount,
@ -494,4 +496,12 @@ class CreateOfferDataModel extends ActivatableDataModel {
walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING); walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE); walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE);
} }
double getPercentageBasedPrice() {
return percentageBasedPrice;
}
void setPercentageBasedPrice(double percentageBasedPrice) {
this.percentageBasedPrice = percentageBasedPrice;
}
} }

View file

@ -106,7 +106,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private ChangeListener<Boolean> amountFocusedListener; private ChangeListener<Boolean> amountFocusedListener;
private ChangeListener<Boolean> minAmountFocusedListener; private ChangeListener<Boolean> minAmountFocusedListener;
private ChangeListener<Boolean> priceFocusedListener; private ChangeListener<Boolean> priceFocusedListener, priceAsPercentageFocusedListener;
private ChangeListener<Boolean> volumeFocusedListener; private ChangeListener<Boolean> volumeFocusedListener;
private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener; private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener;
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener; private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
@ -286,6 +286,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.setMouseTransparent(true); amountTextField.setMouseTransparent(true);
minAmountTextField.setMouseTransparent(true); minAmountTextField.setMouseTransparent(true);
priceTextField.setMouseTransparent(true); priceTextField.setMouseTransparent(true);
priceAsPercentageTextField.setMouseTransparent(true);
fixedPriceButton.setMouseTransparent(true);
percentagePriceButton.setMouseTransparent(true);
volumeTextField.setMouseTransparent(true); volumeTextField.setMouseTransparent(true);
currencyComboBox.setMouseTransparent(true); currencyComboBox.setMouseTransparent(true);
paymentAccountsComboBox.setMouseTransparent(true); paymentAccountsComboBox.setMouseTransparent(true);
@ -414,7 +417,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountBtcLabel.textProperty().bind(model.btcCode); amountBtcLabel.textProperty().bind(model.btcCode);
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> model.tradeCurrencyCode.get() + "/" + model.btcCode.get(), model.btcCode, model.tradeCurrencyCode)); priceCurrencyLabel.textProperty().bind(createStringBinding(() -> model.tradeCurrencyCode.get() + "/" + model.btcCode.get(), model.btcCode, model.tradeCurrencyCode));
priceTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice); priceTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice);
priceCurrencyLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice);
priceAsPercentageTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not()); priceAsPercentageTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not());
priceAsPercentageLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not());
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty()); priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode); volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode);
minAmountBtcLabel.textProperty().bind(model.btcCode); minAmountBtcLabel.textProperty().bind(model.btcCode);
@ -423,6 +428,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.textProperty().bindBidirectional(model.amount); amountTextField.textProperty().bindBidirectional(model.amount);
minAmountTextField.textProperty().bindBidirectional(model.minAmount); minAmountTextField.textProperty().bindBidirectional(model.minAmount);
priceTextField.textProperty().bindBidirectional(model.price); priceTextField.textProperty().bindBidirectional(model.price);
priceAsPercentageTextField.textProperty().bindBidirectional(model.priceAsPercentage);
volumeTextField.textProperty().bindBidirectional(model.volume); volumeTextField.textProperty().bindBidirectional(model.volume);
volumeTextField.promptTextProperty().bind(model.volumePromptLabel); volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
totalToPayTextField.textProperty().bind(model.totalToPay); totalToPayTextField.textProperty().bind(model.totalToPay);
@ -462,7 +468,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountBtcLabel.textProperty().unbind(); amountBtcLabel.textProperty().unbind();
priceCurrencyLabel.textProperty().unbind(); priceCurrencyLabel.textProperty().unbind();
priceTextField.disableProperty().unbind(); priceTextField.disableProperty().unbind();
priceCurrencyLabel.disableProperty().unbind();
priceAsPercentageTextField.disableProperty().unbind(); priceAsPercentageTextField.disableProperty().unbind();
priceAsPercentageLabel.disableProperty().unbind();
volumeCurrencyLabel.textProperty().unbind(); volumeCurrencyLabel.textProperty().unbind();
minAmountBtcLabel.textProperty().unbind(); minAmountBtcLabel.textProperty().unbind();
priceDescriptionLabel.textProperty().unbind(); priceDescriptionLabel.textProperty().unbind();
@ -470,6 +478,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.textProperty().unbindBidirectional(model.amount); amountTextField.textProperty().unbindBidirectional(model.amount);
minAmountTextField.textProperty().unbindBidirectional(model.minAmount); minAmountTextField.textProperty().unbindBidirectional(model.minAmount);
priceTextField.textProperty().unbindBidirectional(model.price); priceTextField.textProperty().unbindBidirectional(model.price);
priceAsPercentageTextField.textProperty().unbindBidirectional(model.priceAsPercentage);
priceAsPercentageLabel.prefWidthProperty().unbind(); priceAsPercentageLabel.prefWidthProperty().unbind();
volumeTextField.textProperty().unbindBidirectional(model.volume); volumeTextField.textProperty().unbindBidirectional(model.volume);
volumeTextField.promptTextProperty().unbindBidirectional(model.volume); volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
@ -536,6 +545,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText()); model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText());
priceTextField.setText(model.price.get()); priceTextField.setText(model.price.get());
}; };
priceAsPercentageFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutPriceAsPercentageTextField(oldValue, newValue, priceAsPercentageTextField.getText());
priceAsPercentageTextField.setText(model.priceAsPercentage.get());
};
volumeFocusedListener = (o, oldValue, newValue) -> { volumeFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText()); model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
volumeTextField.setText(model.volume.get()); volumeTextField.setText(model.volume.get());
@ -595,6 +608,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
tradeCurrencyCodeListener = (observable, oldValue, newValue) -> { tradeCurrencyCodeListener = (observable, oldValue, newValue) -> {
priceTextField.clear(); priceTextField.clear();
priceAsPercentageTextField.clear();
volumeTextField.clear(); volumeTextField.clear();
}; };
@ -633,6 +647,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.focusedProperty().addListener(amountFocusedListener); amountTextField.focusedProperty().addListener(amountFocusedListener);
minAmountTextField.focusedProperty().addListener(minAmountFocusedListener); minAmountTextField.focusedProperty().addListener(minAmountFocusedListener);
priceTextField.focusedProperty().addListener(priceFocusedListener); priceTextField.focusedProperty().addListener(priceFocusedListener);
priceAsPercentageTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().addListener(volumeFocusedListener); volumeTextField.focusedProperty().addListener(volumeFocusedListener);
// warnings // warnings
@ -656,6 +671,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.focusedProperty().removeListener(amountFocusedListener); amountTextField.focusedProperty().removeListener(amountFocusedListener);
minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener); minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener);
priceTextField.focusedProperty().removeListener(priceFocusedListener); priceTextField.focusedProperty().removeListener(priceFocusedListener);
priceAsPercentageTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().removeListener(volumeFocusedListener); volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
// warnings // warnings

View file

@ -71,6 +71,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final StringProperty amount = new SimpleStringProperty(); final StringProperty amount = new SimpleStringProperty();
final StringProperty minAmount = new SimpleStringProperty(); final StringProperty minAmount = new SimpleStringProperty();
final StringProperty price = new SimpleStringProperty(); final StringProperty price = new SimpleStringProperty();
final StringProperty priceAsPercentage = new SimpleStringProperty();
final StringProperty volume = new SimpleStringProperty(); final StringProperty volume = new SimpleStringProperty();
final StringProperty volumeDescriptionLabel = new SimpleStringProperty(); final StringProperty volumeDescriptionLabel = new SimpleStringProperty();
final StringProperty volumePromptLabel = new SimpleStringProperty(); final StringProperty volumePromptLabel = new SimpleStringProperty();
@ -103,7 +104,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<String> amountListener; private ChangeListener<String> amountListener;
private ChangeListener<String> minAmountListener; private ChangeListener<String> minAmountListener;
private ChangeListener<String> priceListener; private ChangeListener<String> priceListener, priceAsPercentageListener;
private ChangeListener<String> volumeListener; private ChangeListener<String> volumeListener;
private ChangeListener<Coin> amountAsCoinListener; private ChangeListener<Coin> amountAsCoinListener;
private ChangeListener<Coin> minAmountAsCoinListener; private ChangeListener<Coin> minAmountAsCoinListener;
@ -114,6 +115,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<String> errorMessageListener; private ChangeListener<String> errorMessageListener;
private Offer offer; private Offer offer;
private Timer timeoutTimer; private Timer timeoutTimer;
private PriceFeed.Type priceFeedType;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -235,9 +237,61 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
setPriceToModel(); setPriceToModel();
calculateVolume(); calculateVolume();
dataModel.calculateTotalToPay(); dataModel.calculateTotalToPay();
MarketPrice marketPrice = priceFeed.getMarketPrice(dataModel.tradeCurrencyCode.get());
if (marketPrice != null) {
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
try {
double priceAsDouble = formatter.parseNumberStringToDouble(price.get());
double priceFactor = priceAsDouble / marketPriceAsDouble;
priceFactor = dataModel.getDirection() == Offer.Direction.BUY ? 1 - priceFactor : 1 + priceFactor;
priceAsPercentage.set(formatter.formatToPercent(priceFactor, 2));
} catch (NumberFormatException t) {
priceAsPercentage.set("");
new Popup().warning("Your input is not a valid number.")
.show();
}
}
} }
updateButtonDisableState(); updateButtonDisableState();
}; };
priceAsPercentageListener = (ov, oldValue, newValue) -> {
try {
if (!newValue.isEmpty() && !newValue.equals("-")) {
double percentageBasedPrice = formatter.parsePercentStringToDouble(newValue);
if (percentageBasedPrice >= 1 || percentageBasedPrice <= -1) {
dataModel.setPercentageBasedPrice(0);
UserThread.execute(() -> priceAsPercentage.set("0"));
new Popup().warning("You cannot set a percentage of 100% or larger. Please enter a percentage number like \"5.4\" for 5.4%")
.show();
} else {
MarketPrice marketPrice = priceFeed.getMarketPrice(dataModel.tradeCurrencyCode.get());
if (marketPrice != null) {
percentageBasedPrice = formatter.roundDouble(percentageBasedPrice, 4);
dataModel.setPercentageBasedPrice(percentageBasedPrice);
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
double factor = dataModel.getDirection() == Offer.Direction.BUY ? 1 - percentageBasedPrice : 1 + percentageBasedPrice;
double targetPrice = marketPriceAsDouble * factor;
price.set(formatter.formatToNumberString(targetPrice, 2));
setPriceToModel();
calculateVolume();
dataModel.calculateTotalToPay();
updateButtonDisableState();
} else {
new Popup().warning("There is no price feed available for that currency. You cannot use percent based price.")
.show();
}
}
} else {
dataModel.setPercentageBasedPrice(0);
}
} catch (Throwable t) {
dataModel.setPercentageBasedPrice(0);
UserThread.execute(() -> priceAsPercentage.set("0"));
new Popup().warning("Your input is not a valid number. Please enter a percentage number like \"5.4\" for 5.4%")
.show();
}
};
volumeListener = (ov, oldValue, newValue) -> { volumeListener = (ov, oldValue, newValue) -> {
if (isFiatInputValid(newValue).isValid) { if (isFiatInputValid(newValue).isValid) {
setVolumeToModel(); setVolumeToModel();
@ -266,6 +320,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
amount.addListener(amountListener); amount.addListener(amountListener);
minAmount.addListener(minAmountListener); minAmount.addListener(minAmountListener);
price.addListener(priceListener); price.addListener(priceListener);
priceAsPercentage.addListener(priceAsPercentageListener);
volume.addListener(volumeListener); volume.addListener(volumeListener);
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding // Binding with Bindings.createObjectBinding does not work because of bi-directional binding
@ -282,6 +337,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
amount.removeListener(amountListener); amount.removeListener(amountListener);
minAmount.removeListener(minAmountListener); minAmount.removeListener(minAmountListener);
price.removeListener(priceListener); price.removeListener(priceListener);
priceAsPercentage.removeListener(priceAsPercentageListener);
volume.removeListener(volumeListener); volume.removeListener(volumeListener);
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding // Binding with Bindings.createObjectBinding does not work because of bi-directional binding
@ -307,6 +363,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
if (dataModel.paymentAccount != null) if (dataModel.paymentAccount != null)
btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit()); btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
priceFeedType = direction == Offer.Direction.BUY ? PriceFeed.Type.ASK : PriceFeed.Type.BID;
return result; return result;
} }
@ -464,6 +522,12 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
} }
} }
void onFocusOutPriceAsPercentageTextField(boolean oldValue, boolean newValue, String userInput) {
if (oldValue && !newValue) {
priceAsPercentage.set(formatter.formatToNumberString(dataModel.getPercentageBasedPrice() * 100, 2));
}
}
void onFocusOutVolumeTextField(boolean oldValue, boolean newValue, String userInput) { void onFocusOutVolumeTextField(boolean oldValue, boolean newValue, String userInput) {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isFiatInputValid(volume.get()); InputValidator.ValidationResult result = isFiatInputValid(volume.get());
@ -492,21 +556,12 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
public boolean isPriceInRange() { public boolean isPriceInRange() {
MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode()); MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode());
if (marketPrice != null) { if (marketPrice != null) {
double marketPriceAsDouble = marketPrice.getPrice(PriceFeed.Type.LAST); double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
Fiat priceAsFiat = dataModel.priceAsFiat.get(); Fiat priceAsFiat = dataModel.priceAsFiat.get();
long shiftDivisor = checkedPow(10, priceAsFiat.smallestUnitExponent()); long shiftDivisor = checkedPow(10, priceAsFiat.smallestUnitExponent());
double offerPrice = ((double) priceAsFiat.longValue()) / ((double) shiftDivisor); double offerPrice = ((double) priceAsFiat.longValue()) / ((double) shiftDivisor);
if (marketPriceAsDouble != 0 && Math.abs(1 - (offerPrice / marketPriceAsDouble)) > preferences.getMaxPriceDistanceInPercent()) { if (marketPriceAsDouble != 0 && Math.abs(1 - (offerPrice / marketPriceAsDouble)) > preferences.getMaxPriceDistanceInPercent()) {
Popup popup = new Popup(); displayPriceOutofRangePopup();
popup.warning("The price you have entered is outside the max. allowed deviation from the market price.\n" +
"The max. allowed deviation is " +
formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent()) +
" and can be adjusted in the preferences.")
.actionButtonText("Change price")
.onAction(() -> popup.hide())
.closeButtonText("Go to \"Preferences\"")
.onClose(() -> navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class))
.show();
return false; return false;
} else { } else {
return true; return true;
@ -516,6 +571,19 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
} }
} }
private void displayPriceOutofRangePopup() {
Popup popup = new Popup();
popup.warning("The price you have entered is outside the max. allowed deviation from the market price.\n" +
"The max. allowed deviation is " +
formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()) +
" and can be adjusted in the preferences.")
.actionButtonText("Change price")
.onAction(() -> popup.hide())
.closeButtonText("Go to \"Preferences\"")
.onClose(() -> navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class))
.show();
}
BSFormatter getFormatter() { BSFormatter getFormatter() {
return formatter; return formatter;
} }

View file

@ -296,19 +296,16 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
deviationListener = (observable, oldValue, newValue) -> { deviationListener = (observable, oldValue, newValue) -> {
try { try {
String input = newValue.replace("%", ""); double value = formatter.parsePercentStringToDouble(newValue);
input = input.replace(",", "."); preferences.setMaxPriceDistanceInPercent(value);
input = input.replace(" ", ""); } catch (NumberFormatException t) {
double value = Double.parseDouble(input);
preferences.setMaxPriceDistanceInPercent(value / 100);
} catch (Throwable t) {
log.error("Exception at parseDouble deviation: " + t.toString()); log.error("Exception at parseDouble deviation: " + t.toString());
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS); UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
} }
}; };
deviationFocusedListener = (observable1, oldValue1, newValue1) -> { deviationFocusedListener = (observable1, oldValue1, newValue1) -> {
if (oldValue1 && !newValue1) if (oldValue1 && !newValue1)
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS); UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
}; };
transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Withdrawal transaction fee (satoshi/byte):").second; transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Withdrawal transaction fee (satoshi/byte):").second;
@ -427,7 +424,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
}); });
blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem())); blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem()));
deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())); deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()));
deviationInputTextField.textProperty().addListener(deviationListener); deviationInputTextField.textProperty().addListener(deviationListener);
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener); deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);

View file

@ -325,11 +325,58 @@ public class BSFormatter {
} }
public String formatToPercent(double value) { public String formatToPercent(double value) {
return formatToPercent(value, 1);
}
public String formatToPercent(double value, int digits) {
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale); DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale);
decimalFormat.setMinimumFractionDigits(1); decimalFormat.setMinimumFractionDigits(digits);
decimalFormat.setMaximumFractionDigits(1); decimalFormat.setMaximumFractionDigits(digits);
decimalFormat.setGroupingUsed(false); decimalFormat.setGroupingUsed(false);
return decimalFormat.format(value * 100.0) + " %"; return decimalFormat.format(value * 100.0);
}
public String formatToNumberString(double value, int digits) {
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale);
decimalFormat.setMinimumFractionDigits(digits);
decimalFormat.setMaximumFractionDigits(digits);
decimalFormat.setGroupingUsed(false);
return decimalFormat.format(value);
}
public double parseNumberStringToDouble(String percentString) throws NumberFormatException {
try {
String input = percentString.replace(",", ".");
input = input.replace(" ", "");
return Double.parseDouble(input);
} catch (NumberFormatException e) {
throw e;
}
}
public String formatToPercentWithSymbol(double value) {
return formatToPercent(value) + " %";
}
public double parsePercentStringToDouble(String percentString) throws NumberFormatException {
try {
String input = percentString.replace("%", "");
input = input.replace(",", ".");
input = input.replace(" ", "");
double value = Double.parseDouble(input);
return value / 100;
} catch (NumberFormatException e) {
throw e;
}
}
public double roundDouble(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (double) tmp / factor;
} }
private String cleanInput(String input) { private String cleanInput(String input) {