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 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 +

View File

@ -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;
}

View File

@ -97,7 +97,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> minAmountAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Double> priceAsPercentage = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> 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;
}
}

View File

@ -106,7 +106,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private ChangeListener<Boolean> amountFocusedListener;
private ChangeListener<Boolean> minAmountFocusedListener;
private ChangeListener<Boolean> priceFocusedListener;
private ChangeListener<Boolean> priceFocusedListener, priceAsPercentageFocusedListener;
private ChangeListener<Boolean> volumeFocusedListener;
private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener;
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
@ -286,6 +286,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.setMouseTransparent(true);
minAmountTextField.setMouseTransparent(true);
priceTextField.setMouseTransparent(true);
priceAsPercentageTextField.setMouseTransparent(true);
fixedPriceButton.setMouseTransparent(true);
percentagePriceButton.setMouseTransparent(true);
volumeTextField.setMouseTransparent(true);
currencyComboBox.setMouseTransparent(true);
paymentAccountsComboBox.setMouseTransparent(true);
@ -414,7 +417,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountBtcLabel.textProperty().bind(model.btcCode);
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> 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<AnchorPane, CreateO
amountTextField.textProperty().bindBidirectional(model.amount);
minAmountTextField.textProperty().bindBidirectional(model.minAmount);
priceTextField.textProperty().bindBidirectional(model.price);
priceAsPercentageTextField.textProperty().bindBidirectional(model.priceAsPercentage);
volumeTextField.textProperty().bindBidirectional(model.volume);
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
totalToPayTextField.textProperty().bind(model.totalToPay);
@ -462,7 +468,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountBtcLabel.textProperty().unbind();
priceCurrencyLabel.textProperty().unbind();
priceTextField.disableProperty().unbind();
priceCurrencyLabel.disableProperty().unbind();
priceAsPercentageTextField.disableProperty().unbind();
priceAsPercentageLabel.disableProperty().unbind();
volumeCurrencyLabel.textProperty().unbind();
minAmountBtcLabel.textProperty().unbind();
priceDescriptionLabel.textProperty().unbind();
@ -470,6 +478,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.textProperty().unbindBidirectional(model.amount);
minAmountTextField.textProperty().unbindBidirectional(model.minAmount);
priceTextField.textProperty().unbindBidirectional(model.price);
priceAsPercentageTextField.textProperty().unbindBidirectional(model.priceAsPercentage);
priceAsPercentageLabel.prefWidthProperty().unbind();
volumeTextField.textProperty().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());
priceTextField.setText(model.price.get());
};
priceAsPercentageFocusedListener = (o, oldValue, newValue) -> {
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<AnchorPane, CreateO
tradeCurrencyCodeListener = (observable, oldValue, newValue) -> {
priceTextField.clear();
priceAsPercentageTextField.clear();
volumeTextField.clear();
};
@ -633,6 +647,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.focusedProperty().addListener(amountFocusedListener);
minAmountTextField.focusedProperty().addListener(minAmountFocusedListener);
priceTextField.focusedProperty().addListener(priceFocusedListener);
priceAsPercentageTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
// warnings
@ -656,6 +671,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
amountTextField.focusedProperty().removeListener(amountFocusedListener);
minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener);
priceTextField.focusedProperty().removeListener(priceFocusedListener);
priceAsPercentageTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
// warnings
@ -975,7 +991,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
priceAsPercentageTextField.setPromptText("Enter % value");
priceAsPercentageLabel.setText("% dist.");
priceAsPercentageLabel.setStyle("-fx-alignment: center;");
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
minAmountTextField = amountValueCurrencyBoxTuple.second;

View File

@ -71,6 +71,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final StringProperty amount = new SimpleStringProperty();
final StringProperty minAmount = new SimpleStringProperty();
final StringProperty price = new SimpleStringProperty();
final StringProperty priceAsPercentage = new SimpleStringProperty();
final StringProperty volume = new SimpleStringProperty();
final StringProperty volumeDescriptionLabel = new SimpleStringProperty();
final StringProperty volumePromptLabel = new SimpleStringProperty();
@ -103,7 +104,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<String> amountListener;
private ChangeListener<String> minAmountListener;
private ChangeListener<String> priceListener;
private ChangeListener<String> priceListener, priceAsPercentageListener;
private ChangeListener<String> volumeListener;
private ChangeListener<Coin> amountAsCoinListener;
private ChangeListener<Coin> minAmountAsCoinListener;
@ -114,6 +115,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<String> errorMessageListener;
private Offer offer;
private Timer timeoutTimer;
private PriceFeed.Type priceFeedType;
///////////////////////////////////////////////////////////////////////////////////////////
@ -235,9 +237,61 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
setPriceToModel();
calculateVolume();
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();
};
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) -> {
if (isFiatInputValid(newValue).isValid) {
setVolumeToModel();
@ -266,6 +320,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
amount.addListener(amountListener);
minAmount.addListener(minAmountListener);
price.addListener(priceListener);
priceAsPercentage.addListener(priceAsPercentageListener);
volume.addListener(volumeListener);
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
@ -282,6 +337,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
amount.removeListener(amountListener);
minAmount.removeListener(minAmountListener);
price.removeListener(priceListener);
priceAsPercentage.removeListener(priceAsPercentageListener);
volume.removeListener(volumeListener);
// 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)
btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
priceFeedType = direction == Offer.Direction.BUY ? PriceFeed.Type.ASK : PriceFeed.Type.BID;
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) {
if (oldValue && !newValue) {
InputValidator.ValidationResult result = isFiatInputValid(volume.get());
@ -492,21 +556,12 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
public boolean isPriceInRange() {
MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode());
if (marketPrice != null) {
double marketPriceAsDouble = marketPrice.getPrice(PriceFeed.Type.LAST);
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
Fiat priceAsFiat = dataModel.priceAsFiat.get();
long shiftDivisor = checkedPow(10, priceAsFiat.smallestUnitExponent());
double offerPrice = ((double) priceAsFiat.longValue()) / ((double) shiftDivisor);
if (marketPriceAsDouble != 0 && Math.abs(1 - (offerPrice / marketPriceAsDouble)) > 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<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() {
return formatter;
}

View File

@ -296,19 +296,16 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
deviationListener = (observable, oldValue, newValue) -> {
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<GridPane, Activatab
});
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.focusedProperty().addListener(deviationFocusedListener);

View File

@ -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) {