Handle errors at take offer screen

This commit is contained in:
Manfred Karrer 2015-03-17 13:25:15 +01:00
parent 9a0e174099
commit e6744fb727
9 changed files with 295 additions and 221 deletions

View File

@ -21,13 +21,13 @@ import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.offer.Offer;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.Preferences;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.common.viewfx.model.Activatable; import io.bitsquare.common.viewfx.model.Activatable;
import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.offer.Offer;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate; import org.bitcoinj.utils.ExchangeRate;
@ -63,11 +63,8 @@ class TakeOfferDataModel implements Activatable, DataModel {
private Offer offer; private Offer offer;
private AddressEntry addressEntry; private AddressEntry addressEntry;
final StringProperty requestTakeOfferErrorMessage = new SimpleStringProperty();
final StringProperty transactionId = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty(); final StringProperty btcCode = new SimpleStringProperty();
final BooleanProperty requestTakeOfferSuccess = new SimpleBooleanProperty();
final BooleanProperty isWalletFunded = new SimpleBooleanProperty(); final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
final BooleanProperty useMBTC = new SimpleBooleanProperty(); final BooleanProperty useMBTC = new SimpleBooleanProperty();
@ -78,8 +75,6 @@ class TakeOfferDataModel implements Activatable, DataModel {
final ObjectProperty<Coin> offerFeeAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> offerFeeAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> networkFeeAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> networkFeeAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Offer.State> offerIsAvailable = new SimpleObjectProperty<>(Offer.State.UNKNOWN);
@Inject @Inject
public TakeOfferDataModel(TradeManager tradeManager, public TakeOfferDataModel(TradeManager tradeManager,
WalletService walletService, WalletService walletService,
@ -130,64 +125,11 @@ class TakeOfferDataModel implements Activatable, DataModel {
}); });
updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress())); updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress()));
offer.stateProperty().addListener((observable, oldValue, newValue) -> {
offerIsAvailable.set(newValue);
});
tradeManager.checkOfferAvailability(offer); tradeManager.checkOfferAvailability(offer);
} }
void takeOffer() { Trade takeOffer() {
final Trade trade = tradeManager.takeOffer(amountAsCoin.get(), offer); return tradeManager.takeOffer(amountAsCoin.get(), offer);
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue);
String errorMessage = "";
if (newValue.getErrorMessage() != null)
errorMessage = "\nError message: " + newValue.getErrorMessage();
switch (newValue) {
case OPEN:
break;
case OFFERER_ACCEPTED:
break;
case OFFERER_REJECTED:
requestTakeOfferErrorMessage.set("Take offer request got rejected. Maybe another trader has taken the offer in the meantime.");
break;
case TAKE_OFFER_FEE_TX_CREATED:
break;
case DEPOSIT_PUBLISHED:
case DEPOSIT_CONFIRMED:
// TODO Check why DEPOSIT_CONFIRMED can happen, refactor state handling
// TODO null pointer happened here!
if (trade.getDepositTx() != null) {
transactionId.set(trade.getDepositTx().getHashAsString());
requestTakeOfferSuccess.set(true);
}
else {
log.warn("trade.getDepositTx() = null. at trade state " + newValue +
" That should not happen and needs more investigation why it can happen.");
}
break;
case FIAT_PAYMENT_STARTED:
break;
case TAKE_OFFER_FEE_PUBLISH_FAILED:
requestTakeOfferErrorMessage.set("An error occurred when paying the trade fee." + errorMessage);
break;
case MESSAGE_SENDING_FAILED:
requestTakeOfferErrorMessage.set("An error occurred when sending a message to the offerer. Maybe there are connection problems. " +
"Please try later again." + errorMessage);
break;
case PAYOUT_PUBLISHED:
break;
case FAULT:
requestTakeOfferErrorMessage.set(errorMessage);
break;
default:
log.error("Unhandled trade state: " + newValue);
break;
}
});
} }
void calculateVolume() { void calculateVolume() {

View File

@ -136,7 +136,7 @@
</ProgressIndicator> </ProgressIndicator>
<Button fx:id="showPaymentInfoScreenButton" text="%takeOffer.amountPriceBox.next" id="show-details-button" <Button fx:id="showPaymentInfoScreenButton" text="%takeOffer.amountPriceBox.next" id="show-details-button"
GridPane.columnIndex="1" GridPane.rowIndex="3" defaultButton="true" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="3" defaultButton="true" visible="false"
onAction="#onShowPayFundsScreen"> onAction="#onShowPaymentScreen">
<GridPane.margin> <GridPane.margin>
<Insets top="15.0"/> <Insets top="15.0"/>
</GridPane.margin> </GridPane.margin>

View File

@ -18,6 +18,8 @@
package io.bitsquare.gui.main.trade.takeoffer; package io.bitsquare.gui.main.trade.takeoffer;
import io.bitsquare.common.viewfx.view.ActivatableViewAndModel;
import io.bitsquare.common.viewfx.view.FxmlView;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.OverlayManager; import io.bitsquare.gui.OverlayManager;
import io.bitsquare.gui.components.AddressTextField; import io.bitsquare.gui.components.AddressTextField;
@ -36,8 +38,6 @@ import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.locale.BSResources; import io.bitsquare.locale.BSResources;
import io.bitsquare.offer.Direction; import io.bitsquare.offer.Direction;
import io.bitsquare.offer.Offer; import io.bitsquare.offer.Offer;
import io.bitsquare.common.viewfx.view.ActivatableViewAndModel;
import io.bitsquare.common.viewfx.view.FxmlView;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -47,7 +47,6 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.HPos; import javafx.geometry.HPos;
@ -95,7 +94,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private final Navigation navigation; private final Navigation navigation;
private final OverlayManager overlayManager; private final OverlayManager overlayManager;
private ChangeListener<Offer.State> offerIsAvailableChangeListener;
private TradeView.CloseHandler closeHandler; private TradeView.CloseHandler closeHandler;
@Inject @Inject
@ -115,8 +113,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
@Override @Override
protected void doDeactivate() { protected void doDeactivate() {
if (offerIsAvailableChangeListener != null)
model.offerIsAvailable.removeListener(offerIsAvailableChangeListener);
} }
public void initWithData(Direction direction, Coin amount, Offer offer) { public void initWithData(Direction direction, Coin amount, Offer offer) {
@ -127,13 +123,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
else else
imageView.setId("image-sell-large"); imageView.setId("image-sell-large");
priceDescriptionLabel.setText(BSResources.get("takeOffer.amountPriceBox.priceDescription", priceDescriptionLabel.setText(BSResources.get("takeOffer.amountPriceBox.priceDescription", model.getFiatCode()));
model.getFiatCode())); volumeDescriptionLabel.setText(BSResources.get("takeOffer.amountPriceBox.volumeDescription", model.getFiatCode()));
volumeDescriptionLabel.setText(BSResources.get("takeOffer.amountPriceBox.volumeDescription",
model.getFiatCode()));
balanceTextField.setup(model.getWalletService(), model.address.get(), balanceTextField.setup(model.getWalletService(), model.address.get(), model.getFormatter());
model.getFormatter());
buyLabel.setText(model.getDirectionLabel()); buyLabel.setText(model.getDirectionLabel());
amountRangeTextField.setText(model.getAmountRange()); amountRangeTextField.setText(model.getAmountRange());
@ -148,113 +141,23 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
acceptedLanguagesTextField.setText(model.getAcceptedLanguages()); acceptedLanguagesTextField.setText(model.getAcceptedLanguages());
acceptedArbitratorsTextField.setText(model.getAcceptedArbitrators()); acceptedArbitratorsTextField.setText(model.getAcceptedArbitrators());
offerIsAvailableChangeListener = (ov, oldValue, newValue) -> handleOfferIsAvailableState(newValue); showCheckAvailabilityScreen();
model.offerIsAvailable.addListener(offerIsAvailableChangeListener);
handleOfferIsAvailableState(model.offerIsAvailable.get());
} }
public void setCloseHandler(TradeView.CloseHandler closeHandler) { public void setCloseHandler(TradeView.CloseHandler closeHandler) {
this.closeHandler = closeHandler; this.closeHandler = closeHandler;
} }
private void handleOfferIsAvailableState(Offer.State state) {
switch (state) {
case UNKNOWN:
break;
case AVAILABLE:
isOfferAvailableLabel.setVisible(false);
isOfferAvailableLabel.setManaged(false);
isOfferAvailableProgressIndicator.setProgress(0);
isOfferAvailableProgressIndicator.setVisible(false);
isOfferAvailableProgressIndicator.setManaged(false);
// In case of returning to a canceled or failed take offer request and if trade wallet is sufficient funded we display directly the payment
// screen
if (!model.isTakeOfferButtonDisabled.get()) {
showPayFundsScreen();
showPaymentInfoScreenButton.setVisible(false);
}
else {
showPaymentInfoScreenButton.setVisible(true);
}
break;
case OFFERER_OFFLINE:
Popups.openWarningPopup("You cannot take that offer", "The offerer is offline.");
Platform.runLater(this::close);
break;
case NOT_AVAILABLE:
Popups.openWarningPopup("You cannot take that offer", "The offer was already taken by another trader.");
Platform.runLater(this::close);
break;
case FAULT:
Popups.openWarningPopup("You cannot take that offer", "The check for the offer availability failed.");
Platform.runLater(this::close);
break;
case REMOVED:
Popups.openWarningPopup("You cannot take that offer", "The offerer has been removed in the meantime.");
Platform.runLater(this::close);
break;
}
}
@FXML @FXML
void onTakeOffer() { void onTakeOffer() {
model.takeOffer(); model.takeOffer();
} }
@FXML @FXML
void onShowPayFundsScreen() { void onShowPaymentScreen() {
showPayFundsScreen(); model.onShowPaymentScreen();
} }
private void showPayFundsScreen() {
// TODO deactivate for testing the moment
/* if (model.displaySecurityDepositInfo()) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
Popups.openInfoPopup("To ensure that both traders behave fair they need to pay a security deposit.",
"The deposit will stay in your local trading wallet until the offer gets accepted by another trader. " +
"\nIt will be refunded to you after the trade has successfully completed.",
actions);
model.securityDepositInfoDisplayed();
}*/
priceAmountPane.setInactive();
showPaymentInfoScreenButton.setVisible(false);
payFundsPane.setVisible(true);
totalToPayLabel.setVisible(true);
totalToPayInfoIconLabel.setVisible(true);
totalToPayTextField.setVisible(true);
addressLabel.setVisible(true);
addressTextField.setVisible(true);
balanceLabel.setVisible(true);
balanceTextField.setVisible(true);
fundsBoxInfoDisplay.setVisible(true);
// for irc demo
//showAdvancedSettingsButton.setVisible(true);
showAdvancedSettingsButton.setManaged(false);
if (expand == null) {
expand = ImageUtil.getImageViewById(ImageUtil.EXPAND);
collapse = ImageUtil.getImageViewById(ImageUtil.COLLAPSE);
}
showAdvancedSettingsButton.setGraphic(expand);
setupTotalToPayInfoIconLabel();
model.onShowPayFundsScreen();
}
@FXML @FXML
void onToggleShowAdvancedSettings() { void onToggleShowAdvancedSettings() {
@ -300,6 +203,23 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
amountTextField.setText(model.amount.get()); amountTextField.setText(model.amount.get());
}); });
model.state.addListener((ov, oldValue, newValue) -> {
switch (newValue) {
case CHECK_AVAILABILITY:
showCheckAvailabilityScreen();
break;
case AMOUNT_SCREEN:
showAmountScreen();
break;
case PAYMENT_SCREEN:
setupPaymentScreen();
break;
case DETAILS_SCREEN:
showDetailsScreen();
break;
}
});
// warnings // warnings
model.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> { model.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> {
if (newValue) { if (newValue) {
@ -309,12 +229,12 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
} }
}); });
model.requestTakeOfferErrorMessage.addListener((o, oldValue, newValue) -> {
model.errorMessage.addListener((o, oldValue, newValue) -> {
if (newValue != null) { if (newValue != null) {
Popups.openErrorPopup(BSResources.get("shared.error"), Popups.openErrorPopup(BSResources.get("shared.error"), BSResources.get("takeOffer.error.message", model.errorMessage.get()));
BSResources.get("takeOffer.amountPriceBox.error.message",
model.requestTakeOfferErrorMessage.get()));
Popups.removeBlurContent(); Popups.removeBlurContent();
Platform.runLater(this::close);
} }
}); });
@ -364,8 +284,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
amountTextField.validationResultProperty().bind(model.amountValidationResult); amountTextField.validationResultProperty().bind(model.amountValidationResult);
// buttons // buttons
takeOfferButton.visibleProperty().bind(model.isTakeOfferButtonVisible); takeOfferButton.disableProperty().bind(model.takeOfferButtonDisabled);
takeOfferButton.disableProperty().bind(model.isTakeOfferButtonDisabled);
takeOfferSpinnerInfoLabel.visibleProperty().bind(model.isTakeOfferSpinnerVisible); takeOfferSpinnerInfoLabel.visibleProperty().bind(model.isTakeOfferSpinnerVisible);
@ -375,6 +294,68 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
}); });
} }
private void showCheckAvailabilityScreen() {
}
private void showAmountScreen() {
isOfferAvailableLabel.setVisible(false);
isOfferAvailableLabel.setManaged(false);
isOfferAvailableProgressIndicator.setProgress(0);
isOfferAvailableProgressIndicator.setVisible(false);
isOfferAvailableProgressIndicator.setManaged(false);
showPaymentInfoScreenButton.setVisible(true);
}
private void setupPaymentScreen() {
// TODO deactivate for testing the moment
/* if (model.displaySecurityDepositInfo()) {
overlayManager.blurContent();
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "CLOSE");
Dialog.Actions.CLOSE.handle(actionEvent);
}
});
Popups.openInfoPopup("To ensure that both traders behave fair they need to pay a security deposit.",
"The deposit will stay in your local trading wallet until the offer gets accepted by another trader. " +
"\nIt will be refunded to you after the trade has successfully completed.",
actions);
model.securityDepositInfoDisplayed();
}*/
priceAmountPane.setInactive();
takeOfferButton.setVisible(true);
showPaymentInfoScreenButton.setVisible(false);
payFundsPane.setVisible(true);
totalToPayLabel.setVisible(true);
totalToPayInfoIconLabel.setVisible(true);
totalToPayTextField.setVisible(true);
addressLabel.setVisible(true);
addressTextField.setVisible(true);
balanceLabel.setVisible(true);
balanceTextField.setVisible(true);
fundsBoxInfoDisplay.setVisible(true);
// for irc demo
//showAdvancedSettingsButton.setVisible(true);
showAdvancedSettingsButton.setManaged(false);
if (expand == null) {
expand = ImageUtil.getImageViewById(ImageUtil.EXPAND);
collapse = ImageUtil.getImageViewById(ImageUtil.COLLAPSE);
}
showAdvancedSettingsButton.setGraphic(expand);
setupTotalToPayInfoIconLabel();
}
private void showDetailsScreen() { private void showDetailsScreen() {
payFundsPane.setInactive(); payFundsPane.setInactive();
@ -386,6 +367,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
toggleDetailsScreen(true); toggleDetailsScreen(true);
} }
private void hideDetailsScreen() { private void hideDetailsScreen() {
payFundsPane.setActive(); payFundsPane.setActive();
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);

View File

@ -18,14 +18,15 @@
package io.bitsquare.gui.main.trade.takeoffer; package io.bitsquare.gui.main.trade.takeoffer;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.common.viewfx.model.ActivatableWithDataModel;
import io.bitsquare.common.viewfx.model.ViewModel;
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.InputValidator; import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.locale.BSResources; import io.bitsquare.locale.BSResources;
import io.bitsquare.offer.Direction; import io.bitsquare.offer.Direction;
import io.bitsquare.offer.Offer; import io.bitsquare.offer.Offer;
import io.bitsquare.common.viewfx.model.ActivatableWithDataModel; import io.bitsquare.trade.Trade;
import io.bitsquare.common.viewfx.model.ViewModel;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -47,6 +48,13 @@ import static javafx.beans.binding.Bindings.createStringBinding;
class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel { class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel {
private static final Logger log = LoggerFactory.getLogger(TakeOfferViewModel.class); private static final Logger log = LoggerFactory.getLogger(TakeOfferViewModel.class);
public static enum State {
CHECK_AVAILABILITY,
AMOUNT_SCREEN,
PAYMENT_SCREEN,
DETAILS_SCREEN
}
private String fiatCode; private String fiatCode;
private String amountRange; private String amountRange;
private String price; private String price;
@ -62,6 +70,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
// Needed for the addressTextField // Needed for the addressTextField
final ObjectProperty<Address> address = new SimpleObjectProperty<>(); final ObjectProperty<Address> address = new SimpleObjectProperty<>();
final ObjectProperty<State> state = new SimpleObjectProperty<>(State.CHECK_AVAILABILITY);
private final BtcValidator btcValidator; private final BtcValidator btcValidator;
private final BSFormatter formatter; private final BSFormatter formatter;
@ -75,22 +84,21 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
final StringProperty securityDeposit = new SimpleStringProperty(); final StringProperty securityDeposit = new SimpleStringProperty();
final StringProperty totalToPay = new SimpleStringProperty(); final StringProperty totalToPay = new SimpleStringProperty();
final StringProperty transactionId = new SimpleStringProperty(); final StringProperty transactionId = new SimpleStringProperty();
final StringProperty requestTakeOfferErrorMessage = new SimpleStringProperty(); final StringProperty errorMessage = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty(); final StringProperty btcCode = new SimpleStringProperty();
final BooleanProperty isTakeOfferButtonVisible = new SimpleBooleanProperty(false); final BooleanProperty takeOfferButtonDisabled = new SimpleBooleanProperty(false);
final BooleanProperty isTakeOfferButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty isTakeOfferSpinnerVisible = new SimpleBooleanProperty(false); final BooleanProperty isTakeOfferSpinnerVisible = new SimpleBooleanProperty(false);
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty(); final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty(); final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
final ObjectProperty<Offer.State> offerIsAvailable = new SimpleObjectProperty<>(Offer.State.UNKNOWN);
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>(); final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
// Needed for the addressTextField // Needed for the addressTextField
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
private boolean takeOfferRequested;
private boolean isAmountAndPriceValidAndWalletFunded;
@Inject @Inject
public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, BSFormatter formatter) { public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, BSFormatter formatter) {
@ -104,6 +112,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
setupBindings(); setupBindings();
setupListeners(); setupListeners();
applyTakeOfferRequestResult(false);
} }
// setOfferBookFilter is a one time call // setOfferBookFilter is a one time call
@ -119,8 +128,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
BSResources.get("takeOffer.validation.amountSmallerThanMinAmount"))); BSResources.get("takeOffer.validation.amountSmallerThanMinAmount")));
} }
updateButtonDisableState();
//model.volumeAsFiat.set(offer.getVolumeByAmount(model.amountAsCoin.get())); //model.volumeAsFiat.set(offer.getVolumeByAmount(model.amountAsCoin.get()));
amountRange = formatter.formatCoinWithCode(offer.getMinAmount()) + " - " + amountRange = formatter.formatCoinWithCode(offer.getMinAmount()) + " - " +
@ -139,16 +146,120 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
bankAccountType = BSResources.get(offer.getFiatAccountType().toString()); bankAccountType = BSResources.get(offer.getFiatAccountType().toString());
bankAccountCurrency = BSResources.get(offer.getCurrency().getDisplayName()); bankAccountCurrency = BSResources.get(offer.getCurrency().getDisplayName());
bankAccountCounty = BSResources.get(offer.getBankAccountCountry().getName()); bankAccountCounty = BSResources.get(offer.getBankAccountCountry().getName());
offer.stateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("offer state = " + newValue);
switch (newValue) {
case UNKNOWN:
break;
case AVAILABLE:
state.set(State.AMOUNT_SCREEN);
break;
case OFFERER_OFFLINE:
if (takeOfferRequested)
errorMessage.set("Take offer request failed because offerer is not online anymore.");
else
errorMessage.set("You cannot take that offer because the offerer is offline.");
takeOfferRequested = false;
break;
case NOT_AVAILABLE:
if (takeOfferRequested)
errorMessage.set("Take offer request failed because offer is not available anymore. " +
"Maybe another trader has taken the offer in the meantime.");
else
errorMessage.set("You cannot take that offer because the offer was already taken by another trader.");
takeOfferRequested = false;
break;
case FAULT:
if (takeOfferRequested)
errorMessage.set("Take offer request failed.");
else
errorMessage.set("The check for the offer availability failed.");
takeOfferRequested = false;
break;
case REMOVED:
if (!takeOfferRequested)
errorMessage.set("You cannot take that offer because the offer has been removed in the meantime.");
takeOfferRequested = false;
break;
default:
log.error("Unhandled offer state: " + newValue);
break;
}
if (errorMessage.get() != null) {
isTakeOfferSpinnerVisible.set(false);
}
evaluateState();
});
evaluateState();
} }
void takeOffer() { void takeOffer() {
dataModel.requestTakeOfferErrorMessage.set(null); errorMessage.set(null);
dataModel.requestTakeOfferSuccess.set(false); takeOfferRequested = true;
applyTakeOfferRequestResult(false);
isTakeOfferButtonDisabled.set(true);
isTakeOfferSpinnerVisible.set(true); isTakeOfferSpinnerVisible.set(true);
dataModel.takeOffer(); Trade trade = dataModel.takeOffer();
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue);
String msg = "";
if (newValue.getErrorMessage() != null)
msg = "\nError message: " + newValue.getErrorMessage();
switch (newValue) {
case OPEN:
break;
case OFFERER_ACCEPTED:
break;
case OFFERER_REJECTED:
errorMessage.set("Your take offer request got rejected. Maybe another trader has taken the offer in the meantime.");
takeOfferRequested = false;
break;
case TAKE_OFFER_FEE_TX_CREATED:
break;
case DEPOSIT_PUBLISHED:
case DEPOSIT_CONFIRMED:
transactionId.set(trade.getDepositTx().getHashAsString());
applyTakeOfferRequestResult(true);
break;
case FIAT_PAYMENT_STARTED:
break;
case TAKE_OFFER_FEE_PUBLISH_FAILED:
errorMessage.set("An error occurred when paying the trade fee." + msg);
takeOfferRequested = false;
break;
case MESSAGE_SENDING_FAILED:
errorMessage.set("An error occurred when sending a message to the offerer. Maybe there are connection problems. " +
"Please try later again." + msg);
takeOfferRequested = false;
break;
case PAYOUT_PUBLISHED:
break;
case FAULT:
errorMessage.set(msg);
takeOfferRequested = false;
break;
default:
log.error("Unhandled trade state: " + newValue);
break;
}
if (errorMessage.get() != null) {
isAmountAndPriceValidAndWalletFunded = false;
isTakeOfferSpinnerVisible.set(false);
}
});
evaluateState();
} }
void securityDepositInfoDisplayed() { void securityDepositInfoDisplayed() {
@ -156,8 +267,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
} }
void onShowPayFundsScreen() { void onShowPaymentScreen() {
isTakeOfferButtonVisible.set(true); state.set(State.PAYMENT_SCREEN);
} }
// On focus out we do validation and apply the data to the model // On focus out we do validation and apply the data to the model
@ -268,26 +379,20 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
calculateVolume(); calculateVolume();
dataModel.calculateTotalToPay(); dataModel.calculateTotalToPay();
} }
updateButtonDisableState(); evaluateState();
}); });
dataModel.isWalletFunded.addListener((ov, oldValue, newValue) -> { dataModel.isWalletFunded.addListener((ov, oldValue, newValue) -> {
updateButtonDisableState(); evaluateState();
}); });
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding // Binding with Bindings.createObjectBinding does not work because of bi-directional binding
dataModel.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue))); dataModel.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue)));
dataModel.requestTakeOfferErrorMessage.addListener((ov, oldValue, newValue) -> {
if (newValue != null) {
isTakeOfferButtonDisabled.set(false);
isTakeOfferSpinnerVisible.set(false);
} }
});
dataModel.requestTakeOfferSuccess.addListener((ov, oldValue, newValue) -> { private void applyTakeOfferRequestResult(boolean success) {
isTakeOfferButtonVisible.set(!newValue);
isTakeOfferSpinnerVisible.set(false); isTakeOfferSpinnerVisible.set(false);
}); showTransactionPublishedScreen.set(success);
} }
private void setupBindings() { private void setupBindings() {
@ -301,11 +406,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
totalToPayAsCoin.bind(dataModel.totalToPayAsCoin); totalToPayAsCoin.bind(dataModel.totalToPayAsCoin);
requestTakeOfferErrorMessage.bind(dataModel.requestTakeOfferErrorMessage);
showTransactionPublishedScreen.bind(dataModel.requestTakeOfferSuccess);
transactionId.bind(dataModel.transactionId);
offerIsAvailable.bind(dataModel.offerIsAvailable);
btcCode.bind(dataModel.btcCode); btcCode.bind(dataModel.btcCode);
} }
@ -318,12 +418,16 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
dataModel.amountAsCoin.set(formatter.parseToCoinWith4Decimals(amount.get())); dataModel.amountAsCoin.set(formatter.parseToCoinWith4Decimals(amount.get()));
} }
private void updateButtonDisableState() { private void evaluateState() {
isTakeOfferButtonDisabled.set(!(isBtcInputValid(amount.get()).isValid && isAmountAndPriceValidAndWalletFunded = isBtcInputValid(amount.get()).isValid &&
dataModel.isMinAmountLessOrEqualAmount() && dataModel.isMinAmountLessOrEqualAmount() &&
!dataModel.isAmountLargerThanOfferAmount() && !dataModel.isAmountLargerThanOfferAmount() &&
dataModel.isWalletFunded.get()) dataModel.isWalletFunded.get();
);
if (isAmountAndPriceValidAndWalletFunded && state.get() != State.CHECK_AVAILABILITY)
state.set(State.PAYMENT_SCREEN);
takeOfferButtonDisabled.set(!isAmountAndPriceValidAndWalletFunded || takeOfferRequested);
} }
private InputValidator.ValidationResult isBtcInputValid(String input) { private InputValidator.ValidationResult isBtcInputValid(String input) {

View File

@ -103,6 +103,7 @@ public class Transitions {
if (removeBlurTimeLine != null) if (removeBlurTimeLine != null)
removeBlurTimeLine.stop(); removeBlurTimeLine.stop();
node.setMouseTransparent(true);
GaussianBlur blur = new GaussianBlur(0.0); GaussianBlur blur = new GaussianBlur(0.0);
Timeline timeline = new Timeline(); Timeline timeline = new Timeline();
KeyValue kv1 = new KeyValue(blur.radiusProperty(), 15.0); KeyValue kv1 = new KeyValue(blur.radiusProperty(), 15.0);
@ -134,6 +135,7 @@ public class Transitions {
public void removeBlur(Node node, int duration, boolean useDarken) { public void removeBlur(Node node, int duration, boolean useDarken) {
if (preferences.getUseEffects()) { if (preferences.getUseEffects()) {
if (node != null) { if (node != null) {
node.setMouseTransparent(false);
GaussianBlur blur = (GaussianBlur) node.getEffect(); GaussianBlur blur = (GaussianBlur) node.getEffect();
if (blur != null) { if (blur != null) {
removeBlurTimeLine = new Timeline(); removeBlurTimeLine = new Timeline();

View File

@ -55,7 +55,17 @@ public class Offer implements Serializable {
AVAILABLE, AVAILABLE,
NOT_AVAILABLE, NOT_AVAILABLE,
FAULT, FAULT,
REMOVED REMOVED;
private String errorMessage;
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
} }
// key attributes for lookup // key attributes for lookup

View File

@ -17,15 +17,14 @@
package io.bitsquare.trade.protocol.availability; package io.bitsquare.trade.protocol.availability;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.network.Message; import io.bitsquare.network.Message;
import io.bitsquare.network.Peer; import io.bitsquare.network.Peer;
import io.bitsquare.offer.Offer;
import io.bitsquare.trade.handlers.MessageHandler; import io.bitsquare.trade.handlers.MessageHandler;
import io.bitsquare.trade.protocol.availability.messages.ReportOfferAvailabilityMessage; import io.bitsquare.trade.protocol.availability.messages.ReportOfferAvailabilityMessage;
import io.bitsquare.trade.protocol.availability.tasks.GetPeerAddress; import io.bitsquare.trade.protocol.availability.tasks.GetPeerAddress;
import io.bitsquare.trade.protocol.availability.tasks.ProcessReportOfferAvailabilityMessage; import io.bitsquare.trade.protocol.availability.tasks.ProcessReportOfferAvailabilityMessage;
import io.bitsquare.trade.protocol.availability.tasks.RequestIsOfferAvailable; import io.bitsquare.trade.protocol.availability.tasks.RequestIsOfferAvailable;
import io.bitsquare.common.taskrunner.TaskRunner;
import javafx.application.Platform; import javafx.application.Platform;
@ -82,7 +81,6 @@ public class CheckOfferAvailabilityProtocol {
public void cancel() { public void cancel() {
isCanceled = true; isCanceled = true;
taskRunner.cancel(); taskRunner.cancel();
model.getOffer().setState(Offer.State.UNKNOWN);
} }

View File

@ -19,6 +19,7 @@ package io.bitsquare.trade.protocol.trade.taker;
import io.bitsquare.network.Message; import io.bitsquare.network.Message;
import io.bitsquare.network.Peer; import io.bitsquare.network.Peer;
import io.bitsquare.offer.Offer;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.handlers.MessageHandler; import io.bitsquare.trade.handlers.MessageHandler;
import io.bitsquare.trade.protocol.trade.TradeMessage; import io.bitsquare.trade.protocol.trade.TradeMessage;
@ -43,6 +44,11 @@ import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCommitDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCreatesAndSignsDepositTx; import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCreatesAndSignsDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.VerifyOfferFeePayment; import io.bitsquare.trade.protocol.trade.taker.tasks.VerifyOfferFeePayment;
import io.bitsquare.trade.protocol.trade.taker.tasks.VerifyOffererAccount; import io.bitsquare.trade.protocol.trade.taker.tasks.VerifyOffererAccount;
import io.bitsquare.util.Utilities;
import java.util.function.Function;
import javafx.animation.AnimationTimer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -51,9 +57,11 @@ import static io.bitsquare.util.Validator.nonEmptyStringOf;
public class SellerAsTakerProtocol { public class SellerAsTakerProtocol {
private static final Logger log = LoggerFactory.getLogger(SellerAsTakerProtocol.class); private static final Logger log = LoggerFactory.getLogger(SellerAsTakerProtocol.class);
public static final int TIMEOUT_DELAY = 10000;
private final SellerAsTakerModel model; private final SellerAsTakerModel model;
private final MessageHandler messageHandler; private final MessageHandler messageHandler;
private AnimationTimer timeoutTimer;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -78,7 +86,15 @@ public class SellerAsTakerProtocol {
SellerAsTakerTaskRunner<SellerAsTakerModel> taskRunner = new SellerAsTakerTaskRunner<>(model, SellerAsTakerTaskRunner<SellerAsTakerModel> taskRunner = new SellerAsTakerTaskRunner<>(model,
() -> { () -> {
log.debug("taskRunner at handleRequestTakeOfferUIEvent completed"); log.debug("taskRunner at takeOffer completed");
startTimeout(animationTimer -> {
Offer.State offerState = Offer.State.FAULT;
offerState.setErrorMessage("We did not get any reply for the take offer request. " +
"Seems that there are connection problems to your peer.");
model.getOffer().setState(Offer.State.FAULT);
return null;
});
}, },
(errorMessage) -> { (errorMessage) -> {
log.error(errorMessage); log.error(errorMessage);
@ -97,6 +113,8 @@ public class SellerAsTakerProtocol {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void handleRespondToTakeOfferRequestMessage(RespondToTakeOfferRequestMessage tradeMessage) { private void handleRespondToTakeOfferRequestMessage(RespondToTakeOfferRequestMessage tradeMessage) {
stopTimeout();
model.setTradeMessage(tradeMessage); model.setTradeMessage(tradeMessage);
SellerAsTakerTaskRunner<SellerAsTakerModel> taskRunner = new SellerAsTakerTaskRunner<>(model, SellerAsTakerTaskRunner<SellerAsTakerModel> taskRunner = new SellerAsTakerTaskRunner<>(model,
@ -223,4 +241,23 @@ public class SellerAsTakerProtocol {
} }
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Timeout
///////////////////////////////////////////////////////////////////////////////////////////
private void startTimeout(Function<AnimationTimer, Void> callback) {
stopTimeout();
timeoutTimer = Utilities.setTimeout(TIMEOUT_DELAY, callback);
timeoutTimer.start();
}
private void stopTimeout() {
if (timeoutTimer != null) {
timeoutTimer.stop();
timeoutTimer = null;
}
}
} }

View File

@ -91,7 +91,6 @@ takeOffer.amountPriceBox.amountRangeDescription=Possible amount range
takeOffer.amountPriceBox.info=Enter the amount of Bitcoin you want to sell. You can choose an amount between the minimum amount and the amount. takeOffer.amountPriceBox.info=Enter the amount of Bitcoin you want to sell. You can choose an amount between the minimum amount and the amount.
takeOffer.amountPriceBox.next=Next step takeOffer.amountPriceBox.next=Next step
takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces=The amount you have entered exceeds the number of allowed decimal places.\nThe amount has been adjusted to 4 decimal places. takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces=The amount you have entered exceeds the number of allowed decimal places.\nThe amount has been adjusted to 4 decimal places.
takeOffer.amountPriceBox.error.message=An error occurred when taking the offer:\n\n {0}
takeOffer.validation.amountSmallerThanMinAmount=Amount cannot be smaller than minimum amount defined in the offer. takeOffer.validation.amountSmallerThanMinAmount=Amount cannot be smaller than minimum amount defined in the offer.
takeOffer.validation.amountLargerThanOfferAmount=Input amount cannot be higher than the amount defined in the offer. takeOffer.validation.amountLargerThanOfferAmount=Input amount cannot be higher than the amount defined in the offer.