From 096f4c01e70e2bfc13fdbe78ca8850b6ce33d6f9 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Fri, 29 Aug 2014 22:11:59 +0200 Subject: [PATCH] fix missing formatting updates --- doc/ui-development-notes.md | 5 +- .../io/bitsquare/gui/CachedCodeBehind.java | 17 +- .../bitsquare/gui/CachedViewController.java | 2 + .../java/io/bitsquare/gui/CodeBehind.java | 19 +- .../io/bitsquare/gui/PresentationModel.java | 6 +- .../java/io/bitsquare/gui/ViewController.java | 2 + .../gui/components/ValidatingTextField.java | 58 +++-- .../bitsquare/gui/trade/TradeController.java | 2 +- .../gui/trade/createoffer/CreateOfferCB.java | 134 +++++----- .../trade/createoffer/CreateOfferModel.java | 70 ++--- .../gui/trade/createoffer/CreateOfferPM.java | 245 +++++++++--------- .../trade/createoffer/CreateOfferView.fxml | 4 +- 12 files changed, 298 insertions(+), 266 deletions(-) diff --git a/doc/ui-development-notes.md b/doc/ui-development-notes.md index b04a5c9740..251968002c 100644 --- a/doc/ui-development-notes.md +++ b/doc/ui-development-notes.md @@ -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 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: -* 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). * 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. @@ -62,7 +61,7 @@ It knows the presentation model but not the model. It is the abstraction/presentation of the view. 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: * Holds the state of the view/CB diff --git a/src/main/java/io/bitsquare/gui/CachedCodeBehind.java b/src/main/java/io/bitsquare/gui/CachedCodeBehind.java index bbdb8b6030..14d0505808 100644 --- a/src/main/java/io/bitsquare/gui/CachedCodeBehind.java +++ b/src/main/java/io/bitsquare/gui/CachedCodeBehind.java @@ -7,11 +7,16 @@ import java.util.ResourceBundle; import org.slf4j.Logger; 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 The PresentationModel used in that class + */ public class CachedCodeBehind extends CodeBehind { private static final Logger log = LoggerFactory.getLogger(CachedCodeBehind.class); - public CachedCodeBehind(T pm) { - super(pm); + public CachedCodeBehind(T presentationModel) { + super(presentationModel); } /** @@ -35,7 +40,7 @@ public class CachedCodeBehind extends CodeBehind }); activate(); - pm.initialized(); + presentationModel.initialized(); } /** @@ -45,7 +50,7 @@ public class CachedCodeBehind extends CodeBehind log.trace("Lifecycle: activate " + this.getClass().getSimpleName()); if (childController instanceof CachedViewController) ((CachedViewController) childController).activate(); - pm.activate(); + presentationModel.activate(); } /** @@ -55,7 +60,7 @@ public class CachedCodeBehind extends CodeBehind log.trace("Lifecycle: deactivate " + this.getClass().getSimpleName()); if (childController instanceof CachedViewController) ((CachedViewController) childController).deactivate(); - pm.deactivate(); + presentationModel.deactivate(); } /** @@ -67,7 +72,7 @@ public class CachedCodeBehind extends CodeBehind super.terminate(); deactivate(); - pm.terminate(); + presentationModel.terminate(); } } diff --git a/src/main/java/io/bitsquare/gui/CachedViewController.java b/src/main/java/io/bitsquare/gui/CachedViewController.java index 8e690f3969..bda37e639e 100644 --- a/src/main/java/io/bitsquare/gui/CachedViewController.java +++ b/src/main/java/io/bitsquare/gui/CachedViewController.java @@ -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 * active and awake it at reactivation. */ +// for new PM pattern use CachedCodeBehind +@Deprecated public abstract class CachedViewController extends ViewController { private static final Logger log = LoggerFactory.getLogger(CachedViewController.class); diff --git a/src/main/java/io/bitsquare/gui/CodeBehind.java b/src/main/java/io/bitsquare/gui/CodeBehind.java index 57fdcc4c27..a689ad906c 100644 --- a/src/main/java/io/bitsquare/gui/CodeBehind.java +++ b/src/main/java/io/bitsquare/gui/CodeBehind.java @@ -11,25 +11,26 @@ import javafx.scene.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Non caching version for code behind classes using the PM pattern + * + * @param The PresentationModel used in that class + */ public class CodeBehind implements Initializable { private static final Logger log = LoggerFactory.getLogger(CodeBehind.class); - protected T pm; + protected T presentationModel; protected ViewController childController; protected ViewController parentController; @FXML protected Parent root; - public CodeBehind(T pm) { - this.pm = pm; + public CodeBehind(T presentationModel) { + this.presentationModel = presentationModel; } public CodeBehind() { } - public T pm() { - return (T) pm; - } - /** * Get called form GUI framework when the UI is ready. * @@ -45,7 +46,7 @@ public class CodeBehind implements Initializable { if (oldValue != null && newValue == null) terminate(); }); - pm.initialized(); + presentationModel.initialized(); } /** @@ -56,7 +57,7 @@ public class CodeBehind implements Initializable { log.trace("Lifecycle: terminate " + this.getClass().getSimpleName()); if (childController != null) childController.terminate(); - pm.terminate(); + presentationModel.terminate(); } /** diff --git a/src/main/java/io/bitsquare/gui/PresentationModel.java b/src/main/java/io/bitsquare/gui/PresentationModel.java index 64dcc2a9e2..75c2bf2db7 100644 --- a/src/main/java/io/bitsquare/gui/PresentationModel.java +++ b/src/main/java/io/bitsquare/gui/PresentationModel.java @@ -2,11 +2,7 @@ package io.bitsquare.gui; public class PresentationModel { - private T model; - - public T model() { - return (T) model; - } + protected T model; public PresentationModel(T model) { this.model = model; diff --git a/src/main/java/io/bitsquare/gui/ViewController.java b/src/main/java/io/bitsquare/gui/ViewController.java index 7cf455a43b..07cac08bef 100644 --- a/src/main/java/io/bitsquare/gui/ViewController.java +++ b/src/main/java/io/bitsquare/gui/ViewController.java @@ -31,6 +31,8 @@ import org.slf4j.LoggerFactory; /** * Base class for all controllers. */ +// for new PM pattern use CodeBehind +@Deprecated public abstract class ViewController implements Initializable { private static final Logger log = LoggerFactory.getLogger(ViewController.class); diff --git a/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java b/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java index 8f2dcaf54f..9255812dcf 100644 --- a/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java +++ b/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java @@ -35,11 +35,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * TextField with validation support. Validation is executed on the Validator object. - * In case of a invalid result we display a error message with a PopOver. - * The position is derived from the textField or if set from the errorPopupLayoutReference object. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. + * TextField with validation support. + * In case the isValid property in amountValidationResultProperty get set to false we display a red border and an error + * message within the errorMessageDisplay placed on the right of the text field. + * The errorMessageDisplay gets closed when the ValidatingTextField instance gets removed from the scene graph or when + * 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 { private static final Logger log = LoggerFactory.getLogger(ValidatingTextField.class); @@ -49,17 +51,17 @@ public class ValidatingTextField extends TextField { final ObjectProperty amountValidationResult = new SimpleObjectProperty<>(new InputValidator.ValidationResult(true)); - private static PopOver popOver; - private Region errorPopupLayoutReference = this; + private static PopOver errorMessageDisplay; + private Region layoutReference = this; /////////////////////////////////////////////////////////////////////////////////////////// // Static /////////////////////////////////////////////////////////////////////////////////////////// - public static void hidePopover() { - if (popOver != null) - popOver.hide(); + public static void hideErrorMessageDisplay() { + if (errorMessageDisplay != null) + errorMessageDisplay.hide(); } /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -73,17 +75,16 @@ public class ValidatingTextField extends TextField { setEffect(newValue.isValid ? null : invalidEffect); if (newValue.isValid) - hidePopover(); + hideErrorMessageDisplay(); else applyErrorMessage(newValue); } }); sceneProperty().addListener((ov, oldValue, newValue) -> { - // we got removed from the scene - // lets hide an open popup + // we got removed from the scene so hide the popup (if open) 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 - * ValidatingTextField instance is used. + * @param layoutReference The node used as reference for positioning. If not set explicitely the + * ValidatingTextField instance is used. */ - public void setErrorPopupLayoutReference(Region errorPopupLayoutReference) { - this.errorPopupLayoutReference = errorPopupLayoutReference; + public void setLayoutReference(Region layoutReference) { + this.layoutReference = layoutReference; } @@ -122,25 +123,26 @@ public class ValidatingTextField extends TextField { private void applyErrorMessage(InputValidator.ValidationResult validationResult) { if (validationResult.isValid) { - if (popOver != null) { - popOver.hide(); + if (errorMessageDisplay != null) { + errorMessageDisplay.hide(); } } else { - if (popOver == null) + if (errorMessageDisplay == null) createErrorPopOver(validationResult.errorMessage); 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() { Window window = getScene().getWindow(); Point2D point; - point = errorPopupLayoutReference.localToScene(0, 0); - double x = point.getX() + window.getX() + errorPopupLayoutReference.getWidth() + 20; + point = layoutReference.localToScene(0, 0); + double x = point.getX() + window.getX() + layoutReference.getWidth() + 20; double y = point.getY() + window.getY() + Math.floor(getHeight() / 2); return new Point2D(x, y); } @@ -151,9 +153,9 @@ public class ValidatingTextField extends TextField { errorLabel.setId("validation-error"); errorLabel.setPadding(new Insets(0, 10, 0, 10)); - popOver = new PopOver(errorLabel); - popOver.setDetachable(false); - popOver.setArrowIndent(5); + ValidatingTextField.errorMessageDisplay = new PopOver(errorLabel); + ValidatingTextField.errorMessageDisplay.setDetachable(false); + ValidatingTextField.errorMessageDisplay.setArrowIndent(5); } } \ No newline at end of file diff --git a/src/main/java/io/bitsquare/gui/trade/TradeController.java b/src/main/java/io/bitsquare/gui/trade/TradeController.java index 148c36b86f..3ea0705aff 100644 --- a/src/main/java/io/bitsquare/gui/trade/TradeController.java +++ b/src/main/java/io/bitsquare/gui/trade/TradeController.java @@ -78,7 +78,7 @@ public class TradeController extends CachedViewController { //TODO update to new verison ((TabPane) root).getSelectionModel().selectedIndexProperty().addListener((observableValue) -> - Platform.runLater(ValidatingTextField::hidePopover)); + Platform.runLater(ValidatingTextField::hideErrorMessageDisplay)); } diff --git a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCB.java b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCB.java index c21bfc8399..9c00dba0b9 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCB.java +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCB.java @@ -58,10 +58,9 @@ public class CreateOfferCB extends CachedCodeBehind { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - //TODO find a better solution, handle at base class? @Inject - public CreateOfferCB(CreateOfferModel model) { - super(new CreateOfferPM(model)); + CreateOfferCB(CreateOfferPM presentationModel) { + super(presentationModel); } @@ -76,7 +75,12 @@ public class CreateOfferCB extends CachedCodeBehind { setupBindings(); setupListeners(); configTextFieldValidators(); - balanceTextField.setup(pm().getWalletFacade(), pm().address.get()); + balanceTextField.setup(presentationModel.getWalletFacade(), presentationModel.address.get()); + } + + @Override + public void activate() { + super.activate(); } @Override @@ -87,13 +91,18 @@ public class CreateOfferCB extends CachedCodeBehind { 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) { - pm().setOrderBookFilter(orderBookFilter); + presentationModel.setOrderBookFilter(orderBookFilter); } @@ -103,13 +112,11 @@ public class CreateOfferCB extends CachedCodeBehind { @FXML public void onPlaceOffer() { - pm().placeOffer(); + presentationModel.placeOffer(); } @FXML public void onClose() { - pm().close(); - TabPane tabPane = ((TabPane) (root.getParent().getParent())); tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem()); } @@ -120,99 +127,104 @@ public class CreateOfferCB extends CachedCodeBehind { /////////////////////////////////////////////////////////////////////////////////////////// private void setupListeners() { - volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - pm().onFocusOutVolumeTextField(oldValue, newValue); - volumeTextField.setText(pm().volume.get()); - }); - + // focus out amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - pm().onFocusOutAmountTextField(oldValue, newValue); - amountTextField.setText(pm().amount.get()); - }); - - priceTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - pm().onFocusOutPriceTextField(oldValue, newValue); - priceTextField.setText(pm().price.get()); + presentationModel.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText()); + amountTextField.setText(presentationModel.amount.get()); }); minAmountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - pm().onFocusOutMinAmountTextField(oldValue, newValue); - minAmountTextField.setText(pm().minAmount.get()); + presentationModel.onFocusOutMinAmountTextField(oldValue, newValue, minAmountTextField.getText()); + 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) { Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" + " 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) { Popups.openWarningPopup("Warning", "The amount you have entered exceeds the number of allowed decimal" + " 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) { 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."); - pm().showWarningAdjustedVolume.set(false); - volumeTextField.setText(pm().volume.get()); + presentationModel.showWarningAdjustedVolume.set(false); + volumeTextField.setText(presentationModel.volume.get()); } }); - pm().requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> { + presentationModel.requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> { if (newValue) { Popups.openErrorPopup("Error", "An error occurred when placing the offer.\n" + - pm().requestPlaceOfferErrorMessage); - pm().requestPlaceOfferFailed.set(false); + presentationModel.requestPlaceOfferErrorMessage); + presentationModel.requestPlaceOfferFailed.set(false); } }); } private void setupBindings() { - buyLabel.textProperty().bind(pm().directionLabel); - amountTextField.textProperty().bindBidirectional(pm().amount); - priceTextField.textProperty().bindBidirectional(pm().price); - volumeTextField.textProperty().bindBidirectional(pm().volume); + buyLabel.textProperty().bind(presentationModel.directionLabel); - minAmountTextField.textProperty().bindBidirectional(pm().minAmount); - collateralLabel.textProperty().bind(pm().collateralLabel); - collateralTextField.textProperty().bind(pm().collateral); - totalToPayTextField.textProperty().bind(pm().totalToPay); + amountTextField.textProperty().bindBidirectional(presentationModel.amount); + minAmountTextField.textProperty().bindBidirectional(presentationModel.minAmount); + priceTextField.textProperty().bindBidirectional(presentationModel.price); + volumeTextField.textProperty().bindBidirectional(presentationModel.volume); - addressTextField.amountAsCoinProperty().bind(pm().totalToPayAsCoin); - addressTextField.paymentLabelProperty().bind(pm().paymentLabel); - addressTextField.addressProperty().bind(pm().addressAsString); + collateralLabel.textProperty().bind(presentationModel.collateralLabel); + collateralTextField.textProperty().bind(presentationModel.collateral); + totalToPayTextField.textProperty().bind(presentationModel.totalToPay); + totalFeesTextField.textProperty().bind(presentationModel.totalFees); - bankAccountTypeTextField.textProperty().bind(pm().bankAccountType); - bankAccountCurrencyTextField.textProperty().bind(pm().bankAccountCurrency); - bankAccountCountyTextField.textProperty().bind(pm().bankAccountCounty); + addressTextField.amountAsCoinProperty().bind(presentationModel.totalToPayAsCoin); + addressTextField.paymentLabelProperty().bind(presentationModel.paymentLabel); + addressTextField.addressProperty().bind(presentationModel.addressAsString); - acceptedCountriesTextField.textProperty().bind(pm().acceptedCountries); - acceptedLanguagesTextField.textProperty().bind(pm().acceptedLanguages); - totalFeesTextField.textProperty().bind(pm().totalFees); - transactionIdTextField.textProperty().bind(pm().transactionId); + bankAccountTypeTextField.textProperty().bind(presentationModel.bankAccountType); + bankAccountCurrencyTextField.textProperty().bind(presentationModel.bankAccountCurrency); + bankAccountCountyTextField.textProperty().bind(presentationModel.bankAccountCounty); - amountTextField.amountValidationResultProperty().bind(pm().amountValidationResult); - minAmountTextField.amountValidationResultProperty().bind(pm().minAmountValidationResult); - priceTextField.amountValidationResultProperty().bind(pm().priceValidationResult); - volumeTextField.amountValidationResultProperty().bind(pm().volumeValidationResult); + acceptedCountriesTextField.textProperty().bind(presentationModel.acceptedCountries); + acceptedLanguagesTextField.textProperty().bind(presentationModel.acceptedLanguages); + transactionIdTextField.textProperty().bind(presentationModel.transactionId); - placeOfferButton.visibleProperty().bind(pm().isPlaceOfferButtonVisible); - placeOfferButton.disableProperty().bind(pm().isPlaceOfferButtonDisabled); - closeButton.visibleProperty().bind(pm().isCloseButtonVisible); + // Validation + amountTextField.amountValidationResultProperty().bind(presentationModel.amountValidationResult); + 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() { Region referenceNode = (Region) amountTextField.getParent(); - amountTextField.setErrorPopupLayoutReference(referenceNode); - priceTextField.setErrorPopupLayoutReference(referenceNode); - volumeTextField.setErrorPopupLayoutReference(referenceNode); + amountTextField.setLayoutReference(referenceNode); + priceTextField.setLayoutReference(referenceNode); + volumeTextField.setLayoutReference(referenceNode); } } diff --git a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferModel.java b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferModel.java index 06e70f7a8a..b45c71dd98 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferModel.java +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferModel.java @@ -100,32 +100,18 @@ class CreateOfferModel extends UIModel { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public CreateOfferModel(TradeManager tradeManager, WalletFacade walletFacade, Settings settings, User user) { + CreateOfferModel(TradeManager tradeManager, WalletFacade walletFacade, Settings settings, User user) { this.tradeManager = tradeManager; this.walletFacade = walletFacade; this.settings = settings; this.user = user; - // static data offerId = UUID.randomUUID().toString(); - totalFeesAsCoin.setValue(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE)); - - //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()); + // Node: Don't do setup in constructor to make object creation faster } + + /////////////////////////////////////////////////////////////////////////////////////////// // Lifecycle /////////////////////////////////////////////////////////////////////////////////////////// @@ -133,11 +119,29 @@ class CreateOfferModel extends UIModel { @Override public void 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 public void 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 @@ -150,41 +154,43 @@ class CreateOfferModel extends UIModel { super.terminate(); } + /////////////////////////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////////////////////////// - void placeOffer() { + // data validation is done in the trade domain tradeManager.requestPlaceOffer(offerId, direction, priceAsFiat.get(), amountAsCoin.get(), minAmountAsCoin.get(), (transaction) -> { - requestPlaceOfferSuccess.setValue(true); - transactionId.setValue(transaction.getHashAsString()); + requestPlaceOfferSuccess.set(true); + transactionId.set(transaction.getHashAsString()); }, (errorMessage) -> { - requestPlaceOfferFailed.setValue(true); - requestPlaceOfferErrorMessage.setValue(errorMessage); + requestPlaceOfferFailed.set(true); + requestPlaceOfferErrorMessage.set(errorMessage); } ); } void calculateVolume() { - if (priceAsFiat.get() != null && amountAsCoin.get() != null /*&& !amountAsCoin.get().isZero()*/) - volumeAsFiat.setValue(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); + if (priceAsFiat.get() != null && amountAsCoin.get() != null /*&& !amountAsCoin.get().isZero()*/) { + volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); + } } void calculateAmount() { if (volumeAsFiat.get() != null && priceAsFiat.get() != null/* && !volumeAsFiat.get().isZero() && !priceAsFiat .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 - amountAsCoin.setValue(reduceto4Dezimals(amountAsCoin.get())); + amountAsCoin.set(reduceto4Dezimals(amountAsCoin.get())); calculateTotalToPay(); calculateCollateral(); } @@ -193,17 +199,16 @@ class CreateOfferModel extends UIModel { void calculateTotalToPay() { calculateCollateral(); - if (collateralAsCoin.get() != null) { - totalToPayAsCoin.setValue(collateralAsCoin.get().add(totalFeesAsCoin.get())); - - } + if (collateralAsCoin.get() != null) + totalToPayAsCoin.set(collateralAsCoin.get().add(totalFeesAsCoin.get())); } void calculateCollateral() { if (amountAsCoin.get() != null) - collateralAsCoin.setValue(amountAsCoin.get().multiply(collateralAsLong.get()).divide(1000)); + collateralAsCoin.set(amountAsCoin.get().multiply(collateralAsLong.get()).divide(1000)); } + /////////////////////////////////////////////////////////////////////////////////////////// // Validation /////////////////////////////////////////////////////////////////////////////////////////// @@ -214,6 +219,7 @@ class CreateOfferModel extends UIModel { return true; } + /////////////////////////////////////////////////////////////////////////////////////////// // Setter/Getter /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPM.java b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPM.java index e7d76584ad..3d3634e074 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPM.java +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPM.java @@ -30,6 +30,8 @@ import io.bitsquare.trade.orderbook.OrderBookFilter; import com.google.bitcoin.core.Address; import com.google.bitcoin.core.Coin; +import javax.inject.Inject; + import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; @@ -48,7 +50,6 @@ import static javafx.beans.binding.Bindings.createStringBinding; class CreateOfferPM extends PresentationModel { private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class); - private BtcValidator btcValidator = new BtcValidator(); private FiatValidator fiatValidator = new FiatValidator(); @@ -85,29 +86,23 @@ class CreateOfferPM extends PresentationModel { final ObjectProperty priceValidationResult = new SimpleObjectProperty<>(); final ObjectProperty volumeValidationResult = new SimpleObjectProperty<>(); - // That is needed for the addressTextField + // Those are needed for the addressTextField final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty

address = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor (called by CB) + // Constructor /////////////////////////////////////////////////////////////////////////////////////////// + @Inject CreateOfferPM(CreateOfferModel model) { super(model); - paymentLabel.setValue("Bitsquare trade (" + model().getOfferId() + ")"); - - if (model().addressEntry != null) { - addressAsString.setValue(model().addressEntry.getAddress().toString()); - address.setValue(model().addressEntry.getAddress()); - } - - setupModelBindings(); - setupUIInputListeners(); + // Node: Don't do setup in constructor to make object creation faster } + /////////////////////////////////////////////////////////////////////////////////////////// // Lifecycle /////////////////////////////////////////////////////////////////////////////////////////// @@ -115,6 +110,17 @@ class CreateOfferPM extends PresentationModel { @Override public void 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 @@ -132,23 +138,27 @@ class CreateOfferPM extends PresentationModel { super.terminate(); } + /////////////////////////////////////////////////////////////////////////////////////////// // Public API methods (called by CB) /////////////////////////////////////////////////////////////////////////////////////////// + // setOrderBookFilter is a one time call void setOrderBookFilter(OrderBookFilter orderBookFilter) { - model().setDirection(orderBookFilter.getDirection()); - directionLabel.setValue(model().getDirection() == Direction.BUY ? "Buy:" : "Sell:"); + model.setDirection(orderBookFilter.getDirection()); + directionLabel.set(model.getDirection() == Direction.BUY ? "Buy:" : "Sell:"); + // apply only if valid if (orderBookFilter.getAmount() != null && isBtcInputValid(orderBookFilter.getAmount().toPlainString()) .isValid) { - model().amountAsCoin.setValue(orderBookFilter.getAmount()); - model().minAmountAsCoin.setValue(orderBookFilter.getAmount()); + model.amountAsCoin.set(orderBookFilter.getAmount()); + model.minAmountAsCoin.set(orderBookFilter.getAmount()); } // TODO use Fiat in orderBookFilter + // apply only if valid 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 { /////////////////////////////////////////////////////////////////////////////////////////// void placeOffer() { - model().placeOffer(); - isPlaceOfferButtonDisabled.setValue(true); - isPlaceOfferButtonVisible.setValue(true); + model.placeOffer(); + isPlaceOfferButtonDisabled.set(true); + isPlaceOfferButtonVisible.set(true); } - void close() { - } /////////////////////////////////////////////////////////////////////////////////////////// // UI events (called by CB) /////////////////////////////////////////////////////////////////////////////////////////// - // when focus out we do validation and apply the data to the model - - void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue) { + // On focus out we do validation and apply the data to the model + void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue, String userInput) { if (oldValue && !newValue) { InputValidator.ValidationResult result = isBtcInputValid(amount.get()); - boolean isValid = result.isValid; - amountValidationResult.setValue(result); - if (isValid) { - showWarningInvalidBtcDecimalPlaces.setValue(!hasBtcValidDecimals(amount.get())); + amountValidationResult.set(result); + if (result.isValid) { + showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(userInput)); // only allow max 4 decimal places for btc values setAmountToModel(); - // reformat input to general btc format + // reformat input + amount.set(formatCoin(model.amountAsCoin.get())); + calculateVolume(); - if (!model().isMinAmountLessOrEqualAmount()) { - amountValidationResult.setValue(new InputValidator.ValidationResult(false, + // handle minAmount/amount relationship + if (!model.isMinAmountLessOrEqualAmount()) { + amountValidationResult.set(new InputValidator.ValidationResult(false, "Amount cannot be smaller than minimum amount.")); } else { - amountValidationResult.setValue(result); + amountValidationResult.set(result); 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) { InputValidator.ValidationResult result = isBtcInputValid(minAmount.get()); - boolean isValid = result.isValid; - minAmountValidationResult.setValue(result); - if (isValid) { - showWarningInvalidBtcDecimalPlaces.setValue(!hasBtcValidDecimals(minAmount.get())); + minAmountValidationResult.set(result); + if (result.isValid) { + showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(userInput)); setMinAmountToModel(); + minAmount.set(formatCoin(model.minAmountAsCoin.get())); - if (!model().isMinAmountLessOrEqualAmount()) { - minAmountValidationResult.setValue(new InputValidator.ValidationResult(false, + if (!model.isMinAmountLessOrEqualAmount()) { + minAmountValidationResult.set(new InputValidator.ValidationResult(false, "Minimum amount cannot be larger than amount.")); } else { - minAmountValidationResult.setValue(result); + minAmountValidationResult.set(result); 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) { InputValidator.ValidationResult result = isFiatInputValid(price.get()); boolean isValid = result.isValid; - priceValidationResult.setValue(result); + priceValidationResult.set(result); if (isValid) { - showWarningInvalidFiatDecimalPlaces.setValue(!hasFiatValidDecimals(price.get())); + showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(userInput)); setPriceToModel(); + price.set(formatFiat(model.priceAsFiat.get())); calculateVolume(); } } } - void onFocusOutVolumeTextField(Boolean oldValue, Boolean newValue) { + void onFocusOutVolumeTextField(Boolean oldValue, Boolean newValue, String userInput) { if (oldValue && !newValue) { InputValidator.ValidationResult result = isBtcInputValid(volume.get()); - boolean isValid = result.isValid; - volumeValidationResult.setValue(result); - if (isValid) { - String origVolume = volume.get(); - showWarningInvalidFiatDecimalPlaces.setValue(!hasFiatValidDecimals(volume.get())); + volumeValidationResult.set(result); + if (result.isValid) { + showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(userInput)); setVolumeToModel(); + volume.set(formatFiat(model.volumeAsFiat.get())); 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 - showWarningAdjustedVolume.setValue(!formatFiat(parseToFiatWith2Decimals(origVolume)).equals(volume + showWarningAdjustedVolume.set(!formatFiat(parseToFiatWith2Decimals(userInput)).equals(volume .get())); } } @@ -259,7 +268,7 @@ class CreateOfferPM extends PresentationModel { /////////////////////////////////////////////////////////////////////////////////////////// WalletFacade getWalletFacade() { - return model().getWalletFacade(); + return model.getWalletFacade(); } @@ -267,129 +276,127 @@ class CreateOfferPM extends PresentationModel { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void setupUIInputListeners() { + private void setupListeners() { - // bindBidirectional for amount, price, volume and minAmount - // We do volume/amount calculation during input + // Bidirectional bindings are used for all input fields: amount, price, volume and minAmount + // We do volume/amount calculation during input, so user has immediate feedback amount.addListener((ov, oldValue, newValue) -> { if (isBtcInputValid(newValue).isValid) { - model().amountAsCoin.setValue(parseToCoinWith4Decimals(newValue)); + setMinAmountToModel(); calculateVolume(); - model().calculateTotalToPay(); - model().calculateCollateral(); + model.calculateTotalToPay(); + model.calculateCollateral(); } }); price.addListener((ov, oldValue, newValue) -> { if (isFiatInputValid(newValue).isValid) { - model().priceAsFiat.setValue(parseToFiatWith2Decimals(newValue)); + setPriceToModel(); calculateVolume(); - model().calculateTotalToPay(); - model().calculateCollateral(); + model.calculateTotalToPay(); + model.calculateCollateral(); } }); volume.addListener((ov, oldValue, newValue) -> { if (isFiatInputValid(newValue).isValid) { - model().volumeAsFiat.setValue(parseToFiatWith2Decimals(newValue)); setVolumeToModel(); setPriceToModel(); - model().calculateAmount(); - model().calculateTotalToPay(); - model().calculateCollateral(); + model.calculateAmount(); + model.calculateTotalToPay(); + model.calculateCollateral(); } }); - // Binding with Bindings.createObjectBinding does not work becaue of bi-directional binding in CB - model().amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatCoin(newValue))); - model().minAmountAsCoin.addListener((ov, oldValue, newValue) -> minAmount.set(formatCoin(newValue))); - model().priceAsFiat.addListener((ov, oldValue, newValue) -> price.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); + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + model.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatCoin(newValue))); + model.minAmountAsCoin.addListener((ov, oldValue, newValue) -> minAmount.set(formatCoin(newValue))); + model.priceAsFiat.addListener((ov, oldValue, newValue) -> price.set(formatFiat(newValue))); + model.volumeAsFiat.addListener((ov, oldValue, newValue) -> volume.set(formatFiat(newValue))); // ObservableLists - model().acceptedCountries.addListener((Observable o) -> acceptedCountries.setValue(BSFormatter - .countryLocalesToString(model().acceptedCountries))); - model().acceptedLanguages.addListener((Observable o) -> acceptedLanguages.setValue(BSFormatter - .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)); + model.acceptedCountries.addListener((Observable o) -> acceptedCountries.set(BSFormatter + .countryLocalesToString(model.acceptedCountries))); + model.acceptedLanguages.addListener((Observable o) -> acceptedLanguages.set(BSFormatter + .languageLocalesToString(model.acceptedLanguages))); } + 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() { setAmountToModel(); setPriceToModel(); - model().calculateVolume(); + model.calculateVolume(); } private void calculateAmount() { setVolumeToModel(); setPriceToModel(); - model().calculateAmount(); + model.calculateAmount(); - if (!model().isMinAmountLessOrEqualAmount()) { - amountValidationResult.setValue(new InputValidator.ValidationResult(false, + // Amount calculation could lead to amount/minAmount invalidation + if (!model.isMinAmountLessOrEqualAmount()) { + amountValidationResult.set(new InputValidator.ValidationResult(false, "Amount cannot be smaller than minimum amount.")); } else { if (amount.get() != null) - amountValidationResult.setValue(isBtcInputValid(amount.get())); + amountValidationResult.set(isBtcInputValid(amount.get())); if (minAmount.get() != null) - minAmountValidationResult.setValue(isBtcInputValid(minAmount.get())); + minAmountValidationResult.set(isBtcInputValid(minAmount.get())); } } private void setAmountToModel() { - model().amountAsCoin.setValue(parseToCoinWith4Decimals(amount.get())); + model.amountAsCoin.set(parseToCoinWith4Decimals(amount.get())); } private void setMinAmountToModel() { - model().minAmountAsCoin.setValue(parseToCoinWith4Decimals(minAmount.get())); + model.minAmountAsCoin.set(parseToCoinWith4Decimals(minAmount.get())); } private void setPriceToModel() { - model().priceAsFiat.setValue(parseToFiatWith2Decimals(price.get())); + model.priceAsFiat.set(parseToFiatWith2Decimals(price.get())); } 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); } - InputValidator.ValidationResult isFiatInputValid(String input) { + private InputValidator.ValidationResult isFiatInputValid(String input) { return fiatValidator.validate(input); } diff --git a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferView.fxml b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferView.fxml index 8439862973..9a41287f26 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferView.fxml +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferView.fxml @@ -49,9 +49,9 @@