update MVP for create offer, improve addressTF, balanceTF, create inputvalidator

This commit is contained in:
Manfred Karrer 2014-08-27 01:29:34 +02:00
parent e8761467ca
commit 8e22e42556
15 changed files with 377 additions and 379 deletions

View file

@ -342,10 +342,12 @@ public class WalletFacade {
public TransactionConfidence getConfidenceForAddress(Address address) {
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
Set<Transaction> transactions = wallet.getTransactions(true);
if (transactions != null) {
transactionConfidenceList.addAll(transactions.stream().map(tx ->
getTransactionConfidence(tx, address)).collect(Collectors.toList()));
if (wallet != null) {
Set<Transaction> 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<TransactionOutput> transactionOutputs, Address address) {

View file

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

View file

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

View file

@ -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.
* <p/>
* <p>
* 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();

View file

@ -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<Coin> 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<Coin> 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) : "";
}
}

View file

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

View file

@ -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):
* <p/>
* Code behind (FXML Controller is part of View, not a classical MVC controller):
* <p>
* Creates Presenter and passes Model from DI to Presenter. Does not hold a reference to Model
* <p/>
* <p>
* - 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)
* <p/>
* - Has no logic and no state, only view elements and a presenter reference!
* <p>
* View:
* - Mostly declared in FXML. Dynamic parts are declared in Controller. If more view elements need to be defined in
* code then use ViewBuilder.
* <p/>
* <p>
* 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));
}
}*/
}

View file

@ -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
* <p/>
* Use Guice for DI to get domain objects
* <p>
* - 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<Coin> 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<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
ObservableList<Country> acceptedCountries = FXCollections.observableArrayList();
ObservableList<Locale> 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;
}
}

View file

@ -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)
* <p/>
* - 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.
* <p>
* - 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<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Address> 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));
}
}
}

View file

@ -111,7 +111,7 @@
focusTraversable="false"/>
<Label GridPane.rowIndex="11" text="Offer Fee + transaction fee:"/>
<TextField GridPane.rowIndex="11" fx:id="feeLabel" editable="false" focusTraversable="false"
<TextField GridPane.rowIndex="11" fx:id="totalFeesTextField" editable="false" focusTraversable="false"
GridPane.columnIndex="1"/>
@ -163,7 +163,7 @@
<Label fx:id="confirmationLabel" text="Checking confirmations..." visible="false" GridPane.columnIndex="3"
GridPane.rowIndex="14"/>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="400"/>

View file

