From d5118b048b7d6d729ae2f9dc03678663c052d26f Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Thu, 14 Apr 2016 21:59:14 +0200 Subject: [PATCH] perc. price UI (WIP) --- .../java/io/bitsquare/trade/offer/Offer.java | 16 ++-- .../io/bitsquare/gui/main/MainViewModel.java | 2 +- .../createoffer/CreateOfferDataModel.java | 16 +++- .../offer/createoffer/CreateOfferView.java | 20 +++- .../createoffer/CreateOfferViewModel.java | 92 ++++++++++++++++--- .../settings/preferences/PreferencesView.java | 15 ++- .../io/bitsquare/gui/util/BSFormatter.java | 53 ++++++++++- 7 files changed, 176 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index 31a0eed0a6..6fc326b289 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -106,7 +106,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload private final long date; private final long protocolVersion; private final long fiatPrice; - private final double percentagePrice; + private final double percentageBasedPrice; private final boolean usePercentageBasedPrice; private final long amount; private final long minAmount; @@ -140,7 +140,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload PubKeyRing pubKeyRing, Direction direction, long fiatPrice, - double percentagePrice, + double percentageBasedPrice, boolean usePercentageBasedPrice, long amount, long minAmount, @@ -157,7 +157,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload this.pubKeyRing = pubKeyRing; this.direction = direction; this.fiatPrice = fiatPrice; - this.percentagePrice = percentagePrice; + this.percentageBasedPrice = percentageBasedPrice; this.usePercentageBasedPrice = usePercentageBasedPrice; this.amount = amount; this.minAmount = minAmount; @@ -321,8 +321,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload return Fiat.valueOf(currencyCode, fiatPrice); } - public double getPercentagePrice() { - return percentagePrice; + public double getPercentageBasedPrice() { + return percentageBasedPrice; } public boolean isUsePercentageBasedPrice() { @@ -408,7 +408,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload Offer offer = (Offer) o; if (date != offer.date) 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 (amount != offer.amount) 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 + (int) (date ^ (date >>> 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 + (usePercentageBasedPrice ? 1 : 0); result = 31 * result + (int) (amount ^ (amount >>> 32)); @@ -467,7 +467,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload "\n\tcurrencyCode='" + currencyCode + '\'' + "\n\tdate=" + date + "\n\tfiatPrice=" + fiatPrice + - "\n\tpercentagePrice=" + percentagePrice + + "\n\tpercentagePrice=" + percentageBasedPrice + "\n\tusePercentageBasedPrice=" + usePercentageBasedPrice + "\n\tamount=" + amount + "\n\tminAmount=" + minAmount + diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index b8ef9a54d7..efb4140b77 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -404,7 +404,7 @@ public class MainViewModel implements ViewModel { result = numPeersString + " / synchronized with " + btcNetworkAsString; btcSplashSyncIconId.set("image-connection-synced"); } else if (percentage > 0.0) { - result = numPeersString + " / synchronizing with " + btcNetworkAsString + ": " + formatter.formatToPercent(percentage); + result = numPeersString + " / synchronizing with " + btcNetworkAsString + ": " + formatter.formatToPercentWithSymbol(percentage); } else { result = numPeersString + " / connecting to " + btcNetworkAsString; } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index ca3ac4c378..b7b464fc14 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -97,7 +97,6 @@ class CreateOfferDataModel extends ActivatableDataModel { final ObjectProperty amountAsCoin = new SimpleObjectProperty<>(); final ObjectProperty minAmountAsCoin = new SimpleObjectProperty<>(); final ObjectProperty priceAsFiat = new SimpleObjectProperty<>(); - final ObjectProperty priceAsPercentage = new SimpleObjectProperty<>(); final ObjectProperty volumeAsFiat = new SimpleObjectProperty<>(); final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty missingCoin = new SimpleObjectProperty<>(Coin.ZERO); @@ -110,6 +109,7 @@ class CreateOfferDataModel extends ActivatableDataModel { private Notification walletFundedNotification; boolean useSavingsWallet; Coin totalAvailableBalance; + private double percentageBasedPrice = 0; /////////////////////////////////////////////////////////////////////////////////////////// @@ -256,7 +256,6 @@ class CreateOfferDataModel extends ActivatableDataModel { Offer createAndGetOffer() { long fiatPrice = priceAsFiat.get() != null ? priceAsFiat.get().getValue() : 0L; - double percentagePrice = 0; long amount = amountAsCoin.get() != null ? amountAsCoin.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; 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, p2PService.getAddress(), keyRing.getPubKeyRing(), direction, fiatPrice, - percentagePrice, + percentageBasedPrice, usePercentageBasedPrice.get(), amount, minAmount, @@ -494,4 +496,12 @@ class CreateOfferDataModel extends ActivatableDataModel { walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING); walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE); } + + double getPercentageBasedPrice() { + return percentageBasedPrice; + } + + void setPercentageBasedPrice(double percentageBasedPrice) { + this.percentageBasedPrice = percentageBasedPrice; + } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index e49fc624a6..d43e236f51 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -106,7 +106,7 @@ public class CreateOfferView extends ActivatableViewAndModel amountFocusedListener; private ChangeListener minAmountFocusedListener; - private ChangeListener priceFocusedListener; + private ChangeListener priceFocusedListener, priceAsPercentageFocusedListener; private ChangeListener volumeFocusedListener; private ChangeListener showWarningInvalidBtcDecimalPlacesListener; private ChangeListener showWarningInvalidFiatDecimalPlacesPlacesListener; @@ -286,6 +286,9 @@ public class CreateOfferView extends ActivatableViewAndModel model.tradeCurrencyCode.get() + "/" + model.btcCode.get(), model.btcCode, model.tradeCurrencyCode)); priceTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice); + priceCurrencyLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice); priceAsPercentageTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not()); + priceAsPercentageLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not()); priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty()); volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode); minAmountBtcLabel.textProperty().bind(model.btcCode); @@ -423,6 +428,7 @@ public class CreateOfferView extends ActivatableViewAndModel { + model.onFocusOutPriceAsPercentageTextField(oldValue, newValue, priceAsPercentageTextField.getText()); + priceAsPercentageTextField.setText(model.priceAsPercentage.get()); + }; volumeFocusedListener = (o, oldValue, newValue) -> { model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText()); volumeTextField.setText(model.volume.get()); @@ -595,6 +608,7 @@ public class CreateOfferView extends ActivatableViewAndModel { priceTextField.clear(); + priceAsPercentageTextField.clear(); volumeTextField.clear(); }; @@ -633,6 +647,7 @@ public class CreateOfferView extends ActivatableViewAndModel amountValueCurrencyBoxTuple = getValueCurrencyBox(BSResources.get("createOffer.amount.prompt")); HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first; minAmountTextField = amountValueCurrencyBoxTuple.second; diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java index 874befc5c1..01376879da 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModel.java @@ -71,6 +71,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel amountListener; private ChangeListener minAmountListener; - private ChangeListener priceListener; + private ChangeListener priceListener, priceAsPercentageListener; private ChangeListener volumeListener; private ChangeListener amountAsCoinListener; private ChangeListener minAmountAsCoinListener; @@ -114,6 +115,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel errorMessageListener; private Offer offer; private Timer timeoutTimer; + private PriceFeed.Type priceFeedType; /////////////////////////////////////////////////////////////////////////////////////////// @@ -235,9 +237,61 @@ class CreateOfferViewModel extends ActivatableWithDataModel { + 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) -> { if (isFiatInputValid(newValue).isValid) { setVolumeToModel(); @@ -266,6 +320,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel preferences.getMaxPriceDistanceInPercent()) { - 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.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(); + displayPriceOutofRangePopup(); return false; } else { return true; @@ -516,6 +571,19 @@ class CreateOfferViewModel extends ActivatableWithDataModel popup.hide()) + .closeButtonText("Go to \"Preferences\"") + .onClose(() -> navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class)) + .show(); + } + BSFormatter getFormatter() { return formatter; } diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java b/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java index 39f48471d7..7501d845aa 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/preferences/PreferencesView.java @@ -296,19 +296,16 @@ public class PreferencesView extends ActivatableViewAndModel { try { - String input = newValue.replace("%", ""); - input = input.replace(",", "."); - input = input.replace(" ", ""); - double value = Double.parseDouble(input); - preferences.setMaxPriceDistanceInPercent(value / 100); - } catch (Throwable t) { + double value = formatter.parsePercentStringToDouble(newValue); + preferences.setMaxPriceDistanceInPercent(value); + } catch (NumberFormatException t) { 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) -> { 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; @@ -427,7 +424,7 @@ public class PreferencesView extends ActivatableViewAndModel preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem())); - deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())); + deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())); deviationInputTextField.textProperty().addListener(deviationListener); deviationInputTextField.focusedProperty().addListener(deviationFocusedListener); diff --git a/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java b/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java index 411e199b73..f0530c0ff3 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java +++ b/gui/src/main/java/io/bitsquare/gui/util/BSFormatter.java @@ -325,11 +325,58 @@ public class BSFormatter { } public String formatToPercent(double value) { + return formatToPercent(value, 1); + } + + public String formatToPercent(double value, int digits) { DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale); - decimalFormat.setMinimumFractionDigits(1); - decimalFormat.setMaximumFractionDigits(1); + decimalFormat.setMinimumFractionDigits(digits); + decimalFormat.setMaximumFractionDigits(digits); 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) {