fix missing formatting updates

This commit is contained in:
Manfred Karrer 2014-08-29 22:11:59 +02:00
parent 3c67b08ff9
commit 096f4c01e7
12 changed files with 298 additions and 266 deletions

View file

@ -44,10 +44,9 @@ It is the JavaFX controller associated with the FXML view, but we don't use the
association with the classical MVC controller. It gets created by the JavaFX framework (FXMLLoader) and also the association with the classical MVC controller. It gets created by the JavaFX framework (FXMLLoader) and also the
setup with the FXML view is done by the framework. setup with the FXML view is done by the framework.
It knows the presentation model but not the model. It knows the presentation model (via Guice injection) but not the model.
#####Responsibility: #####Responsibility:
* Creates presentation model and passes model from Guice injection to the presenter (might be changed).
* Setup binding for updates from PM to view elements (also bidirectional for used for input data). * Setup binding for updates from PM to view elements (also bidirectional for used for input data).
* Those binding are only simple bindings to plain presenter properties, no logical bindings. * Those binding are only simple bindings to plain presenter properties, no logical bindings.
* Listens to UI events (Actions) from UI controls and calls method in presentation model. * Listens to UI events (Actions) from UI controls and calls method in presentation model.
@ -62,7 +61,7 @@ It knows the presentation model but not the model.
It is the abstraction/presentation of the view. It is the abstraction/presentation of the view.
Can be used for unit testing. Can be used for unit testing.
It knows the model but it does not know the CodeBehind (View) It knows the model (via Guice injection) but it does not know the CodeBehind (View)
#####Responsibility: #####Responsibility:
* Holds the state of the view/CB * Holds the state of the view/CB

View file

