mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Remove listeners/unbind at views. Add more error handling
This commit is contained in:
parent
919e31f0d5
commit
3f6f8dd160
@ -23,8 +23,7 @@ import org.slf4j.LoggerFactory;
|
||||
public abstract class Task<T extends Model> {
|
||||
private static final Logger log = LoggerFactory.getLogger(Task.class);
|
||||
|
||||
public static Class<? extends Task> taskToInterceptBeforeRun;
|
||||
public static Class<? extends Task> taskToInterceptAfterRun;
|
||||
public static Class<? extends Task> taskToIntercept;
|
||||
|
||||
private final TaskRunner taskHandler;
|
||||
protected final T model;
|
||||
@ -35,26 +34,11 @@ public abstract class Task<T extends Model> {
|
||||
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<T extends Model> {
|
||||
}
|
||||
|
||||
protected void complete() {
|
||||
try {
|
||||
interceptBeforeComplete();
|
||||
} catch (Throwable t) {
|
||||
appendExceptionToErrorMessage(t);
|
||||
failed();
|
||||
}
|
||||
taskHandler.handleComplete();
|
||||
}
|
||||
|
||||
@ -84,6 +62,7 @@ public abstract class Task<T extends Model> {
|
||||
}
|
||||
|
||||
protected void failed(Throwable t) {
|
||||
t.printStackTrace();
|
||||
appendExceptionToErrorMessage(t);
|
||||
failed();
|
||||
}
|
||||
|
@ -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<T extends Serializable> {
|
||||
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.");
|
||||
|
@ -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);
|
||||
|
@ -59,7 +59,10 @@ public class Offer implements Serializable {
|
||||
AVAILABLE,
|
||||
NOT_AVAILABLE,
|
||||
REMOVED,
|
||||
OFFERER_OFFLINE
|
||||
OFFERER_OFFLINE,
|
||||
|
||||
TIMEOUT,
|
||||
FAULT
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<OfferAvailabilityModel> 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;
|
||||
|
@ -37,13 +37,13 @@ public class GetPeerAddress extends Task<OfferAvailabilityModel> {
|
||||
}
|
||||
|
||||
@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<OfferAvailabilityModel> {
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
model.offer.setState(Offer.State.FAULT);
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,9 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRun() {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
OfferAvailabilityResponse offerAvailabilityResponse = (OfferAvailabilityResponse) model.getMessage();
|
||||
|
||||
if (model.offer.getState() != Offer.State.REMOVED) {
|
||||
@ -47,6 +48,7 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
model.offer.setState(Offer.State.FAULT);
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,9 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
||||
}
|
||||
|
||||
@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<OfferAvailabilityModel> {
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
model.offer.setState(Offer.State.FAULT);
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
@ -33,9 +33,17 @@ public class AddOfferToRemoteOfferBook extends Task<PlaceOfferModel> {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,58 +47,61 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
|
||||
}
|
||||
|
||||
@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<Transaction>() {
|
||||
@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<Transaction>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,9 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRun() {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
Transaction transaction = model.tradeWalletService.createOfferFeeTx(
|
||||
model.walletService.getAddressEntry(model.offer.getId()));
|
||||
|
||||
|
@ -32,8 +32,9 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRun() {
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
model.offer.validate();
|
||||
|
||||
complete();
|
||||
|
@ -102,8 +102,8 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
|
||||
CreateDepositTxInputs.class,
|
||||
SendPayDepositRequest.class
|
||||
);
|
||||
taskRunner.run();
|
||||
startTimeout();
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,8 +116,8 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
|
||||
CreateDepositTxInputs.class,
|
||||
SendPayDepositRequest.class
|
||||
);
|
||||
taskRunner.run();
|
||||
startTimeout();
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,8 +100,8 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
|
||||
CreateAndSignDepositTx.class,
|
||||
SendPublishDepositTxRequest.class
|
||||
);
|
||||
taskRunner.run();
|
||||
startTimeout();
|
||||
taskRunner.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -38,6 +38,16 @@ public class TradeTask extends Task<Trade> {
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
);
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
|
@ -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) {
|
||||
|
@ -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(),
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 " +
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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<Transaction>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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())) {
|
||||
|
@ -38,6 +38,9 @@ public class BuyerTradeState {
|
||||
PAYOUT_TX_COMMITTED,
|
||||
PAYOUT_TX_SENT,
|
||||
|
||||
PAYOUT_BROAD_CASTED
|
||||
PAYOUT_BROAD_CASTED,
|
||||
|
||||
TIMEOUT,
|
||||
FAULT
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ public class SellerTradeState {
|
||||
PAYOUT_TX_RECEIVED,
|
||||
PAYOUT_TX_COMMITTED,
|
||||
|
||||
PAYOUT_BROAD_CASTED
|
||||
PAYOUT_BROAD_CASTED,
|
||||
|
||||
TIMEOUT,
|
||||
FAULT
|
||||
}
|
||||
}
|
||||
|
26
core/src/main/java/io/bitsquare/trade/states/TradePhase.java
Normal file
26
core/src/main/java/io/bitsquare/trade/states/TradePhase.java
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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<State, State> 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();
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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() {
|
||||
|
@ -44,39 +44,11 @@
|
||||
</GridPane.margin>
|
||||
</ComboBox>
|
||||
|
||||
|
||||
<!-- <Label text="P2P network connection:" GridPane.rowIndex="1"/>
|
||||
<TextField fx:id="connectionType" GridPane.rowIndex="1" GridPane.columnIndex="1"
|
||||
mouseTransparent="true" focusTraversable="false"/>
|
||||
|
||||
<Label text="My external visible P2P network address:" GridPane.rowIndex="2"/>
|
||||
<TextField fx:id="nodeAddress" GridPane.rowIndex="2" GridPane.columnIndex="1"
|
||||
mouseTransparent="true" focusTraversable="false"/>
|
||||
-->
|
||||
|
||||
|
||||
<Label text="Intercept before run?:" GridPane.rowIndex="3">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="-15"/>
|
||||
</GridPane.margin>
|
||||
</Label>
|
||||
<CheckBox fx:id="interceptBeforeCheckBox" onAction="#onCheckBoxChanged" GridPane.rowIndex="3" GridPane.columnIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="-15"/>
|
||||
</GridPane.margin>
|
||||
</CheckBox>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="NEVER"/>
|
||||
<RowConstraints vgrow="NEVER"/>
|
||||
<RowConstraints vgrow="NEVER"/>
|
||||
</rowConstraints>
|
||||
|
||||
</GridPane>
|
||||
|
||||
|
||||
|
@ -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<Class> 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<Class> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Coin> offerFeeAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> networkFeeAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> securityDepositAsCoin = new SimpleObjectProperty<>();
|
||||
private final BalanceListener balanceListener;
|
||||
private final ChangeListener<FiatAccount> 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<Fiat> priceAsFiat = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> offerFeeAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> networkFeeAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> securityDepositAsCoin = new SimpleObjectProperty<>();
|
||||
|
||||
final ObservableList<Country> acceptedCountries = FXCollections.observableArrayList();
|
||||
final ObservableList<String> acceptedLanguageCodes = FXCollections.observableArrayList();
|
||||
final ObservableList<Arbitrator> 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;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.offer.createoffer.CreateOfferView"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<ScrollPane fx:id="scrollPane" hbarPolicy="NEVER" vbarPolicy="NEVER" fitToWidth="true" fitToHeight="true"
|
||||
<ScrollPane fx:id="scrollPane" onScroll="#onScroll" hbarPolicy="NEVER" vbarPolicy="NEVER" fitToWidth="true" fitToHeight="true"
|
||||
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
|
||||
AnchorPane.bottomAnchor="0.0">
|
||||
|
||||
|
@ -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<AnchorPane, CreateOfferViewModel> {
|
||||
|
||||
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<AnchorPane, CreateO
|
||||
private boolean detailsVisible;
|
||||
private boolean advancedScreenInited;
|
||||
|
||||
private final Navigation navigation;
|
||||
private final OverlayManager overlayManager;
|
||||
private OfferView.CloseHandler closeHandler;
|
||||
|
||||
private ChangeListener<Boolean> amountFocusedListener;
|
||||
private ChangeListener<Boolean> minAmountFocusedListener;
|
||||
private ChangeListener<Boolean> priceFocusedListener;
|
||||
private ChangeListener<Boolean> volumeFocusedListener;
|
||||
private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener;
|
||||
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
|
||||
private ChangeListener<Boolean> showWarningAdjustedVolumeListener;
|
||||
private ChangeListener<String> requestPlaceOfferErrorMessageListener;
|
||||
private ChangeListener<Boolean> isPlaceOfferSpinnerVisibleListener;
|
||||
private ChangeListener<Boolean> 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<Action> 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<AnchorPane, CreateO
|
||||
this.closeHandler = closeHandler;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI actions
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@FXML
|
||||
void onPlaceOffer() {
|
||||
model.onPlaceOffer();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onScroll() {
|
||||
InputTextField.hideErrorMessageDisplay();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onShowPayFundsScreen() {
|
||||
// TODO deactivate for testing the moment
|
||||
@ -226,6 +466,11 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
Help.openWindow(HelpId.CREATE_OFFER_ADVANCED);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Navigation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void openAccountSettings() {
|
||||
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, RestrictionsView.class);
|
||||
}
|
||||
@ -235,150 +480,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
closeHandler.close();
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
scrollPane.setOnScroll(e -> 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<Action> 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<AnchorPane, CreateO
|
||||
advancedInfoDisplay.setVisible(visible);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void initEditIcons() {
|
||||
acceptedCountriesLabelIcon.setId("clickable-icon");
|
||||
AwesomeDude.setIcon(acceptedCountriesLabelIcon, AwesomeIcon.EDIT_SIGN);
|
||||
|
@ -33,6 +33,7 @@ import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@ -40,6 +41,7 @@ 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 static javafx.beans.binding.Bindings.createStringBinding;
|
||||
|
||||
@ -49,7 +51,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
private final BSFormatter formatter;
|
||||
private final FiatValidator fiatValidator;
|
||||
|
||||
|
||||
final StringProperty amount = new SimpleStringProperty();
|
||||
final StringProperty minAmount = new SimpleStringProperty();
|
||||
final StringProperty price = new SimpleStringProperty();
|
||||
@ -94,6 +95,25 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||
|
||||
private ChangeListener<String> amountListener;
|
||||
private ChangeListener<String> minAmountListener;
|
||||
private ChangeListener<String> priceListener;
|
||||
private ChangeListener<String> volumeListener;
|
||||
private ChangeListener<Coin> amountAsCoinListener;
|
||||
private ChangeListener<Coin> minAmountAsCoinListener;
|
||||
private ChangeListener<Fiat> priceAsFiatListener;
|
||||
private ChangeListener<Fiat> volumeAsFiatListener;
|
||||
private ChangeListener<Boolean> isWalletFundedListener;
|
||||
private ChangeListener<Boolean> requestPlaceOfferSuccessListener;
|
||||
private ChangeListener<String> 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<CreateOfferDataModel
|
||||
addressAsString.set(dataModel.getAddressEntry().getAddress().toString());
|
||||
address.set(dataModel.getAddressEntry().getAddress());
|
||||
}
|
||||
|
||||
setupBindings();
|
||||
setupListeners();
|
||||
createListeners();
|
||||
}
|
||||
|
||||
// setOfferBookFilter is a one time call
|
||||
void initWithData(Offer.Direction direction, Coin amount, Fiat price) {
|
||||
dataModel.initWithData(direction, amount, price);
|
||||
|
||||
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 (amount != null && isBtcInputValid(amount.toPlainString())
|
||||
.isValid) {
|
||||
dataModel.amountAsCoin.set(amount);
|
||||
dataModel.minAmountAsCoin.set(amount);
|
||||
amountValid = true;
|
||||
}
|
||||
|
||||
// apply only if valid
|
||||
boolean priceValid = false;
|
||||
if (price != null && isBtcInputValid(price.toPlainString()).isValid) {
|
||||
dataModel.priceAsFiat.set(formatter.parseToFiatWith2Decimals(price.toPlainString()));
|
||||
priceValid = true;
|
||||
}
|
||||
|
||||
if (amountValid && priceValid)
|
||||
dataModel.calculateTotalToPay();
|
||||
@Override
|
||||
protected void doActivate() {
|
||||
addBindings();
|
||||
addListeners();
|
||||
}
|
||||
|
||||
|
||||
void onPlaceOffer() {
|
||||
dataModel.requestPlaceOfferErrorMessage.set(null);
|
||||
dataModel.requestPlaceOfferSuccess.set(false);
|
||||
|
||||
isPlaceOfferSpinnerVisible.set(true);
|
||||
|
||||
dataModel.onPlaceOffer();
|
||||
@Override
|
||||
protected void doDeactivate() {
|
||||
removeBindings();
|
||||
removeListeners();
|
||||
}
|
||||
|
||||
|
||||
void onShowPayFundsScreen() {
|
||||
isPlaceOfferButtonVisible.set(true);
|
||||
}
|
||||
|
||||
// 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void securityDepositInfoDisplayed() {
|
||||
dataModel.securityDepositInfoDisplayed();
|
||||
}
|
||||
|
||||
|
||||
WalletService getWalletService() {
|
||||
return dataModel.getWalletService();
|
||||
}
|
||||
|
||||
BSFormatter getFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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<CreateOfferDataModel
|
||||
fiatCode.bind(dataModel.fiatCode);
|
||||
}
|
||||
|
||||
private void removeBindings() {
|
||||
totalToPay.unbind();
|
||||
securityDeposit.unbind();
|
||||
tradeAmount.unbind();
|
||||
totalToPayAsCoin.unbind();
|
||||
offerFee.unbind();
|
||||
networkFee.unbind();
|
||||
bankAccountType.unbind();
|
||||
bankAccountCurrency.unbind();
|
||||
bankAccountCounty.unbind();
|
||||
requestPlaceOfferErrorMessage.unbind();
|
||||
showTransactionPublishedScreen.unbind();
|
||||
transactionId.unbind();
|
||||
btcCode.unbind();
|
||||
fiatCode.unbind();
|
||||
}
|
||||
|
||||
private void createListeners() {
|
||||
amountListener = (ov, oldValue, newValue) -> {
|
||||
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<CreateOfferDataModel
|
||||
}
|
||||
|
||||
private InputValidator.ValidationResult isBtcInputValid(String input) {
|
||||
|
||||
return btcValidator.validate(input);
|
||||
}
|
||||
|
||||
private InputValidator.ValidationResult isFiatInputValid(String input) {
|
||||
|
||||
return fiatValidator.validate(input);
|
||||
}
|
||||
|
||||
boolean isSeller() {
|
||||
return dataModel.getDirection() == Offer.Direction.SELL;
|
||||
}
|
||||
}
|
||||
|
@ -18,19 +18,19 @@
|
||||
package io.bitsquare.gui.main.offer.offerbook;
|
||||
|
||||
import io.bitsquare.fiat.FiatAccount;
|
||||
import io.bitsquare.gui.util.GUIUtil;
|
||||
import io.bitsquare.locale.Country;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.trade.TradeManager;
|
||||
import io.bitsquare.trade.offer.Offer;
|
||||
import io.bitsquare.trade.offer.OfferBookService;
|
||||
import io.bitsquare.user.User;
|
||||
import io.bitsquare.util.Utilities;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@ -38,8 +38,6 @@ import javafx.collections.ObservableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* Holds and manages the unsorted and unfiltered offerbook list of both buy and sell offers.
|
||||
* It is handled as singleton by Guice and is used by 2 instances of OfferBookDataModel (one for Buy one for Sell).
|
||||
@ -54,15 +52,15 @@ public class OfferBook {
|
||||
|
||||
private final OfferBookService offerBookService;
|
||||
private final User user;
|
||||
|
||||
private final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
|
||||
private final OfferBookService.Listener offerBookServiceListener;
|
||||
private final ChangeListener<FiatAccount> bankAccountChangeListener;
|
||||
private final ChangeListener<Number> invalidationListener;
|
||||
private final OfferBookService.Listener offerBookServiceListener;
|
||||
|
||||
private final ObservableList<OfferBookListItem> 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<OfferBookListItem> getOfferBookListItems() {
|
||||
ObservableList<OfferBookListItem> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<OfferBookListItem> filteredItems;
|
||||
private final SortedList<OfferBookListItem> sortedItems;
|
||||
// private OfferBookInfo offerBookInfo;
|
||||
private final ChangeListener<FiatAccount> bankAccountChangeListener;
|
||||
|
||||
private ChangeListener<FiatAccount> bankAccountChangeListener;
|
||||
|
||||
private final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Fiat> 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<OfferBookListItem> 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,30 +27,15 @@ public class OfferBookListItem {
|
||||
private final Offer offer;
|
||||
private final ObjectProperty<Country> 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<Country> bankAccountCountryProperty() {
|
||||
return bankAccountCountry;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -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<GridPane, OfferBookVi
|
||||
private final OptionalFiatValidator optionalFiatValidator;
|
||||
private OfferView.OfferActionHandler offerActionHandler;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
OfferBookView(OfferBookViewModel model,
|
||||
Navigation navigation,
|
||||
@ -114,7 +120,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
placeholder.setWrapText(true);
|
||||
table.setPlaceholder(placeholder);
|
||||
|
||||
|
||||
setupValidators();
|
||||
setupComparators();
|
||||
|
||||
@ -129,24 +134,12 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
|
||||
@Override
|
||||
public void doActivate() {
|
||||
amountTextField.setText("");
|
||||
priceTextField.setText("");
|
||||
volumeTextField.setText("");
|
||||
|
||||
setupBindings();
|
||||
addBindings();
|
||||
|
||||
// setOfferBookInfo has been called before
|
||||
SortedList<OfferBookListItem> 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<GridPane, OfferBookVi
|
||||
removeBindings();
|
||||
}
|
||||
|
||||
private void addBindings() {
|
||||
amountTextField.textProperty().bindBidirectional(model.amount);
|
||||
priceTextField.textProperty().bindBidirectional(model.price);
|
||||
volumeTextField.textProperty().bindBidirectional(model.volume);
|
||||
amountBtcLabel.textProperty().bind(model.btcCode);
|
||||
priceFiatLabel.textProperty().bind(model.fiatCode);
|
||||
volumeFiatLabel.textProperty().bind(model.fiatCode);
|
||||
priceDescriptionLabel.textProperty().bind(createStringBinding(() -> 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<GridPane, OfferBookVi
|
||||
model.setDirection(direction);
|
||||
}
|
||||
|
||||
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
|
||||
this.offerActionHandler = offerActionHandler;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI actions
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@FXML
|
||||
void createOffer() {
|
||||
if (model.isRegistered()) {
|
||||
@ -173,6 +225,23 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
}
|
||||
}
|
||||
|
||||
private void openSetupScreen() {
|
||||
overlayManager.blurContent();
|
||||
List<Action> 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<GridPane, OfferBookVi
|
||||
Popups.openWarningPopup("Under construction", "This feature is not implemented yet.");
|
||||
}
|
||||
|
||||
private void openSetupScreen() {
|
||||
overlayManager.blurContent();
|
||||
List<Action> 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<GridPane, OfferBookVi
|
||||
|
||||
}
|
||||
|
||||
private void openRestrictionsWarning(String restrictionsInfo) {
|
||||
overlayManager.blurContent();
|
||||
List<Action> 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<GridPane, OfferBookVi
|
||||
showOnlyMatchingCheckBox.setManaged(visible);
|
||||
}
|
||||
|
||||
private void setupBindings() {
|
||||
amountTextField.textProperty().bindBidirectional(model.amount);
|
||||
priceTextField.textProperty().bindBidirectional(model.price);
|
||||
volumeTextField.textProperty().bindBidirectional(model.volume);
|
||||
amountBtcLabel.textProperty().bind(model.btcCode);
|
||||
priceFiatLabel.textProperty().bind(model.fiatCode);
|
||||
volumeFiatLabel.textProperty().bind(model.fiatCode);
|
||||
priceDescriptionLabel.textProperty().bind(model.fiatCode);
|
||||
volumeDescriptionLabel.textProperty().bind(model.fiatCode);//Price per Bitcoin in EUR
|
||||
priceDescriptionLabel.textProperty().bind(createStringBinding(() ->
|
||||
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<Action> 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<GridPane, OfferBookVi
|
||||
return new TableCell<OfferBookListItem, OfferBookListItem>() {
|
||||
final ImageView iconView = new ImageView();
|
||||
final Button button = new Button();
|
||||
ChangeListener<Country> countryChangeListener;
|
||||
OfferBookListItem item;
|
||||
|
||||
{
|
||||
button.setGraphic(iconView);
|
||||
@ -493,14 +506,26 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
button.setOnAction(event -> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<OfferBookDataModel> im
|
||||
final StringProperty fiatCode = new SimpleStringProperty();
|
||||
final StringProperty restrictionsInfo = new SimpleStringProperty();
|
||||
|
||||
private ChangeListener<String> amountListener;
|
||||
private ChangeListener<String> priceListener;
|
||||
private ChangeListener<String> volumeListener;
|
||||
private ChangeListener<Coin> amountAsCoinListener;
|
||||
private ChangeListener<Fiat> priceAsFiatListener;
|
||||
private ChangeListener<Fiat> volumeAsFiatListener;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public OfferBookViewModel(OfferBookDataModel dataModel, OptionalFiatValidator optionalFiatValidator,
|
||||
@ -64,58 +76,116 @@ class OfferBookViewModel extends ActivatableWithDataModel<OfferBookDataModel> 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<OfferBookListItem> getOfferList() {
|
||||
return dataModel.getOfferList();
|
||||
}
|
||||
@ -162,6 +232,11 @@ class OfferBookViewModel extends ActivatableWithDataModel<OfferBookDataModel> im
|
||||
return dataModel.getPriceAsFiat();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private InputValidator.ValidationResult isBtcInputValid(String input) {
|
||||
return optionalBtcValidator.validate(input);
|
||||
}
|
||||
|
@ -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<Coin> offerFeeAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<ScrollPane fx:id="scrollPane" hbarPolicy="NEVER" vbarPolicy="NEVER" fitToWidth="true" fitToHeight="true"
|
||||
<ScrollPane fx:id="scrollPane" onScroll="#onScroll" hbarPolicy="NEVER" vbarPolicy="NEVER" fitToWidth="true" fitToHeight="true"
|
||||
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
|
||||
AnchorPane.bottomAnchor="0.0">
|
||||
|
||||
|
@ -72,6 +72,9 @@ import static javafx.beans.binding.Bindings.createStringBinding;
|
||||
@FxmlView
|
||||
public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOfferViewModel> {
|
||||
|
||||
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<AnchorPane, TakeOffer
|
||||
private ImageView collapse;
|
||||
private PopOver totalToPayInfoPopover;
|
||||
|
||||
private final Navigation navigation;
|
||||
private final OverlayManager overlayManager;
|
||||
private OfferView.CloseHandler closeHandler;
|
||||
|
||||
private ChangeListener<String> errorMessageChangeListener;
|
||||
private ChangeListener<Boolean> amountFocusedListener;
|
||||
private ChangeListener<Boolean> isTakeOfferSpinnerVisibleListener;
|
||||
private ChangeListener<TakeOfferViewModel.State> stateListener;
|
||||
private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener;
|
||||
private ChangeListener<Boolean> showTransactionPublishedScreenListener;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private TakeOfferView(TakeOfferViewModel model, Navigation navigation,
|
||||
@ -107,19 +118,153 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
|
||||
this.navigation = navigation;
|
||||
this.overlayManager = overlayManager;
|
||||
|
||||
createListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
setupListeners();
|
||||
setupBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doActivate() {
|
||||
addListeners();
|
||||
addBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDeactivate() {
|
||||
model.errorMessage.removeListener(errorMessageChangeListener);
|
||||
removeBindings();
|
||||
removeListeners();
|
||||
}
|
||||
|
||||
private void addBindings() {
|
||||
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);
|
||||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||
takeOfferButton.disableProperty().bind(model.takeOfferButtonDisabled);
|
||||
takeOfferSpinnerInfoLabel.visibleProperty().bind(model.isTakeOfferSpinnerVisible);
|
||||
}
|
||||
|
||||
private void removeBindings() {
|
||||
amountBtcLabel.textProperty().unbind();
|
||||
amountTextField.textProperty().unbindBidirectional(model.amount);
|
||||
volumeTextField.textProperty().unbindBidirectional(model.volume);
|
||||
totalToPayTextField.textProperty().unbind();
|
||||
addressTextField.amountAsCoinProperty().unbind();
|
||||
amountDescriptionLabel.textProperty().unbind();
|
||||
amountTextField.validationResultProperty().unbind();
|
||||
takeOfferButton.disableProperty().unbind();
|
||||
takeOfferSpinnerInfoLabel.visibleProperty().unbind();
|
||||
}
|
||||
|
||||
|
||||
private void createListeners() {
|
||||
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);
|
||||
}
|
||||
};
|
||||
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<Action> 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<AnchorPane, TakeOffer
|
||||
this.closeHandler = closeHandler;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI actions
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@FXML
|
||||
void onTakeOffer() {
|
||||
model.onTakeOffer();
|
||||
@ -166,11 +316,15 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
model.onShowPaymentScreen();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onScroll() {
|
||||
InputTextField.hideErrorMessageDisplay();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void onToggleShowAdvancedSettings() {
|
||||
model.detailsVisible = !model.detailsVisible;
|
||||
if (model.detailsVisible) {
|
||||
model.onToggleShowAdvancedSettings();
|
||||
if (model.isDetailsVisible()) {
|
||||
showAdvancedSettingsButton.setText(BSResources.get("takeOffer.fundsBox.hideAdvanced"));
|
||||
showAdvancedSettingsButton.setGraphic(collapse);
|
||||
showDetailsScreen();
|
||||
@ -197,117 +351,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
Help.openWindow(HelpId.TAKE_OFFER_ADVANCED);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
if (closeHandler != null)
|
||||
closeHandler.close();
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
scrollPane.setOnScroll(e -> 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<Action> 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<AnchorPane, TakeOffer
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
|
||||
scrollPane.layout();
|
||||
|
||||
model.advancedScreenInited = !model.advancedScreenInited;
|
||||
|
||||
toggleDetailsScreen(true);
|
||||
}
|
||||
|
||||
|
||||
private void hideDetailsScreen() {
|
||||
payFundsPane.setActive();
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
@ -418,6 +462,16 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
advancedInfoDisplay.setVisible(visible);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void close() {
|
||||
if (closeHandler != null)
|
||||
closeHandler.close();
|
||||
}
|
||||
|
||||
private void setupTotalToPayInfoIconLabel() {
|
||||
totalToPayInfoIconLabel.setId("clickable-icon");
|
||||
AwesomeDude.setIcon(totalToPayInfoIconLabel, AwesomeIcon.QUESTION_SIGN);
|
||||
|
@ -27,9 +27,11 @@ import io.bitsquare.locale.BSResources;
|
||||
import io.bitsquare.locale.CurrencyUtil;
|
||||
import io.bitsquare.trade.BuyerAsTakerTrade;
|
||||
import io.bitsquare.trade.SellerAsTakerTrade;
|
||||
import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.offer.Offer;
|
||||
import io.bitsquare.trade.states.BuyerTradeState;
|
||||
import io.bitsquare.trade.states.SellerTradeState;
|
||||
import io.bitsquare.trade.states.TradeState;
|
||||
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
@ -42,6 +44,7 @@ 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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -58,6 +61,12 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> 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<TakeOfferDataModel> im
|
||||
private String acceptedArbitratorIds;
|
||||
private String addressAsString;
|
||||
private String paymentLabel;
|
||||
private boolean detailsVisible;
|
||||
|
||||
// Needed for the addressTextField
|
||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<State> 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<TakeOfferDataModel> im
|
||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
||||
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
||||
|
||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
// Needed for the addressTextField
|
||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||
|
||||
final ObjectProperty<State> state = new SimpleObjectProperty<>(State.CHECK_AVAILABILITY);
|
||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
private boolean takeOfferRequested;
|
||||
private boolean isAmountAndPriceValidAndWalletFunded;
|
||||
|
||||
// listeners
|
||||
private ChangeListener<String> amountChangeListener;
|
||||
private ChangeListener<Boolean> isWalletFundedChangeListener;
|
||||
private ChangeListener<Coin> amountAsCoinChangeListener;
|
||||
private ChangeListener<Offer.State> offerStateChangeListener;
|
||||
private ChangeListener<TradeState.ProcessState> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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<TakeOfferDataModel> 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();
|
||||
|
@ -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<AnimationTimer, Void> 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<AnimationTimer, Void> 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;
|
||||
}
|
||||
}
|
||||
|
@ -24,30 +24,15 @@ import org.bitcoinj.core.AddressFormatException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* BtcValidator for validating BTC values.
|
||||
* <p/>
|
||||
* 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);
|
||||
|
@ -23,16 +23,7 @@ import org.bitcoinj.core.NetworkParameters;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* BtcValidator for validating BTC values.
|
||||
* <p/>
|
||||
* 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())
|
||||
|
@ -23,18 +23,12 @@ import io.bitsquare.user.User;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* FiatNumberValidator for validating fiat values.
|
||||
* <p/>
|
||||
* 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));
|
||||
|
@ -19,25 +19,12 @@ package io.bitsquare.gui.util.validation;
|
||||
|
||||
import io.bitsquare.locale.BSResources;
|
||||
|
||||
/**
|
||||
* Base class for other specialized validators.
|
||||
* <p/>
|
||||
* 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;
|
||||
|
@ -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.
|
||||
* <p/>
|
||||
* That class implements just what we need for the moment. It is not intended as a general purpose library class.
|
||||
*/
|
||||
public abstract class NumberValidator extends InputValidator {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected String cleanInput(String input) {
|
||||
return input.replace(",", ".").trim();
|
||||
}
|
||||
|
@ -17,23 +17,10 @@
|
||||
|
||||
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.
|
||||
* <p/>
|
||||
* 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) {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
* <p/>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user