From 3f6f8dd160def7d5fdf04c7ae96c51f5038ea4e7 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 22 Apr 2015 18:38:53 +0200 Subject: [PATCH] Remove listeners/unbind at views. Add more error handling --- .../io/bitsquare/common/taskrunner/Task.java | 33 +- .../java/io/bitsquare/storage/Storage.java | 4 +- .../main/java/io/bitsquare/trade/Trade.java | 9 + .../java/io/bitsquare/trade/offer/Offer.java | 5 +- .../trade/offer/OfferBookService.java | 4 +- .../io/bitsquare/trade/offer/OpenOffer.java | 24 +- .../offer/tomp2p/TomP2POfferBookService.java | 5 +- .../OfferAvailabilityProtocol.java | 41 +- .../availability/tasks/GetPeerAddress.java | 5 +- .../ProcessOfferAvailabilityResponse.java | 4 +- .../tasks/SendOfferAvailabilityRequest.java | 4 +- .../protocol/placeoffer/PlaceOfferModel.java | 2 +- .../placeoffer/PlaceOfferProtocol.java | 9 + .../tasks/AddOfferToRemoteOfferBook.java | 16 +- .../tasks/BroadcastCreateOfferFeeTx.java | 101 ++-- .../placeoffer/tasks/CreateOfferFeeTx.java | 3 +- .../placeoffer/tasks/ValidateOffer.java | 3 +- .../trade/BuyerAsOffererProtocol.java | 2 +- .../protocol/trade/BuyerAsTakerProtocol.java | 2 +- .../trade/SellerAsOffererProtocol.java | 2 +- .../protocol/trade/SellerAsTakerProtocol.java | 4 +- .../trade/protocol/trade/TradeProtocol.java | 28 +- .../trade/protocol/trade/TradeTask.java | 12 +- .../tasks/buyer/CreateDepositTxInputs.java | 3 +- .../buyer/ProcessDepositTxInputsRequest.java | 3 +- .../buyer/ProcessFinalizePayoutTxRequest.java | 3 +- .../buyer/ProcessPublishDepositTxRequest.java | 3 +- .../buyer/SendDepositTxPublishedMessage.java | 3 +- .../buyer/SendFiatTransferStartedMessage.java | 3 +- .../tasks/buyer/SendPayDepositRequest.java | 3 +- .../buyer/SendPayoutTxFinalizedMessage.java | 3 +- .../tasks/buyer/SignAndFinalizePayoutTx.java | 3 +- .../tasks/buyer/SignAndPublishDepositTx.java | 3 +- .../tasks/buyer/VerifyAndSignContract.java | 3 +- .../offerer/VerifyTakeOfferFeePayment.java | 3 +- .../tasks/offerer/VerifyTakerAccount.java | 3 +- .../trade/tasks/seller/CommitDepositTx.java | 3 +- .../tasks/seller/CreateAndSignContract.java | 3 +- .../tasks/seller/CreateAndSignDepositTx.java | 3 +- .../ProcessDepositTxPublishedMessage.java | 3 +- .../ProcessFiatTransferStartedMessage.java | 3 +- .../seller/ProcessPayDepositRequest.java | 3 +- .../ProcessPayoutTxFinalizedMessage.java | 3 +- .../seller/SendDepositTxInputsRequest.java | 5 +- .../seller/SendFinalizePayoutTxRequest.java | 3 +- .../seller/SendPublishDepositTxRequest.java | 3 +- .../trade/tasks/seller/SignPayoutTx.java | 3 +- .../trade/tasks/shared/CommitPayoutTx.java | 3 +- .../SetupPayoutTxLockTimeReachedListener.java | 3 +- .../tasks/taker/BroadcastTakeOfferFeeTx.java | 14 +- .../tasks/taker/CreateTakeOfferFeeTx.java | 8 +- .../tasks/taker/VerifyOfferFeePayment.java | 3 +- .../tasks/taker/VerifyOffererAccount.java | 3 +- .../trade/states/BuyerTradeState.java | 5 +- .../trade/states/SellerTradeState.java | 5 +- .../io/bitsquare/trade/states/TradePhase.java | 26 + .../java/io/bitsquare/user/Preferences.java | 10 +- .../java/io/bitsquare/util/Utilities.java | 32 + .../java/io/bitsquare/app/BitsquareApp.java | 7 +- .../java/io/bitsquare/app/UpdateProcess.java | 20 +- .../gui/components/InputTextField.java | 3 + .../io/bitsquare/gui/main/MainViewModel.java | 21 +- .../bitsquare/gui/main/debug/DebugView.fxml | 28 - .../bitsquare/gui/main/debug/DebugView.java | 47 +- .../createoffer/CreateOfferDataModel.java | 209 ++++--- .../offer/createoffer/CreateOfferView.fxml | 2 +- .../offer/createoffer/CreateOfferView.java | 408 ++++++++----- .../createoffer/CreateOfferViewModel.java | 560 +++++++++++------- .../gui/main/offer/offerbook/OfferBook.java | 94 +-- .../offer/offerbook/OfferBookDataModel.java | 169 ++++-- .../offer/offerbook/OfferBookListItem.java | 17 - .../main/offer/offerbook/OfferBookView.java | 261 ++++---- .../offer/offerbook/OfferBookViewModel.java | 115 +++- .../offer/takeoffer/TakeOfferDataModel.java | 107 ++-- .../main/offer/takeoffer/TakeOfferView.fxml | 2 +- .../main/offer/takeoffer/TakeOfferView.java | 294 +++++---- .../offer/takeoffer/TakeOfferViewModel.java | 436 ++++++++------ .../java/io/bitsquare/gui/util/GUIUtil.java | 36 -- .../util/validation/BtcAddressValidator.java | 20 - .../gui/util/validation/BtcValidator.java | 20 +- .../gui/util/validation/FiatValidator.java | 29 +- .../gui/util/validation/InputValidator.java | 17 - .../gui/util/validation/NumberValidator.java | 6 - .../util/validation/OptionalBtcValidator.java | 42 +- .../validation/OptionalFiatValidator.java | 61 +- .../util/validation/PasswordValidator.java | 9 - .../createoffer/CreateOfferViewModelTest.java | 2 +- .../bitsquare/gui/util/BSFormatterTest.java | 0 .../gui/util/validation/BtcValidatorTest.java | 0 .../util/validation/FiatValidatorTest.java | 0 90 files changed, 1940 insertions(+), 1610 deletions(-) create mode 100644 core/src/main/java/io/bitsquare/trade/states/TradePhase.java rename gui/src/{main => test}/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java (99%) rename gui/src/{main => test}/java/io/bitsquare/gui/util/BSFormatterTest.java (100%) rename gui/src/{main => test}/java/io/bitsquare/gui/util/validation/BtcValidatorTest.java (100%) rename gui/src/{main => test}/java/io/bitsquare/gui/util/validation/FiatValidatorTest.java (100%) diff --git a/core/src/main/java/io/bitsquare/common/taskrunner/Task.java b/core/src/main/java/io/bitsquare/common/taskrunner/Task.java index 6d4f81b0c1..f3750039c8 100644 --- a/core/src/main/java/io/bitsquare/common/taskrunner/Task.java +++ b/core/src/main/java/io/bitsquare/common/taskrunner/Task.java @@ -23,8 +23,7 @@ import org.slf4j.LoggerFactory; public abstract class Task { private static final Logger log = LoggerFactory.getLogger(Task.class); - public static Class taskToInterceptBeforeRun; - public static Class taskToInterceptAfterRun; + public static Class taskToIntercept; private final TaskRunner taskHandler; protected final T model; @@ -35,26 +34,11 @@ public abstract class Task { this.model = model; } - protected void run() { - try { - interceptBeforeRun(); - doRun(); - } catch (Throwable t) { - appendExceptionToErrorMessage(t); - failed(); - } - } + abstract protected void run(); - abstract protected void doRun(); - - private void interceptBeforeRun() { - if (getClass() == taskToInterceptBeforeRun) - throw new InterceptTaskException("Task intercepted before run got executed. Task = " + getClass().getSimpleName()); - } - - private void interceptBeforeComplete() { - if (getClass() == taskToInterceptAfterRun) - throw new InterceptTaskException("Task intercepted before complete was called. Task = " + getClass().getSimpleName()); + protected void runInterceptHook() { + if (getClass() == taskToIntercept) + throw new InterceptTaskException("Task intercepted for testing purpose. Task = " + getClass().getSimpleName()); } protected void appendToErrorMessage(String message) { @@ -69,12 +53,6 @@ public abstract class Task { } protected void complete() { - try { - interceptBeforeComplete(); - } catch (Throwable t) { - appendExceptionToErrorMessage(t); - failed(); - } taskHandler.handleComplete(); } @@ -84,6 +62,7 @@ public abstract class Task { } protected void failed(Throwable t) { + t.printStackTrace(); appendExceptionToErrorMessage(t); failed(); } diff --git a/core/src/main/java/io/bitsquare/storage/Storage.java b/core/src/main/java/io/bitsquare/storage/Storage.java index 2773189ddb..f5e82e1137 100644 --- a/core/src/main/java/io/bitsquare/storage/Storage.java +++ b/core/src/main/java/io/bitsquare/storage/Storage.java @@ -21,8 +21,6 @@ import com.google.common.base.Throwables; import java.io.File; import java.io.IOException; -import java.io.InvalidClassException; -import java.io.InvalidObjectException; import java.io.Serializable; import java.util.concurrent.TimeUnit; @@ -131,7 +129,7 @@ public class Storage { log.info("Backup {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now); return persistedObject; - } catch (InvalidClassException | InvalidObjectException | ClassCastException | ClassNotFoundException e) { + } catch (ClassCastException | ClassNotFoundException | IOException e) { e.printStackTrace(); log.error("Version of persisted class has changed. We cannot read the persisted data anymore. We make a backup and remove the inconsistent " + "file."); diff --git a/core/src/main/java/io/bitsquare/trade/Trade.java b/core/src/main/java/io/bitsquare/trade/Trade.java index 205b4b44bf..e4fd4ba5f4 100644 --- a/core/src/main/java/io/bitsquare/trade/Trade.java +++ b/core/src/main/java/io/bitsquare/trade/Trade.java @@ -33,6 +33,8 @@ import io.bitsquare.storage.Storage; import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.protocol.trade.ProcessModel; import io.bitsquare.trade.protocol.trade.TradeProtocol; +import io.bitsquare.trade.states.BuyerTradeState; +import io.bitsquare.trade.states.SellerTradeState; import io.bitsquare.trade.states.TradeState; import io.bitsquare.user.User; @@ -245,6 +247,13 @@ abstract public class Trade implements Tradable, Model, Serializable { storage.queueUpForSave(); } + public void setFaultState() { + if (this instanceof SellerTrade) + setProcessState(SellerTradeState.ProcessState.FAULT); + else if (this instanceof BuyerTrade) + setProcessState(BuyerTradeState.ProcessState.FAULT); + } + public void setLifeCycleState(Trade.LifeCycleState lifeCycleState) { this.lifeCycleState = lifeCycleState; lifeCycleStateProperty.set(lifeCycleState); diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index c18f12238c..587bf2a025 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -59,7 +59,10 @@ public class Offer implements Serializable { AVAILABLE, NOT_AVAILABLE, REMOVED, - OFFERER_OFFLINE + OFFERER_OFFLINE, + + TIMEOUT, + FAULT } diff --git a/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java b/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java index 0938071f56..ac4d90d7eb 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OfferBookService.java @@ -23,7 +23,7 @@ import io.bitsquare.p2p.DHTService; import java.util.List; -import javafx.beans.property.LongProperty; +import javafx.beans.property.ReadOnlyLongProperty; public interface OfferBookService extends DHTService { @@ -39,7 +39,7 @@ public interface OfferBookService extends DHTService { void removeListener(Listener listener); - LongProperty invalidationTimestampProperty(); + ReadOnlyLongProperty invalidationTimestampProperty(); void requestInvalidationTimeStampFromDHT(String fiatCode); diff --git a/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java b/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java index ed30057478..4d33a4d731 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java @@ -21,14 +21,12 @@ import io.bitsquare.app.Version; import io.bitsquare.storage.Storage; import io.bitsquare.trade.Tradable; import io.bitsquare.trade.TradableList; - -import org.bitcoinj.utils.Threading; +import io.bitsquare.util.Utilities; import java.io.Serializable; import java.util.Date; import java.util.Timer; -import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -95,26 +93,16 @@ public class OpenOffer implements Tradable, Serializable { private void startTimeout() { - log.trace("startTimeout"); stopTimeout(); - timeoutTimer = new Timer(); - TimerTask task = new TimerTask() { - @Override - public void run() { - Threading.USER_THREAD.execute(() -> { - log.debug("Timeout reached"); - if (state == State.RESERVED) - setState(State.AVAILABLE); - }); - } - }; - - timeoutTimer.schedule(task, TIMEOUT); + timeoutTimer = Utilities.setTimeout(TIMEOUT, () -> { + log.debug("Timeout reached"); + if (state == State.RESERVED) + setState(State.AVAILABLE); + }); } protected void stopTimeout() { - log.trace("stopTimeout"); if (timeoutTimer != null) { timeoutTimer.cancel(); timeoutTimer = null; diff --git a/core/src/main/java/io/bitsquare/trade/offer/tomp2p/TomP2POfferBookService.java b/core/src/main/java/io/bitsquare/trade/offer/tomp2p/TomP2POfferBookService.java index e7d866fd9b..9dd2dc4719 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/tomp2p/TomP2POfferBookService.java +++ b/core/src/main/java/io/bitsquare/trade/offer/tomp2p/TomP2POfferBookService.java @@ -34,6 +34,7 @@ import java.util.Map; import javax.inject.Inject; import javafx.beans.property.LongProperty; +import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.SimpleLongProperty; import net.tomp2p.dht.FutureGet; @@ -220,7 +221,7 @@ public class TomP2POfferBookService extends TomP2PDHTService implements OfferBoo if (offerDataObject instanceof Offer) { offers.add((Offer) offerDataObject); } - } catch (ClassNotFoundException | IOException e) { + } catch (ClassCastException | ClassNotFoundException | IOException e) { e.printStackTrace(); log.warn(e.getMessage()); } @@ -293,7 +294,7 @@ public class TomP2POfferBookService extends TomP2PDHTService implements OfferBoo } } - public LongProperty invalidationTimestampProperty() { + public ReadOnlyLongProperty invalidationTimestampProperty() { return invalidationTimestamp; } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/availability/OfferAvailabilityProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/availability/OfferAvailabilityProtocol.java index de157f11f4..0ac463b9e4 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/availability/OfferAvailabilityProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/availability/OfferAvailabilityProtocol.java @@ -30,11 +30,9 @@ import io.bitsquare.trade.protocol.availability.messages.OfferMessage; import io.bitsquare.trade.protocol.availability.tasks.GetPeerAddress; import io.bitsquare.trade.protocol.availability.tasks.ProcessOfferAvailabilityResponse; import io.bitsquare.trade.protocol.availability.tasks.SendOfferAvailabilityRequest; - -import org.bitcoinj.utils.Threading; +import io.bitsquare.util.Utilities; import java.util.Timer; -import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +50,6 @@ public class OfferAvailabilityProtocol { private final DecryptedMessageHandler decryptedMessageHandler; private Timer timeoutTimer; - private boolean isCanceled; private TaskRunner taskRunner; @@ -84,8 +81,14 @@ public class OfferAvailabilityProtocol { model.messageService.addDecryptedMessageHandler(decryptedMessageHandler); taskRunner = new TaskRunner<>(model, - () -> log.debug("sequence at onCheckOfferAvailability completed"), - log::error + () -> { + log.debug("sequence at onCheckOfferAvailability completed"); + stopTimeout(); + }, + (errorMessage) -> { + log.error(errorMessage); + stopTimeout(); + } ); taskRunner.addTasks( GetPeerAddress.class, @@ -96,7 +99,6 @@ public class OfferAvailabilityProtocol { } public void cancel() { - isCanceled = true; taskRunner.cancel(); cleanup(); } @@ -119,15 +121,18 @@ public class OfferAvailabilityProtocol { private void handle(OfferAvailabilityResponse message) { stopTimeout(); + startTimeout(); model.setMessage(message); taskRunner = new TaskRunner<>(model, () -> { - log.debug("sequence at handleReportOfferAvailabilityMessage completed"); + log.debug("sequence at handle OfferAvailabilityResponse completed"); + stopTimeout(); resultHandler.handleResult(); }, (errorMessage) -> { log.error(errorMessage); + stopTimeout(); errorMessageHandler.handleErrorMessage(errorMessage); } ); @@ -136,26 +141,16 @@ public class OfferAvailabilityProtocol { } protected void startTimeout() { - log.debug("startTimeout"); stopTimeout(); - timeoutTimer = new Timer(); - TimerTask task = new TimerTask() { - @Override - public void run() { - Threading.USER_THREAD.execute(() -> { - log.debug("Timeout reached"); - errorMessageHandler.handleErrorMessage("Timeout reached: Peer has not responded."); - model.offer.setState(Offer.State.OFFERER_OFFLINE); - }); - } - }; - - timeoutTimer.schedule(task, TIMEOUT); + timeoutTimer = Utilities.setTimeout(TIMEOUT, () -> { + log.warn("Timeout reached"); + errorMessageHandler.handleErrorMessage("Timeout reached: Peer has not responded."); + model.offer.setState(Offer.State.TIMEOUT); + }); } protected void stopTimeout() { - log.debug("stopTimeout"); if (timeoutTimer != null) { timeoutTimer.cancel(); timeoutTimer = null; diff --git a/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/GetPeerAddress.java b/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/GetPeerAddress.java index ebe8fa835b..9be278505a 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/GetPeerAddress.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/GetPeerAddress.java @@ -37,13 +37,13 @@ public class GetPeerAddress extends Task { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); model.addressService.findPeerAddress(model.offer.getPubKeyRing(), new GetPeerAddressListener() { @Override public void onResult(Peer peer) { model.setPeer(peer); - complete(); } @@ -55,6 +55,7 @@ public class GetPeerAddress extends Task { } }); } catch (Throwable t) { + model.offer.setState(Offer.State.FAULT); failed(t); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/ProcessOfferAvailabilityResponse.java index 0fb4d1cc3f..8751f214d8 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -34,8 +34,9 @@ public class ProcessOfferAvailabilityResponse extends Task { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); OfferAvailabilityRequest message = new OfferAvailabilityRequest(model.offer.getId(), model.getPubKeyRing()); model.messageService.sendEncryptedMessage(model.getPeer(), model.offer.getPubKeyRing(), @@ -55,6 +56,7 @@ public class SendOfferAvailabilityRequest extends Task { } }); } catch (Throwable t) { + model.offer.setState(Offer.State.FAULT); failed(t); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java index 25d3d8561c..d5dbb3a6fe 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java @@ -35,7 +35,7 @@ public class PlaceOfferModel implements Model { public final WalletService walletService; public final TradeWalletService tradeWalletService; public final OfferBookService offerBookService; - + public boolean offerAddedToOfferBook; private Transaction transaction; public PlaceOfferModel(Offer offer, diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocol.java index 6e4e7b256a..05e5d61f82 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocol.java @@ -61,6 +61,15 @@ public class PlaceOfferProtocol { }, (errorMessage) -> { log.error(errorMessage); + + if (model.offerAddedToOfferBook) { + model.offerBookService.removeOffer(model.offer, + () -> { + model.offerAddedToOfferBook = false; + log.debug("Offer removed from offer book."); + }, + (message, throwable) -> log.error(message)); + } errorMessageHandler.handleErrorMessage(errorMessage); } ); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/AddOfferToRemoteOfferBook.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/AddOfferToRemoteOfferBook.java index 532afbadc3..c56d66021c 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/AddOfferToRemoteOfferBook.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/AddOfferToRemoteOfferBook.java @@ -33,9 +33,17 @@ public class AddOfferToRemoteOfferBook extends Task { } @Override - protected void doRun() { - model.offerBookService.addOffer(model.offer, - this::complete, - (message, throwable) -> failed(throwable)); + protected void run() { + try { + runInterceptHook(); + model.offerBookService.addOffer(model.offer, + () -> { + model.offerAddedToOfferBook = true; + complete(); + }, + (message, throwable) -> failed(throwable)); + } catch (Throwable t) { + failed(t); + } } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java index dfdbd67e2f..9a4b862dd4 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java @@ -47,58 +47,61 @@ public class BroadcastCreateOfferFeeTx extends Task { } @Override - protected void doRun() { + protected void run() { + try { + runInterceptHook(); + Coin totalsNeeded = model.offer.getSecurityDeposit().add(FeePolicy.CREATE_OFFER_FEE).add(FeePolicy.TX_FEE); + AddressEntry addressEntry = model.walletService.getAddressEntry(model.offer.getId()); + Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress()); + if (balance.compareTo(totalsNeeded) >= 0) { + model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback() { + @Override + public void onSuccess(Transaction transaction) { + log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString()); - Coin totalsNeeded = model.offer.getSecurityDeposit().add(FeePolicy.CREATE_OFFER_FEE).add(FeePolicy.TX_FEE); - AddressEntry addressEntry = model.walletService.getAddressEntry(model.offer.getId()); - Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress()); - if (balance.compareTo(totalsNeeded) >= 0) { - - model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback() { - @Override - public void onSuccess(Transaction transaction) { - log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString()); - - if (model.getTransaction().getHashAsString().equals(transaction.getHashAsString())) { - // No tx malleability happened after broadcast (still not in blockchain) - complete(); + if (model.getTransaction().getHashAsString().equals(transaction.getHashAsString())) { + // No tx malleability happened after broadcast (still not in blockchain) + complete(); + } + else { + log.warn("Tx malleability happened after broadcast. We publish the changed offer to the DHT again."); + // Tx malleability happened after broadcast. We publish the changed offer to the DHT again. + model.offerBookService.removeOffer(model.offer, + () -> { + log.info("We store now the changed txID to the offer and add that again."); + // We store now the changed txID to the offer and add that again. + model.offer.setOfferFeePaymentTxID(transaction.getHashAsString()); + model.offerBookService.addOffer(model.offer, + BroadcastCreateOfferFeeTx.this::complete, + (message, throwable) -> { + log.error("addOffer failed"); + addOfferFailed = true; + failed(throwable); + updateStateOnFault(); + }); + }, + (message, throwable) -> { + log.error("removeOffer failed"); + removeOfferFailed = true; + failed(throwable); + updateStateOnFault(); + }); + } } - else { - log.warn("Tx malleability happened after broadcast. We publish the changed offer to the DHT again."); - // Tx malleability happened after broadcast. We publish the changed offer to the DHT again. - model.offerBookService.removeOffer(model.offer, - () -> { - log.info("We store now the changed txID to the offer and add that again."); - // We store now the changed txID to the offer and add that again. - model.offer.setOfferFeePaymentTxID(transaction.getHashAsString()); - model.offerBookService.addOffer(model.offer, - BroadcastCreateOfferFeeTx.this::complete, - (message, throwable) -> { - log.error("addOffer failed"); - addOfferFailed = true; - failed(throwable); - updateStateOnFault(); - }); - }, - (message, throwable) -> { - log.error("removeOffer failed"); - removeOfferFailed = true; - failed(throwable); - updateStateOnFault(); - }); - } - } - @Override - public void onFailure(@NotNull Throwable t) { - failed(t); - updateStateOnFault(); - } - }); - } - else { - failed("Not enough balance for placing the offer."); - updateStateOnFault(); + @Override + public void onFailure(@NotNull Throwable t) { + failed(t); + updateStateOnFault(); + } + }); + } + else { + failed("Not enough balance for placing the offer."); + updateStateOnFault(); + } + } catch (Throwable t) { + failed(t); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java index ee5d48632c..a943fb1b97 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java @@ -34,8 +34,9 @@ public class CreateOfferFeeTx extends Task { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); Transaction transaction = model.tradeWalletService.createOfferFeeTx( model.walletService.getAddressEntry(model.offer.getId())); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/ValidateOffer.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/ValidateOffer.java index 4e0e306b01..410dacd4fd 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/ValidateOffer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/ValidateOffer.java @@ -32,8 +32,9 @@ public class ValidateOffer extends Task { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); model.offer.validate(); complete(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java index 10c086c3be..f77dc28d50 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java @@ -102,8 +102,8 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc CreateDepositTxInputs.class, SendPayDepositRequest.class ); - taskRunner.run(); startTimeout(); + taskRunner.run(); } @Override diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java index 6d018e10c0..96af08d75b 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java @@ -116,8 +116,8 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol CreateDepositTxInputs.class, SendPayDepositRequest.class ); - taskRunner.run(); startTimeout(); + taskRunner.run(); } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java index 667c61e7c2..1903bb236f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java @@ -100,8 +100,8 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt CreateAndSignDepositTx.class, SendPublishDepositTxRequest.class ); - taskRunner.run(); startTimeout(); + taskRunner.run(); } @Override diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java index b8f442835e..0f979ab20b 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java @@ -128,8 +128,8 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc BroadcastTakeOfferFeeTx.class, SendDepositTxInputsRequest.class ); - taskRunner.run(); startTimeout(); + taskRunner.run(); } @@ -153,8 +153,8 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc CreateAndSignDepositTx.class, SendPublishDepositTxRequest.class ); - taskRunner.run(); startTimeout(); + taskRunner.run(); } private void handle(DepositTxPublishedMessage tradeMessage) { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeProtocol.java index 345e910bfa..76a32b3de2 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeProtocol.java @@ -32,11 +32,9 @@ import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener; import io.bitsquare.trade.states.BuyerTradeState; import io.bitsquare.trade.states.SellerTradeState; - -import org.bitcoinj.utils.Threading; +import io.bitsquare.util.Utilities; import java.util.Timer; -import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -141,28 +139,18 @@ public abstract class TradeProtocol { } protected void startTimeout() { - log.debug("startTimeout"); stopTimeout(); - timeoutTimer = new Timer(); - TimerTask task = new TimerTask() { - @Override - public void run() { - Threading.USER_THREAD.execute(() -> { - log.debug("Timeout reached"); - /* if (trade instanceof SellerTrade) - trade.setProcessState(SellerTradeState.ProcessState.TIMEOUT); - else if (trade instanceof BuyerTrade) - trade.setProcessState(BuyerTradeState.ProcessState.TIMEOUT);*/ - }); - } - }; - - timeoutTimer.schedule(task, TIMEOUT); + timeoutTimer = Utilities.setTimeout(TIMEOUT, () -> { + log.debug("Timeout reached"); + if (trade instanceof SellerTrade) + trade.setProcessState(SellerTradeState.ProcessState.TIMEOUT); + else if (trade instanceof BuyerTrade) + trade.setProcessState(BuyerTradeState.ProcessState.TIMEOUT); + }); } protected void stopTimeout() { - log.debug("stopTimeout"); if (timeoutTimer != null) { timeoutTimer.cancel(); timeoutTimer = null; diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeTask.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeTask.java index c0f15efb17..8e578ee2b2 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeTask.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradeTask.java @@ -38,6 +38,16 @@ public class TradeTask extends Task { } @Override - protected void doRun() { + protected void run() { + } + + @Override + protected void failed(Throwable t) { + t.printStackTrace(); + appendExceptionToErrorMessage(t); + trade.setThrowable(t); + trade.setErrorMessage(errorMessage); + trade.setFaultState(); + failed(); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputs.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputs.java index 4f63c52b6c..216570f5eb 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputs.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputs.java @@ -37,8 +37,9 @@ public class CreateDepositTxInputs extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); log.debug("trade.id" + trade.getId()); Coin inputAmount = trade.getSecurityDeposit().add(FeePolicy.TX_FEE); TradeWalletService.Result result = processModel.getTradeWalletService().createDepositTxInputs(inputAmount, diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessDepositTxInputsRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessDepositTxInputsRequest.java index 8fa7c58759..386d78a0e7 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessDepositTxInputsRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessDepositTxInputsRequest.java @@ -37,8 +37,9 @@ public class ProcessDepositTxInputsRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); DepositTxInputsRequest message = (DepositTxInputsRequest) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessFinalizePayoutTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessFinalizePayoutTxRequest.java index 9372cda028..d7a1c337e5 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessFinalizePayoutTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessFinalizePayoutTxRequest.java @@ -38,8 +38,9 @@ public class ProcessFinalizePayoutTxRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); FinalizePayoutTxRequest message = (FinalizePayoutTxRequest) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessPublishDepositTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessPublishDepositTxRequest.java index f03170e9c6..b3998f6929 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessPublishDepositTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/ProcessPublishDepositTxRequest.java @@ -37,8 +37,9 @@ public class ProcessPublishDepositTxRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); PublishDepositTxRequest message = (PublishDepositTxRequest) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendDepositTxPublishedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendDepositTxPublishedMessage.java index 136db8ab5f..4a88636475 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendDepositTxPublishedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendDepositTxPublishedMessage.java @@ -36,8 +36,9 @@ public class SendDepositTxPublishedMessage extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); DepositTxPublishedMessage tradeMessage = new DepositTxPublishedMessage(processModel.getId(), trade.getDepositTx()); processModel.getMessageService().sendEncryptedMessage( diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java index bc3048e97e..df46aa6c2a 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java @@ -36,8 +36,9 @@ public class SendFiatTransferStartedMessage extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); FiatTransferStartedMessage tradeMessage = new FiatTransferStartedMessage(processModel.getId(), processModel.getAddressEntry().getAddressString() ); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayDepositRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayDepositRequest.java index 30408e3209..18b78358a0 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayDepositRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayDepositRequest.java @@ -36,8 +36,9 @@ public class SendPayDepositRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); boolean isInitialRequest = trade instanceof BuyerAsTakerTrade; PayDepositRequest tradeMessage = new PayDepositRequest( processModel.getId(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayoutTxFinalizedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayoutTxFinalizedMessage.java index 6f0afc3abf..fac32d8ff1 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayoutTxFinalizedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendPayoutTxFinalizedMessage.java @@ -36,8 +36,9 @@ public class SendPayoutTxFinalizedMessage extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); PayoutTxFinalizedMessage tradeMessage = new PayoutTxFinalizedMessage(processModel.getId(), trade.getPayoutTx()); processModel.getMessageService().sendEncryptedMessage( trade.getTradingPeer(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java index 9299c6480c..96547a9e7d 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java @@ -35,8 +35,9 @@ public class SignAndFinalizePayoutTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); assert trade.getTradeAmount() != null; assert trade.getSecurityDeposit() != null; Coin sellerPayoutAmount = trade.getSecurityDeposit(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTx.java index e8aabaa808..75d623d23c 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTx.java @@ -44,8 +44,9 @@ public class SignAndPublishDepositTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); Coin inputAmount = trade.getSecurityDeposit().add(FeePolicy.TX_FEE); processModel.getTradeWalletService().signAndPublishDepositTx( diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/VerifyAndSignContract.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/VerifyAndSignContract.java index 400719068e..85e234bf49 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/VerifyAndSignContract.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/VerifyAndSignContract.java @@ -35,8 +35,9 @@ public class VerifyAndSignContract extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); Contract contract = new Contract( processModel.getOffer(), trade.getTradeAmount(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakeOfferFeePayment.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakeOfferFeePayment.java index a617fe2d90..82c8a773d6 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakeOfferFeePayment.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakeOfferFeePayment.java @@ -32,8 +32,9 @@ public class VerifyTakeOfferFeePayment extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); //TODO mocked yet, need a confidence listeners int numOfPeersSeenTx = processModel.getWalletService().getNumOfPeersSeenTx(processModel.getTakeOfferFeeTxId()); /* if (numOfPeersSeenTx > 2) { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakerAccount.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakerAccount.java index 8c6c8b3b63..858a8d0a91 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakerAccount.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/VerifyTakerAccount.java @@ -33,8 +33,9 @@ public class VerifyTakerAccount extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); //TODO mocked yet if (processModel.getBlockChainService().verifyAccountRegistration()) { if (processModel.getBlockChainService().isAccountBlackListed(processModel.tradingPeer.getAccountId(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CommitDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CommitDepositTx.java index 602475830a..2f81c4de8b 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CommitDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CommitDepositTx.java @@ -34,8 +34,9 @@ public class CommitDepositTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); // To access tx confidence we need to add that tx into our wallet. Transaction depositTx = processModel.getTradeWalletService().commitTx(trade.getDepositTx()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignContract.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignContract.java index 4f18737230..a3c5fe27b9 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignContract.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignContract.java @@ -34,8 +34,9 @@ public class CreateAndSignContract extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); assert processModel.getTakeOfferFeeTxId() != null; Contract contract = new Contract( processModel.getOffer(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTx.java index b07980d5df..7d7999712e 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTx.java @@ -36,8 +36,9 @@ public class CreateAndSignDepositTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); assert trade.getTradeAmount() != null; Coin inputAmount = trade.getSecurityDeposit().add(FeePolicy.TX_FEE).add(trade.getTradeAmount()); Coin msOutputAmount = inputAmount.add(trade.getSecurityDeposit()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessDepositTxPublishedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessDepositTxPublishedMessage.java index 119158b021..68d6c37739 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessDepositTxPublishedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessDepositTxPublishedMessage.java @@ -37,8 +37,9 @@ public class ProcessDepositTxPublishedMessage extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); DepositTxPublishedMessage message = (DepositTxPublishedMessage) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessFiatTransferStartedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessFiatTransferStartedMessage.java index 940cb1cfcb..c09c2b5a99 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessFiatTransferStartedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessFiatTransferStartedMessage.java @@ -37,8 +37,9 @@ public class ProcessFiatTransferStartedMessage extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); FiatTransferStartedMessage message = (FiatTransferStartedMessage) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayDepositRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayDepositRequest.java index acfebc8909..0ba06beb45 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayDepositRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayDepositRequest.java @@ -36,8 +36,9 @@ public class ProcessPayDepositRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); PayDepositRequest message = (PayDepositRequest) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayoutTxFinalizedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayoutTxFinalizedMessage.java index 5605d76a43..9b80dc7931 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayoutTxFinalizedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/ProcessPayoutTxFinalizedMessage.java @@ -37,8 +37,9 @@ public class ProcessPayoutTxFinalizedMessage extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); PayoutTxFinalizedMessage message = (PayoutTxFinalizedMessage) processModel.getTradeMessage(); checkTradeId(processModel.getId(), message); checkNotNull(message); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendDepositTxInputsRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendDepositTxInputsRequest.java index 17ea6124c8..ebc63df16c 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendDepositTxInputsRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendDepositTxInputsRequest.java @@ -39,8 +39,9 @@ public class SendDepositTxInputsRequest extends TradeTask { private int retryCounter = 0; @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); assert processModel.getTakeOfferFeeTx() != null; DepositTxInputsRequest message = new DepositTxInputsRequest( processModel.getId(), @@ -67,7 +68,7 @@ public class SendDepositTxInputsRequest extends TradeTask { // We try to repeat once and if that fails as well we persist the state for a later retry. if (retryCounter == 0) { retryCounter++; - Threading.USER_THREAD.execute(SendDepositTxInputsRequest.this::doRun); + Threading.USER_THREAD.execute(SendDepositTxInputsRequest.this::run); } else { appendToErrorMessage("Sending TakeOfferFeePayedMessage to offerer failed. Maybe the network connection was " + diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java index 0d3538fb4a..824c1b398f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java @@ -36,8 +36,9 @@ public class SendFinalizePayoutTxRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); FinalizePayoutTxRequest message = new FinalizePayoutTxRequest( processModel.getId(), processModel.getPayoutTxSignature(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendPublishDepositTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendPublishDepositTxRequest.java index bf8493cb18..fe23c7302c 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendPublishDepositTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendPublishDepositTxRequest.java @@ -35,8 +35,9 @@ public class SendPublishDepositTxRequest extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); PublishDepositTxRequest tradeMessage = new PublishDepositTxRequest( processModel.getId(), processModel.getFiatAccount(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java index b68d059fd7..c07486262b 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java @@ -34,8 +34,9 @@ public class SignPayoutTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); assert trade.getTradeAmount() != null; assert trade.getSecurityDeposit() != null; Coin sellerPayoutAmount = trade.getSecurityDeposit(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/CommitPayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/CommitPayoutTx.java index a019000540..2ca925f6ab 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/CommitPayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/CommitPayoutTx.java @@ -38,8 +38,9 @@ public class CommitPayoutTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); Transaction transaction = processModel.getTradeWalletService().commitTx(trade.getPayoutTx()); trade.setPayoutTx(transaction); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/SetupPayoutTxLockTimeReachedListener.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/SetupPayoutTxLockTimeReachedListener.java index 4a1467be77..19d4683d36 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/SetupPayoutTxLockTimeReachedListener.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/shared/SetupPayoutTxLockTimeReachedListener.java @@ -47,8 +47,9 @@ public class SetupPayoutTxLockTimeReachedListener extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); log.debug("ChainHeight/LockTime: {} / {}", processModel.getTradeWalletService().getBestChainHeight(), trade.getLockTime()); if (processModel.getTradeWalletService().getBestChainHeight() >= trade.getLockTime()) { broadcastTx(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/BroadcastTakeOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/BroadcastTakeOfferFeeTx.java index 156ff59306..5cba106298 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/BroadcastTakeOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/BroadcastTakeOfferFeeTx.java @@ -38,35 +38,25 @@ public class BroadcastTakeOfferFeeTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); processModel.getTradeWalletService().broadcastTx(processModel.getTakeOfferFeeTx(), new FutureCallback() { @Override public void onSuccess(Transaction transaction) { log.debug("Take offer fee published successfully. Transaction ID = " + transaction.getHashAsString()); - /* if (trade instanceof SellerTrade) - trade.setProcessState(TakerTradeState.ProcessState.TAKE_OFFER_FEE_PUBLISHED);*/ - complete(); } @Override public void onFailure(@NotNull Throwable t) { - t.printStackTrace(); appendToErrorMessage("Take offer fee payment failed. Maybe your network connection was lost. Please try again."); - trade.setErrorMessage(errorMessage); - - /* if (trade instanceof SellerTrade) - trade.setProcessState(TakerTradeState.ProcessState.TAKE_OFFER_FEE_PUBLISH_FAILED);*/ - failed(t); } }); } catch (Throwable t) { - t.printStackTrace(); - trade.setThrowable(t); failed(t); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java index 5c3e9159f2..49e3e2b1b0 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java @@ -34,20 +34,16 @@ public class CreateTakeOfferFeeTx extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTakeOfferFeeTx(processModel.getAddressEntry()); processModel.setTakeOfferFeeTx(createTakeOfferFeeTx); processModel.setTakeOfferFeeTxId(createTakeOfferFeeTx.getHashAsString()); - /*if (trade instanceof SellerTrade) - trade.setProcessState(TakerTradeState.ProcessState.TAKE_OFFER_FEE_TX_CREATED);*/ - complete(); } catch (Throwable t) { - t.printStackTrace(); - trade.setThrowable(t); failed(t); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOfferFeePayment.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOfferFeePayment.java index 25f148f3f8..d6d61700a3 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOfferFeePayment.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOfferFeePayment.java @@ -32,8 +32,9 @@ public class VerifyOfferFeePayment extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); //TODO impl. missing int numOfPeersSeenTx = processModel.getWalletService().getNumOfPeersSeenTx(processModel.getTakeOfferFeeTx().getHashAsString()); /* if (numOfPeersSeenTx > 2) { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOffererAccount.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOffererAccount.java index cad88a4a3b..3db04d51fa 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOffererAccount.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyOffererAccount.java @@ -32,8 +32,9 @@ public class VerifyOffererAccount extends TradeTask { } @Override - protected void doRun() { + protected void run() { try { + runInterceptHook(); if (processModel.getBlockChainService().verifyAccountRegistration()) { if (processModel.getBlockChainService().isAccountBlackListed(processModel.tradingPeer.getAccountId(), processModel.tradingPeer.getFiatAccount())) { diff --git a/core/src/main/java/io/bitsquare/trade/states/BuyerTradeState.java b/core/src/main/java/io/bitsquare/trade/states/BuyerTradeState.java index a2c49eb472..d314cca9a8 100644 --- a/core/src/main/java/io/bitsquare/trade/states/BuyerTradeState.java +++ b/core/src/main/java/io/bitsquare/trade/states/BuyerTradeState.java @@ -38,6 +38,9 @@ public class BuyerTradeState { PAYOUT_TX_COMMITTED, PAYOUT_TX_SENT, - PAYOUT_BROAD_CASTED + PAYOUT_BROAD_CASTED, + + TIMEOUT, + FAULT } } diff --git a/core/src/main/java/io/bitsquare/trade/states/SellerTradeState.java b/core/src/main/java/io/bitsquare/trade/states/SellerTradeState.java index 093df88b65..aa4b31d74e 100644 --- a/core/src/main/java/io/bitsquare/trade/states/SellerTradeState.java +++ b/core/src/main/java/io/bitsquare/trade/states/SellerTradeState.java @@ -37,6 +37,9 @@ public class SellerTradeState { PAYOUT_TX_RECEIVED, PAYOUT_TX_COMMITTED, - PAYOUT_BROAD_CASTED + PAYOUT_BROAD_CASTED, + + TIMEOUT, + FAULT } } diff --git a/core/src/main/java/io/bitsquare/trade/states/TradePhase.java b/core/src/main/java/io/bitsquare/trade/states/TradePhase.java new file mode 100644 index 0000000000..6e155c7736 --- /dev/null +++ b/core/src/main/java/io/bitsquare/trade/states/TradePhase.java @@ -0,0 +1,26 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.trade.states; + +public enum TradePhase { + PREPARATION, // No damage + TAKE_OFFER__FEE_PAID, // Offer fee can be lost + DEPOSIT_BROAD_CASTED, // Need arbitrator + PAYOUT_BROAD_CASTED // Only charge back risk open + +} diff --git a/core/src/main/java/io/bitsquare/user/Preferences.java b/core/src/main/java/io/bitsquare/user/Preferences.java index c87e4201c0..811482bbb7 100644 --- a/core/src/main/java/io/bitsquare/user/Preferences.java +++ b/core/src/main/java/io/bitsquare/user/Preferences.java @@ -54,9 +54,9 @@ public class Preferences implements Serializable { // Persisted fields private String btcDenomination = MonetaryFormat.CODE_BTC; - private Boolean useAnimations = true; - private Boolean useEffects = true; - private Boolean displaySecurityDepositInfo = true; + private boolean useAnimations = true; + private boolean useEffects = true; + private boolean displaySecurityDepositInfo = true; // Observable wrappers transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination); @@ -111,7 +111,7 @@ public class Preferences implements Serializable { this.useEffectsProperty.set(useEffectsProperty); } - public void setDisplaySecurityDepositInfo(Boolean displaySecurityDepositInfo) { + public void setDisplaySecurityDepositInfo(boolean displaySecurityDepositInfo) { this.displaySecurityDepositInfo = displaySecurityDepositInfo; storage.queueUpForSave(); } @@ -133,7 +133,7 @@ public class Preferences implements Serializable { return useAnimationsProperty.get(); } - public Boolean getDisplaySecurityDepositInfo() { + public boolean getDisplaySecurityDepositInfo() { return displaySecurityDepositInfo; } diff --git a/core/src/main/java/io/bitsquare/util/Utilities.java b/core/src/main/java/io/bitsquare/util/Utilities.java index f2f2baa97b..84beae9c52 100644 --- a/core/src/main/java/io/bitsquare/util/Utilities.java +++ b/core/src/main/java/io/bitsquare/util/Utilities.java @@ -17,6 +17,10 @@ package io.bitsquare.util; +import io.bitsquare.common.handlers.ResultHandler; + +import org.bitcoinj.utils.Threading; + import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -36,6 +40,9 @@ import java.io.Serializable; import java.net.URI; +import java.util.Timer; +import java.util.TimerTask; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +54,31 @@ public class Utilities { private static final Logger log = LoggerFactory.getLogger(Utilities.class); private static long lastTimeStamp = System.currentTimeMillis(); + + public static Timer setTimeout(long delay, ResultHandler handler) { + Timer timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + Threading.USER_THREAD.execute(() -> handler.handleResult()); + } + }; + timer.schedule(task, delay); + return timer; + } + + public static Timer setInterval(long delay, ResultHandler handler) { + Timer timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + Threading.USER_THREAD.execute(() -> handler.handleResult()); + } + }; + timer.scheduleAtFixedRate(task, delay, delay); + return timer; + } + public static String objectToJson(Object object) { Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting().create(); diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index 1fd054f0b4..9bc2ffef68 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -158,7 +158,7 @@ public class BitsquareApp extends Application { primaryStage.show(); //TODO just temp. - //showDebugWindow(); + // showDebugWindow(); } catch (Throwable throwable) { showErrorPopup(throwable, true); } @@ -174,9 +174,8 @@ public class BitsquareApp extends Application { throwable.printStackTrace(); Dialogs.create() .owner(primaryStage) - .title("") - .message("") - .masthead("") + .title("Error") + .message("A fatal exception occurred at startup.") .showException(throwable); if (doShutDown) stop(); diff --git a/gui/src/main/java/io/bitsquare/app/UpdateProcess.java b/gui/src/main/java/io/bitsquare/app/UpdateProcess.java index 5fe86a509e..b2c2b357f0 100644 --- a/gui/src/main/java/io/bitsquare/app/UpdateProcess.java +++ b/gui/src/main/java/io/bitsquare/app/UpdateProcess.java @@ -17,7 +17,7 @@ package io.bitsquare.app; -import io.bitsquare.gui.util.GUIUtil; +import io.bitsquare.util.Utilities; import com.google.inject.Inject; @@ -26,8 +26,8 @@ import java.io.File; import java.nio.file.Path; import java.util.List; +import java.util.Timer; -import javafx.animation.AnimationTimer; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -65,7 +65,7 @@ public class UpdateProcess { protected String errorMessage; protected final Subject process = BehaviorSubject.create(); - protected AnimationTimer timeoutTimer; + protected Timer timeoutTimer; @Inject public UpdateProcess(Environment environment) { @@ -88,11 +88,7 @@ public class UpdateProcess { log.info("UpdateFX current version " + Version.PATCH_VERSION); // process.timeout() will cause an error state back but we don't want to break startup in case of an timeout - timeoutTimer = GUIUtil.setTimeout(10000, animationTimer -> { - process.onCompleted(); - return null; - }); - timeoutTimer.start(); + timeoutTimer = Utilities.setTimeout(10000, () -> process.onCompleted()); String agent = environment.getProperty(BitsquareEnvironment.APP_NAME_KEY) + Version.VERSION; Path dataDirPath = new File(environment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY)).toPath(); @@ -123,12 +119,12 @@ public class UpdateProcess { state.set(State.UPDATE_AVAILABLE); // We stop the timeout and treat it not completed. // The user should click the restart button manually if there are updates available. - timeoutTimer.stop(); + timeoutTimer.cancel(); } else if (summary.highestVersion == Version.PATCH_VERSION) { log.info("UP_TO_DATE"); state.set(State.UP_TO_DATE); - timeoutTimer.stop(); + timeoutTimer.cancel(); process.onCompleted(); } } catch (Throwable e) { @@ -138,7 +134,7 @@ public class UpdateProcess { // so we use state.onCompleted() instead of state.onError() errorMessage = "Exception at processing UpdateSummary: " + e.getMessage(); state.set(State.FAILURE); - timeoutTimer.stop(); + timeoutTimer.cancel(); process.onCompleted(); } }); @@ -150,7 +146,7 @@ public class UpdateProcess { // so we use state.onCompleted() instead of state.onError() errorMessage = "Update failed: " + updater.getException(); state.set(State.FAILURE); - timeoutTimer.stop(); + timeoutTimer.cancel(); process.onCompleted(); }); diff --git a/gui/src/main/java/io/bitsquare/gui/components/InputTextField.java b/gui/src/main/java/io/bitsquare/gui/components/InputTextField.java index 068921996d..ea1757fd13 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/InputTextField.java +++ b/gui/src/main/java/io/bitsquare/gui/components/InputTextField.java @@ -64,6 +64,7 @@ public class InputTextField extends TextField { private InputValidator validator; + /////////////////////////////////////////////////////////////////////////////////////////// // Static /////////////////////////////////////////////////////////////////////////////////////////// @@ -72,6 +73,8 @@ public class InputTextField extends TextField { if (errorMessageDisplay != null) errorMessageDisplay.hide(); } + + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// 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 b20782363a..a1e7354911 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -34,13 +34,11 @@ import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.user.User; - -import org.bitcoinj.utils.Threading; +import io.bitsquare.util.Utilities; import com.google.inject.Inject; import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.TimeoutException; import javafx.application.Platform; @@ -345,7 +343,7 @@ class MainViewModel implements ViewModel { if (numPendingTrades > 0) numPendingTradesAsString.set(String.valueOf(numPendingTrades)); if (numPendingTrades > 9) - numPendingTradesAsString.set("*"); + numPendingTradesAsString.set("-"); showPendingTradesNotification.set(numPendingTrades > 0); } @@ -371,18 +369,11 @@ class MainViewModel implements ViewModel { log.trace("startBlockchainSyncTimeout"); stopBlockchainSyncTimeout(); - blockchainSyncTimeoutTimer = new Timer(); - TimerTask task = new TimerTask() { - @Override - public void run() { - Threading.USER_THREAD.execute(() -> { - log.trace("Timeout reached"); - Platform.runLater(() -> setWalletServiceException(new TimeoutException())); - }); - } - }; - blockchainSyncTimeoutTimer.schedule(task, BLOCKCHAIN_SYNC_TIMEOUT); + blockchainSyncTimeoutTimer = Utilities.setTimeout(BLOCKCHAIN_SYNC_TIMEOUT, () -> { + log.trace("Timeout reached"); + setWalletServiceException(new TimeoutException()); + }); } private void stopBlockchainSyncTimeout() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.fxml b/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.fxml index 9413d04525..4aa32bbf8a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.fxml @@ -44,39 +44,11 @@ - - - - - - - - - - - - - - - - - diff --git a/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.java b/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.java index e1ad27932e..4a407fd46d 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/debug/DebugView.java @@ -21,6 +21,7 @@ import io.bitsquare.common.taskrunner.Task; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.InitializableView; import io.bitsquare.trade.protocol.availability.OfferAvailabilityProtocol; +import io.bitsquare.trade.protocol.availability.tasks.GetPeerAddress; import io.bitsquare.trade.protocol.availability.tasks.ProcessOfferAvailabilityResponse; import io.bitsquare.trade.protocol.availability.tasks.SendOfferAvailabilityRequest; import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol; @@ -32,6 +33,7 @@ import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol; import io.bitsquare.trade.protocol.trade.SellerAsTakerProtocol; import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateDepositTxInputs; import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessDepositTxInputsRequest; +import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessFinalizePayoutTxRequest; import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPublishDepositTxRequest; import io.bitsquare.trade.protocol.trade.tasks.buyer.SendDepositTxPublishedMessage; import io.bitsquare.trade.protocol.trade.tasks.buyer.SendFiatTransferStartedMessage; @@ -39,16 +41,23 @@ import io.bitsquare.trade.protocol.trade.tasks.buyer.SendPayDepositRequest; import io.bitsquare.trade.protocol.trade.tasks.buyer.SendPayoutTxFinalizedMessage; import io.bitsquare.trade.protocol.trade.tasks.buyer.SignAndFinalizePayoutTx; import io.bitsquare.trade.protocol.trade.tasks.buyer.SignAndPublishDepositTx; +import io.bitsquare.trade.protocol.trade.tasks.buyer.VerifyAndSignContract; import io.bitsquare.trade.protocol.trade.tasks.offerer.VerifyTakeOfferFeePayment; import io.bitsquare.trade.protocol.trade.tasks.offerer.VerifyTakerAccount; import io.bitsquare.trade.protocol.trade.tasks.seller.CommitDepositTx; +import io.bitsquare.trade.protocol.trade.tasks.seller.CreateAndSignContract; import io.bitsquare.trade.protocol.trade.tasks.seller.CreateAndSignDepositTx; import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessDepositTxPublishedMessage; import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessFiatTransferStartedMessage; import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessPayDepositRequest; import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessPayoutTxFinalizedMessage; import io.bitsquare.trade.protocol.trade.tasks.seller.SendDepositTxInputsRequest; +import io.bitsquare.trade.protocol.trade.tasks.seller.SendFinalizePayoutTxRequest; +import io.bitsquare.trade.protocol.trade.tasks.seller.SendPublishDepositTxRequest; import io.bitsquare.trade.protocol.trade.tasks.seller.SignPayoutTx; +import io.bitsquare.trade.protocol.trade.tasks.shared.CommitPayoutTx; +import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener; +import io.bitsquare.trade.protocol.trade.tasks.taker.BroadcastTakeOfferFeeTx; import io.bitsquare.trade.protocol.trade.tasks.taker.CreateTakeOfferFeeTx; import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOfferFeePayment; import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOffererAccount; @@ -68,7 +77,6 @@ public class DebugView extends InitializableView { @FXML ComboBox taskComboBox; - @FXML CheckBox interceptBeforeCheckBox; @Inject public DebugView() { @@ -76,12 +84,10 @@ public class DebugView extends InitializableView { @Override public void initialize() { - interceptBeforeCheckBox.setSelected(true); - final ObservableList items = FXCollections.observableArrayList(Arrays.asList( /*---- Protocol ----*/ OfferAvailabilityProtocol.class, - io.bitsquare.trade.protocol.availability.tasks.GetPeerAddress.class, + GetPeerAddress.class, SendOfferAvailabilityRequest.class, ProcessOfferAvailabilityResponse.class, Boolean.class, /* used as seperator*/ @@ -104,34 +110,45 @@ public class DebugView extends InitializableView { ProcessPublishDepositTxRequest.class, VerifyTakerAccount.class, + VerifyAndSignContract.class, SignAndPublishDepositTx.class, SendDepositTxPublishedMessage.class, - SignPayoutTx.class, VerifyTakeOfferFeePayment.class, SendFiatTransferStartedMessage.class, - ProcessPayoutTxFinalizedMessage.class, + ProcessFinalizePayoutTxRequest.class, + SignAndFinalizePayoutTx.class, + CommitPayoutTx.class, + SendPayoutTxFinalizedMessage.class, + SetupPayoutTxLockTimeReachedListener.class, Boolean.class, /* used as seperator*/ /*---- Protocol ----*/ SellerAsTakerProtocol.class, CreateTakeOfferFeeTx.class, + BroadcastTakeOfferFeeTx.class, SendDepositTxInputsRequest.class, ProcessPayDepositRequest.class, VerifyOffererAccount.class, + CreateAndSignContract.class, CreateAndSignDepositTx.class, + SendPublishDepositTxRequest.class, ProcessDepositTxPublishedMessage.class, CommitDepositTx.class, ProcessFiatTransferStartedMessage.class, - SignAndFinalizePayoutTx.class, VerifyOfferFeePayment.class, - SendPayoutTxFinalizedMessage.class + SignPayoutTx.class, + SendFinalizePayoutTxRequest.class, + + ProcessPayoutTxFinalizedMessage.class, + CommitPayoutTx.class, + SetupPayoutTxLockTimeReachedListener.class ) ); @@ -160,20 +177,8 @@ public class DebugView extends InitializableView { void onSelectTask() { Class item = taskComboBox.getSelectionModel().getSelectedItem(); if (!item.getSimpleName().contains("Protocol")) { - if (interceptBeforeCheckBox.isSelected()) { - Task.taskToInterceptBeforeRun = item; - Task.taskToInterceptAfterRun = null; - } - else { - Task.taskToInterceptAfterRun = item; - Task.taskToInterceptBeforeRun = null; - } + Task.taskToIntercept = item; } } - - @FXML - void onCheckBoxChanged() { - onSelectTask(); - } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index 46e82a2dbf..1ed23507bc 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -18,7 +18,6 @@ package io.bitsquare.gui.main.offer.createoffer; import io.bitsquare.arbitration.Arbitrator; -import io.bitsquare.arbitration.ArbitratorService; import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.WalletService; @@ -48,17 +47,15 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.google.common.base.Preconditions.checkNotNull; - /** * Domain for that UI element. * Note that the create offer domain has a deeper scope in the application domain (TradeManager). @@ -67,16 +64,22 @@ import static com.google.common.base.Preconditions.checkNotNull; class CreateOfferDataModel implements Activatable, DataModel { private static final Logger log = LoggerFactory.getLogger(CreateOfferDataModel.class); - private OpenOfferManager openOfferManager; + + private final OpenOfferManager openOfferManager; private final WalletService walletService; private final AccountSettings accountSettings; private final Preferences preferences; + private final User user; private final BSFormatter formatter; - private final String offerId; + private final AddressEntry addressEntry; + final ObjectProperty offerFeeAsCoin = new SimpleObjectProperty<>(); + final ObjectProperty networkFeeAsCoin = new SimpleObjectProperty<>(); + final ObjectProperty securityDepositAsCoin = new SimpleObjectProperty<>(); + private final BalanceListener balanceListener; + private final ChangeListener currentFiatAccountListener; private Offer.Direction direction; - private AddressEntry addressEntry; final StringProperty requestPlaceOfferErrorMessage = new SimpleStringProperty(); final StringProperty transactionId = new SimpleStringProperty(); @@ -95,57 +98,53 @@ class CreateOfferDataModel implements Activatable, DataModel { final ObjectProperty priceAsFiat = new SimpleObjectProperty<>(); final ObjectProperty volumeAsFiat = new SimpleObjectProperty<>(); final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); - final ObjectProperty offerFeeAsCoin = new SimpleObjectProperty<>(); - final ObjectProperty networkFeeAsCoin = new SimpleObjectProperty<>(); - final ObjectProperty securityDepositAsCoin = new SimpleObjectProperty<>(); final ObservableList acceptedCountries = FXCollections.observableArrayList(); final ObservableList acceptedLanguageCodes = FXCollections.observableArrayList(); final ObservableList acceptedArbitrators = FXCollections.observableArrayList(); + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + // non private for testing @Inject - public CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, ArbitratorService arbitratorService, + public CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, AccountSettings accountSettings, Preferences preferences, User user, BSFormatter formatter) { this.openOfferManager = openOfferManager; this.walletService = walletService; this.accountSettings = accountSettings; this.preferences = preferences; + this.user = user; this.formatter = formatter; this.offerId = UUID.randomUUID().toString(); + addressEntry = walletService.getAddressEntry(offerId); + offerFeeAsCoin.set(FeePolicy.CREATE_OFFER_FEE); networkFeeAsCoin.set(FeePolicy.TX_FEE); - if (walletService != null && walletService.getWallet() != null) { - addressEntry = walletService.getAddressEntry(offerId); + // we need to set it here already as it is used before activate + securityDepositAsCoin.set(accountSettings.getSecurityDeposit()); - walletService.addBalanceListener(new BalanceListener(getAddressEntry().getAddress()) { - @Override - public void onBalanceChanged(@NotNull Coin balance) { - updateBalance(balance); - } - }); - updateBalance(walletService.getBalanceForAddress(getAddressEntry().getAddress())); - } + balanceListener = new BalanceListener(getAddressEntry().getAddress()) { + @Override + public void onBalanceChanged(@NotNull Coin balance) { + updateBalance(balance); + } + }; - if (user != null) { - user.currentFiatAccountProperty().addListener((ov, oldValue, newValue) -> applyBankAccount(newValue)); - - applyBankAccount(user.currentFiatAccountProperty().get()); - } - - if (accountSettings != null) - btcCode.bind(preferences.btcDenominationProperty()); - - // we need to set it here already as initWithData is called before activate - if (accountSettings != null) - securityDepositAsCoin.set(accountSettings.getSecurityDeposit()); + currentFiatAccountListener = (observable, oldValue, newValue) -> { + applyBankAccount(newValue); + }; } @Override public void activate() { + addBindings(); + addListeners(); + // might be changed after screen change if (accountSettings != null) { // set it here again to cover the case of an securityDeposit change after a screen change @@ -155,13 +154,50 @@ class CreateOfferDataModel implements Activatable, DataModel { acceptedLanguageCodes.setAll(accountSettings.getAcceptedLanguageLocaleCodes()); acceptedArbitrators.setAll(accountSettings.getAcceptedArbitrators()); } + + updateBalance(walletService.getBalanceForAddress(getAddressEntry().getAddress())); + applyBankAccount(user.currentFiatAccountProperty().get()); } @Override public void deactivate() { - // no-op + removeBindings(); + removeListeners(); } + private void addBindings() { + btcCode.bind(preferences.btcDenominationProperty()); + } + + private void removeBindings() { + btcCode.unbind(); + } + + private void addListeners() { + walletService.addBalanceListener(balanceListener); + user.currentFiatAccountProperty().addListener(currentFiatAccountListener); + + } + + private void removeListeners() { + walletService.removeBalanceListener(balanceListener); + user.currentFiatAccountProperty().removeListener(currentFiatAccountListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + void initWithData(Offer.Direction direction) { + this.direction = direction; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + void onPlaceOffer() { // data validation is done in the trade domain openOfferManager.onPlaceOffer(offerId, @@ -177,47 +213,14 @@ class CreateOfferDataModel implements Activatable, DataModel { ); } - void calculateVolume() { - try { - if (priceAsFiat.get() != null && - amountAsCoin.get() != null && - !amountAsCoin.get().isZero() && - !priceAsFiat.get().isZero()) { - volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); - } - } catch (Throwable t) { - // Should be never reached - log.error(t.toString()); - } + void onSecurityDepositInfoDisplayed() { + preferences.setDisplaySecurityDepositInfo(false); } - void calculateAmount() { - try { - if (volumeAsFiat.get() != null && - priceAsFiat.get() != null && - !volumeAsFiat.get().isZero() && - !priceAsFiat.get().isZero()) { - // If we got a btc value with more then 4 decimals we convert it to max 4 decimals - amountAsCoin.set(formatter.reduceTo4Decimals(new ExchangeRate(priceAsFiat.get()).fiatToCoin - (volumeAsFiat.get()))); - - calculateTotalToPay(); - } - } catch (Throwable t) { - // Should be never reached - log.error(t.toString()); - } - } - - void calculateTotalToPay() { - if (securityDepositAsCoin.get() != null) { - if (direction == Offer.Direction.BUY) - totalToPayAsCoin.set(offerFeeAsCoin.get().add(networkFeeAsCoin.get()).add(securityDepositAsCoin.get())); - else - totalToPayAsCoin.set(offerFeeAsCoin.get().add(networkFeeAsCoin.get()).add(securityDepositAsCoin.get()).add(amountAsCoin.get())); - } - } + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("BooleanMethodIsAlwaysInverted") boolean isMinAmountLessOrEqualAmount() { @@ -227,12 +230,6 @@ class CreateOfferDataModel implements Activatable, DataModel { return true; } - void securityDepositInfoDisplayed() { - preferences.setDisplaySecurityDepositInfo(false); - } - - - @Nullable Offer.Direction getDirection() { return direction; } @@ -245,12 +242,51 @@ class CreateOfferDataModel implements Activatable, DataModel { return offerId; } - private void updateBalance(@NotNull Coin balance) { - isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); + AddressEntry getAddressEntry() { + return addressEntry; } - public AddressEntry getAddressEntry() { - return addressEntry; + boolean getDisplaySecurityDepositInfo() { + return preferences.getDisplaySecurityDepositInfo(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + void calculateVolume() { + if (priceAsFiat.get() != null && + amountAsCoin.get() != null && + !amountAsCoin.get().isZero() && + !priceAsFiat.get().isZero()) { + volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); + } + } + + void calculateAmount() { + if (volumeAsFiat.get() != null && + priceAsFiat.get() != null && + !volumeAsFiat.get().isZero() && + !priceAsFiat.get().isZero()) { + // If we got a btc value with more then 4 decimals we convert it to max 4 decimals + amountAsCoin.set(formatter.reduceTo4Decimals(new ExchangeRate(priceAsFiat.get()).fiatToCoin(volumeAsFiat.get()))); + + calculateTotalToPay(); + } + } + + void calculateTotalToPay() { + if (securityDepositAsCoin.get() != null) { + if (direction == Offer.Direction.BUY) + totalToPayAsCoin.set(offerFeeAsCoin.get().add(networkFeeAsCoin.get()).add(securityDepositAsCoin.get())); + else + totalToPayAsCoin.set(offerFeeAsCoin.get().add(networkFeeAsCoin.get()).add(securityDepositAsCoin.get()).add(amountAsCoin.get())); + } + } + + private void updateBalance(Coin balance) { + isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); } private void applyBankAccount(FiatAccount fiatAccount) { @@ -262,13 +298,4 @@ class CreateOfferDataModel implements Activatable, DataModel { fiatCode.set(fiatAccount.currencyCode); } } - - public Boolean getDisplaySecurityDepositInfo() { - return preferences.getDisplaySecurityDepositInfo(); - } - - public void initWithData(Offer.Direction direction, Coin amount, Fiat price) { - checkNotNull(direction); - this.direction = direction; - } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.fxml b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.fxml index 60e67b1aa8..198f95093e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.fxml @@ -30,7 +30,7 @@ - diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index 3852651888..fe5bfda2e0 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -48,6 +48,7 @@ import java.util.List; import javax.inject.Inject; +import javafx.beans.value.ChangeListener; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.geometry.HPos; @@ -75,6 +76,9 @@ import static javafx.beans.binding.Bindings.createStringBinding; @FxmlView public class CreateOfferView extends ActivatableViewAndModel { + private final Navigation navigation; + private final OverlayManager overlayManager; + @FXML ScrollPane scrollPane; @FXML ImageView imageView; @FXML AddressTextField addressTextField; @@ -100,32 +104,258 @@ public class CreateOfferView extends ActivatableViewAndModel amountFocusedListener; + private ChangeListener minAmountFocusedListener; + private ChangeListener priceFocusedListener; + private ChangeListener volumeFocusedListener; + private ChangeListener showWarningInvalidBtcDecimalPlacesListener; + private ChangeListener showWarningInvalidFiatDecimalPlacesPlacesListener; + private ChangeListener showWarningAdjustedVolumeListener; + private ChangeListener requestPlaceOfferErrorMessageListener; + private ChangeListener isPlaceOfferSpinnerVisibleListener; + private ChangeListener showTransactionPublishedScreen; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + @Inject private CreateOfferView(CreateOfferViewModel model, Navigation navigation, OverlayManager overlayManager) { super(model); + this.navigation = navigation; this.overlayManager = overlayManager; } @Override protected void initialize() { - setupListeners(); - setupBindings(); + createListeners(); balanceTextField.setup(model.getWalletService(), model.address.get(), model.getFormatter()); volumeTextField.setPromptText(BSResources.get("createOffer.volume.prompt", model.fiatCode.get())); } @Override - protected void doDeactivate() { - + protected void doActivate() { + addBindings(); + addListeners(); } + @Override + protected void doDeactivate() { + removeBindings(); + removeListeners(); + } + + private void addBindings() { + amountBtcLabel.textProperty().bind(model.btcCode); + priceFiatLabel.textProperty().bind(model.fiatCode); + volumeFiatLabel.textProperty().bind(model.fiatCode); + minAmountBtcLabel.textProperty().bind(model.btcCode); + + priceDescriptionLabel.textProperty().bind(createStringBinding(() -> + BSResources.get("createOffer.amountPriceBox.priceDescription", model.fiatCode.get()), model.fiatCode)); + + volumeDescriptionLabel.textProperty().bind(createStringBinding(() -> model.volumeDescriptionLabel.get(), model.fiatCode, model.volumeDescriptionLabel)); + + buyLabel.textProperty().bind(model.directionLabel); + amountToTradeLabel.textProperty().bind(model.amountToTradeLabel); + amountTextField.textProperty().bindBidirectional(model.amount); + minAmountTextField.textProperty().bindBidirectional(model.minAmount); + priceTextField.textProperty().bindBidirectional(model.price); + volumeTextField.textProperty().bindBidirectional(model.volume); + amountPriceBoxInfo.textProperty().bind(model.amountPriceBoxInfo); + + totalToPayTextField.textProperty().bind(model.totalToPay); + + addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin); + addressTextField.paymentLabelProperty().bind(model.paymentLabel); + addressTextField.addressProperty().bind(model.addressAsString); + + bankAccountTypeTextField.textProperty().bind(model.bankAccountType); + bankAccountCurrencyTextField.textProperty().bind(model.bankAccountCurrency); + bankAccountCountyTextField.textProperty().bind(model.bankAccountCounty); + + acceptedCountriesTextField.textProperty().bind(model.acceptedCountries); + acceptedLanguagesTextField.textProperty().bind(model.acceptedLanguages); + acceptedArbitratorsTextField.textProperty().bind(model.acceptedArbitrators); + + // Validation + amountTextField.validationResultProperty().bind(model.amountValidationResult); + minAmountTextField.validationResultProperty().bind(model.minAmountValidationResult); + priceTextField.validationResultProperty().bind(model.priceValidationResult); + volumeTextField.validationResultProperty().bind(model.volumeValidationResult); + + // buttons + placeOfferButton.visibleProperty().bind(model.isPlaceOfferButtonVisible); + placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled); + + placeOfferSpinnerInfoLabel.visibleProperty().bind(model.isPlaceOfferSpinnerVisible); + } + + private void removeBindings() { + amountBtcLabel.textProperty().unbind(); + priceFiatLabel.textProperty().unbind(); + volumeFiatLabel.textProperty().unbind(); + minAmountBtcLabel.textProperty().unbind(); + priceDescriptionLabel.textProperty().unbind(); + volumeDescriptionLabel.textProperty().unbind(); + buyLabel.textProperty().unbind(); + amountToTradeLabel.textProperty().unbind(); + amountTextField.textProperty().unbindBidirectional(model.amount); + minAmountTextField.textProperty().unbindBidirectional(model.minAmount); + priceTextField.textProperty().unbindBidirectional(model.price); + volumeTextField.textProperty().unbindBidirectional(model.volume); + amountPriceBoxInfo.textProperty().unbind(); + totalToPayTextField.textProperty().unbind(); + addressTextField.amountAsCoinProperty().unbind(); + addressTextField.paymentLabelProperty().unbind(); + addressTextField.addressProperty().unbind(); + bankAccountTypeTextField.textProperty().unbind(); + bankAccountCurrencyTextField.textProperty().unbind(); + bankAccountCountyTextField.textProperty().unbind(); + acceptedCountriesTextField.textProperty().unbind(); + acceptedLanguagesTextField.textProperty().unbind(); + acceptedArbitratorsTextField.textProperty().unbind(); + amountTextField.validationResultProperty().unbind(); + minAmountTextField.validationResultProperty().unbind(); + priceTextField.validationResultProperty().unbind(); + volumeTextField.validationResultProperty().unbind(); + placeOfferButton.visibleProperty().unbind(); + placeOfferButton.disableProperty().unbind(); + placeOfferSpinnerInfoLabel.visibleProperty().unbind(); + } + + private void createListeners() { + amountFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText()); + amountTextField.setText(model.amount.get()); + }; + minAmountFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutMinAmountTextField(oldValue, newValue, minAmountTextField.getText()); + minAmountTextField.setText(model.minAmount.get()); + }; + priceFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText()); + priceTextField.setText(model.price.get()); + }; + volumeFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText()); + volumeTextField.setText(model.volume.get()); + }; + showWarningInvalidBtcDecimalPlacesListener = (o, oldValue, newValue) -> { + if (newValue) { + Popups.openWarningPopup(BSResources.get("shared.warning"), BSResources.get("createOffer.amountPriceBox.warning.invalidBtcDecimalPlaces")); + model.showWarningInvalidBtcDecimalPlaces.set(false); + } + }; + showWarningInvalidFiatDecimalPlacesPlacesListener = (o, oldValue, newValue) -> { + if (newValue) { + Popups.openWarningPopup(BSResources.get("shared.warning"), BSResources.get("createOffer.amountPriceBox.warning.invalidFiatDecimalPlaces")); + model.showWarningInvalidFiatDecimalPlaces.set(false); + } + }; + showWarningAdjustedVolumeListener = (o, oldValue, newValue) -> { + if (newValue) { + Popups.openWarningPopup(BSResources.get("shared.warning"), BSResources.get("createOffer.amountPriceBox.warning.adjustedVolume")); + model.showWarningAdjustedVolume.set(false); + volumeTextField.setText(model.volume.get()); + } + }; + requestPlaceOfferErrorMessageListener = (o, oldValue, newValue) -> { + if (newValue != null) { + Popups.openErrorPopup(BSResources.get("shared.error"), BSResources.get("createOffer.amountPriceBox.error.message", + model.requestPlaceOfferErrorMessage.get())); + Popups.removeBlurContent(); + } + }; + isPlaceOfferSpinnerVisibleListener = (ov, oldValue, newValue) -> { + placeOfferSpinner.setProgress(newValue ? -1 : 0); + placeOfferSpinner.setVisible(newValue); + }; + + showTransactionPublishedScreen = (o, oldValue, newValue) -> { + // TODO temp just for testing + newValue = false; + close(); + navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class); + + if (newValue) { + overlayManager.blurContent(); + + // Dialogs are a bit limited. There is no callback for the InformationDialog button click, so we added + // our own actions. + List actions = new ArrayList<>(); + /* actions.add(new AbstractAction(BSResources.get("shared.copyTxId")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "COPY"); + Utilities.copyToClipboard(model.transactionId.get()); + } + });*/ + actions.add(new AbstractAction(BSResources.get("shared.close")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "CLOSE"); + try { + close(); + navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class); + } catch (Exception e) { + e.printStackTrace(); + } + Dialog.Actions.CLOSE.handle(actionEvent); + } + }); + Popups.openInfoPopup(BSResources.get("createOffer.success.headline"), + BSResources.get("createOffer.success.info"), + actions); + } + }; + } + + private void addListeners() { + // focus out + amountTextField.focusedProperty().addListener(amountFocusedListener); + minAmountTextField.focusedProperty().addListener(minAmountFocusedListener); + priceTextField.focusedProperty().addListener(priceFocusedListener); + volumeTextField.focusedProperty().addListener(volumeFocusedListener); + + // warnings + model.showWarningInvalidBtcDecimalPlaces.addListener(showWarningInvalidBtcDecimalPlacesListener); + model.showWarningInvalidFiatDecimalPlaces.addListener(showWarningInvalidFiatDecimalPlacesPlacesListener); + model.showWarningAdjustedVolume.addListener(showWarningAdjustedVolumeListener); + model.requestPlaceOfferErrorMessage.addListener(requestPlaceOfferErrorMessageListener); + model.isPlaceOfferSpinnerVisible.addListener(isPlaceOfferSpinnerVisibleListener); + + model.showTransactionPublishedScreen.addListener(showTransactionPublishedScreen); + } + + private void removeListeners() { + // focus out + amountTextField.focusedProperty().removeListener(amountFocusedListener); + minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener); + priceTextField.focusedProperty().removeListener(priceFocusedListener); + volumeTextField.focusedProperty().removeListener(volumeFocusedListener); + + // warnings + model.showWarningInvalidBtcDecimalPlaces.removeListener(showWarningInvalidBtcDecimalPlacesListener); + model.showWarningInvalidFiatDecimalPlaces.removeListener(showWarningInvalidFiatDecimalPlacesPlacesListener); + model.showWarningAdjustedVolume.removeListener(showWarningAdjustedVolumeListener); + model.requestPlaceOfferErrorMessage.removeListener(requestPlaceOfferErrorMessageListener); + model.isPlaceOfferSpinnerVisible.removeListener(isPlaceOfferSpinnerVisibleListener); + + model.showTransactionPublishedScreen.removeListener(showTransactionPublishedScreen); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public void initWithData(Offer.Direction direction, Coin amount, Fiat price) { model.initWithData(direction, amount, price); @@ -139,11 +369,21 @@ public class CreateOfferView extends ActivatableViewAndModel InputTextField.hideErrorMessageDisplay()); - // focus out - amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText()); - amountTextField.setText(model.amount.get()); - }); - - minAmountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - model.onFocusOutMinAmountTextField(oldValue, newValue, minAmountTextField.getText()); - minAmountTextField.setText(model.minAmount.get()); - }); - - priceTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText()); - priceTextField.setText(model.price.get()); - }); - - volumeTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText()); - volumeTextField.setText(model.volume.get()); - }); - - // warnings - model.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> { - if (newValue) { - Popups.openWarningPopup(BSResources.get("shared.warning"), BSResources.get("createOffer.amountPriceBox.warning.invalidBtcDecimalPlaces")); - model.showWarningInvalidBtcDecimalPlaces.set(false); - } - }); - - model.showWarningInvalidFiatDecimalPlaces.addListener((o, oldValue, newValue) -> { - if (newValue) { - Popups.openWarningPopup(BSResources.get("shared.warning"), BSResources.get("createOffer.amountPriceBox.warning.invalidFiatDecimalPlaces")); - model.showWarningInvalidFiatDecimalPlaces.set(false); - } - }); - - model.showWarningAdjustedVolume.addListener((o, oldValue, newValue) -> { - if (newValue) { - Popups.openWarningPopup(BSResources.get("shared.warning"), BSResources.get("createOffer.amountPriceBox.warning.adjustedVolume")); - model.showWarningAdjustedVolume.set(false); - volumeTextField.setText(model.volume.get()); - } - }); - - model.requestPlaceOfferErrorMessage.addListener((o, oldValue, newValue) -> { - if (newValue != null) { - Popups.openErrorPopup(BSResources.get("shared.error"), BSResources.get("createOffer.amountPriceBox.error.message", - model.requestPlaceOfferErrorMessage.get())); - Popups.removeBlurContent(); - } - }); - - model.showTransactionPublishedScreen.addListener((o, oldValue, newValue) -> { - // TODO temp just for testing - newValue = false; - close(); - navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class); - - if (newValue) { - overlayManager.blurContent(); - - // Dialogs are a bit limited. There is no callback for the InformationDialog button click, so we added - // our own actions. - List actions = new ArrayList<>(); - /* actions.add(new AbstractAction(BSResources.get("shared.copyTxId")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "COPY"); - Utilities.copyToClipboard(model.transactionId.get()); - } - });*/ - actions.add(new AbstractAction(BSResources.get("shared.close")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "CLOSE"); - try { - close(); - navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class); - } catch (Exception e) { - e.printStackTrace(); - } - Dialog.Actions.CLOSE.handle(actionEvent); - } - }); - Popups.openInfoPopup(BSResources.get("createOffer.success.headline"), - BSResources.get("createOffer.success.info"), - actions); - } - }); - } - - private void setupBindings() { - amountBtcLabel.textProperty().bind(model.btcCode); - priceFiatLabel.textProperty().bind(model.fiatCode); - volumeFiatLabel.textProperty().bind(model.fiatCode); - minAmountBtcLabel.textProperty().bind(model.btcCode); - - priceDescriptionLabel.textProperty().bind(createStringBinding(() -> - BSResources.get("createOffer.amountPriceBox.priceDescription", model.fiatCode.get()), model.fiatCode)); - - volumeDescriptionLabel.textProperty().bind(createStringBinding(() -> model.volumeDescriptionLabel.get(), model.fiatCode, model.volumeDescriptionLabel)); - - buyLabel.textProperty().bind(model.directionLabel); - amountToTradeLabel.textProperty().bind(model.amountToTradeLabel); - amountTextField.textProperty().bindBidirectional(model.amount); - minAmountTextField.textProperty().bindBidirectional(model.minAmount); - priceTextField.textProperty().bindBidirectional(model.price); - volumeTextField.textProperty().bindBidirectional(model.volume); - amountPriceBoxInfo.textProperty().bind(model.amountPriceBoxInfo); - - totalToPayTextField.textProperty().bind(model.totalToPay); - - addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin); - addressTextField.paymentLabelProperty().bind(model.paymentLabel); - addressTextField.addressProperty().bind(model.addressAsString); - - bankAccountTypeTextField.textProperty().bind(model.bankAccountType); - bankAccountCurrencyTextField.textProperty().bind(model.bankAccountCurrency); - bankAccountCountyTextField.textProperty().bind(model.bankAccountCounty); - - acceptedCountriesTextField.textProperty().bind(model.acceptedCountries); - acceptedLanguagesTextField.textProperty().bind(model.acceptedLanguages); - acceptedArbitratorsTextField.textProperty().bind(model.acceptedArbitrators); - - // Validation - amountTextField.validationResultProperty().bind(model.amountValidationResult); - minAmountTextField.validationResultProperty().bind(model.minAmountValidationResult); - priceTextField.validationResultProperty().bind(model.priceValidationResult); - volumeTextField.validationResultProperty().bind(model.volumeValidationResult); - - // buttons - placeOfferButton.visibleProperty().bind(model.isPlaceOfferButtonVisible); - placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled); - - placeOfferSpinnerInfoLabel.visibleProperty().bind(model.isPlaceOfferSpinnerVisible); - - model.isPlaceOfferSpinnerVisible.addListener((ov, oldValue, newValue) -> { - placeOfferSpinner.setProgress(newValue ? -1 : 0); - placeOfferSpinner.setVisible(newValue); - }); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // State + /////////////////////////////////////////////////////////////////////////////////////////// private void showDetailsScreen() { payFundsPane.setInactive(); @@ -433,6 +538,11 @@ public class CreateOfferView extends ActivatableViewAndModel totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty
address = new SimpleObjectProperty<>(); + private ChangeListener amountListener; + private ChangeListener minAmountListener; + private ChangeListener priceListener; + private ChangeListener volumeListener; + private ChangeListener amountAsCoinListener; + private ChangeListener minAmountAsCoinListener; + private ChangeListener priceAsFiatListener; + private ChangeListener volumeAsFiatListener; + private ChangeListener isWalletFundedListener; + private ChangeListener requestPlaceOfferSuccessListener; + private ChangeListener requestPlaceOfferErrorMessageListener; + private InvalidationListener acceptedCountriesListener; + private InvalidationListener acceptedLanguageCodesListener; + private InvalidationListener acceptedArbitratorsListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// @Inject public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator, @@ -110,234 +130,22 @@ class CreateOfferViewModel extends ActivatableWithDataModel { - if (isBtcInputValid(newValue).isValid) { - setAmountToModel(); - calculateVolume(); - dataModel.calculateTotalToPay(); - } - updateButtonDisableState(); - }); - - minAmount.addListener((ov, oldValue, newValue) -> { - setMinAmountToModel(); - updateButtonDisableState(); - }); - - price.addListener((ov, oldValue, newValue) -> { - if (isFiatInputValid(newValue).isValid) { - setPriceToModel(); - calculateVolume(); - dataModel.calculateTotalToPay(); - } - updateButtonDisableState(); - }); - - volume.addListener((ov, oldValue, newValue) -> { - if (isFiatInputValid(newValue).isValid) { - setVolumeToModel(); - setPriceToModel(); - dataModel.calculateAmount(); - dataModel.calculateTotalToPay(); - } - updateButtonDisableState(); - }); - dataModel.isWalletFunded.addListener((ov, oldValue, newValue) -> { - updateButtonDisableState(); - }); - - // Binding with Bindings.createObjectBinding does not work because of bi-directional binding - dataModel.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue))); - dataModel.minAmountAsCoin.addListener((ov, oldValue, newValue) -> minAmount.set(formatter.formatCoin(newValue))); - dataModel.priceAsFiat.addListener((ov, oldValue, newValue) -> price.set(formatter.formatFiat(newValue))); - dataModel.volumeAsFiat.addListener((ov, oldValue, newValue) -> volume.set(formatter.formatFiat(newValue))); - - dataModel.requestPlaceOfferErrorMessage.addListener((ov, oldValue, newValue) -> { - if (newValue != null) { - isPlaceOfferSpinnerVisible.set(false); - } - }); - dataModel.requestPlaceOfferSuccess.addListener((ov, oldValue, newValue) -> { - isPlaceOfferButtonVisible.set(!newValue); - isPlaceOfferSpinnerVisible.set(false); - }); - - // ObservableLists - dataModel.acceptedCountries.addListener((Observable o) -> acceptedCountries.set(formatter - .countryLocalesToString(dataModel.acceptedCountries))); - dataModel.acceptedLanguageCodes.addListener((Observable o) -> acceptedLanguages.set(formatter - .languageCodesToString(dataModel.acceptedLanguageCodes))); - - - dataModel.acceptedArbitrators.addListener((Observable o) -> - acceptedArbitrators.set(formatter.arbitratorsToNames(dataModel.acceptedArbitrators))); - - } - - private void setupBindings() { + private void addBindings() { totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()), dataModel.totalToPayAsCoin)); securityDeposit.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.securityDepositAsCoin.get()), @@ -366,6 +174,307 @@ class CreateOfferViewModel extends ActivatableWithDataModel { + if (isBtcInputValid(newValue).isValid) { + setAmountToModel(); + calculateVolume(); + dataModel.calculateTotalToPay(); + } + updateButtonDisableState(); + }; + minAmountListener = (ov, oldValue, newValue) -> { + setMinAmountToModel(); + updateButtonDisableState(); + }; + priceListener = (ov, oldValue, newValue) -> { + if (isFiatInputValid(newValue).isValid) { + setPriceToModel(); + calculateVolume(); + dataModel.calculateTotalToPay(); + } + updateButtonDisableState(); + }; + volumeListener = (ov, oldValue, newValue) -> { + if (isFiatInputValid(newValue).isValid) { + setVolumeToModel(); + setPriceToModel(); + dataModel.calculateAmount(); + dataModel.calculateTotalToPay(); + } + updateButtonDisableState(); + }; + amountAsCoinListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue)); + minAmountAsCoinListener = (ov, oldValue, newValue) -> minAmount.set(formatter.formatCoin(newValue)); + priceAsFiatListener = (ov, oldValue, newValue) -> price.set(formatter.formatFiat(newValue)); + volumeAsFiatListener = (ov, oldValue, newValue) -> volume.set(formatter.formatFiat(newValue)); + + isWalletFundedListener = (ov, oldValue, newValue) -> { + updateButtonDisableState(); + }; + requestPlaceOfferSuccessListener = (ov, oldValue, newValue) -> { + isPlaceOfferButtonVisible.set(!newValue); + isPlaceOfferSpinnerVisible.set(false); + }; + requestPlaceOfferErrorMessageListener = (ov, oldValue, newValue) -> { + if (newValue != null) { + isPlaceOfferSpinnerVisible.set(false); + } + }; + + acceptedCountriesListener = (Observable o) -> + acceptedCountries.set(formatter.countryLocalesToString(dataModel.acceptedCountries)); + acceptedLanguageCodesListener = (Observable o) -> acceptedLanguages.set(formatter.languageCodesToString(dataModel.acceptedLanguageCodes)); + acceptedArbitratorsListener = (Observable o) -> acceptedArbitrators.set(formatter.arbitratorsToNames(dataModel.acceptedArbitrators)); + + } + + private void addListeners() { + // Bidirectional bindings are used for all input fields: amount, price, volume and minAmount + // We do volume/amount calculation during input, so user has immediate feedback + amount.addListener(amountListener); + minAmount.addListener(minAmountListener); + price.addListener(priceListener); + volume.addListener(volumeListener); + + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.amountAsCoin.addListener(amountAsCoinListener); + dataModel.minAmountAsCoin.addListener(minAmountAsCoinListener); + dataModel.priceAsFiat.addListener(priceAsFiatListener); + dataModel.volumeAsFiat.addListener(volumeAsFiatListener); + + dataModel.isWalletFunded.addListener(isWalletFundedListener); + dataModel.requestPlaceOfferSuccess.addListener(requestPlaceOfferSuccessListener); + dataModel.requestPlaceOfferErrorMessage.addListener(requestPlaceOfferErrorMessageListener); + + // ObservableLists + dataModel.acceptedCountries.addListener(acceptedCountriesListener); + dataModel.acceptedLanguageCodes.addListener(acceptedLanguageCodesListener); + dataModel.acceptedArbitrators.addListener(acceptedArbitratorsListener); + } + + private void removeListeners() { + amount.removeListener(amountListener); + minAmount.removeListener(minAmountListener); + price.removeListener(priceListener); + volume.removeListener(volumeListener); + + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.amountAsCoin.removeListener(amountAsCoinListener); + dataModel.minAmountAsCoin.removeListener(minAmountAsCoinListener); + dataModel.priceAsFiat.removeListener(priceAsFiatListener); + dataModel.volumeAsFiat.removeListener(volumeAsFiatListener); + + dataModel.isWalletFunded.removeListener(isWalletFundedListener); + dataModel.requestPlaceOfferSuccess.removeListener(requestPlaceOfferSuccessListener); + dataModel.requestPlaceOfferErrorMessage.removeListener(requestPlaceOfferErrorMessageListener); + + // ObservableLists + dataModel.acceptedCountries.removeListener(acceptedCountriesListener); + dataModel.acceptedLanguageCodes.removeListener(acceptedLanguageCodesListener); + dataModel.acceptedArbitrators.removeListener(acceptedArbitratorsListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + void initWithData(Offer.Direction direction, Coin amountAsCoin, Fiat priceAsFiat) { + addListeners(); + + dataModel.initWithData(direction); + + if (dataModel.getDirection() == Offer.Direction.BUY) { + directionLabel.set(BSResources.get("shared.buyBitcoin")); + amountToTradeLabel.set(BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.buy"))); + volumeDescriptionLabel.set(BSResources.get("createOffer.amountPriceBox.buy.volumeDescription", fiatCode.get())); + amountPriceBoxInfo.set(BSResources.get("createOffer.amountPriceBox.buy.info")); + } + else { + directionLabel.set(BSResources.get("shared.sellBitcoin")); + amountToTradeLabel.set(BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.sell"))); + volumeDescriptionLabel.set(BSResources.get("createOffer.amountPriceBox.sell.volumeDescription", fiatCode.get())); + amountPriceBoxInfo.set(BSResources.get("createOffer.amountPriceBox.sell.info")); + } + + + // apply only if valid + boolean amountValid = false; + if (amountAsCoin != null && isBtcInputValid(amountAsCoin.toPlainString()) + .isValid) { + dataModel.amountAsCoin.set(amountAsCoin); + dataModel.minAmountAsCoin.set(amountAsCoin); + amountValid = true; + } + + // apply only if valid + boolean priceValid = false; + if (priceAsFiat != null && isBtcInputValid(priceAsFiat.toPlainString()).isValid) { + dataModel.priceAsFiat.set(formatter.parseToFiatWith2Decimals(priceAsFiat.toPlainString())); + priceValid = true; + } + + if (amountValid && priceValid) + dataModel.calculateTotalToPay(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + void onPlaceOffer() { + dataModel.requestPlaceOfferErrorMessage.set(null); + dataModel.requestPlaceOfferSuccess.set(false); + + isPlaceOfferSpinnerVisible.set(true); + + dataModel.onPlaceOffer(); + } + + + void onShowPayFundsScreen() { + isPlaceOfferButtonVisible.set(true); + } + + void onSecurityDepositInfoDisplayed() { + dataModel.onSecurityDepositInfoDisplayed(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Handle focus + /////////////////////////////////////////////////////////////////////////////////////////// + + // On focus out we do validation and apply the data to the model + void onFocusOutAmountTextField(boolean oldValue, boolean newValue, String userInput) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isBtcInputValid(amount.get()); + amountValidationResult.set(result); + if (result.isValid) { + showWarningInvalidBtcDecimalPlaces.set(!formatter.hasBtcValidDecimals(userInput)); + // only allow max 4 decimal places for btc values + setAmountToModel(); + // reformat input + amount.set(formatter.formatCoin(dataModel.amountAsCoin.get())); + + calculateVolume(); + + // handle minAmount/amount relationship + if (!dataModel.isMinAmountLessOrEqualAmount()) { + amountValidationResult.set(new InputValidator.ValidationResult(false, + BSResources.get("createOffer.validation.amountSmallerThanMinAmount"))); + } + else { + amountValidationResult.set(result); + if (minAmount.get() != null) + minAmountValidationResult.set(isBtcInputValid(minAmount.get())); + } + } + } + } + + void onFocusOutMinAmountTextField(boolean oldValue, boolean newValue, String userInput) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isBtcInputValid(minAmount.get()); + minAmountValidationResult.set(result); + if (result.isValid) { + showWarningInvalidBtcDecimalPlaces.set(!formatter.hasBtcValidDecimals(userInput)); + setMinAmountToModel(); + minAmount.set(formatter.formatCoin(dataModel.minAmountAsCoin.get())); + + if (!dataModel.isMinAmountLessOrEqualAmount()) { + minAmountValidationResult.set(new InputValidator.ValidationResult(false, + BSResources.get("createOffer.validation.minAmountLargerThanAmount"))); + } + else { + minAmountValidationResult.set(result); + if (amount.get() != null) + amountValidationResult.set(isBtcInputValid(amount.get())); + } + } + } + } + + void onFocusOutPriceTextField(boolean oldValue, boolean newValue, String userInput) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isFiatInputValid(price.get()); + boolean isValid = result.isValid; + priceValidationResult.set(result); + if (isValid) { + showWarningInvalidFiatDecimalPlaces.set(!formatter.hasFiatValidDecimals(userInput)); + setPriceToModel(); + price.set(formatter.formatFiat(dataModel.priceAsFiat.get())); + + calculateVolume(); + } + } + } + + void onFocusOutVolumeTextField(boolean oldValue, boolean newValue, String userInput) { + if (oldValue && !newValue) { + InputValidator.ValidationResult result = isBtcInputValid(volume.get()); + volumeValidationResult.set(result); + if (result.isValid) { + showWarningInvalidFiatDecimalPlaces.set(!formatter.hasFiatValidDecimals(userInput)); + setVolumeToModel(); + volume.set(formatter.formatFiat(dataModel.volumeAsFiat.get())); + + calculateAmount(); + + // must be placed after calculateAmount (btc value has been adjusted in case the calculation leads to + // invalid decimal places for the amount value + showWarningAdjustedVolume.set(!formatter.formatFiat(formatter.parseToFiatWith2Decimals(userInput)) + .equals(volume + .get())); + } + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + WalletService getWalletService() { + return dataModel.getWalletService(); + } + + BSFormatter getFormatter() { + return formatter; + } + + boolean getDisplaySecurityDepositInfo() { + return dataModel.getDisplaySecurityDepositInfo(); + } + + boolean isSeller() { + return dataModel.getDirection() == Offer.Direction.SELL; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + private void calculateVolume() { setAmountToModel(); setPriceToModel(); @@ -417,16 +526,11 @@ class CreateOfferViewModel extends ActivatableWithDataModel offerBookListItems = FXCollections.observableArrayList(); - private final OfferBookService.Listener offerBookServiceListener; private final ChangeListener bankAccountChangeListener; private final ChangeListener invalidationListener; + private final OfferBookService.Listener offerBookServiceListener; + + private final ObservableList offerBookListItems = FXCollections.observableArrayList(); + private String fiatCode; - private AnimationTimer pollingTimer; + private Timer pollingTimer; private Country country; - private int numClients = 0; /////////////////////////////////////////////////////////////////////////////////////////// @@ -75,7 +73,7 @@ public class OfferBook { this.user = user; bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue); - invalidationListener = (ov, oldValue, newValue) -> requestGetOffers(); + invalidationListener = (ov, oldValue, newValue) -> offerBookService.getOffers(fiatCode); offerBookServiceListener = new OfferBookService.Listener() { @Override @@ -105,20 +103,31 @@ public class OfferBook { /////////////////////////////////////////////////////////////////////////////////////////// - // Package scope + // API /////////////////////////////////////////////////////////////////////////////////////////// - public void addClient() { - numClients++; - if (numClients == 1) - startPolling(); + void startPolling() { + addListeners(); + setBankAccount(user.currentFiatAccountProperty().get()); + pollingTimer = Utilities.setInterval(POLLING_INTERVAL, () -> offerBookService.requestInvalidationTimeStampFromDHT(fiatCode)); + offerBookService.getOffers(fiatCode); } - public void removeClient() { - numClients--; - checkArgument(numClients >= 0); - if (numClients == 0) - stopPolling(); + void stopPolling() { + pollingTimer.cancel(); + removeListeners(); + } + + private void addListeners() { + user.currentFiatAccountProperty().addListener(bankAccountChangeListener); + offerBookService.addListener(offerBookServiceListener); + offerBookService.invalidationTimestampProperty().addListener(invalidationListener); + } + + private void removeListeners() { + user.currentFiatAccountProperty().removeListener(bankAccountChangeListener); + offerBookService.removeListener(offerBookServiceListener); + offerBookService.invalidationTimestampProperty().removeListener(invalidationListener); } @@ -126,13 +135,13 @@ public class OfferBook { // Getter /////////////////////////////////////////////////////////////////////////////////////////// - public ObservableList getOfferBookListItems() { + ObservableList getOfferBookListItems() { return offerBookListItems; } /////////////////////////////////////////////////////////////////////////////////////////// - // Private + // Utils /////////////////////////////////////////////////////////////////////////////////////////// private void setBankAccount(FiatAccount fiatAccount) { @@ -149,50 +158,9 @@ public class OfferBook { } } - private void addListeners() { - log.debug("addListeners "); - user.currentFiatAccountProperty().addListener(bankAccountChangeListener); - offerBookService.addListener(offerBookServiceListener); - offerBookService.invalidationTimestampProperty().addListener(invalidationListener); - } - - private void removeListeners() { - log.debug("removeListeners "); - user.currentFiatAccountProperty().removeListener(bankAccountChangeListener); - offerBookService.removeListener(offerBookServiceListener); - offerBookService.invalidationTimestampProperty().removeListener(invalidationListener); - } - private void addOfferToOfferBookListItems(Offer offer) { if (offer != null) { offerBookListItems.add(new OfferBookListItem(offer, country)); } } - - private void requestGetOffers() { - offerBookService.getOffers(fiatCode); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Polling - /////////////////////////////////////////////////////////////////////////////////////////// - - // TODO Just temporary, will be removed later when we have a push solution - private void startPolling() { - addListeners(); - setBankAccount(user.currentFiatAccountProperty().get()); - pollingTimer = GUIUtil.setInterval(POLLING_INTERVAL, (animationTimer) -> { - offerBookService.requestInvalidationTimeStampFromDHT(fiatCode); - return null; - }); - - offerBookService.getOffers(fiatCode); - } - - private void stopPolling() { - pollingTimer.stop(); - removeListeners(); - } - } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookDataModel.java index 2c0802973b..a3758d7d7e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookDataModel.java @@ -54,16 +54,16 @@ import org.slf4j.LoggerFactory; class OfferBookDataModel implements Activatable, DataModel { private static final Logger log = LoggerFactory.getLogger(OfferBookDataModel.class); + private final OpenOfferManager openOfferManager; private final User user; private final OfferBook offerBook; private final Preferences preferences; private final BSFormatter formatter; - private final OpenOfferManager openOfferManager; private final FilteredList filteredItems; private final SortedList sortedItems; - // private OfferBookInfo offerBookInfo; - private final ChangeListener bankAccountChangeListener; + + private ChangeListener bankAccountChangeListener; private final ObjectProperty amountAsCoin = new SimpleObjectProperty<>(); private final ObjectProperty priceAsFiat = new SimpleObjectProperty<>(); @@ -76,6 +76,10 @@ class OfferBookDataModel implements Activatable, DataModel { private Offer.Direction direction; + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + @Inject public OfferBookDataModel(User user, OpenOfferManager openOfferManager, OfferBook offerBook, Preferences preferences, BSFormatter formatter) { @@ -87,63 +91,81 @@ class OfferBookDataModel implements Activatable, DataModel { this.filteredItems = new FilteredList<>(offerBook.getOfferBookListItems()); this.sortedItems = new SortedList<>(filteredItems); - this.bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue); + + createListeners(); } @Override public void activate() { + addBindings(); + addListeners(); +/* amountAsCoin.set(null); priceAsFiat.set(null); volumeAsFiat.set(null); - offerBook.addClient(); - user.currentFiatAccountProperty().addListener(bankAccountChangeListener); - btcCode.bind(preferences.btcDenominationProperty()); + //TODO temp for testing + amountAsCoin.set(Coin.COIN); + priceAsFiat.set(Fiat.valueOf("EUR", 300*10000)); + // volumeAsFiat.set(Fiat.valueOf("EUR", 300));*/ setBankAccount(user.currentFiatAccountProperty().get()); applyFilter(); + + offerBook.startPolling(); } @Override public void deactivate() { - offerBook.removeClient(); - user.currentFiatAccountProperty().removeListener(bankAccountChangeListener); + removeBindings(); + removeListeners(); + + offerBook.stopPolling(); + } + + private void addBindings() { + btcCode.bind(preferences.btcDenominationProperty()); + } + + private void removeBindings() { btcCode.unbind(); } + private void createListeners() { + this.bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue); + } + + private void addListeners() { + user.currentFiatAccountProperty().addListener(bankAccountChangeListener); + } + + private void removeListeners() { + user.currentFiatAccountProperty().removeListener(bankAccountChangeListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + + void setDirection(Offer.Direction direction) { + this.direction = direction; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + void onCancelOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { openOfferManager.onCancelOpenOffer(offer, resultHandler, errorMessageHandler); } - void calculateVolume() { - try { - if (priceAsFiat.get() != null && - amountAsCoin.get() != null && - !amountAsCoin.get().isZero() && - !priceAsFiat.get().isZero()) { - volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); - } - } catch (Throwable t) { - // Should be never reached - log.error(t.toString()); - } - } - void calculateAmount() { - try { - if (volumeAsFiat.get() != null && - priceAsFiat.get() != null && - !volumeAsFiat.get().isZero() && - !priceAsFiat.get().isZero()) { - // If we got a btc value with more then 4 decimals we convert it to max 4 decimals - amountAsCoin.set(formatter.reduceTo4Decimals(new ExchangeRate(priceAsFiat.get()).fiatToCoin - (volumeAsFiat.get()))); - } - } catch (Throwable t) { - // Should be never reached - log.error(t.toString()); - } - } + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// boolean isTradable(Offer offer) { // if user has not registered yet we display all @@ -180,27 +202,6 @@ class OfferBookDataModel implements Activatable, DataModel { return countryResult; } - - void setDirection(Offer.Direction direction) { - this.direction = direction; - } - - void setAmount(Coin amount) { - amountAsCoin.set(amount); - applyFilter(); - } - - void setPrice(Fiat price) { - priceAsFiat.set(price); - applyFilter(); - } - - void setVolume(Fiat volume) { - volumeAsFiat.set(volume); - applyFilter(); - } - - SortedList getOfferList() { return sortedItems; } @@ -241,6 +242,57 @@ class OfferBookDataModel implements Activatable, DataModel { return direction; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + void calculateVolume() { + try { + if (priceAsFiat.get() != null && + amountAsCoin.get() != null && + !amountAsCoin.get().isZero() && + !priceAsFiat.get().isZero()) { + volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get())); + } + } catch (Throwable t) { + // Should be never reached + log.error(t.toString()); + } + } + + void calculateAmount() { + try { + if (volumeAsFiat.get() != null && + priceAsFiat.get() != null && + !volumeAsFiat.get().isZero() && + !priceAsFiat.get().isZero()) { + // If we got a btc value with more then 4 decimals we convert it to max 4 decimals + amountAsCoin.set(formatter.reduceTo4Decimals(new ExchangeRate(priceAsFiat.get()).fiatToCoin + (volumeAsFiat.get()))); + } + } catch (Throwable t) { + // Should be never reached + log.error(t.toString()); + } + } + + + void setAmount(Coin amount) { + amountAsCoin.set(amount); + applyFilter(); + } + + void setPrice(Fiat price) { + priceAsFiat.set(price); + applyFilter(); + } + + void setVolume(Fiat volume) { + volumeAsFiat.set(volume); + applyFilter(); + } + private void setBankAccount(FiatAccount fiatAccount) { if (fiatAccount != null) { fiatCode.set(fiatAccount.currencyCode); @@ -274,5 +326,4 @@ class OfferBookDataModel implements Activatable, DataModel { return directionResult && amountResult && priceResult; }); } - } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookListItem.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookListItem.java index 8ef07b45c4..49e87f155d 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookListItem.java @@ -27,30 +27,15 @@ public class OfferBookListItem { private final Offer offer; private final ObjectProperty bankAccountCountry = new SimpleObjectProperty<>(); - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - public OfferBookListItem(Offer offer, Country bankAccountCountry) { this.offer = offer; setBankAccountCountry(bankAccountCountry); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setters - /////////////////////////////////////////////////////////////////////////////////////////// - void setBankAccountCountry(Country bankAccountCountry) { this.bankAccountCountry.set(bankAccountCountry); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters - /////////////////////////////////////////////////////////////////////////////////////////// - public Offer getOffer() { return offer; } @@ -62,7 +47,5 @@ public class OfferBookListItem { ObjectProperty bankAccountCountryProperty() { return bankAccountCountry; } - - } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java index 9ce78f9fe5..dc29f98999 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java @@ -35,6 +35,7 @@ import io.bitsquare.gui.util.ImageUtil; import io.bitsquare.gui.util.validation.OptionalBtcValidator; import io.bitsquare.gui.util.validation.OptionalFiatValidator; import io.bitsquare.locale.BSResources; +import io.bitsquare.locale.Country; import io.bitsquare.trade.offer.Offer; import java.util.ArrayList; @@ -43,7 +44,7 @@ import java.util.List; import javax.inject.Inject; import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.transformation.SortedList; +import javafx.beans.value.ChangeListener; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.*; @@ -84,6 +85,11 @@ public class OfferBookView extends ActivatableViewAndModel offerList = model.getOfferList(); - table.setItems(offerList); - offerList.comparatorProperty().bind(table.comparatorProperty()); - priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? - TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING); + table.setItems(model.getOfferList()); + priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING); table.sort(); - - //TODO temp for testing - amountTextField.setText("1"); - priceTextField.setText("300"); - volumeTextField.setText("300"); } @Override @@ -154,6 +147,56 @@ public class OfferBookView extends ActivatableViewAndModel BSResources.get("Filter by price in {0}", model.fiatCode.get()), model.fiatCode)); + volumeDescriptionLabel.textProperty().bind(createStringBinding(() -> BSResources.get("Filter by amount in {0}", model.fiatCode.get()), model.fiatCode)); + volumeTextField.promptTextProperty().bind(createStringBinding(() -> BSResources.get("Amount in {0}", model.fiatCode.get()), model.fiatCode)); + + model.getOfferList().comparatorProperty().bind(table.comparatorProperty()); + } + + private void removeBindings() { + amountTextField.textProperty().unbind(); + priceTextField.textProperty().unbind(); + volumeTextField.textProperty().unbind(); + amountBtcLabel.textProperty().unbind(); + priceFiatLabel.textProperty().unbind(); + volumeFiatLabel.textProperty().unbind(); + priceDescriptionLabel.textProperty().unbind(); + volumeDescriptionLabel.textProperty().unbind(); + volumeTextField.promptTextProperty().unbind(); + + model.getOfferList().comparatorProperty().unbind(); + } + + private void setupValidators() { + amountTextField.setValidator(optionalBtcValidator); + priceTextField.setValidator(optionalFiatValidator); + volumeTextField.setValidator(optionalFiatValidator); + } + + private void setupComparators() { + priceColumn.setComparator((o1, o2) -> o1.getOffer().getPrice().compareTo(o2.getOffer().getPrice())); + amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount())); + volumeColumn.setComparator((o1, o2) -> + o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume())); + /* countryColumn.setComparator((o1, o2) -> o1.getOffer().getBankAccountCountry().getName().compareTo(o2 + .getOffer() + .getBankAccountCountry().getName()));*/ + bankAccountTypeColumn.setComparator((o1, o2) -> o1.getOffer().getFiatAccountType().compareTo(o2.getOffer() + .getFiatAccountType())); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public void enableCreateOfferButton() { createOfferButton.setDisable(false); } @@ -162,6 +205,15 @@ public class OfferBookView extends ActivatableViewAndModel actions = new ArrayList<>(); + actions.add(new AbstractAction(BSResources.get("shared.ok")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "OK"); + Dialog.Actions.OK.handle(actionEvent); + navigation.setReturnPath(navigation.getCurrentPath()); + navigation.navigateTo(MainView.class, AccountView.class, AccountSetupWizard.class); + } + }); + Popups.openInfoPopup("You don't have setup a trading account.", + "You need to setup your trading account before you can trade.", + actions); + } + @FXML void onToggleShowAdvancedSettings() { detailsVisible = !detailsVisible; @@ -203,23 +272,6 @@ public class OfferBookView extends ActivatableViewAndModel actions = new ArrayList<>(); - actions.add(new AbstractAction(BSResources.get("shared.ok")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "OK"); - Dialog.Actions.OK.handle(actionEvent); - navigation.setReturnPath(navigation.getCurrentPath()); - navigation.navigateTo(MainView.class, AccountView.class, AccountSetupWizard.class); - } - }); - Popups.openInfoPopup("You don't have setup a trading account.", - "You need to setup your trading account before you can trade.", - actions); - } - private void takeOffer(Offer offer) { if (model.isRegistered()) { if (offer.getDirection() == Offer.Direction.BUY) { @@ -248,36 +300,10 @@ public class OfferBookView extends ActivatableViewAndModel actions = new ArrayList<>(); - actions.add(new AbstractAction(BSResources.get("shared.yes")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "YES"); - Dialog.Actions.YES.handle(actionEvent); - } - }); - actions.add(new AbstractAction(BSResources.get("shared.no")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "NO"); - Dialog.Actions.NO.handle(actionEvent); - } - }); - Action response = Popups.openConfirmPopup("Information", - "You do not fulfill the requirements for that offer.", - restrictionsInfo, - actions); - - Popups.removeBlurContent(); - - if (Popups.isYes(response)) - navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, RestrictionsView.class); - else - table.getSelectionModel().clearSelection(); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // State + /////////////////////////////////////////////////////////////////////////////////////////// private void showDetailsScreen() { if (!advancedScreenInited) { @@ -313,61 +339,46 @@ public class OfferBookView extends ActivatableViewAndModel - BSResources.get("Filter by price in {0}", model.fiatCode.get()), - model.fiatCode)); - volumeDescriptionLabel.textProperty().bind(createStringBinding(() -> - BSResources.get("Filter by amount in {0}", model.fiatCode.get()), - model.fiatCode)); - volumeTextField.promptTextProperty().bind(createStringBinding(() -> - BSResources.get("Amount in {0}", model.fiatCode.get()), - model.fiatCode)); + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private void openRestrictionsWarning(String restrictionsInfo) { + overlayManager.blurContent(); + List actions = new ArrayList<>(); + actions.add(new AbstractAction(BSResources.get("shared.yes")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "YES"); + Dialog.Actions.YES.handle(actionEvent); + } + }); + actions.add(new AbstractAction(BSResources.get("shared.no")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "NO"); + Dialog.Actions.NO.handle(actionEvent); + } + }); + + Action response = Popups.openConfirmPopup("Information", + "You do not fulfill the requirements for that offer.", + restrictionsInfo, + actions); + + Popups.removeBlurContent(); + + if (Popups.isYes(response)) + navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, RestrictionsView.class); + else + table.getSelectionModel().clearSelection(); } - private void removeBindings() { - amountTextField.textProperty().unbind(); - priceTextField.textProperty().unbind(); - volumeTextField.textProperty().unbind(); - amountBtcLabel.textProperty().unbind(); - priceFiatLabel.textProperty().unbind(); - volumeFiatLabel.textProperty().unbind(); - priceDescriptionLabel.textProperty().unbind(); - volumeDescriptionLabel.textProperty().unbind(); - priceDescriptionLabel.textProperty().unbind(); - volumeDescriptionLabel.textProperty().unbind(); - volumeTextField.promptTextProperty().unbind(); - } - private void setupValidators() { - amountTextField.setValidator(optionalBtcValidator); - priceTextField.setValidator(optionalFiatValidator); - volumeTextField.setValidator(optionalFiatValidator); - } - - private void setupComparators() { - priceColumn.setComparator((o1, o2) -> o1.getOffer().getPrice().compareTo(o2.getOffer().getPrice())); - amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount())); - volumeColumn.setComparator((o1, o2) -> - o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume())); - /* countryColumn.setComparator((o1, o2) -> o1.getOffer().getBankAccountCountry().getName().compareTo(o2 - .getOffer() - .getBankAccountCountry().getName()));*/ - bankAccountTypeColumn.setComparator((o1, o2) -> o1.getOffer().getFiatAccountType().compareTo(o2.getOffer() - .getFiatAccountType())); - } - - public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) { - this.offerActionHandler = offerActionHandler; - } + /////////////////////////////////////////////////////////////////////////////////////////// + // Table + /////////////////////////////////////////////////////////////////////////////////////////// private void setAmountColumnCellFactory() { amountColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); @@ -438,6 +449,8 @@ public class OfferBookView extends ActivatableViewAndModel() { final ImageView iconView = new ImageView(); final Button button = new Button(); + ChangeListener countryChangeListener; + OfferBookListItem item; { button.setGraphic(iconView); @@ -493,14 +506,26 @@ public class OfferBookView extends ActivatableViewAndModel takeOffer(item.getOffer())); } - //TODO remove listener - item.bankAccountCountryProperty().addListener((ov, o, n) -> verifyIfTradable(item)); + + if (countryChangeListener != null && this.item != null) + item.bankAccountCountryProperty().removeListener(countryChangeListener); + + this.item = item; + countryChangeListener = (ov, o, n) -> verifyIfTradable(this.item); + item.bankAccountCountryProperty().addListener(countryChangeListener); + + verifyIfTradable(item); button.setText(title); setGraphic(button); } else { + if (this.item != null) { + this.item.bankAccountCountryProperty().removeListener(countryChangeListener); + this.item = null; + countryChangeListener = null; + } setGraphic(null); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java index 5dde1e3777..9736d2be89 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java @@ -35,6 +35,7 @@ import com.google.inject.Inject; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; import javafx.collections.transformation.SortedList; import org.slf4j.Logger; @@ -54,6 +55,17 @@ class OfferBookViewModel extends ActivatableWithDataModel im final StringProperty fiatCode = new SimpleStringProperty(); final StringProperty restrictionsInfo = new SimpleStringProperty(); + private ChangeListener amountListener; + private ChangeListener priceListener; + private ChangeListener volumeListener; + private ChangeListener amountAsCoinListener; + private ChangeListener priceAsFiatListener; + private ChangeListener volumeAsFiatListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// @Inject public OfferBookViewModel(OfferBookDataModel dataModel, OptionalFiatValidator optionalFiatValidator, @@ -64,58 +76,116 @@ class OfferBookViewModel extends ActivatableWithDataModel im this.optionalBtcValidator = optionalBtcValidator; this.formatter = formatter; + createListeners(); + } + + @Override + protected void doActivate() { + addBindings(); + addListeners(); + } + + @Override + protected void doDeactivate() { + removeBindings(); + removeListeners(); + } + + private void addBindings() { btcCode.bind(dataModel.btcCode); fiatCode.bind(dataModel.fiatCode); restrictionsInfo.bind(dataModel.restrictionsInfo); + } - // Bidirectional bindings are used for all input fields: amount, price and volume - // We do volume/amount calculation during input, so user has immediate feedback - amount.addListener((ov, oldValue, newValue) -> { + private void removeBindings() { + btcCode.unbind(); + fiatCode.unbind(); + restrictionsInfo.unbind(); + } + + private void createListeners() { + amountAsCoinListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue)); + priceAsFiatListener = (ov, oldValue, newValue) -> price.set(formatter.formatFiat(newValue)); + volumeAsFiatListener = (ov, oldValue, newValue) -> volume.set(formatter.formatFiat(newValue)); + + amountListener = (ov, oldValue, newValue) -> { if (isBtcInputValid(newValue).isValid) { setAmountToModel(); setPriceToModel(); dataModel.calculateVolume(); } - }); - - price.addListener((ov, oldValue, newValue) -> { + }; + priceListener = (ov, oldValue, newValue) -> { if (isFiatInputValid(newValue).isValid) { setAmountToModel(); setPriceToModel(); dataModel.calculateVolume(); } - }); - - volume.addListener((ov, oldValue, newValue) -> { + }; + volumeListener = (ov, oldValue, newValue) -> { if (isFiatInputValid(newValue).isValid) { setPriceToModel(); setVolumeToModel(); dataModel.calculateAmount(); } - }); + }; + } + private void addListeners() { // Binding with Bindings.createObjectBinding does not work because of bi-directional binding - dataModel.amountAsCoinProperty().addListener((ov, oldValue, newValue) -> amount.set(formatter.formatCoin - (newValue))); - dataModel.priceAsFiatProperty().addListener((ov, oldValue, newValue) -> price.set(formatter.formatFiat(newValue))); - dataModel.volumeAsFiatProperty().addListener((ov, oldValue, newValue) -> volume.set(formatter.formatFiat - (newValue))); + dataModel.amountAsCoinProperty().addListener(amountAsCoinListener); + dataModel.priceAsFiatProperty().addListener(priceAsFiatListener); + dataModel.volumeAsFiatProperty().addListener(volumeAsFiatListener); + + // Bidirectional bindings are used for all input fields: amount, price and volume + // We do volume/amount calculation during input, so user has immediate feedback + amount.addListener(amountListener); + price.addListener(priceListener); + volume.addListener(volumeListener); + + amount.set("1"); + price.set("300"); + setAmountToModel(); + setPriceToModel(); } - void onCancelOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - dataModel.onCancelOpenOffer(offer, resultHandler, errorMessageHandler); + private void removeListeners() { + amount.removeListener(amountListener); + price.removeListener(priceListener); + volume.removeListener(volumeListener); + + dataModel.amountAsCoinProperty().removeListener(amountAsCoinListener); + dataModel.priceAsFiatProperty().removeListener(priceAsFiatListener); + dataModel.volumeAsFiatProperty().removeListener(volumeAsFiatListener); } - boolean isTradable(Offer offer) { - return dataModel.isTradable(offer); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// void setDirection(Offer.Direction direction) { dataModel.setDirection(direction); } + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + void onCancelOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + dataModel.onCancelOpenOffer(offer, resultHandler, errorMessageHandler); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + boolean isTradable(Offer offer) { + return dataModel.isTradable(offer); + } + SortedList getOfferList() { return dataModel.getOfferList(); } @@ -162,6 +232,11 @@ class OfferBookViewModel extends ActivatableWithDataModel im return dataModel.getPriceAsFiat(); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + private InputValidator.ValidationResult isBtcInputValid(String input) { return optionalBtcValidator.validate(input); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java index aecaa64586..c04bbfa17c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java @@ -46,11 +46,6 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Domain for that UI element. - * Note that the create offer domain has a deeper scope in the application domain (TradeManager). - * That model is just responsible for the domain specific parts displayed needed in that UI element. - */ class TakeOfferDataModel implements Activatable, DataModel { private static final Logger log = LoggerFactory.getLogger(TakeOfferDataModel.class); @@ -73,6 +68,13 @@ class TakeOfferDataModel implements Activatable, DataModel { final ObjectProperty offerFeeAsCoin = new SimpleObjectProperty<>(); final ObjectProperty networkFeeAsCoin = new SimpleObjectProperty<>(); + private BalanceListener balanceListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + @Inject public TakeOfferDataModel(TradeManager tradeManager, WalletService walletService, @@ -87,58 +89,89 @@ class TakeOfferDataModel implements Activatable, DataModel { @Override public void activate() { - btcCode.bind(preferences.btcDenominationProperty()); + addBindings(); + addListeners(); } @Override public void deactivate() { - btcCode.unbind(); + removeBindings(); + removeListeners(); tradeManager.onCancelAvailabilityRequest(offer); } + private void addBindings() { + btcCode.bind(preferences.btcDenominationProperty()); + } + + private void removeBindings() { + btcCode.unbind(); + } + + private void addListeners() { + walletService.addBalanceListener(balanceListener); + } + + private void removeListeners() { + if (addressEntry != null) + walletService.removeBalanceListener(balanceListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + void initWithData(Coin amount, Offer offer) { this.offer = offer; securityDepositAsCoin.set(offer.getSecurityDeposit()); - if (amount != null && - !amount.isGreaterThan(offer.getAmount()) && - !offer.getMinAmount().isGreaterThan(amount)) { + if (amount != null && !amount.isGreaterThan(offer.getAmount()) && !offer.getMinAmount().isGreaterThan(amount)) amountAsCoin.set(amount); - } - else { + else amountAsCoin.set(offer.getAmount()); - } calculateVolume(); calculateTotalToPay(); addressEntry = walletService.getAddressEntry(offer.getId()); - walletService.addBalanceListener(new BalanceListener(addressEntry.getAddress()) { + assert addressEntry != null; + + balanceListener = new BalanceListener(addressEntry.getAddress()) { @Override public void onBalanceChanged(@NotNull Coin balance) { updateBalance(balance); } - }); + }; updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress())); tradeManager.onCheckOfferAvailability(offer); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI calls + /////////////////////////////////////////////////////////////////////////////////////////// + void onTakeOffer(TakeOfferResultHandler handler) { - tradeManager.onTakeOffer(amountAsCoin.get(), offer, handler::handleResult); + tradeManager.onTakeOffer(amountAsCoin.get(), offer, handler); } + void onSecurityDepositInfoDisplayed() { + preferences.setDisplaySecurityDepositInfo(false); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + void calculateVolume() { - try { - if (offer != null && - offer.getPrice() != null && - amountAsCoin.get() != null && - !amountAsCoin.get().isZero()) { - volumeAsFiat.set(new ExchangeRate(offer.getPrice()).coinToFiat(amountAsCoin.get())); - } - } catch (Throwable t) { - // Should be never reached - log.error(t.toString()); + if (offer != null && + offer.getPrice() != null && + amountAsCoin.get() != null && + !amountAsCoin.get().isZero()) { + volumeAsFiat.set(new ExchangeRate(offer.getPrice()).coinToFiat(amountAsCoin.get())); } } @@ -149,6 +182,15 @@ class TakeOfferDataModel implements Activatable, DataModel { totalToPayAsCoin.set(offerFeeAsCoin.get().add(networkFeeAsCoin.get()).add(securityDepositAsCoin.get()).add(amountAsCoin.get())); } + private void updateBalance(@NotNull Coin balance) { + isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + Offer.Direction getDirection() { return offer.getDirection(); } @@ -168,15 +210,10 @@ class TakeOfferDataModel implements Activatable, DataModel { return true; } - Boolean getDisplaySecurityDepositInfo() { + boolean getDisplaySecurityDepositInfo() { return preferences.getDisplaySecurityDepositInfo(); } - void securityDepositInfoDisplayed() { - preferences.setDisplaySecurityDepositInfo(false); - } - - WalletService getWalletService() { return walletService; } @@ -184,10 +221,4 @@ class TakeOfferDataModel implements Activatable, DataModel { AddressEntry getAddressEntry() { return addressEntry; } - - - private void updateBalance(@NotNull Coin balance) { - isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); - } - } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.fxml b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.fxml index 168c381091..fbc902178d 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.fxml @@ -32,7 +32,7 @@ AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0" xmlns:fx="http://javafx.com/fxml"> - diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java index e17f890063..270d33e875 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java @@ -72,6 +72,9 @@ import static javafx.beans.binding.Bindings.createStringBinding; @FxmlView public class TakeOfferView extends ActivatableViewAndModel { + private final Navigation navigation; + private final OverlayManager overlayManager; + @FXML ScrollPane scrollPane; @FXML ImageView imageView; @FXML InputTextField amountTextField; @@ -94,11 +97,19 @@ public class TakeOfferView extends ActivatableViewAndModel errorMessageChangeListener; + private ChangeListener amountFocusedListener; + private ChangeListener isTakeOfferSpinnerVisibleListener; + private ChangeListener stateListener; + private ChangeListener showWarningInvalidBtcDecimalPlacesListener; + private ChangeListener showTransactionPublishedScreenListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// @Inject private TakeOfferView(TakeOfferViewModel model, Navigation navigation, @@ -107,19 +118,153 @@ public class TakeOfferView extends ActivatableViewAndModel { + if (newValue != null) { + Popups.openErrorPopup(BSResources.get("shared.error"), BSResources.get("takeOffer.error.message", model.errorMessage.get())); + Popups.removeBlurContent(); + Platform.runLater(this::close); + } + }; + amountFocusedListener = (o, oldValue, newValue) -> { + model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText()); + amountTextField.setText(model.amount.get()); + }; + isTakeOfferSpinnerVisibleListener = (ov, oldValue, newValue) -> { + takeOfferSpinner.setProgress(newValue ? -1 : 0); + takeOfferSpinner.setVisible(newValue); + }; + stateListener = (ov, oldValue, newValue) -> { + switch (newValue) { + case CHECK_AVAILABILITY: + showCheckAvailabilityScreen(); + break; + case AMOUNT_SCREEN: + showAmountScreen(); + break; + case PAYMENT_SCREEN: + setupPaymentScreen(); + break; + case DETAILS_SCREEN: + showDetailsScreen(); + break; + } + }; + showWarningInvalidBtcDecimalPlacesListener = (o, oldValue, newValue) -> { + if (newValue) { + Popups.openWarningPopup(BSResources.get("shared.warning"), + BSResources.get("takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces")); + model.showWarningInvalidBtcDecimalPlaces.set(false); + } + }; + showTransactionPublishedScreenListener = (o, oldValue, newValue) -> { + // TODO temp just for testing + newValue = false; + close(); + navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); + + if (newValue) { + overlayManager.blurContent(); + + // Dialogs are a bit limited. There is no callback for the InformationDialog button click, so we added + // our own actions. + List actions = new ArrayList<>(); + /* actions.add(new AbstractAction(BSResources.get("shared.copyTxId")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "COPY"); + Utilities.copyToClipboard(model.transactionId.get()); + } + });*/ + actions.add(new AbstractAction(BSResources.get("shared.close")) { + @Override + public void handle(ActionEvent actionEvent) { + getProperties().put("type", "CLOSE"); + try { + close(); + navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); + } catch (Exception e) { + e.printStackTrace(); + } + Dialog.Actions.CLOSE.handle(actionEvent); + } + }); + + Popups.openInfoPopup(BSResources.get("takeOffer.success.headline"), + BSResources.get("takeOffer.success.info"), + actions); + } + }; + } + + private void addListeners() { + amountTextField.focusedProperty().addListener(amountFocusedListener); + model.isTakeOfferSpinnerVisible.addListener(isTakeOfferSpinnerVisibleListener); + model.state.addListener(stateListener); + model.showWarningInvalidBtcDecimalPlaces.addListener(showWarningInvalidBtcDecimalPlacesListener); + model.errorMessage.addListener(errorMessageChangeListener); + model.showTransactionPublishedScreen.addListener(showTransactionPublishedScreenListener); + } + + private void removeListeners() { + amountTextField.focusedProperty().removeListener(amountFocusedListener); + model.isTakeOfferSpinnerVisible.removeListener(isTakeOfferSpinnerVisibleListener); + model.state.removeListener(stateListener); + model.showWarningInvalidBtcDecimalPlaces.removeListener(showWarningInvalidBtcDecimalPlacesListener); + model.errorMessage.removeListener(errorMessageChangeListener); + model.showTransactionPublishedScreen.removeListener(showTransactionPublishedScreenListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public void initWithData(Coin amount, Offer offer) { model.initWithData(amount, offer); @@ -156,6 +301,11 @@ public class TakeOfferView extends ActivatableViewAndModel InputTextField.hideErrorMessageDisplay()); - - // focus out - amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> { - model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText()); - amountTextField.setText(model.amount.get()); - }); - - model.state.addListener((ov, oldValue, newValue) -> { - switch (newValue) { - case CHECK_AVAILABILITY: - showCheckAvailabilityScreen(); - break; - case AMOUNT_SCREEN: - showAmountScreen(); - break; - case PAYMENT_SCREEN: - setupPaymentScreen(); - break; - case DETAILS_SCREEN: - showDetailsScreen(); - break; - } - }); - - // warnings - model.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> { - if (newValue) { - Popups.openWarningPopup(BSResources.get("shared.warning"), - BSResources.get("takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces")); - model.showWarningInvalidBtcDecimalPlaces.set(false); - } - }); - - errorMessageChangeListener = (o, oldValue, newValue) -> { - if (newValue != null) { - Popups.openErrorPopup(BSResources.get("shared.error"), BSResources.get("takeOffer.error.message", model.errorMessage.get())); - Popups.removeBlurContent(); - Platform.runLater(this::close); - } - }; - model.errorMessage.addListener(errorMessageChangeListener); - - model.showTransactionPublishedScreen.addListener((o, oldValue, newValue) -> { - // TODO temp just for testing - newValue = false; - close(); - navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); - - if (newValue) { - overlayManager.blurContent(); - - // Dialogs are a bit limited. There is no callback for the InformationDialog button click, so we added - // our own actions. - List actions = new ArrayList<>(); - /* actions.add(new AbstractAction(BSResources.get("shared.copyTxId")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "COPY"); - Utilities.copyToClipboard(model.transactionId.get()); - } - });*/ - actions.add(new AbstractAction(BSResources.get("shared.close")) { - @Override - public void handle(ActionEvent actionEvent) { - getProperties().put("type", "CLOSE"); - try { - close(); - navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); - } catch (Exception e) { - e.printStackTrace(); - } - Dialog.Actions.CLOSE.handle(actionEvent); - } - }); - - Popups.openInfoPopup(BSResources.get("takeOffer.success.headline"), - BSResources.get("takeOffer.success.info"), - actions); - } - }); - } - - private void setupBindings() { - amountBtcLabel.textProperty().bind(model.btcCode); - amountTextField.textProperty().bindBidirectional(model.amount); - volumeTextField.textProperty().bindBidirectional(model.volume); - totalToPayTextField.textProperty().bind(model.totalToPay); - addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin); - amountDescriptionLabel.textProperty().bind(model.amountDescription); - - - // Validation - amountTextField.validationResultProperty().bind(model.amountValidationResult); - - // buttons - takeOfferButton.disableProperty().bind(model.takeOfferButtonDisabled); - - takeOfferSpinnerInfoLabel.visibleProperty().bind(model.isTakeOfferSpinnerVisible); - - model.isTakeOfferSpinnerVisible.addListener((ov, oldValue, newValue) -> { - takeOfferSpinner.setProgress(newValue ? -1 : 0); - takeOfferSpinner.setVisible(newValue); - }); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // States/screens + /////////////////////////////////////////////////////////////////////////////////////////// private void showCheckAvailabilityScreen() { @@ -376,12 +423,9 @@ public class TakeOfferView extends ActivatableViewAndModel im DETAILS_SCREEN } + private final BtcValidator btcValidator; + private final BSFormatter formatter; + private final String offerFee; + private final String networkFee; + + // static fields private String amountRange; private String price; private String directionLabel; @@ -69,17 +78,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel im private String acceptedArbitratorIds; private String addressAsString; private String paymentLabel; + private boolean detailsVisible; - // Needed for the addressTextField - final ObjectProperty
address = new SimpleObjectProperty<>(); - final ObjectProperty state = new SimpleObjectProperty<>(State.CHECK_AVAILABILITY); - - private final BtcValidator btcValidator; - private final BSFormatter formatter; - private final String offerFee; - private final String networkFee; - boolean detailsVisible; - boolean advancedScreenInited; final StringProperty amount = new SimpleStringProperty(); final StringProperty volume = new SimpleStringProperty(); @@ -99,12 +99,30 @@ class TakeOfferViewModel extends ActivatableWithDataModel im final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty(); final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty(); - final ObjectProperty amountValidationResult = new SimpleObjectProperty<>(); // Needed for the addressTextField + final ObjectProperty
address = new SimpleObjectProperty<>(); final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); + + final ObjectProperty state = new SimpleObjectProperty<>(State.CHECK_AVAILABILITY); + final ObjectProperty amountValidationResult = new SimpleObjectProperty<>(); + private boolean takeOfferRequested; - private boolean isAmountAndPriceValidAndWalletFunded; + + // listeners + private ChangeListener amountChangeListener; + private ChangeListener isWalletFundedChangeListener; + private ChangeListener amountAsCoinChangeListener; + private ChangeListener offerStateChangeListener; + private ChangeListener tradeStateChangeListener; + // Offer and trade are stored only for remove listener at deactivate + private Offer offer; + private Trade trade; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// @Inject public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, BSFormatter formatter) { @@ -116,23 +134,94 @@ class TakeOfferViewModel extends ActivatableWithDataModel im this.offerFee = formatter.formatCoinWithCode(dataModel.offerFeeAsCoin.get()); this.networkFee = formatter.formatCoinWithCode(dataModel.networkFeeAsCoin.get()); - setupBindings(); - setupListeners(); - applyTakeOfferRequestResult(false); + createListeners(); } - // setOfferBookFilter is a one time call + @Override + protected void doActivate() { + addBindings(); + addListeners(); + isTakeOfferSpinnerVisible.set(false); + showTransactionPublishedScreen.set(false); + } + + @Override + protected void doDeactivate() { + removeBindings(); + removeListeners(); + } + + private void addBindings() { + volume.bind(createStringBinding(() -> formatter.formatFiatWithCode(dataModel.volumeAsFiat.get()), dataModel.volumeAsFiat)); + totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()), dataModel.totalToPayAsCoin)); + securityDeposit.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.securityDepositAsCoin.get()), dataModel.securityDepositAsCoin)); + totalToPayAsCoin.bind(dataModel.totalToPayAsCoin); + btcCode.bind(dataModel.btcCode); + } + + private void removeBindings() { + volume.unbind(); + totalToPay.unbind(); + securityDeposit.unbind(); + totalToPayAsCoin.unbind(); + btcCode.unbind(); + } + + private void createListeners() { + amountChangeListener = (ov, oldValue, newValue) -> { + if (isBtcInputValid(newValue).isValid) { + setAmountToModel(); + calculateVolume(); + dataModel.calculateTotalToPay(); + } + evaluateViewState(); + }; + isWalletFundedChangeListener = (ov, oldValue, newValue) -> evaluateViewState(); + amountAsCoinChangeListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue)); + offerStateChangeListener = (ov, oldValue, newValue) -> applyOfferState(newValue); + tradeStateChangeListener = (ov, oldValue, newValue) -> applyTradeState(newValue); + } + + private void addListeners() { + // Bidirectional bindings are used for all input fields: amount, price, volume and minAmount + // We do volume/amount calculation during input, so user has immediate feedback + amount.addListener(amountChangeListener); + dataModel.isWalletFunded.addListener(isWalletFundedChangeListener); + // Binding with Bindings.createObjectBinding does not work because of bi-directional binding + dataModel.amountAsCoin.addListener(amountAsCoinChangeListener); + + amountChangeListener.changed(null, null, amount.get()); + isWalletFundedChangeListener.changed(null, null, dataModel.isWalletFunded.get()); + amountAsCoinChangeListener.changed(null, null, dataModel.amountAsCoin.get()); + } + + private void removeListeners() { + amount.removeListener(amountChangeListener); + dataModel.isWalletFunded.removeListener(isWalletFundedChangeListener); + dataModel.amountAsCoin.removeListener(amountAsCoinChangeListener); + + if (offer != null) + offer.stateProperty().removeListener(offerStateChangeListener); + + if (trade != null) + trade.processStateProperty().removeListener(tradeStateChangeListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + void initWithData(Coin amount, Offer offer) { dataModel.initWithData(amount, offer); - directionLabel = offer.getDirection() == Offer.Direction.SELL ? - BSResources.get("shared.buyBitcoin") : BSResources.get("shared.sellBitcoin"); + this.offer = offer; + + directionLabel = offer.getDirection() == Offer.Direction.SELL ? BSResources.get("shared.buyBitcoin") : BSResources.get("shared.sellBitcoin"); fiatCode.set(offer.getCurrencyCode()); - if (!dataModel.isMinAmountLessOrEqualAmount()) { - amountValidationResult.set(new InputValidator.ValidationResult(false, - BSResources.get("takeOffer.validation.amountSmallerThanMinAmount"))); - } + if (!dataModel.isMinAmountLessOrEqualAmount()) + amountValidationResult.set(new InputValidator.ValidationResult(false, BSResources.get("takeOffer.validation.amountSmallerThanMinAmount"))); if (dataModel.getDirection() == Offer.Direction.BUY) { amountDescription.set(BSResources.get("takeOffer.amountPriceBox.buy.amountDescription", offer.getId())); @@ -147,17 +236,13 @@ class TakeOfferViewModel extends ActivatableWithDataModel im fundsBoxInfoDisplay.set(BSResources.get("takeOffer.fundsBox.sell.info")); } - //model.volumeAsFiat.set(offer.getVolumeByAmount(model.amountAsCoin.get())); - - amountRange = formatter.formatCoinWithCode(offer.getMinAmount()) + " - " + - formatter.formatCoinWithCode(offer.getAmount()); + amountRange = formatter.formatCoinWithCode(offer.getMinAmount()) + " - " + formatter.formatCoinWithCode(offer.getAmount()); price = formatter.formatFiatWithCode(offer.getPrice()); paymentLabel = BSResources.get("takeOffer.fundsBox.paymentLabel", offer.getId()); - if (dataModel.getAddressEntry() != null) { - addressAsString = dataModel.getAddressEntry().getAddress().toString(); - address.set(dataModel.getAddressEntry().getAddress()); - } + assert dataModel.getAddressEntry() != null; + addressAsString = dataModel.getAddressEntry().getAddress().toString(); + address.set(dataModel.getAddressEntry().getAddress()); acceptedCountries = formatter.countryLocalesToString(offer.getAcceptedCountries()); acceptedLanguages = formatter.languageCodesToString(offer.getAcceptedLanguageCodes()); @@ -166,12 +251,43 @@ class TakeOfferViewModel extends ActivatableWithDataModel im bankAccountCurrency = BSResources.get(CurrencyUtil.getDisplayName(offer.getCurrencyCode())); bankAccountCounty = BSResources.get(offer.getBankAccountCountry().name); - offer.stateProperty().addListener((ov, oldValue, newValue) -> applyOfferState(newValue)); + offer.stateProperty().addListener(offerStateChangeListener); applyOfferState(offer.stateProperty().get()); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // UI actions + /////////////////////////////////////////////////////////////////////////////////////////// + + void onTakeOffer() { + takeOfferRequested = true; + applyOnTakeOfferResult(false); + + isTakeOfferSpinnerVisible.set(true); + dataModel.onTakeOffer((trade) -> { + this.trade = trade; + trade.processStateProperty().addListener(tradeStateChangeListener); + applyTradeState(trade.processStateProperty().get()); + evaluateViewState(); + }); + } + + void onShowPaymentScreen() { + state.set(State.PAYMENT_SCREEN); + } + + void onToggleShowAdvancedSettings() { + detailsVisible = !detailsVisible; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // States + /////////////////////////////////////////////////////////////////////////////////////////// + private void applyOfferState(Offer.State state) { - log.debug("offer state = " + state); + log.debug("applyOfferState state = " + state); switch (state) { case UNDEFINED: @@ -201,14 +317,20 @@ class TakeOfferViewModel extends ActivatableWithDataModel im errorMessage.set("You cannot take that offer because the offerer is offline."); takeOfferRequested = false; break; - /* case FAULT: + case FAULT: if (takeOfferRequested) errorMessage.set("Take offer request failed."); else errorMessage.set("The check for the offer availability failed."); - takeOfferRequested = false; - break;*/ + break; + case TIMEOUT: + if (takeOfferRequested) + errorMessage.set("Take offer request failed due a timeout."); + else + errorMessage.set("The check for the offer availability failed due a timeout."); + takeOfferRequested = false; + break; default: log.error("Unhandled offer state: " + state); break; @@ -218,125 +340,91 @@ class TakeOfferViewModel extends ActivatableWithDataModel im isTakeOfferSpinnerVisible.set(false); } - evaluateState(); + evaluateViewState(); } - void onTakeOffer() { - takeOfferRequested = true; - applyTakeOfferRequestResult(false); + private void applyTradeState(TradeState.ProcessState state) { + log.debug("applyTradeState state = " + state); - isTakeOfferSpinnerVisible.set(true); + String msg = "An error occurred."; + if (trade.getErrorMessage() != null) + msg = "Error message: " + trade.getErrorMessage(); - dataModel.onTakeOffer((trade) -> { - trade.processStateProperty().addListener((ov, oldValue, newValue) -> { - log.debug("trade state = " + newValue); + if (trade instanceof SellerAsTakerTrade) { + switch ((SellerTradeState.ProcessState) state) { + case UNDEFINED: + break; + case DEPOSIT_PUBLISHED_MSG_RECEIVED: + assert trade.getDepositTx() != null; + transactionId.set(trade.getDepositTx().getHashAsString()); + applyOnTakeOfferResult(true); + break; + case DEPOSIT_CONFIRMED: + case FIAT_PAYMENT_STARTED_MSG_RECEIVED: + case FIAT_PAYMENT_RECEIPT: + case FIAT_PAYMENT_RECEIPT_MSG_SENT: + case PAYOUT_TX_RECEIVED: + case PAYOUT_TX_COMMITTED: + case PAYOUT_BROAD_CASTED: + break; + case TIMEOUT: + errorMessage.set("A timeout occurred. Maybe there are connection problems. " + + "Please try later again.\n" + msg); + takeOfferRequested = false; + break; + case FAULT: + errorMessage.set(msg); + takeOfferRequested = false; + break; + default: + log.warn("Unhandled trade state: " + state); + break; + } + } + else if (trade instanceof BuyerAsTakerTrade) { + switch ((BuyerTradeState.ProcessState) state) { + case UNDEFINED: + break; + case DEPOSIT_PUBLISHED: + assert trade.getDepositTx() != null; + transactionId.set(trade.getDepositTx().getHashAsString()); + applyOnTakeOfferResult(true); + break; + case DEPOSIT_PUBLISHED_MSG_SENT: + case DEPOSIT_CONFIRMED: + case FIAT_PAYMENT_STARTED: + case FIAT_PAYMENT_STARTED_MSG_SENT: + case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED: + case PAYOUT_TX_COMMITTED: + case PAYOUT_TX_SENT: + case PAYOUT_BROAD_CASTED: + break; + case TIMEOUT: + errorMessage.set("A timeout occurred. Maybe there are connection problems. " + + "Please try later again.\n" + msg); + takeOfferRequested = false; + break; + case FAULT: + errorMessage.set(msg); + takeOfferRequested = false; + break; + default: + log.warn("Unhandled trade state: " + state); + break; + } + } - String msg = ""; - if (trade.getErrorMessage() != null) - msg = "\nError message: " + trade.getErrorMessage(); - - if (trade instanceof SellerAsTakerTrade) { - switch ((SellerTradeState.ProcessState) newValue) { - case UNDEFINED: - break; - case DEPOSIT_PUBLISHED_MSG_RECEIVED: - assert trade.getDepositTx() != null; - transactionId.set(trade.getDepositTx().getHashAsString()); - applyTakeOfferRequestResult(true); - break; - case DEPOSIT_CONFIRMED: - case FIAT_PAYMENT_STARTED_MSG_RECEIVED: - case FIAT_PAYMENT_RECEIPT: - case FIAT_PAYMENT_RECEIPT_MSG_SENT: - case PAYOUT_TX_RECEIVED: - case PAYOUT_TX_COMMITTED: - case PAYOUT_BROAD_CASTED: - break; - /* case TAKE_OFFER_FEE_PUBLISH_FAILED: - errorMessage.set("An error occurred when paying the trade fee." + msg); - takeOfferRequested = false; - break; - case MESSAGE_SENDING_FAILED: - errorMessage.set("An error occurred when sending a message to the offerer. Maybe there are connection problems. " + - "Please try later again." + msg); - takeOfferRequested = false; - break; - case EXCEPTION: - errorMessage.set(msg); - takeOfferRequested = false; - break;*/ - default: - log.warn("Unhandled trade state: " + newValue); - break; - } - } - else if (trade instanceof BuyerAsTakerTrade) { - switch ((BuyerTradeState.ProcessState) newValue) { - case UNDEFINED: - break; - case DEPOSIT_PUBLISHED: - assert trade.getDepositTx() != null; - transactionId.set(trade.getDepositTx().getHashAsString()); - applyTakeOfferRequestResult(true); - break; - case DEPOSIT_PUBLISHED_MSG_SENT: - case DEPOSIT_CONFIRMED: - case FIAT_PAYMENT_STARTED: - case FIAT_PAYMENT_STARTED_MSG_SENT: - case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED: - case PAYOUT_TX_COMMITTED: - case PAYOUT_TX_SENT: - case PAYOUT_BROAD_CASTED: - break; - /* case TAKE_OFFER_FEE_PUBLISH_FAILED: - errorMessage.set("An error occurred when paying the trade fee." + msg); - takeOfferRequested = false; - break; - case MESSAGE_SENDING_FAILED: - errorMessage.set("Sending a message to the offerer failed. Maybe there are connection problems. " + - "Please try later again." + msg); - takeOfferRequested = false; - break; - case TIMEOUT: - errorMessage.set("Timeout: We did not received a message from the offerer. Maybe there are connection problems. " + - "Please try later again." + msg); - takeOfferRequested = false; - break; - case EXCEPTION: - errorMessage.set(msg); - takeOfferRequested = false; - break;*/ - default: - log.warn("Unhandled trade state: " + newValue); - break; - } - } - - if (errorMessage.get() != null) { - isAmountAndPriceValidAndWalletFunded = false; - isTakeOfferSpinnerVisible.set(false); - } - }); - - evaluateState(); - }); - } - - boolean isSeller() { - return dataModel.getDirection() == Offer.Direction.BUY; - } - - void securityDepositInfoDisplayed() { - dataModel.securityDepositInfoDisplayed(); + if (errorMessage.get() != null) + isTakeOfferSpinnerVisible.set(false); } - void onShowPaymentScreen() { - state.set(State.PAYMENT_SCREEN); - } + /////////////////////////////////////////////////////////////////////////////////////////// + // Focus handling + /////////////////////////////////////////////////////////////////////////////////////////// // On focus out we do validation and apply the data to the model - void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue, String userInput) { + void onFocusOutAmountTextField(boolean oldValue, boolean newValue, String userInput) { if (oldValue && !newValue) { InputValidator.ValidationResult result = isBtcInputValid(amount.get()); amountValidationResult.set(result); @@ -361,6 +449,18 @@ class TakeOfferViewModel extends ActivatableWithDataModel im } + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters + /////////////////////////////////////////////////////////////////////////////////////////// + + boolean isSeller() { + return dataModel.getDirection() == Offer.Direction.BUY; + } + + void securityDepositInfoDisplayed() { + dataModel.onSecurityDepositInfoDisplayed(); + } + WalletService getWalletService() { return dataModel.getWalletService(); } @@ -429,50 +529,24 @@ class TakeOfferViewModel extends ActivatableWithDataModel im return paymentLabel; } - Boolean getDisplaySecurityDepositInfo() { + boolean getDisplaySecurityDepositInfo() { return dataModel.getDisplaySecurityDepositInfo(); } - - private void setupListeners() { - // Bidirectional bindings are used for all input fields: amount, price, volume and minAmount - // We do volume/amount calculation during input, so user has immediate feedback - amount.addListener((ov, oldValue, newValue) -> { - if (isBtcInputValid(newValue).isValid) { - setAmountToModel(); - calculateVolume(); - dataModel.calculateTotalToPay(); - } - evaluateState(); - }); - - dataModel.isWalletFunded.addListener((ov, oldValue, newValue) -> { - evaluateState(); - }); - - // Binding with Bindings.createObjectBinding does not work because of bi-directional binding - dataModel.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue))); + boolean isDetailsVisible() { + return detailsVisible; } - private void applyTakeOfferRequestResult(boolean success) { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Utils + /////////////////////////////////////////////////////////////////////////////////////////// + + private void applyOnTakeOfferResult(boolean success) { isTakeOfferSpinnerVisible.set(false); showTransactionPublishedScreen.set(success); } - private void setupBindings() { - volume.bind(createStringBinding(() -> formatter.formatFiatWithCode(dataModel.volumeAsFiat.get()), - dataModel.volumeAsFiat)); - totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()), - dataModel.totalToPayAsCoin)); - securityDeposit.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.securityDepositAsCoin - .get()), - dataModel.securityDepositAsCoin)); - - totalToPayAsCoin.bind(dataModel.totalToPayAsCoin); - - btcCode.bind(dataModel.btcCode); - } - private void calculateVolume() { setAmountToModel(); dataModel.calculateVolume(); @@ -482,8 +556,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel im dataModel.amountAsCoin.set(formatter.parseToCoinWith4Decimals(amount.get())); } - private void evaluateState() { - isAmountAndPriceValidAndWalletFunded = isBtcInputValid(amount.get()).isValid && + private void evaluateViewState() { + boolean isAmountAndPriceValidAndWalletFunded = isBtcInputValid(amount.get()).isValid && dataModel.isMinAmountLessOrEqualAmount() && !dataModel.isAmountLargerThanOfferAmount() && dataModel.isWalletFunded.get(); diff --git a/gui/src/main/java/io/bitsquare/gui/util/GUIUtil.java b/gui/src/main/java/io/bitsquare/gui/util/GUIUtil.java index c8ee38dedf..c896bffd00 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/GUIUtil.java +++ b/gui/src/main/java/io/bitsquare/gui/util/GUIUtil.java @@ -17,10 +17,6 @@ package io.bitsquare.gui.util; -import java.util.function.Function; - -import javafx.animation.AnimationTimer; -import javafx.application.Platform; import javafx.scene.input.*; import org.slf4j.Logger; @@ -37,36 +33,4 @@ public class GUIUtil { clipboard.setContent(clipboardContent); } } - - public static AnimationTimer setTimeout(int delay, Function callback) { - AnimationTimer animationTimer = new AnimationTimer() { - final long lastTimeStamp = System.currentTimeMillis(); - - @Override - public void handle(long arg0) { - if (System.currentTimeMillis() > delay + lastTimeStamp) { - Platform.runLater(() -> callback.apply(this)); - this.stop(); - } - } - }; - animationTimer.start(); - return animationTimer; - } - - public static AnimationTimer setInterval(int delay, Function callback) { - AnimationTimer animationTimer = new AnimationTimer() { - long lastTimeStamp = System.currentTimeMillis(); - - @Override - public void handle(long arg0) { - if (System.currentTimeMillis() > delay + lastTimeStamp) { - lastTimeStamp = System.currentTimeMillis(); - callback.apply(this); - } - } - }; - animationTimer.start(); - return animationTimer; - } } diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/BtcAddressValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/BtcAddressValidator.java index ded4440a63..a3e23c074a 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/BtcAddressValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/BtcAddressValidator.java @@ -24,30 +24,15 @@ import org.bitcoinj.core.AddressFormatException; import javax.inject.Inject; -/** - * BtcValidator for validating BTC values. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. - */ public final class BtcAddressValidator extends InputValidator { private final BitcoinNetwork bitcoinNetwork; - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - @Inject public BtcAddressValidator(BitcoinNetwork bitcoinNetwork) { this.bitcoinNetwork = bitcoinNetwork; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// - @Override public ValidationResult validate(String input) { @@ -58,11 +43,6 @@ public final class BtcAddressValidator extends InputValidator { return result; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - private ValidationResult validateBtcAddress(String input) { try { new Address(bitcoinNetwork.getParameters(), input); diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/BtcValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/BtcValidator.java index 82b3c86ed5..76a09a52bf 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/BtcValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/BtcValidator.java @@ -23,16 +23,7 @@ import org.bitcoinj.core.NetworkParameters; import java.math.BigDecimal; -/** - * BtcValidator for validating BTC values. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. - */ -public final class BtcValidator extends NumberValidator { - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// +public class BtcValidator extends NumberValidator { @Override public ValidationResult validate(String input) { @@ -52,12 +43,7 @@ public final class BtcValidator extends NumberValidator { return result; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - - private ValidationResult validateIfNotFractionalBtcValue(String input) { + protected ValidationResult validateIfNotFractionalBtcValue(String input) { BigDecimal bd = new BigDecimal(input); final BigDecimal satoshis = bd.movePointRight(8); if (satoshis.scale() > 0) @@ -66,7 +52,7 @@ public final class BtcValidator extends NumberValidator { return new ValidationResult(true); } - private ValidationResult validateIfNotExceedsMaxBtcValue(String input) { + protected ValidationResult validateIfNotExceedsMaxBtcValue(String input) { BigDecimal bd = new BigDecimal(input); final BigDecimal satoshis = bd.movePointRight(8); if (satoshis.longValue() > NetworkParameters.MAX_MONEY.longValue()) diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/FiatValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/FiatValidator.java index 11a4f327c3..baee71e3f0 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/FiatValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/FiatValidator.java @@ -23,18 +23,12 @@ import io.bitsquare.user.User; import javax.inject.Inject; -/** - * FiatNumberValidator for validating fiat values. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. - */ -public final class FiatValidator extends NumberValidator { +public class FiatValidator extends NumberValidator { //TODO Find appropriate values - depends on currencies public static final double MIN_FIAT_VALUE = 0.01; // usually a cent is the smallest currency unit public static final double MAX_FIAT_VALUE = 1000000; - private String currencyCode = "Fiat"; - + protected String currencyCode = "Fiat"; @Inject public FiatValidator(User user) { @@ -51,11 +45,6 @@ public final class FiatValidator extends NumberValidator { } } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// - @Override public ValidationResult validate(String input) { ValidationResult result = validateIfNotEmpty(input); @@ -74,21 +63,11 @@ public final class FiatValidator extends NumberValidator { return result; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setter - /////////////////////////////////////////////////////////////////////////////////////////// - public void setFiatCurrencyCode(String currencyCode) { this.currencyCode = currencyCode; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - - private ValidationResult validateIfNotExceedsMinFiatValue(String input) { + protected ValidationResult validateIfNotExceedsMinFiatValue(String input) { double d = Double.parseDouble(input); if (d < MIN_FIAT_VALUE) return new ValidationResult(false, BSResources.get("validation.fiat.toSmall", currencyCode)); @@ -96,7 +75,7 @@ public final class FiatValidator extends NumberValidator { return new ValidationResult(true); } - private ValidationResult validateIfNotExceedsMaxFiatValue(String input) { + protected ValidationResult validateIfNotExceedsMaxFiatValue(String input) { double d = Double.parseDouble(input); if (d > MAX_FIAT_VALUE) return new ValidationResult(false, BSResources.get("validation.fiat.toLarge", currencyCode)); diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/InputValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/InputValidator.java index 4bef1ab5bd..b4d528f2f3 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/InputValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/InputValidator.java @@ -19,25 +19,12 @@ package io.bitsquare.gui.util.validation; import io.bitsquare.locale.BSResources; -/** - * Base class for other specialized validators. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. - */ public class InputValidator { - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// - public ValidationResult validate(String input) { return validateIfNotEmpty(input); } - /////////////////////////////////////////////////////////////////////////////////////////// - // Protected methods - /////////////////////////////////////////////////////////////////////////////////////////// - protected ValidationResult validateIfNotEmpty(String input) { if (input == null || input.length() == 0) return new ValidationResult(false, BSResources.get("validation.empty")); @@ -45,10 +32,6 @@ public class InputValidator { return new ValidationResult(true); } - /////////////////////////////////////////////////////////////////////////////////////////// - // ValidationResult - /////////////////////////////////////////////////////////////////////////////////////////// - public static class ValidationResult { public final boolean isValid; public final String errorMessage; diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/NumberValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/NumberValidator.java index 70d75d2d8a..dbe447217e 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/NumberValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/NumberValidator.java @@ -24,15 +24,9 @@ import io.bitsquare.locale.BSResources; * Localisation not supported at the moment * The decimal mark can be either "." or ",". Thousand separators are not supported yet, * but might be added alter with Local support. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. */ public abstract class NumberValidator extends InputValidator { - /////////////////////////////////////////////////////////////////////////////////////////// - // Protected methods - /////////////////////////////////////////////////////////////////////////////////////////// - protected String cleanInput(String input) { return input.replace(",", ".").trim(); } diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalBtcValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalBtcValidator.java index 9edb0ac510..cf683f8ebd 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalBtcValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalBtcValidator.java @@ -17,24 +17,11 @@ package io.bitsquare.gui.util.validation; -import io.bitsquare.locale.BSResources; - -import org.bitcoinj.core.NetworkParameters; - -import java.math.BigDecimal; - /** - * BtcValidator for validating BTC values. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. + * That validator accepts empty inputs */ -public final class OptionalBtcValidator extends NumberValidator { - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// - +public class OptionalBtcValidator extends BtcValidator { + @Override public ValidationResult validate(String input) { ValidationResult result = validateIfNotEmpty(input); @@ -63,27 +50,4 @@ public final class OptionalBtcValidator extends NumberValidator { return result; } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - - private ValidationResult validateIfNotFractionalBtcValue(String input) { - BigDecimal bd = new BigDecimal(input); - final BigDecimal satoshis = bd.movePointRight(8); - if (satoshis.scale() > 0) - return new ValidationResult(false, BSResources.get("validation.btc.toSmall")); - else - return new ValidationResult(true); - } - - private ValidationResult validateIfNotExceedsMaxBtcValue(String input) { - BigDecimal bd = new BigDecimal(input); - final BigDecimal satoshis = bd.movePointRight(8); - if (satoshis.longValue() > NetworkParameters.MAX_MONEY.longValue()) - return new ValidationResult(false, BSResources.get("validation.btc.toLarge")); - else - return new ValidationResult(true); - } } diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalFiatValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalFiatValidator.java index 0fcb76ebdf..5d16a95d63 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalFiatValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/OptionalFiatValidator.java @@ -17,45 +17,20 @@ package io.bitsquare.gui.util.validation; -import io.bitsquare.locale.BSResources; -import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.user.User; import javax.inject.Inject; /** - * FiatNumberValidator for validating fiat values. - *

- * That class implements just what we need for the moment. It is not intended as a general purpose library class. + * That validator accepts empty inputs */ -public final class OptionalFiatValidator extends NumberValidator { - - //TODO Find appropriate values - depends on currencies - public static final double MIN_FIAT_VALUE = 0.01; // usually a cent is the smallest currency unit - public static final double MAX_FIAT_VALUE = 1000000; - private String currencyCode = "Fiat"; - +public class OptionalFiatValidator extends FiatValidator { @Inject public OptionalFiatValidator(User user) { - if (user != null) { - if (user.currentFiatAccountProperty().get() == null) - setFiatCurrencyCode(CurrencyUtil.getDefaultCurrencyAsCode()); - else if (user.currentFiatAccountProperty().get() != null) - setFiatCurrencyCode(user.currentFiatAccountProperty().get().currencyCode); - - user.currentFiatAccountProperty().addListener((ov, oldValue, newValue) -> { - if (newValue != null) - setFiatCurrencyCode(newValue.currencyCode); - }); - } + super(user); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// - @Override public ValidationResult validate(String input) { ValidationResult result = validateIfNotEmpty(input); @@ -84,34 +59,4 @@ public final class OptionalFiatValidator extends NumberValidator { return result; } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setter - /////////////////////////////////////////////////////////////////////////////////////////// - - public void setFiatCurrencyCode(String currencyCode) { - this.currencyCode = currencyCode; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - - private ValidationResult validateIfNotExceedsMinFiatValue(String input) { - double d = Double.parseDouble(input); - if (d < MIN_FIAT_VALUE) - return new ValidationResult(false, BSResources.get("validation.fiat.toSmall", currencyCode)); - else - return new ValidationResult(true); - } - - private ValidationResult validateIfNotExceedsMaxFiatValue(String input) { - double d = Double.parseDouble(input); - if (d > MAX_FIAT_VALUE) - return new ValidationResult(false, BSResources.get("validation.fiat.toLarge", currencyCode)); - else - return new ValidationResult(true); - } } diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/PasswordValidator.java b/gui/src/main/java/io/bitsquare/gui/util/validation/PasswordValidator.java index cb44bb7572..b57f5fbad7 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/validation/PasswordValidator.java +++ b/gui/src/main/java/io/bitsquare/gui/util/validation/PasswordValidator.java @@ -21,10 +21,6 @@ import io.bitsquare.locale.BSResources; public final class PasswordValidator extends InputValidator { - /////////////////////////////////////////////////////////////////////////////////////////// - // Public methods - /////////////////////////////////////////////////////////////////////////////////////////// - @Override public ValidationResult validate(String input) { ValidationResult result = validateIfNotEmpty(input); @@ -33,11 +29,6 @@ public final class PasswordValidator extends InputValidator { return result; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - private ValidationResult validateMinLength(String input) { if (input.length() > 7) return new ValidationResult(true); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java b/gui/src/test/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java similarity index 99% rename from gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java rename to gui/src/test/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java index 04c248368d..792da20e44 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java +++ b/gui/src/test/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferViewModelTest.java @@ -44,7 +44,7 @@ public class CreateOfferViewModelTest { BSFormatter formatter = new BSFormatter(new User(), null); formatter.setLocale(Locale.US); formatter.setFiatCurrencyCode("USD"); - model = new CreateOfferDataModel(null, null, null, null, null, null, formatter); + model = new CreateOfferDataModel(null, null, null, null, null, formatter); presenter = new CreateOfferViewModel(model, new FiatValidator(null), new BtcValidator(), formatter); } diff --git a/gui/src/main/java/io/bitsquare/gui/util/BSFormatterTest.java b/gui/src/test/java/io/bitsquare/gui/util/BSFormatterTest.java similarity index 100% rename from gui/src/main/java/io/bitsquare/gui/util/BSFormatterTest.java rename to gui/src/test/java/io/bitsquare/gui/util/BSFormatterTest.java diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/BtcValidatorTest.java b/gui/src/test/java/io/bitsquare/gui/util/validation/BtcValidatorTest.java similarity index 100% rename from gui/src/main/java/io/bitsquare/gui/util/validation/BtcValidatorTest.java rename to gui/src/test/java/io/bitsquare/gui/util/validation/BtcValidatorTest.java diff --git a/gui/src/main/java/io/bitsquare/gui/util/validation/FiatValidatorTest.java b/gui/src/test/java/io/bitsquare/gui/util/validation/FiatValidatorTest.java similarity index 100% rename from gui/src/main/java/io/bitsquare/gui/util/validation/FiatValidatorTest.java rename to gui/src/test/java/io/bitsquare/gui/util/validation/FiatValidatorTest.java