Savings wallet (WIP)

This commit is contained in:
Manfred Karrer 2016-03-30 02:46:02 +02:00
parent 9ac0740e33
commit 37b31a5d0a
82 changed files with 1238 additions and 740 deletions

View file

@ -60,7 +60,7 @@ public class Log {
appender.start(); appender.start();
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.WARN); logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.TRACE);
logbackLogger.addAppender(appender); logbackLogger.addAppender(appender);
// log errors in separate file // log errors in separate file

View file

@ -62,7 +62,7 @@ public final class PubKeyRing implements Payload {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -52,8 +52,7 @@ public final class SealedAndSigned implements Payload {
in.defaultReadObject(); in.defaultReadObject();
sigPublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(sigPublicKeyBytes)); sigPublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(sigPublicKeyBytes));
} catch (Throwable t) { } catch (Throwable t) {
log.error("Exception at readObject: " + t.getMessage()); log.warn("Exception at readObject: " + t.getMessage());
t.printStackTrace();
} }
} }

View file

@ -53,8 +53,7 @@ public final class Alert implements StoragePayload {
in.defaultReadObject(); in.defaultReadObject();
storagePublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(storagePublicKeyBytes)); storagePublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(storagePublicKeyBytes));
} catch (Throwable t) { } catch (Throwable t) {
log.error("Exception at readObject: " + t.getMessage()); log.warn("Exception at readObject: " + t.getMessage());
t.printStackTrace();
} }
} }

View file

@ -134,7 +134,7 @@ public final class Dispute implements Payload {
disputeResultProperty = new SimpleObjectProperty<>(disputeResult); disputeResultProperty = new SimpleObjectProperty<>(disputeResult);
isClosedProperty = new SimpleBooleanProperty(isClosed); isClosedProperty = new SimpleBooleanProperty(isClosed);
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -53,7 +53,7 @@ public final class DisputeList<DisputeCase> extends ArrayList<DisputeCase> imple
try { try {
in.defaultReadObject(); in.defaultReadObject();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -84,7 +84,7 @@ public final class DisputeResult implements Payload {
in.defaultReadObject(); in.defaultReadObject();
init(); init();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -66,7 +66,7 @@ public final class DisputeCommunicationMessage extends DisputeMessage {
arrivedProperty = new SimpleBooleanProperty(arrived); arrivedProperty = new SimpleBooleanProperty(arrived);
storedInMailboxProperty = new SimpleBooleanProperty(storedInMailbox); storedInMailboxProperty = new SimpleBooleanProperty(storedInMailbox);
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -46,7 +46,11 @@ class AddressBasedCoinSelector implements CoinSelector {
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public AddressBasedCoinSelector(NetworkParameters params, AddressEntry addressEntry) { public AddressBasedCoinSelector(NetworkParameters params) {
this.params = params;
}
public AddressBasedCoinSelector(NetworkParameters params, @Nullable AddressEntry addressEntry) {
this.params = params; this.params = params;
this.addressEntry = addressEntry; this.addressEntry = addressEntry;
} }
@ -119,6 +123,9 @@ class AddressBasedCoinSelector implements CoinSelector {
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString
() + " / " + addressEntries.toString()); () + " / " + addressEntries.toString());
} else {
// use savings wallet
return true;
} }
} }
return false; return false;

View file

@ -101,7 +101,7 @@ public final class AddressEntry implements Persistable {
params = RegTestParams.get(); params = RegTestParams.get();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -27,6 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Optional;
/** /**
* The List supporting our persistence solution. * The List supporting our persistence solution.
@ -82,6 +83,17 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
} }
public void swapTradeToSavings(String offerId) {
Optional<AddressEntry> addressEntryOptional = this.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId())).findAny();
if (addressEntryOptional.isPresent()) {
AddressEntry addressEntry = addressEntryOptional.get();
add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.SAVINGS));
remove(addressEntry);
storage.queueUpForSave();
}
}
public AddressEntry getArbitratorAddressEntry() { public AddressEntry getArbitratorAddressEntry() {
if (size() > 0) if (size() > 0)
return get(0); return get(0);

View file

@ -134,19 +134,22 @@ public class TradeWalletService {
/** /**
* @param addressEntry From where we want to spend the transaction fee. Used also as change address. * @param addressEntry From where we want to spend the transaction fee. Used also as change address.
* @param useSavingsWallet
* @param tradingFee The amount of the trading fee. * @param tradingFee The amount of the trading fee.
* @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). * @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). @return The broadcasted transaction
* @return The broadcasted transaction
* @throws InsufficientMoneyException * @throws InsufficientMoneyException
* @throws AddressFormatException * @throws AddressFormatException
*/ */
public Transaction createTradingFeeTx(AddressEntry addressEntry, Coin tradingFee, String feeReceiverAddresses) public Transaction createTradingFeeTx(AddressEntry addressEntry, Address changeAddress, Coin reservedFundsForOffer,
boolean useSavingsWallet, Coin tradingFee, String feeReceiverAddresses)
throws InsufficientMoneyException, AddressFormatException { throws InsufficientMoneyException, AddressFormatException {
Transaction tradingFeeTx = new Transaction(params); Transaction tradingFeeTx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee), Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee),
"You cannot send an amount which are smaller than the fee + dust output."); "You cannot send an amount which are smaller than the fee + dust output.");
Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades()); Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades());
tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses)); tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses));
// the reserved amount we need for the trade we send to our trade address
tradingFeeTx.addOutput(reservedFundsForOffer, addressEntry.getAddress());
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
// wait for 1 confirmation) // wait for 1 confirmation)
@ -154,13 +157,16 @@ public class TradeWalletService {
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tradingFeeTx); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tradingFeeTx);
sendRequest.shuffleOutputs = false; sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey; sendRequest.aesKey = aesKey;
if (useSavingsWallet)
sendRequest.coinSelector = new AddressBasedCoinSelector(params);
else
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// We use a fixed fee // We use a fixed fee
sendRequest.feePerKb = Coin.ZERO; sendRequest.feePerKb = Coin.ZERO;
sendRequest.fee = FeePolicy.getFixedTxFeeForTrades(); sendRequest.fee = FeePolicy.getFixedTxFeeForTrades();
// We use always the same address for all transactions in a trade to keep things simple.
// To be discussed if that introduce any privacy issues. // Change is optional in case of overpay or use of funds from savings wallet
sendRequest.changeAddress = addressEntry.getAddress(); sendRequest.changeAddress = changeAddress;
checkNotNull(wallet, "Wallet must not be null"); checkNotNull(wallet, "Wallet must not be null");
wallet.completeTx(sendRequest); wallet.completeTx(sendRequest);
@ -181,20 +187,20 @@ public class TradeWalletService {
/** /**
* The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the addressEntry for that trade. * The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the takersAddressEntry for that trade.
* That will be used to send to the offerer for creating the deposit transaction. * That will be used to send to the offerer for creating the deposit transaction.
* *
* @param inputAmount Amount of takers input * @param inputAmount Amount of takers input
* @param addressEntry Address entry of taker * @param takersAddressEntry Address entry of taker
* @return A data container holding the inputs, the output value and address * @return A data container holding the inputs, the output value and address
* @throws TransactionVerificationException * @throws TransactionVerificationException
* @throws WalletException * @throws WalletException
*/ */
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry addressEntry) throws public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry takersAddressEntry, Address takersChangeAddress) throws
TransactionVerificationException, WalletException { TransactionVerificationException, WalletException, AddressFormatException {
log.trace("createTakerDepositTxInputs called"); log.trace("createTakerDepositTxInputs called");
log.trace("inputAmount " + inputAmount.toFriendlyString()); log.trace("inputAmount " + inputAmount.toFriendlyString());
log.trace("addressEntry " + addressEntry.toString()); log.trace("takersAddressEntry " + takersAddressEntry.toString());
// We add the mining fee 2 times to the deposit tx: // We add the mining fee 2 times to the deposit tx:
// 1. Will be spent when publishing the deposit tx (paid by buyer) // 1. Will be spent when publishing the deposit tx (paid by buyer)
@ -224,7 +230,7 @@ public class TradeWalletService {
// Find the needed inputs to pay the output, optionally add 1 change output. // Find the needed inputs to pay the output, optionally add 1 change output.
// Normally only 1 input and no change output is used, but we support multiple inputs and 1 change output. // Normally only 1 input and no change output is used, but we support multiple inputs and 1 change output.
// Our spending transaction output is from the create offer fee payment. // Our spending transaction output is from the create offer fee payment.
addAvailableInputsAndChangeOutputs(dummyTX, addressEntry); addAvailableInputsAndChangeOutputs(dummyTX, takersAddressEntry, takersChangeAddress);
// The completeTx() call signs the input, but we don't want to pass over signed tx inputs so we remove the signature // The completeTx() call signs the input, but we don't want to pass over signed tx inputs so we remove the signature
removeSignatures(dummyTX); removeSignatures(dummyTX);
@ -268,7 +274,7 @@ public class TradeWalletService {
* @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input) * @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input)
* @param takerChangeOutputValue Optional taker change output value * @param takerChangeOutputValue Optional taker change output value
* @param takerChangeAddressString Optional taker change address * @param takerChangeAddressString Optional taker change address
* @param offererAddressInfo The offerers address entry. * @param offererAddressEntry The offerers address entry.
* @param buyerPubKey The public key of the buyer. * @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller. * @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator. * @param arbitratorPubKey The public key of the arbitrator.
@ -284,7 +290,8 @@ public class TradeWalletService {
List<RawTransactionInput> takerRawTransactionInputs, List<RawTransactionInput> takerRawTransactionInputs,
long takerChangeOutputValue, long takerChangeOutputValue,
@Nullable String takerChangeAddressString, @Nullable String takerChangeAddressString,
AddressEntry offererAddressInfo, AddressEntry offererAddressEntry,
Address offererChangeAddress,
byte[] buyerPubKey, byte[] buyerPubKey,
byte[] sellerPubKey, byte[] sellerPubKey,
byte[] arbitratorPubKey) byte[] arbitratorPubKey)
@ -308,7 +315,7 @@ public class TradeWalletService {
Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.getFixedTxFeeForTrades()); Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.getFixedTxFeeForTrades());
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params)); TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params));
dummyTx.addOutput(dummyOutput); dummyTx.addOutput(dummyOutput);
addAvailableInputsAndChangeOutputs(dummyTx, offererAddressInfo); addAvailableInputsAndChangeOutputs(dummyTx, offererAddressEntry, offererChangeAddress);
// Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions. // Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
List<TransactionInput> offererInputs = dummyTx.getInputs(); List<TransactionInput> offererInputs = dummyTx.getInputs();
TransactionOutput offererOutput = null; TransactionOutput offererOutput = null;
@ -1035,7 +1042,7 @@ public class TradeWalletService {
} }
} }
private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry) throws WalletException { private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry, Address changeAddress) throws WalletException {
try { try {
// Lets let the framework do the work to find the right inputs // Lets let the framework do the work to find the right inputs
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction);
@ -1047,7 +1054,7 @@ public class TradeWalletService {
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation)
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// We use always the same address in a trade for all transactions // We use always the same address in a trade for all transactions
sendRequest.changeAddress = addressEntry.getAddress(); sendRequest.changeAddress = changeAddress;
// With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection. // With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection.
// We don't commit that tx to the wallet as it will be changed later and it's not signed yet. // We don't commit that tx to the wallet as it will be changed later and it's not signed yet.
// So it will not change the wallet balance. // So it will not change the wallet balance.

View file

@ -305,19 +305,13 @@ public class WalletService {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// AddressInfo // Trade AddressEntry
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public List<AddressEntry> getAddressEntryList() { public List<AddressEntry> getAddressEntryList() {
return ImmutableList.copyOf(addressEntryList); return ImmutableList.copyOf(addressEntryList);
} }
public List<AddressEntry> getSavingsAddressEntryList() {
return getAddressEntryList().stream()
.filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS))
.collect(Collectors.toList());
}
public AddressEntry getArbitratorAddressEntry() { public AddressEntry getArbitratorAddressEntry() {
return arbitratorAddressEntry; return arbitratorAddressEntry;
} }
@ -332,10 +326,6 @@ public class WalletService {
return addressEntryList.getNewTradeAddressEntry(offerId); return addressEntryList.getNewTradeAddressEntry(offerId);
} }
public AddressEntry getNewSavingsAddressEntry() {
return addressEntryList.getNewSavingsAddressEntry();
}
private Optional<AddressEntry> getAddressEntryByAddress(String address) { private Optional<AddressEntry> getAddressEntryByAddress(String address) {
return getAddressEntryList().stream() return getAddressEntryList().stream()
.filter(e -> e.getAddressString() != null && e.getAddressString().equals(address)) .filter(e -> e.getAddressString() != null && e.getAddressString().equals(address))
@ -343,6 +333,72 @@ public class WalletService {
} }
///////////////////////////////////////////////////////////////////////////////////////////
// SavingsAddressEntry
///////////////////////////////////////////////////////////////////////////////////////////
public AddressEntry getNewSavingsAddressEntry() {
return addressEntryList.getNewSavingsAddressEntry();
}
public List<AddressEntry> getSavingsAddressEntryList() {
return getAddressEntryList().stream()
.filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS))
.collect(Collectors.toList());
}
public AddressEntry getUnusedSavingsAddressEntry() {
List<AddressEntry> unusedSavingsAddressEntries = getUnusedSavingsAddressEntries();
if (!unusedSavingsAddressEntries.isEmpty())
return unusedSavingsAddressEntries.get(0);
else
return getNewSavingsAddressEntry();
}
public List<AddressEntry> getUnusedSavingsAddressEntries() {
return getSavingsAddressEntryList().stream()
.filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) == 0)
.collect(Collectors.toList());
}
public List<AddressEntry> getUsedSavingsAddressEntries() {
return getSavingsAddressEntryList().stream()
.filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0)
.collect(Collectors.toList());
}
public List<Address> getUsedSavingsAddresses() {
return getSavingsAddressEntryList().stream()
.filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0)
.map(addressEntry -> addressEntry.getAddress())
.collect(Collectors.toList());
}
public List<Transaction> getUsedSavingWalletTransactions() {
List<Transaction> transactions = new ArrayList<>();
List<TransactionOutput> transactionOutputs = new ArrayList<>();
List<Address> usedSavingsAddresses = getUsedSavingsAddresses();
log.debug("usedSavingsAddresses = " + usedSavingsAddresses);
wallet.getTransactions(true).stream().forEach(transaction -> transactionOutputs.addAll(transaction.getOutputs()));
for (TransactionOutput transactionOutput : transactionOutputs) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
if (usedSavingsAddresses.contains(addressOutput) && transactionOutput.getParentTransaction() != null) {
log.debug("transactionOutput.getParentTransaction() = " + transactionOutput.getParentTransaction().getHashAsString());
transactions.add(transactionOutput.getParentTransaction());
}
}
}
return transactions;
}
public void swapTradeToSavings(String offerId) {
addressEntryList.swapTradeToSavings(offerId);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TransactionConfidence // TransactionConfidence
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -448,6 +504,28 @@ public class WalletService {
return balance; return balance;
} }
public Coin getSavingWalletBalance() {
Coin balance = Coin.ZERO;
for (AddressEntry addressEntry : getSavingsAddressEntryList()) {
balance = balance.add(getBalanceForAddress(addressEntry.getAddress()));
}
return balance;
}
public int getNumTxOutputsForAddress(Address address) {
List<TransactionOutput> transactionOutputs = new ArrayList<>();
wallet.getTransactions(true).stream().forEach(t -> transactionOutputs.addAll(t.getOutputs()));
int outputs = 0;
for (TransactionOutput transactionOutput : transactionOutputs) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
if (addressOutput.equals(address))
outputs++;
}
}
return outputs;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Withdrawal // Withdrawal

View file

