diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java index 14a778b65c..47a270962b 100644 --- a/src/main/java/io/bitsquare/btc/WalletFacade.java +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -342,10 +342,12 @@ public class WalletFacade { public TransactionConfidence getConfidenceForAddress(Address address) { List transactionConfidenceList = new ArrayList<>(); - Set transactions = wallet.getTransactions(true); - if (transactions != null) { - transactionConfidenceList.addAll(transactions.stream().map(tx -> - getTransactionConfidence(tx, address)).collect(Collectors.toList())); + if (wallet != null) { + Set transactions = wallet.getTransactions(true); + if (transactions != null) { + transactionConfidenceList.addAll(transactions.stream().map(tx -> + getTransactionConfidence(tx, address)).collect(Collectors.toList())); + } } return getMostRecentConfidence(transactionConfidenceList); } @@ -430,7 +432,7 @@ public class WalletFacade { /////////////////////////////////////////////////////////////////////////////////////////// public Coin getBalanceForAddress(Address address) { - return getBalance(wallet.calculateAllSpendCandidates(true), address); + return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(true), address) : Coin.ZERO; } private Coin getBalance(LinkedList transactionOutputs, Address address) { diff --git a/src/main/java/io/bitsquare/di/BitSquareModule.java b/src/main/java/io/bitsquare/di/BitSquareModule.java index 1a5fc6c4b8..6d6d0f5c78 100644 --- a/src/main/java/io/bitsquare/di/BitSquareModule.java +++ b/src/main/java/io/bitsquare/di/BitSquareModule.java @@ -27,8 +27,8 @@ import io.bitsquare.msg.BootstrappedPeerFactory; import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.P2PNode; import io.bitsquare.msg.SeedNodeAddress; -import io.bitsquare.settings.Settings; import io.bitsquare.persistence.Persistence; +import io.bitsquare.settings.Settings; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.orderbook.OrderBook; import io.bitsquare.user.User; @@ -66,7 +66,7 @@ public class BitSquareModule extends AbstractModule { bind(TradeManager.class).asEagerSingleton(); bind(BSFormatter.class).asEagerSingleton(); - + //bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.MAIN_NET); // how to use reg test see description in the readme file bind(String.class).annotatedWith(Names.named("networkType")).toInstance(WalletFacade.REG_TEST_NET); diff --git a/src/main/java/io/bitsquare/gui/bitsquare.css b/src/main/java/io/bitsquare/gui/bitsquare.css index 9acd21a996..d53968bf31 100644 --- a/src/main/java/io/bitsquare/gui/bitsquare.css +++ b/src/main/java/io/bitsquare/gui/bitsquare.css @@ -79,28 +79,13 @@ -fx-background-color: transparent; } -#qr-code-icon { - -fx-fill: #0096c9; - -fx-cursor: hand; -} - -#copy-icon { - -fx-fill: #0096c9; - -fx-cursor: hand; -} - -#copy-icon .hover { - -fx-fill: #0096c9; - -fx-cursor: hand; -} - .copy-icon { - -fx-fill: #0096c9; + -fx-text-fill: #0096c9; -fx-cursor: hand; } .copy-icon:hover { - -fx-fill: black; + -fx-text-fill: black; } /* Same stlye like non editable textfield. But textfield spans a whole column in a grid, so we use generally textfield */ @@ -118,6 +103,9 @@ -fx-border-radius: 4; -fx-padding: 4 4 4 4; } +#address-label:hover { + -fx-text-fill: black; +} #funds-confidence { -fx-progress-color: dimgrey; diff --git a/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java b/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java index 02f77f127f..a603e7ea69 100644 --- a/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java +++ b/src/main/java/io/bitsquare/gui/components/ValidatingTextField.java @@ -17,7 +17,7 @@ package io.bitsquare.gui.components; -import io.bitsquare.gui.util.validation.NumberValidator; +import io.bitsquare.gui.util.validation.InputValidator; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -38,7 +38,7 @@ 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. */ public class ValidatingTextField extends TextField { @@ -48,7 +48,7 @@ public class ValidatingTextField extends TextField { private Effect invalidEffect = new DropShadow(BlurType.GAUSSIAN, Color.RED, 4, 0.0, 0, 0); private final BooleanProperty isValid = new SimpleBooleanProperty(true); - private NumberValidator numberValidator; + private InputValidator validator; private boolean validateOnFocusOut = true; private boolean needsValidationOnFocusOut; private Region errorPopupLayoutReference; @@ -88,8 +88,8 @@ public class ValidatingTextField extends TextField { // Setters /////////////////////////////////////////////////////////////////////////////////////////// - public void setNumberValidator(NumberValidator numberValidator) { - this.numberValidator = numberValidator; + public void setValidator(InputValidator validator) { + this.validator = validator; } /** @@ -126,7 +126,7 @@ public class ValidatingTextField extends TextField { }); textProperty().addListener((ov, oldValue, newValue) -> { - if (numberValidator != null) { + if (validator != null) { if (!validateOnFocusOut) validate(newValue); else @@ -144,14 +144,14 @@ public class ValidatingTextField extends TextField { } private void validate(String input) { - if (input != null) { - NumberValidator.ValidationResult validationResult = numberValidator.validate(input); + if (input != null && validator != null) { + InputValidator.ValidationResult validationResult = validator.validate(input); isValid.set(validationResult.isValid); applyErrorMessage(validationResult); } } - private void applyErrorMessage(NumberValidator.ValidationResult validationResult) { + private void applyErrorMessage(InputValidator.ValidationResult validationResult) { if (validationResult.isValid) { if (popOver != null) { popOver.hide(); diff --git a/src/main/java/io/bitsquare/gui/components/btc/AddressTextField.java b/src/main/java/io/bitsquare/gui/components/btc/AddressTextField.java index 9ced27f77d..1e55ff0703 100644 --- a/src/main/java/io/bitsquare/gui/components/btc/AddressTextField.java +++ b/src/main/java/io/bitsquare/gui/components/btc/AddressTextField.java @@ -29,6 +29,10 @@ import java.io.IOException; import java.net.URI; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.scene.control.Label; import javafx.scene.control.*; import javafx.scene.image.Image; @@ -51,44 +55,52 @@ import org.slf4j.LoggerFactory; public class AddressTextField extends AnchorPane { private static final Logger log = LoggerFactory.getLogger(AddressTextField.class); - private final Label copyIcon; - private final Label addressLabel; - private final Label qrCode; - private String address; - private Coin amountToPay; - private String paymentLabel; + private final StringProperty address = new SimpleStringProperty(); + private final StringProperty paymentLabel = new SimpleStringProperty(); + public final ObjectProperty amountAsCoin = new SimpleObjectProperty<>(); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// public AddressTextField() { - addressLabel = new Label(); + Label addressLabel = new Label(); addressLabel.setFocusTraversable(false); addressLabel.setId("address-label"); + addressLabel.textProperty().bind(address); + addressLabel.setOnMouseClicked(mouseEvent -> { + try { + if (address != null) + Desktop.getDesktop().browse(URI.create(getBitcoinURI())); + } catch (IOException e) { + log.warn(e.getMessage()); + Popups.openWarningPopup("Information", "Opening a system Bitcoin wallet application has failed. " + + "Perhaps you don't have one installed?"); + } + }); - copyIcon = new Label(); + Label copyIcon = new Label(); copyIcon.setLayoutY(3); - copyIcon.setId("copy-icon"); + copyIcon.getStyleClass().add("copy-icon"); Tooltip.install(copyIcon, new Tooltip("Copy address to clipboard")); AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY); copyIcon.setOnMouseClicked(e -> { - if (address != null && address.length() > 0) { + if (address.get() != null && address.get().length() > 0) { Clipboard clipboard = Clipboard.getSystemClipboard(); ClipboardContent content = new ClipboardContent(); - content.putString(address); + content.putString(address.get()); clipboard.setContent(content); } }); - - qrCode = new Label(); - qrCode.setId("qr-code-icon"); + Label qrCode = new Label(); + qrCode.getStyleClass().add("copy-icon"); qrCode.setLayoutY(3); AwesomeDude.setIcon(qrCode, AwesomeIcon.QRCODE); Tooltip.install(qrCode, new Tooltip("Show QR code for this address")); qrCode.setOnMouseClicked(e -> { - if (address != null && address.length() > 0) { + if (address.get() != null && address.get().length() > 0) { final byte[] imageBytes = QRCode .from(getBitcoinURI()) .withSize(300, 220) @@ -119,16 +131,6 @@ public class AddressTextField extends AnchorPane { AnchorPane.setLeftAnchor(addressLabel, 0.0); getChildren().addAll(addressLabel, copyIcon, qrCode); - - addressLabel.setOnMouseClicked(mouseEvent -> { - try { - if (address != null) - Desktop.getDesktop().browse(URI.create(getBitcoinURI())); - } catch (IOException e) { - log.warn(e.getMessage()); - Popups.openWarningPopup("Opening wallet app failed", "Perhaps you don't have one installed?"); - } - }); } @@ -137,16 +139,39 @@ public class AddressTextField extends AnchorPane { /////////////////////////////////////////////////////////////////////////////////////////// public void setAddress(String address) { - this.address = address; - addressLabel.setText(address); + this.address.set(address); } - public void setAmountToPay(Coin amountToPay) { - this.amountToPay = amountToPay; + public String getAddress() { + return address.get(); + } + + public StringProperty addressProperty() { + return address; + } + + public Coin getAmountAsCoin() { + return amountAsCoin.get(); + } + + public ObjectProperty amountAsCoinProperty() { + return amountAsCoin; + } + + public void setAmountAsCoin(Coin amountAsCoin) { + this.amountAsCoin.set(amountAsCoin); + } + + public String getPaymentLabel() { + return paymentLabel.get(); + } + + public StringProperty paymentLabelProperty() { + return paymentLabel; } public void setPaymentLabel(String paymentLabel) { - this.paymentLabel = paymentLabel; + this.paymentLabel.set(paymentLabel); } @@ -155,7 +180,7 @@ public class AddressTextField extends AnchorPane { /////////////////////////////////////////////////////////////////////////////////////////// private String getBitcoinURI() { - return BitcoinURI.convertToBitcoinURI(address, amountToPay, paymentLabel, null); + return address.get() != null ? BitcoinURI.convertToBitcoinURI(address.get(), amountAsCoin.get(), + paymentLabel.get(), null) : ""; } - } diff --git a/src/main/java/io/bitsquare/gui/components/btc/BalanceTextField.java b/src/main/java/io/bitsquare/gui/components/btc/BalanceTextField.java index ea1e09091d..3751686fec 100644 --- a/src/main/java/io/bitsquare/gui/components/btc/BalanceTextField.java +++ b/src/main/java/io/bitsquare/gui/components/btc/BalanceTextField.java @@ -21,6 +21,7 @@ import io.bitsquare.btc.WalletFacade; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.ConfidenceListener; import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator; +import io.bitsquare.gui.util.BSFormatter; import com.google.bitcoin.core.Address; import com.google.bitcoin.core.Coin; @@ -36,13 +37,8 @@ public class BalanceTextField extends AnchorPane { private static final Logger log = LoggerFactory.getLogger(BalanceTextField.class); private final TextField balanceTextField; - private Address address; private final Tooltip progressIndicatorTooltip; private final ConfidenceProgressIndicator progressIndicator; - private WalletFacade walletFacade; - private ConfidenceListener confidenceListener; - private BalanceListener balanceListener; - private Coin balance; /////////////////////////////////////////////////////////////////////////////////////////// @@ -72,18 +68,8 @@ public class BalanceTextField extends AnchorPane { getChildren().addAll(balanceTextField, progressIndicator); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setters - /////////////////////////////////////////////////////////////////////////////////////////// - - public void setAddress(Address address) { - this.address = address; - } - - public void setWalletFacade(WalletFacade walletFacade) { - this.walletFacade = walletFacade; - confidenceListener = walletFacade.addConfidenceListener(new ConfidenceListener(address) { + public void setup(WalletFacade walletFacade, Address address) { + walletFacade.addConfidenceListener(new ConfidenceListener(address) { @Override public void onTransactionConfidenceChanged(TransactionConfidence confidence) { updateConfidence(confidence); @@ -91,8 +77,7 @@ public class BalanceTextField extends AnchorPane { }); updateConfidence(walletFacade.getConfidenceForAddress(address)); - - balanceListener = walletFacade.addBalanceListener(new BalanceListener(address) { + walletFacade.addBalanceListener(new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance) { updateBalance(balance); @@ -101,21 +86,6 @@ public class BalanceTextField extends AnchorPane { updateBalance(walletFacade.getBalanceForAddress(address)); } - // TODO not called yet... - public void cleanup() { - walletFacade.removeConfidenceListener(confidenceListener); - walletFacade.removeBalanceListener(balanceListener); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters - /////////////////////////////////////////////////////////////////////////////////////////// - - public Coin getBalance() { - return balance; - } - /////////////////////////////////////////////////////////////////////////////////////////// // Private methods @@ -152,11 +122,7 @@ public class BalanceTextField extends AnchorPane { } private void updateBalance(Coin balance) { - this.balance = balance; - if (balance != null) { - //TODO use BSFormatter - balanceTextField.setText(balance.toFriendlyString()); - } + balanceTextField.setText(BSFormatter.formatBtc(balance)); } } diff --git a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCodeBehind.java b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCodeBehind.java index 5202ee5ffe..0eb777987a 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCodeBehind.java +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferCodeBehind.java @@ -34,44 +34,45 @@ import javax.inject.Inject; import javafx.fxml.FXML; import javafx.scene.control.*; -import javafx.scene.layout.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Code behind (FXML Controller is part of View, not a classical controller from MVC): - *

+ * Code behind (FXML Controller is part of View, not a classical MVC controller): + *

* Creates Presenter and passes Model from DI to Presenter. Does not hold a reference to Model - *

+ *

* - Setup binding from Presenter to View elements (also bidirectional - Inputs). Binding are only to presenters * properties, not logical bindings or cross-view element bindings. * - Listen to UI events (Action) from View and call method in Presenter. * - Is entry node for hierarchical view graphs. Passes method calls to Presenter. Calls methods on sub views. * - Handle lifecycle and self removal from scene graph. * - Non declarative (dynamic) view definitions (if it gets larger, then user a ViewBuilder) - *

+ * - Has no logic and no state, only view elements and a presenter reference! + *

* View: * - Mostly declared in FXML. Dynamic parts are declared in Controller. If more view elements need to be defined in * code then use ViewBuilder. - *

+ *

* Optional ViewBuilder: * - Replacement for FXML view definitions. + * + * Note: Don't assign the root node as it is defined in the base class! + * */ public class CreateOfferCodeBehind extends CachedViewController { private static final Logger log = LoggerFactory.getLogger(CreateOfferCodeBehind.class); private final CreateOfferPresenter presenter; - @FXML private AnchorPane rootContainer; @FXML private Label buyLabel, confirmationLabel, txTitleLabel, collateralLabel; - @FXML private ValidatingTextField amountTextField, minAmountTextField, priceTextField, volumeTextField; @FXML private Button placeOfferButton, closeButton; @FXML private TextField totalToPayTextField, collateralTextField, bankAccountTypeTextField, bankAccountCurrencyTextField, bankAccountCountyTextField, acceptedCountriesTextField, acceptedLanguagesTextField, - feeLabel, transactionIdTextField; + totalFeesTextField, transactionIdTextField; @FXML private ConfidenceProgressIndicator progressIndicator; @FXML private AddressTextField addressTextField; @FXML private BalanceTextField balanceTextField; @@ -94,31 +95,30 @@ public class CreateOfferCodeBehind extends CachedViewController { @Override public void initialize(URL url, ResourceBundle rb) { super.initialize(url, rb); + presenter.onViewInitialized(); + + balanceTextField.setup(presenter.getWalletFacade(), presenter.address.get()); } @Override public void deactivate() { super.deactivate(); + presenter.deactivate(); + ((TradeController) parentController).onCreateOfferViewRemoved(); } @Override public void activate() { super.activate(); + presenter.activate(); setupBindings(); setupListeners(); setupTextFieldValidators(); - - - //addressTextField.setAddress(addressEntry.getAddress().toString()); - //addressTextField.setPaymentLabel("Bitsquare trade (" + offerId + ")"); - - // balanceTextField.setAddress(addressEntry.getAddress()); - //TODO balanceTextField.setWalletFacade(walletFacade); } @@ -130,6 +130,7 @@ public class CreateOfferCodeBehind extends CachedViewController { presenter.setOrderBookFilter(orderBookFilter); } + /////////////////////////////////////////////////////////////////////////////////////////// // UI Handlers /////////////////////////////////////////////////////////////////////////////////////////// @@ -143,7 +144,7 @@ public class CreateOfferCodeBehind extends CachedViewController { public void onClose() { presenter.close(); - TabPane tabPane = ((TabPane) (rootContainer.getParent().getParent())); + TabPane tabPane = ((TabPane) (root.getParent().getParent())); tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem()); } @@ -154,7 +155,7 @@ public class CreateOfferCodeBehind extends CachedViewController { private void setupListeners() { volumeTextField.focusedProperty().addListener((observableValue, oldValue, - newValue) -> presenter.checkVolumeOnFocusOut(oldValue, + newValue) -> presenter.validateVolumeOnFocusOut(oldValue, newValue, volumeTextField.getText())); amountTextField.focusedProperty().addListener((observableValue, oldValue, newValue) -> presenter.onFocusOutAmountTextField(oldValue, @@ -163,7 +164,7 @@ public class CreateOfferCodeBehind extends CachedViewController { newValue) -> presenter.onFocusOutPriceTextField(oldValue, newValue)); - presenter.validateInput.addListener((o, oldValue, newValue) -> { + presenter.needsInputValidation.addListener((o, oldValue, newValue) -> { if (newValue) { amountTextField.reValidate(); minAmountTextField.reValidate(); @@ -179,6 +180,12 @@ public class CreateOfferCodeBehind extends CachedViewController { volumeTextField.setText(presenter.volume.get()); } }); + presenter.requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> { + if (newValue) { + Popups.openErrorPopup("Error", "An error occurred when placing the offer.\n" + + presenter.requestPlaceOfferErrorMessage); + } + }); } private void setupBindings() { @@ -192,17 +199,22 @@ public class CreateOfferCodeBehind extends CachedViewController { collateralTextField.textProperty().bind(presenter.collateral); totalToPayTextField.textProperty().bind(presenter.totalToPay); + addressTextField.amountAsCoinProperty().bind(presenter.totalToPayAsCoin); + addressTextField.paymentLabelProperty().bind(presenter.paymentLabel); + addressTextField.addressProperty().bind(presenter.addressAsString); + bankAccountTypeTextField.textProperty().bind(presenter.bankAccountType); bankAccountCurrencyTextField.textProperty().bind(presenter.bankAccountCurrency); bankAccountCountyTextField.textProperty().bind(presenter.bankAccountCounty); acceptedCountriesTextField.textProperty().bind(presenter.acceptedCountries); acceptedLanguagesTextField.textProperty().bind(presenter.acceptedLanguages); - feeLabel.textProperty().bind(presenter.totalFeesLabel); + totalFeesTextField.textProperty().bind(presenter.totalFees); transactionIdTextField.textProperty().bind(presenter.transactionId); - placeOfferButton.visibleProperty().bind(presenter.placeOfferButtonVisible); - closeButton.visibleProperty().bind(presenter.isOfferPlacedScreen); + placeOfferButton.visibleProperty().bind(presenter.isPlaceOfferButtonVisible); + placeOfferButton.disableProperty().bind(presenter.isPlaceOfferButtonDisabled); + closeButton.visibleProperty().bind(presenter.isCloseButtonVisible); //TODO /* progressIndicator.visibleProperty().bind(viewModel.isOfferPlacedScreen); @@ -240,57 +252,5 @@ public class CreateOfferCodeBehind extends CachedViewController { amountValidator, minAmountValidator);*/ } - /* - private void setVolume() - { - amountAsCoin = parseToCoin(presenter.amount.get()); - priceAsFiat = parseToFiat(presenter.price.get()); - - if (priceAsFiat != null && amountAsCoin != null) - { - tradeVolumeAsFiat = new ExchangeRate(priceAsFiat).coinToFiat(amountAsCoin); - presenter.volume.set(formatFiat(tradeVolumeAsFiat)); - } - } - - private void setAmount() - { - tradeVolumeAsFiat = parseToFiat(presenter.volume.get()); - priceAsFiat = parseToFiat(presenter.price.get()); - - if (tradeVolumeAsFiat != null && priceAsFiat != null && !priceAsFiat.isZero()) - { - amountAsCoin = new ExchangeRate(priceAsFiat).fiatToCoin(tradeVolumeAsFiat); - - // If we got a btc value with more then 4 decimals we convert it to max 4 decimals - amountAsCoin = parseToCoin(formatBtc(amountAsCoin)); - - presenter.amount.set(formatBtc(amountAsCoin)); - setTotalToPay(); - setCollateral(); - } - } - - private void setTotalToPay() - { - setCollateral(); - - totalFeesAsCoin = FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE); - - if (collateralAsCoin != null) - { - totalToPayAsCoin = collateralAsCoin.add(totalFeesAsCoin); - presenter.totalToPay.set(formatBtcWithCode(totalToPayAsCoin)); - } - } - - private void setCollateral() - { - if (amountAsCoin != null) - { - collateralAsCoin = amountAsCoin.multiply(collateralAsLong).divide(1000); - presenter.collateral.set(BSFormatter.formatBtcWithCode(collateralAsCoin)); - } - }*/ } 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 0d4a0a6fb7..9b110ed50d 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferModel.java +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferModel.java @@ -54,10 +54,10 @@ import static com.google.common.base.Preconditions.checkArgument; /** * Data model: * Does not know the Presenter and View (CodeBehind) - * Use Guice for DI - *

+ * Use Guice for DI to get domain objects + *

* - Holds domain data - * - Use Properties for bindable data + * - Apply business logic (no view related, that is done in presenter) */ class CreateOfferModel { private static final Logger log = LoggerFactory.getLogger(CreateOfferModel.class); @@ -67,38 +67,34 @@ class CreateOfferModel { private final Settings settings; private final User user; - String getOfferId() { - return offerId; - } - private final String offerId; - - final Coin totalFeesAsCoin; - - private Direction direction = null; + final Coin totalFeesAsCoin; Coin amountAsCoin; Coin minAmountAsCoin; Coin collateralAsCoin; Fiat priceAsFiat; Fiat tradeVolumeAsFiat; - AddressEntry addressEntry; - final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); - final LongProperty collateralAsLong = new SimpleLongProperty(); - final BooleanProperty requestPlaceOfferSuccess = new SimpleBooleanProperty(false); - final BooleanProperty requestPlaceOfferFailed = new SimpleBooleanProperty(false); final StringProperty requestPlaceOfferErrorMessage = new SimpleStringProperty(); final StringProperty transactionId = new SimpleStringProperty(); - final StringProperty bankAccountCurrency = new SimpleStringProperty(); final StringProperty bankAccountCounty = new SimpleStringProperty(); final StringProperty bankAccountType = new SimpleStringProperty(); + + final LongProperty collateralAsLong = new SimpleLongProperty(); + + final BooleanProperty requestPlaceOfferSuccess = new SimpleBooleanProperty(); + final BooleanProperty requestPlaceOfferFailed = new SimpleBooleanProperty(); + + final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); + ObservableList acceptedCountries = FXCollections.observableArrayList(); ObservableList acceptedLanguages = FXCollections.observableArrayList(); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @@ -110,8 +106,12 @@ class CreateOfferModel { this.settings = settings; this.user = user; + // static data offerId = UUID.randomUUID().toString(); totalFeesAsCoin = 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); } @@ -122,6 +122,7 @@ class CreateOfferModel { /////////////////////////////////////////////////////////////////////////////////////////// void activate() { + // dynamic data, might be changing when switching screen and returning (edit settings) collateralAsLong.set(settings.getCollateral()); BankAccount bankAccount = user.getCurrentBankAccount(); @@ -134,10 +135,13 @@ class CreateOfferModel { acceptedLanguages.setAll(settings.getAcceptedLanguageLocales()); } + void deactivate() { + } + void placeOffer() { tradeManager.requestPlaceOffer(offerId, direction, - priceAsFiat.value, + priceAsFiat, amountAsCoin, minAmountAsCoin, (transaction) -> { @@ -161,10 +165,17 @@ class CreateOfferModel { } void setDirection(Direction direction) { - // direction must not be changed once it is initially set + // direction can not be changed once it is initially set checkArgument(this.direction == null); this.direction = direction; } + public WalletFacade getWalletFacade() { + return walletFacade; + } + + String getOfferId() { + return offerId; + } } diff --git a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPresenter.java b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPresenter.java index 2960042408..260d1dfd96 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPresenter.java +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferPresenter.java @@ -17,11 +17,13 @@ package io.bitsquare.gui.trade.createoffer; +import io.bitsquare.btc.WalletFacade; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.locale.Localisation; import io.bitsquare.trade.Direction; import io.bitsquare.trade.orderbook.OrderBookFilter; +import com.google.bitcoin.core.Address; import com.google.bitcoin.core.Coin; import com.google.bitcoin.utils.ExchangeRate; @@ -43,12 +45,13 @@ import static javafx.beans.binding.Bindings.createStringBinding; /** * Presenter: * Knows Model, does not know the View (CodeBehind) - *

- * - Holds data and state of the View (formatted) - * - Receive view input from CodeBehind. Validates input, apply business logic, format to Presenter properties and - * convert input to Model. - * - Listen to updates from Model, apply business logic and format it to Presenter properties. Model update handling - * can be done via Binding. + *

+ * - Holds data and state of the View (formatting,...) + * - Receive user input via method calls from CodeBehind. + * - Validates input, applies business logic and converts input to Model. + * - Format model data to properties used for binding from the view. + * - Listen to updates from Model via Bindings. + * - Is testable */ class CreateOfferPresenter { private static final Logger log = LoggerFactory.getLogger(CreateOfferPresenter.class); @@ -63,21 +66,27 @@ class CreateOfferPresenter { final StringProperty totalToPay = new SimpleStringProperty(); final StringProperty directionLabel = new SimpleStringProperty(); final StringProperty collateralLabel = new SimpleStringProperty(); - final StringProperty totalFeesLabel = new SimpleStringProperty(); + final StringProperty totalFees = new SimpleStringProperty(); final StringProperty bankAccountType = new SimpleStringProperty(); final StringProperty bankAccountCurrency = new SimpleStringProperty(); final StringProperty bankAccountCounty = new SimpleStringProperty(); final StringProperty acceptedCountries = new SimpleStringProperty(); final StringProperty acceptedLanguages = new SimpleStringProperty(); - final StringProperty address = new SimpleStringProperty(); + final StringProperty addressAsString = new SimpleStringProperty(); final StringProperty paymentLabel = new SimpleStringProperty(); final StringProperty transactionId = new SimpleStringProperty(); - final BooleanProperty isOfferPlacedScreen = new SimpleBooleanProperty(); - final BooleanProperty placeOfferButtonVisible = new SimpleBooleanProperty(true); + final StringProperty requestPlaceOfferErrorMessage = new SimpleStringProperty(); + + final BooleanProperty isCloseButtonVisible = new SimpleBooleanProperty(); + final BooleanProperty isPlaceOfferButtonVisible = new SimpleBooleanProperty(true); final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(); - final BooleanProperty validateInput = new SimpleBooleanProperty(); + final BooleanProperty needsInputValidation = new SimpleBooleanProperty(); final BooleanProperty showVolumeAdjustedWarning = new SimpleBooleanProperty(); + final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty(); + final BooleanProperty requestPlaceOfferFailed = new SimpleBooleanProperty(); + final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); + final ObjectProperty

address = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,9 +103,13 @@ class CreateOfferPresenter { /////////////////////////////////////////////////////////////////////////////////////////// void onViewInitialized() { - totalFeesLabel.set(BSFormatter.formatBtc(model.totalFeesAsCoin)); + totalFees.set(BSFormatter.formatBtc(model.totalFeesAsCoin)); paymentLabel.set("Bitsquare trade (" + model.getOfferId() + ")"); - // address.set(model.addressEntry.getAddress().toString()); + + if (model.addressEntry != null) { + addressAsString.set(model.addressEntry.getAddress().toString()); + address.set(model.addressEntry.getAddress()); + } setupInputListeners(); @@ -113,32 +126,43 @@ class CreateOfferPresenter { model.acceptedLanguages.addListener((Observable o) -> acceptedLanguages.set(BSFormatter .languageLocalesToString(model.acceptedLanguages))); - } + isCloseButtonVisible.bind(model.requestPlaceOfferSuccess); + requestPlaceOfferErrorMessage.bind(model.requestPlaceOfferErrorMessage); + requestPlaceOfferFailed.bind(model.requestPlaceOfferFailed); + showTransactionPublishedScreen.bind(model.requestPlaceOfferSuccess); - void deactivate() { + model.requestPlaceOfferFailed.addListener((o, oldValue, newValue) -> { + if (newValue) isPlaceOfferButtonDisabled.set(false); + }); + + model.requestPlaceOfferSuccess.addListener((o, oldValue, newValue) -> { + if (newValue) isPlaceOfferButtonVisible.set(false); + }); + + // TODO transactionId, } void activate() { model.activate(); - - - // totalToPay.addListener((ov) -> addressTextField.setAmountToPay(model.totalToPayAsCoin)); } + void deactivate() { + model.deactivate(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Public methods /////////////////////////////////////////////////////////////////////////////////////////// - void setOrderBookFilter(OrderBookFilter orderBookFilter) { - // model model.setDirection(orderBookFilter.getDirection()); model.amountAsCoin = orderBookFilter.getAmount(); model.minAmountAsCoin = orderBookFilter.getAmount(); - //TODO + + // TODO use Fiat in orderBookFilter model.priceAsFiat = parseToFiat(String.valueOf(orderBookFilter.getPrice())); - // view props directionLabel.set(model.getDirection() == Direction.BUY ? "Buy:" : "Sell:"); amount.set(formatBtc(model.amountAsCoin)); minAmount.set(formatBtc(model.minAmountAsCoin)); @@ -156,115 +180,52 @@ class CreateOfferPresenter { model.priceAsFiat = parseToFiat(price.get()); model.minAmountAsCoin = parseToCoin(minAmount.get()); - validateInput.set(true); - - //balanceTextField.getBalance() + needsInputValidation.set(true); if (inputValid()) { model.placeOffer(); isPlaceOfferButtonDisabled.set(true); - placeOfferButtonVisible.set(true); - + isPlaceOfferButtonVisible.set(true); } - - /* - { - isOfferPlacedScreen.set(true); - transactionId.set(transaction.getHashAsString()); - } - errorMessage -> { - Popups.openErrorPopup("An error occurred", errorMessage); - isPlaceOfferButtonDisabled.set(false); - } - */ } - void close() { - } /////////////////////////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////////////////////// - private boolean inputValid() { - //TODO - return true; - } - void setupInputListeners() { // bindBidirectional for amount, price, volume and minAmount amount.addListener(ov -> { model.amountAsCoin = parseToCoin(amount.get()); - setVolume(); - setTotalToPay(); - setCollateral(); + calculateVolume(); + calculateTotalToPay(); + calculateCollateral(); }); price.addListener(ov -> { model.priceAsFiat = parseToFiat(price.get()); - setVolume(); - setTotalToPay(); - setCollateral(); + calculateVolume(); + calculateTotalToPay(); + calculateCollateral(); }); volume.addListener(ov -> { model.tradeVolumeAsFiat = parseToFiat(volume.get()); - setAmount(); - setTotalToPay(); - setCollateral(); + calculateAmount(); + calculateTotalToPay(); + calculateCollateral(); }); } - private void setVolume() { - model.amountAsCoin = parseToCoin(amount.get()); - model.priceAsFiat = parseToFiat(price.get()); - - if (model.priceAsFiat != null && model.amountAsCoin != null && !model.amountAsCoin.isZero()) { - model.tradeVolumeAsFiat = new ExchangeRate(model.priceAsFiat).coinToFiat(model.amountAsCoin); - volume.set(formatFiat(model.tradeVolumeAsFiat)); - } - } - - private void setAmount() { - model.tradeVolumeAsFiat = parseToFiat(volume.get()); - model.priceAsFiat = parseToFiat(price.get()); - - if (model.tradeVolumeAsFiat != null && model.priceAsFiat != null && !model.priceAsFiat.isZero()) { - model.amountAsCoin = new ExchangeRate(model.priceAsFiat).fiatToCoin(model.tradeVolumeAsFiat); - - // If we got a btc value with more then 4 decimals we convert it to max 4 decimals - model.amountAsCoin = applyFormatRules(model.amountAsCoin); - amount.set(formatBtc(model.amountAsCoin)); - setTotalToPay(); - setCollateral(); - } - } - - private void setTotalToPay() { - setCollateral(); - - if (model.collateralAsCoin != null) { - model.totalToPayAsCoin.set(model.collateralAsCoin.add(model.totalFeesAsCoin)); - totalToPay.bind(createStringBinding(() -> formatBtcWithCode(model.totalToPayAsCoin.get()), - model.totalToPayAsCoin)); - } - } - - private void setCollateral() { - if (model.amountAsCoin != null) { - model.collateralAsCoin = model.amountAsCoin.multiply(model.collateralAsLong.get()).divide(1000); - collateral.set(BSFormatter.formatBtcWithCode(model.collateralAsCoin)); - } - } - // We adjust the volume if fractional coins result from volume/price division on focus out - void checkVolumeOnFocusOut(Boolean oldValue, Boolean newValue, String volumeTextFieldText) { + void validateVolumeOnFocusOut(Boolean oldValue, Boolean newValue, String volumeTextFieldText) { if (oldValue && !newValue) { - setVolume(); + calculateVolume(); if (!formatFiat(parseToFiat(volumeTextFieldText)).equals(volume.get())) showVolumeAdjustedWarning.set(true); } @@ -293,15 +254,59 @@ class CreateOfferPresenter { // Getters /////////////////////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setters - /////////////////////////////////////////////////////////////////////////////////////////// + WalletFacade getWalletFacade() { + return model.getWalletFacade(); + } /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods + // Private /////////////////////////////////////////////////////////////////////////////////////////// + private boolean inputValid() { + //TODO + return true; + } + private void calculateVolume() { + model.amountAsCoin = parseToCoin(amount.get()); + model.priceAsFiat = parseToFiat(price.get()); + + if (model.priceAsFiat != null && model.amountAsCoin != null && !model.amountAsCoin.isZero()) { + model.tradeVolumeAsFiat = new ExchangeRate(model.priceAsFiat).coinToFiat(model.amountAsCoin); + volume.set(formatFiat(model.tradeVolumeAsFiat)); + } + } + + private void calculateAmount() { + model.tradeVolumeAsFiat = parseToFiat(volume.get()); + model.priceAsFiat = parseToFiat(price.get()); + + if (model.tradeVolumeAsFiat != null && model.priceAsFiat != null && !model.priceAsFiat.isZero()) { + model.amountAsCoin = new ExchangeRate(model.priceAsFiat).fiatToCoin(model.tradeVolumeAsFiat); + + // If we got a btc value with more then 4 decimals we convert it to max 4 decimals + model.amountAsCoin = applyFormatRules(model.amountAsCoin); + amount.set(formatBtc(model.amountAsCoin)); + calculateTotalToPay(); + calculateCollateral(); + } + } + + private void calculateTotalToPay() { + calculateCollateral(); + + if (model.collateralAsCoin != null) { + model.totalToPayAsCoin.set(model.collateralAsCoin.add(model.totalFeesAsCoin)); + totalToPay.bind(createStringBinding(() -> formatBtcWithCode(model.totalToPayAsCoin.get()), + model.totalToPayAsCoin)); + } + } + + private void calculateCollateral() { + if (model.amountAsCoin != null) { + model.collateralAsCoin = model.amountAsCoin.multiply(model.collateralAsLong.get()).divide(1000); + collateral.set(BSFormatter.formatBtcWithCode(model.collateralAsCoin)); + } + } } 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 32ca4dbed8..fd363ac20a 100644 --- a/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferView.fxml +++ b/src/main/java/io/bitsquare/gui/trade/createoffer/CreateOfferView.fxml @@ -111,7 +111,7 @@ focusTraversable="false"/>