Improve UI, add notification popups, fee warning,...

This commit is contained in:
Manfred Karrer 2016-02-08 20:09:08 +01:00
parent 9438bc818e
commit 1f8b1b0e01
50 changed files with 634 additions and 435 deletions

View file

@ -50,4 +50,6 @@ public class Tuple3<A, B, C> implements Serializable {
result = 31 * result + (third != null ? third.hashCode() : 0); result = 31 * result + (third != null ? third.hashCode() : 0);
return result; return result;
} }
} }

View file

@ -52,5 +52,6 @@
<version>4.8</version> <version>4.8</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View file

@ -60,6 +60,11 @@ public class FeePolicy {
return FEE_PER_KB; return FEE_PER_KB;
} }
// Some wallets (Mycelium) don't support higher fees
public static Coin getMinFundingFee() {
return Coin.valueOf(20_000);
}
// 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC // 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC
public static Coin getCreateOfferFee() { public static Coin getCreateOfferFee() {

View file

@ -53,7 +53,6 @@ import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -90,7 +89,6 @@ public class WalletService {
private final IntegerProperty numPeers = new SimpleIntegerProperty(0); private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>(); private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>();
public final BooleanProperty shutDownDone = new SimpleBooleanProperty(); public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
private ArbitraryTransactionBloomFilter arbitraryTransactionBloomFilter;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -705,86 +703,4 @@ public class WalletService {
} }
} }
} }
public void requestTransactionBlockchainProvider(Transaction transaction, Consumer<Coin> resultHandler) {
// TODO use http server over tor to request tx in question
// https://btc.blockr.io/api/v1/tx/info/9a0c37209a45a0e61a50a62fcb7d0f52f3d6ed41faaf0afc044d642ab541b675
}
public void requestTransactionFromBlockChain(Transaction transaction, Consumer<Coin> resultHandler) {
requestTransactionBlockchainProvider(transaction, resultHandler);
/* arbitraryTransactionBloomFilter = new ArbitraryTransactionBloomFilter(transaction, resultHandler);
PeerGroup peerGroup = walletAppKit.peerGroup();
peerGroup.addEventListener(arbitraryTransactionBloomFilter);
peerGroup.addPeerFilterProvider(arbitraryTransactionBloomFilter);
log.debug("transaction=" + transaction);
log.debug("transaction.fee=" + transaction.getFee());*/
}
private class ArbitraryTransactionBloomFilter extends AbstractPeerEventListener implements PeerFilterProvider {
private final Transaction transaction;
private final Consumer<Coin> resultHandler;
private final Set<TransactionOutPoint> transactionOutPoints;
public ArbitraryTransactionBloomFilter(Transaction transaction, Consumer<Coin> resultHandler) {
this.transaction = transaction;
this.resultHandler = resultHandler;
transactionOutPoints = transaction.getInputs().stream()
.map(e -> e.getOutpoint() != null ? e.getOutpoint() : null)
.filter(e -> e != null)
.collect(Collectors.toSet());
log.debug("transaction=" + transaction);
log.debug("transaction.fee=" + transaction.getFee());
log.debug("outpoints=" + transactionOutPoints);
}
@Override
public long getEarliestKeyCreationTime() {
return System.currentTimeMillis() / 1000;
}
@Override
public void beginBloomFilterCalculation() {
}
@Override
public int getBloomFilterElementCount() {
return transactionOutPoints.size();
}
@Override
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak);
for (TransactionOutPoint transactionOutPoint : transactionOutPoints) {
filter.insert(transactionOutPoint.bitcoinSerialize());
}
return filter;
}
@Override
public boolean isRequiringUpdateAllBloomFilter() {
return false;
}
@Override
public void endBloomFilterCalculation() {
}
@Override
public void onTransaction(Peer peer, Transaction tx) {
if (transactionOutPoints.contains(tx))
transactionOutPoints.remove(tx);
if (transactionOutPoints.isEmpty())
resultHandler.accept(transaction.getFee());
log.debug("## onTransaction: transaction=" + tx);
log.debug("## onTransaction: transaction.fee=" + tx.getFee());
}
}
} }

View file

@ -0,0 +1,10 @@
package io.bitsquare.btc.http;
import org.bitcoinj.core.Coin;
import java.io.IOException;
import java.io.Serializable;
public interface BlockchainApiProvider extends Serializable {
Coin getFee(String transactionId) throws IOException, HttpException;
}

View file

@ -0,0 +1,44 @@
package io.bitsquare.btc.http;
import com.google.gson.JsonParser;
import io.bitsquare.app.Log;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
// TODO route over tor, support several providers
public class BlockrIOProvider implements BlockchainApiProvider {
private static final Logger log = LoggerFactory.getLogger(BlockrIOProvider.class);
private final HttpClient httpClient;
public static void main(String[] args) throws HttpException, IOException {
Coin fee = new BlockrIOProvider()
.getFee("df67414652722d38b43dcbcac6927c97626a65bd4e76a2e2787e22948a7c5c47");
log.debug("fee " + fee.toFriendlyString());
}
public BlockrIOProvider() {
httpClient = new HttpClient("https://btc.blockr.io/api/v1/tx/info/");
}
@Override
public Coin getFee(String transactionId) throws IOException, HttpException {
Log.traceCall("transactionId=" + transactionId);
try {
return Coin.parseCoin(new JsonParser()
.parse(httpClient.requestWithGET(transactionId))
.getAsJsonObject()
.get("data")
.getAsJsonObject()
.get("fee")
.getAsString());
} catch (IOException | HttpException e) {
log.warn("Error at requesting transaction data from block explorer " + httpClient + "\n" +
"Error =" + e.getMessage());
throw e;
}
}
}

View file

@ -0,0 +1,52 @@
package io.bitsquare.btc.http;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
// TODO route over tor
public class HttpClient implements Serializable {
private String baseUrl;
public HttpClient(String baseUrl) {
this.baseUrl = baseUrl;
}
public String requestWithGET(String param) throws IOException, HttpException {
HttpURLConnection connection = null;
try {
URL url = new URL(baseUrl + param);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
if (connection.getResponseCode() == 200) {
return convertInputStreamToString(connection.getInputStream());
} else {
connection.getErrorStream().close();
throw new HttpException(convertInputStreamToString(connection.getErrorStream()));
}
} finally {
if (connection != null)
connection.getInputStream().close();
}
}
private String convertInputStreamToString(InputStream inputStream) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
return stringBuilder.toString();
}
@Override
public String toString() {
return "HttpClient{" +
"baseUrl='" + baseUrl + '\'' +
'}';
}
}

View file

@ -0,0 +1,7 @@
package io.bitsquare.btc.http;
public class HttpException extends Exception {
public HttpException(String message) {
super(message);
}
}

View file

@ -61,7 +61,7 @@ public class PaymentMethod implements Serializable, Comparable {
public static final List<PaymentMethod> ALL_VALUES = new ArrayList<>(Arrays.asList( public static final List<PaymentMethod> ALL_VALUES = new ArrayList<>(Arrays.asList(
OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY), // tx instant so min. wait time OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY), // tx instant so min. wait time
PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY), PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY),
SEPA = new PaymentMethod(SEPA_ID, 0, 7 * DAY), // sepa takes 1-3 business days. We use 7 days to include safety for holidays SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY), // sepa takes 1-3 business days. We use 8 days to include safety for holidays
SWISH = new PaymentMethod(SWISH_ID, 0, DAY), SWISH = new PaymentMethod(SWISH_ID, 0, DAY),
ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY), ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY),
/* FED_WIRE = new PaymentMethod(FED_WIRE_ID, 0, DAY),*/ /* FED_WIRE = new PaymentMethod(FED_WIRE_ID, 0, DAY),*/

View file

@ -147,6 +147,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
// Mutable // Mutable
private DecryptedMsgWithPubKey decryptedMsgWithPubKey; private DecryptedMsgWithPubKey decryptedMsgWithPubKey;
private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default
private int takeOfferDateAsBlockHeight;
private Coin tradeAmount; private Coin tradeAmount;
private NodeAddress tradingPeerNodeAddress; private NodeAddress tradingPeerNodeAddress;
protected State state; protected State state;
@ -164,8 +165,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
private int checkPaymentTimeAsBlockHeight; private int checkPaymentTimeAsBlockHeight;
private NodeAddress arbitratorNodeAddress; private NodeAddress arbitratorNodeAddress;
private String takerPaymentAccountId; private String takerPaymentAccountId;
private boolean halfTradePeriodReachedWarningDisplayed;
private boolean tradePeriodOverWarningDisplayed;
private String errorMessage; private String errorMessage;
transient private StringProperty errorMessageProperty; transient private StringProperty errorMessageProperty;
transient private ObjectProperty<Coin> tradeAmountProperty; transient private ObjectProperty<Coin> tradeAmountProperty;
@ -418,6 +417,14 @@ abstract public class Trade implements Tradable, Model, Serializable {
this.takeOfferDate = takeOfferDate; this.takeOfferDate = takeOfferDate;
} }
public int getTakeOfferDateAsBlockHeight() {
return takeOfferDateAsBlockHeight;
}
public void setTakeOfferDateAsBlockHeight(int blockHeight) {
takeOfferDateAsBlockHeight = blockHeight;
}
public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) { public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) {
if (tradingPeerNodeAddress == null) if (tradingPeerNodeAddress == null)
log.error("tradingPeerAddress=null"); log.error("tradingPeerAddress=null");
@ -536,24 +543,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
this.takerPaymentAccountId = takerPaymentAccountId; this.takerPaymentAccountId = takerPaymentAccountId;
} }
public void setHalfTradePeriodReachedWarningDisplayed(boolean halfTradePeriodReachedWarningDisplayed) {
this.halfTradePeriodReachedWarningDisplayed = halfTradePeriodReachedWarningDisplayed;
persist();
}
public boolean isHalfTradePeriodReachedWarningDisplayed() {
return halfTradePeriodReachedWarningDisplayed;
}
public void setTradePeriodOverWarningDisplayed(boolean tradePeriodOverWarningDisplayed) {
this.tradePeriodOverWarningDisplayed = tradePeriodOverWarningDisplayed;
persist();
}
public boolean isTradePeriodOverWarningDisplayed() {
return tradePeriodOverWarningDisplayed;
}
public void setContractHash(byte[] contractHash) { public void setContractHash(byte[] contractHash) {
this.contractHash = contractHash; this.contractHash = contractHash;
} }
@ -638,8 +627,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
"\n\tcheckPaymentTimeAsBlockHeight=" + checkPaymentTimeAsBlockHeight + "\n\tcheckPaymentTimeAsBlockHeight=" + checkPaymentTimeAsBlockHeight +
"\n\tarbitratorNodeAddress=" + arbitratorNodeAddress + "\n\tarbitratorNodeAddress=" + arbitratorNodeAddress +
"\n\ttakerPaymentAccountId='" + takerPaymentAccountId + '\'' + "\n\ttakerPaymentAccountId='" + takerPaymentAccountId + '\'' +
"\n\thalfTradePeriodReachedWarningDisplayed=" + halfTradePeriodReachedWarningDisplayed +
"\n\ttradePeriodOverWarningDisplayed=" + tradePeriodOverWarningDisplayed +
"\n\terrorMessage='" + errorMessage + '\'' + "\n\terrorMessage='" + errorMessage + '\'' +
'}'; '}';
} }