@ -115,7 +115,7 @@ public final class PaymentMethod implements Persistable, Comparable {
this.maxTradePeriod = paymentMethod.getMaxTradePeriod(); this.maxTradePeriod = paymentMethod.getMaxTradePeriod();
this.maxTradeLimitInBitcoin = paymentMethod.getMaxTradeLimitInBitcoin(); this.maxTradeLimitInBitcoin = paymentMethod.getMaxTradeLimitInBitcoin();
} catch (Throwable t) { } catch (Throwable t) {
log.error("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -55,7 +55,7 @@ public final class BuyerAsOffererTrade extends BuyerTrade implements OffererTrad
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -55,7 +55,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -51,7 +51,7 @@ public final class SellerAsOffererTrade extends SellerTrade implements OffererTr
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -52,7 +52,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -59,7 +59,7 @@ public final class TradableList<T extends Tradable> extends ArrayList<T> impleme
try { try {
in.defaultReadObject(); in.defaultReadObject();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -209,7 +209,7 @@ public abstract class Trade implements Tradable, Model {
initAmountProperty(); initAmountProperty();
errorMessageProperty = new SimpleStringProperty(errorMessage); errorMessageProperty = new SimpleStringProperty(errorMessage);
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }
@ -220,7 +220,8 @@ public abstract class Trade implements Tradable, Model {
TradeManager tradeManager, TradeManager tradeManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
User user, User user,
KeyRing keyRing) { KeyRing keyRing,
Coin fundsNeededForTrade) {
Log.traceCall(); Log.traceCall();
processModel.onAllServicesInitialized(offer, processModel.onAllServicesInitialized(offer,
tradeManager, tradeManager,
@ -230,7 +231,8 @@ public abstract class Trade implements Tradable, Model {
tradeWalletService, tradeWalletService,
arbitratorManager, arbitratorManager,
user, user,
keyRing); keyRing,
fundsNeededForTrade);
createProtocol(); createProtocol();

View file

@ -178,7 +178,7 @@ public class TradeManager {
else {*/ else {*/
trade.setStorage(tradableListStorage); trade.setStorage(tradableListStorage);
trade.updateDepositTxFromWallet(tradeWalletService); trade.updateDepositTxFromWallet(tradeWalletService);
initTrade(trade); initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
// } // }
@ -209,7 +209,7 @@ public class TradeManager {
trade = new SellerAsOffererTrade(offer, tradableListStorage); trade = new SellerAsOffererTrade(offer, tradableListStorage);
trade.setStorage(tradableListStorage); trade.setStorage(tradableListStorage);
initTrade(trade); initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
trades.add(trade); trades.add(trade);
((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress); ((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress);
} else { } else {
@ -220,7 +220,7 @@ public class TradeManager {
} }
} }
private void initTrade(Trade trade) { private void initTrade(Trade trade, Coin fundsNeededForTrade) {
trade.init(p2PService, trade.init(p2PService,
walletService, walletService,
tradeWalletService, tradeWalletService,
@ -228,7 +228,8 @@ public class TradeManager {
this, this,
openOfferManager, openOfferManager,
user, user,
keyRing); keyRing,
fundsNeededForTrade);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -256,6 +257,7 @@ public class TradeManager {
// First we check if offer is still available then we create the trade with the protocol // First we check if offer is still available then we create the trade with the protocol
public void onTakeOffer(Coin amount, public void onTakeOffer(Coin amount,
Coin fundsNeededForTrade,
Offer offer, Offer offer,
String paymentAccountId, String paymentAccountId,
TradeResultHandler tradeResultHandler) { TradeResultHandler tradeResultHandler) {
@ -263,11 +265,12 @@ public class TradeManager {
offer.checkOfferAvailability(model, offer.checkOfferAvailability(model,
() -> { () -> {
if (offer.getState() == Offer.State.AVAILABLE) if (offer.getState() == Offer.State.AVAILABLE)
createTrade(amount, offer, paymentAccountId, model, tradeResultHandler); createTrade(amount, fundsNeededForTrade, offer, paymentAccountId, model, tradeResultHandler);
}); });
} }
private void createTrade(Coin amount, private void createTrade(Coin amount,
Coin fundsNeededForTrade,
Offer offer, Offer offer,
String paymentAccountId, String paymentAccountId,
OfferAvailabilityModel model, OfferAvailabilityModel model,
@ -282,7 +285,7 @@ public class TradeManager {
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight()); trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
trade.setTakerPaymentAccountId(paymentAccountId); trade.setTakerPaymentAccountId(paymentAccountId);
initTrade(trade); initTrade(trade, fundsNeededForTrade);
trades.add(trade); trades.add(trade);
((TakerTrade) trade).takeAvailableOffer(); ((TakerTrade) trade).takeAvailableOffer();

View file

@ -178,7 +178,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
// we don't need to fill it as the error message is only relevant locally, so we don't store it in the transmitted object // we don't need to fill it as the error message is only relevant locally, so we don't store it in the transmitted object
errorMessageProperty = new SimpleStringProperty(); errorMessageProperty = new SimpleStringProperty();
} catch (Throwable t) { } catch (Throwable t) {
log.error("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -65,7 +65,7 @@ public final class OpenOffer implements Tradable {
setState(State.AVAILABLE); setState(State.AVAILABLE);
} catch (Throwable t) { } catch (Throwable t) {
log.error("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }
public Date getDate() { public Date getDate() {

View file

@ -44,6 +44,7 @@ import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol; import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -227,9 +228,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// API // API
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void placeOffer(Offer offer, public void placeOffer(Offer offer, Coin reservedFundsForOffer, boolean useSavingsWallet, TransactionResultHandler resultHandler) {
TransactionResultHandler resultHandler) { PlaceOfferModel model = new PlaceOfferModel(offer, reservedFundsForOffer, useSavingsWallet, walletService, tradeWalletService, offerBookService, user);
PlaceOfferModel model = new PlaceOfferModel(offer, walletService, tradeWalletService, offerBookService, user);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model, model,
transaction -> { transaction -> {
@ -272,6 +272,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setState(OpenOffer.State.CANCELED); openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer); openOffers.remove(openOffer);
closedTradableManager.add(openOffer); closedTradableManager.add(openOffer);
walletService.swapTradeToSavings(offer.getId());
resultHandler.handleResult(); resultHandler.handleResult();
}, },
errorMessageHandler); errorMessageHandler);

View file

@ -23,6 +23,7 @@ import io.bitsquare.common.taskrunner.Model;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OfferBookService; import io.bitsquare.trade.offer.OfferBookService;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -31,6 +32,8 @@ public class PlaceOfferModel implements Model {
private static final Logger log = LoggerFactory.getLogger(PlaceOfferModel.class); private static final Logger log = LoggerFactory.getLogger(PlaceOfferModel.class);
public final Offer offer; public final Offer offer;
public final Coin reservedFundsForOffer;
public final boolean useSavingsWallet;
public final WalletService walletService; public final WalletService walletService;
public final TradeWalletService tradeWalletService; public final TradeWalletService tradeWalletService;
public final OfferBookService offerBookService; public final OfferBookService offerBookService;
@ -39,11 +42,15 @@ public class PlaceOfferModel implements Model {
private Transaction transaction; private Transaction transaction;
public PlaceOfferModel(Offer offer, public PlaceOfferModel(Offer offer,
Coin reservedFundsForOffer,
boolean useSavingsWallet,
WalletService walletService, WalletService walletService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
OfferBookService offerBookService, OfferBookService offerBookService,
User user) { User user) {
this.offer = offer; this.offer = offer;
this.reservedFundsForOffer = reservedFundsForOffer;
this.useSavingsWallet = useSavingsWallet;
this.walletService = walletService; this.walletService = walletService;
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
this.offerBookService = offerBookService; this.offerBookService = offerBookService;

View file

@ -18,13 +18,10 @@
package io.bitsquare.trade.protocol.placeoffer.tasks; package io.bitsquare.trade.protocol.placeoffer.tasks;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.common.taskrunner.Task; import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel; import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -44,10 +41,6 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades());
AddressEntry addressEntry = model.walletService.getTradeAddressEntry(model.offer.getId());
Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress());
if (balance.compareTo(totalsNeeded) >= 0) {
model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() { model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() {
@Override @Override
public void onSuccess(Transaction transaction) { public void onSuccess(Transaction transaction) {
@ -101,10 +94,6 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
failed(t); failed(t);
} }
}); });
} else {
updateStateOnFault();
model.offer.setErrorMessage("You don't have enough balance in your wallet for placing the offer.");
}
} catch (Throwable t) { } catch (Throwable t) {
model.offer.setErrorMessage("An error occurred.\n" + model.offer.setErrorMessage("An error occurred.\n" +
"Error message:\n" "Error message:\n"

View file

@ -28,6 +28,8 @@ import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
public class CreateOfferFeeTx extends Task<PlaceOfferModel> { public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateOfferFeeTx.class); private static final Logger log = LoggerFactory.getLogger(CreateOfferFeeTx.class);
@ -43,8 +45,12 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
NodeAddress selectedArbitratorNodeAddress = ArbitrationSelectionRule.select(model.user.getAcceptedArbitratorAddresses(), model.offer); NodeAddress selectedArbitratorNodeAddress = ArbitrationSelectionRule.select(model.user.getAcceptedArbitratorAddresses(), model.offer);
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateOfferFeeTx");
Transaction transaction = model.tradeWalletService.createTradingFeeTx( Transaction transaction = model.tradeWalletService.createTradingFeeTx(
model.walletService.getTradeAddressEntry(model.offer.getId()), model.walletService.getTradeAddressEntry(model.offer.getId()),
model.walletService.getUnusedSavingsAddressEntry().getAddress(),
model.reservedFundsForOffer,
model.useSavingsWallet,
FeePolicy.getCreateOfferFee(), FeePolicy.getCreateOfferFee(),
selectedArbitrator.getBtcAddress()); selectedArbitrator.getBtcAddress());

View file

@ -110,7 +110,7 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
VerifyTakerAccount.class, VerifyTakerAccount.class,
LoadTakeOfferFeeTx.class, LoadTakeOfferFeeTx.class,
CreateAndSignContract.class, CreateAndSignContract.class,
CreateAndSignDepositTxAsBuyer.class, OffererCreatesAndSignsDepositTxAsBuyer.class,
InitWaitPeriodForOpenDispute.class, InitWaitPeriodForOpenDispute.class,
SetupDepositBalanceListener.class, SetupDepositBalanceListener.class,
SendPublishDepositTxRequest.class SendPublishDepositTxRequest.class

View file

@ -95,7 +95,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
LoadCreateOfferFeeTx.class, LoadCreateOfferFeeTx.class,
CreateTakeOfferFeeTx.class, CreateTakeOfferFeeTx.class,
BroadcastTakeOfferFeeTx.class, BroadcastTakeOfferFeeTx.class,
CreateDepositTxInputsAsBuyer.class, TakerCreatesDepositTxInputsAsBuyer.class,
SendPayDepositRequest.class SendPayDepositRequest.class
); );
startTimeout(); startTimeout();

View file

@ -36,6 +36,8 @@ import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -64,6 +66,7 @@ public class ProcessModel implements Model, Serializable {
transient private KeyRing keyRing; transient private KeyRing keyRing;
transient private P2PService p2PService; transient private P2PService p2PService;
// Mutable // Mutable
public final TradingPeer tradingPeer; public final TradingPeer tradingPeer;
transient private TradeMessage tradeMessage; transient private TradeMessage tradeMessage;
@ -80,6 +83,8 @@ public class ProcessModel implements Model, Serializable {
@Nullable @Nullable
private String changeOutputAddress; private String changeOutputAddress;
private Transaction takeOfferFeeTx; private Transaction takeOfferFeeTx;
public boolean useSavingsWallet;
private Coin fundsNeededForTrade;
public ProcessModel() { public ProcessModel() {
tradingPeer = new TradingPeer(); tradingPeer = new TradingPeer();
@ -89,7 +94,7 @@ public class ProcessModel implements Model, Serializable {
try { try {
in.defaultReadObject(); in.defaultReadObject();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }
@ -101,7 +106,8 @@ public class ProcessModel implements Model, Serializable {
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
ArbitratorManager arbitratorManager, ArbitratorManager arbitratorManager,
User user, User user,
KeyRing keyRing) { KeyRing keyRing,
Coin fundsNeededForTrade) {
this.offer = offer; this.offer = offer;
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
@ -111,6 +117,7 @@ public class ProcessModel implements Model, Serializable {
this.user = user; this.user = user;
this.keyRing = keyRing; this.keyRing = keyRing;
this.p2PService = p2PService; this.p2PService = p2PService;
this.fundsNeededForTrade = fundsNeededForTrade;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -154,6 +161,10 @@ public class ProcessModel implements Model, Serializable {
return p2PService.getAddress(); return p2PService.getAddress();
} }
public Coin getFundsNeededForTrade() {
return fundsNeededForTrade;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getter/Setter for Mutable objects // Getter/Setter for Mutable objects
@ -183,6 +194,10 @@ public class ProcessModel implements Model, Serializable {
return walletService.getTradeAddressEntry(offer.getId()); return walletService.getTradeAddressEntry(offer.getId());
} }
public Address getUnusedSavingsAddress() {
return walletService.getUnusedSavingsAddressEntry().getAddress();
}
public byte[] getTradeWalletPubKey() { public byte[] getTradeWalletPubKey() {
return getAddressEntry().getPubKey(); return getAddressEntry().getPubKey();
} }

View file

@ -111,7 +111,7 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
LoadTakeOfferFeeTx.class, LoadTakeOfferFeeTx.class,
InitWaitPeriodForOpenDispute.class, InitWaitPeriodForOpenDispute.class,
CreateAndSignContract.class, CreateAndSignContract.class,
CreateAndSignDepositTxAsSeller.class, OffererCreatesAndSignsDepositTxAsSeller.class,
SetupDepositBalanceListener.class, SetupDepositBalanceListener.class,
SendPublishDepositTxRequest.class SendPublishDepositTxRequest.class
); );

View file

@ -103,7 +103,7 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
LoadCreateOfferFeeTx.class, LoadCreateOfferFeeTx.class,
CreateTakeOfferFeeTx.class, CreateTakeOfferFeeTx.class,
BroadcastTakeOfferFeeTx.class, BroadcastTakeOfferFeeTx.class,
CreateDepositTxInputsAsSeller.class, TakerCreatesDepositTxInputsAsSeller.class,
SendPayDepositRequest.class SendPayDepositRequest.class
); );
startTimeout(); startTimeout();

View file

@ -64,7 +64,7 @@ public final class TradingPeer implements Persistable {
try { try {
in.defaultReadObject(); in.defaultReadObject();
} catch (Throwable t) { } catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage()); log.warn("Cannot be deserialized." + t.getMessage());
} }
} }

View file

@ -29,10 +29,10 @@ import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class CreateAndSignDepositTxAsBuyer extends TradeTask { public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateAndSignDepositTxAsBuyer.class); private static final Logger log = LoggerFactory.getLogger(OffererCreatesAndSignsDepositTxAsBuyer.class);
public CreateAndSignDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) { public OffererCreatesAndSignsDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -60,6 +60,7 @@ public class CreateAndSignDepositTxAsBuyer extends TradeTask {
processModel.tradingPeer.getChangeOutputValue(), processModel.tradingPeer.getChangeOutputValue(),
processModel.tradingPeer.getChangeOutputAddress(), processModel.tradingPeer.getChangeOutputAddress(),
processModel.getAddressEntry(), processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.getTradeWalletPubKey(), processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getTradeWalletPubKey(), processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()));

View file

@ -26,10 +26,10 @@ import org.bitcoinj.core.Coin;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class CreateDepositTxInputsAsBuyer extends TradeTask { public class TakerCreatesDepositTxInputsAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateDepositTxInputsAsBuyer.class); private static final Logger log = LoggerFactory.getLogger(TakerCreatesDepositTxInputsAsBuyer.class);
public CreateDepositTxInputsAsBuyer(TaskRunner taskHandler, Trade trade) { public TakerCreatesDepositTxInputsAsBuyer(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -38,7 +38,9 @@ public class CreateDepositTxInputsAsBuyer extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()); Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades());
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry()); InputsAndChangeOutput result = processModel.getTradeWalletService()
.takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress());
processModel.setRawTransactionInputs(result.rawTransactionInputs); processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue); processModel.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress); processModel.setChangeOutputAddress(result.changeOutputAddress);

View file

@ -29,10 +29,10 @@ import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class CreateAndSignDepositTxAsSeller extends TradeTask { public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateAndSignDepositTxAsSeller.class); private static final Logger log = LoggerFactory.getLogger(OffererCreatesAndSignsDepositTxAsSeller.class);
public CreateAndSignDepositTxAsSeller(TaskRunner taskHandler, Trade trade) { public OffererCreatesAndSignsDepositTxAsSeller(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -60,6 +60,7 @@ public class CreateAndSignDepositTxAsSeller extends TradeTask {
processModel.tradingPeer.getChangeOutputValue(), processModel.tradingPeer.getChangeOutputValue(),
processModel.tradingPeer.getChangeOutputAddress(), processModel.tradingPeer.getChangeOutputAddress(),
processModel.getAddressEntry(), processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.tradingPeer.getTradeWalletPubKey(), processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getTradeWalletPubKey(), processModel.getTradeWalletPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()));

View file

@ -26,10 +26,10 @@ import org.bitcoinj.core.Coin;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class CreateDepositTxInputsAsSeller extends TradeTask { public class TakerCreatesDepositTxInputsAsSeller extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateDepositTxInputsAsSeller.class); private static final Logger log = LoggerFactory.getLogger(TakerCreatesDepositTxInputsAsSeller.class);
public CreateDepositTxInputsAsSeller(TaskRunner taskHandler, Trade trade) { public TakerCreatesDepositTxInputsAsSeller(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -40,8 +40,10 @@ public class CreateDepositTxInputsAsSeller extends TradeTask {
if (trade.getTradeAmount() != null) { if (trade.getTradeAmount() != null) {
Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount()); Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount());
InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel InputsAndChangeOutput result = processModel.getTradeWalletService()
.getAddressEntry()); .takerCreatesDepositsTxInputs(takerInputAmount,
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress());
processModel.setRawTransactionInputs(result.rawTransactionInputs); processModel.setRawTransactionInputs(result.rawTransactionInputs);
processModel.setChangeOutputValue(result.changeOutputValue); processModel.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress); processModel.setChangeOutputAddress(result.changeOutputAddress);

View file

@ -29,6 +29,8 @@ import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
public class CreateTakeOfferFeeTx extends TradeTask { public class CreateTakeOfferFeeTx extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateTakeOfferFeeTx.class); private static final Logger log = LoggerFactory.getLogger(CreateTakeOfferFeeTx.class);
@ -45,8 +47,12 @@ public class CreateTakeOfferFeeTx extends TradeTask {
NodeAddress selectedArbitratorNodeAddress = ArbitrationSelectionRule.select(user.getAcceptedArbitratorAddresses(), processModel.getOffer()); NodeAddress selectedArbitratorNodeAddress = ArbitrationSelectionRule.select(user.getAcceptedArbitratorAddresses(), processModel.getOffer());
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx");
Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx( Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx(
processModel.getAddressEntry(), processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.getFundsNeededForTrade(),
processModel.useSavingsWallet,
FeePolicy.getTakeOfferFee(), FeePolicy.getTakeOfferFee(),
selectedArbitrator.getBtcAddress()); selectedArbitrator.getBtcAddress());

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 = false; public static final boolean DEV_MODE = true;
public static final boolean IS_RELEASE_VERSION = !DEV_MODE && true; public static final boolean IS_RELEASE_VERSION = !DEV_MODE && true;
private static Environment env; private static Environment env;

View file

@ -201,6 +201,12 @@ bg color of non edit textFields: fafafa
-fx-cursor: hand; -fx-cursor: hand;
} }
.internal-funds-icon {
-fx-text-fill: #999;
-fx-cursor: hand;
}
/******************************************************************************* /*******************************************************************************
* * * *
* Tooltip * * Tooltip *
@ -1000,3 +1006,7 @@ textfield */
-fx-text-fill: white; -fx-text-fill: white;
-fx-cursor: hand; -fx-cursor: hand;
} }
#popup-qr-code-info {
-fx-font-size: 11;
}

View file

@ -21,11 +21,16 @@ public class AddressWithIconAndDirection extends AnchorPane {
private final Label directionIcon; private final Label directionIcon;
private final Label label; private final Label label;
public AddressWithIconAndDirection(String text, String address, AwesomeIcon awesomeIcon, boolean received) { public AddressWithIconAndDirection(String text, String address, AwesomeIcon awesomeIcon, boolean received, boolean isInternal) {
directionIcon = new Label(); directionIcon = new Label();
directionIcon.setLayoutY(3); directionIcon.setLayoutY(3);
if (isInternal) {
directionIcon.getStyleClass().add("internal-funds-icon");
AwesomeDude.setIcon(directionIcon, AwesomeIcon.REPEAT);
} else {
directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon"); directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon");
AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT); AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT);
}
directionIcon.setMouseTransparent(true); directionIcon.setMouseTransparent(true);
HBox hBox = new HBox(); HBox hBox = new HBox();
@ -52,7 +57,7 @@ public class AddressWithIconAndDirection extends AnchorPane {
AnchorPane.setLeftAnchor(directionIcon, 3.0); AnchorPane.setLeftAnchor(directionIcon, 3.0);
AnchorPane.setTopAnchor(directionIcon, 2.0); AnchorPane.setTopAnchor(directionIcon, 2.0);
AnchorPane.setLeftAnchor(hBox, 20.0); AnchorPane.setLeftAnchor(hBox, 22.0);
AnchorPane.setRightAnchor(hBox, 15.0); AnchorPane.setRightAnchor(hBox, 15.0);
AnchorPane.setRightAnchor(openLinkIcon, 4.0); AnchorPane.setRightAnchor(openLinkIcon, 4.0);
AnchorPane.setTopAnchor(openLinkIcon, 3.0); AnchorPane.setTopAnchor(openLinkIcon, 3.0);

View file

@ -60,9 +60,11 @@ public class BalanceTextField extends AnchorPane {
getChildren().addAll(textField); getChildren().addAll(textField);
} }
public void setup(Address address, BSFormatter formatter) { public void setFormatter(BSFormatter formatter) {
this.formatter = formatter; this.formatter = formatter;
}
public void setupBalanceListener(Address address) {
balanceListener = new BalanceListener(address) { balanceListener = new BalanceListener(address) {
@Override @Override
public void onBalanceChanged(Coin balance, Transaction tx) { public void onBalanceChanged(Coin balance, Transaction tx) {
@ -74,15 +76,20 @@ public class BalanceTextField extends AnchorPane {
} }
public void cleanup() { public void cleanup() {
if (balanceListener != null)
walletService.removeBalanceListener(balanceListener); walletService.removeBalanceListener(balanceListener);
} }
public void setBalance(Coin balance) {
updateBalance(balance);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private methods // Private methods
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void updateBalance(Coin balance) { private void updateBalance(Coin balance) {
if (formatter != null)
textField.setText(formatter.formatCoinWithCode(balance)); textField.setText(formatter.formatCoinWithCode(balance));
if (balance.isPositive()) if (balance.isPositive())
textField.setEffect(fundedEffect); textField.setEffect(fundedEffect);

View file

@ -473,7 +473,7 @@ public class MainViewModel implements ViewModel {
if (tuple.payload instanceof Ping && if (tuple.payload instanceof Ping &&
((Ping) tuple.payload).nonce == payload.nonce && ((Ping) tuple.payload).nonce == payload.nonce &&
((Ping) tuple.payload).lastRoundTripTime == payload.lastRoundTripTime) ((Ping) tuple.payload).lastRoundTripTime == payload.lastRoundTripTime)
log.trace("Crypto test succeeded"); log.debug("Crypto test succeeded");
else else
throw new CryptoException("Payload not correct after decryption"); throw new CryptoException("Payload not correct after decryption");
} catch (CryptoException e) { } catch (CryptoException e) {
@ -494,9 +494,7 @@ public class MainViewModel implements ViewModel {
} }
}; };
// Delay a bit the test, there was one bug report (Key length not 128//192/256 bits) where the crypto test failed. checkCryptoThread.start();
// TODO investigate
UserThread.runAfter(() -> checkCryptoThread.start(), 3);
} }

View file

@ -51,7 +51,7 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
private ListView<String> languagesListView; private ListView<String> languagesListView;
private ComboBox<String> languageComboBox; private ComboBox<String> languageComboBox;
private TableView<ArbitratorListItem> table; private TableView<ArbitratorListItem> tableView;
private int gridRow = 0; private int gridRow = 0;
private CheckBox autoSelectAllMatchingCheckBox; private CheckBox autoSelectAllMatchingCheckBox;
private ListChangeListener<String> listChangeListener; private ListChangeListener<String> listChangeListener;
@ -83,7 +83,7 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
languagesListView.setItems(model.languageCodes); languagesListView.setItems(model.languageCodes);
languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2); languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
table.setItems(model.arbitratorListItems); tableView.setItems(model.arbitratorListItems);
autoSelectAllMatchingCheckBox.setSelected(model.getAutoSelectArbitrators()); autoSelectAllMatchingCheckBox.setSelected(model.getAutoSelectArbitrators());
} }
@ -188,11 +188,11 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
GridPane.setMargin(tableGroupHeadline, new Insets(40, -10, -10, -10)); GridPane.setMargin(tableGroupHeadline, new Insets(40, -10, -10, -10));
root.getChildren().add(tableGroupHeadline); root.getChildren().add(tableGroupHeadline);
table = new TableView<>(); tableView = new TableView<>();
GridPane.setRowIndex(table, gridRow); GridPane.setRowIndex(tableView, gridRow);
GridPane.setColumnSpan(table, 2); GridPane.setColumnSpan(tableView, 2);
GridPane.setMargin(table, new Insets(60, -10, 5, -10)); GridPane.setMargin(tableView, new Insets(60, -10, 5, -10));
root.getChildren().add(table); root.getChildren().add(tableView);
autoSelectAllMatchingCheckBox = addCheckBox(root, ++gridRow, "Auto select all arbitrators with matching language"); autoSelectAllMatchingCheckBox = addCheckBox(root, ++gridRow, "Auto select all arbitrators with matching language");
GridPane.setColumnSpan(autoSelectAllMatchingCheckBox, 2); GridPane.setColumnSpan(autoSelectAllMatchingCheckBox, 2);
@ -202,15 +202,18 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
autoSelectAllMatchingCheckBox.setOnAction(event -> model.setAutoSelectArbitrators(autoSelectAllMatchingCheckBox.isSelected())); autoSelectAllMatchingCheckBox.setOnAction(event -> model.setAutoSelectArbitrators(autoSelectAllMatchingCheckBox.isSelected()));
TableColumn<ArbitratorListItem, String> dateColumn = new TableColumn("Registration date"); TableColumn<ArbitratorListItem, String> dateColumn = new TableColumn("Registration date");
dateColumn.setSortable(false);
dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getRegistrationDate())); dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getRegistrationDate()));
dateColumn.setMinWidth(130); dateColumn.setMinWidth(130);
dateColumn.setMaxWidth(130); dateColumn.setMaxWidth(130);
TableColumn<ArbitratorListItem, String> nameColumn = new TableColumn("Onion address"); TableColumn<ArbitratorListItem, String> nameColumn = new TableColumn("Onion address");
nameColumn.setSortable(false);
nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getAddressString())); nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getAddressString()));
nameColumn.setMinWidth(90); nameColumn.setMinWidth(90);
TableColumn<ArbitratorListItem, String> languagesColumn = new TableColumn("Languages"); TableColumn<ArbitratorListItem, String> languagesColumn = new TableColumn("Languages");
languagesColumn.setSortable(false);
languagesColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getLanguageCodes())); languagesColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getLanguageCodes()));
languagesColumn.setMinWidth(130); languagesColumn.setMinWidth(130);
@ -309,8 +312,8 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
} }
}); });
table.getColumns().addAll(dateColumn, nameColumn, languagesColumn, selectionColumn); tableView.getColumns().addAll(dateColumn, nameColumn, languagesColumn, selectionColumn);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
} }
} }

