add generic PM, CB, Model baseclasses

This commit is contained in:
Manfred Karrer 2014-08-29 19:55:09 +02:00
parent d9e625a684
commit b7c1fa58d7
7 changed files with 408 additions and 207 deletions

View file

@ -0,0 +1,73 @@
package io.bitsquare.gui;
import java.net.URL;
import java.util.ResourceBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CachedCodeBehind<T extends PresentationModel> extends CodeBehind<T> {
private static final Logger log = LoggerFactory.getLogger(CachedCodeBehind.class);
public CachedCodeBehind(T pm) {
super(pm);
}
/**
* Get called form GUI framework when the UI is ready.
* In caching controllers the initialize is only used for static UI setup.
* The activate() method is called to start resources like.
*
* @param url
* @param rb
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
log.trace("Lifecycle: initialize " + this.getClass().getSimpleName());
root.sceneProperty().addListener((ov, oldValue, newValue) -> {
// we got removed from the scene
// lets terminate
log.trace("Lifecycle: sceneProperty changed: " + this.getClass().getSimpleName() + " / oldValue=" +
oldValue + " / newValue=" + newValue);
if (oldValue == null && newValue != null) activate();
else if (oldValue != null && newValue == null) deactivate();
});
activate();
pm.initialized();
}
/**
* Used to activate resources (adding listeners, starting timers or animations,...)
*/
public void activate() {
log.trace("Lifecycle: activate " + this.getClass().getSimpleName());
if (childController instanceof CachedViewController) ((CachedViewController) childController).activate();
pm.activate();
}
/**
* Used for deactivating resources (removing listeners, stopping timers or animations,...)
*/
public void deactivate() {
log.trace("Lifecycle: deactivate " + this.getClass().getSimpleName());
if (childController instanceof CachedViewController) ((CachedViewController) childController).deactivate();
pm.deactivate();
}
/**
* In caching controllers the terminate calls the deactivate method.
*/
@Override
public void terminate() {
log.trace("Lifecycle: terminate " + this.getClass().getSimpleName());
super.terminate();
deactivate();
pm.terminate();
}
}

View file

@ -0,0 +1,79 @@
package io.bitsquare.gui;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeBehind<T extends PresentationModel> implements Initializable {
private static final Logger log = LoggerFactory.getLogger(CodeBehind.class);
protected T pm;
protected ViewController childController;
protected ViewController parentController;
@FXML protected Parent root;
public CodeBehind(T pm) {
this.pm = pm;
}
public T pm() {
return (T) pm;
}
/**
* Get called form GUI framework when the UI is ready.
*
* @param url
* @param rb
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
log.trace("Lifecycle: initialize " + this.getClass().getSimpleName());
root.sceneProperty().addListener((ov, oldValue, newValue) -> {
// we got removed from the scene
// lets terminate
if (oldValue != null && newValue == null) terminate();
});
pm.initialized();
}
/**
* Called automatically when view gets removed. Used for house keeping (removing listeners,
* stopping timers or animations,...).
*/
public void terminate() {
log.trace("Lifecycle: terminate " + this.getClass().getSimpleName());
if (childController != null) childController.terminate();
pm.terminate();
}
/**
* @param parentController Controller who has created this.getClass().getSimpleName() instance (via
* navigateToView/FXMLLoader).
*/
public void setParentController(ViewController parentController) {
log.trace("Lifecycle: setParentController " + this.getClass().getSimpleName() + " / parent = " +
parentController);
this.parentController = parentController;
}
/**
* @param navigationItem NavigationItem to be loaded.
* @return The ViewController of the loaded view.
*/
public ViewController loadViewAndGetChildController(NavigationItem navigationItem) {
log.trace("Lifecycle: loadViewAndGetChildController " + this.getClass().getSimpleName() + " / navigationItem " +
"= " + navigationItem);
return null;
}
}

View file

@ -0,0 +1,36 @@
package io.bitsquare.gui;
public class PresentationModel<T extends UIModel> {
private T model;
public T model() {
return (T) model;
}
public PresentationModel(T model) {
this.model = model;
}
public PresentationModel() {
}
public void initialized() {
model.initialized();
activate();
}
public void activate() {
model.activate();
}
public void deactivate() {
model.deactivate();
}
public void terminate() {
model.terminate();
deactivate();
}
}