@ -7,11 +7,16 @@ import java.util.ResourceBundle;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/**
* If caching is used for loader we use the CachedViewController for turning the controller into sleep mode if not
* active and awake it at reactivation.
* * @param <T> The PresentationModel used in that class
*/
public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T> { public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T> {
private static final Logger log = LoggerFactory.getLogger(CachedCodeBehind.class); private static final Logger log = LoggerFactory.getLogger(CachedCodeBehind.class);
public CachedCodeBehind(T pm) { public CachedCodeBehind(T presentationModel) {
super(pm); super(presentationModel);
} }
/** /**
@ -35,7 +40,7 @@ public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T>
}); });
activate(); activate();
pm.initialized(); presentationModel.initialized();
} }
/** /**
@ -45,7 +50,7 @@ public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T>
log.trace("Lifecycle: activate " + this.getClass().getSimpleName()); log.trace("Lifecycle: activate " + this.getClass().getSimpleName());
if (childController instanceof CachedViewController) ((CachedViewController) childController).activate(); if (childController instanceof CachedViewController) ((CachedViewController) childController).activate();
pm.activate(); presentationModel.activate();
} }
/** /**
@ -55,7 +60,7 @@ public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T>
log.trace("Lifecycle: deactivate " + this.getClass().getSimpleName()); log.trace("Lifecycle: deactivate " + this.getClass().getSimpleName());
if (childController instanceof CachedViewController) ((CachedViewController) childController).deactivate(); if (childController instanceof CachedViewController) ((CachedViewController) childController).deactivate();
pm.deactivate(); presentationModel.deactivate();
} }
/** /**
@ -67,7 +72,7 @@ public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T>
super.terminate(); super.terminate();
deactivate(); deactivate();
pm.terminate(); presentationModel.terminate();
} }
} }

View file

@ -28,6 +28,8 @@ import org.slf4j.LoggerFactory;
* If caching is used for loader we use the CachedViewController for turning the controller into sleep mode if not * If caching is used for loader we use the CachedViewController for turning the controller into sleep mode if not
* active and awake it at reactivation. * active and awake it at reactivation.
*/ */
// for new PM pattern use CachedCodeBehind
@Deprecated
public abstract class CachedViewController extends ViewController { public abstract class CachedViewController extends ViewController {
private static final Logger log = LoggerFactory.getLogger(CachedViewController.class); private static final Logger log = LoggerFactory.getLogger(CachedViewController.class);

View file

@ -11,25 +11,26 @@ import javafx.scene.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/**
* Non caching version for code behind classes using the PM pattern
*
* @param <T> The PresentationModel used in that class
*/
public class CodeBehind<T extends PresentationModel> implements Initializable { public class CodeBehind<T extends PresentationModel> implements Initializable {
private static final Logger log = LoggerFactory.getLogger(CodeBehind.class); private static final Logger log = LoggerFactory.getLogger(CodeBehind.class);
protected T pm; protected T presentationModel;
protected ViewController childController; protected ViewController childController;
protected ViewController parentController; protected ViewController parentController;
@FXML protected Parent root; @FXML protected Parent root;
public CodeBehind(T pm) { public CodeBehind(T presentationModel) {
this.pm = pm; this.presentationModel = presentationModel;
} }
public CodeBehind() { public CodeBehind() {
} }
public T pm() {
return (T) pm;
}
/** /**
* Get called form GUI framework when the UI is ready. * Get called form GUI framework when the UI is ready.
* *
@ -45,7 +46,7 @@ public class CodeBehind<T extends PresentationModel> implements Initializable {
if (oldValue != null && newValue == null) terminate(); if (oldValue != null && newValue == null) terminate();
}); });
pm.initialized(); presentationModel.initialized();
} }
/** /**
@ -56,7 +57,7 @@ public class CodeBehind<T extends PresentationModel> implements Initializable {
log.trace("Lifecycle: terminate " + this.getClass().getSimpleName()); log.trace("Lifecycle: terminate " + this.getClass().getSimpleName());
if (childController != null) childController.terminate(); if (childController != null) childController.terminate();
pm.terminate(); presentationModel.terminate();
} }
/** /**

View file

@ -2,11 +2,7 @@ package io.bitsquare.gui;
public class PresentationModel<T extends UIModel> { public class PresentationModel<T extends UIModel> {
private T model; protected T model;
public T model() {
return (T) model;
}
public PresentationModel(T model) { public PresentationModel(T model) {
this.model = model; this.model = model;

View file

@ -31,6 +31,8 @@ import org.slf4j.LoggerFactory;
/** /**
* Base class for all controllers. * Base class for all controllers.
*/ */
// for new PM pattern use CodeBehind
@Deprecated
public abstract class ViewController implements Initializable { public abstract class ViewController implements Initializable {
private static final Logger log = LoggerFactory.getLogger(ViewController.class); private static final Logger log = LoggerFactory.getLogger(ViewController.class);

View file

@ -35,11 +35,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* TextField with validation support. Validation is executed on the Validator object. * TextField with validation support.
* In case of a invalid result we display a error message with a PopOver. * In case the isValid property in amountValidationResultProperty get set to false we display a red border and an error
* The position is derived from the textField or if set from the errorPopupLayoutReference object. * message within the errorMessageDisplay placed on the right of the text field.
* <p> * The errorMessageDisplay gets closed when the ValidatingTextField instance gets removed from the scene graph or when
* That class implements just what we need for the moment. It is not intended as a general purpose library class. * hideErrorMessageDisplay() is called.
* There can be only 1 errorMessageDisplays at a time we use static field for it.
* The position is derived from the position of the textField itself or if set from the layoutReference node.
*/ */
public class ValidatingTextField extends TextField { public class ValidatingTextField extends TextField {
private static final Logger log = LoggerFactory.getLogger(ValidatingTextField.class); private static final Logger log = LoggerFactory.getLogger(ValidatingTextField.class);
@ -49,17 +51,17 @@ public class ValidatingTextField extends TextField {
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>(new final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>(new
InputValidator.ValidationResult(true)); InputValidator.ValidationResult(true));
private static PopOver popOver; private static PopOver errorMessageDisplay;
private Region errorPopupLayoutReference = this; private Region layoutReference = this;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Static // Static
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public static void hidePopover() { public static void hideErrorMessageDisplay() {
if (popOver != null) if (errorMessageDisplay != null)
popOver.hide(); errorMessageDisplay.hide();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -73,17 +75,16 @@ public class ValidatingTextField extends TextField {
setEffect(newValue.isValid ? null : invalidEffect); setEffect(newValue.isValid ? null : invalidEffect);
if (newValue.isValid) if (newValue.isValid)
hidePopover(); hideErrorMessageDisplay();
else else
applyErrorMessage(newValue); applyErrorMessage(newValue);
} }
}); });
sceneProperty().addListener((ov, oldValue, newValue) -> { sceneProperty().addListener((ov, oldValue, newValue) -> {
// we got removed from the scene // we got removed from the scene so hide the popup (if open)
// lets hide an open popup
if (newValue == null) if (newValue == null)
hidePopover(); hideErrorMessageDisplay();
}); });
} }
@ -99,11 +100,11 @@ public class ValidatingTextField extends TextField {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
/** /**
* @param errorPopupLayoutReference The node used as reference for positioning. If not set explicitely the * @param layoutReference The node used as reference for positioning. If not set explicitely the
* ValidatingTextField instance is used. * ValidatingTextField instance is used.
*/ */
public void setErrorPopupLayoutReference(Region errorPopupLayoutReference) { public void setLayoutReference(Region layoutReference) {
this.errorPopupLayoutReference = errorPopupLayoutReference; this.layoutReference = layoutReference;
} }
@ -122,25 +123,26 @@ public class ValidatingTextField extends TextField {
private void applyErrorMessage(InputValidator.ValidationResult validationResult) { private void applyErrorMessage(InputValidator.ValidationResult validationResult) {
if (validationResult.isValid) { if (validationResult.isValid) {
if (popOver != null) { if (errorMessageDisplay != null) {
popOver.hide(); errorMessageDisplay.hide();
} }
} }
else { else {
if (popOver == null) if (errorMessageDisplay == null)
createErrorPopOver(validationResult.errorMessage); createErrorPopOver(validationResult.errorMessage);
else else
((Label) popOver.getContentNode()).setText(validationResult.errorMessage); ((Label) errorMessageDisplay.getContentNode()).setText(validationResult.errorMessage);
popOver.show(getScene().getWindow(), getErrorPopupPosition().getX(), getErrorPopupPosition().getY()); errorMessageDisplay.show(getScene().getWindow(), getErrorPopupPosition().getX(),
getErrorPopupPosition().getY());
} }
} }
private Point2D getErrorPopupPosition() { private Point2D getErrorPopupPosition() {
Window window = getScene().getWindow(); Window window = getScene().getWindow();
Point2D point; Point2D point;
point = errorPopupLayoutReference.localToScene(0, 0); point = layoutReference.localToScene(0, 0);
double x = point.getX() + window.getX() + errorPopupLayoutReference.getWidth() + 20; double x = point.getX() + window.getX() + layoutReference.getWidth() + 20;
double y = point.getY() + window.getY() + Math.floor(getHeight() / 2); double y = point.getY() + window.getY() + Math.floor(getHeight() / 2);
return new Point2D(x, y); return new Point2D(x, y);
} }
@ -151,9 +153,9 @@ public class ValidatingTextField extends TextField {
errorLabel.setId("validation-error"); errorLabel.setId("validation-error");
errorLabel.setPadding(new Insets(0, 10, 0, 10)); errorLabel.setPadding(new Insets(0, 10, 0, 10));
popOver = new PopOver(errorLabel); ValidatingTextField.errorMessageDisplay = new PopOver(errorLabel);
popOver.setDetachable(false); ValidatingTextField.errorMessageDisplay.setDetachable(false);
popOver.setArrowIndent(5); ValidatingTextField.errorMessageDisplay.setArrowIndent(5);
} }
} }

View file

@ -78,7 +78,7 @@ public class TradeController extends CachedViewController {
//TODO update to new verison //TODO update to new verison
((TabPane) root).getSelectionModel().selectedIndexProperty().addListener((observableValue) -> ((TabPane) root).getSelectionModel().selectedIndexProperty().addListener((observableValue) ->
Platform.runLater(ValidatingTextField::hidePopover)); Platform.runLater(ValidatingTextField::hideErrorMessageDisplay));
} }

View file

@ -58,10 +58,9 @@ public class CreateOfferCB extends CachedCodeBehind<CreateOfferPM> {
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
//TODO find a better solution, handle at base class?
@Inject @Inject
public CreateOfferCB(CreateOfferModel model) { CreateOfferCB(CreateOfferPM presentationModel) {
super(new CreateOfferPM(model)); super(presentationModel);
} }
@ -76,7 +75,12 @@ public class CreateOfferCB extends CachedCodeBehind<CreateOfferPM> {
setupBindings(); setupBindings();
setupListeners(); setupListeners();
configTextFieldValidators(); configTextFieldValidators();
balanceTextField.setup(pm().getWalletFacade(), pm().address.get()); balanceTextField.setup(presentationModel.getWalletFacade(), presentationModel.address.get());
}
@Override
public void activate() {
super.activate();
} }
@Override @Override
@ -87,13 +91,18 @@ public class CreateOfferCB extends CachedCodeBehind<CreateOfferPM> {
if (parentController != null) ((TradeController) parentController).onCreateOfferViewRemoved(); if (parentController != null) ((TradeController) parentController).onCreateOfferViewRemoved();
} }
@Override
public void terminate() {
super.terminate();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Public methods // Public methods (called form other views/CB)
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void setOrderBookFilter(OrderBookFilter orderBookFilter) { public void setOrderBookFilter(OrderBookFilter orderBookFilter) {
pm().setOrderBookFilter(orderBookFilter); presentationModel.setOrderBookFilter(orderBookFilter);
} }
@ -103,13 +112,11 @@ public class CreateOfferCB extends CachedCodeBehind<CreateOfferPM> {
@FXML @FXML
public void onPlaceOffer() { public void onPlaceOffer() {
pm().placeOffer(); presentationModel.placeOffer();
} }
@FXML @FXML
public void onClose() { public void onClose() {
pm().close();
TabPane tabPane = ((TabPane) (root.getParent().getParent())); TabPane tabPane = ((TabPane) (root.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem()); tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
} }
@ -120,99 +127,104 @@ public class CreateOfferCB extends CachedCodeBehind<CreateOfferPM> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setupListeners() { private void setupListeners() {
volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> { // focus out
pm().onFocusOutVolumeTextField(oldValue, newValue);
volumeTextField.setText(pm().volume.get());
});
amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm().onFocusOutAmountTextField(oldValue, newValue); presentationModel.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
amountTextField.setText(pm().amount.get()); amountTextField.setText(presentationModel.amount.get());
});
priceTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm().onFocusOutPriceTextField(oldValue, newValue);
priceTextField.setText(pm().price.get());
}); });
minAmountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { minAmountTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm().onFocusOutMinAmountTextField(oldValue, newValue); presentationModel.onFocusOutMinAmountTextField(oldValue, newValue, minAmountTextField.getText());
minAmountTextField.setText(pm().minAmount.get()); minAmountTextField.setText(presentationModel.minAmount.get());
}); });
pm().showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> { priceTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
presentationModel.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText());
priceTextField.setText(presentationModel.price.get());
});
volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
presentationModel.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
volumeTextField.setText(presentationModel.volume.get());
});
// warnings
presentationModel.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> {
if (newValue) { if (newValue) {
Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" + Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" +
" places.\nThe amount has been adjusted to 4 decimal places."); " places.\nThe amount has been adjusted to 4 decimal places.");
pm().showWarningInvalidBtcDecimalPlaces.set(false); presentationModel.showWarningInvalidBtcDecimalPlaces.set(false);
} }
}); });
pm().showWarningInvalidFiatDecimalPlaces.addListener((o, oldValue, newValue) -> { presentationModel.showWarningInvalidFiatDecimalPlaces.addListener((o, oldValue, newValue) -> {
if (newValue) { if (newValue) {
Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" + Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" +
" places.\nThe amount has been adjusted to 2 decimal places."); " places.\nThe amount has been adjusted to 2 decimal places.");
pm().showWarningInvalidFiatDecimalPlaces.set(false); presentationModel.showWarningInvalidFiatDecimalPlaces.set(false);
} }
}); });
pm().showWarningAdjustedVolume.addListener((o, oldValue, newValue) -> { presentationModel.showWarningAdjustedVolume.addListener((o, oldValue, newValue) -> {
if (newValue) { if (newValue) {
Popups.openWarningPopup("Warning", "The total volume you have entered leads to invalid fractional " + Popups.openWarningPopup("Warning", "The total volume you have entered leads to invalid fractional " +
"Bitcoin amounts.\nThe amount has been adjusted and a new total volume be calculated from it."); "Bitcoin amounts.\nThe amount has been adjusted and a new total volume be calculated from it.");
pm().showWarningAdjustedVolume.set(false); presentationModel.showWarningAdjustedVolume.set(false);
volumeTextField.setText(pm().volume.get()); volumeTextField.setText(presentationModel.volume.get());
} }
}); });
pm().requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> { presentationModel.requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> {
if (newValue) { if (newValue) {
Popups.openErrorPopup("Error", "An error occurred when placing the offer.\n" + Popups.openErrorPopup("Error", "An error occurred when placing the offer.\n" +
pm().requestPlaceOfferErrorMessage); presentationModel.requestPlaceOfferErrorMessage);
pm().requestPlaceOfferFailed.set(false); presentationModel.requestPlaceOfferFailed.set(false);
} }
}); });
} }
private void setupBindings() { private void setupBindings() {
buyLabel.textProperty().bind(pm().directionLabel); buyLabel.textProperty().bind(presentationModel.directionLabel);
amountTextField.textProperty().bindBidirectional(pm().amount);
priceTextField.textProperty().bindBidirectional(pm().price);
volumeTextField.textProperty().bindBidirectional(pm().volume);
minAmountTextField.textProperty().bindBidirectional(pm().minAmount); amountTextField.textProperty().bindBidirectional(presentationModel.amount);
collateralLabel.textProperty().bind(pm().collateralLabel); minAmountTextField.textProperty().bindBidirectional(presentationModel.minAmount);
collateralTextField.textProperty().bind(pm().collateral); priceTextField.textProperty().bindBidirectional(presentationModel.price);
totalToPayTextField.textProperty().bind(pm().totalToPay); volumeTextField.textProperty().bindBidirectional(presentationModel.volume);
addressTextField.amountAsCoinProperty().bind(pm().totalToPayAsCoin); collateralLabel.textProperty().bind(presentationModel.collateralLabel);
addressTextField.paymentLabelProperty().bind(pm().paymentLabel); collateralTextField.textProperty().bind(presentationModel.collateral);
addressTextField.addressProperty().bind(pm().addressAsString); totalToPayTextField.textProperty().bind(presentationModel.totalToPay);
totalFeesTextField.textProperty().bind(presentationModel.totalFees);
bankAccountTypeTextField.textProperty().bind(pm().bankAccountType); addressTextField.amountAsCoinProperty().bind(presentationModel.totalToPayAsCoin);
bankAccountCurrencyTextField.textProperty().bind(pm().bankAccountCurrency); addressTextField.paymentLabelProperty().bind(presentationModel.paymentLabel);
bankAccountCountyTextField.textProperty().bind(pm().bankAccountCounty); addressTextField.addressProperty().bind(presentationModel.addressAsString);
acceptedCountriesTextField.textProperty().bind(pm().acceptedCountries); bankAccountTypeTextField.textProperty().bind(presentationModel.bankAccountType);
acceptedLanguagesTextField.textProperty().bind(pm().acceptedLanguages); bankAccountCurrencyTextField.textProperty().bind(presentationModel.bankAccountCurrency);
totalFeesTextField.textProperty().bind(pm().totalFees); bankAccountCountyTextField.textProperty().bind(presentationModel.bankAccountCounty);
transactionIdTextField.textProperty().bind(pm().transactionId);
amountTextField.amountValidationResultProperty().bind(pm().amountValidationResult); acceptedCountriesTextField.textProperty().bind(presentationModel.acceptedCountries);
minAmountTextField.amountValidationResultProperty().bind(pm().minAmountValidationResult); acceptedLanguagesTextField.textProperty().bind(presentationModel.acceptedLanguages);
priceTextField.amountValidationResultProperty().bind(pm().priceValidationResult); transactionIdTextField.textProperty().bind(presentationModel.transactionId);
volumeTextField.amountValidationResultProperty().bind(pm().volumeValidationResult);
placeOfferButton.visibleProperty().bind(pm().isPlaceOfferButtonVisible); // Validation
placeOfferButton.disableProperty().bind(pm().isPlaceOfferButtonDisabled); amountTextField.amountValidationResultProperty().bind(presentationModel.amountValidationResult);
closeButton.visibleProperty().bind(pm().isCloseButtonVisible); minAmountTextField.amountValidationResultProperty().bind(presentationModel.minAmountValidationResult);
priceTextField.amountValidationResultProperty().bind(presentationModel.priceValidationResult);
volumeTextField.amountValidationResultProperty().bind(presentationModel.volumeValidationResult);
// buttons
placeOfferButton.visibleProperty().bind(presentationModel.isPlaceOfferButtonVisible);
placeOfferButton.disableProperty().bind(presentationModel.isPlaceOfferButtonDisabled);
closeButton.visibleProperty().bind(presentationModel.isCloseButtonVisible);
} }
private void configTextFieldValidators() { private void configTextFieldValidators() {
Region referenceNode = (Region) amountTextField.getParent(); Region referenceNode = (Region) amountTextField.getParent();
amountTextField.setErrorPopupLayoutReference(referenceNode); amountTextField.setLayoutReference(referenceNode);
priceTextField.setErrorPopupLayoutReference(referenceNode); priceTextField.setLayoutReference(referenceNode);
volumeTextField.setErrorPopupLayoutReference(referenceNode); volumeTextField.setLayoutReference(referenceNode);
} }
} }

