diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index 1b70dcd01c..eec03200fa 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -70,9 +70,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; @@ -135,6 +133,8 @@ public class MainViewModel implements ViewModel { private java.util.Timer numberofBtcPeersTimer; private java.util.Timer numberofP2PNetworkPeersTimer; private Timer startupTimeout; + private Set tradeStateSubscriptions = new HashSet<>(); + private Set disputeStateSubscriptions = new HashSet<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -375,13 +375,13 @@ public class MainViewModel implements ViewModel { tradeManager.getTrades().addListener((ListChangeListener) change -> { change.next(); - addDisputeStateListeners(change.getAddedSubList()); - addTradeStateListeners(change.getAddedSubList()); + setDisputeStateSubscriptions(); + setTradeStateSubscriptions(); pendingTradesChanged(); }); pendingTradesChanged(); - addDisputeStateListeners(tradeManager.getTrades()); - addTradeStateListeners(tradeManager.getTrades()); + setDisputeStateSubscriptions(); + setTradeStateSubscriptions(); // arbitratorManager @@ -533,7 +533,7 @@ public class MainViewModel implements ViewModel { } private void updateP2pNetworkInfoWithPeersChanged(int numPeers) { - p2PNetworkInfo.set("Nr. of connections: " + numPeers); + p2PNetworkInfo.set("Nr. of P2P network peers: " + numPeers); } private void displayAlertIfPresent(Alert alert) { @@ -652,8 +652,8 @@ public class MainViewModel implements ViewModel { private void setWalletServiceException(Throwable error) { setBitcoinNetworkSyncProgress(0); - btcSplashInfo.set("Nr. of peers: " + numBTCPeers + " / connecting to " + btcNetworkAsString + " failed"); - btcFooterInfo.set("Nr. of eers: " + numBTCPeers + " / connecting to " + btcNetworkAsString + " failed"); + btcSplashInfo.set("Nr. of Bitcoin network peers: " + numBTCPeers + " / connecting to " + btcNetworkAsString + " failed"); + btcFooterInfo.set(btcSplashInfo.get()); if (error instanceof TimeoutException) { walletServiceErrorMsg.set("Connecting to the bitcoin network failed because of a timeout."); } else if (error.getCause() instanceof BlockStoreException) { @@ -684,7 +684,7 @@ public class MainViewModel implements ViewModel { break; case HALF_REACHED: id = "displayHalfTradePeriodOver" + trade.getId(); - if (preferences.showAgain(id)) { + if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) { preferences.dontShowAgain(id); new Popup().warning("Your trade with ID " + trade.getShortId() + " has reached the half of the max. allowed trading period and " + @@ -696,7 +696,7 @@ public class MainViewModel implements ViewModel { break; case TRADE_PERIOD_OVER: id = "displayTradePeriodOver" + trade.getId(); - if (preferences.showAgain(id)) { + if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) { preferences.dontShowAgain(id); new Popup().warning("Your trade with ID " + trade.getShortId() + " has reached the max. allowed trading period and is " + @@ -738,71 +738,21 @@ public class MainViewModel implements ViewModel { showPendingTradesNotification.set(numPendingTrades > 0); } - private void addTradeStateListeners(List addedTrades) { - addedTrades.stream().forEach(trade -> { + private void setTradeStateSubscriptions() { + tradeStateSubscriptions.stream().forEach(Subscription::unsubscribe); + tradeStateSubscriptions.clear(); + + tradeManager.getTrades().stream().forEach(trade -> { Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> { if (newValue != null) { - applyState(trade); + applyTradeState(trade); } }); + tradeStateSubscriptions.add(tradeStateSubscription); }); - - - - /* addedTrades.stream() - .forEach(trade -> trade.stateProperty().addListener((observable, oldValue, newValue) -> { - String msg = ""; - log.debug("addTradeStateListeners " + newValue); - switch (newValue) { - case PREPARATION: - case TAKER_FEE_PAID: - case DEPOSIT_PUBLISH_REQUESTED: - case DEPOSIT_PUBLISHED: - case DEPOSIT_SEEN_IN_NETWORK: - case DEPOSIT_PUBLISHED_MSG_SENT: - case DEPOSIT_PUBLISHED_MSG_RECEIVED: - break; - case DEPOSIT_CONFIRMED: - msg = newValue.name(); - break; - case FIAT_PAYMENT_STARTED: - break; - case FIAT_PAYMENT_STARTED_MSG_SENT: - break; - case FIAT_PAYMENT_STARTED_MSG_RECEIVED: - break; - - case FIAT_PAYMENT_RECEIPT: - break; - case FIAT_PAYMENT_RECEIPT_MSG_SENT: - break; - case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED: - break; - - - case PAYOUT_TX_SENT: - break; - case PAYOUT_TX_RECEIVED: - break; - case PAYOUT_TX_COMMITTED: - break; - case PAYOUT_BROAD_CASTED: - break; - - case WITHDRAW_COMPLETED: - break; - - default: - log.warn("unhandled processState " + newValue); - break; - } - - //new Popup().information(msg).show(); - - }));*/ } - private void applyState(Trade trade) { + private void applyTradeState(Trade trade) { Trade.State state = trade.getState(); log.debug("addTradeStateListeners " + state); boolean isBtcBuyer = tradeManager.isMyOfferInBtcBuyerRole(trade.getOffer()); @@ -818,6 +768,7 @@ public class MainViewModel implements ViewModel { case DEPOSIT_CONFIRMED: message = "The deposit transaction of your trade has got the first blockchain confirmation.\n" + "You have to start the payment to the bitcoin seller now."; + break; /* case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED: case PAYOUT_TX_COMMITTED: @@ -853,13 +804,13 @@ public class MainViewModel implements ViewModel { if (message != null) { //TODO we get that called initially before the navigation is inited if (isPendingTradesViewCurrentView || currentPath == null) { - if (preferences.showAgain(id)) + if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) new Popup().headLine(headLine) .message(message) .show(); preferences.dontShowAgain(id); } else { - if (preferences.showAgain(id)) + if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) new Popup().headLine(headLine) .message(message) .actionButtonText("Go to \"Portfolio/Open trades\"") @@ -874,39 +825,51 @@ public class MainViewModel implements ViewModel { } } - private void addDisputeStateListeners(List addedTrades) { - addedTrades.stream().forEach(trade -> trade.disputeStateProperty().addListener((observable, oldValue, newValue) -> { - switch (newValue) { - case NONE: - break; - case DISPUTE_REQUESTED: - break; - case DISPUTE_STARTED_BY_PEER: - disputeManager.findOwnDispute(trade.getId()).ifPresent(dispute -> { - String msg; - if (dispute.isSupportTicket()) - msg = "Your trading peer has encountered technical problems and requested support for trade with ID " + trade.getShortId() + ".\n" + - "Please await further instructions from the arbitrator.\n" + - "Your funds are safe and will be refunded as soon the problem is resolved."; - else - msg = "Your trading peer has requested a dispute for trade with ID " + trade.getShortId() + "."; + private void setDisputeStateSubscriptions() { + disputeStateSubscriptions.stream().forEach(Subscription::unsubscribe); + disputeStateSubscriptions.clear(); - new Popup().information(msg).show(); - }); - break; - case DISPUTE_CLOSED: - new Popup().information("A support ticket for trade with ID " + trade.getShortId() + " has been closed.").show(); - break; - } - })); + tradeManager.getTrades().stream().forEach(trade -> { + Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> { + if (disputeState != null) { + applyDisputeState(trade, disputeState); + } + }); + disputeStateSubscriptions.add(disputeStateSubscription); + }); + } + + private void applyDisputeState(Trade trade, Trade.DisputeState disputeState) { + switch (disputeState) { + case NONE: + break; + case DISPUTE_REQUESTED: + break; + case DISPUTE_STARTED_BY_PEER: + disputeManager.findOwnDispute(trade.getId()).ifPresent(dispute -> { + String msg; + if (dispute.isSupportTicket()) + msg = "Your trading peer has encountered technical problems and requested support for trade with ID " + trade.getShortId() + ".\n" + + "Please await further instructions from the arbitrator.\n" + + "Your funds are safe and will be refunded as soon the problem is resolved."; + else + msg = "Your trading peer has requested a dispute for trade with ID " + trade.getShortId() + "."; + + new Popup().information(msg).show(); + }); + break; + case DISPUTE_CLOSED: + new Popup().information("A support ticket for trade with ID " + trade.getShortId() + " has been closed.").show(); + break; + } } private void setBitcoinNetworkSyncProgress(double value) { btcSyncProgress.set(value); - String numPeers = "Nr. of peers: " + numBTCPeers; + String numPeers = "Nr. of Bitcoin network peers: " + numBTCPeers; if (value == 1) { btcSplashInfo.set(numPeers + " / synchronized with " + btcNetworkAsString); - btcFooterInfo.set(numPeers + " / synchronized with " + btcNetworkAsString); + btcFooterInfo.set(btcSplashInfo.get()); btcSplashSyncIconId.set("image-connection-synced"); stopCheckForBtcSyncStateTimer(); } else if (value > 0.0) { @@ -916,7 +879,7 @@ public class MainViewModel implements ViewModel { stopCheckForBtcSyncStateTimer(); } else if (value == -1) { btcSplashInfo.set(numPeers + " / connecting to " + btcNetworkAsString); - btcFooterInfo.set(numPeers + " / connecting to " + btcNetworkAsString); + btcFooterInfo.set(btcSplashInfo.get()); } else { log.error("Not allowed value at setBitcoinNetworkSyncProgress: " + value); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/BuyerSubView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/BuyerSubView.java index c25ce3d3c4..bb0def1ad3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/BuyerSubView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/BuyerSubView.java @@ -17,9 +17,10 @@ package io.bitsquare.gui.main.portfolio.pendingtrades; +import io.bitsquare.app.Log; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.buyer.*; -import javafx.beans.value.ChangeListener; +import org.fxmisc.easybind.EasyBind; public class BuyerSubView extends TradeSubView { private TradeWizardItem step1; @@ -28,8 +29,6 @@ public class BuyerSubView extends TradeSubView { private TradeWizardItem step4; private TradeWizardItem step5; - private final ChangeListener stateChangeListener; - /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -37,20 +36,13 @@ public class BuyerSubView extends TradeSubView { public BuyerSubView(PendingTradesViewModel model) { super(model); - stateChangeListener = (ov, oldValue, newValue) -> applyState(newValue); + } @Override protected void activate() { + viewStateSubscription = EasyBind.subscribe(model.getBuyerState(), this::onViewStateChanged); super.activate(); - model.getBuyerState().addListener(stateChangeListener); - applyState(model.getBuyerState().get()); - } - - @Override - protected void deactivate() { - super.deactivate(); - model.getBuyerState().removeListener(stateChangeListener); } @Override @@ -81,64 +73,51 @@ public class BuyerSubView extends TradeSubView { // State /////////////////////////////////////////////////////////////////////////////////////////// - private void applyState(PendingTradesViewModel.BuyerState state) { - log.debug("applyState " + state); + @Override + protected void onViewStateChanged(PendingTradesViewModel.State viewState) { + Log.traceCall(viewState.toString()); + if (viewState != null) { + PendingTradesViewModel.BuyerState buyerState = (PendingTradesViewModel.BuyerState) viewState; - step1.setDisabled(); - step2.setDisabled(); - step3.setDisabled(); - step4.setDisabled(); - step5.setDisabled(); + step1.setDisabled(); + step2.setDisabled(); + step3.setDisabled(); + step4.setDisabled(); + step5.setDisabled(); - if (tradeStepView != null) - tradeStepView.doDeactivate(); - - switch (state) { - case UNDEFINED: - contentPane.getChildren().clear(); - leftVBox.getChildren().clear(); - break; - case WAIT_FOR_BLOCKCHAIN_CONFIRMATION: - showItem(step1); - break; - case REQUEST_START_FIAT_PAYMENT: - step1.setCompleted(); - showItem(step2); - break; - case WAIT_FOR_FIAT_PAYMENT_RECEIPT: - step1.setCompleted(); - step2.setCompleted(); - showItem(step3); - break; - case WAIT_FOR_BROADCAST_AFTER_UNLOCK: - step1.setCompleted(); - step2.setCompleted(); - step3.setCompleted(); - showItem(step4); - break; - case REQUEST_WITHDRAWAL: - step1.setCompleted(); - step2.setCompleted(); - step3.setCompleted(); - step4.setCompleted(); - showItem(step5); - - BuyerStep5View buyerStep5View = (BuyerStep5View) tradeStepView; - buyerStep5View.setBtcTradeAmountLabelText("You have bought:"); - buyerStep5View.setFiatTradeAmountLabelText("You have paid:"); - buyerStep5View.setBtcTradeAmountTextFieldText(model.getTradeVolume()); - buyerStep5View.setFiatTradeAmountTextFieldText(model.getFiatVolume()); - buyerStep5View.setFeesTextFieldText(model.getTotalFees()); - buyerStep5View.setSecurityDepositTextFieldText(model.getSecurityDeposit()); - buyerStep5View.setWithdrawAmountTextFieldText(model.getPayoutAmount()); - break; - default: - log.warn("unhandled buyerState " + state); - break; + switch (buyerState) { + case UNDEFINED: + break; + case WAIT_FOR_BLOCKCHAIN_CONFIRMATION: + showItem(step1); + break; + case REQUEST_START_FIAT_PAYMENT: + step1.setCompleted(); + showItem(step2); + break; + case WAIT_FOR_FIAT_PAYMENT_RECEIPT: + step1.setCompleted(); + step2.setCompleted(); + showItem(step3); + break; + case WAIT_FOR_BROADCAST_AFTER_UNLOCK: + step1.setCompleted(); + step2.setCompleted(); + step3.setCompleted(); + showItem(step4); + break; + case REQUEST_WITHDRAWAL: + step1.setCompleted(); + step2.setCompleted(); + step3.setCompleted(); + step4.setCompleted(); + showItem(step5); + break; + default: + log.warn("unhandled buyerState " + buyerState); + break; + } } - - if (tradeStepView != null) - tradeStepView.doActivate(); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java index bf7de4c585..fcda89e404 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -35,11 +35,17 @@ import io.bitsquare.gui.main.disputes.DisputesView; import io.bitsquare.gui.popups.SelectDepositTxPopup; import io.bitsquare.gui.popups.WalletPasswordPopup; import io.bitsquare.payment.PaymentAccountContractData; -import io.bitsquare.trade.*; +import io.bitsquare.trade.BuyerTrade; +import io.bitsquare.trade.SellerTrade; +import io.bitsquare.trade.Trade; +import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.offer.Offer; import io.bitsquare.user.Preferences; import io.bitsquare.user.User; -import javafx.beans.property.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -48,6 +54,7 @@ import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.spongycastle.crypto.params.KeyParameter; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -56,24 +63,22 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; public class PendingTradesDataModel extends ActivatableDataModel { - private final TradeManager tradeManager; - private final WalletService walletService; + public final TradeManager tradeManager; + public final WalletService walletService; private final TradeWalletService tradeWalletService; private final User user; private final KeyRing keyRing; - private final DisputeManager disputeManager; + public final DisputeManager disputeManager; private final Navigation navigation; private final WalletPasswordPopup walletPasswordPopup; - private final ObservableList list = FXCollections.observableArrayList(); - private PendingTradesListItem selectedItem; + final ObservableList list = FXCollections.observableArrayList(); private final ListChangeListener tradesListChangeListener; private boolean isOfferer; - private final ObjectProperty tradeProperty = new SimpleObjectProperty<>(); - private final StringProperty txId = new SimpleStringProperty(); - private Trade trade; - private final Preferences preferences; + final ObjectProperty selectedItemProperty = new SimpleObjectProperty<>(); + public final StringProperty txId = new SimpleStringProperty(); + public final Preferences preferences; /////////////////////////////////////////////////////////////////////////////////////////// @@ -108,88 +113,37 @@ public class PendingTradesDataModel extends ActivatableDataModel { tradeManager.getTrades().removeListener(tradesListChangeListener); } - private void onListChanged() { - Log.traceCall(); - list.clear(); - list.addAll(tradeManager.getTrades().stream().map(PendingTradesListItem::new).collect(Collectors.toList())); - - // we sort by date, earliest first - list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate())); - - // TODO improve selectedItem handling - // selectedItem does not get set to null if we dont have the view visible - // So if the item gets removed form the list, and a new item is added we need to check if the old - // selectedItem is in the new list, if not we know it is an invalid one - if (list.size() == 1) - onSelectTrade(list.get(0)); - else if (list.size() > 1 && (selectedItem == null || !list.contains(selectedItem))) - onSelectTrade(list.get(0)); - else if (list.size() == 0) - onSelectTrade(null); - } - /////////////////////////////////////////////////////////////////////////////////////////// // UI actions /////////////////////////////////////////////////////////////////////////////////////////// - void onSelectTrade(PendingTradesListItem item) { - // clean up previous selectedItem - selectedItem = item; - - if (item == null) { - trade = null; - tradeProperty.set(null); - } else { - trade = item.getTrade(); - tradeProperty.set(trade); - - isOfferer = tradeManager.isMyOffer(trade.getOffer()); - - if (trade.getDepositTx() != null) - txId.set(trade.getDepositTx().getHashAsString()); - } + void onSelectItem(PendingTradesListItem item) { + doSelectItem(item); } - void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - checkNotNull(trade, "trade must not be null"); - checkArgument(trade instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade"); - checkArgument(trade.getDisputeState() == Trade.DisputeState.NONE, "Check failed: trade.getDisputeState() == Trade.DisputeState.NONE"); - ((BuyerTrade) trade).onFiatPaymentStarted(resultHandler, errorMessageHandler); + public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + checkNotNull(getTrade(), "trade must not be null"); + checkArgument(getTrade() instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade"); + checkArgument(getTrade().getDisputeState() == Trade.DisputeState.NONE, "Check failed: trade.getDisputeState() == Trade.DisputeState.NONE"); + ((BuyerTrade) getTrade()).onFiatPaymentStarted(resultHandler, errorMessageHandler); } - void onFiatPaymentReceived() { - checkNotNull(trade, "trade must not be null"); - if (trade instanceof SellerTrade && trade.getDisputeState() == Trade.DisputeState.NONE) - ((SellerTrade) trade).onFiatPaymentReceived(); + public void onFiatPaymentReceived() { + 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(); } public void onWithdrawRequest(String toAddress, ResultHandler resultHandler, FaultHandler faultHandler) { - checkNotNull(trade, "trade must not be null"); + checkNotNull(getTrade(), "trade must not be null"); if (walletService.getWallet().isEncrypted()) { walletPasswordPopup.onAesKey(aesKey -> doWithdrawRequest(toAddress, aesKey, resultHandler, faultHandler)).show(); } else doWithdrawRequest(toAddress, null, resultHandler, faultHandler); } - private void doWithdrawRequest(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, FaultHandler faultHandler) { - if (toAddress != null && toAddress.length() > 0) { - tradeManager.onWithdrawRequest( - toAddress, - aesKey, - trade, - () -> { - resultHandler.handleResult(); - }, - (errorMessage, throwable) -> { - log.error(errorMessage); - faultHandler.handleFault(errorMessage, throwable); - }); - } else { - faultHandler.handleFault("No receiver address defined", null); - } - } - public void onOpenDispute() { tryOpenDispute(false); } @@ -198,11 +152,142 @@ public class PendingTradesDataModel extends ActivatableDataModel { tryOpenDispute(true); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + @Nullable + public PendingTradesListItem getSelectedItem() { + return selectedItemProperty.get() != null ? selectedItemProperty.get() : null; + } + + @Nullable + public Trade getTrade() { + return selectedItemProperty.get() != null ? selectedItemProperty.get().getTrade() : null; + } + + @Nullable + Offer getOffer() { + return getTrade() != null ? getTrade().getOffer() : null; + } + + boolean isBuyOffer() { + return getOffer() != null && getOffer().getDirection() == Offer.Direction.BUY; + } + + boolean isOfferer(Offer offer) { + return tradeManager.isMyOffer(offer); + } + + boolean isOfferer() { + return isOfferer; + } + + Coin getTotalFees() { + return FeePolicy.getFixedTxFeeForTrades().add(isOfferer() ? FeePolicy.getCreateOfferFee() : FeePolicy.getTakeOfferFee()); + } + + public String getCurrencyCode() { + return getOffer() != null ? getOffer().getCurrencyCode() : ""; + } + + public Offer.Direction getDirection(Offer offer) { + isOfferer = tradeManager.isMyOffer(offer); + return isOfferer ? offer.getDirection() : offer.getMirroredDirection(); + } + + void addBlockChainListener(BlockChainListener blockChainListener) { + tradeWalletService.addBlockChainListener(blockChainListener); + } + + void removeBlockChainListener(BlockChainListener blockChainListener) { + tradeWalletService.removeBlockChainListener(blockChainListener); + } + + public long getLockTime() { + return getTrade() != null ? getTrade().getLockTimeAsBlockHeight() : 0; + } + + public long getOpenDisputeTimeAsBlockHeight() { + return getTrade() != null ? getTrade().getOpenDisputeTimeAsBlockHeight() : 0; + } + + public int getBestChainHeight() { + return tradeWalletService.getBestChainHeight(); + } + + @Nullable + public PaymentAccountContractData getSellersPaymentAccountContractData() { + if (getTrade() != null && getTrade().getContract() != null) + return getTrade().getContract().getSellerPaymentAccountContractData(); + else + return null; + } + + public String getReference() { + return getOffer() != null ? getOffer().getReferenceText() : ""; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void onListChanged() { + Log.traceCall(); + list.clear(); + list.addAll(tradeManager.getTrades().stream().map(PendingTradesListItem::new).collect(Collectors.toList())); + + // we sort by date, earliest first + list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate())); + + selectBestItem(); + } + + private void selectBestItem() { + if (list.size() == 1) + doSelectItem(list.get(0)); + else if (list.size() > 1 && (selectedItemProperty.get() == null || !list.contains(selectedItemProperty.get()))) + doSelectItem(list.get(0)); + else if (list.size() == 0) + doSelectItem(null); + } + + private void doSelectItem(PendingTradesListItem item) { + if (item != null) { + Trade trade = item.getTrade(); + isOfferer = tradeManager.isMyOffer(trade.getOffer()); + if (trade.getDepositTx() != null) + txId.set(trade.getDepositTx().getHashAsString()); + } + selectedItemProperty.set(item); + } + + private void doWithdrawRequest(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, FaultHandler faultHandler) { + if (toAddress != null && toAddress.length() > 0) { + tradeManager.onWithdrawRequest( + toAddress, + aesKey, + getTrade(), + () -> { + resultHandler.handleResult(); + selectBestItem(); + }, + (errorMessage, throwable) -> { + log.error(errorMessage); + faultHandler.handleFault(errorMessage, throwable); + }); + } else { + faultHandler.handleFault("No receiver address defined", null); + } + } + private void tryOpenDispute(boolean isSupportTicket) { - if (trade != null) { - Transaction depositTx = trade.getDepositTx(); + if (getTrade() != null) { + Transaction depositTx = getTrade().getDepositTx(); if (depositTx != null) { - doOpenDispute(isSupportTicket, trade.getDepositTx()); + doOpenDispute(isSupportTicket, getTrade().getDepositTx()); } else { log.warn("Trade.depositTx is null. We try to find the tx in our wallet."); List candidates = new ArrayList<>(); @@ -220,21 +305,6 @@ public class PendingTradesDataModel extends ActivatableDataModel { } }); - /*transactions.stream().forEach(transaction -> { - Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet()); - if (!valueSentFromMe.isZero()) { - // spending tx - for (TransactionOutput transactionOutput : transaction.getOutputs()) { - if (!transactionOutput.isMine(walletService.getWallet())) { - if (transactionOutput.getScriptPubKey().isPayToScriptHash()) { - // MS tx - candidates.add(transaction); - } - } - } - } - });*/ - if (candidates.size() == 1) doOpenDispute(isSupportTicket, candidates.get(0)); else if (candidates.size() > 1) @@ -262,12 +332,13 @@ public class PendingTradesDataModel extends ActivatableDataModel { } else { log.warn("depositTx is null"); } - Transaction payoutTx = trade.getPayoutTx(); + Transaction payoutTx = getTrade().getPayoutTx(); if (payoutTx != null) { payoutTxSerialized = payoutTx.bitcoinSerialize(); payoutTxHashAsString = payoutTx.getHashAsString(); } + Trade trade = getTrade(); Dispute dispute = new Dispute(disputeManager.getDisputeStorage(), trade.getId(), keyRing.getPubKeyRing().hashCode(), // traderId @@ -292,110 +363,5 @@ public class PendingTradesDataModel extends ActivatableDataModel { disputeManager.sendOpenNewDisputeMessage(dispute); navigation.navigateTo(MainView.class, DisputesView.class); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters - /////////////////////////////////////////////////////////////////////////////////////////// - - ObservableList getList() { - return list; - } - - boolean isBuyOffer() { - return trade.getOffer().getDirection() == Offer.Direction.BUY; - } - - boolean isOfferer(Offer offer) { - return tradeManager.isMyOffer(offer); - } - - boolean isOfferer() { - return isOfferer; - } - - Coin getTotalFees() { - return FeePolicy.getFixedTxFeeForTrades().add(isOfferer() ? FeePolicy.getCreateOfferFee() : FeePolicy.getTakeOfferFee()); - } - - PendingTradesListItem getSelectedItem() { - return selectedItem; - } - - String getCurrencyCode() { - return trade.getOffer().getCurrencyCode(); - } - - public Offer.Direction getDirection(Offer offer) { - // gets called earlier than onSelectTrade event handler - isOfferer = tradeManager.isMyOffer(offer); - return isOfferer ? offer.getDirection() : offer.getMirroredDirection(); - } - - Coin getPayoutAmount() { - return trade.getPayoutAmount(); - } - - Contract getContract() { - return trade.getContract(); - } - - public Trade getTrade() { - return trade; - } - - ReadOnlyObjectProperty getTradeProperty() { - return tradeProperty; - } - - ReadOnlyStringProperty getTxId() { - return txId; - } - - void addBlockChainListener(BlockChainListener blockChainListener) { - tradeWalletService.addBlockChainListener(blockChainListener); - } - - void removeBlockChainListener(BlockChainListener blockChainListener) { - tradeWalletService.removeBlockChainListener(blockChainListener); - } - - public long getLockTime() { - return trade.getLockTimeAsBlockHeight(); - } - - public long getCheckPaymentTimeAsBlockHeight() { - return trade.getCheckPaymentTimeAsBlockHeight(); - } - - public long getOpenDisputeTimeAsBlockHeight() { - return trade.getOpenDisputeTimeAsBlockHeight(); - } - - public int getBestChainHeight() { - return tradeWalletService.getBestChainHeight(); - } - - public PaymentAccountContractData getSellersPaymentAccountContractData() { - if (trade.getContract() != null) - return trade.getContract().getSellerPaymentAccountContractData(); - else - return null; - } - - public String getReference() { - return trade.getOffer().getReferenceText(); - } - - public WalletService getWalletService() { - return walletService; - } - - public DisputeManager getDisputeManager() { - return disputeManager; - } - - public Preferences getPreferences() { - return preferences; - } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesListItem.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesListItem.java index 0135f15298..d994f1cb31 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesListItem.java @@ -52,4 +52,5 @@ public class PendingTradesListItem { public Fiat getPrice() { return trade.getOffer().getPrice(); } + } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java index 5da24f81da..77e85c3160 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java @@ -17,6 +17,7 @@ package io.bitsquare.gui.main.portfolio.pendingtrades; +import io.bitsquare.app.Log; import io.bitsquare.common.UserThread; import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.FxmlView; @@ -24,11 +25,7 @@ import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.popups.OpenEmergencyTicketPopup; import io.bitsquare.gui.popups.TradeDetailsPopup; import io.bitsquare.gui.util.BSFormatter; -import io.bitsquare.trade.Trade; -import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.beans.value.ChangeListener; -import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.Scene; @@ -44,6 +41,8 @@ import javafx.util.Callback; import javafx.util.StringConverter; import org.bitcoinj.core.Coin; import org.bitcoinj.utils.Fiat; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; import javax.inject.Inject; @@ -61,14 +60,12 @@ public class PendingTradesView extends ActivatableViewAndModel tradeAmountColumn; - private ChangeListener selectedItemChangeListener; - private TradeSubView currentSubView; - private ChangeListener appFocusChangeListener; - private ReadOnlyBooleanProperty appFocusProperty; - private ChangeListener currentTradeChangeListener; + private TradeSubView selectedSubView; private EventHandler keyEventEventHandler; private Scene scene; - private ListChangeListener listChangeListener; + private Subscription selectedTableItemSubscription; + private Subscription selectedItemSubscription; + private Subscription appFocusSubscription; /////////////////////////////////////////////////////////////////////////////////////////// @@ -95,30 +92,6 @@ public class PendingTradesView extends ActivatableViewAndModel { - model.onSelectTrade(newValue); - log.debug("selectedItemChangeListener {} ", newValue); - if (newValue != null) - setNewSubView(newValue.getTrade()); - }; - listChangeListener = c -> updateSelectedItem(); - - appFocusChangeListener = (observable, oldValue, newValue) -> { - if (newValue && model.getSelectedItem() != null) { - // Focus selectedItem from model - int index = table.getItems().indexOf(model.getSelectedItem()); - UserThread.execute(() -> { - //TODO app wide focus - //table.requestFocus(); - //UserThread.execute(() -> table.getFocusModel().focus(index)); - }); - } - }; - - currentTradeChangeListener = (observable, oldValue, newValue) -> { - log.debug("currentTradeChangeListener {} ", newValue); - // setNewSubView(newValue); - }; // we use a hidden emergency shortcut to open support ticket keyEventEventHandler = event -> { @@ -129,51 +102,95 @@ public class PendingTradesView extends ActivatableViewAndModel { + if (isFocused && model.dataModel.selectedItemProperty.get() != null) { + // Focus selectedItem from model + int index = table.getItems().indexOf(model.dataModel.selectedItemProperty.get()); + UserThread.execute(() -> { + //TODO app wide focus + //table.requestFocus(); + //UserThread.execute(() -> table.getFocusModel().focus(index)); + }); + } + });*/ } + table.setItems(model.dataModel.list); - appFocusProperty.addListener(appFocusChangeListener); - model.currentTrade().addListener(currentTradeChangeListener); - //setNewSubView(model.currentTrade().get()); - table.setItems(model.getList()); - table.getSelectionModel().selectedItemProperty().addListener(selectedItemChangeListener); + selectedItemSubscription = EasyBind.subscribe(model.dataModel.selectedItemProperty, selectedItem -> { + if (selectedItem != null) { + if (selectedSubView != null) + selectedSubView.deactivate(); - if (model.getSelectedItem() == null) - model.getList().addListener(listChangeListener); + if (selectedItem.getTrade() != null) { + // If we are the offerer the direction is like expected + // If we are the taker the direction is mirrored + if (model.dataModel.isOfferer()) + selectedSubView = model.dataModel.isBuyOffer() ? new BuyerSubView(model) : new SellerSubView(model); + else + selectedSubView = model.dataModel.isBuyOffer() ? new SellerSubView(model) : new BuyerSubView(model); + selectedSubView.setMinHeight(430); + VBox.setVgrow(selectedSubView, Priority.ALWAYS); + if (root.getChildren().size() == 1) + root.getChildren().add(selectedSubView); + else if (root.getChildren().size() == 2) + root.getChildren().set(1, selectedSubView); + selectedSubView.activate(); + } - updateSelectedItem(); + updateTableSelection(); + } else { + removeSelectedSubView(); + } + + model.onSelectedItemChanged(selectedItem); + }); + + selectedTableItemSubscription = EasyBind.subscribe(table.getSelectionModel().selectedItemProperty(), + selectedItem -> { + if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get())) + model.dataModel.onSelectItem(selectedItem); + }); + + updateTableSelection(); } @Override protected void deactivate() { - table.getSelectionModel().selectedItemProperty().removeListener(selectedItemChangeListener); + selectedItemSubscription.unsubscribe(); + selectedTableItemSubscription.unsubscribe(); + if (appFocusSubscription != null) + appFocusSubscription.unsubscribe(); - model.getList().removeListener(listChangeListener); - - if (model.currentTrade() != null) - model.currentTrade().removeListener(currentTradeChangeListener); - - if (appFocusProperty != null) { - appFocusProperty.removeListener(appFocusChangeListener); - appFocusProperty = null; - } - - if (currentSubView != null) - currentSubView.deactivate(); + removeSelectedSubView(); if (scene != null) scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); } - private void updateSelectedItem() { - PendingTradesListItem selectedItem = model.getSelectedItem(); - if (selectedItem != null) { + private void removeSelectedSubView() { + if (selectedSubView != null) { + selectedSubView.deactivate(); + root.getChildren().remove(selectedSubView); + selectedSubView = null; + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void updateTableSelection() { + PendingTradesListItem selectedItemFromModel = model.dataModel.selectedItemProperty.get(); + if (selectedItemFromModel != null) { // Select and focus selectedItem from model - int index = table.getItems().indexOf(selectedItem); + int index = table.getItems().indexOf(selectedItemFromModel); UserThread.execute(() -> { //TODO app wide focus table.getSelectionModel().select(index); @@ -182,37 +199,6 @@ public class PendingTradesView extends ActivatableViewAndModel implements ViewModel { private Subscription tradeStateSubscription; - private interface State { + interface State { } enum BuyerState implements State { @@ -67,20 +64,15 @@ public class PendingTradesViewModel extends ActivatableWithDataModel buyerState = new SimpleObjectProperty<>(PendingTradesViewModel.BuyerState.UNDEFINED); - private final ObjectProperty sellerState = new SimpleObjectProperty<>(UNDEFINED); + private final ObjectProperty buyerState = new SimpleObjectProperty<>(); + private final ObjectProperty sellerState = new SimpleObjectProperty<>(); - private final StringProperty txId = new SimpleStringProperty(); private final BooleanProperty withdrawalButtonDisable = new SimpleBooleanProperty(true); @@ -92,65 +84,38 @@ public class PendingTradesViewModel extends ActivatableWithDataModel tradeStateChangeListener; + @Override protected void activate() { - setTradeStateSubscription(); - - txId.bind(dataModel.getTxId()); } + // Dont set own listener as we need to control the order of the calls + public void onSelectedItemChanged(PendingTradesListItem selectedItem) { + if (tradeStateSubscription != null) + tradeStateSubscription.unsubscribe(); + + if (selectedItem != null) + tradeStateSubscription = EasyBind.subscribe(selectedItem.getTrade().stateProperty(), this::onTradeStateChanged); + } + @Override protected void deactivate() { if (tradeStateSubscription != null) { tradeStateSubscription.unsubscribe(); tradeStateSubscription = null; } - txId.unbind(); - } - - private void setTradeStateSubscription() { - if (tradeStateSubscription != null) - tradeStateSubscription.unsubscribe(); - - if (dataModel.getTrade() != null) { - tradeStateSubscription = EasyBind.subscribe(dataModel.getTrade().stateProperty(), newValue -> { - log.debug("tradeStateSubscription " + newValue); - if (newValue != null) { - applyState(newValue); - } - }); - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // UI actions - /////////////////////////////////////////////////////////////////////////////////////////// - - void onSelectTrade(PendingTradesListItem item) { - dataModel.onSelectTrade(item); - - // call it after dataModel.onSelectTrade as trade is set - setTradeStateSubscription(); } @@ -166,76 +131,34 @@ public class PendingTradesViewModel extends ActivatableWithDataModel currentTrade() { - return dataModel.getTradeProperty(); - } - - public void fiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - dataModel.onFiatPaymentStarted(resultHandler, errorMessageHandler); - } - - public void fiatPaymentReceived() { - dataModel.onFiatPaymentReceived(); - } - public void withdrawAddressFocusOut(String text) { withdrawalButtonDisable.set(!btcAddressValidator.validate(text).isValid); } public String getPayoutAmount() { - return formatter.formatCoinWithCode(dataModel.getPayoutAmount()); - } - - ObservableList getList() { - return dataModel.getList(); - } - - public boolean isOfferer() { - return dataModel.isOfferer(); - } - - PendingTradesListItem getSelectedItem() { - return dataModel.getSelectedItem(); - } - - public String getCurrencyCode() { - return dataModel.getCurrencyCode(); - } - - public BtcAddressValidator getBtcAddressValidator() { - return btcAddressValidator; - } - - public boolean isBootstrapped() { - return p2PService.isBootstrapped(); + return dataModel.getTrade() != null ? formatter.formatCoinWithCode(dataModel.getTrade().getPayoutAmount()) : ""; } // columns - public String getRemainingTime() { - return formatter.getPeriodBetweenBlockHeights(getBestChainHeight(), - dataModel.getTrade().getOpenDisputeTimeAsBlockHeight()); + if (dataModel.getTrade() != null) + return formatter.getPeriodBetweenBlockHeights(getBestChainHeight(), + dataModel.getTrade().getOpenDisputeTimeAsBlockHeight()); + else + return ""; } public double getRemainingTimeAsPercentage() { - double remainingBlocks = dataModel.getTrade().getOpenDisputeTimeAsBlockHeight() - getBestChainHeight(); - double maxPeriod = dataModel.getTrade().getOffer().getPaymentMethod().getMaxTradePeriod(); - if (maxPeriod != 0) - return 1 - remainingBlocks / maxPeriod; - else + if (dataModel.getTrade() != null && dataModel.getOffer() != null) { + double remainingBlocks = dataModel.getTrade().getOpenDisputeTimeAsBlockHeight() - getBestChainHeight(); + double maxPeriod = dataModel.getOffer().getPaymentMethod().getMaxTradePeriod(); + if (maxPeriod != 0) + return 1 - remainingBlocks / maxPeriod; + else + return 0; + } else { return 0; + } } public boolean showWarning(Trade trade) { @@ -270,10 +193,6 @@ public class PendingTradesViewModel extends ActivatableWithDataModel stateChangeListener; - /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -37,20 +36,12 @@ public class SellerSubView extends TradeSubView { public SellerSubView(PendingTradesViewModel model) { super(model); - stateChangeListener = (ov, oldValue, newValue) -> applyState(newValue); } @Override protected void activate() { + viewStateSubscription = EasyBind.subscribe(model.getSellerState(), this::onViewStateChanged); super.activate(); - model.getSellerState().addListener(stateChangeListener); - applyState(model.getSellerState().get()); - } - - @Override - protected void deactivate() { - super.deactivate(); - model.getSellerState().removeListener(stateChangeListener); } @Override @@ -81,74 +72,60 @@ public class SellerSubView extends TradeSubView { // State /////////////////////////////////////////////////////////////////////////////////////////// - private void applyState(PendingTradesViewModel.SellerState viewState) { - log.debug("applyState " + viewState); + @Override + protected void onViewStateChanged(PendingTradesViewModel.State viewState) { + Log.traceCall(viewState.toString()); + if (viewState != null) { + PendingTradesViewModel.SellerState sellerState = (PendingTradesViewModel.SellerState) viewState; - step1.setDisabled(); - step2.setDisabled(); - step3.setDisabled(); - step4.setDisabled(); - step5.setDisabled(); + step1.setDisabled(); + step2.setDisabled(); + step3.setDisabled(); + step4.setDisabled(); + step5.setDisabled(); - if (tradeStepView != null) - tradeStepView.doDeactivate(); + switch (sellerState) { + case UNDEFINED: + break; + case WAIT_FOR_BLOCKCHAIN_CONFIRMATION: + showItem(step1); + break; + case WAIT_FOR_FIAT_PAYMENT_STARTED: + step1.setCompleted(); + showItem(step2); + break; + case REQUEST_CONFIRM_FIAT_PAYMENT_RECEIVED: + step1.setCompleted(); + step2.setCompleted(); + showItem(step3); + break; + case WAIT_FOR_PAYOUT_TX: + step1.setCompleted(); + step2.setCompleted(); + showItem(step3); - switch (viewState) { - case UNDEFINED: - contentPane.getChildren().clear(); - leftVBox.getChildren().clear(); - break; - case WAIT_FOR_BLOCKCHAIN_CONFIRMATION: - showItem(step1); - break; - case WAIT_FOR_FIAT_PAYMENT_STARTED: - step1.setCompleted(); - showItem(step2); - break; - case REQUEST_CONFIRM_FIAT_PAYMENT_RECEIVED: - step1.setCompleted(); - step2.setCompleted(); - showItem(step3); - break; - case WAIT_FOR_PAYOUT_TX: - step1.setCompleted(); - step2.setCompleted(); - showItem(step3); - - // We don't use a wizard for that step as it only gets displayed in case the other peer is offline - tradeStepView = new SellerStep3bView(model); - contentPane.getChildren().setAll(tradeStepView); - break; - case WAIT_FOR_BROADCAST_AFTER_UNLOCK: - step1.setCompleted(); - step2.setCompleted(); - step3.setCompleted(); - showItem(step4); - break; - case REQUEST_WITHDRAWAL: - step1.setCompleted(); - step2.setCompleted(); - step3.setCompleted(); - step4.setCompleted(); - showItem(step5); - - SellerStep5View sellerStep5View = (SellerStep5View) tradeStepView; - sellerStep5View.setBtcTradeAmountLabelText("You have sold:"); - sellerStep5View.setFiatTradeAmountLabelText("You have received:"); - sellerStep5View.setBtcTradeAmountTextFieldText(model.getTradeVolume()); - sellerStep5View.setFiatTradeAmountTextFieldText(model.getFiatVolume()); - sellerStep5View.setFeesTextFieldText(model.getTotalFees()); - sellerStep5View.setSecurityDepositTextFieldText(model.getSecurityDeposit()); - - sellerStep5View.setWithdrawAmountTextFieldText(model.getPayoutAmount()); - break; - default: - log.warn("unhandled viewState " + viewState); - break; + // We don't use a wizard for that step as it only gets displayed in case the other peer is offline + tradeStepView = new SellerStep3bView(model); + contentPane.getChildren().setAll(tradeStepView); + break; + case WAIT_FOR_BROADCAST_AFTER_UNLOCK: + step1.setCompleted(); + step2.setCompleted(); + step3.setCompleted(); + showItem(step4); + break; + case REQUEST_WITHDRAWAL: + step1.setCompleted(); + step2.setCompleted(); + step3.setCompleted(); + step4.setCompleted(); + showItem(step5); + break; + default: + log.warn("unhandled viewState " + sellerState); + break; + } } - - if (tradeStepView != null) - tradeStepView.doActivate(); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/TradeSubView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/TradeSubView.java index 1a915e5d48..6e6eb4c396 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/TradeSubView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/TradeSubView.java @@ -25,6 +25,7 @@ import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.*; +import org.fxmisc.easybind.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +43,7 @@ public abstract class TradeSubView extends HBox { protected GridPane leftGridPane; protected TitledGroupBg tradeProcessTitledGroupBg; protected int leftGridPaneRowIndex = 0; + protected Subscription viewStateSubscription; /////////////////////////////////////////////////////////////////////////////////////////// @@ -59,11 +61,15 @@ public abstract class TradeSubView extends HBox { } protected void deactivate() { + if (viewStateSubscription != null) + viewStateSubscription.unsubscribe(); + if (tradeStepView != null) - tradeStepView.doDeactivate(); + tradeStepView.deactivate(); if (openDisputeButton != null) leftGridPane.getChildren().remove(openDisputeButton); + if (notificationGroup != null) notificationGroup.removeItselfFrom(leftGridPane); } @@ -74,9 +80,9 @@ public abstract class TradeSubView extends HBox { leftGridPane = new GridPane(); leftGridPane.setPrefWidth(340); - VBox.setMargin(leftGridPane, new Insets(0, 10, 10, 10)); leftGridPane.setHgap(Layout.GRID_GAP); leftGridPane.setVgap(Layout.GRID_GAP); + VBox.setMargin(leftGridPane, new Insets(0, 10, 10, 10)); leftVBox.getChildren().add(leftGridPane); leftGridPaneRowIndex = 0; @@ -132,6 +138,8 @@ public abstract class TradeSubView extends HBox { abstract protected void addWizards(); + abstract protected void onViewStateChanged(PendingTradesViewModel.State viewState); + protected void addWizardsToGridPane(TradeWizardItem tradeWizardItem) { if (leftGridPaneRowIndex == 0) GridPane.setMargin(tradeWizardItem, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0)); @@ -143,12 +151,15 @@ public abstract class TradeSubView extends HBox { } private void createAndAddTradeStepView(Class viewClass) { + if (tradeStepView != null) + tradeStepView.deactivate(); try { tradeStepView = viewClass.getDeclaredConstructor(PendingTradesViewModel.class).newInstance(model); contentPane.getChildren().setAll(tradeStepView); - tradeStepView.setNotificationGroup(notificationGroup); + tradeStepView.activate(); } catch (Exception e) { + log.error("Creating viewClass {} caused an error {}", viewClass, e.getMessage()); e.printStackTrace(); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/TradeStepView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/TradeStepView.java index 5266b7f879..d5f2c425fb 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -17,6 +17,7 @@ package io.bitsquare.gui.main.portfolio.pendingtrades.steps; +import io.bitsquare.app.Log; import io.bitsquare.arbitration.Dispute; import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.components.TxIdTextField; @@ -27,7 +28,6 @@ import io.bitsquare.gui.popups.Popup; import io.bitsquare.gui.util.Layout; import io.bitsquare.trade.Trade; import io.bitsquare.user.Preferences; -import javafx.beans.value.ChangeListener; import javafx.scene.control.ProgressBar; import javafx.scene.control.TextField; import javafx.scene.layout.AnchorPane; @@ -42,16 +42,16 @@ import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; import static io.bitsquare.gui.util.FormBuilder.*; public abstract class TradeStepView extends AnchorPane { protected final Logger log = LoggerFactory.getLogger(this.getClass()); protected final PendingTradesViewModel model; - private final Trade trade; + protected final Trade trade; protected final Preferences preferences; protected final GridPane gridPane; - private final ChangeListener txIdChangeListener; private Subscription errorMessageSubscription; private Subscription disputeStateSubscription; @@ -63,6 +63,7 @@ public abstract class TradeStepView extends AnchorPane { private ProgressBar timeLeftProgressBar; private TxIdTextField txIdTextField; protected TradeSubView.NotificationGroup notificationGroup; + private Subscription txIdSubscription; /////////////////////////////////////////////////////////////////////////////////////////// @@ -71,8 +72,9 @@ public abstract class TradeStepView extends AnchorPane { protected TradeStepView(PendingTradesViewModel model) { this.model = model; - preferences = model.dataModel.getPreferences(); - trade = model.getTrade(); + preferences = model.dataModel.preferences; + trade = model.dataModel.getTrade(); + checkNotNull(trade, "trade must not be null at TradeStepView"); gridPane = addGridPane(this); @@ -81,16 +83,16 @@ public abstract class TradeStepView extends AnchorPane { AnchorPane.setTopAnchor(this, -10d); AnchorPane.setBottomAnchor(this, 0d); - txIdChangeListener = (ov, oldValue, newValue) -> txIdTextField.setup(newValue); - addContent(); } - public void doActivate() { + public void activate() { if (txIdTextField != null) { - txIdTextField.setup(model.txIdProperty().get()); + txIdTextField.setup(model.dataModel.txId.get()); + if (txIdSubscription != null) + txIdSubscription.unsubscribe(); - model.txIdProperty().addListener(txIdChangeListener); + txIdSubscription = EasyBind.subscribe(model.dataModel.txId, id -> txIdTextField.setup(id)); } errorMessageSubscription = EasyBind.subscribe(trade.errorMessageProperty(), newValue -> { @@ -111,16 +113,16 @@ public abstract class TradeStepView extends AnchorPane { } }); - timer = FxTimer.runPeriodically(Duration.ofSeconds(1), this::updateTimeLeft); - + timer = FxTimer.runPeriodically(Duration.ofMinutes(1), this::updateTimeLeft); } - public void doDeactivate() { - if (txIdTextField != null) { - txIdTextField.cleanup(); + public void deactivate() { + Log.traceCall(); + if (txIdSubscription != null) + txIdSubscription.unsubscribe(); - model.txIdProperty().removeListener(txIdChangeListener); - } + if (txIdTextField != null) + txIdTextField.cleanup(); if (errorMessageSubscription != null) errorMessageSubscription.unsubscribe(); @@ -189,7 +191,7 @@ public abstract class TradeStepView extends AnchorPane { timeLeftProgressBar.setProgress(model.getRemainingTimeAsPercentage()); if (remainingTime != null) { timeLeftTextField.setText(remainingTime); - if (model.showWarning(model.getTrade()) || model.showDispute(model.getTrade())) { + if (model.showWarning(trade) || model.showDispute(trade)) { timeLeftTextField.setStyle("-fx-text-fill: -bs-error-red"); timeLeftProgressBar.setStyle("-fx-accent: -bs-error-red;"); } @@ -231,44 +233,36 @@ public abstract class TradeStepView extends AnchorPane { protected void setWarningHeadline() { if (notificationGroup != null) { notificationGroup.titledGroupBg.setText("Warning"); - //notificationGroup.setId("trade-notification-warning"); } } protected void setInformationHeadline() { if (notificationGroup != null) { notificationGroup.titledGroupBg.setText("Notification"); - //notificationGroup.titledGroupBg.setId("titled-group-bg-warn"); - //notificationGroup.label.setId("titled-group-bg-label-warn"); - //notificationLabel.setId("titled-group-bg-label-warn"); } } protected void setOpenDisputeHeadline() { if (notificationGroup != null) { notificationGroup.titledGroupBg.setText("Open a dispute"); - //notificationGroup.setId("trade-notification-dispute"); } } protected void setDisputeOpenedHeadline() { if (notificationGroup != null) { notificationGroup.titledGroupBg.setText("Dispute opened"); - //notificationGroup.setId("trade-notification-dispute"); } } protected void setRequestSupportHeadline() { if (notificationGroup != null) { notificationGroup.titledGroupBg.setText("Open support ticket"); - //notificationGroup.setId("trade-notification-support"); } } protected void setSupportOpenedHeadline() { if (notificationGroup != null) { notificationGroup.titledGroupBg.setText("Support ticket opened"); - //notificationGroup.setId("trade-notification-support"); } } @@ -350,7 +344,7 @@ public abstract class TradeStepView extends AnchorPane { break; case DISPUTE_REQUESTED: onDisputeOpened(); - ownDispute = model.dataModel.getDisputeManager().findOwnDispute(trade.getId()); + ownDispute = model.dataModel.disputeManager.findOwnDispute(trade.getId()); ownDispute.ifPresent(dispute -> { String msg; if (dispute.isSupportTicket()) { @@ -369,7 +363,7 @@ public abstract class TradeStepView extends AnchorPane { break; case DISPUTE_STARTED_BY_PEER: onDisputeOpened(); - ownDispute = model.dataModel.getDisputeManager().findOwnDispute(trade.getId()); + ownDispute = model.dataModel.disputeManager.findOwnDispute(trade.getId()); ownDispute.ifPresent(dispute -> { String msg; if (dispute.isSupportTicket()) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 5efb83dbe4..35f4eeb9c2 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -27,13 +27,18 @@ import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView; import io.bitsquare.gui.popups.Popup; import io.bitsquare.gui.util.Layout; import io.bitsquare.locale.BSResources; +import io.bitsquare.locale.CurrencyUtil; +import io.bitsquare.payment.BlockChainAccountContractData; import io.bitsquare.payment.PaymentAccountContractData; import io.bitsquare.payment.PaymentMethod; +import io.bitsquare.trade.Trade; import io.bitsquare.user.PopupId; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.GridPane; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; import static io.bitsquare.gui.util.FormBuilder.*; @@ -42,6 +47,7 @@ public class BuyerStep2View extends TradeStepView { private Button paymentStartedButton; private Label statusLabel; private ProgressIndicator statusProgressIndicator; + private Subscription tradeStatePropertySubscription; /////////////////////////////////////////////////////////////////////////////////////////// @@ -53,26 +59,54 @@ public class BuyerStep2View extends TradeStepView { } @Override - public void doActivate() { - super.doActivate(); + public void activate() { + super.activate(); + //TODO we get called twice, check why + if (tradeStatePropertySubscription == null) { + tradeStatePropertySubscription = EasyBind.subscribe(trade.stateProperty(), state -> { + if (state.equals(Trade.State.DEPOSIT_CONFIRMED)) { + PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData(); + String id = "StartPaymentPopup_" + trade.getId(); + if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) { + String message = ""; + if (paymentAccountContractData instanceof BlockChainAccountContractData) + message = "Please transfer from your external " + + CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()) + " wallet\n" + + model.formatter.formatFiatWithCode(trade.getTradeVolume()) + " to the bitcoin seller.\n\n" + + "Here are the payment account details of the bitcoin seller:\n" + + "" + paymentAccountContractData.getPaymentDetailsForTradePopup() + ".\n\n" + + "You can copy & paste the receivers address from the main screen after closing that popup."; + else if (paymentAccountContractData != null) + message = "Please go to your online banking web page and pay\n" + + model.formatter.formatFiatWithCode(trade.getTradeVolume()) + " to the bitcoin seller.\n\n" + + "Here are the payment account details of the bitcoin seller:\n" + + "" + paymentAccountContractData.getPaymentDetailsForTradePopup() + ".\n\n" + + "Please don't forget to add the reference text " + trade.getShortId() + + " so the receiver can assign your payment to this trade.\n" + + "DO NOT use any additional notice in the reference text like " + + "Bitcoin, Btc, Trade or Bitsquare.\n\n" + + "You can copy & paste the values from the main screen after closing that popup."; - /* String id = PopupId.SEND_PAYMENT_INFO; - if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) { - //TODO use payment method and trade values - new Popup().information("You need to transfer now the agreed amount to your trading partner.\n" + - "Please take care that you use the exact data presented here, including the reference text\n" + - "Please do not click the \"Payment started\" button before you have completed the transfer.\n" + - "Make sure that you make the transfer soon to not exceed the trading period.") - .onClose(() -> preferences.dontShowAgain(id)) - .show(); - }*/ + new Popup().headLine("Notification for trade with ID " + trade.getShortId()) + .message(message) + .closeButtonText("I understand") + .dontShowAgainId(id, preferences) + .show(); + } + } + }); + } } @Override - public void doDeactivate() { - super.doDeactivate(); + public void deactivate() { + super.deactivate(); removeStatusProgressIndicator(); + if (tradeStatePropertySubscription != null) { + tradeStatePropertySubscription.unsubscribe(); + tradeStatePropertySubscription = null; + } } @@ -117,11 +151,12 @@ public class BuyerStep2View extends TradeStepView { log.error("Not supported PaymentMethod: " + paymentMethodName); } - addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Reference text:", model.getReference()); + if (!(paymentAccountContractData instanceof BlockChainAccountContractData)) + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Reference text:", model.dataModel.getReference()); GridPane.setRowSpan(accountTitledGroupBg, gridRow - 3); - Tuple3 tuple3 = addButtonWithStatus(gridPane, ++gridRow, "Payment started"); + Tuple3 tuple3 = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "Payment started"); paymentStartedButton = tuple3.first; paymentStartedButton.setOnAction(e -> onPaymentStarted()); statusProgressIndicator = tuple3.second; @@ -136,7 +171,7 @@ public class BuyerStep2View extends TradeStepView { @Override protected String getWarningText() { setWarningHeadline(); - return "You still have not done your " + model.getCurrencyCode() + " payment!\n" + + return "You still have not done your " + model.dataModel.getCurrencyCode() + " payment!\n" + "Please note that the trade has to be completed until " + model.getOpenDisputeTimeAsFormattedDate() + " otherwise the trade will be investigated by the arbitrator."; @@ -165,7 +200,7 @@ public class BuyerStep2View extends TradeStepView { /////////////////////////////////////////////////////////////////////////////////////////// private void onPaymentStarted() { - if (model.isBootstrapped()) { + if (model.p2PService.isBootstrapped()) { String key = PopupId.PAYMENT_SENT; if (preferences.showAgain(key) && !BitsquareApp.DEV_MODE) { new Popup().headLine("Confirmation") @@ -197,7 +232,7 @@ public class BuyerStep2View extends TradeStepView { statusLabel.setText("Sending message to your trading partner.\n" + "Please wait until you get the confirmation that the message has arrived."); - model.fiatPaymentStarted(() -> { + model.dataModel.onPaymentStarted(() -> { // We would not really need an update as the success triggers a screen change removeStatusProgressIndicator(); statusLabel.setText(""); diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java index fd223dff94..8bfa3616e8 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java @@ -43,7 +43,7 @@ public class BuyerStep3View extends TradeStepView { @Override protected String getInfoText() { return "Waiting for the bitcoin seller's confirmation " + - "for the receipt of the " + model.getCurrencyCode() + " payment."; + "for the receipt of the " + model.dataModel.getCurrencyCode() + " payment."; } @@ -55,7 +55,7 @@ public class BuyerStep3View extends TradeStepView { protected String getWarningText() { setInformationHeadline(); String substitute = model.isBlockChainMethod() ? - "on the " + model.getCurrencyCode() + "blockchain" : + "on the " + model.dataModel.getCurrencyCode() + "blockchain" : "at your payment provider (e.g. bank)"; return "The seller still has not confirmed your payment!\n" + "Please check " + substitute + " if the payment sending was successful.\n" + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index e87249afb9..48c701ec39 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -71,16 +71,16 @@ public class BuyerStep4View extends TradeStepView { } @Override - public void doActivate() { - super.doActivate(); + public void activate() { + super.activate(); model.addBlockChainListener(blockChainListener); updateDateFromBlockHeight(model.getBestChainHeight()); } @Override - public void doDeactivate() { - super.doDeactivate(); + public void deactivate() { + super.deactivate(); model.removeBlockChainListener(blockChainListener); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java index 8f8ddf8b88..9a32feeb79 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java @@ -18,9 +18,8 @@ package io.bitsquare.gui.main.portfolio.pendingtrades.steps.buyer; import io.bitsquare.app.BitsquareApp; -import io.bitsquare.common.UserThread; +import io.bitsquare.app.Log; import io.bitsquare.common.util.Tuple2; -import io.bitsquare.gui.components.InputTextField; import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView; import io.bitsquare.gui.popups.Popup; @@ -35,14 +34,9 @@ import static io.bitsquare.gui.util.FormBuilder.*; public class BuyerStep5View extends TradeStepView { private final ChangeListener focusedPropertyListener; - private Label btcTradeAmountLabel; - private TextField btcTradeAmountTextField; - private Label fiatTradeAmountLabel; - private TextField fiatTradeAmountTextField; - private TextField feesTextField; - private TextField securityDepositTextField; - private InputTextField withdrawAddressTextField; - private TextField withdrawAmountTextField; + protected Label btcTradeAmountLabel; + protected Label fiatTradeAmountLabel; + private TextField withdrawAddressTextField; private Button withdrawButton; @@ -60,8 +54,8 @@ public class BuyerStep5View extends TradeStepView { } @Override - public void doActivate() { - super.doActivate(); + public void activate() { + super.activate(); // TODO valid. handler need improvement //withdrawAddressTextField.focusedProperty().addListener(focusedPropertyListener); @@ -70,21 +64,22 @@ public class BuyerStep5View extends TradeStepView { // We need to handle both cases: Address not set and address already set (when returning from other view) // We get address validation after focus out, so first make sure we loose focus and then set it again as hint for user to put address in - UserThread.execute(() -> { - //TODO app wide focus - // withdrawAddressTextField.requestFocus(); - /* UserThread.execute(() -> { + //TODO app wide focus + /* UserThread.execute(() -> { + withdrawAddressTextField.requestFocus(); + UserThread.execute(() -> { this.requestFocus(); UserThread.execute(() -> withdrawAddressTextField.requestFocus()); - });*/ - }); + }); + });*/ hideNotificationGroup(); } @Override - public void doDeactivate() { - super.doDeactivate(); + public void deactivate() { + Log.traceCall(); + super.deactivate(); //withdrawAddressTextField.focusedProperty().removeListener(focusedPropertyListener); // withdrawButton.disableProperty().unbind(); } @@ -97,22 +92,18 @@ public class BuyerStep5View extends TradeStepView { @Override protected void addContent() { addTitledGroupBg(gridPane, gridRow, 4, "Summary of completed trade ", 0); - Tuple2 btcTradeAmountPair = addLabelTextField(gridPane, gridRow, "You have bought:", "", Layout.FIRST_ROW_DISTANCE); + Tuple2 btcTradeAmountPair = addLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.FIRST_ROW_DISTANCE); btcTradeAmountLabel = btcTradeAmountPair.first; - btcTradeAmountTextField = btcTradeAmountPair.second; - Tuple2 fiatTradeAmountPair = addLabelTextField(gridPane, ++gridRow, "You have paid:"); + Tuple2 fiatTradeAmountPair = addLabelTextField(gridPane, ++gridRow, getFiatTradeAmountLabel(), model.getFiatVolume()); fiatTradeAmountLabel = fiatTradeAmountPair.first; - fiatTradeAmountTextField = fiatTradeAmountPair.second; - Tuple2 feesPair = addLabelTextField(gridPane, ++gridRow, "Total fees paid:"); - feesTextField = feesPair.second; + Tuple2 feesPair = addLabelTextField(gridPane, ++gridRow, "Total fees paid:", model.getTotalFees()); - Tuple2 securityDepositPair = addLabelTextField(gridPane, ++gridRow, "Refunded security deposit:"); - securityDepositTextField = securityDepositPair.second; + Tuple2 securityDepositPair = addLabelTextField(gridPane, ++gridRow, "Refunded security deposit:", model.getSecurityDeposit()); addTitledGroupBg(gridPane, ++gridRow, 2, "Withdraw your bitcoins", Layout.GROUP_DISTANCE); - withdrawAmountTextField = addLabelTextField(gridPane, gridRow, "Amount to withdraw:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + addLabelTextField(gridPane, gridRow, "Amount to withdraw:", model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); withdrawAddressTextField = addLabelInputTextField(gridPane, ++gridRow, "Withdraw to address:").second; withdrawButton = addButtonAfterGroup(gridPane, ++gridRow, "Withdraw to external wallet"); withdrawButton.setOnAction(e -> { @@ -143,36 +134,11 @@ public class BuyerStep5View extends TradeStepView { withdrawAddressTextField.setText("mhpVDvMjJT1Gn7da44dkq1HXd3wXdFZpXu"); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setters - /////////////////////////////////////////////////////////////////////////////////////////// - - public void setBtcTradeAmountLabelText(String text) { - btcTradeAmountLabel.setText(text); + protected String getBtcTradeAmountLabel() { + return "You have bought:"; } - public void setFiatTradeAmountLabelText(String text) { - fiatTradeAmountLabel.setText(text); - } - - public void setBtcTradeAmountTextFieldText(String text) { - btcTradeAmountTextField.setText(text); - } - - public void setFiatTradeAmountTextFieldText(String text) { - fiatTradeAmountTextField.setText(text); - } - - public void setFeesTextFieldText(String text) { - feesTextField.setText(text); - } - - public void setSecurityDepositTextFieldText(String text) { - securityDepositTextField.setText(text); - } - - public void setWithdrawAmountTextFieldText(String text) { - withdrawAmountTextField.setText(text); + protected String getFiatTradeAmountLabel() { + return "You have paid:"; } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java index 7f6fe2b31d..6e46728efe 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java @@ -43,7 +43,7 @@ public class SellerStep2View extends TradeStepView { @Override protected String getInfoText() { return "The deposit transaction has at least one blockchain confirmation.\n" + - "You need to wait until that the bitcoin buyer starts the \" + model.getCurrencyCode() + \" payment."; + "You need to wait until that the bitcoin buyer starts the " + model.dataModel.getCurrencyCode() + " payment."; } @@ -54,7 +54,7 @@ public class SellerStep2View extends TradeStepView { @Override protected String getWarningText() { setInformationHeadline(); - return "The buyer still has not done the " + model.getCurrencyCode() + " payment.\n" + + return "The buyer still has not done the " + model.dataModel.getCurrencyCode() + " payment.\n" + "You need to wait until he starts the payment.\n" + "If the trade has not been completed on " + model.getOpenDisputeTimeAsFormattedDate() + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index d02abeac4c..6c9987bc3f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -18,24 +18,37 @@ package io.bitsquare.gui.main.portfolio.pendingtrades.steps.seller; import io.bitsquare.app.BitsquareApp; +import io.bitsquare.common.util.Tuple3; +import io.bitsquare.gui.components.TextFieldWithCopyIcon; +import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView; import io.bitsquare.gui.popups.Popup; +import io.bitsquare.gui.util.Layout; +import io.bitsquare.locale.CurrencyUtil; +import io.bitsquare.payment.BlockChainAccountContractData; +import io.bitsquare.payment.PaymentAccountContractData; +import io.bitsquare.trade.Contract; +import io.bitsquare.trade.Trade; import io.bitsquare.user.PopupId; import io.bitsquare.user.Preferences; -import javafx.geometry.HPos; -import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; +import org.apache.commons.lang3.StringUtils; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; + +import static io.bitsquare.gui.util.FormBuilder.*; public class SellerStep3View extends TradeStepView { private Button confirmFiatReceivedButton; private Label statusLabel; private ProgressIndicator statusProgressIndicator; + private Subscription tradeStatePropertySubscription; /////////////////////////////////////////////////////////////////////////////////////////// @@ -48,45 +61,100 @@ public class SellerStep3View extends TradeStepView { } @Override - public void doActivate() { - super.doActivate(); + public void activate() { + super.activate(); + + tradeStatePropertySubscription = EasyBind.subscribe(trade.stateProperty(), state -> { + if (state.equals(Trade.State.FIAT_PAYMENT_STARTED_MSG_RECEIVED)) { + PaymentAccountContractData paymentAccountContractData = model.dataModel.getSellersPaymentAccountContractData(); + String id = "ConfirmPaymentPopup_" + trade.getId(); + if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) { + String message; + String tradeAmountWithCode = model.formatter.formatFiatWithCode(trade.getTradeVolume()); + if (paymentAccountContractData instanceof BlockChainAccountContractData) { + String nameByCode = CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()); + String address = ((BlockChainAccountContractData) paymentAccountContractData).getAddress(); + message = "Please check on your favorite " + + nameByCode + + " blockchain explorer if the transaction to your receiving address\n" + + "" + address + "\n" + + "has already " + + "sufficient blockchain confirmations.\n" + + "The payment amount has to be " + tradeAmountWithCode + "\n\n" + + "You can copy & paste your " + nameByCode + " address from the main screen after " + + "closing that popup."; + } else { + message = "Please go to your online banking web page and check if you have received " + + tradeAmountWithCode + " from the bitcoin buyer.\n\n" + + "The reference text of the transaction is: " + trade.getShortId(); + } + new Popup().headLine("Notification for trade with ID " + trade.getShortId()) + .message(message) + .closeButtonText("I understand") + .dontShowAgainId(id, preferences) + .show(); + + } + } + }); } @Override - public void doDeactivate() { - super.doDeactivate(); + public void deactivate() { + super.deactivate(); + if (tradeStatePropertySubscription != null) + tradeStatePropertySubscription.unsubscribe(); statusProgressIndicator.setProgress(0); } + /////////////////////////////////////////////////////////////////////////////////////////// // Content /////////////////////////////////////////////////////////////////////////////////////////// @Override protected void addContent() { - super.addContent(); + addTradeInfoBlock(); - HBox hBox = new HBox(); - hBox.setSpacing(10); - confirmFiatReceivedButton = new Button("Confirm payment receipt"); - confirmFiatReceivedButton.setDefaultButton(true); + TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2, "Confirm payment receipt", Layout.GROUP_DISTANCE); + + TextFieldWithCopyIcon field = addLabelTextFieldWithCopyIcon(gridPane, gridRow, "Amount to receive:", + model.getFiatAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + field.setCopyWithoutCurrencyPostFix(true); + + String paymentDetails = ""; + String title = ""; + boolean isBlockChain = false; + String nameByCode = CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()); + Contract contract = trade.getContract(); + if (contract != null) { + PaymentAccountContractData paymentAccountContractData = contract.getSellerPaymentAccountContractData(); + if (paymentAccountContractData instanceof BlockChainAccountContractData) { + paymentDetails = ((BlockChainAccountContractData) paymentAccountContractData).getAddress(); + title = "Your " + nameByCode + " address:"; + isBlockChain = true; + } else { + paymentDetails = paymentAccountContractData.getPaymentDetails(); + title = "Your payment account:"; + } + } + + TextFieldWithCopyIcon paymentDetailsTextField = addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, + title, StringUtils.abbreviate(paymentDetails, 56)).second; + paymentDetailsTextField.setMouseTransparent(false); + paymentDetailsTextField.setTooltip(new Tooltip(paymentDetails)); + + if (!isBlockChain) { + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Reference text:", model.dataModel.getReference()); + GridPane.setRowSpan(titledGroupBg, 3); + } + + Tuple3 tuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "Confirm payment receipt"); + confirmFiatReceivedButton = tuple.first; confirmFiatReceivedButton.setOnAction(e -> onPaymentReceived()); - - statusProgressIndicator = new ProgressIndicator(0); - statusProgressIndicator.setPrefHeight(24); - statusProgressIndicator.setPrefWidth(24); - statusProgressIndicator.setVisible(false); - - statusLabel = new Label(); - statusLabel.setPadding(new Insets(5, 0, 0, 0)); - - hBox.getChildren().addAll(confirmFiatReceivedButton, statusProgressIndicator, statusLabel); - GridPane.setRowIndex(hBox, ++gridRow); - GridPane.setColumnIndex(hBox, 0); - GridPane.setHalignment(hBox, HPos.LEFT); - GridPane.setMargin(hBox, new Insets(15, 0, 0, 0)); - gridPane.getChildren().add(hBox); + statusProgressIndicator = tuple.second; + statusLabel = tuple.third; } @@ -94,19 +162,15 @@ public class SellerStep3View extends TradeStepView { // Info /////////////////////////////////////////////////////////////////////////////////////////// - @Override - protected String getInfoBlockTitle() { - return "Confirm payment receipt"; - } @Override protected String getInfoText() { if (model.isBlockChainMethod()) { - return "The bitcoin buyer has started the " + model.getCurrencyCode() + " payment.\n" + - "Check for blockchain confirmations at your Altcoin wallet or block explorer and " + + return "The bitcoin buyer has started the " + model.dataModel.getCurrencyCode() + " payment.\n" + + "Check for blockchain confirmations at your cryptocurrency wallet or block explorer and " + "confirm the payment when you have sufficient blockchain confirmations."; } else { - return "The bitcoin buyer has started the " + model.getCurrencyCode() + " payment.\n" + + return "The bitcoin buyer has started the " + model.dataModel.getCurrencyCode() + " payment.\n" + "Check at your payment account (e.g. bank account) and confirm when you have " + "received the payment."; } @@ -121,7 +185,7 @@ public class SellerStep3View extends TradeStepView { protected String getWarningText() { setWarningHeadline(); String substitute = model.isBlockChainMethod() ? - "on the " + model.getCurrencyCode() + "blockchain" : + "on the " + model.dataModel.getCurrencyCode() + "blockchain" : "at your payment provider (e.g. bank)"; return "You still have not confirmed the receipt of the payment!\n" + "Please check " + substitute + " if you have received the payment.\n" + @@ -154,8 +218,8 @@ public class SellerStep3View extends TradeStepView { private void onPaymentReceived() { log.debug("onPaymentReceived"); - if (model.isBootstrapped()) { - Preferences preferences = model.dataModel.getPreferences(); + if (model.p2PService.isBootstrapped()) { + Preferences preferences = model.dataModel.preferences; String key = PopupId.PAYMENT_RECEIVED; if (preferences.showAgain(key) && !BitsquareApp.DEV_MODE) { new Popup().headLine("Confirmation") @@ -183,7 +247,7 @@ public class SellerStep3View extends TradeStepView { statusProgressIndicator.setProgress(-1); statusLabel.setText("Sending message to trading partner..."); - model.fiatPaymentReceived(); + model.dataModel.onFiatPaymentReceived(); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep5View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep5View.java index 0c02014f9f..04adc30036 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep5View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/seller/SellerStep5View.java @@ -29,4 +29,14 @@ public class SellerStep5View extends BuyerStep5View { public SellerStep5View(PendingTradesViewModel model) { super(model); } + + @Override + protected String getBtcTradeAmountLabel() { + return "You have sold:"; + } + + @Override + protected String getFiatTradeAmountLabel() { + return "You have received:"; + } }