View file

@ -281,6 +281,7 @@ public class TradeManager {
trade = new BuyerAsTakerTrade(offer, amount, model.getPeerNodeAddress(), tradableListStorage); trade = new BuyerAsTakerTrade(offer, amount, model.getPeerNodeAddress(), tradableListStorage);
trade.setTakeOfferDate(new Date()); trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
trade.setTakerPaymentAccountId(paymentAccountId); trade.setTakerPaymentAccountId(paymentAccountId);
initTrade(trade); initTrade(trade);
@ -382,8 +383,11 @@ public class TradeManager {
return offer.isMyOffer(keyRing); return offer.isMyOffer(keyRing);
} }
public boolean isMyOfferInBtcBuyerRole(Offer offer) {
return !(isMyOffer(offer) ^ offer.getDirection() == Offer.Direction.BUY);
}
public Optional<Trade> getTradeById(String tradeId) { public Optional<Trade> getTradeById(String tradeId) {
return trades.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); return trades.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
} }
} }

View file

@ -133,10 +133,7 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsOffererTrade, TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsOffererTrade,
() -> handleTaskRunnerSuccess("handle DepositTxPublishedMessage"), () -> handleTaskRunnerSuccess("handle DepositTxPublishedMessage"),
this::handleTaskRunnerFault); this::handleTaskRunnerFault);
taskRunner.addTasks( taskRunner.addTasks(ProcessDepositTxPublishedMessage.class);
ProcessDepositTxPublishedMessage.class,
AddDepositTxToWallet.class
);
taskRunner.run(); taskRunner.run();
} }

View file

@ -132,10 +132,7 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
() -> handleTaskRunnerSuccess("DepositTxPublishedMessage"), () -> handleTaskRunnerSuccess("DepositTxPublishedMessage"),
this::handleTaskRunnerFault); this::handleTaskRunnerFault);
taskRunner.addTasks( taskRunner.addTasks(ProcessDepositTxPublishedMessage.class);
ProcessDepositTxPublishedMessage.class,
AddDepositTxToWallet.class
);
taskRunner.run(); taskRunner.run();
} }

View file

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

View file

@ -1,48 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.trade.tasks.offerer;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddDepositTxToWallet extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(AddDepositTxToWallet.class);
public AddDepositTxToWallet(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// To access tx confidence we need to add that tx into our wallet.
Transaction depositTx = processModel.getTradeWalletService().addTransactionToWallet(trade.getDepositTx());
// update with full tx
trade.setDepositTx(depositTx);
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -22,6 +22,7 @@ import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage; import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask; import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -47,9 +48,15 @@ public class ProcessDepositTxPublishedMessage extends TradeTask {
checkTradeId(processModel.getId(), message); checkTradeId(processModel.getId(), message);
checkNotNull(message); checkNotNull(message);
checkArgument(message.depositTx != null); checkArgument(message.depositTx != null);
trade.setDepositTx(processModel.getWalletService().getTransactionFromSerializedTx(message.depositTx));
// To access tx confidence we need to add that tx into our wallet.
Transaction transactionFromSerializedTx = processModel.getWalletService().getTransactionFromSerializedTx(message.depositTx);
// update with full tx
trade.setDepositTx(processModel.getTradeWalletService().addTransactionToWallet(transactionFromSerializedTx));
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED); trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED);
trade.setTakeOfferDate(new Date()); trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
if (trade instanceof OffererTrade) if (trade instanceof OffererTrade)
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer()); processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());

View file

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

View file