View file

@ -100,32 +100,18 @@ class CreateOfferModel extends UIModel {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public CreateOfferModel(TradeManager tradeManager, WalletFacade walletFacade, Settings settings, User user) { CreateOfferModel(TradeManager tradeManager, WalletFacade walletFacade, Settings settings, User user) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.walletFacade = walletFacade; this.walletFacade = walletFacade;
this.settings = settings; this.settings = settings;
this.user = user; this.user = user;
// static data
offerId = UUID.randomUUID().toString(); offerId = UUID.randomUUID().toString();
totalFeesAsCoin.setValue(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE));
// Node: Don't do setup in constructor to make object creation faster
//TODO just for unit testing, use mockito?
if (walletFacade != null && walletFacade.getWallet() != null)
addressEntry = walletFacade.getAddressInfoByTradeID(offerId);
collateralAsLong.setValue(settings.getCollateral());
BankAccount bankAccount = user.getCurrentBankAccount();
if (bankAccount != null) {
bankAccountType.setValue(bankAccount.getBankAccountType().toString());
bankAccountCurrency.setValue(bankAccount.getCurrency().getCurrencyCode());
bankAccountCounty.setValue(bankAccount.getCountry().getName());
}
acceptedCountries.setAll(settings.getAcceptedCountries());
acceptedLanguages.setAll(settings.getAcceptedLanguageLocales());
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle // Lifecycle
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -133,11 +119,29 @@ class CreateOfferModel extends UIModel {
@Override @Override
public void initialized() { public void initialized() {
super.initialized(); super.initialized();
// static data
totalFeesAsCoin.set(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE));
if (walletFacade != null && walletFacade.getWallet() != null)
addressEntry = walletFacade.getAddressInfoByTradeID(offerId);
} }
@Override @Override
public void activate() { public void activate() {
super.activate(); super.activate();
// might be changed after screen change
collateralAsLong.set(settings.getCollateral());
BankAccount bankAccount = user.getCurrentBankAccount();
if (bankAccount != null) {
bankAccountType.set(bankAccount.getBankAccountType().toString());
bankAccountCurrency.set(bankAccount.getCurrency().getCurrencyCode());
bankAccountCounty.set(bankAccount.getCountry().getName());
}
acceptedCountries.setAll(settings.getAcceptedCountries());
acceptedLanguages.setAll(settings.getAcceptedLanguageLocales());
} }
@Override @Override
@ -150,41 +154,43 @@ class CreateOfferModel extends UIModel {
super.terminate(); super.terminate();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Methods // Methods
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
void placeOffer() { void placeOffer() {
// data validation is done in the trade domain
tradeManager.requestPlaceOffer(offerId, tradeManager.requestPlaceOffer(offerId,
direction, direction,
priceAsFiat.get(), priceAsFiat.get(),
amountAsCoin.get(), amountAsCoin.get(),
minAmountAsCoin.get(), minAmountAsCoin.get(),
(transaction) -> { (transaction) -> {
requestPlaceOfferSuccess.setValue(true); requestPlaceOfferSuccess.set(true);
transactionId.setValue(transaction.getHashAsString()); transactionId.set(transaction.getHashAsString());
}, },
(errorMessage) -> { (errorMessage) -> {
requestPlaceOfferFailed.setValue(true); requestPlaceOfferFailed.set(true);
requestPlaceOfferErrorMessage.setValue(errorMessage); requestPlaceOfferErrorMessage.set(errorMessage);
} }
); );
} }
void calculateVolume() { void calculateVolume() {
if (priceAsFiat.get() != null && amountAsCoin.get() != null /*&& !amountAsCoin.get().isZero()*/) if (priceAsFiat.get() != null && amountAsCoin.get() != null /*&& !amountAsCoin.get().isZero()*/) {
volumeAsFiat.setValue(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get()));
}
} }
void calculateAmount() { void calculateAmount() {
if (volumeAsFiat.get() != null && priceAsFiat.get() != null/* && !volumeAsFiat.get().isZero() && !priceAsFiat if (volumeAsFiat.get() != null && priceAsFiat.get() != null/* && !volumeAsFiat.get().isZero() && !priceAsFiat
.get().isZero()*/) { .get().isZero()*/) {
amountAsCoin.setValue(new ExchangeRate(priceAsFiat.get()).fiatToCoin(volumeAsFiat.get())); amountAsCoin.set(new ExchangeRate(priceAsFiat.get()).fiatToCoin(volumeAsFiat.get()));
// If we got a btc value with more then 4 decimals we convert it to max 4 decimals // If we got a btc value with more then 4 decimals we convert it to max 4 decimals
amountAsCoin.setValue(reduceto4Dezimals(amountAsCoin.get())); amountAsCoin.set(reduceto4Dezimals(amountAsCoin.get()));
calculateTotalToPay(); calculateTotalToPay();
calculateCollateral(); calculateCollateral();
} }
@ -193,17 +199,16 @@ class CreateOfferModel extends UIModel {
void calculateTotalToPay() { void calculateTotalToPay() {
calculateCollateral(); calculateCollateral();
if (collateralAsCoin.get() != null) { if (collateralAsCoin.get() != null)
totalToPayAsCoin.setValue(collateralAsCoin.get().add(totalFeesAsCoin.get())); totalToPayAsCoin.set(collateralAsCoin.get().add(totalFeesAsCoin.get()));
}
} }
void calculateCollateral() { void calculateCollateral() {
if (amountAsCoin.get() != null) if (amountAsCoin.get() != null)
collateralAsCoin.setValue(amountAsCoin.get().multiply(collateralAsLong.get()).divide(1000)); collateralAsCoin.set(amountAsCoin.get().multiply(collateralAsLong.get()).divide(1000));
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Validation // Validation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -214,6 +219,7 @@ class CreateOfferModel extends UIModel {
return true; return true;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Setter/Getter // Setter/Getter
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -30,6 +30,8 @@ import io.bitsquare.trade.orderbook.OrderBookFilter;
import com.google.bitcoin.core.Address; import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.Coin; import com.google.bitcoin.core.Coin;
import javax.inject.Inject;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@ -48,7 +50,6 @@ import static javafx.beans.binding.Bindings.createStringBinding;
class CreateOfferPM extends PresentationModel<CreateOfferModel> { class CreateOfferPM extends PresentationModel<CreateOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class); private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class);
private BtcValidator btcValidator = new BtcValidator(); private BtcValidator btcValidator = new BtcValidator();
private FiatValidator fiatValidator = new FiatValidator(); private FiatValidator fiatValidator = new FiatValidator();
@ -85,29 +86,23 @@ class CreateOfferPM extends PresentationModel<CreateOfferModel> {
final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>(); final ObjectProperty<InputValidator.ValidationResult> priceValidationResult = new SimpleObjectProperty<>();
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>(); final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
// That is needed for the addressTextField // Those are needed for the addressTextField
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Address> address = new SimpleObjectProperty<>(); final ObjectProperty<Address> address = new SimpleObjectProperty<>();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor (called by CB) // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject
CreateOfferPM(CreateOfferModel model) { CreateOfferPM(CreateOfferModel model) {
super(model); super(model);
paymentLabel.setValue("Bitsquare trade (" + model().getOfferId() + ")"); // Node: Don't do setup in constructor to make object creation faster
if (model().addressEntry != null) {
addressAsString.setValue(model().addressEntry.getAddress().toString());
address.setValue(model().addressEntry.getAddress());
}
setupModelBindings();
setupUIInputListeners();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle // Lifecycle
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -115,6 +110,17 @@ class CreateOfferPM extends PresentationModel<CreateOfferModel> {
@Override @Override
public void initialized() { public void initialized() {
super.initialized(); super.initialized();
// static
paymentLabel.set("Bitsquare trade (" + model.getOfferId() + ")");
if (model.addressEntry != null) {
addressAsString.set(model.addressEntry.getAddress().toString());
address.set(model.addressEntry.getAddress());
}
setupBindings();
setupListeners();
} }
@Override @Override
@ -132,23 +138,27 @@ class CreateOfferPM extends PresentationModel<CreateOfferModel> {
super.terminate(); super.terminate();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Public API methods (called by CB) // Public API methods (called by CB)
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// setOrderBookFilter is a one time call
void setOrderBookFilter(OrderBookFilter orderBookFilter) { void setOrderBookFilter(OrderBookFilter orderBookFilter) {
model().setDirection(orderBookFilter.getDirection()); model.setDirection(orderBookFilter.getDirection());
directionLabel.setValue(model().getDirection() == Direction.BUY ? "Buy:" : "Sell:"); directionLabel.set(model.getDirection() == Direction.BUY ? "Buy:" : "Sell:");
// apply only if valid
if (orderBookFilter.getAmount() != null && isBtcInputValid(orderBookFilter.getAmount().toPlainString()) if (orderBookFilter.getAmount() != null && isBtcInputValid(orderBookFilter.getAmount().toPlainString())
.isValid) { .isValid) {
model().amountAsCoin.setValue(orderBookFilter.getAmount()); model.amountAsCoin.set(orderBookFilter.getAmount());
model().minAmountAsCoin.setValue(orderBookFilter.getAmount()); model.minAmountAsCoin.set(orderBookFilter.getAmount());
} }
// TODO use Fiat in orderBookFilter // TODO use Fiat in orderBookFilter
// apply only if valid
if (orderBookFilter.getPrice() != 0 && isBtcInputValid(String.valueOf(orderBookFilter.getPrice())).isValid) if (orderBookFilter.getPrice() != 0 && isBtcInputValid(String.valueOf(orderBookFilter.getPrice())).isValid)
model().priceAsFiat.setValue(parseToFiatWith2Decimals(String.valueOf(orderBookFilter.getPrice()))); model.priceAsFiat.set(parseToFiatWith2Decimals(String.valueOf(orderBookFilter.getPrice())));
} }
@ -157,97 +167,96 @@ class CreateOfferPM extends PresentationModel<CreateOfferModel> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
void placeOffer() { void placeOffer() {
model().placeOffer(); model.placeOffer();
isPlaceOfferButtonDisabled.setValue(true); isPlaceOfferButtonDisabled.set(true);
isPlaceOfferButtonVisible.setValue(true); isPlaceOfferButtonVisible.set(true);
} }
void close() {
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// UI events (called by CB) // UI events (called by CB)
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// when focus out we do validation and apply the data to the model // On focus out we do validation and apply the data to the model
void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue, String userInput) {
void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue) {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(amount.get()); InputValidator.ValidationResult result = isBtcInputValid(amount.get());
boolean isValid = result.isValid; amountValidationResult.set(result);
amountValidationResult.setValue(result); if (result.isValid) {
if (isValid) { showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(userInput));
showWarningInvalidBtcDecimalPlaces.setValue(!hasBtcValidDecimals(amount.get()));
// only allow max 4 decimal places for btc values // only allow max 4 decimal places for btc values
setAmountToModel(); setAmountToModel();
// reformat input to general btc format // reformat input
amount.set(formatCoin(model.amountAsCoin.get()));
calculateVolume(); calculateVolume();
if (!model().isMinAmountLessOrEqualAmount()) { // handle minAmount/amount relationship
amountValidationResult.setValue(new InputValidator.ValidationResult(false, if (!model.isMinAmountLessOrEqualAmount()) {
amountValidationResult.set(new InputValidator.ValidationResult(false,
"Amount cannot be smaller than minimum amount.")); "Amount cannot be smaller than minimum amount."));
} }
else { else {
amountValidationResult.setValue(result); amountValidationResult.set(result);
if (minAmount.get() != null) if (minAmount.get() != null)
minAmountValidationResult.setValue(isBtcInputValid(minAmount.get())); minAmountValidationResult.set(isBtcInputValid(minAmount.get()));
} }
} }
} }
} }
void onFocusOutMinAmountTextField(Boolean oldValue, Boolean newValue) { void onFocusOutMinAmountTextField(Boolean oldValue, Boolean newValue, String userInput) {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(minAmount.get()); InputValidator.ValidationResult result = isBtcInputValid(minAmount.get());
boolean isValid = result.isValid; minAmountValidationResult.set(result);
minAmountValidationResult.setValue(result); if (result.isValid) {
if (isValid) { showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(userInput));
showWarningInvalidBtcDecimalPlaces.setValue(!hasBtcValidDecimals(minAmount.get()));
setMinAmountToModel(); setMinAmountToModel();
minAmount.set(formatCoin(model.minAmountAsCoin.get()));
if (!model().isMinAmountLessOrEqualAmount()) { if (!model.isMinAmountLessOrEqualAmount()) {
minAmountValidationResult.setValue(new InputValidator.ValidationResult(false, minAmountValidationResult.set(new InputValidator.ValidationResult(false,
"Minimum amount cannot be larger than amount.")); "Minimum amount cannot be larger than amount."));
} }
else { else {
minAmountValidationResult.setValue(result); minAmountValidationResult.set(result);
if (amount.get() != null) if (amount.get() != null)
amountValidationResult.setValue(isBtcInputValid(amount.get())); amountValidationResult.set(isBtcInputValid(amount.get()));
} }
} }
} }
} }
void onFocusOutPriceTextField(Boolean oldValue, Boolean newValue) { void onFocusOutPriceTextField(Boolean oldValue, Boolean newValue, String userInput) {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isFiatInputValid(price.get()); InputValidator.ValidationResult result = isFiatInputValid(price.get());
boolean isValid = result.isValid; boolean isValid = result.isValid;
priceValidationResult.setValue(result); priceValidationResult.set(result);
if (isValid) { if (isValid) {
showWarningInvalidFiatDecimalPlaces.setValue(!hasFiatValidDecimals(price.get())); showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(userInput));
setPriceToModel(); setPriceToModel();
price.set(formatFiat(model.priceAsFiat.get()));
calculateVolume(); calculateVolume();
} }
} }
} }
void onFocusOutVolumeTextField(Boolean oldValue, Boolean newValue) { void onFocusOutVolumeTextField(Boolean oldValue, Boolean newValue, String userInput) {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(volume.get()); InputValidator.ValidationResult result = isBtcInputValid(volume.get());
boolean isValid = result.isValid; volumeValidationResult.set(result);
volumeValidationResult.setValue(result); if (result.isValid) {
if (isValid) { showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(userInput));
String origVolume = volume.get();
showWarningInvalidFiatDecimalPlaces.setValue(!hasFiatValidDecimals(volume.get()));
setVolumeToModel(); setVolumeToModel();
volume.set(formatFiat(model.volumeAsFiat.get()));
calculateAmount(); calculateAmount();
// must be after calculateAmount (btc value has been adjusted in case the calculation leads to // must be placed after calculateAmount (btc value has been adjusted in case the calculation leads to
// invalid decimal places for the amount value // invalid decimal places for the amount value
showWarningAdjustedVolume.setValue(!formatFiat(parseToFiatWith2Decimals(origVolume)).equals(volume showWarningAdjustedVolume.set(!formatFiat(parseToFiatWith2Decimals(userInput)).equals(volume
.get())); .get()));
} }
} }
@ -259,7 +268,7 @@ class CreateOfferPM extends PresentationModel<CreateOfferModel> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
WalletFacade getWalletFacade() { WalletFacade getWalletFacade() {
return model().getWalletFacade(); return model.getWalletFacade();
} }
@ -267,129 +276,127 @@ class CreateOfferPM extends PresentationModel<CreateOfferModel> {
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setupUIInputListeners() { private void setupListeners() {
// bindBidirectional for 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 // We do volume/amount calculation during input, so user has immediate feedback
amount.addListener((ov, oldValue, newValue) -> { amount.addListener((ov, oldValue, newValue) -> {
if (isBtcInputValid(newValue).isValid) { if (isBtcInputValid(newValue).isValid) {
model().amountAsCoin.setValue(parseToCoinWith4Decimals(newValue)); setMinAmountToModel();
calculateVolume(); calculateVolume();
model().calculateTotalToPay(); model.calculateTotalToPay();
model().calculateCollateral(); model.calculateCollateral();
} }
}); });
price.addListener((ov, oldValue, newValue) -> { price.addListener((ov, oldValue, newValue) -> {
if (isFiatInputValid(newValue).isValid) { if (isFiatInputValid(newValue).isValid) {
model().priceAsFiat.setValue(parseToFiatWith2Decimals(newValue)); setPriceToModel();
calculateVolume(); calculateVolume();
model().calculateTotalToPay(); model.calculateTotalToPay();
model().calculateCollateral(); model.calculateCollateral();
} }
}); });
volume.addListener((ov, oldValue, newValue) -> { volume.addListener((ov, oldValue, newValue) -> {
if (isFiatInputValid(newValue).isValid) { if (isFiatInputValid(newValue).isValid) {
model().volumeAsFiat.setValue(parseToFiatWith2Decimals(newValue));
setVolumeToModel(); setVolumeToModel();
setPriceToModel(); setPriceToModel();
model().calculateAmount(); model.calculateAmount();
model().calculateTotalToPay(); model.calculateTotalToPay();
model().calculateCollateral(); model.calculateCollateral();
} }
}); });
// Binding with Bindings.createObjectBinding does not work becaue of bi-directional binding in CB // Binding with Bindings.createObjectBinding does not work because of bi-directional binding
model().amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatCoin(newValue))); model.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatCoin(newValue)));
model().minAmountAsCoin.addListener((ov, oldValue, newValue) -> minAmount.set(formatCoin(newValue))); model.minAmountAsCoin.addListener((ov, oldValue, newValue) -> minAmount.set(formatCoin(newValue)));
model().priceAsFiat.addListener((ov, oldValue, newValue) -> price.set(formatFiat(newValue))); model.priceAsFiat.addListener((ov, oldValue, newValue) -> price.set(formatFiat(newValue)));
model().volumeAsFiat.addListener((ov, oldValue, newValue) -> volume.set(formatFiat(newValue))); model.volumeAsFiat.addListener((ov, oldValue, newValue) -> volume.set(formatFiat(newValue)));
}
private void setupModelBindings() {
totalToPay.bind(createStringBinding(() -> formatCoinWithCode(model().totalToPayAsCoin.get()),
model().totalToPayAsCoin));
collateral.bind(createStringBinding(() -> formatCoinWithCode(model().collateralAsCoin.get()),
model().collateralAsCoin));
collateralLabel.bind(Bindings.createStringBinding(() -> "Collateral (" + BSFormatter.formatCollateralPercent
(model().collateralAsLong.get()) + "):", model().collateralAsLong));
totalToPayAsCoin.bind(model().totalToPayAsCoin);
bankAccountType.bind(Bindings.createStringBinding(() -> Localisation.get(model().bankAccountType.get()),
model().bankAccountType));
bankAccountCurrency.bind(model().bankAccountCurrency);
bankAccountCounty.bind(model().bankAccountCounty);
// ObservableLists // ObservableLists
model().acceptedCountries.addListener((Observable o) -> acceptedCountries.setValue(BSFormatter model.acceptedCountries.addListener((Observable o) -> acceptedCountries.set(BSFormatter
.countryLocalesToString(model().acceptedCountries))); .countryLocalesToString(model.acceptedCountries)));
model().acceptedLanguages.addListener((Observable o) -> acceptedLanguages.setValue(BSFormatter model.acceptedLanguages.addListener((Observable o) -> acceptedLanguages.set(BSFormatter
.languageLocalesToString(model().acceptedLanguages))); .languageLocalesToString(model.acceptedLanguages)));
isCloseButtonVisible.bind(model().requestPlaceOfferSuccess);
requestPlaceOfferErrorMessage.bind(model().requestPlaceOfferErrorMessage);
requestPlaceOfferFailed.bind(model().requestPlaceOfferFailed);
showTransactionPublishedScreen.bind(model().requestPlaceOfferSuccess);
isPlaceOfferButtonDisabled.bind(Bindings.createBooleanBinding(() -> !model().requestPlaceOfferFailed.get(),
model().requestPlaceOfferFailed));
isPlaceOfferButtonVisible.bind(Bindings.createBooleanBinding(() -> !model().requestPlaceOfferSuccess.get(),
model().requestPlaceOfferSuccess));
} }
private void setupBindings() {
totalToPay.bind(createStringBinding(() -> formatCoinWithCode(model.totalToPayAsCoin.get()),
model.totalToPayAsCoin));
collateral.bind(createStringBinding(() -> formatCoinWithCode(model.collateralAsCoin.get()),
model.collateralAsCoin));
collateralLabel.bind(Bindings.createStringBinding(() -> "Collateral (" + BSFormatter.formatCollateralPercent
(model.collateralAsLong.get()) + "):", model.collateralAsLong));
totalToPayAsCoin.bind(model.totalToPayAsCoin);
totalFees.bind(createStringBinding(() -> formatCoinWithCode(model.totalFeesAsCoin.get()),
model.totalFeesAsCoin));
bankAccountType.bind(Bindings.createStringBinding(() -> Localisation.get(model.bankAccountType.get()),
model.bankAccountType));
bankAccountCurrency.bind(model.bankAccountCurrency);
bankAccountCounty.bind(model.bankAccountCounty);
isCloseButtonVisible.bind(model.requestPlaceOfferSuccess);
requestPlaceOfferErrorMessage.bind(model.requestPlaceOfferErrorMessage);
requestPlaceOfferFailed.bind(model.requestPlaceOfferFailed);
showTransactionPublishedScreen.bind(model.requestPlaceOfferSuccess);
isPlaceOfferButtonDisabled.bind(Bindings.createBooleanBinding(() -> !model.requestPlaceOfferFailed.get(),
model.requestPlaceOfferFailed));
isPlaceOfferButtonVisible.bind(Bindings.createBooleanBinding(() -> !model.requestPlaceOfferSuccess.get(),
model.requestPlaceOfferSuccess));
}
private void calculateVolume() { private void calculateVolume() {
setAmountToModel(); setAmountToModel();
setPriceToModel(); setPriceToModel();
model().calculateVolume(); model.calculateVolume();
} }
private void calculateAmount() { private void calculateAmount() {
setVolumeToModel(); setVolumeToModel();
setPriceToModel(); setPriceToModel();
model().calculateAmount(); model.calculateAmount();
if (!model().isMinAmountLessOrEqualAmount()) { // Amount calculation could lead to amount/minAmount invalidation
amountValidationResult.setValue(new InputValidator.ValidationResult(false, if (!model.isMinAmountLessOrEqualAmount()) {
amountValidationResult.set(new InputValidator.ValidationResult(false,
"Amount cannot be smaller than minimum amount.")); "Amount cannot be smaller than minimum amount."));
} }
else { else {
if (amount.get() != null) if (amount.get() != null)
amountValidationResult.setValue(isBtcInputValid(amount.get())); amountValidationResult.set(isBtcInputValid(amount.get()));
if (minAmount.get() != null) if (minAmount.get() != null)
minAmountValidationResult.setValue(isBtcInputValid(minAmount.get())); minAmountValidationResult.set(isBtcInputValid(minAmount.get()));
} }
} }
private void setAmountToModel() { private void setAmountToModel() {
model().amountAsCoin.setValue(parseToCoinWith4Decimals(amount.get())); model.amountAsCoin.set(parseToCoinWith4Decimals(amount.get()));
} }
private void setMinAmountToModel() { private void setMinAmountToModel() {
model().minAmountAsCoin.setValue(parseToCoinWith4Decimals(minAmount.get())); model.minAmountAsCoin.set(parseToCoinWith4Decimals(minAmount.get()));
} }
private void setPriceToModel() { private void setPriceToModel() {
model().priceAsFiat.setValue(parseToFiatWith2Decimals(price.get())); model.priceAsFiat.set(parseToFiatWith2Decimals(price.get()));
} }
private void setVolumeToModel() { private void setVolumeToModel() {
model().volumeAsFiat.setValue(parseToFiatWith2Decimals(volume.get())); model.volumeAsFiat.set(parseToFiatWith2Decimals(volume.get()));
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Package scope for testing
///////////////////////////////////////////////////////////////////////////////////////////
InputValidator.ValidationResult isBtcInputValid(String input) { private InputValidator.ValidationResult isBtcInputValid(String input) {
return btcValidator.validate(input); return btcValidator.validate(input);
} }
InputValidator.ValidationResult isFiatInputValid(String input) { private InputValidator.ValidationResult isFiatInputValid(String input) {
return fiatValidator.validate(input); return fiatValidator.validate(input);
} }

View file

@ -49,9 +49,9 @@
<Label GridPane.rowIndex="1" fx:id="buyLabel"/> <Label GridPane.rowIndex="1" fx:id="buyLabel"/>
<HBox GridPane.rowIndex="1" GridPane.columnIndex="1" spacing="5" GridPane.hgrow="NEVER" alignment="CENTER_LEFT"> <HBox GridPane.rowIndex="1" GridPane.columnIndex="1" spacing="5" GridPane.hgrow="NEVER" alignment="CENTER_LEFT">
<ValidatingTextField fx:id="amountTextField" prefWidth="100.0" alignment="CENTER_RIGHT"/> <ValidatingTextField fx:id="amountTextField" prefWidth="100.0" alignment="CENTER_RIGHT"/>
<Label text="BTC @"/> <Label text="BTC for price of "/>
<ValidatingTextField fx:id="priceTextField" prefWidth="100.0" alignment="CENTER_RIGHT"/> <ValidatingTextField fx:id="priceTextField" prefWidth="100.0" alignment="CENTER_RIGHT"/>
<Label text="EUR ="/> <Label text="EUR/BTC ="/>
<ValidatingTextField fx:id="volumeTextField" prefWidth="100.0" alignment="CENTER_RIGHT"/> <ValidatingTextField fx:id="volumeTextField" prefWidth="100.0" alignment="CENTER_RIGHT"/>
<Label text="EUR in total"/> <Label text="EUR in total"/>
</HBox> </HBox>