View file

@ -83,7 +83,7 @@ public class DebugView extends InitializableView {
VerifyArbitrationSelection.class, VerifyArbitrationSelection.class,
VerifyTakerAccount.class, VerifyTakerAccount.class,
CreateAndSignContract.class, CreateAndSignContract.class,
CreateAndSignDepositTxAsBuyer.class, OffererCreatesAndSignsDepositTxAsBuyer.class,
LoadTakeOfferFeeTx.class, LoadTakeOfferFeeTx.class,
InitWaitPeriodForOpenDispute.class, InitWaitPeriodForOpenDispute.class,
SetupDepositBalanceListener.class, SetupDepositBalanceListener.class,
@ -106,7 +106,7 @@ public class DebugView extends InitializableView {
SelectArbitrator.class, SelectArbitrator.class,
CreateTakeOfferFeeTx.class, CreateTakeOfferFeeTx.class,
BroadcastTakeOfferFeeTx.class, BroadcastTakeOfferFeeTx.class,
CreateDepositTxInputsAsSeller.class, TakerCreatesDepositTxInputsAsSeller.class,
SendPayDepositRequest.class, SendPayDepositRequest.class,
ProcessPublishDepositTxRequest.class, ProcessPublishDepositTxRequest.class,
@ -132,7 +132,7 @@ public class DebugView extends InitializableView {
SelectArbitrator.class, SelectArbitrator.class,
CreateTakeOfferFeeTx.class, CreateTakeOfferFeeTx.class,
BroadcastTakeOfferFeeTx.class, BroadcastTakeOfferFeeTx.class,
CreateDepositTxInputsAsSeller.class, TakerCreatesDepositTxInputsAsSeller.class,
SendPayDepositRequest.class, SendPayDepositRequest.class,
ProcessPublishDepositTxRequest.class, ProcessPublishDepositTxRequest.class,
@ -159,7 +159,7 @@ public class DebugView extends InitializableView {
VerifyTakerAccount.class, VerifyTakerAccount.class,
InitWaitPeriodForOpenDispute.class, InitWaitPeriodForOpenDispute.class,
CreateAndSignContract.class, CreateAndSignContract.class,
CreateAndSignDepositTxAsBuyer.class, OffererCreatesAndSignsDepositTxAsBuyer.class,
SetupDepositBalanceListener.class, SetupDepositBalanceListener.class,
SendPublishDepositTxRequest.class, SendPublishDepositTxRequest.class,

View file

@ -90,7 +90,9 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
private final List<Attachment> tempAttachments = new ArrayList<>(); private final List<Attachment> tempAttachments = new ArrayList<>();
private TableView<Dispute> disputesTable; private TableView<Dispute> tableView;
private SortedList<Dispute> sortedList;
private Dispute selectedDispute; private Dispute selectedDispute;
private ListView<DisputeCommunicationMessage> messageListView; private ListView<DisputeCommunicationMessage> messageListView;
private TextArea inputTextArea; private TextArea inputTextArea;
@ -133,28 +135,32 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
@Override @Override
public void initialize() { public void initialize() {
disputesTable = new TableView<>(); tableView = new TableView<>();
VBox.setVgrow(disputesTable, Priority.SOMETIMES); VBox.setVgrow(tableView, Priority.SOMETIMES);
disputesTable.setMinHeight(150); tableView.setMinHeight(150);
root.getChildren().add(disputesTable); root.getChildren().add(tableView);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<Dispute, Dispute> tradeIdColumn = getTradeIdColumn();
disputesTable.getColumns().add(tradeIdColumn);
TableColumn<Dispute, Dispute> roleColumn = getRoleColumn();
disputesTable.getColumns().add(roleColumn);
TableColumn<Dispute, Dispute> dateColumn = getDateColumn();
disputesTable.getColumns().add(dateColumn);
TableColumn<Dispute, Dispute> contractColumn = getContractColumn();
disputesTable.getColumns().add(contractColumn);
TableColumn<Dispute, Dispute> stateColumn = getStateColumn();
disputesTable.getColumns().add(stateColumn);
disputesTable.getSortOrder().add(dateColumn);
disputesTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("There are no open tickets"); Label placeholder = new Label("There are no open tickets");
placeholder.setWrapText(true); placeholder.setWrapText(true);
disputesTable.setPlaceholder(placeholder); tableView.setPlaceholder(placeholder);
disputesTable.getSelectionModel().clearSelection(); tableView.getSelectionModel().clearSelection();
TableColumn<Dispute, Dispute> tradeIdColumn = getTradeIdColumn();
tableView.getColumns().add(tradeIdColumn);
TableColumn<Dispute, Dispute> roleColumn = getRoleColumn();
tableView.getColumns().add(roleColumn);
TableColumn<Dispute, Dispute> dateColumn = getDateColumn();
tableView.getColumns().add(dateColumn);
TableColumn<Dispute, Dispute> contractColumn = getContractColumn();
tableView.getColumns().add(contractColumn);
TableColumn<Dispute, Dispute> stateColumn = getStateColumn();
tableView.getColumns().add(stateColumn);
tradeIdColumn.setComparator((o1, o2) -> o1.getTradeId().compareTo(o2.getTradeId()));
dateColumn.setComparator((o1, o2) -> o1.getOpeningDate().compareTo(o2.getOpeningDate()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
/*inputTextAreaListener = (observable, oldValue, newValue) -> /*inputTextAreaListener = (observable, oldValue, newValue) ->
sendButton.setDisable(newValue.length() == 0 sendButton.setDisable(newValue.length() == 0
@ -183,24 +189,27 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
@Override @Override
protected void activate() { protected void activate() {
FilteredList<Dispute> filteredList = new FilteredList<>(disputeManager.getDisputesAsObservableList()); FilteredList<Dispute> filteredList = new FilteredList<>(disputeManager.getDisputesAsObservableList());
setFilteredListPredicate(filteredList); setFilteredListPredicate(filteredList);
SortedList<Dispute> sortedList = new SortedList<>(filteredList);
// sortedList.setComparator((o1, o2) -> o2.getOpeningDate().compareTo(o1.getOpeningDate()));
sortedList.comparatorProperty().bind(disputesTable.comparatorProperty());
disputesTable.setItems(sortedList);
disputesTable.sort();
selectedDisputeSubscription = EasyBind.subscribe(disputesTable.getSelectionModel().selectedItemProperty(), this::onSelectDispute);
Dispute selectedItem = disputesTable.getSelectionModel().getSelectedItem(); sortedList = new SortedList<>(filteredList);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
// sortedList.setComparator((o1, o2) -> o2.getOpeningDate().compareTo(o1.getOpeningDate()));
selectedDisputeSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectDispute);
Dispute selectedItem = tableView.getSelectionModel().getSelectedItem();
if (selectedItem != null) if (selectedItem != null)
disputesTable.getSelectionModel().select(selectedItem); tableView.getSelectionModel().select(selectedItem);
scrollToBottom(); scrollToBottom();
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
sortedList.comparatorProperty().unbind();
selectedDisputeSubscription.unsubscribe(); selectedDisputeSubscription.unsubscribe();
removeListenersOnSelectDispute(); removeListenersOnSelectDispute();
} }
@ -752,6 +761,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
setMinWidth(130); setMinWidth(130);
} }
}; };
column.setSortable(false);
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
column.setCellFactory( column.setCellFactory(
new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() { new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() {
@ -809,6 +819,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
setSortable(false); setSortable(false);
} }
}; };
column.setSortable(false);
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
column.setCellFactory( column.setCellFactory(
new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() { new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() {
@ -846,6 +857,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
setMinWidth(50); setMinWidth(50);
} }
}; };
column.setSortable(false);
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
column.setCellFactory( column.setCellFactory(
new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() { new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() {

View file

@ -37,6 +37,8 @@ public class DepositListItem {
private final Logger log = LoggerFactory.getLogger(this.getClass()); private final Logger log = LoggerFactory.getLogger(this.getClass());
private final StringProperty balance = new SimpleStringProperty(); private final StringProperty balance = new SimpleStringProperty();
private final WalletService walletService; private final WalletService walletService;
private Coin balanceAsCoin;
private BSFormatter formatter; private BSFormatter formatter;
private final ConfidenceProgressIndicator progressIndicator; private final ConfidenceProgressIndicator progressIndicator;
private final Tooltip tooltip; private final Tooltip tooltip;
@ -44,8 +46,9 @@ public class DepositListItem {
private String balanceString; private String balanceString;
private String addressString; private String addressString;
private String status = "Unused"; private String usage = "-";
private TxConfidenceListener txConfidenceListener; private TxConfidenceListener txConfidenceListener;
private int numTxOutputs = 0;
// public DepositListItem(AddressEntry addressEntry, Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) { // public DepositListItem(AddressEntry addressEntry, Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
public DepositListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) { public DepositListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) {
@ -67,17 +70,17 @@ public class DepositListItem {
walletService.addBalanceListener(new BalanceListener(address) { walletService.addBalanceListener(new BalanceListener(address) {
@Override @Override
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) { public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
DepositListItem.this.balanceAsCoin = balanceAsCoin;
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin)); DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
updateConfidence(walletService.getConfidenceForTxId(tx.getHashAsString())); updateConfidence(walletService.getConfidenceForTxId(tx.getHashAsString()));
if (balanceAsCoin.isPositive()) updateUsage(address);
status = "Funded";
} }
}); });
Coin balanceAsCoin = walletService.getBalanceForAddress(address); balanceAsCoin = walletService.getBalanceForAddress(address);
balance.set(formatter.formatCoin(balanceAsCoin)); balance.set(formatter.formatCoin(balanceAsCoin));
if (balanceAsCoin.isPositive())
status = "Funded"; updateUsage(address);
TransactionConfidence transactionConfidence = walletService.getConfidenceForAddress(address); TransactionConfidence transactionConfidence = walletService.getConfidenceForAddress(address);
if (transactionConfidence != null) { if (transactionConfidence != null) {
@ -93,8 +96,9 @@ public class DepositListItem {
} }
} }
public void setStatus(String status) { private void updateUsage(Address address) {
this.status = status; numTxOutputs = walletService.getNumTxOutputsForAddress(address);
usage = numTxOutputs == 0 ? "Unused" : "Used in " + numTxOutputs + " transactions";
} }
public void cleanup() { public void cleanup() {
@ -134,8 +138,8 @@ public class DepositListItem {
return addressString; return addressString;
} }
public String getStatus() { public String getUsage() {
return status; return usage;
} }
public final StringProperty balanceProperty() { public final StringProperty balanceProperty() {
@ -146,4 +150,11 @@ public class DepositListItem {
return balance.get(); return balance.get();
} }
public Coin getBalanceAsCoin() {
return balanceAsCoin;
}
public int getNumTxOutputs() {
return numTxOutputs;
}
} }

View file

@ -18,7 +18,6 @@
--> -->
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.deposit.DepositView" <VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.deposit.DepositView"
@ -27,18 +26,13 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Select" fx:id="selectColumn" minWidth="110" maxWidth="110" sortable="false"/> <TableColumn text="Select" fx:id="selectColumn" minWidth="110" maxWidth="110" sortable="false"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="260"/> <TableColumn text="Address" fx:id="addressColumn" minWidth="320"/>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="150" maxWidth="150"> <TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="150"/>
<cellValueFactory> <TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="150"/>
<PropertyValueFactory property="balance"/> <TableColumn text="Usage" fx:id="usageColumn" minWidth="200"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="150" maxWidth="150"/>
<TableColumn text="Status" fx:id="statusColumn" minWidth="150" maxWidth="150"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -18,7 +18,9 @@
package io.bitsquare.gui.main.funds.deposit; package io.bitsquare.gui.main.funds.deposit;
import de.jensd.fx.fontawesome.AwesomeIcon; import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple2;
@ -27,26 +29,20 @@ import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.AddressTextField; import io.bitsquare.gui.components.AddressTextField;
import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.QRCodeWindow; import io.bitsquare.gui.main.overlays.windows.QRCodeWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout; import io.bitsquare.gui.util.Layout;
import io.bitsquare.gui.util.validation.BtcAddressValidator;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager;
import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.HPos;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
@ -58,6 +54,8 @@ import net.glxn.qrgen.image.ImageType;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.uri.BitcoinURI; import org.bitcoinj.uri.BitcoinURI;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.inject.Inject; import javax.inject.Inject;
@ -72,30 +70,27 @@ public class DepositView extends ActivatableView<VBox, Void> {
GridPane gridPane; GridPane gridPane;
@FXML @FXML
TableView<DepositListItem> table; TableView<DepositListItem> tableView;
@FXML @FXML
TableColumn<DepositListItem, DepositListItem> selectColumn, addressColumn, balanceColumn, confidenceColumn, statusColumn; TableColumn<DepositListItem, DepositListItem> selectColumn, addressColumn, balanceColumn, confidenceColumn, usageColumn;
private ImageView qrCodeImageView; private ImageView qrCodeImageView;
private int gridRow = 0; private int gridRow = 0;
private AddressTextField addressTextField; private AddressTextField addressTextField;
Button generateNewAddressButton; Button generateNewAddressButton;
private final WalletService walletService; private final WalletService walletService;
private final TradeManager tradeManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
private final OpenOfferManager openOfferManager;
private final BSFormatter formatter; private final BSFormatter formatter;
private final Preferences preferences; private final Preferences preferences;
private final BtcAddressValidator btcAddressValidator; private final ObservableList<DepositListItem> observableList = FXCollections.observableArrayList();
private final WalletPasswordWindow walletPasswordWindow; private final SortedList<DepositListItem> sortedList = new SortedList<>(observableList);
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
private final ObservableList<DepositListItem> depositAddresses = FXCollections.observableArrayList();
private BalanceListener balanceListener; private BalanceListener balanceListener;
private TitledGroupBg titledGroupBg; private TitledGroupBg titledGroupBg;
private Label addressLabel; private Label addressLabel, amountLabel;
private Label qrCodeLabel; private Label qrCodeLabel;
private InputTextField amountTextField;
private Subscription amountTextFieldSubscription;
private String paymentLabel;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -103,36 +98,34 @@ public class DepositView extends ActivatableView<VBox, Void> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private DepositView(WalletService walletService, TradeManager tradeManager, private DepositView(WalletService walletService,
ClosedTradableManager closedTradableManager, BSFormatter formatter,
FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager, Preferences preferences) {
BSFormatter formatter, Preferences preferences,
BtcAddressValidator btcAddressValidator, WalletPasswordWindow walletPasswordWindow,
OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
this.walletService = walletService; this.walletService = walletService;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
this.openOfferManager = openOfferManager;
this.formatter = formatter; this.formatter = formatter;
this.preferences = preferences; this.preferences = preferences;
this.btcAddressValidator = btcAddressValidator;
this.walletPasswordWindow = walletPasswordWindow;
this.offerDetailsWindow = offerDetailsWindow;
this.tradeDetailsWindow = tradeDetailsWindow;
} }
@Override @Override
public void initialize() { public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No deposit addresses are generated yet")); tableView.setPlaceholder(new Label("No deposit addresses are generated yet"));
setSelectColumnCellFactory(); setSelectColumnCellFactory();
setAddressColumnCellFactory(); setAddressColumnCellFactory();
setStatusColumnCellFactory(); setBalanceColumnCellFactory();
setUsageColumnCellFactory();
setConfidenceColumnCellFactory(); setConfidenceColumnCellFactory();
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 2, "Fund your wallet"); addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString()));
balanceColumn.setComparator((o1, o2) -> o1.getBalanceAsCoin().compareTo(o2.getBalanceAsCoin()));
confidenceColumn.setComparator((o1, o2) -> Double.valueOf(o1.getProgressIndicator().getProgress())
.compareTo(o2.getProgressIndicator().getProgress()));
usageColumn.setComparator((a, b) -> (a.getNumTxOutputs() < b.getNumTxOutputs()) ? -1 : ((a.getNumTxOutputs() == b.getNumTxOutputs()) ? 0 : 1));
tableView.getSortOrder().add(usageColumn);
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 3, "Fund your wallet");
qrCodeLabel = addLabel(gridPane, gridRow, "QR-Code:", 0); qrCodeLabel = addLabel(gridPane, gridRow, "QR-Code:", 0);
//GridPane.setMargin(qrCodeLabel, new Insets(Layout.FIRST_ROW_DISTANCE - 9, 0, 0, 5)); //GridPane.setMargin(qrCodeLabel, new Insets(Layout.FIRST_ROW_DISTANCE - 9, 0, 0, 5));
@ -148,9 +141,18 @@ public class DepositView extends ActivatableView<VBox, Void> {
Tuple2<Label, AddressTextField> addressTuple = addLabelAddressTextField(gridPane, ++gridRow, "Address:"); Tuple2<Label, AddressTextField> addressTuple = addLabelAddressTextField(gridPane, ++gridRow, "Address:");
addressLabel = addressTuple.first; addressLabel = addressTuple.first;
GridPane.setValignment(addressLabel, VPos.TOP); //GridPane.setValignment(addressLabel, VPos.TOP);
GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0)); //GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0));
addressTextField = addressTuple.second; addressTextField = addressTuple.second;
paymentLabel = "Fund Bitsquare wallet";
addressTextField.setPaymentLabel(paymentLabel);
Tuple2<Label, InputTextField> amountTuple = addLabelInputTextField(gridPane, ++gridRow, "Amount in BTC (optional):");
amountLabel = amountTuple.first;
amountTextField = amountTuple.second;
if (BitsquareApp.DEV_MODE)
amountTextField.setText("1");
titledGroupBg.setVisible(false); titledGroupBg.setVisible(false);
titledGroupBg.setManaged(false); titledGroupBg.setManaged(false);
@ -162,16 +164,18 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressLabel.setManaged(false); addressLabel.setManaged(false);
addressTextField.setVisible(false); addressTextField.setVisible(false);
addressTextField.setManaged(false); addressTextField.setManaged(false);
amountLabel.setVisible(false);
amountTextField.setManaged(false);
generateNewAddressButton = addButton(gridPane, ++gridRow, "Generate new address", -20); generateNewAddressButton = addButton(gridPane, ++gridRow, "Generate new address", -20);
GridPane.setColumnIndex(generateNewAddressButton, 0);
GridPane.setHalignment(generateNewAddressButton, HPos.LEFT);
generateNewAddressButton.setOnAction(event -> { generateNewAddressButton.setOnAction(event -> {
boolean hasUnUsedAddress = walletService.getSavingsAddressEntryList().stream() boolean hasUnUsedAddress = observableList.stream().filter(e -> e.getNumTxOutputs() == 0).findAny().isPresent();
.filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
.findAny().isPresent();
if (hasUnUsedAddress) { if (hasUnUsedAddress) {
new Popup().warning("You have already addresses generated which are still not used.\n" + new Popup().warning("You have addresses which are not used in any transaction.\n" +
"Please select in the address table an unused address.").show(); "Please select in the address table any unused address.").show();
} else { } else {
AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry(); AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry();
fillForm(newSavingsAddressEntry.getAddressString()); fillForm(newSavingsAddressEntry.getAddressString());
@ -187,25 +191,44 @@ public class DepositView extends ActivatableView<VBox, Void> {
}; };
} }
private Coin getAmountAsCoin() {
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
senderAmount = Coin.ZERO;
/* new Popup()
.warning("The amount is lower than the transaction fee and the min. possible tx value (dust).")
.show();*/
}
return senderAmount;
}
@NotNull @NotNull
private String getBitcoinURI() { private String getBitcoinURI() {
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(), return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
null, getAmountAsCoin(),
null, paymentLabel,
null); null);
} }
@Override @Override
protected void activate() { protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList(); updateList();
walletService.addBalanceListener(balanceListener); walletService.addBalanceListener(balanceListener);
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
addressTextField.setAmountAsCoin(formatter.parseToCoin(t));
updateQRCode();
});
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
depositAddresses.forEach(DepositListItem::cleanup); sortedList.comparatorProperty().unbind();
observableList.forEach(DepositListItem::cleanup);
walletService.removeBalanceListener(balanceListener); walletService.removeBalanceListener(balanceListener);
amountTextFieldSubscription.unsubscribe();
} }
@ -225,11 +248,19 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressLabel.setManaged(true); addressLabel.setManaged(true);
addressTextField.setVisible(true); addressTextField.setVisible(true);
addressTextField.setManaged(true); addressTextField.setManaged(true);
amountLabel.setVisible(true);
amountTextField.setManaged(true);
GridPane.setMargin(generateNewAddressButton, new Insets(15, 0, 0, 0)); GridPane.setMargin(generateNewAddressButton, new Insets(15, 0, 0, 0));
addressTextField.setAddress(address); addressTextField.setAddress(address);
updateQRCode();
}
private void updateQRCode() {
if (addressTextField.getAddress() != null && !addressTextField.getAddress().isEmpty()) {
final byte[] imageBytes = QRCode final byte[] imageBytes = QRCode
.from(getBitcoinURI()) .from(getBitcoinURI())
.withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border .withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border
@ -238,7 +269,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
.toByteArray(); .toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes)); Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage); qrCodeImageView.setImage(qrImage);
}
} }
private void openBlockExplorer(DepositListItem item) { private void openBlockExplorer(DepositListItem item) {
@ -258,10 +289,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() { private void updateList() {
depositAddresses.clear(); observableList.clear();
walletService.getSavingsAddressEntryList().stream() walletService.getSavingsAddressEntryList().stream()
.forEach(e -> depositAddresses.add(new DepositListItem(e, walletService, formatter))); .forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter)));
table.setItems(depositAddresses);
} }
@ -269,9 +299,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
// ColumnCellFactories // ColumnCellFactories
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setStatusColumnCellFactory() { private void setUsageColumnCellFactory() {
statusColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); usageColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
statusColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>, usageColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>,
TableCell<DepositListItem, DepositListItem>>() { TableCell<DepositListItem, DepositListItem>>() {
@Override @Override
@ -283,7 +313,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
public void updateItem(final DepositListItem item, boolean empty) { public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (item != null && !empty) {
setGraphic(new Label(item.getStatus())); setGraphic(new Label(item.getUsage()));
} else { } else {
setGraphic(null); setGraphic(null);
} }
@ -364,6 +394,33 @@ public class DepositView extends ActivatableView<VBox, Void> {
}); });
} }
private void setBalanceColumnCellFactory() {
balanceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
balanceColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>,
TableCell<DepositListItem, DepositListItem>>() {
@Override
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
DepositListItem> column) {
return new TableCell<DepositListItem, DepositListItem>() {
@Override
public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (!textProperty().isBound())
textProperty().bind(item.balanceProperty());
} else {
textProperty().unbind();
setText("");
}
}
};
}
});
}
private void setConfidenceColumnCellFactory() { private void setConfidenceColumnCellFactory() {
confidenceColumn.setCellValueFactory((addressListItem) -> confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue())); new ReadOnlyObjectWrapper<>(addressListItem.getValue()));