@ -21,6 +21,8 @@ import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.http.BlockchainApiProvider;
import io.bitsquare.btc.http.BlockrIOProvider;
import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency; import io.bitsquare.locale.TradeCurrency;
@ -65,12 +67,15 @@ public class Preferences implements Serializable {
new BlockChainExplorer("Blockr.io", "https://btc.blockr.io/tx/info/", "https://btc.blockr.io/address/info/"), new BlockChainExplorer("Blockr.io", "https://btc.blockr.io/tx/info/", "https://btc.blockr.io/address/info/"),
new BlockChainExplorer("Biteasy", "https://www.biteasy.com/transactions/", "https://www.biteasy.com/addresses/") new BlockChainExplorer("Biteasy", "https://www.biteasy.com/transactions/", "https://www.biteasy.com/addresses/")
)); ));
private BlockchainApiProvider blockchainApiProvider;
public static List<String> getBtcDenominations() { public static List<String> getBtcDenominations() {
return BTC_DENOMINATIONS; return BTC_DENOMINATIONS;
} }
private static Locale defaultLocale = Locale.getDefault(); private static Locale defaultLocale = Locale.getDefault();
//TODO test with other locales
//private static Locale defaultLocale = Locale.US;
public static Locale getDefaultLocale() { public static Locale getDefaultLocale() {
return defaultLocale; return defaultLocale;
@ -91,7 +96,6 @@ public class Preferences implements Serializable {
private String btcDenomination = MonetaryFormat.CODE_BTC; private String btcDenomination = MonetaryFormat.CODE_BTC;
private boolean useAnimations = true; private boolean useAnimations = true;
private boolean useEffects = true; private boolean useEffects = true;
private boolean displaySecurityDepositInfo = true;
private final ArrayList<TradeCurrency> tradeCurrencies; private final ArrayList<TradeCurrency> tradeCurrencies;
private BlockChainExplorer blockChainExplorerMainNet; private BlockChainExplorer blockChainExplorerMainNet;
private BlockChainExplorer blockChainExplorerTestNet; private BlockChainExplorer blockChainExplorerTestNet;
@ -130,7 +134,6 @@ public class Preferences implements Serializable {
setUseEffects(persisted.useEffects); setUseEffects(persisted.useEffects);
setTradeCurrencies(persisted.tradeCurrencies); setTradeCurrencies(persisted.tradeCurrencies);
tradeCurrencies = new ArrayList<>(tradeCurrenciesAsObservable); tradeCurrencies = new ArrayList<>(tradeCurrenciesAsObservable);
displaySecurityDepositInfo = persisted.getDisplaySecurityDepositInfo();
setBlockChainExplorerTestNet(persisted.getBlockChainExplorerTestNet()); setBlockChainExplorerTestNet(persisted.getBlockChainExplorerTestNet());
setBlockChainExplorerMainNet(persisted.getBlockChainExplorerMainNet()); setBlockChainExplorerMainNet(persisted.getBlockChainExplorerMainNet());
@ -153,6 +156,8 @@ public class Preferences implements Serializable {
defaultTradeCurrency = preferredTradeCurrency; defaultTradeCurrency = preferredTradeCurrency;
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ(); useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
blockchainApiProvider = persisted.getBlockchainApiProvider();
try { try {
setTxFeePerKB(persisted.getTxFeePerKB()); setTxFeePerKB(persisted.getTxFeePerKB());
} catch (Exception e) { } catch (Exception e) {
@ -175,6 +180,8 @@ public class Preferences implements Serializable {
preferredLocale = getDefaultLocale(); preferredLocale = getDefaultLocale();
preferredTradeCurrency = getDefaultTradeCurrency(); preferredTradeCurrency = getDefaultTradeCurrency();
blockchainApiProvider = new BlockrIOProvider();
storage.queueUpForSave(); storage.queueUpForSave();
} }
@ -223,11 +230,6 @@ public class Preferences implements Serializable {
this.useEffectsProperty.set(useEffectsProperty); this.useEffectsProperty.set(useEffectsProperty);
} }
public void setDisplaySecurityDepositInfo(boolean displaySecurityDepositInfo) {
this.displaySecurityDepositInfo = displaySecurityDepositInfo;
storage.queueUpForSave(2000);
}
public void setBitcoinNetwork(BitcoinNetwork bitcoinNetwork) { public void setBitcoinNetwork(BitcoinNetwork bitcoinNetwork) {
if (this.bitcoinNetwork != bitcoinNetwork) if (this.bitcoinNetwork != bitcoinNetwork)
bitsquareEnvironment.saveBitcoinNetwork(bitcoinNetwork); bitsquareEnvironment.saveBitcoinNetwork(bitcoinNetwork);
@ -302,6 +304,10 @@ public class Preferences implements Serializable {
storage.queueUpForSave(); storage.queueUpForSave();
} }
public void setBlockchainApiProvider(BlockchainApiProvider blockchainApiProvider) {
this.blockchainApiProvider = blockchainApiProvider;
storage.queueUpForSave();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getter // Getter
@ -319,10 +325,6 @@ public class Preferences implements Serializable {
return useAnimationsProperty.get(); return useAnimationsProperty.get();
} }
public boolean getDisplaySecurityDepositInfo() {
return displaySecurityDepositInfo;
}
public StringProperty btcDenominationProperty() { public StringProperty btcDenominationProperty() {
return btcDenominationProperty; return btcDenominationProperty;
} }
@ -425,4 +427,9 @@ public class Preferences implements Serializable {
public boolean getUseTorForBitcoinJ() { public boolean getUseTorForBitcoinJ() {
return useTorForBitcoinJ; return useTorForBitcoinJ;
} }
public BlockchainApiProvider getBlockchainApiProvider() {
return blockchainApiProvider;
}
} }

View file

@ -75,7 +75,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 = true; public static final boolean DEV_MODE = false;
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;

View file

@ -585,12 +585,14 @@ textfield */
-fx-text-fill: -bs-grey; -fx-text-fill: -bs-grey;
-fx-body-color: linear-gradient(to bottom, -bs-content-bg-grey, #F0F0F0); -fx-body-color: linear-gradient(to bottom, -bs-content-bg-grey, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, -bs-bg-grey, #ccc); -fx-outer-border: linear-gradient(to bottom, -bs-bg-grey, #ccc);
/* -fx-body-color: #F0F0F0;
-fx-outer-border: #ccc;*/
-fx-background-color: -fx-shadow-highlight-color, -fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border, -fx-outer-border,
-fx-inner-border, -fx-inner-border,
-fx-body-color; -fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2; -fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px; -fx-background-radius: 1px, 1px, 1px, 1px;
} }
#trade-wizard-item-background-active { #trade-wizard-item-background-active {
@ -598,23 +600,27 @@ textfield */
-fx-font-size: 14; -fx-font-size: 14;
-fx-body-color: linear-gradient(to bottom, #f1f6f7, #e7f5f9); -fx-body-color: linear-gradient(to bottom, #f1f6f7, #e7f5f9);
-fx-outer-border: linear-gradient(to bottom, #b5e1ef, #6aa4b6); -fx-outer-border: linear-gradient(to bottom, #b5e1ef, #6aa4b6);
/* -fx-body-color: #e7f5f9;
-fx-outer-border: #6aa4b6;*/
-fx-background-color: -fx-shadow-highlight-color, -fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border, -fx-outer-border,
-fx-inner-border, -fx-inner-border,
-fx-body-color; -fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2; -fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px; -fx-background-radius: 1px, 1px, 1px, 1px;
} }
#trade-wizard-item-background-completed { #trade-wizard-item-background-completed {
-fx-body-color: linear-gradient(to bottom, -bs-content-bg-grey, #E1E9E1); -fx-body-color: linear-gradient(to bottom, -bs-content-bg-grey, #E1E9E1);
-fx-outer-border: linear-gradient(to bottom, #99ba9c, #619865); -fx-outer-border: linear-gradient(to bottom, #99ba9c, #619865);
/* -fx-body-color: #def6df;
-fx-outer-border: #7db581;*/
-fx-background-color: -fx-shadow-highlight-color, -fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border, -fx-outer-border,
-fx-inner-border, -fx-inner-border,
-fx-body-color; -fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2; -fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px; -fx-background-radius: 1px, 1px, 1px, 1px;
} }
#open-support-button { #open-support-button {

View file

@ -30,7 +30,6 @@ public class HyperlinkWithIcon extends HBox {
icon = new Label(); icon = new Label();
icon.getStyleClass().add("external-link-icon"); icon.getStyleClass().add("external-link-icon");
AwesomeDude.setIcon(icon, awesomeIcon); AwesomeDude.setIcon(icon, awesomeIcon);
icon.setMinWidth(20); icon.setMinWidth(20);
icon.setOpacity(0.7); icon.setOpacity(0.7);

View file

@ -23,7 +23,6 @@ import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.*; import io.bitsquare.gui.common.view.*;
import io.bitsquare.gui.components.SystemNotification;
import io.bitsquare.gui.main.account.AccountView; import io.bitsquare.gui.main.account.AccountView;
import io.bitsquare.gui.main.disputes.DisputesView; import io.bitsquare.gui.main.disputes.DisputesView;
import io.bitsquare.gui.main.funds.FundsView; import io.bitsquare.gui.main.funds.FundsView;
@ -454,10 +453,10 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
notification.visibleProperty().bind(model.showPendingTradesNotification); notification.visibleProperty().bind(model.showPendingTradesNotification);
buttonHolder.getChildren().add(notification); buttonHolder.getChildren().add(notification);
model.showPendingTradesNotification.addListener((ov, oldValue, newValue) -> { /* model.showPendingTradesNotification.addListener((ov, oldValue, newValue) -> {
if (newValue) if (newValue)
SystemNotification.openInfoNotification(title, "You received a new trade message."); SystemNotification.openInfoNotification(title, "You received a new trade message.");
}); });*/
} }
private void setupDisputesIcon(Pane buttonHolder) { private void setupDisputesIcon(Pane buttonHolder) {
@ -478,10 +477,10 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
notification.visibleProperty().bind(model.showOpenDisputesNotification); notification.visibleProperty().bind(model.showOpenDisputesNotification);
buttonHolder.getChildren().add(notification); buttonHolder.getChildren().add(notification);
model.showOpenDisputesNotification.addListener((ov, oldValue, newValue) -> { /* model.showOpenDisputesNotification.addListener((ov, oldValue, newValue) -> {
if (newValue) if (newValue)
SystemNotification.openInfoNotification(title, "You received a dispute message."); SystemNotification.openInfoNotification(title, "You received a dispute message.");
}); });*/
} }
private class NavButton extends ToggleButton { private class NavButton extends ToggleButton {

View file

@ -29,10 +29,14 @@ import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.*; import io.bitsquare.btc.*;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ViewModel; import io.bitsquare.gui.common.model.ViewModel;
import io.bitsquare.gui.common.view.ViewPath;
import io.bitsquare.gui.components.BalanceTextField; import io.bitsquare.gui.components.BalanceTextField;
import io.bitsquare.gui.components.BalanceWithConfirmationTextField; import io.bitsquare.gui.components.BalanceWithConfirmationTextField;
import io.bitsquare.gui.components.TxIdTextField; import io.bitsquare.gui.components.TxIdTextField;
import io.bitsquare.gui.main.portfolio.PortfolioView;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView;
import io.bitsquare.gui.popups.DisplayAlertMessagePopup; import io.bitsquare.gui.popups.DisplayAlertMessagePopup;
import io.bitsquare.gui.popups.Popup; import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.popups.WalletPasswordPopup; import io.bitsquare.gui.popups.WalletPasswordPopup;
@ -57,6 +61,7 @@ import javafx.collections.ListChangeListener;
import org.bitcoinj.core.*; import org.bitcoinj.core.*;
import org.bitcoinj.store.BlockStoreException; import org.bitcoinj.store.BlockStoreException;
import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding; import org.fxmisc.easybind.monadic.MonadicBinding;
import org.reactfx.util.FxTimer; import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer; import org.reactfx.util.Timer;
@ -85,6 +90,7 @@ public class MainViewModel implements ViewModel {
private final Preferences preferences; private final Preferences preferences;
private final AlertManager alertManager; private final AlertManager alertManager;
private final WalletPasswordPopup walletPasswordPopup; private final WalletPasswordPopup walletPasswordPopup;
private Navigation navigation;
private final BSFormatter formatter; private final BSFormatter formatter;
// BTC network // BTC network
@ -135,7 +141,7 @@ public class MainViewModel implements ViewModel {
ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager, ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager,
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences, OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
User user, AlertManager alertManager, WalletPasswordPopup walletPasswordPopup, User user, AlertManager alertManager, WalletPasswordPopup walletPasswordPopup,
BSFormatter formatter) { Navigation navigation, BSFormatter formatter) {
this.user = user; this.user = user;
this.walletService = walletService; this.walletService = walletService;
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
@ -147,6 +153,7 @@ public class MainViewModel implements ViewModel {
this.preferences = preferences; this.preferences = preferences;
this.alertManager = alertManager; this.alertManager = alertManager;
this.walletPasswordPopup = walletPasswordPopup; this.walletPasswordPopup = walletPasswordPopup;
this.navigation = navigation;
this.formatter = formatter; this.formatter = formatter;
btcNetworkAsString = formatter.formatBitcoinNetwork(preferences.getBitcoinNetwork()) + btcNetworkAsString = formatter.formatBitcoinNetwork(preferences.getBitcoinNetwork()) +
@ -362,10 +369,12 @@ public class MainViewModel implements ViewModel {
tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> { tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> {
change.next(); change.next();
addDisputeStateListeners(change.getAddedSubList()); addDisputeStateListeners(change.getAddedSubList());
addTradeStateListeners(change.getAddedSubList());
pendingTradesChanged(); pendingTradesChanged();
}); });
pendingTradesChanged(); pendingTradesChanged();
addDisputeStateListeners(tradeManager.getTrades()); addDisputeStateListeners(tradeManager.getTrades());
addTradeStateListeners(tradeManager.getTrades());
// arbitratorManager // arbitratorManager
@ -653,25 +662,25 @@ public class MainViewModel implements ViewModel {
case HALF_REACHED: case HALF_REACHED:
id = "displayHalfTradePeriodOver" + trade.getId(); id = "displayHalfTradePeriodOver" + trade.getId();
if (preferences.showAgain(id)) { if (preferences.showAgain(id)) {
preferences.dontShowAgain(id);
new Popup().warning("Your trade with ID " + trade.getShortId() + new Popup().warning("Your trade with ID " + trade.getShortId() +
" has reached the half of the max. allowed trading period and " + " has reached the half of the max. allowed trading period and " +
"is still not completed.\n\n" + "is still not completed.\n\n" +
"The trade period ends on " + limitDate + "\n\n" + "The trade period ends on " + limitDate + "\n\n" +
"Please check your trade state at \"Portfolio/Open trades\" for further information.") "Please check your trade state at \"Portfolio/Open trades\" for further information.")
.onClose(() -> preferences.dontShowAgain(id))
.show(); .show();
} }
break; break;
case TRADE_PERIOD_OVER: case TRADE_PERIOD_OVER:
id = "displayTradePeriodOver" + trade.getId(); id = "displayTradePeriodOver" + trade.getId();
if (preferences.showAgain(id)) { if (preferences.showAgain(id)) {
preferences.dontShowAgain(id);
new Popup().warning("Your trade with ID " + trade.getShortId() + new Popup().warning("Your trade with ID " + trade.getShortId() +
" has reached the max. allowed trading period and is " + " has reached the max. allowed trading period and is " +
"not completed.\n\n" + "not completed.\n\n" +
"The trade period ended on " + limitDate + "\n\n" + "The trade period ended on " + limitDate + "\n\n" +
"Please check your trade at \"Portfolio/Open trades\" for contacting " + "Please check your trade at \"Portfolio/Open trades\" for contacting " +
"the arbitrator.") "the arbitrator.")
.onClose(() -> preferences.dontShowAgain(id))
.show(); .show();
} }
break; break;
@ -706,6 +715,142 @@ public class MainViewModel implements ViewModel {
showPendingTradesNotification.set(numPendingTrades > 0); showPendingTradesNotification.set(numPendingTrades > 0);
} }
private void addTradeStateListeners(List<? extends Trade> addedTrades) {
addedTrades.stream().forEach(trade -> {
Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
if (newValue != null) {
applyState(trade);
}
});
});
/* addedTrades.stream()
.forEach(trade -> trade.stateProperty().addListener((observable, oldValue, newValue) -> {
String msg = "";
log.debug("addTradeStateListeners " + newValue);
switch (newValue) {
case PREPARATION:
case TAKER_FEE_PAID:
case DEPOSIT_PUBLISH_REQUESTED:
case DEPOSIT_PUBLISHED:
case DEPOSIT_SEEN_IN_NETWORK:
case DEPOSIT_PUBLISHED_MSG_SENT:
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
break;
case DEPOSIT_CONFIRMED:
msg = newValue.name();
break;
case FIAT_PAYMENT_STARTED:
break;
case FIAT_PAYMENT_STARTED_MSG_SENT:
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
break;
case FIAT_PAYMENT_RECEIPT:
break;
case FIAT_PAYMENT_RECEIPT_MSG_SENT:
break;
case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
break;
case PAYOUT_TX_SENT:
break;
case PAYOUT_TX_RECEIVED:
break;
case PAYOUT_TX_COMMITTED:
break;
case PAYOUT_BROAD_CASTED:
break;
case WITHDRAW_COMPLETED:
break;
default:
log.warn("unhandled processState " + newValue);
break;
}
//new Popup().information(msg).show();
}));*/
}
private void applyState(Trade trade) {
Trade.State state = trade.getState();
log.debug("addTradeStateListeners " + state);
boolean isBtcBuyer = tradeManager.isMyOfferInBtcBuyerRole(trade.getOffer());
String headLine = "Notification for trade with ID " + trade.getShortId();
String message = null;
String id = "notificationPopup_" + state + trade.getId();
if (isBtcBuyer) {
switch (state) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a seller.\n" +
"You need to wait for one blockchain confirmation before starting the payment.";
break;
case DEPOSIT_CONFIRMED:
message = "The deposit transaction of your trade has got the first blockchain confirmation.\n" +
"You have to start the payment to the bitcoin seller now.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
case PAYOUT_TX_COMMITTED:
case PAYOUT_TX_SENT:*/
case PAYOUT_BROAD_CASTED:
message = "The bitcoin seller has confirmed the receipt of your payment and the payout transaction has been published.\n" +
"The trade is now completed and you can withdraw your funds.";
break;
}
} else {
switch (state) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a buyer.\n" +
"You need to wait for one blockchain confirmation before starting the payment.";
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
message = "The bitcoin buyer has started the payment.\n" +
"Please check your payment account if you have received his payment.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_SENT:
case PAYOUT_TX_RECEIVED:
case PAYOUT_TX_COMMITTED:*/
case PAYOUT_BROAD_CASTED:
message = "The payout transaction has been published.\n" +
"The trade is now completed and you can withdraw your funds.";
}
}
ViewPath currentPath = navigation.getCurrentPath();
boolean isPendingTradesViewCurrentView = currentPath != null &&
currentPath.size() == 3 &&
currentPath.get(2).equals(PendingTradesView.class);
if (message != null) {
//TODO we get that called initially before the navigation is inited
if (isPendingTradesViewCurrentView || currentPath == null) {
if (preferences.showAgain(id))
new Popup().headLine(headLine)
.message(message)
.show();
preferences.dontShowAgain(id);
} else {
if (preferences.showAgain(id))
new Popup().headLine(headLine)
.message(message)
.actionButtonText("Go to \"Portfolio/Open trades\"")
.onAction(() -> {
FxTimer.runLater(Duration.ofMillis(100),
() -> navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class)
);
})
.show();
preferences.dontShowAgain(id);
}
}
}
private void addDisputeStateListeners(List<? extends Trade> addedTrades) { private void addDisputeStateListeners(List<? extends Trade> addedTrades) {
addedTrades.stream().forEach(trade -> trade.disputeStateProperty().addListener((observable, oldValue, newValue) -> { addedTrades.stream().forEach(trade -> trade.disputeStateProperty().addListener((observable, oldValue, newValue) -> {
switch (newValue) { switch (newValue) {

View file

@ -89,7 +89,7 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
String text = "Bitsquare does not use a single application wallet, but dedicated wallets for every trade.\n" + String text = "Bitsquare does not use a single application wallet, but dedicated wallets for every trade.\n" +
"Funding of the wallet will be done when needed, for instance when you create or take an offer.\n" + "Funding of the wallet will be done when needed, for instance when you create or take an offer.\n" +
"Withdrawing funds can be done after a trade is completed.\n" + "Withdrawing funds can be done after a trade is completed.\n" +
"Dedicated wallets help protect user privacy and prevent leaking information of previous trades to other\n" + "Dedicated wallets help protect user privacy and prevent leaking information of previous trades to other" +
"traders.\n\n" + "traders.\n\n" +
"For more background information please see the Bitsquare FAQ on our web page."; "For more background information please see the Bitsquare FAQ on our web page.";
if (preferences.showAgain(key) && !BitsquareApp.DEV_MODE) if (preferences.showAgain(key) && !BitsquareApp.DEV_MODE)

View file

@ -346,7 +346,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawToTextField.setPromptText("Fill in your destination address"); withdrawToTextField.setPromptText("Fill in your destination address");
if (BitsquareApp.DEV_MODE) if (BitsquareApp.DEV_MODE)
withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq"); withdrawToTextField.setText("mhpVDvMjJT1Gn7da44dkq1HXd3wXdFZpXu");
} }
private Optional<Tradable> getTradable(WithdrawalListItem item) { private Optional<Tradable> getTradable(WithdrawalListItem item) {

View file

@ -33,12 +33,12 @@ public class BuyOfferView extends OfferView {
@Override @Override
protected String getCreateOfferTabName() { protected String getCreateOfferTabName() {
return "Create offer for buying bitcoin"; return "Create offer";
} }
@Override @Override
protected String getTakeOfferTabName() { protected String getTakeOfferTabName() {
return "Take offer for buying bitcoin"; return "Take offer";
} }
} }

View file

@ -33,12 +33,12 @@ public class SellOfferView extends OfferView {
@Override @Override
protected String getCreateOfferTabName() { protected String getCreateOfferTabName() {
return "Create offer for selling bitcoin"; return "Create offer";
} }
@Override @Override
protected String getTakeOfferTabName() { protected String getTakeOfferTabName() {
return "Take offer for selling bitcoin"; return "Take offer";
} }
} }

View file

@ -165,11 +165,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
boolean isFeeFromFundingTxSufficient() { boolean isFeeFromFundingTxSufficient() {
// if fee was never set because of api provider not available we check with default value and return true // if fee was never set because of api provider not available we check with default value and return true
log.debug("FeePolicy.getFeePerKb() " + FeePolicy.getFeePerKb());
log.debug("feeFromFundingTxProperty " + feeFromFundingTxProperty);
log.debug(">? " + (feeFromFundingTxProperty.get().compareTo(FeePolicy.getFeePerKb()) >= 0));
return feeFromFundingTxProperty.get().equals(Coin.NEGATIVE_SATOSHI) || return feeFromFundingTxProperty.get().equals(Coin.NEGATIVE_SATOSHI) ||
feeFromFundingTxProperty.get().compareTo(FeePolicy.getFeePerKb()) >= 0; feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinFundingFee()) >= 0;
} }
private void addListeners() { private void addListeners() {
@ -177,7 +174,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
walletService.getWallet().addEventListener(new WalletEventListener() { walletService.getWallet().addEventListener(new WalletEventListener() {
@Override @Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
requestFee(tx.getHashAsString()); requestFeeFromBlockchain(tx.getHashAsString());
} }
@Override @Override
@ -207,7 +204,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener); user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener);
} }
private void requestFee(String transactionId) { private void requestFeeFromBlockchain(String transactionId) {
try { try {
feeFromFundingTxProperty.set(preferences.getBlockchainApiProvider().getFee(transactionId)); feeFromFundingTxProperty.set(preferences.getBlockchainApiProvider().getFee(transactionId));
} catch (IOException | HttpException e) { } catch (IOException | HttpException e) {
@ -216,7 +213,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
retryRequestFeeCounter++; retryRequestFeeCounter++;
log.warn("We try again after 5 seconds"); log.warn("We try again after 5 seconds");
// TODO if we have more providers, try another one // TODO if we have more providers, try another one
UserThread.runAfter(() -> requestFee(transactionId), 5); UserThread.runAfter(() -> requestFeeFromBlockchain(transactionId), 5);
} }
} }
} }
@ -292,11 +289,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
); );
} }
void onSecurityDepositInfoDisplayed() {
preferences.setDisplaySecurityDepositInfo(false);
}
public void onPaymentAccountSelected(PaymentAccount paymentAccount) { public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
if (paymentAccount != null) if (paymentAccount != null)
this.paymentAccount = paymentAccount; this.paymentAccount = paymentAccount;
@ -336,10 +328,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
return addressEntry; return addressEntry;
} }
boolean getDisplaySecurityDepositInfo() {
return preferences.getDisplaySecurityDepositInfo();
}
public TradeCurrency getTradeCurrency() { public TradeCurrency getTradeCurrency() {
return tradeCurrency; return tradeCurrency;
} }

View file

@ -229,16 +229,24 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void onShowFundsScreen() { private void onShowFundsScreen() {
if (!BitsquareApp.DEV_MODE) { if (!BitsquareApp.DEV_MODE) {
if (model.getDisplaySecurityDepositInfo()) { String id = "tradeWalletInfoPopup";
if (model.dataModel.getPreferences().showAgain(id)) {
new Popup().information("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" + new Popup().information("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" +
"The deposit will stay in your local trading wallet until the offer gets accepted by another trader.\n" + "The deposit will stay in your local trading wallet until the offer gets accepted by another trader.\n" +
"It will be refunded to you after the trade has successfully completed.\n\n" + "It will be refunded to you after the trade has successfully completed.\n\n" +
"You need to pay in the exact amount displayed to you from your external bitcoin wallet into the " + "You need to pay in the exact amount displayed to you from your external bitcoin wallet into the " +
"Bitsquare trade wallet. The amount is the sum of the security deposit, the trading fee and " + "Bitsquare trade wallet.\n" +
"The amount is the sum of the security deposit, the trading fee and " +
"the bitcoin mining fee.\n" + "the bitcoin mining fee.\n" +
"You can see the details when you move the mouse over the question mark.").show(); "You can see the details when you move the mouse over the question mark.\n\n" +
"Important notice!\n" +
model.onSecurityDepositInfoDisplayed(); "Please take care that you use a mining fee of at least " +
model.formatter.formatCoinWithCode(FeePolicy.getMinFundingFee()) + " when you transfer bitcoin from your external " +
"wallet to ensure the trade transactions will get into the blockchain.\n" +
"A too low mining fee might result in a delayed trade and will be rejected!")
.closeButtonText("I understand")
.show();
model.dataModel.getPreferences().dontShowAgain(id);
} }
} }
@ -440,7 +448,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
if (!model.dataModel.isFeeFromFundingTxSufficient()) { if (!model.dataModel.isFeeFromFundingTxSufficient()) {
new Popup().warning("The mining fee from your funding transaction is not sufficiently high.\n\n" + new Popup().warning("The mining fee from your funding transaction is not sufficiently high.\n\n" +
"You need to use at least a mining fee of " + "You need to use at least a mining fee of " +
model.formatCoin(FeePolicy.getFeePerKb()) + ".\n\n" + model.formatCoin(FeePolicy.getMinFundingFee()) + ".\n\n" +
"The fee used in your funding transaction was only " + model.formatCoin(newValue) + ".\n\n" + "The fee used in your funding transaction was only " + model.formatCoin(newValue) + ".\n\n" +
"The trade transactions might take too much time to be included in " + "The trade transactions might take too much time to be included in " +
"a block if the fee is too low.\n" + "a block if the fee is too low.\n" +

View file

@ -45,7 +45,7 @@ import static javafx.beans.binding.Bindings.createStringBinding;
class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel { class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel {
private final BtcValidator btcValidator; private final BtcValidator btcValidator;
private final P2PService p2PService; private final P2PService p2PService;
private final BSFormatter formatter; final BSFormatter formatter;
private final FiatValidator fiatValidator; private final FiatValidator fiatValidator;
private String amountDescription; private String amountDescription;
@ -322,10 +322,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
isPlaceOfferButtonVisible.set(true); isPlaceOfferButtonVisible.set(true);
} }
void onSecurityDepositInfoDisplayed() {
dataModel.onSecurityDepositInfoDisplayed();
}
public void onPaymentAccountSelected(PaymentAccount paymentAccount) { public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
dataModel.onPaymentAccountSelected(paymentAccount); dataModel.onPaymentAccountSelected(paymentAccount);
} }
@ -431,10 +427,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
return formatter; return formatter;
} }
boolean getDisplaySecurityDepositInfo() {
return dataModel.getDisplaySecurityDepositInfo();
}
boolean isSellOffer() { boolean isSellOffer() {
return dataModel.getDirection() == Offer.Direction.SELL; return dataModel.getDirection() == Offer.Direction.SELL;
} }

View file

@ -184,10 +184,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
); );
} }
void onSecurityDepositInfoDisplayed() {
preferences.setDisplaySecurityDepositInfo(false);
}
public void onPaymentAccountSelected(PaymentAccount paymentAccount) { public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
if (paymentAccount != null) if (paymentAccount != null)
this.paymentAccount = paymentAccount; this.paymentAccount = paymentAccount;
@ -202,10 +198,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
return offer.getDirection(); return offer.getDirection();
} }
boolean getDisplaySecurityDepositInfo() {
return preferences.getDisplaySecurityDepositInfo();
}
public Offer getOffer() { public Offer getOffer() {
return offer; return offer;
} }

View file

@ -20,6 +20,7 @@ package io.bitsquare.gui.main.offer.takeoffer;
import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon; import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.app.BitsquareApp; import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Tuple3; import io.bitsquare.common.util.Tuple3;
@ -214,7 +215,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
FxTimer.runLater(Duration.ofMillis(100), FxTimer.runLater(Duration.ofMillis(100),
() -> { () -> {
new Popup().information(BSResources.get("takeOffer.success.info")) new Popup().information(BSResources.get("takeOffer.success.info"))
.actionButtonText("Go to \"Open trades\"") .actionButtonText("Go to \"Portfolio/Open trades\"")
.onAction(() -> { .onAction(() -> {
close(); close();
FxTimer.runLater(Duration.ofMillis(100), FxTimer.runLater(Duration.ofMillis(100),
@ -335,17 +336,23 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private void onShowPayFundsScreen() { private void onShowPayFundsScreen() {
if (!BitsquareApp.DEV_MODE) { if (!BitsquareApp.DEV_MODE) {
if (model.getDisplaySecurityDepositInfo()) { String id = "tradeWalletInfoPopup";
MainView.blur(); if (model.dataModel.getPreferences().showAgain(id)) {
new Popup().information("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" + new Popup().information("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" +
"The security deposit will be refunded to you after the trade has successfully completed.\n\n" + "The deposit will be refunded to you after the trade has successfully completed.\n\n" +
"You need to pay in the exact amount displayed to you from your external bitcoin wallet into the " + "You need to pay in the exact amount displayed to you from your external bitcoin wallet into the " +
"Bitsquare trade wallet. In case you over pay, you will get it refunded after the trade.\n\n" + "Bitsquare trade wallet.\n" +
"The amount needed for funding is the sum of the trade amount, the security deposit, " + "The amount is the sum of the trade amount, the security deposit, the trading fee and " +
"the trading fee and the bitcoin mining fee.\n" + "the bitcoin mining fee.\n" +
"You can see the details when you move the mouse over the question mark.").show(); "You can see the details when you move the mouse over the question mark.\n\n" +
"Important notice!\n" +
model.onSecurityDepositInfoDisplayed(); "Please take care that you use a mining fee of at least " +
model.formatter.formatCoinWithCode(FeePolicy.getMinFundingFee()) + " when you transfer bitcoin from your external " +
"wallet to ensure the trade transactions will get into the blockchain.\n" +
"A too low mining fee might result in a delayed trade and will be rejected!")
.closeButtonText("I understand")
.show();
model.dataModel.getPreferences().dontShowAgain(id);
} }
} }

View file

@ -49,7 +49,7 @@ import static javafx.beans.binding.Bindings.createStringBinding;
class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel { class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel {
private final BtcValidator btcValidator; private final BtcValidator btcValidator;
private P2PService p2PService; private P2PService p2PService;
private final BSFormatter formatter; final BSFormatter formatter;
private String amountRange; private String amountRange;
private String addressAsString; private String addressAsString;
@ -194,10 +194,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
dataModel.onPaymentAccountSelected(paymentAccount); dataModel.onPaymentAccountSelected(paymentAccount);
} }
void onSecurityDepositInfoDisplayed() {
dataModel.onSecurityDepositInfoDisplayed();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Handle focus // Handle focus
@ -471,10 +467,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return formatter; return formatter;
} }
boolean getDisplaySecurityDepositInfo() {
return dataModel.getDisplaySecurityDepositInfo();
}
boolean isSeller() { boolean isSeller() {
return dataModel.getDirection() == Offer.Direction.BUY; return dataModel.getDirection() == Offer.Direction.BUY;
} }

View file

@ -61,10 +61,19 @@ public class BuyerSubView extends TradeSubView {
step4 = new TradeWizardItem(BuyerStep4View.class, "Wait for payout unlock"); step4 = new TradeWizardItem(BuyerStep4View.class, "Wait for payout unlock");
step5 = new TradeWizardItem(BuyerStep5View.class, "Completed"); step5 = new TradeWizardItem(BuyerStep5View.class, "Completed");
if (model.getLockTime() > 0) if (model.getLockTime() > 0) {
leftVBox.getChildren().setAll(step1, step2, step3, step4, step5); addWizardsToGridPane(step1);
else addWizardsToGridPane(step2);
leftVBox.getChildren().setAll(step1, step2, step3, step5); addWizardsToGridPane(step3);
addWizardsToGridPane(step4);
addWizardsToGridPane(step5);
} else {
addWizardsToGridPane(step1);
addWizardsToGridPane(step2);
addWizardsToGridPane(step3);
addWizardsToGridPane(step5);
}
} }

View file

@ -24,17 +24,14 @@ import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.FeePolicy; 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.UserThread;
import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableDataModel; import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.disputes.DisputesView; import io.bitsquare.gui.main.disputes.DisputesView;
import io.bitsquare.gui.main.portfolio.PortfolioView;
import io.bitsquare.gui.main.portfolio.closedtrades.ClosedTradesView;
import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.popups.SelectDepositTxPopup; import io.bitsquare.gui.popups.SelectDepositTxPopup;
import io.bitsquare.gui.popups.WalletPasswordPopup; import io.bitsquare.gui.popups.WalletPasswordPopup;
import io.bitsquare.payment.PaymentAccountContractData; import io.bitsquare.payment.PaymentAccountContractData;
@ -167,25 +164,29 @@ public class PendingTradesDataModel extends ActivatableDataModel {
((SellerTrade) trade).onFiatPaymentReceived(); ((SellerTrade) trade).onFiatPaymentReceived();
} }
void onWithdrawRequest(String toAddress) { public void onWithdrawRequest(String toAddress, ResultHandler resultHandler, FaultHandler faultHandler) {
checkNotNull(trade, "trade must not be null"); checkNotNull(trade, "trade must not be null");
if (walletService.getWallet().isEncrypted()) { if (walletService.getWallet().isEncrypted()) {
walletPasswordPopup.onAesKey(aesKey -> doWithdrawRequest(toAddress, aesKey)).show(); walletPasswordPopup.onAesKey(aesKey -> doWithdrawRequest(toAddress, aesKey, resultHandler, faultHandler)).show();
} else } else
doWithdrawRequest(toAddress, null); doWithdrawRequest(toAddress, null, resultHandler, faultHandler);
} }
private void doWithdrawRequest(String toAddress, KeyParameter aesKey) { private void doWithdrawRequest(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, FaultHandler faultHandler) {
if (toAddress != null && toAddress.length() > 0) { if (toAddress != null && toAddress.length() > 0) {
tradeManager.onWithdrawRequest( tradeManager.onWithdrawRequest(
toAddress, toAddress,
aesKey, aesKey,
trade, trade,
() -> UserThread.execute(() -> navigation.navigateTo(MainView.class, PortfolioView.class, ClosedTradesView.class)), () -> {
resultHandler.handleResult();
},
(errorMessage, throwable) -> { (errorMessage, throwable) -> {
log.error(errorMessage); log.error(errorMessage);
new Popup().error("An error occurred:\n" + throwable.getMessage()).show(); faultHandler.handleFault(errorMessage, throwable);
}); });
} else {
faultHandler.handleFault("No receiver address defined", null);
} }
} }

View file

@ -135,6 +135,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
if (dataModel.getTrade() != null) { if (dataModel.getTrade() != null) {
tradeStateSubscription = EasyBind.subscribe(dataModel.getTrade().stateProperty(), newValue -> { tradeStateSubscription = EasyBind.subscribe(dataModel.getTrade().stateProperty(), newValue -> {
log.debug("tradeStateSubscription " + newValue);
if (newValue != null) { if (newValue != null) {
applyState(newValue); applyState(newValue);
} }
@ -191,10 +192,6 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
dataModel.onFiatPaymentReceived(); dataModel.onFiatPaymentReceived();
} }
public void onWithdrawRequest(String withdrawToAddress) {
dataModel.onWithdrawRequest(withdrawToAddress);
}
public void withdrawAddressFocusOut(String text) { public void withdrawAddressFocusOut(String text) {
withdrawalButtonDisable.set(!btcAddressValidator.validate(text).isValid); withdrawalButtonDisable.set(!btcAddressValidator.validate(text).isValid);
} }
@ -324,7 +321,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
} }
public String getOpenDisputeTimeAsFormattedDate() { public String getOpenDisputeTimeAsFormattedDate() {
return formatter.addBlocksToNowDateFormatted(getOpenDisputeTimeAsBlockHeight() - getBestChainHeight()); return formatter.addBlocksToNowDateFormatted(getOpenDisputeTimeAsBlockHeight() - getBestChainHeight() + (getLockTime() - getBestChainHeight()));
} }
public String getReference() { public String getReference() {
@ -448,9 +445,13 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
break; break;
case WITHDRAW_COMPLETED: case WITHDRAW_COMPLETED:
sellerState.set(UNDEFINED);
buyerState.set(PendingTradesViewModel.BuyerState.UNDEFINED);
break; break;
default: default:
sellerState.set(UNDEFINED);
buyerState.set(PendingTradesViewModel.BuyerState.UNDEFINED);
log.warn("unhandled processState " + tradeState); log.warn("unhandled processState " + tradeState);
break; break;
} }

View file

@ -20,6 +20,7 @@ package io.bitsquare.gui.main.portfolio.pendingtrades;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.seller.*; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.seller.*;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.scene.layout.GridPane;
public class SellerSubView extends TradeSubView { public class SellerSubView extends TradeSubView {
private TradeWizardItem step1; private TradeWizardItem step1;
@ -61,10 +62,20 @@ public class SellerSubView extends TradeSubView {
step4 = new TradeWizardItem(SellerStep4aView.class, "Wait for payout unlock"); step4 = new TradeWizardItem(SellerStep4aView.class, "Wait for payout unlock");
step5 = new TradeWizardItem(SellerStep5View.class, "Completed"); step5 = new TradeWizardItem(SellerStep5View.class, "Completed");
if (model.getLockTime() > 0) if (model.getLockTime() > 0) {
leftVBox.getChildren().setAll(step1, step2, step3, step4, step5); addWizardsToGridPane(step1);
else addWizardsToGridPane(step2);
leftVBox.getChildren().setAll(step1, step2, step3, step5); addWizardsToGridPane(step3);
addWizardsToGridPane(step4);
addWizardsToGridPane(step5);
} else {
addWizardsToGridPane(step1);
addWizardsToGridPane(step2);
addWizardsToGridPane(step3);
addWizardsToGridPane(step5);
GridPane.setRowSpan(tradeProcessTitledGroupBg, 4);
}
} }

View file

@ -17,21 +17,18 @@
package io.bitsquare.gui.main.portfolio.pendingtrades; package io.bitsquare.gui.main.portfolio.pendingtrades;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem;
import io.bitsquare.gui.util.Layout; import io.bitsquare.gui.util.Layout;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.FormBuilder.addMultilineLabel; import static io.bitsquare.gui.util.FormBuilder.*;
import static io.bitsquare.gui.util.FormBuilder.addTitledGroupBg;
public abstract class TradeSubView extends HBox { public abstract class TradeSubView extends HBox {
protected final Logger log = LoggerFactory.getLogger(this.getClass()); protected final Logger log = LoggerFactory.getLogger(this.getClass());
@ -41,7 +38,10 @@ public abstract class TradeSubView extends HBox {
protected AnchorPane contentPane; protected AnchorPane contentPane;
protected TradeStepView tradeStepView; protected TradeStepView tradeStepView;
private Button openDisputeButton; private Button openDisputeButton;
private Tuple3<GridPane, TitledGroupBg, Label> notificationTuple; private NotificationGroup notificationGroup;
protected GridPane leftGridPane;
protected TitledGroupBg tradeProcessTitledGroupBg;
protected int leftGridPaneRowIndex = 0;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -63,41 +63,66 @@ public abstract class TradeSubView extends HBox {
tradeStepView.doDeactivate(); tradeStepView.doDeactivate();
if (openDisputeButton != null) if (openDisputeButton != null)
leftVBox.getChildren().remove(openDisputeButton); leftGridPane.getChildren().remove(openDisputeButton);
if (notificationTuple != null) if (notificationGroup != null)
leftVBox.getChildren().remove(notificationTuple.first); notificationGroup.removeItselfFrom(leftGridPane);
} }
private void buildViews() { private void buildViews() {
addLeftBox(); addLeftBox();
addContentPane(); addContentPane();
leftGridPane = new GridPane();
leftGridPane.setPrefWidth(340);
VBox.setMargin(leftGridPane, new Insets(0, 10, 10, 10));
leftGridPane.setHgap(Layout.GRID_GAP);
leftGridPane.setVgap(Layout.GRID_GAP);
leftVBox.getChildren().add(leftGridPane);
leftGridPaneRowIndex = 0;
tradeProcessTitledGroupBg = addTitledGroupBg(leftGridPane, leftGridPaneRowIndex, 1, "Trade process");
addWizards(); addWizards();
openDisputeButton = new Button("Open Dispute"); TitledGroupBg noticeTitledGroupBg = addTitledGroupBg(leftGridPane, leftGridPaneRowIndex, 1, "", Layout.GROUP_DISTANCE);
openDisputeButton.setPrefHeight(40); Label label = addMultilineLabel(leftGridPane, leftGridPaneRowIndex, "", Layout.FIRST_ROW_AND_GROUP_DISTANCE);
openDisputeButton.setPrefWidth(360); openDisputeButton = addButtonAfterGroup(leftGridPane, ++leftGridPaneRowIndex, "Open Dispute");
openDisputeButton.setPadding(new Insets(0, 20, 0, 10)); GridPane.setColumnIndex(openDisputeButton, 0);
openDisputeButton.setAlignment(Pos.CENTER);
openDisputeButton.setDefaultButton(true);
openDisputeButton.setId("open-dispute-button"); openDisputeButton.setId("open-dispute-button");
openDisputeButton.setVisible(false);
openDisputeButton.setManaged(false);
leftVBox.getChildren().add(openDisputeButton);
VBox.setMargin(openDisputeButton, new Insets(10, 0, 0, 0));
// notification fields notificationGroup = new NotificationGroup(noticeTitledGroupBg, label, openDisputeButton);
GridPane gridPane = new GridPane(); notificationGroup.setLabelAndHeadlineVisible(false);
gridPane.setPrefWidth(340); notificationGroup.setButtonVisible(false);
VBox.setMargin(gridPane, new Insets(10, 10, 10, 10)); }
gridPane.setHgap(Layout.GRID_GAP);
gridPane.setVgap(Layout.GRID_GAP);
gridPane.setVisible(false);
gridPane.setManaged(false);
leftVBox.getChildren().add(gridPane);
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, 0, 4, "Important notice", 20); public static class NotificationGroup {
Label label = addMultilineLabel(gridPane, 0, Layout.FIRST_ROW_DISTANCE + 20); public final TitledGroupBg titledGroupBg;
notificationTuple = new Tuple3<>(gridPane, titledGroupBg, label); public final Label label;
public final Button button;
public NotificationGroup(TitledGroupBg titledGroupBg, Label label, Button button) {
this.titledGroupBg = titledGroupBg;
this.label = label;
this.button = button;
}
public void setLabelAndHeadlineVisible(boolean isVisible) {
titledGroupBg.setVisible(isVisible);
label.setVisible(isVisible);
titledGroupBg.setManaged(isVisible);
label.setManaged(isVisible);
}
public void setButtonVisible(boolean isVisible) {
button.setVisible(isVisible);
button.setManaged(isVisible);
}
public void removeItselfFrom(GridPane leftGridPane) {
leftGridPane.getChildren().remove(titledGroupBg);
leftGridPane.getChildren().remove(label);
leftGridPane.getChildren().remove(button);
}
} }
protected void showItem(TradeWizardItem item) { protected void showItem(TradeWizardItem item) {
@ -107,13 +132,22 @@ public abstract class TradeSubView extends HBox {
abstract protected void addWizards(); abstract protected void addWizards();
protected void addWizardsToGridPane(TradeWizardItem tradeWizardItem) {
if (leftGridPaneRowIndex == 0)
GridPane.setMargin(tradeWizardItem, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0));
GridPane.setRowIndex(tradeWizardItem, leftGridPaneRowIndex++);
leftGridPane.getChildren().add(tradeWizardItem);
GridPane.setRowSpan(tradeProcessTitledGroupBg, leftGridPaneRowIndex);
GridPane.setFillWidth(tradeWizardItem, true);
}
private void createAndAddTradeStepView(Class<? extends TradeStepView> viewClass) { private void createAndAddTradeStepView(Class<? extends TradeStepView> viewClass) {
try { try {
tradeStepView = viewClass.getDeclaredConstructor(PendingTradesViewModel.class).newInstance(model); tradeStepView = viewClass.getDeclaredConstructor(PendingTradesViewModel.class).newInstance(model);
contentPane.getChildren().setAll(tradeStepView); contentPane.getChildren().setAll(tradeStepView);
tradeStepView.setNotificationFields(notificationTuple); tradeStepView.setNotificationGroup(notificationGroup);
tradeStepView.setOpenDisputeButton(openDisputeButton);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -18,18 +18,16 @@
package io.bitsquare.gui.main.portfolio.pendingtrades.steps; package io.bitsquare.gui.main.portfolio.pendingtrades.steps;
import io.bitsquare.arbitration.Dispute; import io.bitsquare.arbitration.Dispute;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.components.TxIdTextField; import io.bitsquare.gui.components.TxIdTextField;
import io.bitsquare.gui.components.paymentmethods.PaymentMethodForm; import io.bitsquare.gui.components.paymentmethods.PaymentMethodForm;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel; import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel;
import io.bitsquare.gui.main.portfolio.pendingtrades.TradeSubView;
import io.bitsquare.gui.popups.Popup; import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.util.Layout; import io.bitsquare.gui.util.Layout;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar; import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
@ -63,11 +61,8 @@ public abstract class TradeStepView extends AnchorPane {
protected TitledGroupBg tradeInfoTitledGroupBg; protected TitledGroupBg tradeInfoTitledGroupBg;
private TextField timeLeftTextField; private TextField timeLeftTextField;
private ProgressBar timeLeftProgressBar; private ProgressBar timeLeftProgressBar;
private GridPane notificationGridPane;
private Label notificationLabel;
private TitledGroupBg notificationTitledGroupBg;
protected Button openDisputeButton;
private TxIdTextField txIdTextField; private TxIdTextField txIdTextField;
protected TradeSubView.NotificationGroup notificationGroup;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -135,8 +130,8 @@ public abstract class TradeStepView extends AnchorPane {
if (tradePeriodStateSubscription != null) if (tradePeriodStateSubscription != null)
tradePeriodStateSubscription.unsubscribe(); tradePeriodStateSubscription.unsubscribe();
if (openDisputeButton != null) if (notificationGroup != null)
openDisputeButton.setOnAction(null); notificationGroup.button.setOnAction(null);
if (timer != null) if (timer != null)
timer.stop(); timer.stop();
@ -212,63 +207,68 @@ public abstract class TradeStepView extends AnchorPane {
// We have the dispute button and text field on the left side, but we handle the content here as it // We have the dispute button and text field on the left side, but we handle the content here as it
// is trade state specific // is trade state specific
public void setNotificationFields(Tuple3<GridPane, TitledGroupBg, Label> notificationTuple) { public void setNotificationGroup(TradeSubView.NotificationGroup notificationGroup) {
this.notificationGridPane = notificationTuple.first; this.notificationGroup = notificationGroup;
this.notificationTitledGroupBg = notificationTuple.second;
this.notificationLabel = notificationTuple.third;
}
public void setOpenDisputeButton(Button openDisputeButton) {
this.openDisputeButton = openDisputeButton;
} }
private void showDisputeInfoLabel() { private void showDisputeInfoLabel() {
if (notificationGridPane != null) { if (notificationGroup != null) {
notificationGridPane.setVisible(true); notificationGroup.setLabelAndHeadlineVisible(true);
notificationGridPane.setManaged(true);
} }
} }
private void showOpenDisputeButton() { private void showOpenDisputeButton() {
if (openDisputeButton != null) { if (notificationGroup != null) {
openDisputeButton.setVisible(true); notificationGroup.setButtonVisible(true);
openDisputeButton.setManaged(true); notificationGroup.button.setOnAction(e -> {
openDisputeButton.setOnAction(e -> { notificationGroup.button.setDisable(true);
openDisputeButton.setDisable(true);
onDisputeOpened(); onDisputeOpened();
setDisputeState();
model.dataModel.onOpenDispute(); model.dataModel.onOpenDispute();
}); });
} }
} }
protected void setWarningState() { protected void setWarningHeadline() {
if (notificationGridPane != null) { if (notificationGroup != null) {
notificationTitledGroupBg.setText("Warning"); notificationGroup.titledGroupBg.setText("Warning");
//notificationGridPane.setId("trade-notification-warning"); //notificationGroup.setId("trade-notification-warning");
} }
} }
protected void setInformationState() { protected void setInformationHeadline() {
if (notificationGridPane != null) { if (notificationGroup != null) {
notificationTitledGroupBg.setText("Notification"); notificationGroup.titledGroupBg.setText("Notification");
notificationTitledGroupBg.setId("titled-group-bg-warn"); //notificationGroup.titledGroupBg.setId("titled-group-bg-warn");
notificationTitledGroupBg.getLabel().setId("titled-group-bg-label-warn"); //notificationGroup.label.setId("titled-group-bg-label-warn");
//notificationLabel.setId("titled-group-bg-label-warn"); //notificationLabel.setId("titled-group-bg-label-warn");
} }
} }
protected void setDisputeState() { protected void setOpenDisputeHeadline() {
if (notificationGridPane != null) { if (notificationGroup != null) {
notificationTitledGroupBg.setText("Dispute opened"); notificationGroup.titledGroupBg.setText("Open a dispute");
//notificationGridPane.setId("trade-notification-dispute"); //notificationGroup.setId("trade-notification-dispute");
} }
} }
protected void setSupportState() { protected void setDisputeOpenedHeadline() {
if (notificationGridPane != null) { if (notificationGroup != null) {
notificationTitledGroupBg.setText("Support ticket opened"); notificationGroup.titledGroupBg.setText("Dispute opened");
//notificationGridPane.setId("trade-notification-support"); //notificationGroup.setId("trade-notification-dispute");
}
}
protected void setRequestSupportHeadline() {
if (notificationGroup != null) {
notificationGroup.titledGroupBg.setText("Open support ticket");
//notificationGroup.setId("trade-notification-support");
}
}
protected void setSupportOpenedHeadline() {
if (notificationGroup != null) {
notificationGroup.titledGroupBg.setText("Support ticket opened");
//notificationGroup.setId("trade-notification-support");
} }
} }
@ -277,10 +277,10 @@ public abstract class TradeStepView extends AnchorPane {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void showSupportFields() { private void showSupportFields() {
if (openDisputeButton != null) { if (notificationGroup != null) {
openDisputeButton.setText("Request support"); notificationGroup.button.setText("Request support");
openDisputeButton.setId("open-support-button"); notificationGroup.button.setId("open-support-button");
openDisputeButton.setOnAction(e -> model.dataModel.onOpenSupportTicket()); notificationGroup.button.setOnAction(e -> model.dataModel.onOpenSupportTicket());
} }
new Popup().warning(trade.errorMessageProperty().getValue() new Popup().warning(trade.errorMessageProperty().getValue()
+ "\n\nPlease report the problem to your arbitrator.\n\n" + + "\n\nPlease report the problem to your arbitrator.\n\n" +
@ -299,8 +299,8 @@ public abstract class TradeStepView extends AnchorPane {
private void showWarning() { private void showWarning() {
showDisputeInfoLabel(); showDisputeInfoLabel();
if (notificationLabel != null) if (notificationGroup != null)
notificationLabel.setText(getWarningText()); notificationGroup.label.setText(getWarningText());
} }
protected String getWarningText() { protected String getWarningText() {
@ -315,19 +315,20 @@ public abstract class TradeStepView extends AnchorPane {
private void onOpenForDispute() { private void onOpenForDispute() {
showDisputeInfoLabel(); showDisputeInfoLabel();
showOpenDisputeButton(); showOpenDisputeButton();
setOpenDisputeHeadline();
if (notificationLabel != null) if (notificationGroup != null)
notificationLabel.setText(getOpenForDisputeText()); notificationGroup.label.setText(getOpenForDisputeText());
} }
private void onDisputeOpened() { private void onDisputeOpened() {
showDisputeInfoLabel(); showDisputeInfoLabel();
showOpenDisputeButton(); showOpenDisputeButton();
applyOnDisputeOpened(); applyOnDisputeOpened();
setDisputeOpenedHeadline();
if (notificationGroup != null)
if (openDisputeButton != null) notificationGroup.button.setDisable(true);
openDisputeButton.setDisable(true);
} }
protected String getOpenForDisputeText() { protected String getOpenForDisputeText() {
@ -337,6 +338,11 @@ public abstract class TradeStepView extends AnchorPane {
protected void applyOnDisputeOpened() { protected void applyOnDisputeOpened() {
} }
protected void hideNotificationGroup() {
notificationGroup.setLabelAndHeadlineVisible(false);
notificationGroup.setButtonVisible(false);
}
private void updateDisputeState(Trade.DisputeState disputeState) { private void updateDisputeState(Trade.DisputeState disputeState) {
Optional<Dispute> ownDispute; Optional<Dispute> ownDispute;
switch (disputeState) { switch (disputeState) {
@ -348,16 +354,16 @@ public abstract class TradeStepView extends AnchorPane {
ownDispute.ifPresent(dispute -> { ownDispute.ifPresent(dispute -> {
String msg; String msg;
if (dispute.isSupportTicket()) { if (dispute.isSupportTicket()) {
setSupportState(); setSupportOpenedHeadline();
msg = "You opened already a support ticket.\n" + msg = "You opened already a support ticket.\n" +
"Please communicate in the support section with the arbitrator."; "Please communicate in the support section with the arbitrator.";
} else { } else {
setDisputeState(); setDisputeOpenedHeadline();
msg = "You opened already a dispute.\n" + msg = "You opened already a dispute.\n" +
"Please communicate in the support section with the arbitrator."; "Please communicate in the support section with the arbitrator.";
} }
if (notificationLabel != null) if (notificationGroup != null)
notificationLabel.setText(msg); notificationGroup.label.setText(msg);
}); });
break; break;
@ -367,16 +373,16 @@ public abstract class TradeStepView extends AnchorPane {
ownDispute.ifPresent(dispute -> { ownDispute.ifPresent(dispute -> {
String msg; String msg;
if (dispute.isSupportTicket()) { if (dispute.isSupportTicket()) {
setSupportState(); setSupportOpenedHeadline();
msg = "Your trading peer opened a support ticket due technical problems.\n" + msg = "Your trading peer opened a support ticket due technical problems.\n" +
"Please communicate in the support section with the arbitrator."; "Please communicate in the support section with the arbitrator.";
} else { } else {
setDisputeState(); setDisputeOpenedHeadline();
msg = "Your trading peer opened a dispute.\n" + msg = "Your trading peer opened a dispute.\n" +
"Please communicate in the support section with the arbitrator."; "Please communicate in the support section with the arbitrator.";
} }
if (notificationLabel != null) if (notificationGroup != null)
notificationLabel.setText(msg); notificationGroup.label.setText(msg);
}); });
break; break;
case DISPUTE_CLOSED: case DISPUTE_CLOSED:

View file

@ -39,7 +39,6 @@ public class TradeWizardItem extends Button {
setText(title); setText(title);
setPrefHeight(40); setPrefHeight(40);
setPrefWidth(360); setPrefWidth(360);
setPadding(new Insets(0, 20, 0, 10));
setAlignment(Pos.CENTER_LEFT); setAlignment(Pos.CENTER_LEFT);
setDisabled(); setDisabled();
} }

View file

@ -56,7 +56,7 @@ public class BuyerStep2View extends TradeStepView {
public void doActivate() { public void doActivate() {
super.doActivate(); super.doActivate();
String id = PopupId.SEND_PAYMENT_INFO; /* String id = PopupId.SEND_PAYMENT_INFO;
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) { if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) {
//TODO use payment method and trade values //TODO use payment method and trade values
new Popup().information("You need to transfer now the agreed amount to your trading partner.\n" + new Popup().information("You need to transfer now the agreed amount to your trading partner.\n" +
@ -65,7 +65,7 @@ public class BuyerStep2View extends TradeStepView {
"Make sure that you make the transfer soon to not exceed the trading period.") "Make sure that you make the transfer soon to not exceed the trading period.")
.onClose(() -> preferences.dontShowAgain(id)) .onClose(() -> preferences.dontShowAgain(id))
.show(); .show();
} }*/
} }
@Override @Override
@ -135,9 +135,9 @@ public class BuyerStep2View extends TradeStepView {
@Override @Override
protected String getWarningText() { protected String getWarningText() {
setWarningState(); setWarningHeadline();
return "You still have not done your " + model.getCurrencyCode() + " payment!\n" + return "You still have not done your " + model.getCurrencyCode() + " payment!\n" +
"Please not that the trade has to be completed until " + "Please note that the trade has to be completed until " +
model.getOpenDisputeTimeAsFormattedDate() + model.getOpenDisputeTimeAsFormattedDate() +
" otherwise the trade will be investigated by the arbitrator."; " otherwise the trade will be investigated by the arbitrator.";
} }
@ -150,9 +150,8 @@ public class BuyerStep2View extends TradeStepView {
@Override @Override
protected String getOpenForDisputeText() { protected String getOpenForDisputeText() {
return "You have not completed your payment!\n" + return "You have not completed your payment!\n" +
"The max. period for the trade has elapsed (" + "The max. period for the trade has elapsed.\n" +
model.getOpenDisputeTimeAsFormattedDate() + ")." + "\nPlease contact the arbitrator for opening a dispute.";
"\nPlease contact now the arbitrator for opening a dispute.";
} }
@Override @Override
@ -205,10 +204,10 @@ public class BuyerStep2View extends TradeStepView {
// In case the first send failed we got the support button displayed. // In case the first send failed we got the support button displayed.
// If it succeeds at a second try we remove the support button again. // If it succeeds at a second try we remove the support button again.
if (openDisputeButton != null) { //TODO check for support. in case of a dispute we dont want to hid ethe button
openDisputeButton.setVisible(false); /*if (notificationGroup != null) {
openDisputeButton.setManaged(false); notificationGroup.setButtonVisible(false);
} }*/
}, errorMessage -> { }, errorMessage -> {
removeStatusProgressIndicator(); removeStatusProgressIndicator();
statusLabel.setText("Sending message to your trading partner failed.\n" + statusLabel.setText("Sending message to your trading partner failed.\n" +

View file

@ -55,7 +55,7 @@ public class BuyerStep3View extends TradeStepView {
@Override @Override
protected String getWarningText() { protected String getWarningText() {
setInformationState(); setInformationHeadline();
String substitute = model.isBlockChainMethod() ? String substitute = model.isBlockChainMethod() ?
"on the " + model.getCurrencyCode() + "blockchain" : "on the " + model.getCurrencyCode() + "blockchain" :
"at your payment provider (e.g. bank)"; "at your payment provider (e.g. bank)";
@ -74,9 +74,8 @@ public class BuyerStep3View extends TradeStepView {
@Override @Override
protected String getOpenForDisputeText() { protected String getOpenForDisputeText() {
return "The seller has not confirmed your payment!\n" + return "The seller has not confirmed your payment!\n" +
"The max. period for the trade has elapsed (" + "The max. period for the trade has elapsed.\n" +
model.getOpenDisputeTimeAsFormattedDate() + "Please contact the arbitrator for opening a dispute.";
") and you need to contact now the arbitrator to investigate the problem.";
} }
@Override @Override

View file

@ -19,7 +19,6 @@ package io.bitsquare.gui.main.portfolio.pendingtrades.steps.buyer;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel; import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView;
import io.bitsquare.gui.util.Layout;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import org.bitcoinj.core.*; import org.bitcoinj.core.*;
@ -76,6 +75,7 @@ public class BuyerStep4View extends TradeStepView {
super.doActivate(); super.doActivate();
model.addBlockChainListener(blockChainListener); model.addBlockChainListener(blockChainListener);
updateDateFromBlockHeight(model.getBestChainHeight());
} }
@Override @Override
@ -93,9 +93,9 @@ public class BuyerStep4View extends TradeStepView {
@Override @Override
protected void addContent() { protected void addContent() {
addTradeInfoBlock(); addTradeInfoBlock();
blockTextField = addLabelTextField(gridPane, gridRow, "Block(s) to wait until lock time elapsed:", "", Layout.FIRST_ROW_DISTANCE).second; blockTextField = addLabelTextField(gridPane, gridRow, "Block(s) to wait until lock time elapsed:", "").second;
timeTextField = addLabelTextField(gridPane, ++gridRow, "Approx. date when payout gets unlocked:").second; timeTextField = addLabelTextField(gridPane, ++gridRow, "Approx. date when payout gets unlocked:").second;
GridPane.setRowSpan(tradeInfoTitledGroupBg, 4); GridPane.setRowSpan(tradeInfoTitledGroupBg, 5);
addInfoBlock(); addInfoBlock();
} }
@ -125,7 +125,7 @@ public class BuyerStep4View extends TradeStepView {
@Override @Override
protected String getWarningText() { protected String getWarningText() {
setInformationState(); setInformationHeadline();
return "The payout transaction is still blocked by the lock time!\n" + return "The payout transaction is still blocked by the lock time!\n" +
"If the trade has not been completed on " + "If the trade has not been completed on " +
model.getOpenDisputeTimeAsFormattedDate() + model.getOpenDisputeTimeAsFormattedDate() +

View file

@ -23,6 +23,7 @@ import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.components.InputTextField; import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel; import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView; import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView;
import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.util.Layout; import io.bitsquare.gui.util.Layout;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.scene.control.Button; import javafx.scene.control.Button;
@ -77,6 +78,8 @@ public class BuyerStep5View extends TradeStepView {
UserThread.execute(() -> withdrawAddressTextField.requestFocus()); UserThread.execute(() -> withdrawAddressTextField.requestFocus());
});*/ });*/
}); });
hideNotificationGroup();
} }
@Override @Override
@ -113,22 +116,31 @@ public class BuyerStep5View extends TradeStepView {
withdrawAddressTextField = addLabelInputTextField(gridPane, ++gridRow, "Withdraw to address:").second; withdrawAddressTextField = addLabelInputTextField(gridPane, ++gridRow, "Withdraw to address:").second;
withdrawButton = addButtonAfterGroup(gridPane, ++gridRow, "Withdraw to external wallet"); withdrawButton = addButtonAfterGroup(gridPane, ++gridRow, "Withdraw to external wallet");
withdrawButton.setOnAction(e -> { withdrawButton.setOnAction(e -> {
model.onWithdrawRequest(withdrawAddressTextField.getText());
withdrawButton.setDisable(true); withdrawButton.setDisable(true);
model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(),
() -> {
String id = "TradeCompletedInfoPopup";
if (preferences.showAgain(id)) {
new Popup()
.information("You can review your completed trades under \"Portfolio/History\" or " +
"review your transactions under \"Funds/Transactions\"")
.dontShowAgainId(id, preferences)
.show();
}
withdrawButton.setDisable(true);
},
(errorMessage, throwable) -> {
withdrawButton.setDisable(false);
if (throwable == null)
new Popup().warning(errorMessage).show();
else
new Popup().error("An error occurred:\n" + throwable.getMessage()).show();
});
}); });
if (BitsquareApp.DEV_MODE) if (BitsquareApp.DEV_MODE)
withdrawAddressTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq"); withdrawAddressTextField.setText("mhpVDvMjJT1Gn7da44dkq1HXd3wXdFZpXu");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Warning
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected String getWarningText() {
setInformationState();
return "The trade took a bit longer as expected but has been completed successfully in the allowed trade period.";
} }

View file

@ -56,8 +56,8 @@ public class SellerStep2View extends TradeStepView {
@Override @Override
protected String getWarningText() { protected String getWarningText() {
setInformationState(); setInformationHeadline();
return "The buyer still has not done the " + model.getCurrencyCode() + " payment!\n" + return "The buyer still has not done the " + model.getCurrencyCode() + " payment.\n" +
"You need to wait until he starts the payment.\n" + "You need to wait until he starts the payment.\n" +
"If the trade has not been completed on " + "If the trade has not been completed on " +
model.getOpenDisputeTimeAsFormattedDate() + model.getOpenDisputeTimeAsFormattedDate() +

View file

@ -122,7 +122,7 @@ public class SellerStep3View extends TradeStepView {
@Override @Override
protected String getWarningText() { protected String getWarningText() {
setWarningState(); setWarningHeadline();
String substitute = model.isBlockChainMethod() ? String substitute = model.isBlockChainMethod() ?
"on the " + model.getCurrencyCode() + "blockchain" : "on the " + model.getCurrencyCode() + "blockchain" :
"at your payment provider (e.g. bank)"; "at your payment provider (e.g. bank)";
@ -141,9 +141,8 @@ public class SellerStep3View extends TradeStepView {
@Override @Override
protected String getOpenForDisputeText() { protected String getOpenForDisputeText() {
return "You have not confirmed the receipt of the payment!\n" + return "You have not confirmed the receipt of the payment!\n" +
"The max. period for the trade has elapsed (" + "The max. period for the trade has elapsed.\n" +
model.getOpenDisputeTimeAsFormattedDate() + ")." + "Please contact the arbitrator for opening a dispute.";
"\nPlease contact now the arbitrator for opening a dispute.";
} }
@Override @Override

View file

@ -54,7 +54,7 @@ public class SellerStep4bView extends TradeStepView {
@Override @Override
protected String getWarningText() { protected String getWarningText() {
setInformationState(); setInformationHeadline();
return "The trading peer has not finalized the payout transaction!\n" + return "The trading peer has not finalized the payout transaction!\n" +
"He might be offline. You need to wait until he finalizes the payout transaction.\n" + "He might be offline. You need to wait until he finalizes the payout transaction.\n" +
"If the trade has not been completed on " + "If the trade has not been completed on " +
@ -70,9 +70,8 @@ public class SellerStep4bView extends TradeStepView {
@Override @Override
protected String getOpenForDisputeText() { protected String getOpenForDisputeText() {
return "The trading peer has not finalized the payout transaction!\n" + return "The trading peer has not finalized the payout transaction!\n" +
"The max. period for the trade has elapsed (" + "The max. period for the trade has elapsed.\n" +
model.getOpenDisputeTimeAsFormattedDate() + ").\n" + "Please contact the arbitrator for opening a dispute.";
"Please contact now the arbitrator for opening a dispute.";
} }
@Override @Override

View file

@ -17,6 +17,7 @@
package io.bitsquare.gui.popups; package io.bitsquare.gui.popups;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
@ -52,6 +53,7 @@ public class OfferDetailsPopup extends Popup {
private final BSFormatter formatter; private final BSFormatter formatter;
private final Preferences preferences; private final Preferences preferences;
private final User user; private final User user;
private KeyRing keyRing;
private final Navigation navigation; private final Navigation navigation;
private Offer offer; private Offer offer;
private Coin tradeAmount; private Coin tradeAmount;
@ -64,10 +66,11 @@ public class OfferDetailsPopup extends Popup {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public OfferDetailsPopup(BSFormatter formatter, Preferences preferences, User user, Navigation navigation) { public OfferDetailsPopup(BSFormatter formatter, Preferences preferences, User user, KeyRing keyRing, Navigation navigation) {
this.formatter = formatter; this.formatter = formatter;
this.preferences = preferences; this.preferences = preferences;
this.user = user; this.user = user;
this.keyRing = keyRing;
this.navigation = navigation; this.navigation = navigation;
} }
@ -139,6 +142,9 @@ public class OfferDetailsPopup extends Popup {
addLabelTextField(gridPane, ++rowIndex, "Amount:", formatter.formatCoinWithCode(offer.getAmount())); addLabelTextField(gridPane, ++rowIndex, "Amount:", formatter.formatCoinWithCode(offer.getAmount()));
addLabelTextField(gridPane, ++rowIndex, "Min. amount:", formatter.formatCoinWithCode(offer.getMinAmount())); addLabelTextField(gridPane, ++rowIndex, "Min. amount:", formatter.formatCoinWithCode(offer.getMinAmount()));
} }
if (offer.isMyOffer(keyRing) && user.getPaymentAccount(offer.getOffererPaymentAccountId()) != null)
addLabelTextField(gridPane, ++rowIndex, "Payment account:", user.getPaymentAccount(offer.getOffererPaymentAccountId()).getAccountName());
else
addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId())); addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId()));
rows = 3; rows = 3;

View file

@ -118,6 +118,14 @@ public class Popup {
return this; return this;
} }
public Popup notification(String message) {
// TODO use icons
this.headLine = "Notification";
this.message = message;
setTruncatedMessage();
return this;
}
public Popup information(String message) { public Popup information(String message) {
this.headLine = "Information"; this.headLine = "Information";
this.message = message; this.message = message;
@ -219,7 +227,7 @@ public class Popup {
scene.getStylesheets().setAll(owner.getScene().getStylesheets()); scene.getStylesheets().setAll(owner.getScene().getStylesheets());
scene.setFill(Color.TRANSPARENT); scene.setFill(Color.TRANSPARENT);
stage.setScene(scene); stage.setScene(scene);
stage.initModality(Modality.APPLICATION_MODAL); stage.initModality(Modality.WINDOW_MODAL);
stage.initStyle(StageStyle.TRANSPARENT); stage.initStyle(StageStyle.TRANSPARENT);
stage.initOwner(owner.getScene().getWindow()); stage.initOwner(owner.getScene().getWindow());
stage.show(); stage.show();
@ -317,10 +325,11 @@ public class Popup {
private void addDontShowAgainCheckBox() { private void addDontShowAgainCheckBox() {
if (dontShowAgainId != null && preferences != null) { if (dontShowAgainId != null && preferences != null) {
CheckBox dontShowAgain = addCheckBox(gridPane, ++rowIndex, "Don't show again", 10); CheckBox dontShowAgainCheckBox = addCheckBox(gridPane, rowIndex, "Don't show again", 30);
GridPane.setHalignment(dontShowAgain, HPos.RIGHT); GridPane.setColumnIndex(dontShowAgainCheckBox, 0);
dontShowAgain.setOnAction(e -> { GridPane.setHalignment(dontShowAgainCheckBox, HPos.LEFT);
if (dontShowAgain.isSelected()) dontShowAgainCheckBox.setOnAction(e -> {
if (dontShowAgainCheckBox.isSelected())
preferences.dontShowAgain(dontShowAgainId); preferences.dontShowAgain(dontShowAgainId);
}); });
} }
@ -352,7 +361,7 @@ public class Popup {
GridPane.setHalignment(hBox, HPos.RIGHT); GridPane.setHalignment(hBox, HPos.RIGHT);
GridPane.setRowIndex(hBox, ++rowIndex); GridPane.setRowIndex(hBox, ++rowIndex);
GridPane.setColumnSpan(hBox, 2); GridPane.setColumnSpan(hBox, 2);
GridPane.setMargin(hBox, new Insets(20, 0, 0, 0)); GridPane.setMargin(hBox, new Insets(30, 0, 0, 0));
gridPane.getChildren().add(hBox); gridPane.getChildren().add(hBox);
} else { } else {
closeButton.setDefaultButton(true); closeButton.setDefaultButton(true);
@ -366,8 +375,8 @@ public class Popup {
} }
protected void setTruncatedMessage() { protected void setTruncatedMessage() {
if (message != null && message.length() > 800) if (message != null && message.length() > 900)
truncatedMessage = StringUtils.abbreviate(message, 800); truncatedMessage = StringUtils.abbreviate(message, 900);
else else
truncatedMessage = message; truncatedMessage = message;
} }

View file

@ -577,23 +577,20 @@ public class FormBuilder {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public static Button addButton(GridPane gridPane, int rowIndex, String title) { public static Button addButton(GridPane gridPane, int rowIndex, String title) {
Button button = new Button(title); return addButton(gridPane, rowIndex, title, 0);
button.setDefaultButton(true);
GridPane.setRowIndex(button, rowIndex);
GridPane.setColumnIndex(button, 1);
gridPane.getChildren().add(button);
return button;
} }
public static Button addButtonAfterGroup(GridPane gridPane, public static Button addButtonAfterGroup(GridPane gridPane, int rowIndex, String title) {
int rowIndex, return addButton(gridPane, rowIndex, title, 15);
String title) { }
public static Button addButton(GridPane gridPane, int rowIndex, String title, double top) {
Button button = new Button(title); Button button = new Button(title);
button.setDefaultButton(true); button.setDefaultButton(true);
GridPane.setRowIndex(button, rowIndex); GridPane.setRowIndex(button, rowIndex);
GridPane.setColumnIndex(button, 1); GridPane.setColumnIndex(button, 1);
GridPane.setMargin(button, new Insets(15, 0, 0, 0));
gridPane.getChildren().add(button); gridPane.getChildren().add(button);
GridPane.setMargin(button, new Insets(top, 0, 0, 0));
return button; return button;
} }