View file

@ -0,0 +1,22 @@
package io.bitsquare.gui;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UIModel<T> {
private static final Logger log = LoggerFactory.getLogger(UIModel.class);
public void initialized() {
activate();
}
public void activate() {
}
public void deactivate() {
}
public void terminate() {
deactivate();
}
}

View file

@ -17,7 +17,7 @@
package io.bitsquare.gui.trade.createoffer; package io.bitsquare.gui.trade.createoffer;
import io.bitsquare.gui.CachedViewController; import io.bitsquare.gui.CachedCodeBehind;
import io.bitsquare.gui.components.Popups; import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.components.ValidatingTextField; import io.bitsquare.gui.components.ValidatingTextField;
import io.bitsquare.gui.components.btc.AddressTextField; import io.bitsquare.gui.components.btc.AddressTextField;
@ -39,11 +39,9 @@ import javafx.scene.layout.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class CreateOfferCB extends CachedViewController { public class CreateOfferCB extends CachedCodeBehind<CreateOfferPM> {
private static final Logger log = LoggerFactory.getLogger(CreateOfferCB.class); private static final Logger log = LoggerFactory.getLogger(CreateOfferCB.class);
private final CreateOfferPM pm;
@FXML private Label buyLabel, confirmationLabel, txTitleLabel, collateralLabel; @FXML private Label buyLabel, confirmationLabel, txTitleLabel, collateralLabel;
@FXML private ValidatingTextField amountTextField, minAmountTextField, priceTextField, volumeTextField; @FXML private ValidatingTextField amountTextField, minAmountTextField, priceTextField, volumeTextField;
@FXML private Button placeOfferButton, closeButton; @FXML private Button placeOfferButton, closeButton;
@ -63,7 +61,7 @@ public class CreateOfferCB extends CachedViewController {
//TODO find a better solution, handle at base class? //TODO find a better solution, handle at base class?
@Inject @Inject
public CreateOfferCB(CreateOfferModel model) { public CreateOfferCB(CreateOfferModel model) {
pm = new CreateOfferPM(model); super(new CreateOfferPM(model));
} }
@ -75,41 +73,27 @@ public class CreateOfferCB extends CachedViewController {
public void initialize(URL url, ResourceBundle rb) { public void initialize(URL url, ResourceBundle rb) {
super.initialize(url, rb); super.initialize(url, rb);
//TODO handle in base class
pm.onViewInitialized();
setupBindings(); setupBindings();
setupListeners(); setupListeners();
configTextFieldValidators(); configTextFieldValidators();
balanceTextField.setup(pm.getWalletFacade(), pm.address.get()); balanceTextField.setup(pm().getWalletFacade(), pm().address.get());
} }
@Override @Override
public void deactivate() { public void deactivate() {
super.deactivate(); super.deactivate();
//TODO handle in base class
pm.deactivate();
//TODO check that again //TODO check that again
if (parentController != null) ((TradeController) parentController).onCreateOfferViewRemoved(); if (parentController != null) ((TradeController) parentController).onCreateOfferViewRemoved();
} }
@Override
public void activate() {
super.activate();
//TODO handle in base class
pm.activate();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Public methods // Public methods
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void setOrderBookFilter(OrderBookFilter orderBookFilter) { public void setOrderBookFilter(OrderBookFilter orderBookFilter) {
pm.setOrderBookFilter(orderBookFilter); pm().setOrderBookFilter(orderBookFilter);
} }
@ -119,12 +103,12 @@ public class CreateOfferCB extends CachedViewController {
@FXML @FXML
public void onPlaceOffer() { public void onPlaceOffer() {
pm.placeOffer(); pm().placeOffer();
} }
@FXML @FXML
public void onClose() { public void onClose() {
pm.close(); 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());
@ -137,91 +121,91 @@ public class CreateOfferCB extends CachedViewController {
private void setupListeners() { private void setupListeners() {
volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> { volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm.onFocusOutVolumeTextField(oldValue, newValue); pm().onFocusOutVolumeTextField(oldValue, newValue);
volumeTextField.setText(pm.volume.get()); volumeTextField.setText(pm().volume.get());
}); });
amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm.onFocusOutAmountTextField(oldValue, newValue); pm().onFocusOutAmountTextField(oldValue, newValue);
amountTextField.setText(pm.amount.get()); amountTextField.setText(pm().amount.get());
}); });
priceTextField.focusedProperty().addListener((o, oldValue, newValue) -> { priceTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm.onFocusOutPriceTextField(oldValue, newValue); pm().onFocusOutPriceTextField(oldValue, newValue);
priceTextField.setText(pm.price.get()); priceTextField.setText(pm().price.get());
}); });
minAmountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { minAmountTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
pm.onFocusOutMinAmountTextField(oldValue, newValue); pm().onFocusOutMinAmountTextField(oldValue, newValue);
minAmountTextField.setText(pm.minAmount.get()); minAmountTextField.setText(pm().minAmount.get());
}); });
pm.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> { pm().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); pm().showWarningInvalidBtcDecimalPlaces.set(false);
} }
}); });
pm.showWarningInvalidFiatDecimalPlaces.addListener((o, oldValue, newValue) -> { pm().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); pm().showWarningInvalidFiatDecimalPlaces.set(false);
} }
}); });
pm.showWarningAdjustedVolume.addListener((o, oldValue, newValue) -> { pm().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); pm().showWarningAdjustedVolume.set(false);
volumeTextField.setText(pm.volume.get()); volumeTextField.setText(pm().volume.get());
} }
}); });
pm.requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> { pm().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); pm().requestPlaceOfferErrorMessage);
pm.requestPlaceOfferFailed.set(false); pm().requestPlaceOfferFailed.set(false);
} }
}); });
} }
private void setupBindings() { private void setupBindings() {
buyLabel.textProperty().bind(pm.directionLabel); buyLabel.textProperty().bind(pm().directionLabel);
amountTextField.textProperty().bindBidirectional(pm.amount); amountTextField.textProperty().bindBidirectional(pm().amount);
priceTextField.textProperty().bindBidirectional(pm.price); priceTextField.textProperty().bindBidirectional(pm().price);
volumeTextField.textProperty().bindBidirectional(pm.volume); volumeTextField.textProperty().bindBidirectional(pm().volume);
minAmountTextField.textProperty().bindBidirectional(pm.minAmount); minAmountTextField.textProperty().bindBidirectional(pm().minAmount);
collateralLabel.textProperty().bind(pm.collateralLabel); collateralLabel.textProperty().bind(pm().collateralLabel);
collateralTextField.textProperty().bind(pm.collateral); collateralTextField.textProperty().bind(pm().collateral);
totalToPayTextField.textProperty().bind(pm.totalToPay); totalToPayTextField.textProperty().bind(pm().totalToPay);
addressTextField.amountAsCoinProperty().bind(pm.totalToPayAsCoin); addressTextField.amountAsCoinProperty().bind(pm().totalToPayAsCoin);
addressTextField.paymentLabelProperty().bind(pm.paymentLabel); addressTextField.paymentLabelProperty().bind(pm().paymentLabel);
addressTextField.addressProperty().bind(pm.addressAsString); addressTextField.addressProperty().bind(pm().addressAsString);
bankAccountTypeTextField.textProperty().bind(pm.bankAccountType); bankAccountTypeTextField.textProperty().bind(pm().bankAccountType);
bankAccountCurrencyTextField.textProperty().bind(pm.bankAccountCurrency); bankAccountCurrencyTextField.textProperty().bind(pm().bankAccountCurrency);
bankAccountCountyTextField.textProperty().bind(pm.bankAccountCounty); bankAccountCountyTextField.textProperty().bind(pm().bankAccountCounty);
acceptedCountriesTextField.textProperty().bind(pm.acceptedCountries); acceptedCountriesTextField.textProperty().bind(pm().acceptedCountries);
acceptedLanguagesTextField.textProperty().bind(pm.acceptedLanguages); acceptedLanguagesTextField.textProperty().bind(pm().acceptedLanguages);
totalFeesTextField.textProperty().bind(pm.totalFees); totalFeesTextField.textProperty().bind(pm().totalFees);
transactionIdTextField.textProperty().bind(pm.transactionId); transactionIdTextField.textProperty().bind(pm().transactionId);
amountTextField.amountValidationResultProperty().bind(pm.amountValidationResult); amountTextField.amountValidationResultProperty().bind(pm().amountValidationResult);
minAmountTextField.amountValidationResultProperty().bind(pm.minAmountValidationResult); minAmountTextField.amountValidationResultProperty().bind(pm().minAmountValidationResult);
priceTextField.amountValidationResultProperty().bind(pm.priceValidationResult); priceTextField.amountValidationResultProperty().bind(pm().priceValidationResult);
volumeTextField.amountValidationResultProperty().bind(pm.volumeValidationResult); volumeTextField.amountValidationResultProperty().bind(pm().volumeValidationResult);
placeOfferButton.visibleProperty().bind(pm.isPlaceOfferButtonVisible); placeOfferButton.visibleProperty().bind(pm().isPlaceOfferButtonVisible);
placeOfferButton.disableProperty().bind(pm.isPlaceOfferButtonDisabled); placeOfferButton.disableProperty().bind(pm().isPlaceOfferButtonDisabled);
closeButton.visibleProperty().bind(pm.isCloseButtonVisible); closeButton.visibleProperty().bind(pm().isCloseButtonVisible);
} }
private void configTextFieldValidators() { private void configTextFieldValidators() {

View file

@ -21,6 +21,7 @@ import io.bitsquare.bank.BankAccount;
import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletFacade; import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.UIModel;
import io.bitsquare.locale.Country; import io.bitsquare.locale.Country;
import io.bitsquare.settings.Settings; import io.bitsquare.settings.Settings;
import io.bitsquare.trade.Direction; import io.bitsquare.trade.Direction;
@ -58,7 +59,7 @@ import static io.bitsquare.gui.util.BSFormatter.reduceto4Dezimals;
* Note that the create offer domain has a deeper scope in the application domain (TradeManager). * Note that the create offer domain has a deeper scope in the application domain (TradeManager).
* That model is just responsible for the domain specific parts displayed needed in that UI element. * That model is just responsible for the domain specific parts displayed needed in that UI element.
*/ */
class CreateOfferModel { class CreateOfferModel extends UIModel {
private static final Logger log = LoggerFactory.getLogger(CreateOfferModel.class); private static final Logger log = LoggerFactory.getLogger(CreateOfferModel.class);
private final TradeManager tradeManager; private final TradeManager tradeManager;
@ -99,7 +100,7 @@ class CreateOfferModel {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
CreateOfferModel(TradeManager tradeManager, WalletFacade walletFacade, Settings settings, User user) { public 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;
@ -107,35 +108,52 @@ class CreateOfferModel {
// static data // static data
offerId = UUID.randomUUID().toString(); offerId = UUID.randomUUID().toString();
totalFeesAsCoin.set(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE)); totalFeesAsCoin.setValue(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE));
//TODO just for unit testing, use mockito? //TODO just for unit testing, use mockito?
if (walletFacade != null && walletFacade.getWallet() != null) if (walletFacade != null && walletFacade.getWallet() != null)
addressEntry = walletFacade.getAddressInfoByTradeID(offerId); 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
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialized() {
super.initialized();
} }
@Override
public void activate() {
super.activate();
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void terminate() {
super.terminate();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Methods // Methods
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
void activate() {
// dynamic data, might be changing when switching screen and returning (edit settings)
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());
}
void deactivate() {
}
void placeOffer() { void placeOffer() {
tradeManager.requestPlaceOffer(offerId, tradeManager.requestPlaceOffer(offerId,
@ -144,29 +162,29 @@ class CreateOfferModel {
amountAsCoin.get(), amountAsCoin.get(),
minAmountAsCoin.get(), minAmountAsCoin.get(),
(transaction) -> { (transaction) -> {
requestPlaceOfferSuccess.set(true); requestPlaceOfferSuccess.setValue(true);
transactionId.set(transaction.getHashAsString()); transactionId.setValue(transaction.getHashAsString());
}, },
(errorMessage) -> { (errorMessage) -> {
requestPlaceOfferFailed.set(true); requestPlaceOfferFailed.setValue(true);
requestPlaceOfferErrorMessage.set(errorMessage); requestPlaceOfferErrorMessage.setValue(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.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); volumeAsFiat.setValue(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.set(new ExchangeRate(priceAsFiat.get()).fiatToCoin(volumeAsFiat.get())); amountAsCoin.setValue(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.set(reduceto4Dezimals(amountAsCoin.get())); amountAsCoin.setValue(reduceto4Dezimals(amountAsCoin.get()));
calculateTotalToPay(); calculateTotalToPay();
calculateCollateral(); calculateCollateral();
} }
@ -176,14 +194,14 @@ class CreateOfferModel {
calculateCollateral(); calculateCollateral();
if (collateralAsCoin.get() != null) { if (collateralAsCoin.get() != null) {
totalToPayAsCoin.set(collateralAsCoin.get().add(totalFeesAsCoin.get())); totalToPayAsCoin.setValue(collateralAsCoin.get().add(totalFeesAsCoin.get()));
} }
} }
void calculateCollateral() { void calculateCollateral() {
if (amountAsCoin.get() != null) if (amountAsCoin.get() != null)
collateralAsCoin.set(amountAsCoin.get().multiply(collateralAsLong.get()).divide(1000)); collateralAsCoin.setValue(amountAsCoin.get().multiply(collateralAsLong.get()).divide(1000));
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -18,6 +18,7 @@
package io.bitsquare.gui.trade.createoffer; package io.bitsquare.gui.trade.createoffer;
import io.bitsquare.btc.WalletFacade; import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.PresentationModel;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.validation.BtcValidator; import io.bitsquare.gui.util.validation.BtcValidator;
import io.bitsquare.gui.util.validation.FiatValidator; import io.bitsquare.gui.util.validation.FiatValidator;
@ -44,10 +45,10 @@ import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.BSFormatter.*; import static io.bitsquare.gui.util.BSFormatter.*;
import static javafx.beans.binding.Bindings.createStringBinding; import static javafx.beans.binding.Bindings.createStringBinding;
class CreateOfferPM { class CreateOfferPM extends PresentationModel<CreateOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class); private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class);
private CreateOfferModel model;
private BtcValidator btcValidator = new BtcValidator(); private BtcValidator btcValidator = new BtcValidator();
private FiatValidator fiatValidator = new FiatValidator(); private FiatValidator fiatValidator = new FiatValidator();
@ -86,7 +87,6 @@ class CreateOfferPM {
// That is needed for the addressTextField // That is 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<>();
@ -95,60 +95,60 @@ class CreateOfferPM {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
CreateOfferPM(CreateOfferModel model) { CreateOfferPM(CreateOfferModel model) {
this.model = model; super(model);
}
paymentLabel.setValue("Bitsquare trade (" + model().getOfferId() + ")");
/////////////////////////////////////////////////////////////////////////////////////////// if (model().addressEntry != null) {
// Lifecycle (called by CB) addressAsString.setValue(model().addressEntry.getAddress().toString());
/////////////////////////////////////////////////////////////////////////////////////////// address.setValue(model().addressEntry.getAddress());
void onViewInitialized() {
// todo move to contr.
// static
paymentLabel.set("Bitsquare trade (" + model.getOfferId() + ")");
if (model.addressEntry != null) {
addressAsString.set(model.addressEntry.getAddress().toString());
address.set(model.addressEntry.getAddress());
} }
setupModelBindings(); setupModelBindings();
setupUIInputListeners(); setupUIInputListeners();
// TODO transactionId,
} }
void activate() { ///////////////////////////////////////////////////////////////////////////////////////////
//TODO handle in base class // Lifecycle
model.activate(); ///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialized() {
super.initialized();
} }
@Override
void deactivate() { public void activate() {
//TODO handle in base class super.activate();
model.deactivate();
} }
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void terminate() {
super.terminate();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Public API methods (called by CB) // Public API methods (called by CB)
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
void setOrderBookFilter(OrderBookFilter orderBookFilter) { void setOrderBookFilter(OrderBookFilter orderBookFilter) {
model.setDirection(orderBookFilter.getDirection()); model().setDirection(orderBookFilter.getDirection());
directionLabel.set(model.getDirection() == Direction.BUY ? "Buy:" : "Sell:"); directionLabel.setValue(model().getDirection() == Direction.BUY ? "Buy:" : "Sell:");
if (orderBookFilter.getAmount() != null && isBtcInputValid(orderBookFilter.getAmount().toPlainString()) if (orderBookFilter.getAmount() != null && isBtcInputValid(orderBookFilter.getAmount().toPlainString())
.isValid) { .isValid) {
model.amountAsCoin.set(orderBookFilter.getAmount()); model().amountAsCoin.setValue(orderBookFilter.getAmount());
model.minAmountAsCoin.set(orderBookFilter.getAmount()); model().minAmountAsCoin.setValue(orderBookFilter.getAmount());
} }
// TODO use Fiat in orderBookFilter // TODO use Fiat in orderBookFilter
if (orderBookFilter.getPrice() != 0 && isBtcInputValid(String.valueOf(orderBookFilter.getPrice())).isValid) if (orderBookFilter.getPrice() != 0 && isBtcInputValid(String.valueOf(orderBookFilter.getPrice())).isValid)
model.priceAsFiat.set(parseToFiatWith2Decimals(String.valueOf(orderBookFilter.getPrice()))); model().priceAsFiat.setValue(parseToFiatWith2Decimals(String.valueOf(orderBookFilter.getPrice())));
} }
@ -157,9 +157,9 @@ class CreateOfferPM {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
void placeOffer() { void placeOffer() {
model.placeOffer(); model().placeOffer();
isPlaceOfferButtonDisabled.set(true); isPlaceOfferButtonDisabled.setValue(true);
isPlaceOfferButtonVisible.set(true); isPlaceOfferButtonVisible.setValue(true);
} }
void close() { void close() {
@ -175,22 +175,22 @@ class CreateOfferPM {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(amount.get()); InputValidator.ValidationResult result = isBtcInputValid(amount.get());
boolean isValid = result.isValid; boolean isValid = result.isValid;
amountValidationResult.set(result); amountValidationResult.setValue(result);
if (isValid) { if (isValid) {
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(amount.get())); 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 to general btc format
calculateVolume(); calculateVolume();
if (!model.isMinAmountLessOrEqualAmount()) { if (!model().isMinAmountLessOrEqualAmount()) {
amountValidationResult.set(new InputValidator.ValidationResult(false, amountValidationResult.setValue(new InputValidator.ValidationResult(false,
"Amount cannot be smaller than minimum amount.")); "Amount cannot be smaller than minimum amount."));
} }
else { else {
amountValidationResult.set(result); amountValidationResult.setValue(result);
if (minAmount.get() != null) if (minAmount.get() != null)
minAmountValidationResult.set(isBtcInputValid(minAmount.get())); minAmountValidationResult.setValue(isBtcInputValid(minAmount.get()));
} }
} }
} }
@ -201,19 +201,19 @@ class CreateOfferPM {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(minAmount.get()); InputValidator.ValidationResult result = isBtcInputValid(minAmount.get());
boolean isValid = result.isValid; boolean isValid = result.isValid;
minAmountValidationResult.set(result); minAmountValidationResult.setValue(result);
if (isValid) { if (isValid) {
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(minAmount.get())); showWarningInvalidBtcDecimalPlaces.setValue(!hasBtcValidDecimals(minAmount.get()));
setMinAmountToModel(); setMinAmountToModel();
if (!model.isMinAmountLessOrEqualAmount()) { if (!model().isMinAmountLessOrEqualAmount()) {
minAmountValidationResult.set(new InputValidator.ValidationResult(false, minAmountValidationResult.setValue(new InputValidator.ValidationResult(false,
"Minimum amount cannot be larger than amount.")); "Minimum amount cannot be larger than amount."));
} }
else { else {
minAmountValidationResult.set(result); minAmountValidationResult.setValue(result);
if (amount.get() != null) if (amount.get() != null)
amountValidationResult.set(isBtcInputValid(amount.get())); amountValidationResult.setValue(isBtcInputValid(amount.get()));
} }
} }
} }
@ -223,9 +223,9 @@ class CreateOfferPM {
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.set(result); priceValidationResult.setValue(result);
if (isValid) { if (isValid) {
showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(price.get())); showWarningInvalidFiatDecimalPlaces.setValue(!hasFiatValidDecimals(price.get()));
setPriceToModel(); setPriceToModel();
calculateVolume(); calculateVolume();
@ -237,17 +237,18 @@ class CreateOfferPM {
if (oldValue && !newValue) { if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(volume.get()); InputValidator.ValidationResult result = isBtcInputValid(volume.get());
boolean isValid = result.isValid; boolean isValid = result.isValid;
volumeValidationResult.set(result); volumeValidationResult.setValue(result);
if (isValid) { if (isValid) {
String origVolume = volume.get(); String origVolume = volume.get();
showWarningInvalidFiatDecimalPlaces.set(!hasFiatValidDecimals(volume.get())); showWarningInvalidFiatDecimalPlaces.setValue(!hasFiatValidDecimals(volume.get()));
setVolumeToModel(); setVolumeToModel();
calculateAmount(); calculateAmount();
// must be after calculateAmount (btc value has been adjusted in case the calculation leads to // must be 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.set(!formatFiat(parseToFiatWith2Decimals(origVolume)).equals(volume.get())); showWarningAdjustedVolume.setValue(!formatFiat(parseToFiatWith2Decimals(origVolume)).equals(volume
.get()));
} }
} }
} }
@ -258,7 +259,7 @@ class CreateOfferPM {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
WalletFacade getWalletFacade() { WalletFacade getWalletFacade() {
return model.getWalletFacade(); return model().getWalletFacade();
} }
@ -272,123 +273,111 @@ class CreateOfferPM {
// We do volume/amount calculation during input // We do volume/amount calculation during input
amount.addListener((ov, oldValue, newValue) -> { amount.addListener((ov, oldValue, newValue) -> {
if (isBtcInputValid(newValue).isValid) { if (isBtcInputValid(newValue).isValid) {
model.amountAsCoin.set(parseToCoinWith4Decimals(newValue)); model().amountAsCoin.setValue(parseToCoinWith4Decimals(newValue));
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.set(parseToFiatWith2Decimals(newValue)); model().priceAsFiat.setValue(parseToFiatWith2Decimals(newValue));
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.set(parseToFiatWith2Decimals(newValue)); 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
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() { private void setupModelBindings() {
totalToPay.bind(createStringBinding(() -> formatCoinWithCode(model().totalToPayAsCoin.get()),
amount.bind(Bindings.createObjectBinding(() -> formatCoin(model.amountAsCoin.get()), model.amountAsCoin)); model().totalToPayAsCoin));
minAmount.bind(Bindings.createObjectBinding(() -> formatCoin(model.minAmountAsCoin.get()), collateral.bind(createStringBinding(() -> formatCoinWithCode(model().collateralAsCoin.get()),
model.minAmountAsCoin)); model().collateralAsCoin));
price.bind(Bindings.createObjectBinding(() -> formatFiat(model.priceAsFiat.get()), model.priceAsFiat));
volume.bind(Bindings.createObjectBinding(() -> formatFiat(model.volumeAsFiat.get()), model.volumeAsFiat));
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 collateralLabel.bind(Bindings.createStringBinding(() -> "Collateral (" + BSFormatter.formatCollateralPercent
(model.collateralAsLong.get()) + "):", model.collateralAsLong)); (model().collateralAsLong.get()) + "):", model().collateralAsLong));
totalToPayAsCoin.bind(model.totalToPayAsCoin); totalToPayAsCoin.bind(model().totalToPayAsCoin);
bankAccountType.bind(Bindings.createStringBinding(() -> Localisation.get(model.bankAccountType.get()), bankAccountType.bind(Bindings.createStringBinding(() -> Localisation.get(model().bankAccountType.get()),
model.bankAccountType)); model().bankAccountType));
bankAccountCurrency.bind(model.bankAccountCurrency); bankAccountCurrency.bind(model().bankAccountCurrency);
bankAccountCounty.bind(model.bankAccountCounty); bankAccountCounty.bind(model().bankAccountCounty);
// ObservableLists // ObservableLists
model.acceptedCountries.addListener((Observable o) -> acceptedCountries.set(BSFormatter model().acceptedCountries.addListener((Observable o) -> acceptedCountries.setValue(BSFormatter
.countryLocalesToString(model.acceptedCountries))); .countryLocalesToString(model().acceptedCountries)));
model.acceptedLanguages.addListener((Observable o) -> acceptedLanguages.set(BSFormatter model().acceptedLanguages.addListener((Observable o) -> acceptedLanguages.setValue(BSFormatter
.languageLocalesToString(model.acceptedLanguages))); .languageLocalesToString(model().acceptedLanguages)));
isCloseButtonVisible.bind(model.requestPlaceOfferSuccess); isCloseButtonVisible.bind(model().requestPlaceOfferSuccess);
requestPlaceOfferErrorMessage.bind(model.requestPlaceOfferErrorMessage); requestPlaceOfferErrorMessage.bind(model().requestPlaceOfferErrorMessage);
requestPlaceOfferFailed.bind(model.requestPlaceOfferFailed); requestPlaceOfferFailed.bind(model().requestPlaceOfferFailed);
showTransactionPublishedScreen.bind(model.requestPlaceOfferSuccess); showTransactionPublishedScreen.bind(model().requestPlaceOfferSuccess);
isPlaceOfferButtonDisabled.bind(Bindings.createBooleanBinding(() -> !model().requestPlaceOfferFailed.get(),
model().requestPlaceOfferFailed));
amount.bind(Bindings.createObjectBinding(() -> formatCoin(model.amountAsCoin.get()), model.amountAsCoin)); isPlaceOfferButtonVisible.bind(Bindings.createBooleanBinding(() -> !model().requestPlaceOfferSuccess.get(),
model().requestPlaceOfferSuccess));
isPlaceOfferButtonDisabled.bind(Bindings.createBooleanBinding(() -> !model.requestPlaceOfferFailed.get(),
model.requestPlaceOfferFailed));
isPlaceOfferButtonVisible.bind(Bindings.createBooleanBinding(() -> !model.requestPlaceOfferSuccess.get(),
model.requestPlaceOfferSuccess));
/* model.requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> {
if (newValue) isPlaceOfferButtonDisabled.set(false);
});
model.requestPlaceOfferSuccess.addListener((o, oldValue, newValue) -> {
if (newValue) isPlaceOfferButtonVisible.set(false);
});*/
} }
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()) { if (!model().isMinAmountLessOrEqualAmount()) {
amountValidationResult.set(new InputValidator.ValidationResult(false, amountValidationResult.setValue(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.set(isBtcInputValid(amount.get())); amountValidationResult.setValue(isBtcInputValid(amount.get()));
if (minAmount.get() != null) if (minAmount.get() != null)
minAmountValidationResult.set(isBtcInputValid(minAmount.get())); minAmountValidationResult.setValue(isBtcInputValid(minAmount.get()));
} }
} }
private void setAmountToModel() { private void setAmountToModel() {
model.amountAsCoin.set(parseToCoinWith4Decimals(amount.get())); model().amountAsCoin.setValue(parseToCoinWith4Decimals(amount.get()));
} }
private void setMinAmountToModel() { private void setMinAmountToModel() {
model.minAmountAsCoin.set(parseToCoinWith4Decimals(minAmount.get())); model().minAmountAsCoin.setValue(parseToCoinWith4Decimals(minAmount.get()));
} }
private void setPriceToModel() { private void setPriceToModel() {
model.priceAsFiat.set(parseToFiatWith2Decimals(price.get())); model().priceAsFiat.setValue(parseToFiatWith2Decimals(price.get()));
} }
private void setVolumeToModel() { private void setVolumeToModel() {
model.volumeAsFiat.set(parseToFiatWith2Decimals(volume.get())); model().volumeAsFiat.setValue(parseToFiatWith2Decimals(volume.get()));
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////