View file

@ -142,4 +142,9 @@ public class ReservedListItem {
public String getFundsInfo() { public String getFundsInfo() {
return fundsInfo; return fundsInfo;
} }
public Tradable getTradable() {
return tradable;
}
} }

View file

@ -18,7 +18,6 @@
--> -->
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.reserved.ReservedView" <VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.reserved.ReservedView"
@ -27,15 +26,11 @@
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"/> <TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"/>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="260"/> <TableColumn text="Details" fx:id="detailsColumn" minWidth="260"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="320"> <TableColumn text="Address" fx:id="addressColumn" minWidth="320"/>
<cellValueFactory>
<PropertyValueFactory property="addressString"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="110"/> <TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="110"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -38,6 +38,7 @@ import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -53,7 +54,7 @@ import java.util.stream.Stream;
@FxmlView @FxmlView
public class ReservedView extends ActivatableView<VBox, Void> { public class ReservedView extends ActivatableView<VBox, Void> {
@FXML @FXML
TableView<ReservedListItem> table; TableView<ReservedListItem> tableView;
@FXML @FXML
TableColumn<ReservedListItem, ReservedListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, confidenceColumn; TableColumn<ReservedListItem, ReservedListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, confidenceColumn;
@ -64,7 +65,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
private final BSFormatter formatter; private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final ObservableList<ReservedListItem> reservedAddresses = FXCollections.observableArrayList(); private final ObservableList<ReservedListItem> observableList = FXCollections.observableArrayList();
private final SortedList<ReservedListItem> sortedList = new SortedList<>(observableList);
private BalanceListener balanceListener; private BalanceListener balanceListener;
@ -87,13 +89,21 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@Override @Override
public void initialize() { public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No funds are reserved in open offers or trades")); tableView.setPlaceholder(new Label("No funds are reserved in open offers or trades"));
setDateColumnCellFactory(); setDateColumnCellFactory();
setDetailsColumnCellFactory(); setDetailsColumnCellFactory();
setAddressColumnCellFactory(); setAddressColumnCellFactory();
setBalanceColumnCellFactory(); setBalanceColumnCellFactory();
table.getSortOrder().add(dateColumn);
addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString()));
detailsColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId()));
balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance()));
dateColumn.setComparator((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()));
tableView.getSortOrder().add(dateColumn);
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
balanceListener = new BalanceListener() { balanceListener = new BalanceListener() {
@Override @Override
public void onBalanceChanged(Coin balance, Transaction tx) { public void onBalanceChanged(Coin balance, Transaction tx) {
@ -104,6 +114,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@Override @Override
protected void activate() { protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList(); updateList();
walletService.addBalanceListener(balanceListener); walletService.addBalanceListener(balanceListener);
@ -111,7 +123,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@Override @Override
protected void deactivate() { protected void deactivate() {
reservedAddresses.forEach(ReservedListItem::cleanup); sortedList.comparatorProperty().unbind();
observableList.forEach(ReservedListItem::cleanup);
walletService.removeBalanceListener(balanceListener); walletService.removeBalanceListener(balanceListener);
} }
@ -121,13 +134,12 @@ public class ReservedView extends ActivatableView<VBox, Void> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() { private void updateList() {
reservedAddresses.forEach(ReservedListItem::cleanup); observableList.forEach(ReservedListItem::cleanup);
reservedAddresses.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) observableList.clear();
observableList.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
.filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID)
.map(tradable -> new ReservedListItem(tradable, walletService.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter)) .map(tradable -> new ReservedListItem(tradable, walletService.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter))
.collect(Collectors.toList())); .collect(Collectors.toList()));
reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()));
table.setItems(reservedAddresses);
} }
private void openBlockExplorer(ReservedListItem item) { private void openBlockExplorer(ReservedListItem item) {

View file

@ -24,24 +24,24 @@ import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.trade.Tradable; import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.OpenOffer; import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import org.bitcoinj.core.*; import org.bitcoinj.core.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Optional; import java.util.Optional;
public class TransactionsListItem { public class TransactionsListItem {
private final Logger log = LoggerFactory.getLogger(this.getClass()); private final Logger log = LoggerFactory.getLogger(this.getClass());
private final StringProperty date = new SimpleStringProperty();
private final StringProperty amount = new SimpleStringProperty(); private String date;
private final String txId; private final String txId;
private final WalletService walletService; private final WalletService walletService;
private final ConfidenceProgressIndicator progressIndicator; private final ConfidenceProgressIndicator progressIndicator;
private final Tooltip tooltip; private final Tooltip tooltip;
private boolean isInternal;
@Nullable
private Tradable tradable; private Tradable tradable;
private String details; private String details;
private String addressString; private String addressString;
@ -49,18 +49,19 @@ public class TransactionsListItem {
private TxConfidenceListener txConfidenceListener; private TxConfidenceListener txConfidenceListener;
private boolean received; private boolean received;
private boolean detailsAvailable; private boolean detailsAvailable;
private Coin amountAsCoin = Coin.ZERO;
private BSFormatter formatter;
public TransactionsListItem(Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) { public TransactionsListItem(Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
this.formatter = formatter;
txId = transaction.getHashAsString(); txId = transaction.getHashAsString();
this.walletService = walletService; this.walletService = walletService;
Coin valueSentToMe = transaction.getValueSentToMe(walletService.getWallet()); Coin valueSentToMe = transaction.getValueSentToMe(walletService.getWallet());
Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet()); Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet());
Coin amountAsCoin;
Address address = null; Address address = null;
if (valueSentToMe.isZero()) { if (valueSentToMe.isZero()) {
amountAsCoin = valueSentFromMe; amountAsCoin = valueSentFromMe.multiply(-1);
amount.set("-" + formatter.formatCoin(amountAsCoin));
for (TransactionOutput transactionOutput : transaction.getOutputs()) { for (TransactionOutput transactionOutput : transaction.getOutputs()) {
if (!transactionOutput.isMine(walletService.getWallet())) { if (!transactionOutput.isMine(walletService.getWallet())) {
@ -75,7 +76,7 @@ public class TransactionsListItem {
} }
} else if (valueSentFromMe.isZero()) { } else if (valueSentFromMe.isZero()) {
amountAsCoin = valueSentToMe; amountAsCoin = valueSentToMe;
amount.set(formatter.formatCoin(amountAsCoin));
direction = "Received with:"; direction = "Received with:";
received = true; received = true;
@ -88,9 +89,8 @@ public class TransactionsListItem {
} }
} }
} }
} else { } else/* if (tradableOptional.isPresent())*/ {
amountAsCoin = valueSentToMe.subtract(valueSentFromMe); amountAsCoin = valueSentToMe.subtract(valueSentFromMe);
amount.set(formatter.formatCoin(amountAsCoin));
boolean outgoing = false; boolean outgoing = false;
for (TransactionOutput transactionOutput : transaction.getOutputs()) { for (TransactionOutput transactionOutput : transaction.getOutputs()) {
if (!transactionOutput.isMine(walletService.getWallet())) { if (!transactionOutput.isMine(walletService.getWallet())) {
@ -107,7 +107,25 @@ public class TransactionsListItem {
direction = "Sent to:"; direction = "Sent to:";
received = false; received = false;
} }
} /*else {
// savings wallet tx
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
if (transactionOutput.isMine(walletService.getWallet())) {
if (transactionOutput.getScriptPubKey().isSentToAddress() ||
transactionOutput.getScriptPubKey().isPayToScriptHash()) {
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getWallet().getParams());
addressString = address.toString();
amountAsCoin = transactionOutput.getValue().multiply(-1);
} }
}
}
direction = "Transferred to:";
received = false;
isInternal = true;
details = "Change output";
}*/
if (tradableOptional.isPresent()) { if (tradableOptional.isPresent()) {
@ -141,11 +159,11 @@ public class TransactionsListItem {
} else { } else {
if (amountAsCoin.isZero()) if (amountAsCoin.isZero())
details = "No refund from dispute"; details = "No refund from dispute";
else else if (!isInternal)
details = received ? "Received funds" : "Withdrawn from wallet"; details = received ? "Received funds" : "Withdrawn from wallet";
} }
date.set(formatter.formatDateTime(transaction.getUpdateTime())); date = formatter.formatDateTime(transaction.getUpdateTime());
// confidence // confidence
progressIndicator = new ConfidenceProgressIndicator(); progressIndicator = new ConfidenceProgressIndicator();
@ -202,14 +220,20 @@ public class TransactionsListItem {
return progressIndicator; return progressIndicator;
} }
public final StringProperty dateProperty() { public final String getDate() {
return this.date; return date;
} }
public final StringProperty amountProperty() {
return this.amount; public String getAmount() {
return formatter.formatCoin(amountAsCoin);
} }
public Coin getAmountAsCoin() {
return amountAsCoin;
}
public String getAddressString() { public String getAddressString() {
return addressString; return addressString;
} }
@ -218,6 +242,10 @@ public class TransactionsListItem {
return direction; return direction;
} }
public boolean isInternal() {
return isInternal;
}
public String getTxId() { public String getTxId() {
return txId; return txId;
} }
@ -234,6 +262,7 @@ public class TransactionsListItem {
return detailsAvailable; return detailsAvailable;
} }
@Nullable
public Tradable getTradable() { public Tradable getTradable() {
return tradable; return tradable;
} }

View file

@ -18,7 +18,6 @@
--> -->
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.transactions.TransactionsView" <VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.transactions.TransactionsView"
@ -26,22 +25,13 @@
<padding> <padding>
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"> <TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"/>
<cellValueFactory>
<PropertyValueFactory property="date"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="220" maxWidth="220"/> <TableColumn text="Details" fx:id="detailsColumn" minWidth="220" maxWidth="220"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="260"/> <TableColumn text="Address" fx:id="addressColumn" minWidth="260"/>
<TableColumn text="Transaction" fx:id="transactionColumn" minWidth="180"/> <TableColumn text="Transaction" fx:id="transactionColumn" minWidth="180"/>
<TableColumn text="Amount (BTC)" fx:id="amountColumn" minWidth="110" maxWidth="110"> <TableColumn text="Amount (BTC)" fx:id="amountColumn" minWidth="110" maxWidth="110"/>
<cellValueFactory>
<PropertyValueFactory property="amount"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="110" maxWidth="110"/> <TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="110" maxWidth="110"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -40,6 +40,7 @@ import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -58,12 +59,12 @@ import java.util.stream.Stream;
public class TransactionsView extends ActivatableView<VBox, Void> { public class TransactionsView extends ActivatableView<VBox, Void> {
@FXML @FXML
TableView<TransactionsListItem> table; TableView<TransactionsListItem> tableView;
@FXML @FXML
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, typeColumn, TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, confidenceColumn;
confidenceColumn;
private final ObservableList<TransactionsListItem> transactionsListItems = FXCollections.observableArrayList(); private final ObservableList<TransactionsListItem> observableList = FXCollections.observableArrayList();
private final SortedList<TransactionsListItem> sortedList = new SortedList<>(observableList);
private final WalletService walletService; private final WalletService walletService;
private final TradeManager tradeManager; private final TradeManager tradeManager;
@ -102,13 +103,31 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override @Override
public void initialize() { public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No transactions available")); tableView.setPlaceholder(new Label("No transactions available"));
setDateColumnCellFactory();
setDetailsColumnCellFactory(); setDetailsColumnCellFactory();
setAddressColumnCellFactory(); setAddressColumnCellFactory();
setTransactionColumnCellFactory(); setTransactionColumnCellFactory();
setAmountColumnCellFactory();
setConfidenceColumnCellFactory(); setConfidenceColumnCellFactory();
table.getSortOrder().add(dateColumn);
dateColumn.setComparator((o1, o2) -> o1.getDate().compareTo(o2.getDate()));
detailsColumn.setComparator((o1, o2) -> {
String id1 = o1.getTradable() != null ? o1.getTradable().getId() : o1.getDetails();
String id2 = o2.getTradable() != null ? o2.getTradable().getId() : o2.getDetails();
return id1.compareTo(id2);
});
addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString()));
transactionColumn.setComparator((o1, o2) -> o1.getTxId().compareTo(o2.getTxId()));
amountColumn.setComparator((o1, o2) -> o1.getAmountAsCoin().compareTo(o2.getAmountAsCoin()));
confidenceColumn.setComparator((o1, o2) -> Double.valueOf(o1.getProgressIndicator().getProgress())
.compareTo(o2.getProgressIndicator().getProgress()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
walletEventListener = new WalletEventListener() { walletEventListener = new WalletEventListener() {
@Override @Override
@ -149,13 +168,17 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override @Override
protected void activate() { protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList(); updateList();
walletService.getWallet().addEventListener(walletEventListener); walletService.getWallet().addEventListener(walletEventListener);
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
transactionsListItems.forEach(TransactionsListItem::cleanup); sortedList.comparatorProperty().unbind();
observableList.forEach(TransactionsListItem::cleanup);
walletService.getWallet().removeEventListener(walletEventListener); walletService.getWallet().removeEventListener(walletEventListener);
} }
@ -170,7 +193,40 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream()); Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream());
Set<Tradable> all = concat3.collect(Collectors.toSet()); Set<Tradable> all = concat3.collect(Collectors.toSet());
List<TransactionsListItem> listItems = walletService.getWallet().getRecentTransactions(1000, true).stream() Set<Transaction> transactions = walletService.getWallet().getTransactions(true);
/* List<TransactionsListItem> transactionsListItems = new ArrayList<>();
for (Transaction transaction : transactions) {
Optional<Tradable> tradableOptional = all.stream()
.filter(tradable -> {
String txId = transaction.getHashAsString();
if (tradable instanceof OpenOffer)
return tradable.getOffer().getOfferFeePaymentTxID().equals(txId);
else if (tradable instanceof Trade) {
Trade trade = (Trade) tradable;
boolean isTakeOfferFeeTx = txId.equals(trade.getTakeOfferFeeTxId());
boolean isOfferFeeTx = trade.getOffer() != null &&
txId.equals(trade.getOffer().getOfferFeePaymentTxID());
boolean isDepositTx = trade.getDepositTx() != null &&
trade.getDepositTx().getHashAsString().equals(txId);
boolean isPayoutTx = trade.getPayoutTx() != null &&
trade.getPayoutTx().getHashAsString().equals(txId);
boolean isDisputedPayoutTx = disputeManager.getDisputesAsObservableList().stream()
.filter(dispute -> txId.equals(dispute.getDisputePayoutTxId()) &&
tradable.getId().equals(dispute.getTradeId()))
.findAny()
.isPresent();
return isTakeOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx || isDisputedPayoutTx;
} else
return false;
})
.findAny();
// if (tradableOptional.isPresent())
transactionsListItems.add(new TransactionsListItem(transaction, walletService, tradableOptional, formatter));
}*/
List<TransactionsListItem> transactionsListItems = transactions.stream()
.map(transaction -> { .map(transaction -> {
Optional<Tradable> tradableOptional = all.stream() Optional<Tradable> tradableOptional = all.stream()
.filter(tradable -> { .filter(tradable -> {
@ -202,10 +258,15 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
/* List<TransactionsListItem> usedSavingWalletEntries = walletService.getUsedSavingWalletTransactions()
.stream()
.map(transaction -> new TransactionsListItem(transaction, walletService, Optional.<Tradable>empty(), formatter))
.collect(Collectors.toList());
transactionsListItems.addAll(usedSavingWalletEntries);*/
// are sorted by getRecentTransactions // are sorted by getRecentTransactions
transactionsListItems.forEach(TransactionsListItem::cleanup); observableList.forEach(TransactionsListItem::cleanup);
transactionsListItems.setAll(listItems); observableList.setAll(transactionsListItems);
table.setItems(transactionsListItems);
} }
private void openBlockExplorer(TransactionsListItem item) { private void openBlockExplorer(TransactionsListItem item) {
@ -232,6 +293,33 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
// ColumnCellFactories // ColumnCellFactories
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setDateColumnCellFactory() {
dateColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<TransactionsListItem, TransactionsListItem>, TableCell<TransactionsListItem,
TransactionsListItem>>() {
@Override
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
TransactionsListItem> column) {
return new TableCell<TransactionsListItem, TransactionsListItem>() {
@Override
public void updateItem(final TransactionsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getDate());
} else {
setText("");
}
}
};
}
});
}
private void setDetailsColumnCellFactory() { private void setDetailsColumnCellFactory() {
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
detailsColumn.setCellFactory( detailsColumn.setCellFactory(
@ -289,7 +377,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
if (item != null && !empty) { if (item != null && !empty) {
String addressString = item.getAddressString(); String addressString = item.getAddressString();
field = new AddressWithIconAndDirection(item.getDirection(), addressString, field = new AddressWithIconAndDirection(item.getDirection(), addressString,
AwesomeIcon.EXTERNAL_LINK, item.getReceived()); AwesomeIcon.EXTERNAL_LINK, item.getReceived(), item.isInternal());
field.setOnAction(event -> openBlockExplorer(item)); field.setOnAction(event -> openBlockExplorer(item));
field.setTooltip(new Tooltip("Open external blockchain explorer for " + field.setTooltip(new Tooltip("Open external blockchain explorer for " +
"address: " + addressString)); "address: " + addressString));
@ -339,6 +427,33 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
}); });
} }
private void setAmountColumnCellFactory() {
amountColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
amountColumn.setCellFactory(
new Callback<TableColumn<TransactionsListItem, TransactionsListItem>, TableCell<TransactionsListItem,
TransactionsListItem>>() {
@Override
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
TransactionsListItem> column) {
return new TableCell<TransactionsListItem, TransactionsListItem>() {
@Override
public void updateItem(final TransactionsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmount());
} else {
setText("");
}
}
};
}
});
}
private void setConfidenceColumnCellFactory() { private void setConfidenceColumnCellFactory() {
confidenceColumn.setCellValueFactory((addressListItem) -> confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue())); new ReadOnlyObjectWrapper<>(addressListItem.getValue()));

View file

@ -19,7 +19,6 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.withdrawal.WithdrawalView" <VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.withdrawal.WithdrawalView"
spacing="10" xmlns:fx="http://javafx.com/fxml"> spacing="10" xmlns:fx="http://javafx.com/fxml">
@ -27,17 +26,11 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Select" fx:id="selectColumn" minWidth="60" maxWidth="60" sortable="false"/> <TableColumn text="Select" fx:id="selectColumn" minWidth="60" maxWidth="60" sortable="false"/>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"/> <TableColumn text="Address" fx:id="addressColumn" minWidth="320"/>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="160"/> <TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="310" maxWidth="310"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="320">
<cellValueFactory>
<PropertyValueFactory property="addressString"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="110"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -20,7 +20,6 @@ package io.bitsquare.gui.main.funds.withdrawal;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
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.AddressEntry;
import io.bitsquare.btc.Restrictions; import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
@ -46,6 +45,7 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -59,10 +59,7 @@ import org.jetbrains.annotations.NotNull;
import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.KeyParameter;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Date; import java.util.*;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -72,11 +69,11 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML @FXML
Button withdrawButton; Button withdrawButton;
@FXML @FXML
TableView<WithdrawalListItem> table; TableView<WithdrawalListItem> tableView;
@FXML @FXML
TextField withdrawFromTextField, withdrawToTextField, amountTextField; TextField withdrawFromTextField, withdrawToTextField, amountTextField;
@FXML @FXML
TableColumn<WithdrawalListItem, WithdrawalListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, selectColumn; TableColumn<WithdrawalListItem, WithdrawalListItem> addressColumn, balanceColumn, selectColumn;
private final WalletService walletService; private final WalletService walletService;
private final TradeManager tradeManager; private final TradeManager tradeManager;
@ -89,7 +86,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private final WalletPasswordWindow walletPasswordWindow; private final WalletPasswordWindow walletPasswordWindow;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final ObservableList<WithdrawalListItem> fundedAddresses = FXCollections.observableArrayList(); private final ObservableList<WithdrawalListItem> observableList = FXCollections.observableArrayList();
private final SortedList<WithdrawalListItem> sortedList = new SortedList<>(observableList);
private Set<WithdrawalListItem> selectedItems = new HashSet<>(); private Set<WithdrawalListItem> selectedItems = new HashSet<>();
private BalanceListener balanceListener; private BalanceListener balanceListener;
private Set<String> fromAddresses; private Set<String> fromAddresses;
@ -121,15 +119,18 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@Override @Override
public void initialize() { public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No funds for withdrawal are available")); tableView.setPlaceholder(new Label("No funds for withdrawal are available"));
setDateColumnCellFactory(); tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
setDetailsColumnCellFactory();
setAddressColumnCellFactory(); setAddressColumnCellFactory();
setBalanceColumnCellFactory(); setBalanceColumnCellFactory();
setSelectColumnCellFactory(); setSelectColumnCellFactory();
table.getSortOrder().add(dateColumn);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString()));
balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance()));
balanceColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(balanceColumn);
balanceListener = new BalanceListener() { balanceListener = new BalanceListener() {
@Override @Override
@ -141,6 +142,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@Override @Override
protected void activate() { protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList(); updateList();
reset(); reset();
@ -152,7 +155,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@Override @Override
protected void deactivate() { protected void deactivate() {
fundedAddresses.forEach(WithdrawalListItem::cleanup); sortedList.comparatorProperty().unbind();
observableList.forEach(WithdrawalListItem::cleanup);
withdrawButton.disableProperty().unbind(); withdrawButton.disableProperty().unbind();
walletService.removeBalanceListener(balanceListener); walletService.removeBalanceListener(balanceListener);
} }
@ -174,6 +178,14 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
} else { } else {
log.error("onWithdraw transaction is null"); log.error("onWithdraw transaction is null");
} }
List<Trade> trades = new ArrayList<>(tradeManager.getTrades());
trades.stream()
.filter(trade -> trade.getState().getPhase() == Trade.Phase.PAYOUT_PAID)
.forEach(trade -> {
if (walletService.getBalanceForAddress(walletService.getTradeAddressEntry(trade.getId()).getAddress()).isZero())
tradeManager.addTradeToClosedTrades(trade);
});
} }
@Override @Override
@ -283,32 +295,16 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private void updateList() { private void updateList() {
Set<String> reservedTrades = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) Set<String> reservedTrades = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
.filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID)
.map(tradable -> tradable.getOffer().getId()) .map(tradable -> tradable.getOffer().getId())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
fundedAddresses.forEach(WithdrawalListItem::cleanup); observableList.forEach(WithdrawalListItem::cleanup);
fundedAddresses.setAll(walletService.getAddressEntryList().stream() observableList.setAll(walletService.getAddressEntryList().stream()
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive()) .filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
.filter(e -> !reservedTrades.contains(e.getOfferId())) .filter(e -> !reservedTrades.contains(e.getOfferId()))
.map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, formatter)) .map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, formatter))
.collect(Collectors.toList())); .collect(Collectors.toList()));
fundedAddresses.sort((o1, o2) -> {
Optional<Tradable> tradable1 = getTradable(o1);
Optional<Tradable> tradable2 = getTradable(o2);
// if we dont have a date we set it to now as it is likely a recent funding tx
// TODO get tx date from wallet instead
Date date1 = new Date();
Date date2 = new Date();
if (tradable1.isPresent())
date1 = tradable1.get().getDate();
if (tradable2.isPresent())
date2 = tradable2.get().getDate();
return date2.compareTo(date1);
});
table.setItems(fundedAddresses);
} }
private void doWithdraw(Coin amount, FutureCallback<Transaction> callback) { private void doWithdraw(Coin amount, FutureCallback<Transaction> callback) {
@ -335,7 +331,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private void reset() { private void reset() {
selectedItems = new HashSet<>(); selectedItems = new HashSet<>();
table.getSelectionModel().clearSelection(); tableView.getSelectionModel().clearSelection();
withdrawFromTextField.setText(""); withdrawFromTextField.setText("");
withdrawFromTextField.setPromptText("Select a source address from the table"); withdrawFromTextField.setPromptText("Select a source address from the table");
@ -348,7 +344,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("mi8k5f9L972VgDaT4LgjAhriC9hHEPL7EW"); withdrawToTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi");
} }
private Optional<Tradable> getTradable(WithdrawalListItem item) { private Optional<Tradable> getTradable(WithdrawalListItem item) {
@ -374,92 +370,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
// ColumnCellFactories // ColumnCellFactories
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setDateColumnCellFactory() {
dateColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
dateColumn.setCellFactory(new Callback<TableColumn<WithdrawalListItem, WithdrawalListItem>,
TableCell<WithdrawalListItem, WithdrawalListItem>>() {
@Override
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
WithdrawalListItem> column) {
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
@Override
public void updateItem(final WithdrawalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (getTradable(item).isPresent())
setText(formatter.formatDateTime(getTradable(item).get().getDate()));
else
setText("No date available");
} else {
setText("");
}
}
};
}
});
}
private void setDetailsColumnCellFactory() {
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
detailsColumn.setCellFactory(new Callback<TableColumn<WithdrawalListItem, WithdrawalListItem>,
TableCell<WithdrawalListItem, WithdrawalListItem>>() {
@Override
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
WithdrawalListItem> column) {
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
private HyperlinkWithIcon field;
@Override
public void updateItem(final WithdrawalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
Optional<Tradable> tradableOptional = getTradable(item);
if (tradableOptional.isPresent()) {
AddressEntry addressEntry = item.getAddressEntry();
String details;
if (addressEntry.getContext() == AddressEntry.Context.TRADE) {
String prefix;
Tradable tradable = tradableOptional.get();
if (tradable instanceof Trade)
prefix = "Trade ID: ";
else if (tradable instanceof OpenOffer)
prefix = "Offer ID: ";
else
prefix = "";
details = prefix + addressEntry.getShortOfferId();
} else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) {
details = "Arbitration fee";
} else {
details = "-";
}
field = new HyperlinkWithIcon(details, AwesomeIcon.INFO_SIGN);
field.setOnAction(event -> openDetailPopup(item));
field.setTooltip(new Tooltip("Open popup for details"));
setGraphic(field);
} else if (item.getAddressEntry().getContext() == AddressEntry.Context.ARBITRATOR) {
setGraphic(new Label("Arbitrators fee"));
} else {
setGraphic(new Label("No details available"));
}
} else {
setGraphic(null);
if (field != null)
field.setOnAction(null);
}
}
};
}
});
}
private void setAddressColumnCellFactory() { private void setAddressColumnCellFactory() {
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
addressColumn.setCellFactory( addressColumn.setCellFactory(

View file

@ -23,6 +23,7 @@ import io.bitsquare.gui.components.TableGroupHeadline;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.CurrencyUtil;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TableCell; import javafx.scene.control.TableCell;
@ -38,7 +39,8 @@ import javax.inject.Inject;
public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, MarketsStatisticViewModel> { public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, MarketsStatisticViewModel> {
private final BSFormatter formatter; private final BSFormatter formatter;
private final int gridRow = 0; private final int gridRow = 0;
private TableView<MarketStatisticItem> statisticsTableView; private TableView<MarketStatisticItem> tableView;
private SortedList<MarketStatisticItem> sortedList;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -58,29 +60,44 @@ public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, Mar
GridPane.setMargin(header, new Insets(0, -10, -10, -10)); GridPane.setMargin(header, new Insets(0, -10, -10, -10));
root.getChildren().add(header); root.getChildren().add(header);
statisticsTableView = new TableView<>(); tableView = new TableView<>();
GridPane.setRowIndex(statisticsTableView, gridRow); GridPane.setRowIndex(tableView, gridRow);
GridPane.setMargin(statisticsTableView, new Insets(20, -10, -10, -10)); GridPane.setMargin(tableView, new Insets(20, -10, -10, -10));
GridPane.setVgrow(statisticsTableView, Priority.ALWAYS); GridPane.setVgrow(tableView, Priority.ALWAYS);
GridPane.setHgrow(statisticsTableView, Priority.ALWAYS); GridPane.setHgrow(tableView, Priority.ALWAYS);
root.getChildren().add(statisticsTableView); root.getChildren().add(tableView);
statisticsTableView.getColumns().add(getCurrencyColumn());
statisticsTableView.getColumns().add(getNumberOfOffersColumn());
statisticsTableView.getColumns().add(getTotalAmountColumn());
statisticsTableView.getColumns().add(getSpreadColumn());
statisticsTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("Currently there is no data available"); Label placeholder = new Label("Currently there is no data available");
placeholder.setWrapText(true); placeholder.setWrapText(true);
statisticsTableView.setPlaceholder(placeholder); tableView.setPlaceholder(placeholder);
TableColumn<MarketStatisticItem, MarketStatisticItem> currencyColumn = getCurrencyColumn();
tableView.getColumns().add(currencyColumn);
TableColumn<MarketStatisticItem, MarketStatisticItem> numberOfOffersColumn = getNumberOfOffersColumn();
tableView.getColumns().add(numberOfOffersColumn);
TableColumn<MarketStatisticItem, MarketStatisticItem> totalAmountColumn = getTotalAmountColumn();
tableView.getColumns().add(totalAmountColumn);
TableColumn<MarketStatisticItem, MarketStatisticItem> spreadColumn = getSpreadColumn();
tableView.getColumns().add(spreadColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
currencyColumn.setComparator((o1, o2) -> o1.currencyCode.compareTo(o2.currencyCode));
numberOfOffersColumn.setComparator((o1, o2) -> Integer.valueOf(o1.numberOfOffers).compareTo(o2.numberOfOffers));
totalAmountColumn.setComparator((o1, o2) -> o1.totalAmount.compareTo(o2.totalAmount));
spreadColumn.setComparator((o1, o2) -> o1.spread != null && o2.spread != null ? o1.spread.compareTo(o2.spread) : 0);
tableView.getSortOrder().add(numberOfOffersColumn);
} }
@Override @Override
protected void activate() { protected void activate() {
statisticsTableView.setItems(model.marketStatisticItems); sortedList = new SortedList<>(model.marketStatisticItems);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
sortedList.comparatorProperty().unbind();
} }

View file

@ -28,6 +28,7 @@ import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableDataModel; import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.overlays.notifications.Notification; import io.bitsquare.gui.main.overlays.notifications.Notification;
import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.main.overlays.popups.Popup;
@ -64,13 +65,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
class CreateOfferDataModel extends ActivatableDataModel { class CreateOfferDataModel extends ActivatableDataModel {
private final OpenOfferManager openOfferManager; private final OpenOfferManager openOfferManager;
private final WalletService walletService; final WalletService walletService;
private final TradeWalletService tradeWalletService; private final TradeWalletService tradeWalletService;
private final Preferences preferences; private final Preferences preferences;
private final User user; private final User user;
private final KeyRing keyRing; private final KeyRing keyRing;
private final P2PService p2PService; private final P2PService p2PService;
private final PriceFeed priceFeed; private final PriceFeed priceFeed;
private Navigation navigation;
private final WalletPasswordWindow walletPasswordWindow; private final WalletPasswordWindow walletPasswordWindow;
private final BlockchainService blockchainService; private final BlockchainService blockchainService;
private final BSFormatter formatter; private final BSFormatter formatter;
@ -98,12 +100,16 @@ class CreateOfferDataModel extends ActivatableDataModel {
final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>(); final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>(); final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> missingCoin = new SimpleObjectProperty<>(Coin.ZERO);
final ObjectProperty<Coin> balance = new SimpleObjectProperty<>();
final ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList(); final ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList();
PaymentAccount paymentAccount; PaymentAccount paymentAccount;
private boolean isTabSelected; private boolean isTabSelected;
private Notification walletFundedNotification; private Notification walletFundedNotification;
boolean useSavingsWallet;
Coin totalAvailableBalance;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -113,6 +119,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
@Inject @Inject
CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService, CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService,
Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeed priceFeed, Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeed priceFeed,
Navigation navigation,
WalletPasswordWindow walletPasswordWindow, BlockchainService blockchainService, BSFormatter formatter) { WalletPasswordWindow walletPasswordWindow, BlockchainService blockchainService, BSFormatter formatter) {
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
this.walletService = walletService; this.walletService = walletService;
@ -122,6 +129,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
this.keyRing = keyRing; this.keyRing = keyRing;
this.p2PService = p2PService; this.p2PService = p2PService;
this.priceFeed = priceFeed; this.priceFeed = priceFeed;
this.navigation = navigation;
this.walletPasswordWindow = walletPasswordWindow; this.walletPasswordWindow = walletPasswordWindow;
this.blockchainService = blockchainService; this.blockchainService = blockchainService;
this.formatter = formatter; this.formatter = formatter;
@ -135,7 +143,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
balanceListener = new BalanceListener(getAddressEntry().getAddress()) { balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
@Override @Override
public void onBalanceChanged(Coin balance, Transaction tx) { public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance); updateBalance();
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) { if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString()); SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
@ -172,7 +180,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
addListeners(); addListeners();
paymentAccounts.setAll(user.getPaymentAccounts()); paymentAccounts.setAll(user.getPaymentAccounts());
updateBalance(walletService.getBalanceForAddress(getAddressEntry().getAddress())); calculateTotalToPay();
updateBalance();
if (direction == Offer.Direction.BUY) if (direction == Offer.Direction.BUY)
calculateTotalToPay(); calculateTotalToPay();
@ -290,7 +299,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
} }
private void doPlaceOffer(Offer offer, TransactionResultHandler resultHandler) { private void doPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
openOfferManager.placeOffer(offer, resultHandler); openOfferManager.placeOffer(offer, totalToPayAsCoin.get().subtract(offerFeeAsCoin), useSavingsWallet, resultHandler);
} }
public void onPaymentAccountSelected(PaymentAccount paymentAccount) { public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
@ -311,6 +320,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
} }
} }
void useSavingsWalletForFunding() {
useSavingsWallet = true;
updateBalance();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Getters
@ -380,16 +394,30 @@ class CreateOfferDataModel extends ActivatableDataModel {
void calculateTotalToPay() { void calculateTotalToPay() {
if (securityDepositAsCoin != null) { if (securityDepositAsCoin != null) {
if (direction == Offer.Direction.BUY) Coin feeAndSecDeposit = offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin);
totalToPayAsCoin.set(offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin)); Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get());
Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDepositAndAmount;
totalToPayAsCoin.set(required);
}
}
void updateBalance() {
Coin tradeWalletBalance = walletService.getBalanceForAddress(getAddressEntry().getAddress());
if (useSavingsWallet) {
Coin savingWalletBalance = walletService.getSavingWalletBalance();
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
balance.set(totalToPayAsCoin.get());
else else
totalToPayAsCoin.set(offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get())); balance.set(totalAvailableBalance);
} } else {
balance.set(tradeWalletBalance);
} }
private void updateBalance(Coin balance) { missingCoin.set(totalToPayAsCoin.get().subtract(balance.get()));
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
isWalletFunded.set(isBalanceSufficient(balance.get()));
if (isWalletFunded.get()) { if (isWalletFunded.get()) {
walletService.removeBalanceListener(balanceListener); walletService.removeBalanceListener(balanceListener);
if (walletFundedNotification == null) { if (walletFundedNotification == null) {
@ -404,6 +432,10 @@ class CreateOfferDataModel extends ActivatableDataModel {
} }
} }
private boolean isBalanceSufficient(Coin balance) {
return totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0;
}
public Coin getOfferFeeAsCoin() { public Coin getOfferFeeAsCoin() {
return offerFeeAsCoin; return offerFeeAsCoin;
} }
@ -423,4 +455,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
public Preferences getPreferences() { public Preferences getPreferences() {
return preferences; return preferences;
} }
public void swapTradeToSavings() {
walletService.swapTradeToSavings(getOfferId());
}
} }

View file

@ -69,6 +69,7 @@ import org.jetbrains.annotations.NotNull;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*; import static io.bitsquare.gui.util.FormBuilder.*;
@ -85,13 +86,12 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private ImageView imageView; private ImageView imageView;
private AddressTextField addressTextField; private AddressTextField addressTextField;
private BalanceTextField balanceTextField; private BalanceTextField balanceTextField;
private ProgressIndicator spinner;
private TitledGroupBg payFundsPane; private TitledGroupBg payFundsPane;
private Button nextButton, cancelButton1, cancelButton2, placeOfferButton; private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, placeOfferButton;
private InputTextField amountTextField, minAmountTextField, priceTextField, volumeTextField; private InputTextField amountTextField, minAmountTextField, priceTextField, volumeTextField;
private TextField currencyTextField; private TextField currencyTextField;
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel, amountBtcLabel, priceCurrencyLabel, private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel, amountBtcLabel, priceCurrencyLabel,
volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, spinnerInfoLabel, currencyTextFieldLabel, volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, currencyTextFieldLabel,
currencyComboBoxLabel; currencyComboBoxLabel;
private TextFieldWithCopyIcon totalToPayTextField; private TextFieldWithCopyIcon totalToPayTextField;
private ComboBox<PaymentAccount> paymentAccountsComboBox; private ComboBox<PaymentAccount> paymentAccountsComboBox;
@ -108,7 +108,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener; private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
private ChangeListener<Boolean> showWarningAdjustedVolumeListener; private ChangeListener<Boolean> showWarningAdjustedVolumeListener;
private ChangeListener<String> errorMessageListener; private ChangeListener<String> errorMessageListener;
private ChangeListener<Boolean> isSpinnerVisibleListener;
private ChangeListener<Boolean> placeOfferCompletedListener; private ChangeListener<Boolean> placeOfferCompletedListener;
private ChangeListener<Coin> feeFromFundingTxListener; private ChangeListener<Coin> feeFromFundingTxListener;
private EventHandler<ActionEvent> paymentAccountsComboBoxSelectionHandler; private EventHandler<ActionEvent> paymentAccountsComboBoxSelectionHandler;
@ -118,6 +117,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private final Preferences preferences; private final Preferences preferences;
private ChangeListener<String> tradeCurrencyCodeListener; private ChangeListener<String> tradeCurrencyCodeListener;
private ImageView qrCodeImageView; private ImageView qrCodeImageView;
private ChangeListener<Coin> balanceListener;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -143,7 +143,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
createListeners(); createListeners();
balanceTextField.setup(model.address.get(), model.getFormatter()); balanceTextField.setFormatter(model.getFormatter());
balanceListener = (observable, oldValue, newValue) -> balanceTextField.setBalance(newValue);
paymentAccountsComboBox.setConverter(new StringConverter<PaymentAccount>() { paymentAccountsComboBox.setConverter(new StringConverter<PaymentAccount>() {
@Override @Override
public String toString(PaymentAccount paymentAccount) { public String toString(PaymentAccount paymentAccount) {
@ -173,8 +175,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
onPaymentAccountsComboBoxSelected(); onPaymentAccountsComboBoxSelected();
if (spinner != null && placeOfferButton.isVisible()) balanceTextField.setBalance(model.dataModel.balance.get());
spinner.setProgress(-1);
} }
@Override @Override
@ -183,9 +184,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
removeListeners(); removeListeners();
if (balanceTextField != null) if (balanceTextField != null)
balanceTextField.cleanup(); balanceTextField.cleanup();
if (spinner != null)
spinner.setProgress(0);
} }
@ -216,9 +214,14 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
// called form parent as the view does not get notified when the tab is closed // called form parent as the view does not get notified when the tab is closed
public void onClose() { public void onClose() {
// we use model.placeOfferCompleted to not react on close which was triggered by a successful placeOffer // we use model.placeOfferCompleted to not react on close which was triggered by a successful placeOffer
if (model.dataModel.isWalletFunded.get() && !model.placeOfferCompleted.get()) if (model.dataModel.balance.get().isPositive() && !model.placeOfferCompleted.get()) {
model.dataModel.swapTradeToSavings();
new Popup().information("You have already funds paid in.\n" + new Popup().information("You have already funds paid in.\n" +
"In the \"Funds/Available for withdrawal\" section you can withdraw those funds.").show(); "In the \"Funds/Available for withdrawal\" section you can withdraw those funds.")
.actionButtonText("Go to \"Funds/Available for withdrawal\"")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class))
.show();
}
} }
public void setCloseHandler(OfferView.CloseHandler closeHandler) { public void setCloseHandler(OfferView.CloseHandler closeHandler) {
@ -264,8 +267,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
currencyComboBox.setMouseTransparent(true); currencyComboBox.setMouseTransparent(true);
paymentAccountsComboBox.setMouseTransparent(true); paymentAccountsComboBox.setMouseTransparent(true);
spinner.setProgress(-1);
if (!BitsquareApp.DEV_MODE) { if (!BitsquareApp.DEV_MODE) {
String key = "securityDepositInfo"; String key = "securityDepositInfo";
new Popup().backgroundInfo("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" + new Popup().backgroundInfo("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" +
@ -310,6 +311,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
qrCodeImageView.setVisible(true); qrCodeImageView.setVisible(true);
balanceLabel.setVisible(true); balanceLabel.setVisible(true);
balanceTextField.setVisible(true); balanceTextField.setVisible(true);
fundFromSavingsWalletButton.setVisible(true);
fundFromExternalWalletButton.setVisible(true);
placeOfferButton.setVisible(true); placeOfferButton.setVisible(true);
cancelButton2.setVisible(true); cancelButton2.setVisible(true);
//root.requestFocus(); //root.requestFocus();
@ -392,7 +395,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
volumeTextField.promptTextProperty().bind(model.volumePromptLabel); volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
totalToPayTextField.textProperty().bind(model.totalToPay); totalToPayTextField.textProperty().bind(model.totalToPay);
addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin); addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
// Validation // Validation
amountTextField.validationResultProperty().bind(model.amountValidationResult); amountTextField.validationResultProperty().bind(model.amountValidationResult);
@ -403,10 +406,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
// buttons // buttons
placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled); placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled);
cancelButton2.disableProperty().bind(model.cancelButtonDisabled); cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
fundFromSavingsWalletButton.disableProperty().bind(model.dataModel.isWalletFunded);
spinner.visibleProperty().bind(model.isSpinnerVisible); fundFromExternalWalletButton.disableProperty().bind(model.dataModel.isWalletFunded);
spinnerInfoLabel.visibleProperty().bind(model.isSpinnerVisible);
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
// payment account // payment account
currencyComboBox.prefWidthProperty().bind(paymentAccountsComboBox.widthProperty()); currencyComboBox.prefWidthProperty().bind(paymentAccountsComboBox.widthProperty());
@ -438,9 +439,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
volumeTextField.validationResultProperty().unbind(); volumeTextField.validationResultProperty().unbind();
placeOfferButton.disableProperty().unbind(); placeOfferButton.disableProperty().unbind();
cancelButton2.disableProperty().unbind(); cancelButton2.disableProperty().unbind();
spinner.visibleProperty().unbind();
spinnerInfoLabel.visibleProperty().unbind();
spinnerInfoLabel.textProperty().unbind();
currencyComboBox.managedProperty().unbind(); currencyComboBox.managedProperty().unbind();
currencyComboBoxLabel.visibleProperty().unbind(); currencyComboBoxLabel.visibleProperty().unbind();
currencyComboBoxLabel.managedProperty().unbind(); currencyComboBoxLabel.managedProperty().unbind();
@ -496,7 +494,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
"Please try to restart you application and check your network connection to see if you can resolve the issue.") "Please try to restart you application and check your network connection to see if you can resolve the issue.")
.show(), 100, TimeUnit.MILLISECONDS); .show(), 100, TimeUnit.MILLISECONDS);
}; };
isSpinnerVisibleListener = (ov, oldValue, newValue) -> spinner.setProgress(newValue ? -1 : 0);
feeFromFundingTxListener = (observable, oldValue, newValue) -> { feeFromFundingTxListener = (observable, oldValue, newValue) -> {
log.debug("feeFromFundingTxListener " + newValue); log.debug("feeFromFundingTxListener " + newValue);
@ -514,6 +511,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
.closeButtonText("Close") .closeButtonText("Close")
.onClose(() -> { .onClose(() -> {
close(); close();
model.dataModel.swapTradeToSavings();
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
}) })
.show(); .show();
@ -558,6 +556,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void addListeners() { private void addListeners() {
model.tradeCurrencyCode.addListener(tradeCurrencyCodeListener); model.tradeCurrencyCode.addListener(tradeCurrencyCodeListener);
model.dataModel.balance.addListener(balanceListener);
// focus out // focus out
amountTextField.focusedProperty().addListener(amountFocusedListener); amountTextField.focusedProperty().addListener(amountFocusedListener);
@ -570,7 +569,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
model.showWarningInvalidFiatDecimalPlaces.addListener(showWarningInvalidFiatDecimalPlacesPlacesListener); model.showWarningInvalidFiatDecimalPlaces.addListener(showWarningInvalidFiatDecimalPlacesPlacesListener);
model.showWarningAdjustedVolume.addListener(showWarningAdjustedVolumeListener); model.showWarningAdjustedVolume.addListener(showWarningAdjustedVolumeListener);
model.errorMessage.addListener(errorMessageListener); model.errorMessage.addListener(errorMessageListener);
model.isSpinnerVisible.addListener(isSpinnerVisibleListener);
model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener); model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
model.placeOfferCompleted.addListener(placeOfferCompletedListener); model.placeOfferCompleted.addListener(placeOfferCompletedListener);
@ -582,6 +580,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void removeListeners() { private void removeListeners() {
model.tradeCurrencyCode.removeListener(tradeCurrencyCodeListener); model.tradeCurrencyCode.removeListener(tradeCurrencyCodeListener);
model.dataModel.balance.removeListener(balanceListener);
// focus out // focus out
amountTextField.focusedProperty().removeListener(amountFocusedListener); amountTextField.focusedProperty().removeListener(amountFocusedListener);
@ -594,7 +593,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
model.showWarningInvalidFiatDecimalPlaces.removeListener(showWarningInvalidFiatDecimalPlacesPlacesListener); model.showWarningInvalidFiatDecimalPlaces.removeListener(showWarningInvalidFiatDecimalPlacesPlacesListener);
model.showWarningAdjustedVolume.removeListener(showWarningAdjustedVolumeListener); model.showWarningAdjustedVolume.removeListener(showWarningAdjustedVolumeListener);
model.errorMessage.removeListener(errorMessageListener); model.errorMessage.removeListener(errorMessageListener);
model.isSpinnerVisible.removeListener(isSpinnerVisibleListener);
model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener); model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
model.placeOfferCompleted.removeListener(placeOfferCompletedListener); model.placeOfferCompleted.removeListener(placeOfferCompletedListener);
@ -698,7 +696,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
//UserThread.runAfter(() -> nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS); //UserThread.runAfter(() -> nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS);
cancelButton1 = tuple.second; cancelButton1 = tuple.second;
cancelButton1.setDefaultButton(false); cancelButton1.setDefaultButton(false);
cancelButton1.setOnAction(e -> close()); cancelButton1.setOnAction(e -> {
close();
model.dataModel.swapTradeToSavings();
});
cancelButton1.setId("cancel-button"); cancelButton1.setId("cancel-button");
GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0)); GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0));
@ -757,27 +758,47 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
balanceTextField = balanceTuple.second; balanceTextField = balanceTuple.second;
balanceTextField.setVisible(false); balanceTextField.setVisible(false);
Tuple3<Button, ProgressIndicator, Label> placeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, ""); Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, "Transfer from Bitsquare wallet", "Fund from external wallet");
placeOfferButton = placeOfferTuple.first; fundFromSavingsWalletButton = tuple.first;
fundFromSavingsWalletButton.setVisible(false);
fundFromSavingsWalletButton.setDefaultButton(false);
fundFromSavingsWalletButton.setOnAction(e -> model.useSavingsWalletForFunding());
fundFromExternalWalletButton = tuple.second;
fundFromExternalWalletButton.setVisible(false);
fundFromExternalWalletButton.setDefaultButton(false);
fundFromExternalWalletButton.setOnAction(e -> {
try {
Utilities.openURI(URI.create(getBitcoinURI()));
} catch (Exception ex) {
log.warn(ex.getMessage());
new Popup().warning("Opening a default bitcoin wallet application has failed. " +
"Perhaps you don't have one installed?").show();
}
});
placeOfferButton = addButton(gridPane, ++gridRow, "");
placeOfferButton.setVisible(false); placeOfferButton.setVisible(false);
placeOfferButton.setOnAction(e -> onPlaceOffer()); placeOfferButton.setOnAction(e -> onPlaceOffer());
placeOfferButton.setMinHeight(40); placeOfferButton.setMinHeight(40);
placeOfferButton.setPadding(new Insets(0, 20, 0, 20)); placeOfferButton.setPadding(new Insets(0, 20, 0, 20));
spinner = placeOfferTuple.second;
spinnerInfoLabel = placeOfferTuple.third;
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel")); cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
cancelButton2.setOnAction(e -> { cancelButton2.setOnAction(e -> {
if (model.dataModel.isWalletFunded.get()) if (model.dataModel.isWalletFunded.get()) {
new Popup().warning("You have already paid in the funds.\n" + new Popup().warning("You have already paid in the funds.\n" +
"Are you sure you want to cancel.") "Are you sure you want to cancel.")
.actionButtonText("No") .actionButtonText("No")
.closeButtonText("Yes, close") .closeButtonText("Yes, close")
.onClose(() -> close()) .onClose(() -> {
.show();
else
close(); close();
model.dataModel.swapTradeToSavings();
})
.show();
} else {
close();
model.dataModel.swapTradeToSavings();
}
}); });
cancelButton2.setDefaultButton(false); cancelButton2.setDefaultButton(false);
cancelButton2.setVisible(false); cancelButton2.setVisible(false);
@ -786,7 +807,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
@NotNull @NotNull
private String getBitcoinURI() { private String getBitcoinURI() {
return model.getAddressAsString() != null ? BitcoinURI.convertToBitcoinURI(model.getAddressAsString(), model.totalToPayAsCoin.get(), return model.getAddressAsString() != null ? BitcoinURI.convertToBitcoinURI(model.getAddressAsString(), model.dataModel.missingCoin.get(),
model.getPaymentLabel(), null) : ""; model.getPaymentLabel(), null) : "";
} }

View file

@ -18,7 +18,6 @@
package io.bitsquare.gui.main.offer.createoffer; package io.bitsquare.gui.main.offer.createoffer;
import io.bitsquare.app.BitsquareApp; import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.pricefeed.MarketPrice; import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.common.Timer; import io.bitsquare.common.Timer;
@ -27,6 +26,8 @@ import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableWithDataModel; import io.bitsquare.gui.common.model.ActivatableWithDataModel;
import io.bitsquare.gui.common.model.ViewModel; import io.bitsquare.gui.common.model.ViewModel;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.funds.FundsView;
import io.bitsquare.gui.main.funds.deposit.DepositView;
import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.settings.SettingsView; import io.bitsquare.gui.main.settings.SettingsView;
import io.bitsquare.gui.main.settings.preferences.PreferencesView; import io.bitsquare.gui.main.settings.preferences.PreferencesView;
@ -77,12 +78,10 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final StringProperty errorMessage = new SimpleStringProperty(); final StringProperty errorMessage = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty(); final StringProperty btcCode = new SimpleStringProperty();
final StringProperty tradeCurrencyCode = new SimpleStringProperty(); final StringProperty tradeCurrencyCode = new SimpleStringProperty();
final StringProperty spinnerInfoText = new SimpleStringProperty("");
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true); final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty(); final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty();
final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true); final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty();
final BooleanProperty showWarningAdjustedVolume = new SimpleBooleanProperty(); final BooleanProperty showWarningAdjustedVolume = new SimpleBooleanProperty();
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty(); final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty(); final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
@ -95,7 +94,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>(); final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
// Those are needed for the addressTextField // Those are needed for the addressTextField
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Address> address = new SimpleObjectProperty<>(); final ObjectProperty<Address> address = new SimpleObjectProperty<>();
private ChangeListener<String> amountListener; private ChangeListener<String> amountListener;
@ -108,7 +106,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<Fiat> volumeAsFiatListener; private ChangeListener<Fiat> volumeAsFiatListener;
private ChangeListener<Boolean> isWalletFundedListener; private ChangeListener<Boolean> isWalletFundedListener;
private ChangeListener<Coin> feeFromFundingTxListener; private ChangeListener<Coin> feeFromFundingTxListener;
private ChangeListener<String> requestPlaceOfferErrorMessageListener;
private ChangeListener<String> errorMessageListener; private ChangeListener<String> errorMessageListener;
private Offer offer; private Offer offer;
private Timer timeoutTimer; private Timer timeoutTimer;
@ -170,8 +167,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
directionLabel = BSResources.get("shared.sellBitcoin"); directionLabel = BSResources.get("shared.sellBitcoin");
amountDescription = BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.sell")); amountDescription = BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.sell"));
} }
updateSpinnerInfo();
} }
@Override @Override
@ -181,16 +176,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
stopTimeoutTimer(); stopTimeoutTimer();
} }
private void updateSpinnerInfo() {
if (dataModel.isWalletFunded.get() || !showPayFundsScreenDisplayed) {
isSpinnerVisible.set(false);
spinnerInfoText.set("");
} else if (showPayFundsScreenDisplayed) {
spinnerInfoText.set("Waiting for receiving funds...");
isSpinnerVisible.set(true);
}
}
private void addBindings() { private void addBindings() {
if (dataModel.getDirection() == Offer.Direction.BUY) { if (dataModel.getDirection() == Offer.Direction.BUY) {
volumeDescriptionLabel.bind(createStringBinding( volumeDescriptionLabel.bind(createStringBinding(
@ -212,7 +197,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
tradeAmount.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.amountAsCoin.get()), tradeAmount.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.amountAsCoin.get()),
dataModel.amountAsCoin)); dataModel.amountAsCoin));
totalToPayAsCoin.bind(dataModel.totalToPayAsCoin);
btcCode.bind(dataModel.btcCode); btcCode.bind(dataModel.btcCode);
tradeCurrencyCode.bind(dataModel.tradeCurrencyCode); tradeCurrencyCode.bind(dataModel.tradeCurrencyCode);
@ -221,7 +205,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private void removeBindings() { private void removeBindings() {
totalToPay.unbind(); totalToPay.unbind();
tradeAmount.unbind(); tradeAmount.unbind();
totalToPayAsCoin.unbind();
btcCode.unbind(); btcCode.unbind();
tradeCurrencyCode.unbind(); tradeCurrencyCode.unbind();
volumeDescriptionLabel.unbind(); volumeDescriptionLabel.unbind();
@ -265,20 +248,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
isWalletFundedListener = (ov, oldValue, newValue) -> { isWalletFundedListener = (ov, oldValue, newValue) -> {
updateButtonDisableState(); updateButtonDisableState();
spinnerInfoText.set("Checking funding tx miner fee...");
}; };
feeFromFundingTxListener = (ov, oldValue, newValue) -> { feeFromFundingTxListener = (ov, oldValue, newValue) -> {
updateButtonDisableState(); updateButtonDisableState();
if (newValue.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0) {
isSpinnerVisible.set(false);
spinnerInfoText.set("");
}
};
requestPlaceOfferErrorMessageListener = (ov, oldValue, newValue) -> {
if (newValue != null) {
isSpinnerVisible.set(false);
spinnerInfoText.set("");
}
}; };
} }
@ -298,7 +270,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener); dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
dataModel.isWalletFunded.addListener(isWalletFundedListener); dataModel.isWalletFunded.addListener(isWalletFundedListener);
errorMessage.addListener(requestPlaceOfferErrorMessageListener);
} }
private void removeListeners() { private void removeListeners() {
@ -315,7 +286,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener); dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
dataModel.isWalletFunded.removeListener(isWalletFundedListener); dataModel.isWalletFunded.removeListener(isWalletFundedListener);
errorMessage.removeListener(requestPlaceOfferErrorMessageListener);
if (offer != null && errorMessageListener != null) if (offer != null && errorMessageListener != null)
offer.errorMessageProperty().removeListener(errorMessageListener); offer.errorMessageProperty().removeListener(errorMessageListener);
@ -420,9 +390,27 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
} }
} }
public void onShowPayFundsScreen() { void onShowPayFundsScreen() {
showPayFundsScreenDisplayed = true; showPayFundsScreenDisplayed = true;
updateSpinnerInfo(); }
boolean useSavingsWalletForFunding() {
dataModel.useSavingsWalletForFunding();
if (dataModel.isWalletFunded.get()) {
updateButtonDisableState();
return true;
} else {
new Popup().warning("You don't have enough funds in your Bitsquare wallet.\n" +
"You need " + formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()) + " but you have only " +
formatter.formatCoinWithCode(dataModel.totalAvailableBalance) + " in your Bitsquare wallet.\n\n" +
"Please fund that trade from an external Bitcoin wallet or fund your Bitsquare " +
"wallet at \"Funds/Depost funds\".")
.actionButtonText("Go to \"Funds/Depost funds\"")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
.show();
return false;
}
} }
@ -648,7 +636,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
isNextButtonDisabled.set(!inputDataValid); isNextButtonDisabled.set(!inputDataValid);
isPlaceOfferButtonDisabled.set(!(inputDataValid && isPlaceOfferButtonDisabled.set(!(inputDataValid &&
dataModel.isWalletFunded.get() && dataModel.isWalletFunded.get() &&
dataModel.isFeeFromFundingTxSufficient()) (dataModel.useSavingsWallet || dataModel.isFeeFromFundingTxSufficient()))
); );
} }
} }

View file

@ -66,7 +66,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
private ComboBox<TradeCurrency> currencyComboBox; private ComboBox<TradeCurrency> currencyComboBox;
private ComboBox<PaymentMethod> paymentMethodComboBox; private ComboBox<PaymentMethod> paymentMethodComboBox;
private Button createOfferButton; private Button createOfferButton;
private TableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, priceColumn, paymentMethodColumn; private TableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, priceColumn, paymentMethodColumn, avatarColumn;
private TableView<OfferBookListItem> tableView; private TableView<OfferBookListItem> tableView;
private OfferView.OfferActionHandler offerActionHandler; private OfferView.OfferActionHandler offerActionHandler;
@ -155,7 +155,8 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
paymentMethodColumn = getPaymentMethodColumn(); paymentMethodColumn = getPaymentMethodColumn();
tableView.getColumns().add(paymentMethodColumn); tableView.getColumns().add(paymentMethodColumn);
tableView.getColumns().add(getActionColumn()); tableView.getColumns().add(getActionColumn());
tableView.getColumns().add(getAvatarColumn()); avatarColumn = getAvatarColumn();
tableView.getColumns().add(avatarColumn);
tableView.getSortOrder().add(priceColumn); tableView.getSortOrder().add(priceColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -167,6 +168,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount())); amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount()));
volumeColumn.setComparator((o1, o2) -> o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume())); volumeColumn.setComparator((o1, o2) -> o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume()));
paymentMethodColumn.setComparator((o1, o2) -> o1.getOffer().getPaymentMethod().compareTo(o2.getOffer().getPaymentMethod())); paymentMethodColumn.setComparator((o1, o2) -> o1.getOffer().getPaymentMethod().compareTo(o2.getOffer().getPaymentMethod()));
avatarColumn.setComparator((o1, o2) -> o1.getOffer().getOwnerNodeAddress().hostName.compareTo(o2.getOffer().getOwnerNodeAddress().hostName));
createOfferButton = addButton(root, ++gridRow, ""); createOfferButton = addButton(root, ++gridRow, "");
createOfferButton.setMinHeight(40); createOfferButton.setMinHeight(40);
@ -218,7 +220,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
tableView.setItems(model.getOfferList()); tableView.setItems(model.getOfferList());
priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING); priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
tableView.sort();
} }
@Override @Override
@ -572,7 +573,10 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
if (button != null) if (button != null)
button.setOnAction(null); button.setOnAction(null);
TableRow tableRow = getTableRow(); TableRow tableRow = getTableRow();
if (tableRow != null) tableRow.setOpacity(1); if (tableRow != null) {
tableRow.setOpacity(1);
tableRow.setOnMouseClicked(null);
}
} }
} }
}; };

View file

@ -232,6 +232,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
private void doTakeOffer(TradeResultHandler tradeResultHandler) { private void doTakeOffer(TradeResultHandler tradeResultHandler) {
tradeManager.onTakeOffer(amountAsCoin.get(), tradeManager.onTakeOffer(amountAsCoin.get(),
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
offer, offer,
paymentAccount.getId(), paymentAccount.getId(),
tradeResultHandler tradeResultHandler

View file

@ -353,7 +353,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
takeOfferButton.setText("Review take offer for selling bitcoin"); takeOfferButton.setText("Review take offer for selling bitcoin");
} }
balanceTextField.setup(model.address.get(), model.getFormatter()); balanceTextField.setFormatter(model.getFormatter());
balanceTextField.setupBalanceListener(model.address.get());
boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1; boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1;
paymentAccountsLabel.setVisible(showComboBox); paymentAccountsLabel.setVisible(showComboBox);

View file

@ -625,7 +625,6 @@ public abstract class Overlay<T extends Overlay> {
messageLabel = new Label(truncatedMessage); messageLabel = new Label(truncatedMessage);
messageLabel.setMouseTransparent(true); messageLabel.setMouseTransparent(true);
messageLabel.setWrapText(true); messageLabel.setWrapText(true);
messageLabel.setId("popup-message");
GridPane.setHalignment(messageLabel, HPos.LEFT); GridPane.setHalignment(messageLabel, HPos.LEFT);
GridPane.setHgrow(messageLabel, Priority.ALWAYS); GridPane.setHgrow(messageLabel, Priority.ALWAYS);
GridPane.setMargin(messageLabel, new Insets(3, 0, 0, 0)); GridPane.setMargin(messageLabel, new Insets(3, 0, 0, 0));

View file

@ -2,9 +2,12 @@ package io.bitsquare.gui.main.overlays.windows;
import io.bitsquare.gui.main.overlays.Overlay; import io.bitsquare.gui.main.overlays.Overlay;
import javafx.geometry.HPos; import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import net.glxn.qrgen.QRCode; import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType; import net.glxn.qrgen.image.ImageType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -15,9 +18,10 @@ import java.io.ByteArrayInputStream;
public class QRCodeWindow extends Overlay<QRCodeWindow> { public class QRCodeWindow extends Overlay<QRCodeWindow> {
private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class); private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class);
private final ImageView qrCodeImageView; private final ImageView qrCodeImageView;
private String bitcoinURI;
public QRCodeWindow(String bitcoinURI) { public QRCodeWindow(String bitcoinURI) {
this.bitcoinURI = bitcoinURI;
final byte[] imageBytes = QRCode final byte[] imageBytes = QRCode
.from(bitcoinURI) .from(bitcoinURI)
.withSize(250, 250) .withSize(250, 250)
@ -45,6 +49,18 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
GridPane.setHalignment(qrCodeImageView, HPos.CENTER); GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
gridPane.getChildren().add(qrCodeImageView); gridPane.getChildren().add(qrCodeImageView);
Label infoLabel = new Label("Payment request:\n" + bitcoinURI);
infoLabel.setMouseTransparent(true);
infoLabel.setWrapText(true);
infoLabel.setId("popup-qr-code-info");
GridPane.setHalignment(infoLabel, HPos.CENTER);
GridPane.setHgrow(infoLabel, Priority.ALWAYS);
GridPane.setMargin(infoLabel, new Insets(3, 0, 0, 0));
GridPane.setRowIndex(infoLabel, ++rowIndex);
GridPane.setColumnIndex(infoLabel, 0);
GridPane.setColumnSpan(infoLabel, 2);
gridPane.getChildren().add(infoLabel);
addCloseButton(); addCloseButton();
applyStyles(); applyStyles();
display(); display();

View file

@ -26,15 +26,15 @@
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Trade ID" fx:id="tradeIdColumn" minWidth="120" maxWidth="120" sortable="false"/> <TableColumn text="Trade ID" fx:id="tradeIdColumn" minWidth="120" maxWidth="120"/>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="150"/> <TableColumn text="Date/Time" fx:id="dateColumn" minWidth="150"/>
<TableColumn text="Trade amount in BTC" fx:id="amountColumn" minWidth="130"/> <TableColumn text="Trade amount in BTC" fx:id="amountColumn" minWidth="130"/>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/> <TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
<TableColumn text="Trade amount" fx:id="volumeColumn" minWidth="130"/> <TableColumn text="Trade amount" fx:id="volumeColumn" minWidth="130"/>
<TableColumn text="Trade type" fx:id="directionColumn" minWidth="80"/> <TableColumn text="Trade type" fx:id="directionColumn" minWidth="80"/>
<TableColumn text="State" fx:id="stateColumn" minWidth="80" sortable="false"/> <TableColumn text="State" fx:id="stateColumn" minWidth="80"/>
<TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/> <TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -24,15 +24,19 @@ import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.ImageUtil; import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.trade.Tradable; import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.OpenOffer; import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Callback; import javafx.util.Callback;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import javax.inject.Inject; import javax.inject.Inject;
@ -40,13 +44,14 @@ import javax.inject.Inject;
public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTradesViewModel> { public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTradesViewModel> {
@FXML @FXML
TableView<ClosedTradableListItem> table; TableView<ClosedTradableListItem> tableView;
@FXML @FXML
TableColumn<ClosedTradableListItem, ClosedTradableListItem> priceColumn, amountColumn, volumeColumn, TableColumn<ClosedTradableListItem, ClosedTradableListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn; directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn;
private final BSFormatter formatter; private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private SortedList<ClosedTradableListItem> sortedList;
@Inject @Inject
public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
@ -58,6 +63,9 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
@Override @Override
public void initialize() { public void initialize() {
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No closed trades available"));
setTradeIdColumnCellFactory(); setTradeIdColumnCellFactory();
setDirectionColumnCellFactory(); setDirectionColumnCellFactory();
setAmountColumnCellFactory(); setAmountColumnCellFactory();
@ -67,13 +75,55 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
setStateColumnCellFactory(); setStateColumnCellFactory();
setAvatarColumnCellFactory(); setAvatarColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); /* , , ,
table.setPlaceholder(new Label("No closed trades available")); , , , , avatarColumn;
*/
tradeIdColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId()));
dateColumn.setComparator((o1, o2) -> o1.getTradable().getDate().compareTo(o2.getTradable().getDate()));
directionColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getDirection().compareTo(o2.getTradable().getOffer().getDirection()));
priceColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getPrice().compareTo(o2.getTradable().getOffer().getPrice()));
volumeColumn.setComparator((o1, o2) -> {
if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) {
Fiat tradeVolume1 = ((Trade) o1.getTradable()).getTradeVolume();
Fiat tradeVolume2 = ((Trade) o2.getTradable()).getTradeVolume();
return tradeVolume1 != null && tradeVolume2 != null ? tradeVolume1.compareTo(tradeVolume2) : 0;
} else
return 0;
});
amountColumn.setComparator((o1, o2) -> {
if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) {
Coin amount1 = ((Trade) o1.getTradable()).getTradeAmount();
Coin amount2 = ((Trade) o2.getTradable()).getTradeAmount();
return amount1 != null && amount2 != null ? amount1.compareTo(amount2) : 0;
} else
return 0;
});
avatarColumn.setComparator((o1, o2) -> {
if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) {
NodeAddress tradingPeerNodeAddress1 = ((Trade) o1.getTradable()).getTradingPeerNodeAddress();
NodeAddress tradingPeerNodeAddress2 = ((Trade) o2.getTradable()).getTradingPeerNodeAddress();
String address1 = tradingPeerNodeAddress1 != null ? tradingPeerNodeAddress1.hostName : "";
String address2 = tradingPeerNodeAddress2 != null ? tradingPeerNodeAddress2.hostName : "";
return address1 != null && address2 != null ? address1.compareTo(address2) : 0;
} else
return 0;
});
stateColumn.setComparator((o1, o2) -> model.getState(o1).compareTo(model.getState(o2)));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
} }
@Override @Override
protected void activate() { protected void activate() {
table.setItems(model.getList()); sortedList = new SortedList<>(model.getList());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
} }

View file

@ -26,15 +26,15 @@
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Trade ID" fx:id="tradeIdColumn" minWidth="120" maxWidth="120" sortable="false"/> <TableColumn text="Trade ID" fx:id="tradeIdColumn" minWidth="120" maxWidth="120"/>
<TableColumn text="Date" fx:id="dateColumn" minWidth="130"/> <TableColumn text="Date" fx:id="dateColumn" minWidth="130"/>
<TableColumn text="Trade amount in BTC" fx:id="amountColumn" minWidth="130"/> <TableColumn text="Trade amount in BTC" fx:id="amountColumn" minWidth="130"/>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/> <TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
<TableColumn text="Trade amount in EUR" fx:id="volumeColumn" minWidth="130"/> <TableColumn text="Trade amount in EUR" fx:id="volumeColumn" minWidth="130"/>
<TableColumn text="Trade type" fx:id="directionColumn" minWidth="80"/> <TableColumn text="Trade type" fx:id="directionColumn" minWidth="80"/>
<TableColumn text="State" fx:id="stateColumn" minWidth="80" sortable="false"/> <TableColumn text="State" fx:id="stateColumn" minWidth="80"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -22,6 +22,7 @@ import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -33,11 +34,12 @@ import javax.inject.Inject;
public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTradesViewModel> { public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTradesViewModel> {
@FXML @FXML
TableView<FailedTradesListItem> table; TableView<FailedTradesListItem> tableView;
@FXML @FXML
TableColumn<FailedTradesListItem, FailedTradesListItem> priceColumn, amountColumn, volumeColumn, TableColumn<FailedTradesListItem, FailedTradesListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, tradeIdColumn, stateColumn; directionColumn, dateColumn, tradeIdColumn, stateColumn;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private SortedList<FailedTradesListItem> sortedList;
@Inject @Inject
public FailedTradesView(FailedTradesViewModel model, TradeDetailsWindow tradeDetailsWindow) { public FailedTradesView(FailedTradesViewModel model, TradeDetailsWindow tradeDetailsWindow) {
@ -47,6 +49,9 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
@Override @Override
public void initialize() { public void initialize() {
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No closed trades available"));
setTradeIdColumnCellFactory(); setTradeIdColumnCellFactory();
setDirectionColumnCellFactory(); setDirectionColumnCellFactory();
setAmountColumnCellFactory(); setAmountColumnCellFactory();
@ -55,15 +60,31 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
setDateColumnCellFactory(); setDateColumnCellFactory();
setStateColumnCellFactory(); setStateColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tradeIdColumn.setComparator((o1, o2) -> o1.getTrade().getId().compareTo(o2.getTrade().getId()));
table.setPlaceholder(new Label("No closed trades available")); dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate()));
priceColumn.setComparator((o1, o2) -> o1.getTrade().getOffer().getPrice().compareTo(o2.getTrade().getOffer().getPrice()));
volumeColumn.setComparator((o1, o2) -> o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume()));
amountColumn.setComparator((o1, o2) -> o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount()));
stateColumn.setComparator((o1, o2) -> model.getState(o1).compareTo(model.getState(o2)));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
} }
@Override @Override
protected void activate() { protected void activate() {
table.setItems(model.getList()); sortedList = new SortedList<>(model.getList());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
} }
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
}
private void setTradeIdColumnCellFactory() { private void setTradeIdColumnCellFactory() {
tradeIdColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue())); tradeIdColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
tradeIdColumn.setCellFactory( tradeIdColumn.setCellFactory(

View file

@ -26,9 +26,9 @@
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS"> <TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Offer ID" fx:id="offerIdColumn" minWidth="100" sortable="false"/> <TableColumn text="Offer ID" fx:id="offerIdColumn" minWidth="100"/>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="130"/> <TableColumn text="Date/Time" fx:id="dateColumn" minWidth="130"/>
<TableColumn text="Amount in BTC (Min.)" fx:id="amountColumn" minWidth="130"/> <TableColumn text="Amount in BTC (Min.)" fx:id="amountColumn" minWidth="130"/>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/> <TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>

View file

@ -28,6 +28,7 @@ import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.trade.offer.OpenOffer; import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
@ -40,12 +41,13 @@ import javax.inject.Inject;
public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersViewModel> { public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersViewModel> {
@FXML @FXML
TableView<OpenOfferListItem> table; TableView<OpenOfferListItem> tableView;
@FXML @FXML
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn, TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, offerIdColumn, removeItemColumn; directionColumn, dateColumn, offerIdColumn, removeItemColumn;
private final Navigation navigation; private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
private SortedList<OpenOfferListItem> sortedList;
@Inject @Inject
public OpenOffersView(OpenOffersViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow) { public OpenOffersView(OpenOffersViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow) {
@ -64,13 +66,30 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
setDateColumnCellFactory(); setDateColumnCellFactory();
setRemoveColumnCellFactory(); setRemoveColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No open offers available")); tableView.setPlaceholder(new Label("No open offers available"));
offerIdColumn.setComparator((o1, o2) -> o1.getOffer().getId().compareTo(o2.getOffer().getId()));
directionColumn.setComparator((o1, o2) -> o1.getOffer().getDirection().compareTo(o2.getOffer().getDirection()));
amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount()));
priceColumn.setComparator((o1, o2) -> o1.getOffer().getPrice().compareTo(o2.getOffer().getPrice()));
volumeColumn.setComparator((o1, o2) -> o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume()));
dateColumn.setComparator((o1, o2) -> o1.getOffer().getDate().compareTo(o2.getOffer().getDate()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
} }
@Override @Override
protected void activate() { protected void activate() {
table.setItems(model.getList()); sortedList = new SortedList<>(model.getList());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
} }
private void onRemoveOpenOffer(OpenOffer openOffer) { private void onRemoveOpenOffer(OpenOffer openOffer) {

View file

@ -18,7 +18,6 @@
--> -->
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView" <VBox fx:id="root" fx:controller="io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView"
@ -27,29 +26,13 @@
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding> </padding>
<TableView fx:id="table" VBox.vgrow="SOMETIMES"> <TableView fx:id="tableView" VBox.vgrow="SOMETIMES">
<columns> <columns>
<TableColumn text="Trade ID" fx:id="idColumn" minWidth="100" sortable="false"> <TableColumn text="Trade ID" fx:id="idColumn" minWidth="100"/>
<cellValueFactory>
<PropertyValueFactory property="id"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="130"/> <TableColumn text="Date/Time" fx:id="dateColumn" minWidth="130"/>
<TableColumn text="Trade amount in BTC" fx:id="tradeAmountColumn" minWidth="130"> <TableColumn text="Trade amount in BTC" fx:id="tradeAmountColumn" minWidth="130"/>
<cellValueFactory> <TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
<PropertyValueFactory property="tradeAmount"/> <TableColumn text="Trade amount" fx:id="tradeVolumeColumn" minWidth="130"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100">
<cellValueFactory>
<PropertyValueFactory property="price"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Trade amount" fx:id="tradeVolumeColumn" minWidth="130">
<cellValueFactory>
<PropertyValueFactory property="tradeVolume"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Payment method" fx:id="paymentMethodColumn" minWidth="120"/> <TableColumn text="Payment method" fx:id="paymentMethodColumn" minWidth="120"/>
<TableColumn text="My role" fx:id="roleColumn" minWidth="120" maxWidth="120"/> <TableColumn text="My role" fx:id="roleColumn" minWidth="120" maxWidth="120"/>
<TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/> <TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/>

View file

@ -17,7 +17,6 @@
package io.bitsquare.gui.main.portfolio.pendingtrades; package io.bitsquare.gui.main.portfolio.pendingtrades;
import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
@ -27,12 +26,12 @@ import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.ImageUtil; import io.bitsquare.gui.util.ImageUtil;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyCombination;
@ -40,9 +39,6 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Callback; import javafx.util.Callback;
import javafx.util.StringConverter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription; import org.fxmisc.easybind.Subscription;
@ -54,14 +50,12 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final BSFormatter formatter; private final BSFormatter formatter;
@FXML @FXML
TableView<PendingTradesListItem> table; TableView<PendingTradesListItem> tableView;
@FXML @FXML
TableColumn<PendingTradesListItem, Fiat> priceColumn, tradeVolumeColumn; TableColumn<PendingTradesListItem, PendingTradesListItem> priceColumn, tradeVolumeColumn, tradeAmountColumn, avatarColumn, roleColumn, paymentMethodColumn, idColumn, dateColumn;
@FXML @FXML
TableColumn<PendingTradesListItem, PendingTradesListItem> avatarColumn, roleColumn, paymentMethodColumn, idColumn, dateColumn;
@FXML
TableColumn<PendingTradesListItem, Coin> tradeAmountColumn;
private SortedList<PendingTradesListItem> sortedList;
private TradeSubView selectedSubView; private TradeSubView selectedSubView;
private EventHandler<KeyEvent> keyEventEventHandler; private EventHandler<KeyEvent> keyEventEventHandler;
private Scene scene; private Scene scene;
@ -92,9 +86,22 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
setRoleColumnCellFactory(); setRoleColumnCellFactory();
setAvatarColumnCellFactory(); setAvatarColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No pending trades available")); tableView.setPlaceholder(new Label("No pending trades available"));
table.setMinHeight(100); tableView.setMinHeight(100);
idColumn.setComparator((o1, o2) -> o1.getTrade().getId().compareTo(o2.getTrade().getId()));
dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate()));
tradeVolumeColumn.setComparator((o1, o2) -> o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume()));
tradeAmountColumn.setComparator((o1, o2) -> o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount()));
priceColumn.setComparator((o1, o2) -> o1.getPrice().compareTo(o2.getPrice()));
paymentMethodColumn.setComparator((o1, o2) -> o1.getTrade().getOffer().getPaymentMethod().getId().compareTo(o2.getTrade().getOffer().getPaymentMethod().getId()));
avatarColumn.setComparator((o1, o2) -> o1.getTrade().getTradingPeerNodeAddress().hostName.compareTo(o2.getTrade().getTradingPeerNodeAddress().hostName));
roleColumn.setComparator((o1, o2) -> model.getMyRole(o1).compareTo(model.getMyRole(o2)));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
// we use a hidden emergency shortcut to open support ticket // we use a hidden emergency shortcut to open support ticket
keyEventEventHandler = event -> { keyEventEventHandler = event -> {
@ -116,7 +123,10 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
@Override @Override
protected void activate() { protected void activate() {
Log.traceCall(); sortedList = new SortedList<>(model.dataModel.list);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
scene = root.getScene(); scene = root.getScene();
if (scene != null) { if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
@ -133,7 +143,6 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
} }
});*/ });*/
} }
table.setItems(model.dataModel.list);
selectedItemSubscription = EasyBind.subscribe(model.dataModel.selectedItemProperty, selectedItem -> { selectedItemSubscription = EasyBind.subscribe(model.dataModel.selectedItemProperty, selectedItem -> {
if (selectedItem != null) { if (selectedItem != null) {
@ -164,7 +173,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
selectedSubView.activate(); selectedSubView.activate();
}); });
selectedTableItemSubscription = EasyBind.subscribe(table.getSelectionModel().selectedItemProperty(), selectedTableItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(),
selectedItem -> { selectedItem -> {
if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get())) if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get()))
model.dataModel.onSelectItem(selectedItem); model.dataModel.onSelectItem(selectedItem);
@ -175,6 +184,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
@Override @Override
protected void deactivate() { protected void deactivate() {
sortedList.comparatorProperty().unbind();
selectedItemSubscription.unsubscribe(); selectedItemSubscription.unsubscribe();
selectedTableItemSubscription.unsubscribe(); selectedTableItemSubscription.unsubscribe();
if (appFocusSubscription != null) if (appFocusSubscription != null)
@ -203,10 +213,10 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
PendingTradesListItem selectedItemFromModel = model.dataModel.selectedItemProperty.get(); PendingTradesListItem selectedItemFromModel = model.dataModel.selectedItemProperty.get();
if (selectedItemFromModel != null) { if (selectedItemFromModel != null) {
// Select and focus selectedItem from model // Select and focus selectedItem from model
int index = table.getItems().indexOf(selectedItemFromModel); int index = tableView.getItems().indexOf(selectedItemFromModel);
UserThread.execute(() -> { UserThread.execute(() -> {
//TODO app wide focus //TODO app wide focus
table.getSelectionModel().select(index); tableView.getSelectionModel().select(index);
//table.requestFocus(); //table.requestFocus();
//UserThread.execute(() -> table.getFocusModel().focus(index)); //UserThread.execute(() -> table.getFocusModel().focus(index));
}); });
@ -280,49 +290,69 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
} }
private void setAmountColumnCellFactory() { private void setAmountColumnCellFactory() {
tradeAmountColumn.setCellFactory(TextFieldTableCell.<PendingTradesListItem, Coin>forTableColumn( tradeAmountColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
new StringConverter<Coin>() { tradeAmountColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override @Override
public String toString(Coin value) { public TableCell<PendingTradesListItem, PendingTradesListItem> call(
return formatter.formatCoinWithCode(value); TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
} return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override @Override
public Coin fromString(String string) { public void updateItem(final PendingTradesListItem item, boolean empty) {
return null; super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.formatCoinWithCode(item.getTrade().getPayoutAmount()));
else
setText(null);
} }
})); };
}
});
} }
private void setPriceColumnCellFactory() { private void setPriceColumnCellFactory() {
priceColumn.setCellFactory(TextFieldTableCell.<PendingTradesListItem, Fiat>forTableColumn( priceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
new StringConverter<Fiat>() { priceColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override @Override
public String toString(Fiat value) { public TableCell<PendingTradesListItem, PendingTradesListItem> call(
return formatter.formatPriceWithCode(value); TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
} return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override @Override
public Fiat fromString(String string) { public void updateItem(final PendingTradesListItem item, boolean empty) {
return null; super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.formatPriceWithCode(item.getPrice()));
else
setText(null);
} }
})); };
}
});
} }
private void setVolumeColumnCellFactory() { private void setVolumeColumnCellFactory() {
tradeVolumeColumn.setCellFactory(TextFieldTableCell.<PendingTradesListItem, Fiat>forTableColumn( tradeVolumeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
new StringConverter<Fiat>() { tradeVolumeColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override @Override
public String toString(Fiat value) { public TableCell<PendingTradesListItem, PendingTradesListItem> call(
return formatter.formatFiatWithCode(value); TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
} return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override @Override
public Fiat fromString(String string) { public void updateItem(final PendingTradesListItem item, boolean empty) {
return null; super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.formatPriceWithCode(item.getTrade().getTradeVolume()));
else
setText(null);
} }
})); };
}
});
} }
private void setPaymentMethodColumnCellFactory() { private void setPaymentMethodColumnCellFactory() {

View file

@ -48,7 +48,7 @@ public class BuyerStep5View extends TradeStepView {
protected Label btcTradeAmountLabel; protected Label btcTradeAmountLabel;
protected Label fiatTradeAmountLabel; protected Label fiatTradeAmountLabel;
private InputTextField withdrawAddressTextField; private InputTextField withdrawAddressTextField;
private Button withdrawButton; private Button withdrawToExternalWalletButton, useSavingsWalletButton;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -116,11 +116,19 @@ public class BuyerStep5View extends TradeStepView {
addTitledGroupBg(gridPane, ++gridRow, 2, "Withdraw your bitcoins", Layout.GROUP_DISTANCE); addTitledGroupBg(gridPane, ++gridRow, 2, "Withdraw your bitcoins", Layout.GROUP_DISTANCE);
addLabelTextField(gridPane, gridRow, "Amount to withdraw:", model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); addLabelTextField(gridPane, gridRow, "Amount to withdraw:", model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
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.setOnAction(e -> reviewWithdrawal()); Tuple2<Button, Button> tuple2 = add2ButtonsAfterGroup(gridPane, ++gridRow, "Move to Bitsquare wallet", "Withdraw to external wallet");
useSavingsWalletButton = tuple2.first;
withdrawToExternalWalletButton = tuple2.second;
useSavingsWalletButton.setOnAction(e -> {
model.dataModel.walletService.swapTradeToSavings(trade.getId());
handleTradeCompleted();
model.dataModel.tradeManager.addTradeToClosedTrades(trade);
});
withdrawToExternalWalletButton.setOnAction(e -> reviewWithdrawal());
if (BitsquareApp.DEV_MODE) { if (BitsquareApp.DEV_MODE) {
withdrawAddressTextField.setText("mi8k5f9L972VgDaT4LgjAhriC9hHEPL7EW"); withdrawAddressTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi");
} else { } else {
String key = "tradeCompleted" + trade.getId(); String key = "tradeCompleted" + trade.getId();
if (preferences.showAgain(key)) { if (preferences.showAgain(key)) {
@ -133,29 +141,6 @@ public class BuyerStep5View extends TradeStepView {
} }
} }
private void doWithdrawal() {
withdrawButton.setDisable(true);
model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(),
() -> {
String key = "tradeCompleteWithdrawCompletedInfo";
new Popup().headLine("Withdrawal completed")
.feedback("Your completed trades are stored under \"Portfolio/History\".\n" +
"You can review all your bitcoin transactions under \"Funds/Transactions\"")
.actionButtonText("Go to \"Transactions\"")
.onAction(() -> model.dataModel.navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class))
.dontShowAgainId(key, preferences)
.show();
withdrawButton.setDisable(true);
},
(errorMessage, throwable) -> {
withdrawButton.setDisable(false);
if (throwable != null && throwable.getMessage() != null)
new Popup().error(errorMessage + "\n\n" + throwable.getMessage()).show();
else
new Popup().error(errorMessage).show();
});
}
private void reviewWithdrawal() { private void reviewWithdrawal() {
Coin senderAmount = trade.getPayoutAmount(); Coin senderAmount = trade.getPayoutAmount();
WalletService walletService = model.dataModel.walletService; WalletService walletService = model.dataModel.walletService;
@ -174,11 +159,11 @@ public class BuyerStep5View extends TradeStepView {
validateWithdrawAddress(); validateWithdrawAddress();
} else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) { } else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
try { try {
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null);
Coin receiverAmount = senderAmount.subtract(requiredFee);
if (BitsquareApp.DEV_MODE) { if (BitsquareApp.DEV_MODE) {
doWithdrawal(); doWithdrawal();
} else { } else {
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null);
Coin receiverAmount = senderAmount.subtract(requiredFee);
BSFormatter formatter = model.formatter; BSFormatter formatter = model.formatter;
String key = "reviewWithdrawalAtTradeComplete"; String key = "reviewWithdrawalAtTradeComplete";
if (preferences.showAgain(key)) { if (preferences.showAgain(key)) {
@ -190,9 +175,12 @@ public class BuyerStep5View extends TradeStepView {
"The recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" + "The recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" +
"Are you sure you want to proceed with the withdrawal?") "Are you sure you want to proceed with the withdrawal?")
.closeButtonText("Cancel") .closeButtonText("Cancel")
.onClose(() -> withdrawButton.setDisable(false)) .onClose(() -> {
useSavingsWalletButton.setDisable(false);
withdrawToExternalWalletButton.setDisable(false);
})
.actionButtonText("Yes") .actionButtonText("Yes")
.onAction(this::doWithdrawal) .onAction(() -> doWithdrawal())
.dontShowAgainId(key, preferences) .dontShowAgainId(key, preferences)
.show(); .show();
} else { } else {
@ -210,10 +198,41 @@ public class BuyerStep5View extends TradeStepView {
} }
} }
private void doWithdrawal() {
useSavingsWalletButton.setDisable(true);
withdrawToExternalWalletButton.setDisable(true);
model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(),
() -> {
handleTradeCompleted();
},
(errorMessage, throwable) -> {
useSavingsWalletButton.setDisable(false);
withdrawToExternalWalletButton.setDisable(false);
if (throwable != null && throwable.getMessage() != null)
new Popup().error(errorMessage + "\n\n" + throwable.getMessage()).show();
else
new Popup().error(errorMessage).show();
});
}
private void handleTradeCompleted() {
String key = "tradeCompleteWithdrawCompletedInfo";
new Popup().headLine("Withdrawal completed")
.feedback("Your completed trades are stored under \"Portfolio/History\".\n" +
"You can review all your bitcoin transactions under \"Funds/Transactions\"")
.actionButtonText("Go to \"Transactions\"")
.onAction(() -> model.dataModel.navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class))
.dontShowAgainId(key, preferences)
.show();
useSavingsWalletButton.setDisable(true);
withdrawToExternalWalletButton.setDisable(true);
}
private void validateWithdrawAddress() { private void validateWithdrawAddress() {
withdrawAddressTextField.setValidator(model.btcAddressValidator); withdrawAddressTextField.setValidator(model.btcAddressValidator);
withdrawAddressTextField.requestFocus(); withdrawAddressTextField.requestFocus();
withdrawButton.requestFocus(); useSavingsWalletButton.requestFocus();
} }
protected String getBtcTradeAmountLabel() { protected String getBtcTradeAmountLabel() {

View file

@ -66,7 +66,7 @@
</TextField> </TextField>
<Label fx:id="p2PPeersLabel" text="Connected peers:" GridPane.rowIndex="4"/> <Label fx:id="p2PPeersLabel" text="Connected peers:" GridPane.rowIndex="4"/>
<TableView fx:id="p2PPeerTable" GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" <TableView fx:id="tableView" GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
GridPane.vgrow="ALWAYS"> GridPane.vgrow="ALWAYS">
<columns> <columns>
<TableColumn text="Onion address" fx:id="onionAddressColumn" minWidth="220"> <TableColumn text="Onion address" fx:id="onionAddressColumn" minWidth="220">

View file

@ -32,6 +32,7 @@ import io.bitsquare.p2p.network.Statistic;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.VPos; import javafx.geometry.VPos;
@ -68,7 +69,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
@FXML @FXML
CheckBox useTorCheckBox; CheckBox useTorCheckBox;
@FXML @FXML
TableView<P2pNetworkListItem> p2PPeerTable; TableView<P2pNetworkListItem> tableView;
@FXML @FXML
TableColumn<P2pNetworkListItem, String> onionAddressColumn, connectionTypeColumn, creationDateColumn, TableColumn<P2pNetworkListItem, String> onionAddressColumn, connectionTypeColumn, creationDateColumn,
/*lastActivityColumn,*/ roundTripTimeColumn, sentBytesColumn, receivedBytesColumn, peerTypeColumn; /*lastActivityColumn,*/ roundTripTimeColumn, sentBytesColumn, receivedBytesColumn, peerTypeColumn;
@ -76,6 +77,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
private Subscription bitcoinPeersSubscription; private Subscription bitcoinPeersSubscription;
private Subscription nodeAddressSubscription; private Subscription nodeAddressSubscription;
private ObservableList<P2pNetworkListItem> networkListItems = FXCollections.observableArrayList(); private ObservableList<P2pNetworkListItem> networkListItems = FXCollections.observableArrayList();
private final SortedList<P2pNetworkListItem> sortedList = new SortedList<>(networkListItems);
@Inject @Inject
public NetworkSettingsView(WalletService walletService, P2PService p2PService, Preferences preferences, Clock clock, public NetworkSettingsView(WalletService walletService, P2PService p2PService, Preferences preferences, Clock clock,
@ -110,10 +112,10 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
} }
}); });
p2PPeerTable.setMinHeight(300); tableView.setMinHeight(300);
p2PPeerTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
p2PPeerTable.setPlaceholder(new Label("No connections are available")); tableView.setPlaceholder(new Label("No connections are available"));
p2PPeerTable.getSortOrder().add(creationDateColumn); tableView.getSortOrder().add(creationDateColumn);
creationDateColumn.setSortType(TableColumn.SortType.ASCENDING); creationDateColumn.setSortType(TableColumn.SortType.ASCENDING);
@ -152,8 +154,8 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
totalTraffic.textProperty().bind(EasyBind.combine(Statistic.totalSentBytesProperty(), Statistic.totalReceivedBytesProperty(), totalTraffic.textProperty().bind(EasyBind.combine(Statistic.totalSentBytesProperty(), Statistic.totalReceivedBytesProperty(),
(sent, received) -> "Sent: " + formatter.formatBytes((int) sent) + ", received: " + formatter.formatBytes((int) received))); (sent, received) -> "Sent: " + formatter.formatBytes((int) sent) + ", received: " + formatter.formatBytes((int) received)));
p2PPeerTable.setItems(networkListItems); sortedList.comparatorProperty().bind(tableView.comparatorProperty());
p2PPeerTable.sort(); tableView.setItems(sortedList);
} }
@Override @Override
@ -169,12 +171,14 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
if (numP2PPeersSubscription != null) if (numP2PPeersSubscription != null)
numP2PPeersSubscription.unsubscribe(); numP2PPeersSubscription.unsubscribe();
p2PPeerTable.getItems().forEach(P2pNetworkListItem::cleanup);
totalTraffic.textProperty().unbind(); totalTraffic.textProperty().unbind();
sortedList.comparatorProperty().unbind();
tableView.getItems().forEach(P2pNetworkListItem::cleanup);
} }
private void updateP2PTable() { private void updateP2PTable() {
p2PPeerTable.getItems().forEach(P2pNetworkListItem::cleanup); tableView.getItems().forEach(P2pNetworkListItem::cleanup);
networkListItems.clear(); networkListItems.clear();
networkListItems.setAll(p2PService.getNetworkNode().getAllConnections().stream() networkListItems.setAll(p2PService.getNetworkNode().getAllConnections().stream()
.map(connection -> new P2pNetworkListItem(connection, clock, formatter)) .map(connection -> new P2pNetworkListItem(connection, clock, formatter))

View file

@ -838,17 +838,6 @@ public class FormBuilder {
return new Tuple3<>(button, progressIndicator, label); return new Tuple3<>(button, progressIndicator, label);
} }
public static void removeRowFromGridPane(GridPane gridPane, int gridRow) {
removeRowsFromGridPane(gridPane, gridRow, gridRow);
}
public static void removeRowsFromGridPane(GridPane gridPane, int fromGridRow, int toGridRow) {
Set<Node> nodes = new CopyOnWriteArraySet<>(gridPane.getChildren());
nodes.stream()
.filter(e -> GridPane.getRowIndex(e) >= fromGridRow && GridPane.getRowIndex(e) <= toGridRow)
.forEach(e -> gridPane.getChildren().remove(e));
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Trade: HBox, InputTextField, Label // Trade: HBox, InputTextField, Label
@ -913,4 +902,21 @@ public class FormBuilder {
return new Tuple2<>(label, listView); return new Tuple2<>(label, listView);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Remove
///////////////////////////////////////////////////////////////////////////////////////////
public static void removeRowFromGridPane(GridPane gridPane, int gridRow) {
removeRowsFromGridPane(gridPane, gridRow, gridRow);
}
public static void removeRowsFromGridPane(GridPane gridPane, int fromGridRow, int toGridRow) {
Set<Node> nodes = new CopyOnWriteArraySet<>(gridPane.getChildren());
nodes.stream()
.filter(e -> GridPane.getRowIndex(e) >= fromGridRow && GridPane.getRowIndex(e) <= toGridRow)
.forEach(e -> gridPane.getChildren().remove(e));
}
} }

View file

@ -442,7 +442,10 @@ public class P2PDataStorage implements MessageListener, ConnectionListener {
else else
result = expirableMailboxStoragePayload.receiverPubKeyForRemoveOperation.equals(protectedStorageEntry.ownerPubKey); result = expirableMailboxStoragePayload.receiverPubKeyForRemoveOperation.equals(protectedStorageEntry.ownerPubKey);
} else { } else {
result = protectedStorageEntry != null && protectedStorageEntry.getStoragePayload() != null && // TODO We got sometimes a nullpointer at protectedStorageEntry.ownerPubKey
// Probably caused by an exception at deserialization: Offer: Cannot be deserialized.null
result = protectedStorageEntry != null && protectedStorageEntry.ownerPubKey != null &&
protectedStorageEntry.getStoragePayload() != null &&
protectedStorageEntry.ownerPubKey.equals(protectedStorageEntry.getStoragePayload().getOwnerPubKey()); protectedStorageEntry.ownerPubKey.equals(protectedStorageEntry.getStoragePayload().getOwnerPubKey());
} }

View file

@ -69,8 +69,7 @@ public final class MailboxStoragePayload implements StoragePayload {
senderPubKeyForAddOperation = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(senderPubKeyForAddOperationBytes)); senderPubKeyForAddOperation = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(senderPubKeyForAddOperationBytes));
receiverPubKeyForRemoveOperation = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(receiverPubKeyForRemoveOperationBytes)); receiverPubKeyForRemoveOperation = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(receiverPubKeyForRemoveOperationBytes));
} catch (Throwable t) { } catch (Throwable t) {
log.error("Exception at readObject: " + t.getMessage()); log.warn("Exception at readObject: " + t.getMessage());
t.printStackTrace();
} }
} }

View file

@ -38,8 +38,7 @@ public class ProtectedMailboxStorageEntry extends ProtectedStorageEntry {
receiversPubKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(receiversPubKeyBytes)); receiversPubKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(receiversPubKeyBytes));
updateTimeStamp(); updateTimeStamp();
} catch (Throwable t) { } catch (Throwable t) {
log.error("Exception at readObject: " + t.getMessage()); log.warn("Exception at readObject: " + t.getMessage());
t.printStackTrace();
} }
} }

View file

@ -43,8 +43,7 @@ public class ProtectedStorageEntry implements Payload {
ownerPubKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(ownerPubKeyBytes)); ownerPubKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(ownerPubKeyBytes));
updateTimeStamp(); updateTimeStamp();
} catch (Throwable t) { } catch (Throwable t) {
log.error("Exception at readObject: " + t.getMessage()); log.warn("Exception at readObject: " + t.getMessage());
t.printStackTrace();
} }
} }