@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
/**
* BtcValidator for validating BTC values.
* <p/>
* <p>
* That class implements just what we need for the moment. It is not intended as a general purpose library class.
*/
public class BtcValidator extends NumberValidator {

View file

@ -0,0 +1,107 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.util.validation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* BaseValidator for validating basic number values.
* Localisation not supported at the moment
* The decimal mark can be either "." or ",". Thousand separators are not supported yet,
* but might be added alter with Local support.
* <p/>
* That class implements just what we need for the moment. It is not intended as a general purpose library class.
*/
public abstract class InputValidator {
private static final Logger log = LoggerFactory.getLogger(InputValidator.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Abstract methods
///////////////////////////////////////////////////////////////////////////////////////////
abstract public ValidationResult validate(String input);
///////////////////////////////////////////////////////////////////////////////////////////
// Protected methods
///////////////////////////////////////////////////////////////////////////////////////////
protected ValidationResult validateIfNotEmpty(String input) {
if (input == null || input.length() == 0)
return new ValidationResult(false, "Empty input is not allowed.", ErrorType.EMPTY_INPUT);
else
return new ValidationResult(true);
}
protected String cleanInput(String input) {
return input.replace(",", ".").trim();
}
///////////////////////////////////////////////////////////////////////////////////////////
// ErrorType
///////////////////////////////////////////////////////////////////////////////////////////
public enum ErrorType {
EMPTY_INPUT,
NOT_A_NUMBER,
ZERO_NUMBER,
NEGATIVE_NUMBER,
FRACTIONAL_SATOSHI,
EXCEEDS_MAX_FIAT_VALUE, UNDERCUT_MIN_FIAT_VALUE, AMOUNT_LESS_THAN_MIN_AMOUNT,
MIN_AMOUNT_LARGER_THAN_MIN_AMOUNT, EXCEEDS_MAX_BTC_VALUE
}
///////////////////////////////////////////////////////////////////////////////////////////
// ValidationResult
///////////////////////////////////////////////////////////////////////////////////////////
public static class ValidationResult {
public final boolean isValid;
public final String errorMessage;
public final ErrorType errorType;
public ValidationResult(boolean isValid, String errorMessage, ErrorType errorType) {
this.isValid = isValid;
this.errorMessage = errorMessage;
this.errorType = errorType;
}
ValidationResult(boolean isValid) {
this(isValid, null, null);
}
public ValidationResult and(ValidationResult next) {
if (this.isValid)
return next;
else
return this;
}
@Override
public String toString() {
return "ValidationResult{" +
"isValid=" + isValid +
", errorMessage='" + errorMessage + '\'' +
", errorType=" + errorType +
'}';
}
}
}

View file

@ -28,27 +28,14 @@ import org.slf4j.LoggerFactory;
* <p/>
* That class implements just what we need for the moment. It is not intended as a general purpose library class.
*/
public abstract class NumberValidator {
public abstract class NumberValidator extends InputValidator {
private static final Logger log = LoggerFactory.getLogger(NumberValidator.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Abstract methods
///////////////////////////////////////////////////////////////////////////////////////////
abstract public ValidationResult validate(String input);
///////////////////////////////////////////////////////////////////////////////////////////
// Protected methods
///////////////////////////////////////////////////////////////////////////////////////////
protected ValidationResult validateIfNotEmpty(String input) {
if (input == null || input.length() == 0)
return new ValidationResult(false, "Empty input is not allowed.", ErrorType.EMPTY_INPUT);
else
return new ValidationResult(true);
}
protected String cleanInput(String input) {
return input.replace(",", ".").trim();
}
@ -75,56 +62,4 @@ public abstract class NumberValidator {
else
return new ValidationResult(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// ErrorType
///////////////////////////////////////////////////////////////////////////////////////////
public enum ErrorType {
EMPTY_INPUT,
NOT_A_NUMBER,
ZERO_NUMBER,
NEGATIVE_NUMBER,
FRACTIONAL_SATOSHI,
EXCEEDS_MAX_FIAT_VALUE, UNDERCUT_MIN_FIAT_VALUE, AMOUNT_LESS_THAN_MIN_AMOUNT,
MIN_AMOUNT_LARGER_THAN_MIN_AMOUNT, EXCEEDS_MAX_BTC_VALUE
}
///////////////////////////////////////////////////////////////////////////////////////////
// ValidationResult
///////////////////////////////////////////////////////////////////////////////////////////
public static class ValidationResult {
public final boolean isValid;
public final String errorMessage;
public final ErrorType errorType;
public ValidationResult(boolean isValid, String errorMessage, ErrorType errorType) {
this.isValid = isValid;
this.errorMessage = errorMessage;
this.errorType = errorType;
}
ValidationResult(boolean isValid) {
this(isValid, null, null);
}
public ValidationResult and(ValidationResult next) {
if (this.isValid)
return next;
else
return this;
}
@Override
public String toString() {
return "ValidationResult{" +
"isValid=" + isValid +
", errorMessage='" + errorMessage + '\'' +
", errorType=" + errorType +
'}';
}
}
}

View file

@ -23,12 +23,12 @@ import io.bitsquare.crypto.CryptoFacade;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.TakeOfferRequestListener;
import io.bitsquare.settings.Settings;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.settings.Settings;
import io.bitsquare.trade.handlers.ErrorMessageHandler;
import io.bitsquare.trade.handlers.TransactionResultHandler;
import io.bitsquare.trade.protocol.trade.TradeMessage;
import io.bitsquare.trade.protocol.createoffer.CreateOfferCoordinator;
import io.bitsquare.trade.protocol.trade.TradeMessage;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAcceptsOfferProtocol;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAcceptsOfferProtocolListener;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferInitedMessage;
@ -47,6 +47,7 @@ import com.google.bitcoin.core.Coin;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.bitcoin.core.Utils;
import com.google.bitcoin.utils.Fiat;
import java.io.IOException;
@ -68,7 +69,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The domain for the trading
* The domain for the trading
* TODO: Too messy, need to be improved a lot....
*/
public class TradeManager {
@ -160,16 +161,17 @@ public class TradeManager {
public void requestPlaceOffer(String id,
Direction direction,
double price,
Fiat price,
Coin amount,
Coin minAmount,
TransactionResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
// TODO price.value -> use Fiat in Offer for price
Offer offer = new Offer(id,
user.getMessagePublicKey(),
direction,
price,
price.value,
amount,
minAmount,
user.getCurrentBankAccount().getBankAccountType(),

View file

@ -17,14 +17,11 @@
package io.bitsquare.trade.protocol.createoffer.tasks;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.handlers.FaultHandler;
import io.bitsquare.trade.handlers.ResultHandler;
import com.google.bitcoin.core.Coin;
import javax.annotation.concurrent.Immutable;
import org.slf4j.Logger;
@ -62,8 +59,8 @@ public class ValidateOffer {
checkArgument(offer.getPrice() > 0);
// TODO check balance
Coin collateralAsCoin = offer.getAmount().divide((long) (1d / offer.getCollateral()));
Coin totalsToFund = collateralAsCoin.add(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE));
// Coin collateralAsCoin = offer.getAmount().divide((long) (1d / offer.getCollateral()));
// Coin totalsToFund = collateralAsCoin.add(FeePolicy.CREATE_OFFER_FEE.add(FeePolicy.TX_FEE));
// getAddressInfoByTradeID(offerId)
// TODO when offer is flattened continue here...