mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-24 06:19:23 -04:00
Merge branch 'percentbasedprice' into Development
This commit is contained in:
commit
1434d37733
45 changed files with 613 additions and 152 deletions
|
@ -22,6 +22,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
// TODO use https://github.com/timmolter/XChange
|
||||||
public class PriceFeed {
|
public class PriceFeed {
|
||||||
private static final Logger log = LoggerFactory.getLogger(PriceFeed.class);
|
private static final Logger log = LoggerFactory.getLogger(PriceFeed.class);
|
||||||
|
|
||||||
|
@ -44,9 +45,9 @@ public class PriceFeed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final long PERIOD_FIAT_SEC = 2 * 60;
|
private static final long PERIOD_FIAT_SEC = 90;
|
||||||
private static final long PERIOD_ALL_FIAT_SEC = 60 * 5;
|
private static final long PERIOD_ALL_FIAT_SEC = 60 * 3;
|
||||||
private static final long PERIOD_ALL_CRYPTO_SEC = 60 * 5;
|
private static final long PERIOD_ALL_CRYPTO_SEC = 60 * 3;
|
||||||
|
|
||||||
private final Map<String, MarketPrice> cache = new HashMap<>();
|
private final Map<String, MarketPrice> cache = new HashMap<>();
|
||||||
private final PriceProvider fiatPriceProvider = new BitcoinAveragePriceProvider();
|
private final PriceProvider fiatPriceProvider = new BitcoinAveragePriceProvider();
|
||||||
|
|
|
@ -7,7 +7,6 @@ import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
// https://api.bitfinex.com/v1/pubticker/BTCUSD
|
|
||||||
public interface PriceProvider extends Serializable {
|
public interface PriceProvider extends Serializable {
|
||||||
Map<String, MarketPrice> getAllPrices() throws IOException, HttpException;
|
Map<String, MarketPrice> getAllPrices() throws IOException, HttpException;
|
||||||
|
|
||||||
|
|
|
@ -18,22 +18,18 @@
|
||||||
package io.bitsquare.trade;
|
package io.bitsquare.trade;
|
||||||
|
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
|
||||||
import io.bitsquare.p2p.NodeAddress;
|
import io.bitsquare.p2p.NodeAddress;
|
||||||
import io.bitsquare.storage.Storage;
|
import io.bitsquare.storage.Storage;
|
||||||
import io.bitsquare.trade.offer.Offer;
|
import io.bitsquare.trade.offer.Offer;
|
||||||
import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol;
|
import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol;
|
||||||
import io.bitsquare.trade.protocol.trade.OffererProtocol;
|
import io.bitsquare.trade.protocol.trade.OffererProtocol;
|
||||||
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
|
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
public final class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade {
|
public final class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade {
|
||||||
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
||||||
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
|
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
|
||||||
|
@ -73,12 +69,4 @@ public final class BuyerAsOffererTrade extends BuyerTrade implements OffererTrad
|
||||||
public void handleTakeOfferRequest(TradeMessage message, NodeAddress taker) {
|
public void handleTakeOfferRequest(TradeMessage message, NodeAddress taker) {
|
||||||
((OffererProtocol) tradeProtocol).handleTakeOfferRequest(message, taker);
|
((OffererProtocol) tradeProtocol).handleTakeOfferRequest(message, taker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Coin getPayoutAmount() {
|
|
||||||
checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
|
|
||||||
|
|
||||||
return FeePolicy.getSecurityDeposit().add(getTradeAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package io.bitsquare.trade;
|
package io.bitsquare.trade;
|
||||||
|
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
|
||||||
import io.bitsquare.p2p.NodeAddress;
|
import io.bitsquare.p2p.NodeAddress;
|
||||||
import io.bitsquare.storage.Storage;
|
import io.bitsquare.storage.Storage;
|
||||||
import io.bitsquare.trade.offer.Offer;
|
import io.bitsquare.trade.offer.Offer;
|
||||||
|
@ -32,7 +31,6 @@ import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||||
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
||||||
|
@ -45,8 +43,8 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||||
// Constructor, initialization
|
// Constructor, initialization
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public BuyerAsTakerTrade(Offer offer, Coin tradeAmount, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
public BuyerAsTakerTrade(Offer offer, Coin tradeAmount, long tradePrice, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
||||||
super(offer, tradeAmount, tradingPeerNodeAddress, storage);
|
super(offer, tradeAmount, tradePrice, tradingPeerNodeAddress, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||||
|
@ -74,11 +72,4 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
|
||||||
checkArgument(tradeProtocol instanceof TakerProtocol, "tradeProtocol NOT instanceof TakerProtocol");
|
checkArgument(tradeProtocol instanceof TakerProtocol, "tradeProtocol NOT instanceof TakerProtocol");
|
||||||
((TakerProtocol) tradeProtocol).takeAvailableOffer();
|
((TakerProtocol) tradeProtocol).takeAvailableOffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Coin getPayoutAmount() {
|
|
||||||
checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
|
|
||||||
|
|
||||||
return FeePolicy.getSecurityDeposit().add(getTradeAmount());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bitsquare.trade;
|
package io.bitsquare.trade;
|
||||||
|
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
|
import io.bitsquare.btc.FeePolicy;
|
||||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
import io.bitsquare.p2p.NodeAddress;
|
import io.bitsquare.p2p.NodeAddress;
|
||||||
|
@ -29,6 +30,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
public abstract class BuyerTrade extends Trade {
|
public abstract class BuyerTrade extends Trade {
|
||||||
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
// That object is saved to disc. We need to take care of changes to not break deserialization.
|
||||||
|
@ -36,8 +38,8 @@ public abstract class BuyerTrade extends Trade {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererTrade.class);
|
private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererTrade.class);
|
||||||
|
|
||||||
BuyerTrade(Offer offer, Coin tradeAmount, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
BuyerTrade(Offer offer, Coin tradeAmount, long tradePrice, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
||||||
super(offer, tradeAmount, tradingPeerNodeAddress, storage);
|
super(offer, tradeAmount, tradePrice, tradingPeerNodeAddress, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuyerTrade(Offer offer, Storage<? extends TradableList> storage) {
|
BuyerTrade(Offer offer, Storage<? extends TradableList> storage) {
|
||||||
|
@ -64,6 +66,12 @@ public abstract class BuyerTrade extends Trade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coin getPayoutAmount() {
|
||||||
|
checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
|
||||||
|
|
||||||
|
return FeePolicy.getSecurityDeposit().add(getTradeAmount());
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Setter for Mutable objects
|
// Setter for Mutable objects
|
||||||
|
|
|
@ -25,6 +25,7 @@ import io.bitsquare.p2p.NodeAddress;
|
||||||
import io.bitsquare.payment.PaymentAccountContractData;
|
import io.bitsquare.payment.PaymentAccountContractData;
|
||||||
import io.bitsquare.trade.offer.Offer;
|
import io.bitsquare.trade.offer.Offer;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.utils.Fiat;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -40,6 +41,7 @@ public final class Contract implements Payload {
|
||||||
|
|
||||||
public final Offer offer;
|
public final Offer offer;
|
||||||
private final long tradeAmount;
|
private final long tradeAmount;
|
||||||
|
private final long tradePrice;
|
||||||
public final String takeOfferFeeTxID;
|
public final String takeOfferFeeTxID;
|
||||||
public final NodeAddress arbitratorNodeAddress;
|
public final NodeAddress arbitratorNodeAddress;
|
||||||
private final boolean isBuyerOffererAndSellerTaker;
|
private final boolean isBuyerOffererAndSellerTaker;
|
||||||
|
@ -64,6 +66,7 @@ public final class Contract implements Payload {
|
||||||
|
|
||||||
public Contract(Offer offer,
|
public Contract(Offer offer,
|
||||||
Coin tradeAmount,
|
Coin tradeAmount,
|
||||||
|
Fiat tradePrice,
|
||||||
String takeOfferFeeTxID,
|
String takeOfferFeeTxID,
|
||||||
NodeAddress buyerNodeAddress,
|
NodeAddress buyerNodeAddress,
|
||||||
NodeAddress sellerNodeAddress,
|
NodeAddress sellerNodeAddress,
|
||||||
|
@ -80,6 +83,7 @@ public final class Contract implements Payload {
|
||||||
byte[] offererBtcPubKey,
|
byte[] offererBtcPubKey,
|
||||||
byte[] takerBtcPubKey) {
|
byte[] takerBtcPubKey) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
|
this.tradePrice = tradePrice.value;
|
||||||
this.buyerNodeAddress = buyerNodeAddress;
|
this.buyerNodeAddress = buyerNodeAddress;
|
||||||
this.sellerNodeAddress = sellerNodeAddress;
|
this.sellerNodeAddress = sellerNodeAddress;
|
||||||
this.tradeAmount = tradeAmount.value;
|
this.tradeAmount = tradeAmount.value;
|
||||||
|
@ -154,6 +158,10 @@ public final class Contract implements Payload {
|
||||||
return Coin.valueOf(tradeAmount);
|
return Coin.valueOf(tradeAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Fiat getTradePrice() {
|
||||||
|
return Fiat.valueOf(offer.getCurrencyCode(), tradePrice);
|
||||||
|
}
|
||||||
|
|
||||||
public NodeAddress getBuyerNodeAddress() {
|
public NodeAddress getBuyerNodeAddress() {
|
||||||
return buyerNodeAddress;
|
return buyerNodeAddress;
|
||||||
}
|
}
|
||||||
|
@ -171,6 +179,7 @@ public final class Contract implements Payload {
|
||||||
Contract contract = (Contract) o;
|
Contract contract = (Contract) o;
|
||||||
|
|
||||||
if (tradeAmount != contract.tradeAmount) return false;
|
if (tradeAmount != contract.tradeAmount) return false;
|
||||||
|
if (tradePrice != contract.tradePrice) return false;
|
||||||
if (isBuyerOffererAndSellerTaker != contract.isBuyerOffererAndSellerTaker) return false;
|
if (isBuyerOffererAndSellerTaker != contract.isBuyerOffererAndSellerTaker) return false;
|
||||||
if (offer != null ? !offer.equals(contract.offer) : contract.offer != null) return false;
|
if (offer != null ? !offer.equals(contract.offer) : contract.offer != null) return false;
|
||||||
if (takeOfferFeeTxID != null ? !takeOfferFeeTxID.equals(contract.takeOfferFeeTxID) : contract.takeOfferFeeTxID != null)
|
if (takeOfferFeeTxID != null ? !takeOfferFeeTxID.equals(contract.takeOfferFeeTxID) : contract.takeOfferFeeTxID != null)
|
||||||
|
@ -206,6 +215,7 @@ public final class Contract implements Payload {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = offer != null ? offer.hashCode() : 0;
|
int result = offer != null ? offer.hashCode() : 0;
|
||||||
result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32));
|
result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32));
|
||||||
|
result = 31 * result + (int) (tradePrice ^ (tradePrice >>> 32));
|
||||||
result = 31 * result + (takeOfferFeeTxID != null ? takeOfferFeeTxID.hashCode() : 0);
|
result = 31 * result + (takeOfferFeeTxID != null ? takeOfferFeeTxID.hashCode() : 0);
|
||||||
result = 31 * result + (arbitratorNodeAddress != null ? arbitratorNodeAddress.hashCode() : 0);
|
result = 31 * result + (arbitratorNodeAddress != null ? arbitratorNodeAddress.hashCode() : 0);
|
||||||
result = 31 * result + (isBuyerOffererAndSellerTaker ? 1 : 0);
|
result = 31 * result + (isBuyerOffererAndSellerTaker ? 1 : 0);
|
||||||
|
@ -229,6 +239,7 @@ public final class Contract implements Payload {
|
||||||
return "Contract{" +
|
return "Contract{" +
|
||||||
"\n\toffer=" + offer +
|
"\n\toffer=" + offer +
|
||||||
"\n\ttradeAmount=" + tradeAmount +
|
"\n\ttradeAmount=" + tradeAmount +
|
||||||
|
"\n\ttradePrice=" + tradePrice +
|
||||||
"\n\ttakeOfferFeeTxID='" + takeOfferFeeTxID + '\'' +
|
"\n\ttakeOfferFeeTxID='" + takeOfferFeeTxID + '\'' +
|
||||||
"\n\tarbitratorAddress=" + arbitratorNodeAddress +
|
"\n\tarbitratorAddress=" + arbitratorNodeAddress +
|
||||||
"\n\tisBuyerOffererAndSellerTaker=" + isBuyerOffererAndSellerTaker +
|
"\n\tisBuyerOffererAndSellerTaker=" + isBuyerOffererAndSellerTaker +
|
||||||
|
|
|
@ -42,8 +42,8 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
|
||||||
// Constructor, initialization
|
// Constructor, initialization
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public SellerAsTakerTrade(Offer offer, Coin tradeAmount, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
public SellerAsTakerTrade(Offer offer, Coin tradeAmount, long tradePrice, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
||||||
super(offer, tradeAmount, tradingPeerNodeAddress, storage);
|
super(offer, tradeAmount, tradePrice, tradingPeerNodeAddress, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
|
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bitsquare.trade;
|
package io.bitsquare.trade;
|
||||||
|
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
|
import io.bitsquare.btc.FeePolicy;
|
||||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
import io.bitsquare.p2p.NodeAddress;
|
import io.bitsquare.p2p.NodeAddress;
|
||||||
|
@ -36,8 +37,8 @@ public abstract class SellerTrade extends Trade {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(BuyerAsTakerTrade.class);
|
private static final Logger log = LoggerFactory.getLogger(BuyerAsTakerTrade.class);
|
||||||
|
|
||||||
SellerTrade(Offer offer, Coin tradeAmount, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
SellerTrade(Offer offer, Coin tradeAmount, long tradePrice, NodeAddress tradingPeerNodeAddress, Storage<? extends TradableList> storage) {
|
||||||
super(offer, tradeAmount, tradingPeerNodeAddress, storage);
|
super(offer, tradeAmount, tradePrice, tradingPeerNodeAddress, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
SellerTrade(Offer offer, Storage<? extends TradableList> storage) {
|
SellerTrade(Offer offer, Storage<? extends TradableList> storage) {
|
||||||
|
@ -64,6 +65,11 @@ public abstract class SellerTrade extends Trade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coin getPayoutAmount() {
|
||||||
|
return FeePolicy.getSecurityDeposit();
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Setter for Mutable objects
|
// Setter for Mutable objects
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -24,7 +24,6 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import io.bitsquare.app.Log;
|
import io.bitsquare.app.Log;
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
import io.bitsquare.arbitration.ArbitratorManager;
|
import io.bitsquare.arbitration.ArbitratorManager;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
|
||||||
import io.bitsquare.btc.TradeWalletService;
|
import io.bitsquare.btc.TradeWalletService;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
|
@ -42,6 +41,7 @@ import javafx.beans.property.*;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
import org.bitcoinj.core.TransactionConfidence;
|
import org.bitcoinj.core.TransactionConfidence;
|
||||||
|
import org.bitcoinj.utils.ExchangeRate;
|
||||||
import org.bitcoinj.utils.Fiat;
|
import org.bitcoinj.utils.Fiat;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -168,6 +168,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
transient private ObjectProperty<Fiat> tradeVolumeProperty;
|
transient private ObjectProperty<Fiat> tradeVolumeProperty;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String takeOfferFeeTxId;
|
private String takeOfferFeeTxId;
|
||||||
|
private long tradePrice;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -190,11 +191,12 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
// taker
|
// taker
|
||||||
protected Trade(Offer offer, Coin tradeAmount, NodeAddress tradingPeerNodeAddress,
|
protected Trade(Offer offer, Coin tradeAmount, long tradePrice, NodeAddress tradingPeerNodeAddress,
|
||||||
Storage<? extends TradableList> storage) {
|
Storage<? extends TradableList> storage) {
|
||||||
|
|
||||||
this(offer, storage);
|
this(offer, storage);
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
|
this.tradePrice = tradePrice;
|
||||||
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
|
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
|
||||||
tradeAmountProperty.set(tradeAmount);
|
tradeAmountProperty.set(tradeAmount);
|
||||||
tradeVolumeProperty.set(getTradeVolume());
|
tradeVolumeProperty.set(getTradeVolume());
|
||||||
|
@ -374,9 +376,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return offer;
|
return offer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getPayoutAmount() {
|
abstract public Coin getPayoutAmount();
|
||||||
return FeePolicy.getSecurityDeposit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProcessModel getProcessModel() {
|
public ProcessModel getProcessModel() {
|
||||||
return processModel;
|
return processModel;
|
||||||
|
@ -384,8 +384,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Fiat getTradeVolume() {
|
public Fiat getTradeVolume() {
|
||||||
if (tradeAmount != null)
|
if (tradeAmount != null && getTradePrice() != null)
|
||||||
return offer.getVolumeByAmount(tradeAmount);
|
return new ExchangeRate(getTradePrice()).coinToFiat(tradeAmount);
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -455,6 +455,14 @@ public abstract class Trade implements Tradable, Model {
|
||||||
tradeVolumeProperty.set(getTradeVolume());
|
tradeVolumeProperty.set(getTradeVolume());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTradePrice(long tradePrice) {
|
||||||
|
this.tradePrice = tradePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fiat getTradePrice() {
|
||||||
|
return Fiat.valueOf(offer.getCurrencyCode(), tradePrice);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Coin getTradeAmount() {
|
public Coin getTradeAmount() {
|
||||||
return tradeAmount;
|
return tradeAmount;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import io.bitsquare.btc.AddressEntry;
|
||||||
import io.bitsquare.btc.AddressEntryException;
|
import io.bitsquare.btc.AddressEntryException;
|
||||||
import io.bitsquare.btc.TradeWalletService;
|
import io.bitsquare.btc.TradeWalletService;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
import io.bitsquare.common.handlers.FaultHandler;
|
import io.bitsquare.common.handlers.FaultHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
|
@ -97,6 +98,7 @@ public class TradeManager {
|
||||||
FailedTradesManager failedTradesManager,
|
FailedTradesManager failedTradesManager,
|
||||||
ArbitratorManager arbitratorManager,
|
ArbitratorManager arbitratorManager,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
|
PriceFeed priceFeed,
|
||||||
@Named("storage.dir") File storageDir) {
|
@Named("storage.dir") File storageDir) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
|
@ -109,7 +111,8 @@ public class TradeManager {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
|
||||||
tradableListStorage = new Storage<>(storageDir);
|
tradableListStorage = new Storage<>(storageDir);
|
||||||
this.trades = new TradableList<>(tradableListStorage, "PendingTrades");
|
trades = new TradableList<>(tradableListStorage, "PendingTrades");
|
||||||
|
trades.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
|
||||||
|
|
||||||
p2PService.addDecryptedDirectMessageListener(new DecryptedDirectMessageListener() {
|
p2PService.addDecryptedDirectMessageListener(new DecryptedDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -261,6 +264,7 @@ public class TradeManager {
|
||||||
|
|
||||||
// First we check if offer is still available then we create the trade with the protocol
|
// First we check if offer is still available then we create the trade with the protocol
|
||||||
public void onTakeOffer(Coin amount,
|
public void onTakeOffer(Coin amount,
|
||||||
|
long tradePrice,
|
||||||
Coin fundsNeededForTrade,
|
Coin fundsNeededForTrade,
|
||||||
Offer offer,
|
Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
|
@ -270,11 +274,12 @@ public class TradeManager {
|
||||||
offer.checkOfferAvailability(model,
|
offer.checkOfferAvailability(model,
|
||||||
() -> {
|
() -> {
|
||||||
if (offer.getState() == Offer.State.AVAILABLE)
|
if (offer.getState() == Offer.State.AVAILABLE)
|
||||||
createTrade(amount, fundsNeededForTrade, offer, paymentAccountId, useSavingsWallet, model, tradeResultHandler);
|
createTrade(amount, tradePrice, fundsNeededForTrade, offer, paymentAccountId, useSavingsWallet, model, tradeResultHandler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTrade(Coin amount,
|
private void createTrade(Coin amount,
|
||||||
|
long tradePrice,
|
||||||
Coin fundsNeededForTrade,
|
Coin fundsNeededForTrade,
|
||||||
Offer offer,
|
Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
|
@ -283,9 +288,9 @@ public class TradeManager {
|
||||||
TradeResultHandler tradeResultHandler) {
|
TradeResultHandler tradeResultHandler) {
|
||||||
Trade trade;
|
Trade trade;
|
||||||
if (offer.getDirection() == Offer.Direction.BUY)
|
if (offer.getDirection() == Offer.Direction.BUY)
|
||||||
trade = new SellerAsTakerTrade(offer, amount, model.getPeerNodeAddress(), tradableListStorage);
|
trade = new SellerAsTakerTrade(offer, amount, tradePrice, model.getPeerNodeAddress(), tradableListStorage);
|
||||||
else
|
else
|
||||||
trade = new BuyerAsTakerTrade(offer, amount, model.getPeerNodeAddress(), tradableListStorage);
|
trade = new BuyerAsTakerTrade(offer, amount, tradePrice, model.getPeerNodeAddress(), tradableListStorage);
|
||||||
|
|
||||||
trade.setTakerPaymentAccountId(paymentAccountId);
|
trade.setTakerPaymentAccountId(paymentAccountId);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bitsquare.trade.closed;
|
package io.bitsquare.trade.closed;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
import io.bitsquare.storage.Storage;
|
import io.bitsquare.storage.Storage;
|
||||||
import io.bitsquare.trade.Tradable;
|
import io.bitsquare.trade.Tradable;
|
||||||
|
@ -37,9 +38,10 @@ public class ClosedTradableManager {
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ClosedTradableManager(KeyRing keyRing, @Named("storage.dir") File storageDir) {
|
public ClosedTradableManager(KeyRing keyRing, PriceFeed priceFeed, @Named("storage.dir") File storageDir) {
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.closedTrades = new TradableList<>(new Storage<>(storageDir), "ClosedTrades");
|
this.closedTrades = new TradableList<>(new Storage<>(storageDir), "ClosedTrades");
|
||||||
|
closedTrades.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(Tradable tradable) {
|
public void add(Tradable tradable) {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bitsquare.trade.failed;
|
package io.bitsquare.trade.failed;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
import io.bitsquare.storage.Storage;
|
import io.bitsquare.storage.Storage;
|
||||||
import io.bitsquare.trade.TradableList;
|
import io.bitsquare.trade.TradableList;
|
||||||
|
@ -37,9 +38,10 @@ public class FailedTradesManager {
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FailedTradesManager(KeyRing keyRing, @Named("storage.dir") File storageDir) {
|
public FailedTradesManager(KeyRing keyRing, PriceFeed priceFeed, @Named("storage.dir") File storageDir) {
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.failedTrades = new TradableList<>(new Storage<>(storageDir), "FailedTrades");
|
this.failedTrades = new TradableList<>(new Storage<>(storageDir), "FailedTrades");
|
||||||
|
failedTrades.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(Trade trade) {
|
public void add(Trade trade) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ package io.bitsquare.trade.offer;
|
||||||
|
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
import io.bitsquare.btc.Restrictions;
|
import io.bitsquare.btc.Restrictions;
|
||||||
|
import io.bitsquare.btc.pricefeed.MarketPrice;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
import io.bitsquare.common.crypto.PubKeyRing;
|
import io.bitsquare.common.crypto.PubKeyRing;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
|
@ -106,6 +108,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
private final long date;
|
private final long date;
|
||||||
private final long protocolVersion;
|
private final long protocolVersion;
|
||||||
private final long fiatPrice;
|
private final long fiatPrice;
|
||||||
|
private final double marketPriceMargin;
|
||||||
|
private final boolean usePercentageBasedPrice;
|
||||||
private final long amount;
|
private final long amount;
|
||||||
private final long minAmount;
|
private final long minAmount;
|
||||||
private final NodeAddress offererNodeAddress;
|
private final NodeAddress offererNodeAddress;
|
||||||
|
@ -127,6 +131,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
transient private OfferAvailabilityProtocol availabilityProtocol;
|
transient private OfferAvailabilityProtocol availabilityProtocol;
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
transient private StringProperty errorMessageProperty = new SimpleStringProperty();
|
transient private StringProperty errorMessageProperty = new SimpleStringProperty();
|
||||||
|
transient private PriceFeed priceFeed;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -138,6 +143,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
Direction direction,
|
Direction direction,
|
||||||
long fiatPrice,
|
long fiatPrice,
|
||||||
|
double marketPriceMargin,
|
||||||
|
boolean usePercentageBasedPrice,
|
||||||
long amount,
|
long amount,
|
||||||
long minAmount,
|
long minAmount,
|
||||||
String currencyCode,
|
String currencyCode,
|
||||||
|
@ -147,12 +154,15 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
@Nullable String countryCode,
|
@Nullable String countryCode,
|
||||||
@Nullable ArrayList<String> acceptedCountryCodes,
|
@Nullable ArrayList<String> acceptedCountryCodes,
|
||||||
@Nullable String bankId,
|
@Nullable String bankId,
|
||||||
@Nullable ArrayList<String> acceptedBankIds) {
|
@Nullable ArrayList<String> acceptedBankIds,
|
||||||
|
PriceFeed priceFeed) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.offererNodeAddress = offererNodeAddress;
|
this.offererNodeAddress = offererNodeAddress;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
this.fiatPrice = fiatPrice;
|
this.fiatPrice = fiatPrice;
|
||||||
|
this.marketPriceMargin = marketPriceMargin;
|
||||||
|
this.usePercentageBasedPrice = usePercentageBasedPrice;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.minAmount = minAmount;
|
this.minAmount = minAmount;
|
||||||
this.currencyCode = currencyCode;
|
this.currencyCode = currencyCode;
|
||||||
|
@ -163,6 +173,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
this.acceptedCountryCodes = acceptedCountryCodes;
|
this.acceptedCountryCodes = acceptedCountryCodes;
|
||||||
this.bankId = bankId;
|
this.bankId = bankId;
|
||||||
this.acceptedBankIds = acceptedBankIds;
|
this.acceptedBankIds = acceptedBankIds;
|
||||||
|
this.priceFeed = priceFeed;
|
||||||
|
|
||||||
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
|
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
|
||||||
|
|
||||||
|
@ -218,7 +229,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
|
|
||||||
public Fiat getVolumeByAmount(Coin amount) {
|
public Fiat getVolumeByAmount(Coin amount) {
|
||||||
if (fiatPrice != 0 && amount != null && !amount.isZero())
|
if (fiatPrice != 0 && amount != null && !amount.isZero())
|
||||||
return new ExchangeRate(Fiat.valueOf(currencyCode, fiatPrice)).coinToFiat(amount);
|
return new ExchangeRate(getPrice()).coinToFiat(amount);
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -263,6 +274,10 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
// Setters
|
// Setters
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void setPriceFeed(PriceFeed priceFeed) {
|
||||||
|
this.priceFeed = priceFeed;
|
||||||
|
}
|
||||||
|
|
||||||
public void setState(State state) {
|
public void setState(State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
stateProperty().set(state);
|
stateProperty().set(state);
|
||||||
|
@ -312,7 +327,45 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
}
|
}
|
||||||
|
|
||||||
public Fiat getPrice() {
|
public Fiat getPrice() {
|
||||||
return Fiat.valueOf(currencyCode, fiatPrice);
|
Fiat priceAsFiat = Fiat.valueOf(currencyCode, fiatPrice);
|
||||||
|
if (usePercentageBasedPrice && priceFeed != null) {
|
||||||
|
MarketPrice marketPrice = priceFeed.getMarketPrice(currencyCode);
|
||||||
|
if (marketPrice != null) {
|
||||||
|
PriceFeed.Type priceFeedType = direction == Direction.SELL ? PriceFeed.Type.ASK : PriceFeed.Type.BID;
|
||||||
|
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
|
||||||
|
double factor = direction == Offer.Direction.BUY ? 1 - marketPriceMargin : 1 + marketPriceMargin;
|
||||||
|
double targetPrice = marketPriceAsDouble * factor;
|
||||||
|
|
||||||
|
// round
|
||||||
|
long factor1 = (long) Math.pow(10, 2);
|
||||||
|
targetPrice = targetPrice * factor1;
|
||||||
|
long tmp = Math.round(targetPrice);
|
||||||
|
targetPrice = (double) tmp / factor1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Fiat.parseFiat(currencyCode, String.valueOf(targetPrice));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Exception at parseToFiat: " + e.toString());
|
||||||
|
log.warn("We use the static price.");
|
||||||
|
return priceAsFiat;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("We don't have a market price. We use the static price instead.");
|
||||||
|
return priceAsFiat;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (priceFeed == null)
|
||||||
|
log.warn("priceFeed must not be null");
|
||||||
|
return priceAsFiat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMarketPriceMargin() {
|
||||||
|
return marketPriceMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getUsePercentageBasedPrice() {
|
||||||
|
return usePercentageBasedPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getAmount() {
|
public Coin getAmount() {
|
||||||
|
@ -391,11 +444,11 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (!(o instanceof Offer)) return false;
|
if (!(o instanceof Offer)) return false;
|
||||||
|
|
||||||
Offer offer = (Offer) o;
|
Offer offer = (Offer) o;
|
||||||
|
|
||||||
if (date != offer.date) return false;
|
if (date != offer.date) return false;
|
||||||
if (fiatPrice != offer.fiatPrice) return false;
|
if (fiatPrice != offer.fiatPrice) return false;
|
||||||
|
if (Double.compare(offer.marketPriceMargin, marketPriceMargin) != 0) return false;
|
||||||
|
if (usePercentageBasedPrice != offer.usePercentageBasedPrice) return false;
|
||||||
if (amount != offer.amount) return false;
|
if (amount != offer.amount) return false;
|
||||||
if (minAmount != offer.minAmount) return false;
|
if (minAmount != offer.minAmount) return false;
|
||||||
if (id != null ? !id.equals(offer.id) : offer.id != null) return false;
|
if (id != null ? !id.equals(offer.id) : offer.id != null) return false;
|
||||||
|
@ -418,7 +471,6 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
if (arbitratorNodeAddresses != null ? !arbitratorNodeAddresses.equals(offer.arbitratorNodeAddresses) : offer.arbitratorNodeAddresses != null)
|
if (arbitratorNodeAddresses != null ? !arbitratorNodeAddresses.equals(offer.arbitratorNodeAddresses) : offer.arbitratorNodeAddresses != null)
|
||||||
return false;
|
return false;
|
||||||
return !(offerFeePaymentTxID != null ? !offerFeePaymentTxID.equals(offer.offerFeePaymentTxID) : offer.offerFeePaymentTxID != null);
|
return !(offerFeePaymentTxID != null ? !offerFeePaymentTxID.equals(offer.offerFeePaymentTxID) : offer.offerFeePaymentTxID != null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -428,6 +480,9 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0);
|
result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0);
|
||||||
result = 31 * result + (int) (date ^ (date >>> 32));
|
result = 31 * result + (int) (date ^ (date >>> 32));
|
||||||
result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32));
|
result = 31 * result + (int) (fiatPrice ^ (fiatPrice >>> 32));
|
||||||
|
long temp = Double.doubleToLongBits(marketPriceMargin);
|
||||||
|
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
result = 31 * result + (usePercentageBasedPrice ? 1 : 0);
|
||||||
result = 31 * result + (int) (amount ^ (amount >>> 32));
|
result = 31 * result + (int) (amount ^ (amount >>> 32));
|
||||||
result = 31 * result + (int) (minAmount ^ (minAmount >>> 32));
|
result = 31 * result + (int) (minAmount ^ (minAmount >>> 32));
|
||||||
result = 31 * result + (offererNodeAddress != null ? offererNodeAddress.hashCode() : 0);
|
result = 31 * result + (offererNodeAddress != null ? offererNodeAddress.hashCode() : 0);
|
||||||
|
@ -451,6 +506,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
"\n\tcurrencyCode='" + currencyCode + '\'' +
|
"\n\tcurrencyCode='" + currencyCode + '\'' +
|
||||||
"\n\tdate=" + date +
|
"\n\tdate=" + date +
|
||||||
"\n\tfiatPrice=" + fiatPrice +
|
"\n\tfiatPrice=" + fiatPrice +
|
||||||
|
"\n\tmarketPriceMargin=" + marketPriceMargin +
|
||||||
|
"\n\tusePercentageBasedPrice=" + usePercentageBasedPrice +
|
||||||
"\n\tamount=" + amount +
|
"\n\tamount=" + amount +
|
||||||
"\n\tminAmount=" + minAmount +
|
"\n\tminAmount=" + minAmount +
|
||||||
"\n\toffererAddress=" + offererNodeAddress +
|
"\n\toffererAddress=" + offererNodeAddress +
|
||||||
|
@ -471,5 +528,4 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
|
||||||
"\n\tTAC_TAKER=" + TAC_TAKER +
|
"\n\tTAC_TAKER=" + TAC_TAKER +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package io.bitsquare.trade.offer;
|
package io.bitsquare.trade.offer;
|
||||||
|
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
import io.bitsquare.common.handlers.ErrorMessageHandler;
|
||||||
import io.bitsquare.common.handlers.ResultHandler;
|
import io.bitsquare.common.handlers.ResultHandler;
|
||||||
import io.bitsquare.p2p.P2PService;
|
import io.bitsquare.p2p.P2PService;
|
||||||
|
@ -45,6 +46,7 @@ public class OfferBookService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private PriceFeed priceFeed;
|
||||||
private final List<OfferBookChangedListener> offerBookChangedListeners = new LinkedList<>();
|
private final List<OfferBookChangedListener> offerBookChangedListeners = new LinkedList<>();
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,15 +55,19 @@ public class OfferBookService {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public OfferBookService(P2PService p2PService) {
|
public OfferBookService(P2PService p2PService, PriceFeed priceFeed) {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.priceFeed = priceFeed;
|
||||||
|
|
||||||
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
|
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAdded(ProtectedStorageEntry data) {
|
public void onAdded(ProtectedStorageEntry data) {
|
||||||
offerBookChangedListeners.stream().forEach(listener -> {
|
offerBookChangedListeners.stream().forEach(listener -> {
|
||||||
if (data.getStoragePayload() instanceof Offer)
|
if (data.getStoragePayload() instanceof Offer) {
|
||||||
listener.onAdded((Offer) data.getStoragePayload());
|
Offer offer = (Offer) data.getStoragePayload();
|
||||||
|
offer.setPriceFeed(priceFeed);
|
||||||
|
listener.onAdded(offer);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +124,11 @@ public class OfferBookService {
|
||||||
public List<Offer> getOffers() {
|
public List<Offer> getOffers() {
|
||||||
return p2PService.getDataMap().values().stream()
|
return p2PService.getDataMap().values().stream()
|
||||||
.filter(data -> data.getStoragePayload() instanceof Offer)
|
.filter(data -> data.getStoragePayload() instanceof Offer)
|
||||||
.map(data -> (Offer) data.getStoragePayload())
|
.map(data -> {
|
||||||
|
Offer offer = (Offer) data.getStoragePayload();
|
||||||
|
offer.setPriceFeed(priceFeed);
|
||||||
|
return offer;
|
||||||
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import io.bitsquare.app.Log;
|
||||||
import io.bitsquare.btc.AddressEntry;
|
import io.bitsquare.btc.AddressEntry;
|
||||||
import io.bitsquare.btc.TradeWalletService;
|
import io.bitsquare.btc.TradeWalletService;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.Timer;
|
import io.bitsquare.common.Timer;
|
||||||
import io.bitsquare.common.UserThread;
|
import io.bitsquare.common.UserThread;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
|
@ -95,6 +96,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
OfferBookService offerBookService,
|
OfferBookService offerBookService,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
|
PriceFeed priceFeed,
|
||||||
@Named("storage.dir") File storageDir) {
|
@Named("storage.dir") File storageDir) {
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
@ -105,7 +107,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
this.closedTradableManager = closedTradableManager;
|
this.closedTradableManager = closedTradableManager;
|
||||||
|
|
||||||
openOffersStorage = new Storage<>(storageDir);
|
openOffersStorage = new Storage<>(storageDir);
|
||||||
this.openOffers = new TradableList<>(openOffersStorage, "OpenOffers");
|
openOffers = new TradableList<>(openOffersStorage, "OpenOffers");
|
||||||
|
openOffers.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
|
||||||
|
|
||||||
// In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook
|
// In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(OpenOfferManager.this::shutDown,
|
Runtime.getRuntime().addShutdownHook(new Thread(OpenOfferManager.this::shutDown,
|
||||||
|
|
|
@ -68,7 +68,6 @@ public class ProcessModel implements Model, Serializable {
|
||||||
transient private KeyRing keyRing;
|
transient private KeyRing keyRing;
|
||||||
transient private P2PService p2PService;
|
transient private P2PService p2PService;
|
||||||
|
|
||||||
|
|
||||||
// Mutable
|
// Mutable
|
||||||
public final TradingPeer tradingPeer;
|
public final TradingPeer tradingPeer;
|
||||||
transient private TradeMessage tradeMessage;
|
transient private TradeMessage tradeMessage;
|
||||||
|
|
|
@ -35,6 +35,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
|
||||||
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
|
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
|
||||||
|
|
||||||
public final long tradeAmount;
|
public final long tradeAmount;
|
||||||
|
public final long tradePrice;
|
||||||
public final byte[] takerMultiSigPubKey;
|
public final byte[] takerMultiSigPubKey;
|
||||||
public final ArrayList<RawTransactionInput> rawTransactionInputs;
|
public final ArrayList<RawTransactionInput> rawTransactionInputs;
|
||||||
public final long changeOutputValue;
|
public final long changeOutputValue;
|
||||||
|
@ -52,6 +53,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
|
||||||
public PayDepositRequest(NodeAddress senderNodeAddress,
|
public PayDepositRequest(NodeAddress senderNodeAddress,
|
||||||
String tradeId,
|
String tradeId,
|
||||||
long tradeAmount,
|
long tradeAmount,
|
||||||
|
long tradePrice,
|
||||||
ArrayList<RawTransactionInput> rawTransactionInputs,
|
ArrayList<RawTransactionInput> rawTransactionInputs,
|
||||||
long changeOutputValue,
|
long changeOutputValue,
|
||||||
String changeOutputAddress,
|
String changeOutputAddress,
|
||||||
|
@ -66,6 +68,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess
|
||||||
super(tradeId);
|
super(tradeId);
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
this.senderNodeAddress = senderNodeAddress;
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
|
this.tradePrice = tradePrice;
|
||||||
this.rawTransactionInputs = rawTransactionInputs;
|
this.rawTransactionInputs = rawTransactionInputs;
|
||||||
this.changeOutputValue = changeOutputValue;
|
this.changeOutputValue = changeOutputValue;
|
||||||
this.changeOutputAddress = changeOutputAddress;
|
this.changeOutputAddress = changeOutputAddress;
|
||||||
|
|
|
@ -60,6 +60,7 @@ public class CreateAndSignContract extends TradeTask {
|
||||||
Contract contract = new Contract(
|
Contract contract = new Contract(
|
||||||
processModel.getOffer(),
|
processModel.getOffer(),
|
||||||
trade.getTradeAmount(),
|
trade.getTradeAmount(),
|
||||||
|
trade.getTradePrice(),
|
||||||
trade.getTakeOfferFeeTxId(),
|
trade.getTakeOfferFeeTxId(),
|
||||||
buyerNodeAddress,
|
buyerNodeAddress,
|
||||||
sellerNodeAddress,
|
sellerNodeAddress,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import io.bitsquare.trade.Trade;
|
||||||
import io.bitsquare.trade.protocol.trade.messages.PayDepositRequest;
|
import io.bitsquare.trade.protocol.trade.messages.PayDepositRequest;
|
||||||
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.utils.Fiat;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -76,6 +77,26 @@ public class ProcessPayDepositRequest extends TradeTask {
|
||||||
if (payDepositRequest.acceptedArbitratorNodeAddresses.size() < 1)
|
if (payDepositRequest.acceptedArbitratorNodeAddresses.size() < 1)
|
||||||
failed("acceptedArbitratorNames size must be at least 1");
|
failed("acceptedArbitratorNames size must be at least 1");
|
||||||
trade.setArbitratorNodeAddress(checkNotNull(payDepositRequest.arbitratorNodeAddress));
|
trade.setArbitratorNodeAddress(checkNotNull(payDepositRequest.arbitratorNodeAddress));
|
||||||
|
|
||||||
|
long takersTradePrice = payDepositRequest.tradePrice;
|
||||||
|
checkArgument(takersTradePrice > 0);
|
||||||
|
Fiat tradePriceAsFiat = Fiat.valueOf(trade.getOffer().getCurrencyCode(), takersTradePrice);
|
||||||
|
Fiat offerPriceAsFiat = trade.getOffer().getPrice();
|
||||||
|
double factor = (double) takersTradePrice / (double) offerPriceAsFiat.value;
|
||||||
|
// We allow max. 2 % difference between own offer price calculation and takers calculation.
|
||||||
|
// Market price might be different at offerers and takers side so we need a bit of tolerance.
|
||||||
|
// The tolerance will get smaller once we have multiple price feeds avoiding fast price fluctuations
|
||||||
|
// from one provider.
|
||||||
|
if (Math.abs(1 - factor) > 0.02) {
|
||||||
|
String msg = "Takers tradePrice is outside our market price tolerance.\n" +
|
||||||
|
"tradePriceAsFiat=" + tradePriceAsFiat.toFriendlyString() + "\n" +
|
||||||
|
"offerPriceAsFiat=" + offerPriceAsFiat.toFriendlyString();
|
||||||
|
log.warn(msg);
|
||||||
|
failed(msg);
|
||||||
|
}
|
||||||
|
trade.setTradePrice(takersTradePrice);
|
||||||
|
|
||||||
|
|
||||||
checkArgument(payDepositRequest.tradeAmount > 0);
|
checkArgument(payDepositRequest.tradeAmount > 0);
|
||||||
trade.setTradeAmount(Coin.valueOf(payDepositRequest.tradeAmount));
|
trade.setTradeAmount(Coin.valueOf(payDepositRequest.tradeAmount));
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class SendPayDepositRequest extends TradeTask {
|
||||||
processModel.getMyAddress(),
|
processModel.getMyAddress(),
|
||||||
processModel.getId(),
|
processModel.getId(),
|
||||||
trade.getTradeAmount().value,
|
trade.getTradeAmount().value,
|
||||||
|
trade.getTradePrice().value,
|
||||||
processModel.getRawTransactionInputs(),
|
processModel.getRawTransactionInputs(),
|
||||||
processModel.getChangeOutputValue(),
|
processModel.getChangeOutputValue(),
|
||||||
processModel.getChangeOutputAddress(),
|
processModel.getChangeOutputAddress(),
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class VerifyAndSignContract extends TradeTask {
|
||||||
Contract contract = new Contract(
|
Contract contract = new Contract(
|
||||||
processModel.getOffer(),
|
processModel.getOffer(),
|
||||||
trade.getTradeAmount(),
|
trade.getTradeAmount(),
|
||||||
|
trade.getTradePrice(),
|
||||||
trade.getTakeOfferFeeTxId(),
|
trade.getTakeOfferFeeTxId(),
|
||||||
buyerNodeAddress,
|
buyerNodeAddress,
|
||||||
sellerNodeAddress,
|
sellerNodeAddress,
|
||||||
|
|
|
@ -110,6 +110,7 @@ public final class Preferences implements Persistable {
|
||||||
private double maxPriceDistanceInPercent;
|
private double maxPriceDistanceInPercent;
|
||||||
private boolean useInvertedMarketPrice;
|
private boolean useInvertedMarketPrice;
|
||||||
private boolean useStickyMarketPrice = false;
|
private boolean useStickyMarketPrice = false;
|
||||||
|
private boolean usePercentageBasedPrice = false;
|
||||||
|
|
||||||
// Observable wrappers
|
// Observable wrappers
|
||||||
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
|
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
|
||||||
|
@ -162,6 +163,7 @@ public final class Preferences implements Persistable {
|
||||||
// useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
|
// useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
|
||||||
useTorForBitcoinJ = false;
|
useTorForBitcoinJ = false;
|
||||||
useStickyMarketPrice = persisted.getUseStickyMarketPrice();
|
useStickyMarketPrice = persisted.getUseStickyMarketPrice();
|
||||||
|
usePercentageBasedPrice = persisted.getUsePercentageBasedPrice();
|
||||||
showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook();
|
showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook();
|
||||||
maxPriceDistanceInPercent = persisted.getMaxPriceDistanceInPercent();
|
maxPriceDistanceInPercent = persisted.getMaxPriceDistanceInPercent();
|
||||||
// Backward compatible to version 0.3.6. Can be removed after a while
|
// Backward compatible to version 0.3.6. Can be removed after a while
|
||||||
|
@ -368,6 +370,12 @@ public final class Preferences implements Persistable {
|
||||||
storage.queueUpForSave();
|
storage.queueUpForSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUsePercentageBasedPrice(boolean usePercentageBasedPrice) {
|
||||||
|
this.usePercentageBasedPrice = usePercentageBasedPrice;
|
||||||
|
storage.queueUpForSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getter
|
// Getter
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -488,6 +496,10 @@ public final class Preferences implements Persistable {
|
||||||
return useStickyMarketPrice;
|
return useStickyMarketPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getUsePercentageBasedPrice() {
|
||||||
|
return usePercentageBasedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Private
|
// Private
|
||||||
|
|
|
@ -76,7 +76,7 @@ import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY;
|
||||||
public class BitsquareApp extends Application {
|
public class BitsquareApp extends Application {
|
||||||
private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class);
|
private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class);
|
||||||
|
|
||||||
public static final boolean DEV_MODE = false;
|
public static final boolean DEV_MODE = true;
|
||||||
public static final boolean IS_RELEASE_VERSION = !DEV_MODE && true;
|
public static final boolean IS_RELEASE_VERSION = !DEV_MODE && true;
|
||||||
|
|
||||||
private static Environment env;
|
private static Environment env;
|
||||||
|
|
|
@ -496,6 +496,26 @@ textfield */
|
||||||
-fx-border-insets: 0 0 0 -2;
|
-fx-border-insets: 0 0 0 -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggle-price-left {
|
||||||
|
-fx-border-radius: 4 0 0 4;
|
||||||
|
-fx-padding: 4 4 4 4;
|
||||||
|
-fx-border-color: #aaa;
|
||||||
|
-fx-border-style: solid none solid solid;
|
||||||
|
-fx-border-insets: 0 -2 0 0;
|
||||||
|
-fx-background-insets: 0 -2 0 0;
|
||||||
|
-fx-background-radius: 4 0 0 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-price-right {
|
||||||
|
-fx-border-radius: 0 4 4 0;
|
||||||
|
-fx-padding: 4 4 4 4;
|
||||||
|
-fx-border-color: #aaa;
|
||||||
|
-fx-border-style: solid solid solid none;
|
||||||
|
-fx-border-insets: 0 0 0 -2;
|
||||||
|
-fx-background-insets: 0 0 0 -2;
|
||||||
|
-fx-background-radius: 0 4 4 0;
|
||||||
|
}
|
||||||
|
|
||||||
#totals-separator {
|
#totals-separator {
|
||||||
-fx-background: #AAAAAA;
|
-fx-background: #AAAAAA;
|
||||||
}
|
}
|
||||||
|
|
|
@ -404,7 +404,7 @@ public class MainViewModel implements ViewModel {
|
||||||
result = numPeersString + " / synchronized with " + btcNetworkAsString;
|
result = numPeersString + " / synchronized with " + btcNetworkAsString;
|
||||||
btcSplashSyncIconId.set("image-connection-synced");
|
btcSplashSyncIconId.set("image-connection-synced");
|
||||||
} else if (percentage > 0.0) {
|
} else if (percentage > 0.0) {
|
||||||
result = numPeersString + " / synchronizing with " + btcNetworkAsString + ": " + formatter.formatToPercent(percentage);
|
result = numPeersString + " / synchronizing with " + btcNetworkAsString + ": " + formatter.formatToPercentWithSymbol(percentage);
|
||||||
} else {
|
} else {
|
||||||
result = numPeersString + " / connecting to " + btcNetworkAsString;
|
result = numPeersString + " / connecting to " + btcNetworkAsString;
|
||||||
}
|
}
|
||||||
|
@ -497,7 +497,11 @@ public class MainViewModel implements ViewModel {
|
||||||
setupBtcNumPeersWatcher();
|
setupBtcNumPeersWatcher();
|
||||||
setupP2PNumPeersWatcher();
|
setupP2PNumPeersWatcher();
|
||||||
updateBalance();
|
updateBalance();
|
||||||
setupDevDummyPaymentAccount();
|
if (BitsquareApp.DEV_MODE) {
|
||||||
|
preferences.setShowOwnOffersInOfferBook(true);
|
||||||
|
if (user.getPaymentAccounts().isEmpty())
|
||||||
|
setupDevDummyPaymentAccount();
|
||||||
|
}
|
||||||
setupMarketPriceFeed();
|
setupMarketPriceFeed();
|
||||||
swapPendingOfferFundingEntries();
|
swapPendingOfferFundingEntries();
|
||||||
fillPriceFeedComboBoxItems();
|
fillPriceFeedComboBoxItems();
|
||||||
|
@ -896,12 +900,10 @@ public class MainViewModel implements ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupDevDummyPaymentAccount() {
|
private void setupDevDummyPaymentAccount() {
|
||||||
if (BitsquareApp.DEV_MODE && user.getPaymentAccounts().isEmpty()) {
|
OKPayAccount okPayAccount = new OKPayAccount();
|
||||||
OKPayAccount okPayAccount = new OKPayAccount();
|
okPayAccount.setAccountNr("dummy");
|
||||||
okPayAccount.setAccountNr("dummy");
|
okPayAccount.setAccountName("OKPay dummy");
|
||||||
okPayAccount.setAccountName("OKPay dummy");
|
okPayAccount.setSelectedTradeCurrency(CurrencyUtil.getDefaultTradeCurrency());
|
||||||
okPayAccount.setSelectedTradeCurrency(CurrencyUtil.getDefaultTradeCurrency());
|
user.addPaymentAccount(okPayAccount);
|
||||||
user.addPaymentAccount(okPayAccount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
final StringProperty btcCode = new SimpleStringProperty();
|
final StringProperty btcCode = new SimpleStringProperty();
|
||||||
|
|
||||||
final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
|
final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
|
||||||
|
final BooleanProperty usePercentageBasedPrice = new SimpleBooleanProperty();
|
||||||
//final BooleanProperty isMainNet = new SimpleBooleanProperty();
|
//final BooleanProperty isMainNet = new SimpleBooleanProperty();
|
||||||
//final BooleanProperty isFeeFromFundingTxSufficient = new SimpleBooleanProperty();
|
//final BooleanProperty isFeeFromFundingTxSufficient = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
@ -108,6 +109,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
private Notification walletFundedNotification;
|
private Notification walletFundedNotification;
|
||||||
boolean useSavingsWallet;
|
boolean useSavingsWallet;
|
||||||
Coin totalAvailableBalance;
|
Coin totalAvailableBalance;
|
||||||
|
private double percentageBasedPrice = 0;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -138,6 +140,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
|
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
|
||||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||||
|
|
||||||
|
usePercentageBasedPrice.set(preferences.getUsePercentageBasedPrice());
|
||||||
|
|
||||||
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
|
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
public void onBalanceChanged(Coin balance, Transaction tx) {
|
||||||
|
@ -253,6 +257,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
|
|
||||||
Offer createAndGetOffer() {
|
Offer createAndGetOffer() {
|
||||||
long fiatPrice = priceAsFiat.get() != null ? priceAsFiat.get().getValue() : 0L;
|
long fiatPrice = priceAsFiat.get() != null ? priceAsFiat.get().getValue() : 0L;
|
||||||
|
|
||||||
long amount = amountAsCoin.get() != null ? amountAsCoin.get().getValue() : 0L;
|
long amount = amountAsCoin.get() != null ? amountAsCoin.get().getValue() : 0L;
|
||||||
long minAmount = minAmountAsCoin.get() != null ? minAmountAsCoin.get().getValue() : 0L;
|
long minAmount = minAmountAsCoin.get() != null ? minAmountAsCoin.get().getValue() : 0L;
|
||||||
|
|
||||||
|
@ -284,6 +289,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
keyRing.getPubKeyRing(),
|
keyRing.getPubKeyRing(),
|
||||||
direction,
|
direction,
|
||||||
fiatPrice,
|
fiatPrice,
|
||||||
|
percentageBasedPrice,
|
||||||
|
usePercentageBasedPrice.get(),
|
||||||
amount,
|
amount,
|
||||||
minAmount,
|
minAmount,
|
||||||
tradeCurrencyCode.get(),
|
tradeCurrencyCode.get(),
|
||||||
|
@ -293,7 +300,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
countryCode,
|
countryCode,
|
||||||
acceptedCountryCodes,
|
acceptedCountryCodes,
|
||||||
bankId,
|
bankId,
|
||||||
acceptedBanks);
|
acceptedBanks,
|
||||||
|
priceFeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
|
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
|
||||||
|
@ -378,6 +386,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
return user.getAcceptedArbitrators().size() > 0;
|
return user.getAcceptedArbitrators().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUsePercentageBasedPrice(boolean usePercentageBasedPrice) {
|
||||||
|
this.usePercentageBasedPrice.set(usePercentageBasedPrice);
|
||||||
|
preferences.setUsePercentageBasedPrice(usePercentageBasedPrice);
|
||||||
|
}
|
||||||
|
|
||||||
/*boolean isFeeFromFundingTxSufficient() {
|
/*boolean isFeeFromFundingTxSufficient() {
|
||||||
return !isMainNet.get() || feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0;
|
return !isMainNet.get() || feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0;
|
||||||
}*/
|
}*/
|
||||||
|
@ -484,4 +497,12 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
|
walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
|
||||||
walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE);
|
walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double getPercentageBasedPrice() {
|
||||||
|
return percentageBasedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPercentageBasedPrice(double percentageBasedPrice) {
|
||||||
|
this.percentageBasedPrice = percentageBasedPrice;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.*;
|
import javafx.geometry.*;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
@ -70,6 +71,8 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||||
|
@ -88,22 +91,23 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
private BalanceTextField balanceTextField;
|
private BalanceTextField balanceTextField;
|
||||||
private TitledGroupBg payFundsPane;
|
private TitledGroupBg payFundsPane;
|
||||||
private ProgressIndicator spinner;
|
private ProgressIndicator spinner;
|
||||||
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, placeOfferButton;
|
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, placeOfferButton, usePercentageBasedPriceButton;
|
||||||
private InputTextField amountTextField, minAmountTextField, priceTextField, volumeTextField;
|
private InputTextField amountTextField, minAmountTextField, priceTextField, priceAsPercentageTextField, volumeTextField;
|
||||||
private TextField currencyTextField;
|
private TextField currencyTextField;
|
||||||
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel, amountBtcLabel, priceCurrencyLabel,
|
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel, amountBtcLabel, priceCurrencyLabel,
|
||||||
volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, currencyTextFieldLabel,
|
volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, currencyTextFieldLabel,
|
||||||
currencyComboBoxLabel, spinnerInfoLabel;
|
currencyComboBoxLabel, spinnerInfoLabel, priceAsPercentageLabel;
|
||||||
private TextFieldWithCopyIcon totalToPayTextField;
|
private TextFieldWithCopyIcon totalToPayTextField;
|
||||||
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
||||||
private ComboBox<TradeCurrency> currencyComboBox;
|
private ComboBox<TradeCurrency> currencyComboBox;
|
||||||
private PopOver totalToPayInfoPopover;
|
private PopOver totalToPayInfoPopover;
|
||||||
|
private ToggleButton fixedPriceButton, percentagePriceButton;
|
||||||
|
|
||||||
private OfferView.CloseHandler closeHandler;
|
private OfferView.CloseHandler closeHandler;
|
||||||
|
|
||||||
private ChangeListener<Boolean> amountFocusedListener;
|
private ChangeListener<Boolean> amountFocusedListener;
|
||||||
private ChangeListener<Boolean> minAmountFocusedListener;
|
private ChangeListener<Boolean> minAmountFocusedListener;
|
||||||
private ChangeListener<Boolean> priceFocusedListener;
|
private ChangeListener<Boolean> priceFocusedListener, priceAsPercentageFocusedListener;
|
||||||
private ChangeListener<Boolean> volumeFocusedListener;
|
private ChangeListener<Boolean> volumeFocusedListener;
|
||||||
private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener;
|
private ChangeListener<Boolean> showWarningInvalidBtcDecimalPlacesListener;
|
||||||
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
|
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
|
||||||
|
@ -122,6 +126,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
private Subscription isSpinnerVisibleSubscription;
|
private Subscription isSpinnerVisibleSubscription;
|
||||||
private Subscription cancelButton2StyleSubscription;
|
private Subscription cancelButton2StyleSubscription;
|
||||||
private Subscription balanceSubscription;
|
private Subscription balanceSubscription;
|
||||||
|
private List<Node> editOfferElements = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -175,6 +180,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
if (spinner != null && spinner.isVisible())
|
if (spinner != null && spinner.isVisible())
|
||||||
spinner.setProgress(-1);
|
spinner.setProgress(-1);
|
||||||
|
|
||||||
|
percentagePriceButton.setSelected(model.dataModel.usePercentageBasedPrice.get());
|
||||||
|
fixedPriceButton.setSelected(!model.dataModel.usePercentageBasedPrice.get());
|
||||||
|
|
||||||
directionLabel.setText(model.getDirectionLabel());
|
directionLabel.setText(model.getDirectionLabel());
|
||||||
amountDescriptionLabel.setText(model.getAmountDescription());
|
amountDescriptionLabel.setText(model.getAmountDescription());
|
||||||
addressTextField.setAddress(model.getAddressAsString());
|
addressTextField.setAddress(model.getAddressAsString());
|
||||||
|
@ -277,12 +285,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
private void onShowPayFundsScreen() {
|
private void onShowPayFundsScreen() {
|
||||||
model.onShowPayFundsScreen();
|
model.onShowPayFundsScreen();
|
||||||
|
|
||||||
amountTextField.setMouseTransparent(true);
|
editOfferElements.stream().forEach(node -> {
|
||||||
minAmountTextField.setMouseTransparent(true);
|
node.setMouseTransparent(true);
|
||||||
priceTextField.setMouseTransparent(true);
|
node.setFocusTraversable(false);
|
||||||
volumeTextField.setMouseTransparent(true);
|
});
|
||||||
currencyComboBox.setMouseTransparent(true);
|
|
||||||
paymentAccountsComboBox.setMouseTransparent(true);
|
|
||||||
|
|
||||||
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
||||||
|
|
||||||
|
@ -407,6 +413,11 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
private void addBindings() {
|
private void addBindings() {
|
||||||
amountBtcLabel.textProperty().bind(model.btcCode);
|
amountBtcLabel.textProperty().bind(model.btcCode);
|
||||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> model.tradeCurrencyCode.get() + "/" + model.btcCode.get(), model.btcCode, model.tradeCurrencyCode));
|
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> model.tradeCurrencyCode.get() + "/" + model.btcCode.get(), model.btcCode, model.tradeCurrencyCode));
|
||||||
|
priceTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice);
|
||||||
|
priceCurrencyLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice);
|
||||||
|
priceAsPercentageTextField.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not());
|
||||||
|
priceAsPercentageLabel.disableProperty().bind(model.dataModel.usePercentageBasedPrice.not());
|
||||||
|
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
|
||||||
volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode);
|
volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode);
|
||||||
minAmountBtcLabel.textProperty().bind(model.btcCode);
|
minAmountBtcLabel.textProperty().bind(model.btcCode);
|
||||||
priceDescriptionLabel.textProperty().bind(createStringBinding(() -> BSResources.get("createOffer.amountPriceBox.priceDescription", model.tradeCurrencyCode.get()), model.tradeCurrencyCode));
|
priceDescriptionLabel.textProperty().bind(createStringBinding(() -> BSResources.get("createOffer.amountPriceBox.priceDescription", model.tradeCurrencyCode.get()), model.tradeCurrencyCode));
|
||||||
|
@ -414,6 +425,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
amountTextField.textProperty().bindBidirectional(model.amount);
|
amountTextField.textProperty().bindBidirectional(model.amount);
|
||||||
minAmountTextField.textProperty().bindBidirectional(model.minAmount);
|
minAmountTextField.textProperty().bindBidirectional(model.minAmount);
|
||||||
priceTextField.textProperty().bindBidirectional(model.price);
|
priceTextField.textProperty().bindBidirectional(model.price);
|
||||||
|
priceAsPercentageTextField.textProperty().bindBidirectional(model.priceAsPercentage);
|
||||||
volumeTextField.textProperty().bindBidirectional(model.volume);
|
volumeTextField.textProperty().bindBidirectional(model.volume);
|
||||||
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
|
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
|
||||||
totalToPayTextField.textProperty().bind(model.totalToPay);
|
totalToPayTextField.textProperty().bind(model.totalToPay);
|
||||||
|
@ -452,6 +464,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
private void removeBindings() {
|
private void removeBindings() {
|
||||||
amountBtcLabel.textProperty().unbind();
|
amountBtcLabel.textProperty().unbind();
|
||||||
priceCurrencyLabel.textProperty().unbind();
|
priceCurrencyLabel.textProperty().unbind();
|
||||||
|
priceTextField.disableProperty().unbind();
|
||||||
|
priceCurrencyLabel.disableProperty().unbind();
|
||||||
|
priceAsPercentageTextField.disableProperty().unbind();
|
||||||
|
priceAsPercentageLabel.disableProperty().unbind();
|
||||||
volumeCurrencyLabel.textProperty().unbind();
|
volumeCurrencyLabel.textProperty().unbind();
|
||||||
minAmountBtcLabel.textProperty().unbind();
|
minAmountBtcLabel.textProperty().unbind();
|
||||||
priceDescriptionLabel.textProperty().unbind();
|
priceDescriptionLabel.textProperty().unbind();
|
||||||
|
@ -459,6 +475,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
amountTextField.textProperty().unbindBidirectional(model.amount);
|
amountTextField.textProperty().unbindBidirectional(model.amount);
|
||||||
minAmountTextField.textProperty().unbindBidirectional(model.minAmount);
|
minAmountTextField.textProperty().unbindBidirectional(model.minAmount);
|
||||||
priceTextField.textProperty().unbindBidirectional(model.price);
|
priceTextField.textProperty().unbindBidirectional(model.price);
|
||||||
|
priceAsPercentageTextField.textProperty().unbindBidirectional(model.priceAsPercentage);
|
||||||
|
priceAsPercentageLabel.prefWidthProperty().unbind();
|
||||||
volumeTextField.textProperty().unbindBidirectional(model.volume);
|
volumeTextField.textProperty().unbindBidirectional(model.volume);
|
||||||
volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
|
volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
|
||||||
totalToPayTextField.textProperty().unbind();
|
totalToPayTextField.textProperty().unbind();
|
||||||
|
@ -524,6 +542,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText());
|
model.onFocusOutPriceTextField(oldValue, newValue, priceTextField.getText());
|
||||||
priceTextField.setText(model.price.get());
|
priceTextField.setText(model.price.get());
|
||||||
};
|
};
|
||||||
|
priceAsPercentageFocusedListener = (o, oldValue, newValue) -> {
|
||||||
|
model.onFocusOutPriceAsPercentageTextField(oldValue, newValue, priceAsPercentageTextField.getText());
|
||||||
|
priceAsPercentageTextField.setText(model.priceAsPercentage.get());
|
||||||
|
};
|
||||||
volumeFocusedListener = (o, oldValue, newValue) -> {
|
volumeFocusedListener = (o, oldValue, newValue) -> {
|
||||||
model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
|
model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
|
||||||
volumeTextField.setText(model.volume.get());
|
volumeTextField.setText(model.volume.get());
|
||||||
|
@ -583,6 +605,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
|
|
||||||
tradeCurrencyCodeListener = (observable, oldValue, newValue) -> {
|
tradeCurrencyCodeListener = (observable, oldValue, newValue) -> {
|
||||||
priceTextField.clear();
|
priceTextField.clear();
|
||||||
|
priceAsPercentageTextField.clear();
|
||||||
volumeTextField.clear();
|
volumeTextField.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -621,6 +644,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
amountTextField.focusedProperty().addListener(amountFocusedListener);
|
||||||
minAmountTextField.focusedProperty().addListener(minAmountFocusedListener);
|
minAmountTextField.focusedProperty().addListener(minAmountFocusedListener);
|
||||||
priceTextField.focusedProperty().addListener(priceFocusedListener);
|
priceTextField.focusedProperty().addListener(priceFocusedListener);
|
||||||
|
priceAsPercentageTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
|
||||||
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
|
||||||
|
|
||||||
// warnings
|
// warnings
|
||||||
|
@ -644,6 +668,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
amountTextField.focusedProperty().removeListener(amountFocusedListener);
|
||||||
minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener);
|
minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener);
|
||||||
priceTextField.focusedProperty().removeListener(priceFocusedListener);
|
priceTextField.focusedProperty().removeListener(priceFocusedListener);
|
||||||
|
priceAsPercentageTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
|
||||||
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
|
||||||
|
|
||||||
// warnings
|
// warnings
|
||||||
|
@ -702,11 +727,14 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
|
|
||||||
paymentAccountsComboBox = addLabelComboBox(gridPane, gridRow, "Payment account:", Layout.FIRST_ROW_DISTANCE).second;
|
paymentAccountsComboBox = addLabelComboBox(gridPane, gridRow, "Payment account:", Layout.FIRST_ROW_DISTANCE).second;
|
||||||
paymentAccountsComboBox.setPromptText("Select payment account");
|
paymentAccountsComboBox.setPromptText("Select payment account");
|
||||||
|
editOfferElements.add(paymentAccountsComboBox);
|
||||||
|
|
||||||
// we display either currencyComboBox (multi currency account) or currencyTextField (single)
|
// we display either currencyComboBox (multi currency account) or currencyTextField (single)
|
||||||
Tuple2<Label, ComboBox> currencyComboBoxTuple = addLabelComboBox(gridPane, ++gridRow, "Currency:");
|
Tuple2<Label, ComboBox> currencyComboBoxTuple = addLabelComboBox(gridPane, ++gridRow, "Currency:");
|
||||||
currencyComboBoxLabel = currencyComboBoxTuple.first;
|
currencyComboBoxLabel = currencyComboBoxTuple.first;
|
||||||
|
editOfferElements.add(currencyComboBoxLabel);
|
||||||
currencyComboBox = currencyComboBoxTuple.second;
|
currencyComboBox = currencyComboBoxTuple.second;
|
||||||
|
editOfferElements.add(currencyComboBox);
|
||||||
currencyComboBox.setPromptText("Select currency");
|
currencyComboBox.setPromptText("Select currency");
|
||||||
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
|
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -722,7 +750,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
|
|
||||||
Tuple2<Label, TextField> currencyTextFieldTuple = addLabelTextField(gridPane, gridRow, "Currency:", "", 5);
|
Tuple2<Label, TextField> currencyTextFieldTuple = addLabelTextField(gridPane, gridRow, "Currency:", "", 5);
|
||||||
currencyTextFieldLabel = currencyTextFieldTuple.first;
|
currencyTextFieldLabel = currencyTextFieldTuple.first;
|
||||||
|
editOfferElements.add(currencyTextFieldLabel);
|
||||||
currencyTextField = currencyTextFieldTuple.second;
|
currencyTextField = currencyTextFieldTuple.second;
|
||||||
|
editOfferElements.add(currencyTextField);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAmountPriceGroup() {
|
private void addAmountPriceGroup() {
|
||||||
|
@ -745,14 +775,15 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
gridPane.getChildren().add(imageVBox);
|
gridPane.getChildren().add(imageVBox);
|
||||||
|
|
||||||
addAmountPriceFields();
|
addAmountPriceFields();
|
||||||
|
addSecondRow();
|
||||||
addMinAmountBox();
|
|
||||||
|
|
||||||
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, BSResources.get("createOffer.amountPriceBox.next"), BSResources.get("shared.cancel"));
|
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, BSResources.get("createOffer.amountPriceBox.next"), BSResources.get("shared.cancel"));
|
||||||
nextButton = tuple.first;
|
nextButton = tuple.first;
|
||||||
|
editOfferElements.add(nextButton);
|
||||||
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
||||||
//UserThread.runAfter(() -> nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS);
|
//UserThread.runAfter(() -> nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS);
|
||||||
cancelButton1 = tuple.second;
|
cancelButton1 = tuple.second;
|
||||||
|
editOfferElements.add(cancelButton1);
|
||||||
cancelButton1.setDefaultButton(false);
|
cancelButton1.setDefaultButton(false);
|
||||||
cancelButton1.setOnAction(e -> {
|
cancelButton1.setOnAction(e -> {
|
||||||
close();
|
close();
|
||||||
|
@ -887,9 +918,12 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
|
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
|
||||||
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
|
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
|
||||||
amountTextField = amountValueCurrencyBoxTuple.second;
|
amountTextField = amountValueCurrencyBoxTuple.second;
|
||||||
|
editOfferElements.add(amountTextField);
|
||||||
amountBtcLabel = amountValueCurrencyBoxTuple.third;
|
amountBtcLabel = amountValueCurrencyBoxTuple.third;
|
||||||
|
editOfferElements.add(amountBtcLabel);
|
||||||
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, model.getAmountDescription());
|
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, model.getAmountDescription());
|
||||||
amountDescriptionLabel = amountInputBoxTuple.first;
|
amountDescriptionLabel = amountInputBoxTuple.first;
|
||||||
|
editOfferElements.add(amountDescriptionLabel);
|
||||||
VBox amountBox = amountInputBoxTuple.second;
|
VBox amountBox = amountInputBoxTuple.second;
|
||||||
|
|
||||||
// x
|
// x
|
||||||
|
@ -897,15 +931,42 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
xLabel.setFont(Font.font("Helvetica-Bold", 20));
|
xLabel.setFont(Font.font("Helvetica-Bold", 20));
|
||||||
xLabel.setPadding(new Insets(14, 3, 0, 3));
|
xLabel.setPadding(new Insets(14, 3, 0, 3));
|
||||||
|
|
||||||
// price
|
// price as fiat
|
||||||
Tuple3<HBox, InputTextField, Label> priceValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt"));
|
Tuple3<HBox, InputTextField, Label> priceValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt"));
|
||||||
HBox priceValueCurrencyBox = priceValueCurrencyBoxTuple.first;
|
HBox priceValueCurrencyBox = priceValueCurrencyBoxTuple.first;
|
||||||
priceTextField = priceValueCurrencyBoxTuple.second;
|
priceTextField = priceValueCurrencyBoxTuple.second;
|
||||||
|
editOfferElements.add(priceTextField);
|
||||||
priceCurrencyLabel = priceValueCurrencyBoxTuple.third;
|
priceCurrencyLabel = priceValueCurrencyBoxTuple.third;
|
||||||
|
editOfferElements.add(priceCurrencyLabel);
|
||||||
Tuple2<Label, VBox> priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, BSResources.get("createOffer.amountPriceBox.priceDescription"));
|
Tuple2<Label, VBox> priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, BSResources.get("createOffer.amountPriceBox.priceDescription"));
|
||||||
priceDescriptionLabel = priceInputBoxTuple.first;
|
priceDescriptionLabel = priceInputBoxTuple.first;
|
||||||
|
editOfferElements.add(priceDescriptionLabel);
|
||||||
VBox priceBox = priceInputBoxTuple.second;
|
VBox priceBox = priceInputBoxTuple.second;
|
||||||
|
|
||||||
|
// Fixed/Percentage toggle
|
||||||
|
ToggleGroup toggleGroup = new ToggleGroup();
|
||||||
|
fixedPriceButton = new ToggleButton("Fixed");
|
||||||
|
editOfferElements.add(fixedPriceButton);
|
||||||
|
fixedPriceButton.setId("toggle-price-left");
|
||||||
|
fixedPriceButton.setToggleGroup(toggleGroup);
|
||||||
|
fixedPriceButton.selectedProperty().addListener((ov, oldValue, newValue) -> {
|
||||||
|
model.dataModel.setUsePercentageBasedPrice(!newValue);
|
||||||
|
percentagePriceButton.setSelected(!newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
percentagePriceButton = new ToggleButton("Percentage");
|
||||||
|
editOfferElements.add(percentagePriceButton);
|
||||||
|
percentagePriceButton.setId("toggle-price-right");
|
||||||
|
percentagePriceButton.setToggleGroup(toggleGroup);
|
||||||
|
percentagePriceButton.selectedProperty().addListener((ov, oldValue, newValue) -> {
|
||||||
|
model.dataModel.setUsePercentageBasedPrice(newValue);
|
||||||
|
fixedPriceButton.setSelected(!newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
HBox toggleButtons = new HBox();
|
||||||
|
toggleButtons.setPadding(new Insets(18, 0, 0, 0));
|
||||||
|
toggleButtons.getChildren().addAll(fixedPriceButton, percentagePriceButton);
|
||||||
|
|
||||||
// =
|
// =
|
||||||
Label resultLabel = new Label("=");
|
Label resultLabel = new Label("=");
|
||||||
resultLabel.setFont(Font.font("Helvetica-Bold", 20));
|
resultLabel.setFont(Font.font("Helvetica-Bold", 20));
|
||||||
|
@ -915,15 +976,18 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
Tuple3<HBox, InputTextField, Label> volumeValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.volume.prompt"));
|
Tuple3<HBox, InputTextField, Label> volumeValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.volume.prompt"));
|
||||||
HBox volumeValueCurrencyBox = volumeValueCurrencyBoxTuple.first;
|
HBox volumeValueCurrencyBox = volumeValueCurrencyBoxTuple.first;
|
||||||
volumeTextField = volumeValueCurrencyBoxTuple.second;
|
volumeTextField = volumeValueCurrencyBoxTuple.second;
|
||||||
|
editOfferElements.add(volumeTextField);
|
||||||
volumeCurrencyLabel = volumeValueCurrencyBoxTuple.third;
|
volumeCurrencyLabel = volumeValueCurrencyBoxTuple.third;
|
||||||
|
editOfferElements.add(volumeCurrencyLabel);
|
||||||
Tuple2<Label, VBox> volumeInputBoxTuple = getTradeInputBox(volumeValueCurrencyBox, model.volumeDescriptionLabel.get());
|
Tuple2<Label, VBox> volumeInputBoxTuple = getTradeInputBox(volumeValueCurrencyBox, model.volumeDescriptionLabel.get());
|
||||||
volumeDescriptionLabel = volumeInputBoxTuple.first;
|
volumeDescriptionLabel = volumeInputBoxTuple.first;
|
||||||
|
editOfferElements.add(volumeDescriptionLabel);
|
||||||
VBox volumeBox = volumeInputBoxTuple.second;
|
VBox volumeBox = volumeInputBoxTuple.second;
|
||||||
|
|
||||||
HBox hBox = new HBox();
|
HBox hBox = new HBox();
|
||||||
hBox.setSpacing(5);
|
hBox.setSpacing(5);
|
||||||
hBox.setAlignment(Pos.CENTER_LEFT);
|
hBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
hBox.getChildren().addAll(amountBox, xLabel, priceBox, resultLabel, volumeBox);
|
hBox.getChildren().addAll(amountBox, xLabel, priceBox, toggleButtons, resultLabel, volumeBox);
|
||||||
GridPane.setRowIndex(hBox, gridRow);
|
GridPane.setRowIndex(hBox, gridRow);
|
||||||
GridPane.setColumnIndex(hBox, 1);
|
GridPane.setColumnIndex(hBox, 1);
|
||||||
GridPane.setMargin(hBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 10, 0, 0));
|
GridPane.setMargin(hBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 10, 0, 0));
|
||||||
|
@ -931,19 +995,46 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
gridPane.getChildren().add(hBox);
|
gridPane.getChildren().add(hBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMinAmountBox() {
|
private void addSecondRow() {
|
||||||
|
Tuple3<HBox, InputTextField, Label> priceAsPercentageTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt"));
|
||||||
|
HBox priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first;
|
||||||
|
priceAsPercentageTextField = priceAsPercentageTuple.second;
|
||||||
|
editOfferElements.add(priceAsPercentageTextField);
|
||||||
|
priceAsPercentageLabel = priceAsPercentageTuple.third;
|
||||||
|
editOfferElements.add(priceAsPercentageLabel);
|
||||||
|
|
||||||
|
Tuple2<Label, VBox> priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox, "Distance in % from market price");
|
||||||
|
priceAsPercentageInputBoxTuple.first.setPrefWidth(200);
|
||||||
|
VBox priceAsPercentageInputBox = priceAsPercentageInputBoxTuple.second;
|
||||||
|
|
||||||
|
priceAsPercentageTextField.setPromptText("Enter % value");
|
||||||
|
priceAsPercentageLabel.setText("%");
|
||||||
|
priceAsPercentageLabel.setStyle("-fx-alignment: center;");
|
||||||
|
|
||||||
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
|
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
|
||||||
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
|
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
|
||||||
minAmountTextField = amountValueCurrencyBoxTuple.second;
|
minAmountTextField = amountValueCurrencyBoxTuple.second;
|
||||||
|
editOfferElements.add(minAmountTextField);
|
||||||
minAmountBtcLabel = amountValueCurrencyBoxTuple.third;
|
minAmountBtcLabel = amountValueCurrencyBoxTuple.third;
|
||||||
|
editOfferElements.add(minAmountBtcLabel);
|
||||||
|
|
||||||
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, BSResources.get("createOffer.amountPriceBox" +
|
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, BSResources.get("createOffer.amountPriceBox" +
|
||||||
".minAmountDescription"));
|
".minAmountDescription"));
|
||||||
VBox box = amountInputBoxTuple.second;
|
|
||||||
GridPane.setRowIndex(box, ++gridRow);
|
Label xLabel = new Label("x");
|
||||||
GridPane.setColumnIndex(box, 1);
|
xLabel.setFont(Font.font("Helvetica-Bold", 20));
|
||||||
GridPane.setMargin(box, new Insets(5, 10, 5, 0));
|
xLabel.setPadding(new Insets(14, 3, 0, 3));
|
||||||
gridPane.getChildren().add(box);
|
xLabel.setVisible(false); // we just use it to get the same layout as the upper row
|
||||||
|
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
hBox.setSpacing(5);
|
||||||
|
hBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
hBox.getChildren().addAll(amountInputBoxTuple.second, xLabel, priceAsPercentageInputBox);
|
||||||
|
GridPane.setRowIndex(hBox, ++gridRow);
|
||||||
|
GridPane.setColumnIndex(hBox, 1);
|
||||||
|
GridPane.setMargin(hBox, new Insets(5, 10, 5, 0));
|
||||||
|
GridPane.setColumnSpan(hBox, 2);
|
||||||
|
gridPane.getChildren().add(hBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
final StringProperty amount = new SimpleStringProperty();
|
final StringProperty amount = new SimpleStringProperty();
|
||||||
final StringProperty minAmount = new SimpleStringProperty();
|
final StringProperty minAmount = new SimpleStringProperty();
|
||||||
final StringProperty price = new SimpleStringProperty();
|
final StringProperty price = new SimpleStringProperty();
|
||||||
|
final StringProperty priceAsPercentage = new SimpleStringProperty();
|
||||||
final StringProperty volume = new SimpleStringProperty();
|
final StringProperty volume = new SimpleStringProperty();
|
||||||
final StringProperty volumeDescriptionLabel = new SimpleStringProperty();
|
final StringProperty volumeDescriptionLabel = new SimpleStringProperty();
|
||||||
final StringProperty volumePromptLabel = new SimpleStringProperty();
|
final StringProperty volumePromptLabel = new SimpleStringProperty();
|
||||||
|
@ -103,7 +104,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
|
|
||||||
private ChangeListener<String> amountListener;
|
private ChangeListener<String> amountListener;
|
||||||
private ChangeListener<String> minAmountListener;
|
private ChangeListener<String> minAmountListener;
|
||||||
private ChangeListener<String> priceListener;
|
private ChangeListener<String> priceListener, priceAsPercentageListener;
|
||||||
private ChangeListener<String> volumeListener;
|
private ChangeListener<String> volumeListener;
|
||||||
private ChangeListener<Coin> amountAsCoinListener;
|
private ChangeListener<Coin> amountAsCoinListener;
|
||||||
private ChangeListener<Coin> minAmountAsCoinListener;
|
private ChangeListener<Coin> minAmountAsCoinListener;
|
||||||
|
@ -114,6 +115,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
private ChangeListener<String> errorMessageListener;
|
private ChangeListener<String> errorMessageListener;
|
||||||
private Offer offer;
|
private Offer offer;
|
||||||
private Timer timeoutTimer;
|
private Timer timeoutTimer;
|
||||||
|
private PriceFeed.Type priceFeedType;
|
||||||
|
private boolean priceAsPercentageIsInput;
|
||||||
|
private ChangeListener<Boolean> usePercentageBasedPriceListener;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -235,9 +239,70 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
setPriceToModel();
|
setPriceToModel();
|
||||||
calculateVolume();
|
calculateVolume();
|
||||||
dataModel.calculateTotalToPay();
|
dataModel.calculateTotalToPay();
|
||||||
|
|
||||||
|
if (!priceAsPercentageIsInput) {
|
||||||
|
MarketPrice marketPrice = priceFeed.getMarketPrice(dataModel.tradeCurrencyCode.get());
|
||||||
|
if (marketPrice != null) {
|
||||||
|
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
|
||||||
|
try {
|
||||||
|
double priceAsDouble = formatter.parseNumberStringToDouble(price.get());
|
||||||
|
double priceFactor = priceAsDouble / marketPriceAsDouble;
|
||||||
|
priceFactor = dataModel.getDirection() == Offer.Direction.BUY ? 1 - priceFactor : 1 + priceFactor;
|
||||||
|
priceAsPercentage.set(formatter.formatToPercent(priceFactor, 2));
|
||||||
|
} catch (NumberFormatException t) {
|
||||||
|
priceAsPercentage.set("");
|
||||||
|
new Popup().warning("Your input is not a valid number.")
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
};
|
};
|
||||||
|
priceAsPercentageListener = (ov, oldValue, newValue) -> {
|
||||||
|
if (priceAsPercentageIsInput) {
|
||||||
|
try {
|
||||||
|
if (!newValue.isEmpty() && !newValue.equals("-")) {
|
||||||
|
double percentageBasedPrice = formatter.parsePercentStringToDouble(newValue);
|
||||||
|
if (percentageBasedPrice >= 1 || percentageBasedPrice <= -1) {
|
||||||
|
dataModel.setPercentageBasedPrice(0);
|
||||||
|
UserThread.execute(() -> priceAsPercentage.set("0"));
|
||||||
|
new Popup().warning("You cannot set a percentage of 100% or larger. Please enter a percentage number like \"5.4\" for 5.4%")
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
MarketPrice marketPrice = priceFeed.getMarketPrice(dataModel.tradeCurrencyCode.get());
|
||||||
|
if (marketPrice != null) {
|
||||||
|
percentageBasedPrice = formatter.roundDouble(percentageBasedPrice, 4);
|
||||||
|
dataModel.setPercentageBasedPrice(percentageBasedPrice);
|
||||||
|
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
|
||||||
|
double factor = dataModel.getDirection() == Offer.Direction.BUY ? 1 - percentageBasedPrice : 1 + percentageBasedPrice;
|
||||||
|
double targetPrice = marketPriceAsDouble * factor;
|
||||||
|
price.set(formatter.formatToNumberString(targetPrice, 2));
|
||||||
|
setPriceToModel();
|
||||||
|
calculateVolume();
|
||||||
|
dataModel.calculateTotalToPay();
|
||||||
|
updateButtonDisableState();
|
||||||
|
} else {
|
||||||
|
new Popup().warning("There is no price feed available for that currency. You cannot use percent based price.")
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dataModel.setPercentageBasedPrice(0);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
dataModel.setPercentageBasedPrice(0);
|
||||||
|
UserThread.execute(() -> priceAsPercentage.set("0"));
|
||||||
|
new Popup().warning("Your input is not a valid number. Please enter a percentage number like \"5.4\" for 5.4%")
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
usePercentageBasedPriceListener = (observable, oldValue, newValue) -> {
|
||||||
|
if (newValue)
|
||||||
|
priceValidationResult.set(new InputValidator.ValidationResult(true));
|
||||||
|
};
|
||||||
|
|
||||||
volumeListener = (ov, oldValue, newValue) -> {
|
volumeListener = (ov, oldValue, newValue) -> {
|
||||||
if (isFiatInputValid(newValue).isValid) {
|
if (isFiatInputValid(newValue).isValid) {
|
||||||
setVolumeToModel();
|
setVolumeToModel();
|
||||||
|
@ -266,6 +331,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
amount.addListener(amountListener);
|
amount.addListener(amountListener);
|
||||||
minAmount.addListener(minAmountListener);
|
minAmount.addListener(minAmountListener);
|
||||||
price.addListener(priceListener);
|
price.addListener(priceListener);
|
||||||
|
priceAsPercentage.addListener(priceAsPercentageListener);
|
||||||
|
dataModel.usePercentageBasedPrice.addListener(usePercentageBasedPriceListener);
|
||||||
volume.addListener(volumeListener);
|
volume.addListener(volumeListener);
|
||||||
|
|
||||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||||
|
@ -282,6 +349,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
amount.removeListener(amountListener);
|
amount.removeListener(amountListener);
|
||||||
minAmount.removeListener(minAmountListener);
|
minAmount.removeListener(minAmountListener);
|
||||||
price.removeListener(priceListener);
|
price.removeListener(priceListener);
|
||||||
|
priceAsPercentage.removeListener(priceAsPercentageListener);
|
||||||
|
dataModel.usePercentageBasedPrice.removeListener(usePercentageBasedPriceListener);
|
||||||
volume.removeListener(volumeListener);
|
volume.removeListener(volumeListener);
|
||||||
|
|
||||||
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
|
||||||
|
@ -307,6 +376,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
if (dataModel.paymentAccount != null)
|
if (dataModel.paymentAccount != null)
|
||||||
btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
|
btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
|
||||||
|
|
||||||
|
priceFeedType = direction == Offer.Direction.SELL ? PriceFeed.Type.ASK : PriceFeed.Type.BID;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,8 +488,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
|
|
||||||
// handle minAmount/amount relationship
|
// handle minAmount/amount relationship
|
||||||
if (!dataModel.isMinAmountLessOrEqualAmount()) {
|
if (!dataModel.isMinAmountLessOrEqualAmount()) {
|
||||||
amountValidationResult.set(new InputValidator.ValidationResult(false,
|
minAmount.set(amount.get());
|
||||||
BSResources.get("createOffer.validation.amountSmallerThanMinAmount")));
|
/*amountValidationResult.set(new InputValidator.ValidationResult(false,
|
||||||
|
BSResources.get("createOffer.validation.amountSmallerThanMinAmount")));*/
|
||||||
} else {
|
} else {
|
||||||
amountValidationResult.set(result);
|
amountValidationResult.set(result);
|
||||||
if (minAmount.get() != null)
|
if (minAmount.get() != null)
|
||||||
|
@ -438,8 +510,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
minAmount.set(formatter.formatCoin(dataModel.minAmountAsCoin.get()));
|
minAmount.set(formatter.formatCoin(dataModel.minAmountAsCoin.get()));
|
||||||
|
|
||||||
if (!dataModel.isMinAmountLessOrEqualAmount()) {
|
if (!dataModel.isMinAmountLessOrEqualAmount()) {
|
||||||
minAmountValidationResult.set(new InputValidator.ValidationResult(false,
|
amount.set(minAmount.get());
|
||||||
BSResources.get("createOffer.validation.minAmountLargerThanAmount")));
|
/* minAmountValidationResult.set(new InputValidator.ValidationResult(false,
|
||||||
|
BSResources.get("createOffer.validation.minAmountLargerThanAmount")));*/
|
||||||
} else {
|
} else {
|
||||||
minAmountValidationResult.set(result);
|
minAmountValidationResult.set(result);
|
||||||
if (amount.get() != null)
|
if (amount.get() != null)
|
||||||
|
@ -464,6 +537,12 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFocusOutPriceAsPercentageTextField(boolean oldValue, boolean newValue, String userInput) {
|
||||||
|
priceAsPercentageIsInput = !oldValue && newValue;
|
||||||
|
if (oldValue && !newValue)
|
||||||
|
priceAsPercentage.set(formatter.formatToNumberString(dataModel.getPercentageBasedPrice() * 100, 2));
|
||||||
|
}
|
||||||
|
|
||||||
void onFocusOutVolumeTextField(boolean oldValue, boolean newValue, String userInput) {
|
void onFocusOutVolumeTextField(boolean oldValue, boolean newValue, String userInput) {
|
||||||
if (oldValue && !newValue) {
|
if (oldValue && !newValue) {
|
||||||
InputValidator.ValidationResult result = isFiatInputValid(volume.get());
|
InputValidator.ValidationResult result = isFiatInputValid(volume.get());
|
||||||
|
@ -492,21 +571,14 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
public boolean isPriceInRange() {
|
public boolean isPriceInRange() {
|
||||||
MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode());
|
MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode());
|
||||||
if (marketPrice != null) {
|
if (marketPrice != null) {
|
||||||
double marketPriceAsDouble = marketPrice.getPrice(PriceFeed.Type.LAST);
|
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
|
||||||
Fiat priceAsFiat = dataModel.priceAsFiat.get();
|
Fiat priceAsFiat = dataModel.priceAsFiat.get();
|
||||||
long shiftDivisor = checkedPow(10, priceAsFiat.smallestUnitExponent());
|
long shiftDivisor = checkedPow(10, priceAsFiat.smallestUnitExponent());
|
||||||
double offerPrice = ((double) priceAsFiat.longValue()) / ((double) shiftDivisor);
|
double offerPrice = ((double) priceAsFiat.longValue()) / ((double) shiftDivisor);
|
||||||
if (marketPriceAsDouble != 0 && Math.abs(1 - (offerPrice / marketPriceAsDouble)) > preferences.getMaxPriceDistanceInPercent()) {
|
double percentage = Math.abs(1 - (offerPrice / marketPriceAsDouble));
|
||||||
Popup popup = new Popup();
|
percentage = formatter.roundDouble(percentage, 2);
|
||||||
popup.warning("The price you have entered is outside the max. allowed deviation from the market price.\n" +
|
if (marketPriceAsDouble != 0 && percentage > preferences.getMaxPriceDistanceInPercent()) {
|
||||||
"The max. allowed deviation is " +
|
displayPriceOutOfRangePopup();
|
||||||
formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent()) +
|
|
||||||
" and can be adjusted in the preferences.")
|
|
||||||
.actionButtonText("Change price")
|
|
||||||
.onAction(() -> popup.hide())
|
|
||||||
.closeButtonText("Go to \"Preferences\"")
|
|
||||||
.onClose(() -> navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class))
|
|
||||||
.show();
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -516,6 +588,19 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayPriceOutOfRangePopup() {
|
||||||
|
Popup popup = new Popup();
|
||||||
|
popup.warning("The price you have entered is outside the max. allowed deviation from the market price.\n" +
|
||||||
|
"The max. allowed deviation is " +
|
||||||
|
formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()) +
|
||||||
|
" and can be adjusted in the preferences.")
|
||||||
|
.actionButtonText("Change price")
|
||||||
|
.onAction(() -> popup.hide())
|
||||||
|
.closeButtonText("Go to \"Preferences\"")
|
||||||
|
.onClose(() -> navigation.navigateTo(MainView.class, SettingsView.class, PreferencesView.class))
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
BSFormatter getFormatter() {
|
BSFormatter getFormatter() {
|
||||||
return formatter;
|
return formatter;
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,10 +257,19 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPrice(OfferBookListItem item) {
|
String getPrice(OfferBookListItem item) {
|
||||||
|
if ((item == null))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
Offer offer = item.getOffer();
|
||||||
|
Fiat price = offer.getPrice();
|
||||||
|
String postFix = "";
|
||||||
|
if (offer.getUsePercentageBasedPrice()) {
|
||||||
|
postFix = " (" + formatter.formatToPercentWithSymbol(offer.getMarketPriceMargin()) + ")";
|
||||||
|
}
|
||||||
if (showAllTradeCurrenciesProperty.get())
|
if (showAllTradeCurrenciesProperty.get())
|
||||||
return (item != null) ? formatter.formatFiatWithCode(item.getOffer().getPrice()) : "";
|
return formatter.formatPriceWithCode(price) + postFix;
|
||||||
else
|
else
|
||||||
return (item != null) ? formatter.formatFiat(item.getOffer().getPrice()) : "";
|
return formatter.formatFiat(price) + postFix;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getVolume(OfferBookListItem item) {
|
String getVolume(OfferBookListItem item) {
|
||||||
|
|
|
@ -92,6 +92,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
boolean useSavingsWallet;
|
boolean useSavingsWallet;
|
||||||
Coin totalAvailableBalance;
|
Coin totalAvailableBalance;
|
||||||
private Notification walletFundedNotification;
|
private Notification walletFundedNotification;
|
||||||
|
Fiat tradePrice;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -158,6 +159,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
// called before activate
|
// called before activate
|
||||||
void initWithData(Offer offer) {
|
void initWithData(Offer offer) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
|
tradePrice = offer.getPrice();
|
||||||
|
|
||||||
addressEntry = walletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING);
|
addressEntry = walletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING);
|
||||||
checkNotNull(addressEntry, "addressEntry must not be null");
|
checkNotNull(addressEntry, "addressEntry must not be null");
|
||||||
|
@ -227,6 +229,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
// have it persisted as well.
|
// have it persisted as well.
|
||||||
void onTakeOffer(TradeResultHandler tradeResultHandler) {
|
void onTakeOffer(TradeResultHandler tradeResultHandler) {
|
||||||
tradeManager.onTakeOffer(amountAsCoin.get(),
|
tradeManager.onTakeOffer(amountAsCoin.get(),
|
||||||
|
tradePrice.getValue(),
|
||||||
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
|
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
|
||||||
offer,
|
offer,
|
||||||
paymentAccount.getId(),
|
paymentAccount.getId(),
|
||||||
|
@ -308,7 +311,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
if (offer != null &&
|
if (offer != null &&
|
||||||
amountAsCoin.get() != null &&
|
amountAsCoin.get() != null &&
|
||||||
!amountAsCoin.get().isZero()) {
|
!amountAsCoin.get().isZero()) {
|
||||||
volumeAsFiat.set(new ExchangeRate(offer.getPrice()).coinToFiat(amountAsCoin.get()));
|
volumeAsFiat.set(new ExchangeRate(tradePrice).coinToFiat(amountAsCoin.get()));
|
||||||
|
|
||||||
updateBalance();
|
updateBalance();
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,9 +90,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
private TitledGroupBg payFundsPane;
|
private TitledGroupBg payFundsPane;
|
||||||
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, takeOfferButton;
|
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, takeOfferButton;
|
||||||
private InputTextField amountTextField;
|
private InputTextField amountTextField;
|
||||||
private TextField paymentMethodTextField, currencyTextField, priceTextField, volumeTextField, amountRangeTextField;
|
private TextField paymentMethodTextField, currencyTextField, priceTextField, priceAsPercentageTextField, volumeTextField, amountRangeTextField;
|
||||||
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
|
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
|
||||||
amountBtcLabel, priceCurrencyLabel,
|
amountBtcLabel, priceCurrencyLabel, priceAsPercentageLabel,
|
||||||
volumeCurrencyLabel, amountRangeBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, spinnerInfoLabel, offerAvailabilitySpinnerLabel;
|
volumeCurrencyLabel, amountRangeBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, spinnerInfoLabel, offerAvailabilitySpinnerLabel;
|
||||||
private TextFieldWithCopyIcon totalToPayTextField;
|
private TextFieldWithCopyIcon totalToPayTextField;
|
||||||
private PopOver totalToPayInfoPopover;
|
private PopOver totalToPayInfoPopover;
|
||||||
|
@ -116,6 +116,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
// private Subscription noSufficientFeeSubscription;
|
// private Subscription noSufficientFeeSubscription;
|
||||||
// private MonadicBinding<Boolean> noSufficientFeeBinding;
|
// private MonadicBinding<Boolean> noSufficientFeeBinding;
|
||||||
private Subscription cancelButton2StyleSubscription;
|
private Subscription cancelButton2StyleSubscription;
|
||||||
|
private VBox priceAsPercentageInputBox;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -195,6 +196,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
public void initWithData(Offer offer) {
|
public void initWithData(Offer offer) {
|
||||||
model.initWithData(offer);
|
model.initWithData(offer);
|
||||||
|
|
||||||
|
priceAsPercentageInputBox.setVisible(offer.getUsePercentageBasedPrice());
|
||||||
|
|
||||||
if (model.getOffer().getDirection() == Offer.Direction.SELL) {
|
if (model.getOffer().getDirection() == Offer.Direction.SELL) {
|
||||||
imageView.setId("image-buy-large");
|
imageView.setId("image-buy-large");
|
||||||
directionLabel.setId("direction-icon-label-buy");
|
directionLabel.setId("direction-icon-label-buy");
|
||||||
|
@ -211,7 +214,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
takeOfferButton.setText("Review offer for selling bitcoin");
|
takeOfferButton.setText("Review offer for selling bitcoin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1;
|
boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1;
|
||||||
paymentAccountsLabel.setVisible(showComboBox);
|
paymentAccountsLabel.setVisible(showComboBox);
|
||||||
paymentAccountsLabel.setManaged(showComboBox);
|
paymentAccountsLabel.setManaged(showComboBox);
|
||||||
|
@ -228,6 +230,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
amountDescriptionLabel.setText(model.getAmountDescription());
|
amountDescriptionLabel.setText(model.getAmountDescription());
|
||||||
amountRangeTextField.setText(model.getAmountRange());
|
amountRangeTextField.setText(model.getAmountRange());
|
||||||
priceTextField.setText(model.getPrice());
|
priceTextField.setText(model.getPrice());
|
||||||
|
priceAsPercentageTextField.setText(model.marketPriceMargin);
|
||||||
addressTextField.setPaymentLabel(model.getPaymentLabel());
|
addressTextField.setPaymentLabel(model.getPaymentLabel());
|
||||||
addressTextField.setAddress(model.dataModel.getAddressEntry().getAddressString());
|
addressTextField.setAddress(model.dataModel.getAddressEntry().getAddressString());
|
||||||
}
|
}
|
||||||
|
@ -269,7 +272,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
offerDetailsWindow.hide();
|
offerDetailsWindow.hide();
|
||||||
offerDetailsWindowDisplayed = false;
|
offerDetailsWindowDisplayed = false;
|
||||||
})
|
})
|
||||||
).show(model.getOffer(), model.dataModel.amountAsCoin.get());
|
).show(model.getOffer(), model.dataModel.amountAsCoin.get(), model.dataModel.tradePrice);
|
||||||
offerDetailsWindowDisplayed = true;
|
offerDetailsWindowDisplayed = true;
|
||||||
} else {
|
} else {
|
||||||
new Popup().warning("You have no arbitrator selected.\n" +
|
new Popup().warning("You have no arbitrator selected.\n" +
|
||||||
|
@ -284,7 +287,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
model.onShowPayFundsScreen();
|
model.onShowPayFundsScreen();
|
||||||
|
|
||||||
amountTextField.setMouseTransparent(true);
|
amountTextField.setMouseTransparent(true);
|
||||||
|
amountTextField.setFocusTraversable(false);
|
||||||
priceTextField.setMouseTransparent(true);
|
priceTextField.setMouseTransparent(true);
|
||||||
|
priceAsPercentageTextField.setMouseTransparent(true);
|
||||||
volumeTextField.setMouseTransparent(true);
|
volumeTextField.setMouseTransparent(true);
|
||||||
|
|
||||||
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
||||||
|
@ -389,6 +394,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
|
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
|
||||||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> model.dataModel.getCurrencyCode() + "/" + model.btcCode.get(), model.btcCode));
|
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> model.dataModel.getCurrencyCode() + "/" + model.btcCode.get(), model.btcCode));
|
||||||
|
priceAsPercentageLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
|
||||||
amountRangeBtcLabel.textProperty().bind(model.btcCode);
|
amountRangeBtcLabel.textProperty().bind(model.btcCode);
|
||||||
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
nextButton.disableProperty().bind(model.isNextButtonDisabled);
|
||||||
|
|
||||||
|
@ -414,6 +420,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
addressTextField.amountAsCoinProperty().unbind();
|
addressTextField.amountAsCoinProperty().unbind();
|
||||||
amountTextField.validationResultProperty().unbind();
|
amountTextField.validationResultProperty().unbind();
|
||||||
priceCurrencyLabel.textProperty().unbind();
|
priceCurrencyLabel.textProperty().unbind();
|
||||||
|
priceAsPercentageLabel.prefWidthProperty().unbind();
|
||||||
amountRangeBtcLabel.textProperty().unbind();
|
amountRangeBtcLabel.textProperty().unbind();
|
||||||
nextButton.disableProperty().unbind();
|
nextButton.disableProperty().unbind();
|
||||||
|
|
||||||
|
@ -638,8 +645,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
gridPane.getChildren().add(imageVBox);
|
gridPane.getChildren().add(imageVBox);
|
||||||
|
|
||||||
addAmountPriceFields();
|
addAmountPriceFields();
|
||||||
|
addSecondRow();
|
||||||
addAmountRangeBox();
|
|
||||||
|
|
||||||
HBox hBox = new HBox();
|
HBox hBox = new HBox();
|
||||||
hBox.setSpacing(10);
|
hBox.setSpacing(10);
|
||||||
|
@ -839,18 +845,43 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
gridPane.getChildren().add(hBox);
|
gridPane.getChildren().add(hBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAmountRangeBox() {
|
private void addSecondRow() {
|
||||||
|
Tuple3<HBox, TextField, Label> priceAsPercentageTuple = getValueCurrencyBox();
|
||||||
|
HBox priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first;
|
||||||
|
priceAsPercentageTextField = priceAsPercentageTuple.second;
|
||||||
|
priceAsPercentageLabel = priceAsPercentageTuple.third;
|
||||||
|
|
||||||
|
Tuple2<Label, VBox> priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox, "Distance in % from market price");
|
||||||
|
priceAsPercentageInputBoxTuple.first.setPrefWidth(200);
|
||||||
|
priceAsPercentageInputBox = priceAsPercentageInputBoxTuple.second;
|
||||||
|
|
||||||
|
priceAsPercentageTextField.setPromptText("Enter % value");
|
||||||
|
priceAsPercentageLabel.setText("%");
|
||||||
|
priceAsPercentageLabel.setStyle("-fx-alignment: center;");
|
||||||
|
|
||||||
|
|
||||||
Tuple3<HBox, TextField, Label> amountValueCurrencyBoxTuple = getValueCurrencyBox();
|
Tuple3<HBox, TextField, Label> amountValueCurrencyBoxTuple = getValueCurrencyBox();
|
||||||
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
|
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
|
||||||
amountRangeTextField = amountValueCurrencyBoxTuple.second;
|
amountRangeTextField = amountValueCurrencyBoxTuple.second;
|
||||||
amountRangeBtcLabel = amountValueCurrencyBoxTuple.third;
|
amountRangeBtcLabel = amountValueCurrencyBoxTuple.third;
|
||||||
|
|
||||||
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, BSResources.get("takeOffer.amountPriceBox.amountRangeDescription"));
|
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, BSResources.get("takeOffer.amountPriceBox.amountRangeDescription"));
|
||||||
VBox box = amountInputBoxTuple.second;
|
|
||||||
GridPane.setRowIndex(box, ++gridRow);
|
Label xLabel = new Label("x");
|
||||||
GridPane.setColumnIndex(box, 1);
|
xLabel.setFont(Font.font("Helvetica-Bold", 20));
|
||||||
GridPane.setMargin(box, new Insets(5, 10, 5, 0));
|
xLabel.setPadding(new Insets(14, 3, 0, 3));
|
||||||
gridPane.getChildren().add(box);
|
xLabel.setVisible(false); // we just use it to get the same layout as the upper row
|
||||||
|
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
hBox.setSpacing(5);
|
||||||
|
hBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
hBox.getChildren().addAll(amountInputBoxTuple.second, xLabel, priceAsPercentageInputBox);
|
||||||
|
|
||||||
|
GridPane.setRowIndex(hBox, ++gridRow);
|
||||||
|
GridPane.setColumnIndex(hBox, 1);
|
||||||
|
GridPane.setMargin(hBox, new Insets(5, 10, 5, 0));
|
||||||
|
GridPane.setColumnSpan(hBox, 2);
|
||||||
|
gridPane.getChildren().add(hBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package io.bitsquare.gui.main.offer.takeoffer;
|
package io.bitsquare.gui.main.offer.takeoffer;
|
||||||
|
|
||||||
import io.bitsquare.arbitration.Arbitrator;
|
import io.bitsquare.arbitration.Arbitrator;
|
||||||
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.gui.Navigation;
|
import io.bitsquare.gui.Navigation;
|
||||||
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
||||||
import io.bitsquare.gui.common.model.ViewModel;
|
import io.bitsquare.gui.common.model.ViewModel;
|
||||||
|
@ -52,6 +53,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
final TakeOfferDataModel dataModel;
|
final TakeOfferDataModel dataModel;
|
||||||
private final BtcValidator btcValidator;
|
private final BtcValidator btcValidator;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private PriceFeed priceFeed;
|
||||||
private final Navigation navigation;
|
private final Navigation navigation;
|
||||||
final BSFormatter formatter;
|
final BSFormatter formatter;
|
||||||
|
|
||||||
|
@ -94,6 +96,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
private ConnectionListener connectionListener;
|
private ConnectionListener connectionListener;
|
||||||
// private Subscription isFeeSufficientSubscription;
|
// private Subscription isFeeSufficientSubscription;
|
||||||
private Runnable takeOfferSucceededHandler;
|
private Runnable takeOfferSucceededHandler;
|
||||||
|
String marketPriceMargin;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -101,13 +104,14 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService,
|
public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService, PriceFeed priceFeed,
|
||||||
Navigation navigation, BSFormatter formatter) {
|
Navigation navigation, BSFormatter formatter) {
|
||||||
super(dataModel);
|
super(dataModel);
|
||||||
this.dataModel = dataModel;
|
this.dataModel = dataModel;
|
||||||
|
|
||||||
this.btcValidator = btcValidator;
|
this.btcValidator = btcValidator;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.priceFeed = priceFeed;
|
||||||
this.navigation = navigation;
|
this.navigation = navigation;
|
||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
|
|
||||||
|
@ -159,7 +163,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
|
|
||||||
amountRange = formatter.formatCoin(offer.getMinAmount()) + " - " + formatter.formatCoin(offer.getAmount());
|
amountRange = formatter.formatCoin(offer.getMinAmount()) + " - " + formatter.formatCoin(offer.getAmount());
|
||||||
price = formatter.formatFiat(offer.getPrice());
|
price = formatter.formatFiat(dataModel.tradePrice);
|
||||||
|
marketPriceMargin = formatter.formatToPercentWithSymbol(offer.getMarketPriceMargin());
|
||||||
paymentLabel = BSResources.get("takeOffer.fundsBox.paymentLabel", offer.getId());
|
paymentLabel = BSResources.get("takeOffer.fundsBox.paymentLabel", offer.getId());
|
||||||
|
|
||||||
checkNotNull(dataModel.getAddressEntry(), "dataModel.getAddressEntry() must not be null");
|
checkNotNull(dataModel.getAddressEntry(), "dataModel.getAddressEntry() must not be null");
|
||||||
|
|
|
@ -123,7 +123,7 @@ public class ContractWindow extends Overlay<ContractWindow> {
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Offer date:", formatter.formatDateTime(offer.getDate()));
|
addLabelTextField(gridPane, ++rowIndex, "Offer date:", formatter.formatDateTime(offer.getDate()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Trade date:", formatter.formatDateTime(dispute.getTradeDate()));
|
addLabelTextField(gridPane, ++rowIndex, "Trade date:", formatter.formatDateTime(dispute.getTradeDate()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Trade type:", formatter.getDirectionBothSides(offer.getDirection()));
|
addLabelTextField(gridPane, ++rowIndex, "Trade type:", formatter.getDirectionBothSides(offer.getDirection()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Price:", formatter.formatFiat(offer.getPrice()) + " " + offer.getCurrencyCode());
|
addLabelTextField(gridPane, ++rowIndex, "Trade price:", formatter.formatFiat(contract.getTradePrice()) + " " + offer.getCurrencyCode());
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Trade amount:", formatter.formatCoinWithCode(contract.getTradeAmount()));
|
addLabelTextField(gridPane, ++rowIndex, "Trade amount:", formatter.formatCoinWithCode(contract.getTradeAmount()));
|
||||||
addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Buyer bitcoin address:",
|
addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Buyer bitcoin address:",
|
||||||
contract.getBuyerPayoutAddressString()).second.setMouseTransparent(false);
|
contract.getBuyerPayoutAddressString()).second.setMouseTransparent(false);
|
||||||
|
|
|
@ -47,6 +47,7 @@ import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.utils.ExchangeRate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -232,8 +233,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
||||||
}
|
}
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Traders role:", role);
|
addLabelTextField(gridPane, ++rowIndex, "Traders role:", role);
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Trade amount:", formatter.formatCoinWithCode(contract.getTradeAmount()));
|
addLabelTextField(gridPane, ++rowIndex, "Trade amount:", formatter.formatCoinWithCode(contract.getTradeAmount()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Trade volume:", formatter.formatFiatWithCode(contract.offer.getVolumeByAmount(contract.getTradeAmount())));
|
addLabelTextField(gridPane, ++rowIndex, "Trade price:", formatter.formatFiatWithCode(contract.getTradePrice()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Price:", formatter.formatFiatWithCode(contract.offer.getPrice()));
|
addLabelTextField(gridPane, ++rowIndex, "Trade volume:", formatter.formatFiatWithCode(new ExchangeRate(contract.getTradePrice()).coinToFiat(contract.getTradeAmount())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCheckboxes() {
|
private void addCheckboxes() {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.bitcoinj.utils.Fiat;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -61,6 +62,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
||||||
private final Navigation navigation;
|
private final Navigation navigation;
|
||||||
private Offer offer;
|
private Offer offer;
|
||||||
private Coin tradeAmount;
|
private Coin tradeAmount;
|
||||||
|
private Fiat tradePrice;
|
||||||
private Optional<Runnable> placeOfferHandlerOptional = Optional.empty();
|
private Optional<Runnable> placeOfferHandlerOptional = Optional.empty();
|
||||||
private Optional<Runnable> takeOfferHandlerOptional = Optional.empty();
|
private Optional<Runnable> takeOfferHandlerOptional = Optional.empty();
|
||||||
private ProgressIndicator spinner;
|
private ProgressIndicator spinner;
|
||||||
|
@ -80,9 +82,10 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
||||||
type = Type.Confirmation;
|
type = Type.Confirmation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void show(Offer offer, Coin tradeAmount) {
|
public void show(Offer offer, Coin tradeAmount, Fiat tradePrice) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.tradeAmount = tradeAmount;
|
this.tradeAmount = tradeAmount;
|
||||||
|
this.tradePrice = tradePrice;
|
||||||
|
|
||||||
rowIndex = -1;
|
rowIndex = -1;
|
||||||
width = 900;
|
width = 900;
|
||||||
|
@ -172,7 +175,10 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
||||||
addLabelTextField(gridPane, ++rowIndex, CurrencyUtil.getNameByCode(offer.getCurrencyCode()) + " amount" + fiatDirectionInfo, formatter.formatFiatWithCode(offer.getVolumeByAmount(offer.getAmount())));
|
addLabelTextField(gridPane, ++rowIndex, CurrencyUtil.getNameByCode(offer.getCurrencyCode()) + " amount" + fiatDirectionInfo, formatter.formatFiatWithCode(offer.getVolumeByAmount(offer.getAmount())));
|
||||||
}
|
}
|
||||||
|
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Price:", formatter.formatFiat(offer.getPrice()) + " " + offer.getCurrencyCode() + "/" + "BTC");
|
if (takeOfferHandlerOptional.isPresent())
|
||||||
|
addLabelTextField(gridPane, ++rowIndex, "Price:", formatter.formatFiat(tradePrice) + " " + offer.getCurrencyCode() + "/" + "BTC");
|
||||||
|
else
|
||||||
|
addLabelTextField(gridPane, ++rowIndex, "Price:", formatter.formatFiat(offer.getPrice()) + " " + offer.getCurrencyCode() + "/" + "BTC");
|
||||||
|
|
||||||
if (offer.isMyOffer(keyRing) && user.getPaymentAccount(offer.getOffererPaymentAccountId()) != null)
|
if (offer.isMyOffer(keyRing) && user.getPaymentAccount(offer.getOffererPaymentAccountId()) != null)
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Payment account:", user.getPaymentAccount(offer.getOffererPaymentAccountId()).getAccountName());
|
addLabelTextField(gridPane, ++rowIndex, "Payment account:", user.getPaymentAccount(offer.getOffererPaymentAccountId()).getAccountName());
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class TradeDetailsWindow extends Overlay<TradeDetailsWindow> {
|
||||||
|
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Bitcoin amount" + btcDirectionInfo, formatter.formatCoinWithCode(trade.getTradeAmount()));
|
addLabelTextField(gridPane, ++rowIndex, "Bitcoin amount" + btcDirectionInfo, formatter.formatCoinWithCode(trade.getTradeAmount()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, CurrencyUtil.getNameByCode(offer.getCurrencyCode()) + " amount" + fiatDirectionInfo, formatter.formatFiatWithCode(trade.getTradeVolume()));
|
addLabelTextField(gridPane, ++rowIndex, CurrencyUtil.getNameByCode(offer.getCurrencyCode()) + " amount" + fiatDirectionInfo, formatter.formatFiatWithCode(trade.getTradeVolume()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Price:", formatter.formatPriceWithCode(offer.getPrice()));
|
addLabelTextField(gridPane, ++rowIndex, "Trade price:", formatter.formatPriceWithCode(trade.getTradePrice()));
|
||||||
addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId()));
|
addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId()));
|
||||||
|
|
||||||
// second group
|
// second group
|
||||||
|
|
|
@ -81,7 +81,13 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
|
||||||
tradeIdColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId()));
|
tradeIdColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId()));
|
||||||
dateColumn.setComparator((o1, o2) -> o1.getTradable().getDate().compareTo(o2.getTradable().getDate()));
|
dateColumn.setComparator((o1, o2) -> o1.getTradable().getDate().compareTo(o2.getTradable().getDate()));
|
||||||
directionColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getDirection().compareTo(o2.getTradable().getOffer().getDirection()));
|
directionColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getDirection().compareTo(o2.getTradable().getOffer().getDirection()));
|
||||||
priceColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getPrice().compareTo(o2.getTradable().getOffer().getPrice()));
|
priceColumn.setComparator((o1, o2) -> {
|
||||||
|
Tradable tradable = o1.getTradable();
|
||||||
|
if (tradable instanceof Trade)
|
||||||
|
return ((Trade) o1.getTradable()).getTradePrice().compareTo(((Trade) o2.getTradable()).getTradePrice());
|
||||||
|
else
|
||||||
|
return o1.getTradable().getOffer().getPrice().compareTo(o2.getTradable().getOffer().getPrice());
|
||||||
|
});
|
||||||
volumeColumn.setComparator((o1, o2) -> {
|
volumeColumn.setComparator((o1, o2) -> {
|
||||||
if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) {
|
if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) {
|
||||||
Fiat tradeVolume1 = ((Trade) o1.getTradable()).getTradeVolume();
|
Fiat tradeVolume1 = ((Trade) o1.getTradable()).getTradeVolume();
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.google.inject.Inject;
|
||||||
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
||||||
import io.bitsquare.gui.common.model.ViewModel;
|
import io.bitsquare.gui.common.model.ViewModel;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
|
import io.bitsquare.trade.Tradable;
|
||||||
import io.bitsquare.trade.Trade;
|
import io.bitsquare.trade.Trade;
|
||||||
import io.bitsquare.trade.offer.OpenOffer;
|
import io.bitsquare.trade.offer.OpenOffer;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
@ -54,7 +55,13 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPrice(ClosedTradableListItem item) {
|
String getPrice(ClosedTradableListItem item) {
|
||||||
return (item != null) ? formatter.formatFiat(item.getTradable().getOffer().getPrice()) : "";
|
if (item == null)
|
||||||
|
return "";
|
||||||
|
Tradable tradable = item.getTradable();
|
||||||
|
if (tradable instanceof Trade)
|
||||||
|
return formatter.formatFiat(((Trade) tradable).getTradePrice());
|
||||||
|
else
|
||||||
|
return formatter.formatFiat(tradable.getOffer().getPrice());
|
||||||
}
|
}
|
||||||
|
|
||||||
String getVolume(ClosedTradableListItem item) {
|
String getVolume(ClosedTradableListItem item) {
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
|
||||||
|
|
||||||
tradeIdColumn.setComparator((o1, o2) -> o1.getTrade().getId().compareTo(o2.getTrade().getId()));
|
tradeIdColumn.setComparator((o1, o2) -> o1.getTrade().getId().compareTo(o2.getTrade().getId()));
|
||||||
dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate()));
|
dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate()));
|
||||||
priceColumn.setComparator((o1, o2) -> o1.getTrade().getOffer().getPrice().compareTo(o2.getTrade().getOffer().getPrice()));
|
priceColumn.setComparator((o1, o2) -> o1.getTrade().getTradePrice().compareTo(o2.getTrade().getTradePrice()));
|
||||||
volumeColumn.setComparator((o1, o2) -> o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume()));
|
volumeColumn.setComparator((o1, o2) -> o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume()));
|
||||||
amountColumn.setComparator((o1, o2) -> o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount()));
|
amountColumn.setComparator((o1, o2) -> o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount()));
|
||||||
stateColumn.setComparator((o1, o2) -> model.getState(o1).compareTo(model.getState(o2)));
|
stateColumn.setComparator((o1, o2) -> model.getState(o1).compareTo(model.getState(o2)));
|
||||||
|
|
|
@ -51,7 +51,7 @@ class FailedTradesViewModel extends ActivatableWithDataModel<FailedTradesDataMod
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPrice(FailedTradesListItem item) {
|
String getPrice(FailedTradesListItem item) {
|
||||||
return (item != null) ? formatter.formatFiat(item.getTrade().getOffer().getPrice()) : "";
|
return (item != null) ? formatter.formatFiat(item.getTrade().getTradePrice()) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String getVolume(FailedTradesListItem item) {
|
String getVolume(FailedTradesListItem item) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class PendingTradesListItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Fiat getPrice() {
|
public Fiat getPrice() {
|
||||||
return trade.getOffer().getPrice();
|
return trade.getTradePrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,7 +336,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
||||||
public void updateItem(final PendingTradesListItem item, boolean empty) {
|
public void updateItem(final PendingTradesListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
if (item != null && !empty)
|
if (item != null && !empty)
|
||||||
setText(formatter.formatCoinWithCode(item.getTrade().getPayoutAmount()));
|
setText(formatter.formatCoinWithCode(item.getTrade().getTradeAmount()));
|
||||||
else
|
else
|
||||||
setText(null);
|
setText(null);
|
||||||
}
|
}
|
||||||
|
@ -380,7 +380,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
||||||
public void updateItem(final PendingTradesListItem item, boolean empty) {
|
public void updateItem(final PendingTradesListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
if (item != null && !empty)
|
if (item != null && !empty)
|
||||||
setText(formatter.formatPriceWithCode(item.getTrade().getTradeVolume()));
|
setText(formatter.formatFiatWithCode(item.getTrade().getTradeVolume()));
|
||||||
else
|
else
|
||||||
setText(null);
|
setText(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,19 +296,16 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
|
|
||||||
deviationListener = (observable, oldValue, newValue) -> {
|
deviationListener = (observable, oldValue, newValue) -> {
|
||||||
try {
|
try {
|
||||||
String input = newValue.replace("%", "");
|
double value = formatter.parsePercentStringToDouble(newValue);
|
||||||
input = input.replace(",", ".");
|
preferences.setMaxPriceDistanceInPercent(value);
|
||||||
input = input.replace(" ", "");
|
} catch (NumberFormatException t) {
|
||||||
double value = Double.parseDouble(input);
|
|
||||||
preferences.setMaxPriceDistanceInPercent(value / 100);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
log.error("Exception at parseDouble deviation: " + t.toString());
|
log.error("Exception at parseDouble deviation: " + t.toString());
|
||||||
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
deviationFocusedListener = (observable1, oldValue1, newValue1) -> {
|
deviationFocusedListener = (observable1, oldValue1, newValue1) -> {
|
||||||
if (oldValue1 && !newValue1)
|
if (oldValue1 && !newValue1)
|
||||||
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
|
||||||
};
|
};
|
||||||
|
|
||||||
transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Withdrawal transaction fee (satoshi/byte):").second;
|
transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Withdrawal transaction fee (satoshi/byte):").second;
|
||||||
|
@ -427,7 +424,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Activatab
|
||||||
});
|
});
|
||||||
blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem()));
|
blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem()));
|
||||||
|
|
||||||
deviationInputTextField.setText(formatter.formatToPercent(preferences.getMaxPriceDistanceInPercent()));
|
deviationInputTextField.setText(formatter.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()));
|
||||||
deviationInputTextField.textProperty().addListener(deviationListener);
|
deviationInputTextField.textProperty().addListener(deviationListener);
|
||||||
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);
|
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);
|
||||||
|
|
||||||
|
|
|
@ -325,11 +325,57 @@ public class BSFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String formatToPercent(double value) {
|
public String formatToPercent(double value) {
|
||||||
|
return formatToPercent(value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatToPercent(double value, int digits) {
|
||||||
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale);
|
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale);
|
||||||
decimalFormat.setMinimumFractionDigits(1);
|
decimalFormat.setMinimumFractionDigits(digits);
|
||||||
decimalFormat.setMaximumFractionDigits(1);
|
decimalFormat.setMaximumFractionDigits(digits);
|
||||||
decimalFormat.setGroupingUsed(false);
|
decimalFormat.setGroupingUsed(false);
|
||||||
return decimalFormat.format(value * 100.0) + " %";
|
return decimalFormat.format(value * 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatToNumberString(double value, int digits) {
|
||||||
|
DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance(locale);
|
||||||
|
decimalFormat.setMinimumFractionDigits(digits);
|
||||||
|
decimalFormat.setMaximumFractionDigits(digits);
|
||||||
|
decimalFormat.setGroupingUsed(false);
|
||||||
|
return decimalFormat.format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double parseNumberStringToDouble(String percentString) throws NumberFormatException {
|
||||||
|
try {
|
||||||
|
String input = percentString.replace(",", ".");
|
||||||
|
input = input.replace(" ", "");
|
||||||
|
return Double.parseDouble(input);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatToPercentWithSymbol(double value) {
|
||||||
|
return formatToPercent(value) + " %";
|
||||||
|
}
|
||||||
|
|
||||||
|
public double parsePercentStringToDouble(String percentString) throws NumberFormatException {
|
||||||
|
try {
|
||||||
|
String input = percentString.replace("%", "");
|
||||||
|
input = input.replace(",", ".");
|
||||||
|
input = input.replace(" ", "");
|
||||||
|
double value = Double.parseDouble(input);
|
||||||
|
return value / 100;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double roundDouble(double value, int places) {
|
||||||
|
if (places < 0) throw new IllegalArgumentException();
|
||||||
|
long factor = (long) Math.pow(10, places);
|
||||||
|
value = value * factor;
|
||||||
|
long tmp = Math.round(value);
|
||||||
|
return (double) tmp / factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String cleanInput(String input) {
|
private String cleanInput(String input) {
|
||||||
|
|
|
@ -267,6 +267,8 @@ public class OfferBookViewModelTest {
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
0,
|
0,
|
||||||
tradeCurrencyCode,
|
tradeCurrencyCode,
|
||||||
null,
|
null,
|
||||||
|
@ -275,6 +277,7 @@ public class OfferBookViewModelTest {
|
||||||
countryCode,
|
countryCode,
|
||||||
acceptedCountryCodes,
|
acceptedCountryCodes,
|
||||||
bankId,
|
bankId,
|
||||||
acceptedBanks);
|
acceptedBanks,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue