Improve trade state names

This commit is contained in:
Manfred Karrer 2016-03-03 12:57:57 +01:00
parent f5b996ef00
commit afd88bc55f
38 changed files with 235 additions and 177 deletions

View file

@ -18,6 +18,8 @@
package io.bitsquare.trade;
import io.bitsquare.app.Version;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer;
@ -48,9 +50,9 @@ public abstract class SellerTrade extends Trade {
state = State.PREPARATION;
}
public void onFiatPaymentReceived() {
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
checkArgument(tradeProtocol instanceof SellerProtocol, "tradeProtocol NOT instanceof SellerProtocol");
((SellerProtocol) tradeProtocol).onFiatPaymentReceived();
((SellerProtocol) tradeProtocol).onFiatPaymentReceived(resultHandler, errorMessageHandler);
}

View file

@ -67,24 +67,24 @@ public abstract class Trade implements Tradable, Model {
TAKER_FEE_PAID(Phase.TAKER_FEE_PAID),
DEPOSIT_PUBLISH_REQUESTED(Phase.DEPOSIT_REQUESTED),
DEPOSIT_PUBLISHED(Phase.DEPOSIT_PAID),
OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
TAKER_PUBLISHED_DEPOSIT_TX(Phase.DEPOSIT_PAID),
DEPOSIT_SEEN_IN_NETWORK(Phase.DEPOSIT_PAID), // triggered by balance update, used only in error cases
DEPOSIT_PUBLISHED_MSG_SENT(Phase.DEPOSIT_PAID),
DEPOSIT_PUBLISHED_MSG_RECEIVED(Phase.DEPOSIT_PAID),
DEPOSIT_CONFIRMED(Phase.DEPOSIT_PAID),
TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PAID),
OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG(Phase.DEPOSIT_PAID),
DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN(Phase.DEPOSIT_PAID),
FIAT_PAYMENT_STARTED(Phase.FIAT_SENT),
FIAT_PAYMENT_STARTED_MSG_SENT(Phase.FIAT_SENT),
FIAT_PAYMENT_STARTED_MSG_RECEIVED(Phase.FIAT_SENT),
BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED(Phase.FIAT_SENT),
BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT),
FIAT_PAYMENT_RECEIPT(Phase.FIAT_RECEIVED),
FIAT_PAYMENT_RECEIPT_MSG_SENT(Phase.FIAT_RECEIVED),
FIAT_PAYMENT_RECEIPT_MSG_RECEIVED(Phase.FIAT_RECEIVED),
SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT(Phase.FIAT_RECEIVED),
SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG(Phase.FIAT_RECEIVED),
BUYER_RECEIVED_FIAT_PAYMENT_RECEIPT_MSG(Phase.FIAT_RECEIVED),
PAYOUT_TX_COMMITTED(Phase.PAYOUT_PAID),
PAYOUT_TX_SENT(Phase.PAYOUT_PAID),
PAYOUT_TX_RECEIVED_AND_COMMITTED(Phase.PAYOUT_PAID),
BUYER_COMMITTED_PAYOUT_TX(Phase.PAYOUT_PAID), //TODO needed?
BUYER_STARTED_SEND_PAYOUT_TX(Phase.PAYOUT_PAID), // not from the success/arrived handler!
SELLER_RECEIVED_AND_COMMITTED_PAYOUT_TX(Phase.PAYOUT_PAID),
PAYOUT_BROAD_CASTED(Phase.PAYOUT_PAID),
WITHDRAW_COMPLETED(Phase.WITHDRAWN);
@ -569,7 +569,7 @@ public abstract class Trade implements Tradable, Model {
TransactionConfidence transactionConfidence = depositTx.getConfidence();
log.debug("transactionConfidence " + transactionConfidence.getDepthInBlocks());
if (transactionConfidence.getDepthInBlocks() > 0) {
handleConfidenceResult();
setConfirmedState();
} else {
ListenableFuture<TransactionConfidence> future = transactionConfidence.getDepthFuture(1);
Futures.addCallback(future, new FutureCallback<TransactionConfidence>() {
@ -577,7 +577,7 @@ public abstract class Trade implements Tradable, Model {
public void onSuccess(TransactionConfidence result) {
log.debug("transactionConfidence " + transactionConfidence.getDepthInBlocks());
log.debug("state " + state);
handleConfidenceResult();
setConfirmedState();
}
@Override
@ -596,9 +596,10 @@ public abstract class Trade implements Tradable, Model {
abstract protected void createProtocol();
private void handleConfidenceResult() {
if (state.ordinal() < State.DEPOSIT_CONFIRMED.ordinal())
setState(State.DEPOSIT_CONFIRMED);
private void setConfirmedState() {
// we oly apply the state if we are not already further in the process
if (state.ordinal() < State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.ordinal())
setState(State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN);
}
abstract protected void initStates();

View file

@ -144,7 +144,7 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
// User clicked the "bank transfer started" button
@Override
public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
buyerAsOffererTrade.setState(Trade.State.FIAT_PAYMENT_STARTED);
buyerAsOffererTrade.setState(Trade.State.BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED);
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsOffererTrade,
() -> {

View file

@ -133,7 +133,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
// User clicked the "bank transfer started" button
@Override
public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
buyerAsTakerTrade.setState(Trade.State.FIAT_PAYMENT_STARTED);
buyerAsTakerTrade.setState(Trade.State.BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED);
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
() -> {

View file

@ -18,6 +18,8 @@
package io.bitsquare.trade.protocol.trade;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.messaging.MailboxMessage;
@ -159,12 +161,18 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
// User clicked the "bank transfer received" button, so we release the funds for pay out
@Override
public void onFiatPaymentReceived() {
sellerAsOffererTrade.setState(Trade.State.FIAT_PAYMENT_RECEIPT);
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
sellerAsOffererTrade.setState(Trade.State.SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT);
TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsOffererTrade,
() -> handleTaskRunnerSuccess("onFiatPaymentReceived"),
this::handleTaskRunnerFault);
() -> {
resultHandler.handleResult();
handleTaskRunnerSuccess("onFiatPaymentReceived");
},
(errorMessage) -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(errorMessage);
});
taskRunner.addTasks(
VerifyTakeOfferFeePayment.class,

View file

@ -18,6 +18,8 @@
package io.bitsquare.trade.protocol.trade;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.messaging.MailboxMessage;
@ -157,13 +159,19 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
// User clicked the "bank transfer received" button, so we release the funds for pay out
@Override
public void onFiatPaymentReceived() {
sellerAsTakerTrade.setState(Trade.State.FIAT_PAYMENT_RECEIPT);
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
sellerAsTakerTrade.setState(Trade.State.SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT);
TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade,
() -> handleTaskRunnerSuccess("onFiatPaymentReceived"),
this::handleTaskRunnerFault);
() -> {
resultHandler.handleResult();
handleTaskRunnerSuccess("onFiatPaymentReceived");
},
(errorMessage) -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(errorMessage);
});
taskRunner.addTasks(
VerifyOfferFeePayment.class,
SignPayoutTx.class,

View file

@ -17,6 +17,9 @@
package io.bitsquare.trade.protocol.trade;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
public interface SellerProtocol {
void onFiatPaymentReceived();
void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
}

View file

@ -136,7 +136,7 @@ public abstract class TradeProtocol {
Trade.State tradeState = trade.getState();
log.debug("cleanupTradable tradeState=" + tradeState);
boolean isOffererTrade = trade instanceof OffererTrade;
if (isOffererTrade && (tradeState == Trade.State.DEPOSIT_PUBLISH_REQUESTED || tradeState == Trade.State.DEPOSIT_SEEN_IN_NETWORK))
if (isOffererTrade && (tradeState == Trade.State.OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST || tradeState == Trade.State.DEPOSIT_SEEN_IN_NETWORK))
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
boolean isTakerTrade = trade instanceof TakerTrade;

View file

@ -52,7 +52,7 @@ public class ProcessFinalizePayoutTxRequest extends TradeTask {
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.FIAT_PAYMENT_RECEIPT_MSG_RECEIVED);
trade.setState(Trade.State.BUYER_RECEIVED_FIAT_PAYMENT_RECEIPT_MSG);
complete();
} catch (Throwable t) {

View file

@ -48,14 +48,14 @@ public class SendFiatTransferStartedMessage extends TradeTask {
@Override
public void onArrived() {
log.info("Message arrived at peer.");
trade.setState(Trade.State.FIAT_PAYMENT_STARTED_MSG_SENT);
trade.setState(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG);
complete();
}
@Override
public void onStoredInMailbox() {
log.info("Message stored in mailbox.");
trade.setState(Trade.State.FIAT_PAYMENT_STARTED_MSG_SENT);
trade.setState(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG);
complete();
}

View file

@ -67,7 +67,7 @@ public class SendPayoutTxFinalizedMessage extends TradeTask {
);
// state must not be set in onArrived or onStoredInMailbox handlers as we would get that
// called delayed and would overwrite the broad cast state set by the next task
trade.setState(Trade.State.PAYOUT_TX_SENT);
trade.setState(Trade.State.BUYER_STARTED_SEND_PAYOUT_TX);
} else {
log.error("trade.getPayoutTx() = " + trade.getPayoutTx());
failed("PayoutTx is null");

View file

@ -57,7 +57,7 @@ public class SignAndFinalizePayoutTx extends TradeTask {
);
trade.setPayoutTx(transaction);
trade.setState(Trade.State.PAYOUT_TX_COMMITTED);
trade.setState(Trade.State.BUYER_COMMITTED_PAYOUT_TX);
complete();
} catch (Throwable t) {

View file

@ -64,7 +64,7 @@ public class SignAndPublishDepositTxAsBuyer extends TradeTask {
trade.setDepositTx(transaction);
//trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
trade.setState(Trade.State.DEPOSIT_PUBLISHED);
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
complete();
}

View file

@ -63,7 +63,7 @@ public class ProcessDepositTxPublishedMessage extends TradeTask {
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED);
trade.setState(Trade.State.OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
complete();
} catch (Throwable t) {

View file

@ -58,6 +58,7 @@ public class SendPublishDepositTxRequest extends TradeTask {
@Override
public void onArrived() {
log.trace("Message arrived at peer.");
trade.setState(Trade.State.OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST);
complete();
}
@ -68,9 +69,6 @@ public class SendPublishDepositTxRequest extends TradeTask {
}
}
);
//TODO should it be in success handler?
trade.setState(Trade.State.DEPOSIT_PUBLISH_REQUESTED);
} catch (Throwable t) {
failed(t);
}

View file

@ -62,7 +62,7 @@ public class SetupDepositBalanceListener extends TradeTask {
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
log.debug("tradeStateSubscription newValue " + newValue);
if (newValue == Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED
if (newValue == Trade.State.OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG
|| newValue == Trade.State.DEPOSIT_SEEN_IN_NETWORK) {
walletService.removeBalanceListener(balanceListener);
@ -91,7 +91,7 @@ public class SetupDepositBalanceListener extends TradeTask {
if (trade instanceof OffererTrade) {
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
if (tradeState == Trade.State.DEPOSIT_PUBLISH_REQUESTED) {
if (tradeState == Trade.State.OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST) {
trade.setState(Trade.State.DEPOSIT_SEEN_IN_NETWORK);
} else if (tradeState.getPhase() == Trade.Phase.PREPARATION) {
processModel.getTradeManager().removePreparedTrade(trade);

View file

@ -51,7 +51,7 @@ public class ProcessFiatTransferStartedMessage extends TradeTask {
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.FIAT_PAYMENT_STARTED_MSG_RECEIVED);
trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG);
complete();
} catch (Throwable t) {

View file

@ -52,7 +52,7 @@ public class ProcessPayoutTxFinalizedMessage extends TradeTask {
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.PAYOUT_TX_RECEIVED_AND_COMMITTED);
trade.setState(Trade.State.SELLER_RECEIVED_AND_COMMITTED_PAYOUT_TX);
complete();
} catch (Throwable t) {

View file

@ -53,14 +53,14 @@ public class SendFinalizePayoutTxRequest extends TradeTask {
@Override
public void onArrived() {
log.trace("Message arrived at peer.");
trade.setState(Trade.State.FIAT_PAYMENT_RECEIPT_MSG_SENT);
trade.setState(Trade.State.SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG);
complete();
}
@Override
public void onStoredInMailbox() {
log.trace("Message stored in mailbox.");
trade.setState(Trade.State.FIAT_PAYMENT_RECEIPT_MSG_SENT);
trade.setState(Trade.State.SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG);
complete();
}

View file

@ -62,7 +62,7 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask {
trade.setDepositTx(transaction);
//trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
trade.setState(Trade.State.DEPOSIT_PUBLISHED);
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
complete();
}

View file

@ -49,14 +49,14 @@ public class SendDepositTxPublishedMessage extends TradeTask {
@Override
public void onArrived() {
log.trace("Message arrived at peer.");
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_SENT);
trade.setState(Trade.State.TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
complete();
}
@Override
public void onStoredInMailbox() {
log.trace("Message stored in mailbox.");
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_SENT);
trade.setState(Trade.State.TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG);
complete();
}

View file

@ -99,7 +99,7 @@ public class BlockChainForm extends PaymentMethodForm {
addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(blockChainAccount.getPaymentMethod().getId()));
TextField field = addLabelTextField(gridPane, ++gridRow, "Cryptocurrency address:", blockChainAccount.getAddress()).second;
field.setMouseTransparent(false);
addLabelTextField(gridPane, ++gridRow, "Crypto currency:", blockChainAccount.getSingleTradeCurrency().getNameAndCode());
addLabelTextField(gridPane, ++gridRow, "Cryptocurrency:", blockChainAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
}
@ -112,7 +112,7 @@ public class BlockChainForm extends PaymentMethodForm {
@Override
protected void addTradeCurrencyComboBox() {
currencyComboBox = addLabelComboBox(gridPane, ++gridRow, "Crypto currency:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
currencyComboBox = addLabelComboBox(gridPane, ++gridRow, "Cryptocurrency:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
currencyComboBox.setPromptText("Select cryptocurrency");
currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedCryptoCurrencies()));
currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 20));

View file

@ -162,7 +162,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
}
});
} else {
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
// Simulate a bit of delay
UserThread.runAfter(() -> feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx()), 1);
}
}
};

View file

@ -187,15 +187,12 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
public void initWithData(Offer.Direction direction, TradeCurrency tradeCurrency) {
model.initWithData(direction, tradeCurrency);
ImageView iconView = new ImageView();
placeOfferButton.setGraphic(iconView);
if (direction == Offer.Direction.BUY) {
imageView.setId("image-buy-large");
placeOfferButton.setId("buy-button-big");
placeOfferButton.setText("Review place offer for buying bitcoin");
nextButton.setId("buy-button");
iconView.setId("image-buy-white");
} else {
imageView.setId("image-sell-large");
// only needed for sell
@ -204,7 +201,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
placeOfferButton.setId("sell-button-big");
placeOfferButton.setText("Review place offer for selling bitcoin");
nextButton.setId("sell-button");
iconView.setId("image-sell-white");
}
}
@ -250,6 +246,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
}
private void onShowFundsScreen() {
model.onShowFundsScreen();
amountTextField.setMouseTransparent(true);
minAmountTextField.setMouseTransparent(true);
priceTextField.setMouseTransparent(true);
@ -257,7 +254,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
currencyComboBox.setMouseTransparent(true);
paymentAccountsComboBox.setMouseTransparent(true);
spinner.setVisible(true);
spinner.setProgress(-1);
if (!BitsquareApp.DEV_MODE) {
@ -388,7 +384,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
// buttons
placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled);
cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
spinner.visibleProperty().bind(model.isSpinnerVisible);
spinnerInfoLabel.visibleProperty().bind(model.isSpinnerVisible);
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
// payment account
currencyComboBox.prefWidthProperty().bind(paymentAccountsComboBox.widthProperty());
@ -420,7 +419,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
volumeTextField.validationResultProperty().unbind();
placeOfferButton.disableProperty().unbind();
cancelButton2.disableProperty().unbind();
spinner.visibleProperty().unbind();
spinnerInfoLabel.visibleProperty().unbind();
spinnerInfoLabel.textProperty().unbind();
currencyComboBox.managedProperty().unbind();
currencyComboBoxLabel.visibleProperty().unbind();
currencyComboBoxLabel.managedProperty().unbind();
@ -476,10 +477,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
"Please try to restart you application and check your network connection to see if you can resolve the issue.")
.show(), 100, TimeUnit.MILLISECONDS);
};
isSpinnerVisibleListener = (ov, oldValue, newValue) -> {
spinner.setProgress(newValue ? -1 : 0);
spinner.setVisible(newValue);
};
isSpinnerVisibleListener = (ov, oldValue, newValue) -> spinner.setProgress(newValue ? -1 : 0);
feeFromFundingTxListener = (observable, oldValue, newValue) -> {
log.debug("feeFromFundingTxListener " + newValue);
@ -517,18 +515,25 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class);
} else if (newValue) {
// We need a bit of delay to avoid issues with fade out/fade in of 2 popups
UserThread.runAfter(() ->
UserThread.runAfter(() -> {
String key = "createOfferSuccessInfo";
if (preferences.showAgain(key)) {
new Popup().headLine(BSResources.get("createOffer.success.headline"))
.feedback(BSResources.get("createOffer.success.info"))
.dontShowAgainId(key, preferences)
.actionButtonText("Go to \"My open offers\"")
.onAction(() -> {
close();
UserThread.runAfter(() ->
navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class),
100, TimeUnit.MILLISECONDS);
close();
})
.onClose(this::close)
.show(),
.show();
} else {
close();
}
},
500, TimeUnit.MILLISECONDS);
}
};
@ -725,11 +730,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
spinner = placeOfferTuple.second;
spinner.setProgress(0);
spinner.setVisible(false);
spinner.setPrefSize(18, 18);
//spinner.setPrefSize(18, 18);
spinnerInfoLabel = placeOfferTuple.third;
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
spinnerInfoLabel.setVisible(false);
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
cancelButton2.setOnAction(e -> close());

View file

@ -70,7 +70,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty();
final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty(true);
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty();
final BooleanProperty showWarningAdjustedVolume = new SimpleBooleanProperty();
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
@ -363,6 +363,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.onCurrencySelected(tradeCurrency);
}
public void onShowFundsScreen() {
isSpinnerVisible.set(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handle focus

View file

@ -254,12 +254,12 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
private void onCreateOffer() {
if (!model.hasPaymentAccount()) {
openPopupForMissingAccountSetup("You have not setup a payment account",
"You need to setup a national currency or crypto currency account before you can create an offer.\n" +
"You need to setup a national currency or cryptocurrency account before you can create an offer.\n" +
"Do you want to setup an account?", FiatAccountsView.class, "\"Account\"");
} else if (!model.hasPaymentAccountForCurrency()) {
openPopupForMissingAccountSetup("You don't have a payment account for the currency:\n" +
model.getSelectedTradeCurrency().getCodeAndName(),
"You need to setup a payment account for the selected currency to be able to trade in that currency.\n" +
openPopupForMissingAccountSetup("No matching payment account",
"You don't have a payment account for the currency required for that offer.\n" +
"You need to setup a payment account for that currency to be able to take this offer.\n" +
"Do you want to do this now?", FiatAccountsView.class, "\"Account\"");
} else if (!model.hasAcceptedArbitrators()) {
openPopupForMissingAccountSetup("You don't have an arbitrator selected.",
@ -277,8 +277,9 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
"You need to setup at least one arbitrator to be able to trade.\n" +
"Do you want to do this now?", ArbitratorSelectionView.class, "\"Arbitrator selection\"");
} else if (!isPaymentAccountValidForOffer) {
openPopupForMissingAccountSetup("You don't have a payment account with the payment method required for that offer.",
"You need to setup a payment account with that payment method if you want to take that offer.\n" +
openPopupForMissingAccountSetup("No matching payment account",
"You don't have a payment account with the payment method required for that offer.\n" +
"You need to setup a payment account with that payment method if you want to take this offer.\n" +
"Do you want to do this now?", FiatAccountsView.class, "\"Account\"");
} else if (!hasSameProtocolVersion) {
new Popup().warning("That offer requires a different protocol version as the one used in your " +

View file

@ -198,7 +198,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
}
});
} else {
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
// Simulate a bit of delay
UserThread.runAfter(() -> feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx()), 1);
}
}
};

View file

@ -205,17 +205,25 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class);
} else if (newValue && model.getTrade() != null && model.getTrade().errorMessageProperty().get() == null) {
UserThread.runAfter(
() -> new Popup().headLine(BSResources.get("takeOffer.success.headline"))
.feedback(BSResources.get("takeOffer.success.info"))
.actionButtonText("Go to \"Open trades\"")
.onAction(() -> {
close();
UserThread.runAfter(
() -> navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class)
, 100, TimeUnit.MILLISECONDS);
})
.onClose(this::close)
.show(), 500, TimeUnit.MILLISECONDS);
() -> {
String key = "takeOfferSuccessInfo";
if (preferences.showAgain(key)) {
new Popup().headLine(BSResources.get("takeOffer.success.headline"))
.feedback(BSResources.get("takeOffer.success.info"))
.actionButtonText("Go to \"Open trades\"")
.dontShowAgainId(key, preferences)
.onAction(() -> {
UserThread.runAfter(
() -> navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class)
, 100, TimeUnit.MILLISECONDS);
close();
})
.onClose(this::close)
.show();
} else {
close();
}
}, 500, TimeUnit.MILLISECONDS);
}
});
@ -294,8 +302,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
public void initWithData(Offer offer) {
model.initWithData(offer);
ImageView iconView = new ImageView();
takeOfferButton.setGraphic(iconView);
if (model.getOffer().getDirection() == Offer.Direction.SELL) {
imageView.setId("image-buy-large");
directionLabel.setId("direction-icon-label-buy");
@ -303,7 +309,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
takeOfferButton.setId("buy-button-big");
takeOfferButton.setText("Review take offer for buying bitcoin");
nextButton.setId("buy-button");
iconView.setId("image-buy-white");
} else {
imageView.setId("image-sell-large");
directionLabel.setId("direction-icon-label-sell");
@ -311,7 +316,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
takeOfferButton.setId("sell-button-big");
nextButton.setId("sell-button");
takeOfferButton.setText("Review take offer for selling bitcoin");
iconView.setId("image-sell-white");
}
balanceTextField.setup(model.address.get(), model.getFormatter());

View file

@ -327,10 +327,10 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
private void applyTradeState(Trade.State tradeState) {
log.debug("applyTradeState state = " + tradeState);
if (trade.getState() == Trade.State.DEPOSIT_PUBLISHED
if (trade.getState() == Trade.State.TAKER_PUBLISHED_DEPOSIT_TX
|| trade.getState() == Trade.State.DEPOSIT_SEEN_IN_NETWORK
|| trade.getState() == Trade.State.DEPOSIT_PUBLISHED_MSG_SENT
|| trade.getState() == Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED) {
|| trade.getState() == Trade.State.TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG
|| trade.getState() == Trade.State.OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) {
if (trade.getDepositTx() != null) {
if (takeOfferSucceededHandler != null)
takeOfferSucceededHandler.run();

View file

@ -307,10 +307,9 @@ public abstract class Overlay<T extends Overlay> {
scene.getStylesheets().setAll(rootScene.getStylesheets());
scene.setFill(Color.TRANSPARENT);
stage.setScene(scene);
Window window = rootScene.getWindow();
setModality();
stage.initStyle(StageStyle.TRANSPARENT);
Window window = rootScene.getWindow();
stage.initOwner(window);
stage.show();
layout();
@ -342,6 +341,7 @@ public abstract class Overlay<T extends Overlay> {
}
protected void setModality() {
stage.initOwner(owner.getScene().getWindow());
stage.initModality(Modality.WINDOW_MODAL);
}

View file

@ -32,7 +32,7 @@ public class Notification extends Overlay<Notification> {
public void onReadyForDisplay() {
super.display();
if (autoClose && autoCloseTimer == null)
autoCloseTimer = UserThread.runAfter(this::hide, 5);
autoCloseTimer = UserThread.runAfter(this::hide, 4);
}
@Override

View file

@ -147,10 +147,10 @@ public class NotificationCenter {
String message = null;
if (tradeManager.isBuyer(trade.getOffer())) {
switch (tradeState) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
case OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG:
message = "Your offer has been accepted by a seller.";
break;
case DEPOSIT_CONFIRMED:
case DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN:
message = "Your trade has at least one blockchain confirmation.\n" +
"You can start the payment now.";
@ -164,10 +164,10 @@ public class NotificationCenter {
}
} else {
switch (tradeState) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
case OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG:
message = "Your offer has been accepted by a buyer.";
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
case SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG:
message = "The bitcoin buyer has started the payment.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_SENT:

View file

@ -242,12 +242,15 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
Tuple3<Button, ProgressIndicator, Label> placeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++rowIndex, isPlaceOffer ? placeOfferButtonText : takeOfferButtonText);
Button button = placeOfferTuple.first;
button.setMinHeight(40);
button.setPadding(new Insets(0, 20, 0, 20));
button.setGraphic(iconView);
button.setId(isBuyerRole ? "buy-button" : "sell-button");
button.setGraphicTextGap(10);
button.setId(isBuyerRole ? "buy-button-big" : "sell-button-big");
button.setText(isPlaceOffer ? placeOfferButtonText : takeOfferButtonText);
spinner = placeOfferTuple.second;
spinner.setPrefSize(18, 18);
//spinner.setPrefSize(18, 18);
spinner.setVisible(false);
Label spinnerInfoLabel = placeOfferTuple.third;

View file

@ -140,11 +140,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
((BuyerTrade) getTrade()).onFiatPaymentStarted(resultHandler, errorMessageHandler);
}
public void onFiatPaymentReceived() {
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
checkNotNull(getTrade(), "trade must not be null");
checkArgument(getTrade() instanceof SellerTrade, "Check failed: trade instanceof SellerTrade");
if (getTrade().getDisputeState() == Trade.DisputeState.NONE)
((SellerTrade) getTrade()).onFiatPaymentReceived();
((SellerTrade) getTrade()).onFiatPaymentReceived(resultHandler, errorMessageHandler);
}
public void onWithdrawRequest(String toAddress, ResultHandler resultHandler, FaultHandler faultHandler) {

View file

@ -263,6 +263,9 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
private void onTradeStateChanged(Trade.State tradeState) {
Log.traceCall(tradeState.toString());
// TODO what is first valid state for trade?
switch (tradeState) {
case PREPARATION:
sellerState.set(UNDEFINED);
@ -270,44 +273,37 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
break;
case TAKER_FEE_PAID:
break;
case DEPOSIT_PUBLISH_REQUESTED:
break;
case DEPOSIT_PUBLISHED:
case OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST:
case TAKER_PUBLISHED_DEPOSIT_TX:
case DEPOSIT_SEEN_IN_NETWORK:
case DEPOSIT_PUBLISHED_MSG_SENT:
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
case TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG:
case OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG:
sellerState.set(WAIT_FOR_BLOCKCHAIN_CONFIRMATION);
buyerState.set(PendingTradesViewModel.BuyerState.WAIT_FOR_BLOCKCHAIN_CONFIRMATION);
break;
case DEPOSIT_CONFIRMED:
buyerState.set(PendingTradesViewModel.BuyerState.REQUEST_START_FIAT_PAYMENT);
case DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN:
sellerState.set(WAIT_FOR_FIAT_PAYMENT_STARTED);
buyerState.set(PendingTradesViewModel.BuyerState.REQUEST_START_FIAT_PAYMENT);
case BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED: // we stick with the state until we get the msg sent success
buyerState.set(PendingTradesViewModel.BuyerState.REQUEST_START_FIAT_PAYMENT);
break;
case FIAT_PAYMENT_STARTED:
case FIAT_PAYMENT_STARTED_MSG_SENT:
case BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG:
buyerState.set(PendingTradesViewModel.BuyerState.WAIT_FOR_FIAT_PAYMENT_RECEIPT);
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
case FIAT_PAYMENT_RECEIPT: // In case the msg sending failed we stick in that view state
case SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG: // seller
case SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT: // we stick with the state until we get the msg sent success
sellerState.set(REQUEST_CONFIRM_FIAT_PAYMENT_RECEIVED);
break;
case FIAT_PAYMENT_RECEIPT_MSG_SENT:
case SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG:
sellerState.set(WAIT_FOR_PAYOUT_TX);
break;
case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
buyerState.set(PendingTradesViewModel.BuyerState.WAIT_FOR_FIAT_PAYMENT_RECEIPT);
break;
case PAYOUT_TX_COMMITTED:
case PAYOUT_TX_SENT:
case BUYER_RECEIVED_FIAT_PAYMENT_RECEIPT_MSG:
case BUYER_COMMITTED_PAYOUT_TX:
case BUYER_STARTED_SEND_PAYOUT_TX:
buyerState.set(PendingTradesViewModel.BuyerState.WAIT_FOR_BROADCAST_AFTER_UNLOCK);
break;
case PAYOUT_TX_RECEIVED_AND_COMMITTED:
case SELLER_RECEIVED_AND_COMMITTED_PAYOUT_TX:
sellerState.set(SellerState.WAIT_FOR_BROADCAST_AFTER_UNLOCK);
break;
case PAYOUT_BROAD_CASTED:

View file

@ -44,7 +44,7 @@ import static io.bitsquare.gui.util.FormBuilder.*;
public class BuyerStep2View extends TradeStepView {
private Button paymentStartedButton;
private Button confirmButton;
private Label statusLabel;
private ProgressIndicator statusProgressIndicator;
private Subscription tradeStatePropertySubscription;
@ -65,7 +65,7 @@ public class BuyerStep2View extends TradeStepView {
//TODO we get called twice, check why
if (tradeStatePropertySubscription == null) {
tradeStatePropertySubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
if (state.equals(Trade.State.DEPOSIT_CONFIRMED)) {
if (state == Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) {
PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData();
String key = "StartPaymentPopup_" + trade.getId();
if (attentionRequiredPopup == null && !BitsquareApp.DEV_MODE) {
@ -96,6 +96,11 @@ public class BuyerStep2View extends TradeStepView {
attentionRequiredPopup.show();
}
} else if (state == Trade.State.BUYER_CONFIRMED_FIAT_PAYMENT_INITIATED) {
showStatusInfo();
statusLabel.setText("Sending confirmation...");
} else if (state == Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG) {
hideStatusInfo();
}
});
}
@ -105,7 +110,7 @@ public class BuyerStep2View extends TradeStepView {
public void deactivate() {
super.deactivate();
removeStatusProgressIndicator();
hideStatusInfo();
if (tradeStatePropertySubscription != null) {
tradeStatePropertySubscription.unsubscribe();
tradeStatePropertySubscription = null;
@ -169,10 +174,12 @@ public class BuyerStep2View extends TradeStepView {
GridPane.setRowSpan(accountTitledGroupBg, gridRow - 3);
Tuple3<Button, ProgressIndicator, Label> tuple3 = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "Payment started");
paymentStartedButton = tuple3.first;
paymentStartedButton.setOnAction(e -> onPaymentStarted());
confirmButton = tuple3.first;
confirmButton.setOnAction(e -> onPaymentStarted());
statusProgressIndicator = tuple3.second;
statusLabel = tuple3.third;
hideStatusInfo();
}
@ -203,7 +210,7 @@ public class BuyerStep2View extends TradeStepView {
@Override
protected void applyOnDisputeOpened() {
paymentStartedButton.setDisable(true);
confirmButton.setDisable(true);
}
@ -235,41 +242,31 @@ public class BuyerStep2View extends TradeStepView {
}
private void confirmPaymentStarted() {
paymentStartedButton.setDisable(true);
paymentStartedButton.setMinWidth(130);
statusProgressIndicator.setVisible(true);
statusProgressIndicator.setManaged(true);
statusProgressIndicator.setProgress(-1);
statusLabel.setWrapText(true);
statusLabel.setPrefWidth(160);
statusLabel.setText("Sending message to your trading partner.\n" +
"Please wait until you get the confirmation that the message has arrived.");
confirmButton.setDisable(true);
model.dataModel.onPaymentStarted(() -> {
// We would not really need an update as the success triggers a screen change
removeStatusProgressIndicator();
statusLabel.setText("");
// In case the first send failed we got the support button displayed.
// If it succeeds at a second try we remove the support button again.
//TODO check for support. in case of a dispute we dont want to hid ethe button
/*if (notificationGroup != null) {
notificationGroup.setButtonVisible(false);
}*/
//TODO check for support. in case of a dispute we dont want to hide the button
//if (notificationGroup != null)
// notificationGroup.setButtonVisible(false);
}, errorMessage -> {
removeStatusProgressIndicator();
statusLabel.setText("Sending message to your trading partner failed.\n" +
"Please try again and if it continue to fail report a bug.");
paymentStartedButton.setDisable(false);
confirmButton.setDisable(false);
hideStatusInfo();
new Popup().warning("Sending message to your trading partner failed.\n" +
"Please try again and if it continue to fail report a bug.").show();
});
}
private void removeStatusProgressIndicator() {
statusProgressIndicator.setVisible(false);
statusProgressIndicator.setProgress(0);
statusProgressIndicator.setManaged(false);
private void showStatusInfo() {
statusProgressIndicator.setVisible(true);
statusProgressIndicator.setManaged(true);
statusProgressIndicator.setProgress(-1);
}
private void hideStatusInfo() {
statusProgressIndicator.setVisible(false);
statusProgressIndicator.setManaged(false);
statusProgressIndicator.setProgress(0);
statusLabel.setText("");
}
}

View file

@ -45,7 +45,7 @@ import static io.bitsquare.gui.util.FormBuilder.*;
public class SellerStep3View extends TradeStepView {
private Button confirmFiatReceivedButton;
private Button confirmButton;
private Label statusLabel;
private ProgressIndicator statusProgressIndicator;
private Subscription tradeStatePropertySubscription;
@ -65,7 +65,7 @@ public class SellerStep3View extends TradeStepView {
super.activate();
tradeStatePropertySubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
if (state.equals(Trade.State.FIAT_PAYMENT_STARTED_MSG_RECEIVED)) {
if (state == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) {
PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData();
String key = "ConfirmPaymentPopup_" + trade.getId();
if (attentionRequiredPopup == null && !BitsquareApp.DEV_MODE) {
@ -95,6 +95,11 @@ public class SellerStep3View extends TradeStepView {
attentionRequiredPopup.show();
}
} else if (state == Trade.State.SELLER_CONFIRMED_FIAT_PAYMENT_RECEIPT) {
showStatusInfo();
statusLabel.setText("Sending confirmation...");
} else if (state == Trade.State.SELLER_SENT_FIAT_PAYMENT_RECEIPT_MSG) {
hideStatusInfo();
}
});
}
@ -103,9 +108,12 @@ public class SellerStep3View extends TradeStepView {
public void deactivate() {
super.deactivate();
if (tradeStatePropertySubscription != null)
if (tradeStatePropertySubscription != null) {
tradeStatePropertySubscription.unsubscribe();
statusProgressIndicator.setProgress(0);
tradeStatePropertySubscription = null;
}
hideStatusInfo();
}
@ -151,10 +159,12 @@ public class SellerStep3View extends TradeStepView {
}
Tuple3<Button, ProgressIndicator, Label> tuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "Confirm payment receipt");
confirmFiatReceivedButton = tuple.first;
confirmFiatReceivedButton.setOnAction(e -> onPaymentReceived());
confirmButton = tuple.first;
confirmButton.setOnAction(e -> onPaymentReceived());
statusProgressIndicator = tuple.second;
statusLabel = tuple.third;
hideStatusInfo();
}
@ -208,7 +218,7 @@ public class SellerStep3View extends TradeStepView {
@Override
protected void applyOnDisputeOpened() {
confirmFiatReceivedButton.setDisable(true);
confirmButton.setDisable(true);
}
@ -243,13 +253,33 @@ public class SellerStep3View extends TradeStepView {
}
private void confirmPaymentReceived() {
confirmFiatReceivedButton.setDisable(true);
confirmButton.setDisable(true);
model.dataModel.onFiatPaymentReceived(() -> {
// In case the first send failed we got the support button displayed.
// If it succeeds at a second try we remove the support button again.
//TODO check for support. in case of a dispute we dont want to hide the button
//if (notificationGroup != null)
// notificationGroup.setButtonVisible(false);
}, errorMessage -> {
confirmButton.setDisable(false);
hideStatusInfo();
new Popup().warning("Sending message to your trading partner failed.\n" +
"Please try again and if it continue to fail report a bug.").show();
});
}
private void showStatusInfo() {
statusProgressIndicator.setVisible(true);
statusProgressIndicator.setManaged(true);
statusProgressIndicator.setProgress(-1);
statusLabel.setText("Sending message to trading partner...");
}
model.dataModel.onFiatPaymentReceived();
private void hideStatusInfo() {
statusProgressIndicator.setVisible(false);
statusProgressIndicator.setManaged(false);
statusProgressIndicator.setProgress(0);
statusLabel.setText("");
}
}

View file

@ -260,7 +260,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
Tuple2<Label, ComboBox> labelComboBoxTuple2 = addLabelComboBox(root, gridRow);
cryptoCurrenciesComboBox = labelComboBoxTuple2.second;
GridPane.setColumnIndex(cryptoCurrenciesComboBox, 3);
cryptoCurrenciesComboBox.setPromptText("Add crypto currency");
cryptoCurrenciesComboBox.setPromptText("Add cryptocurrency");
cryptoCurrenciesComboBox.setConverter(new StringConverter<CryptoCurrency>() {
@Override
public String toString(CryptoCurrency tradeCurrency) {