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();
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);
// log errors in separate file

View File

@ -62,7 +62,7 @@ public final class PubKeyRing implements Payload {
e.printStackTrace();
log.error(e.getMessage());
} 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();
sigPublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(sigPublicKeyBytes));
} catch (Throwable t) {
log.error("Exception at readObject: " + t.getMessage());
t.printStackTrace();
log.warn("Exception at readObject: " + t.getMessage());
}
}

View File

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

View File

@ -134,7 +134,7 @@ public final class Dispute implements Payload {
disputeResultProperty = new SimpleObjectProperty<>(disputeResult);
isClosedProperty = new SimpleBooleanProperty(isClosed);
} 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 {
in.defaultReadObject();
} 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();
init();
} 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);
storedInMailboxProperty = new SimpleBooleanProperty(storedInMailbox);
} 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
///////////////////////////////////////////////////////////////////////////////////////////
public AddressBasedCoinSelector(NetworkParameters params, AddressEntry addressEntry) {
public AddressBasedCoinSelector(NetworkParameters params) {
this.params = params;
}
public AddressBasedCoinSelector(NetworkParameters params, @Nullable AddressEntry addressEntry) {
this.params = params;
this.addressEntry = addressEntry;
}
@ -119,6 +123,9 @@ class AddressBasedCoinSelector implements CoinSelector {
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString
() + " / " + addressEntries.toString());
} else {
// use savings wallet
return true;
}
}
return false;

View File

@ -101,7 +101,7 @@ public final class AddressEntry implements Persistable {
params = RegTestParams.get();
} 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 java.util.ArrayList;
import java.util.Optional;
/**
* 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() {
if (size() > 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 useSavingsWallet
* @param tradingFee The amount of the trading fee.
* @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator).
* @return The broadcasted transaction
* @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). @return The broadcasted transaction
* @throws InsufficientMoneyException
* @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 {
Transaction tradingFeeTx = new Transaction(params);
Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee),
"You cannot send an amount which are smaller than the fee + dust output.");
Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades());
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
// wait for 1 confirmation)
@ -154,13 +157,16 @@ public class TradeWalletService {
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tradingFeeTx);
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
if (useSavingsWallet)
sendRequest.coinSelector = new AddressBasedCoinSelector(params);
else
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// We use a fixed fee
sendRequest.feePerKb = Coin.ZERO;
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.
sendRequest.changeAddress = addressEntry.getAddress();
// Change is optional in case of overpay or use of funds from savings wallet
sendRequest.changeAddress = changeAddress;
checkNotNull(wallet, "Wallet must not be null");
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.
*
* @param inputAmount Amount of takers input
* @param addressEntry Address entry of taker
* @param inputAmount Amount of takers input
* @param takersAddressEntry Address entry of taker
* @return A data container holding the inputs, the output value and address
* @throws TransactionVerificationException
* @throws WalletException
*/
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry addressEntry) throws
TransactionVerificationException, WalletException {
public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry takersAddressEntry, Address takersChangeAddress) throws
TransactionVerificationException, WalletException, AddressFormatException {
log.trace("createTakerDepositTxInputs called");
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:
// 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.
// 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.
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
removeSignatures(dummyTX);
@ -261,17 +267,17 @@ public class TradeWalletService {
/**
* The offerer creates the deposit transaction using the takers input(s) and optional output and signs his input(s).
*
* @param offererIsBuyer The flag indicating if we are in the offerer as buyer role or the opposite.
* @param contractHash The hash of the contract to be added to the OP_RETURN output.
* @param offererInputAmount The input amount of the offerer.
* @param msOutputAmount The output amount to our MS output.
* @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 takerChangeAddressString Optional taker change address
* @param offererAddressInfo The offerers address entry.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @param offererIsBuyer The flag indicating if we are in the offerer as buyer role or the opposite.
* @param contractHash The hash of the contract to be added to the OP_RETURN output.
* @param offererInputAmount The input amount of the offerer.
* @param msOutputAmount The output amount to our MS output.
* @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 takerChangeAddressString Optional taker change address
* @param offererAddressEntry The offerers address entry.
* @param buyerPubKey The public key of the buyer.
* @param sellerPubKey The public key of the seller.
* @param arbitratorPubKey The public key of the arbitrator.
* @return A data container holding the serialized transaction and the offerer raw inputs
* @throws SigningException
* @throws TransactionVerificationException
@ -284,7 +290,8 @@ public class TradeWalletService {
List<RawTransactionInput> takerRawTransactionInputs,
long takerChangeOutputValue,
@Nullable String takerChangeAddressString,
AddressEntry offererAddressInfo,
AddressEntry offererAddressEntry,
Address offererChangeAddress,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
@ -308,7 +315,7 @@ public class TradeWalletService {
Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.getFixedTxFeeForTrades());
TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params));
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.
List<TransactionInput> offererInputs = dummyTx.getInputs();
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 {
// Lets let the framework do the work to find the right inputs
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)
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// 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.
// 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.

View File

@ -305,19 +305,13 @@ public class WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
// AddressInfo
// Trade AddressEntry
///////////////////////////////////////////////////////////////////////////////////////////
public List<AddressEntry> getAddressEntryList() {
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() {
return arbitratorAddressEntry;
}
@ -332,10 +326,6 @@ public class WalletService {
return addressEntryList.getNewTradeAddressEntry(offerId);
}
public AddressEntry getNewSavingsAddressEntry() {
return addressEntryList.getNewSavingsAddressEntry();
}
private Optional<AddressEntry> getAddressEntryByAddress(String address) {
return getAddressEntryList().stream()
.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
///////////////////////////////////////////////////////////////////////////////////////////
@ -448,6 +504,28 @@ public class WalletService {
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

View File

@ -115,7 +115,7 @@ public final class PaymentMethod implements Persistable, Comparable {
this.maxTradePeriod = paymentMethod.getMaxTradePeriod();
this.maxTradeLimitInBitcoin = paymentMethod.getMaxTradeLimitInBitcoin();
} 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();
initAmountProperty();
} 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();
initAmountProperty();
} 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();
initAmountProperty();
} 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();
initAmountProperty();
} 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 {
in.defaultReadObject();
} 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();
errorMessageProperty = new SimpleStringProperty(errorMessage);
} 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,
OpenOfferManager openOfferManager,
User user,
KeyRing keyRing) {
KeyRing keyRing,
Coin fundsNeededForTrade) {
Log.traceCall();
processModel.onAllServicesInitialized(offer,
tradeManager,
@ -230,7 +231,8 @@ public abstract class Trade implements Tradable, Model {
tradeWalletService,
arbitratorManager,
user,
keyRing);
keyRing,
fundsNeededForTrade);
createProtocol();

View File

@ -178,7 +178,7 @@ public class TradeManager {
else {*/
trade.setStorage(tradableListStorage);
trade.updateDepositTxFromWallet(tradeWalletService);
initTrade(trade);
initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
// }
@ -209,7 +209,7 @@ public class TradeManager {
trade = new SellerAsOffererTrade(offer, tradableListStorage);
trade.setStorage(tradableListStorage);
initTrade(trade);
initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
trades.add(trade);
((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress);
} else {
@ -220,7 +220,7 @@ public class TradeManager {
}
}
private void initTrade(Trade trade) {
private void initTrade(Trade trade, Coin fundsNeededForTrade) {
trade.init(p2PService,
walletService,
tradeWalletService,
@ -228,7 +228,8 @@ public class TradeManager {
this,
openOfferManager,
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
public void onTakeOffer(Coin amount,
Coin fundsNeededForTrade,
Offer offer,
String paymentAccountId,
TradeResultHandler tradeResultHandler) {
@ -263,11 +265,12 @@ public class TradeManager {
offer.checkOfferAvailability(model,
() -> {
if (offer.getState() == Offer.State.AVAILABLE)
createTrade(amount, offer, paymentAccountId, model, tradeResultHandler);
createTrade(amount, fundsNeededForTrade, offer, paymentAccountId, model, tradeResultHandler);
});
}
private void createTrade(Coin amount,
Coin fundsNeededForTrade,
Offer offer,
String paymentAccountId,
OfferAvailabilityModel model,
@ -282,7 +285,7 @@ public class TradeManager {
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
trade.setTakerPaymentAccountId(paymentAccountId);
initTrade(trade);
initTrade(trade, fundsNeededForTrade);
trades.add(trade);
((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
errorMessageProperty = new SimpleStringProperty();
} 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);
} catch (Throwable t) {
log.error("Cannot be deserialized." + t.getMessage());
log.warn("Cannot be deserialized." + t.getMessage());
}
}
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.user.User;
import javafx.collections.ObservableList;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -227,9 +228,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void placeOffer(Offer offer,
TransactionResultHandler resultHandler) {
PlaceOfferModel model = new PlaceOfferModel(offer, walletService, tradeWalletService, offerBookService, user);
public void placeOffer(Offer offer, Coin reservedFundsForOffer, boolean useSavingsWallet, TransactionResultHandler resultHandler) {
PlaceOfferModel model = new PlaceOfferModel(offer, reservedFundsForOffer, useSavingsWallet, walletService, tradeWalletService, offerBookService, user);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model,
transaction -> {
@ -272,6 +272,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setState(OpenOffer.State.CANCELED);
openOffers.remove(openOffer);
closedTradableManager.add(openOffer);
walletService.swapTradeToSavings(offer.getId());
resultHandler.handleResult();
},
errorMessageHandler);

View File

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

View File

@ -18,13 +18,10 @@
package io.bitsquare.trade.protocol.placeoffer.tasks;
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.TaskRunner;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@ -44,67 +41,59 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
protected void run() {
try {
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>() {
@Override
public void onSuccess(Transaction transaction) {
log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString());
model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString());
if (model.getTransaction().getHashAsString().equals(transaction.getHashAsString())) {
model.offer.setState(Offer.State.OFFER_FEE_PAID);
// No tx malleability happened after broadcast (still not in blockchain)
complete();
} else {
log.warn("Tx malleability happened after broadcast. We publish the changed offer to the P2P network again.");
// Tx malleability happened after broadcast. We first remove the malleable offer.
// Then we publish the changed offer to the P2P network again after setting the new TxId.
// Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out
model.offerBookService.removeOffer(model.offer,
() -> {
log.info("We store now the changed txID to the offer and add that again.");
// We store now the changed txID to the offer and add that again.
model.offer.setOfferFeePaymentTxID(transaction.getHashAsString());
model.setTransaction(transaction);
model.offerBookService.addOffer(model.offer,
() -> complete(),
errorMessage -> {
log.error("addOffer failed");
addOfferFailed = true;
updateStateOnFault();
model.offer.setErrorMessage("An error occurred when adding the offer to the P2P network.\n" +
"Error message:\n"
+ errorMessage);
failed(errorMessage);
});
},
errorMessage -> {
log.error("removeOffer failed");
removeOfferFailed = true;
updateStateOnFault();
model.offer.setErrorMessage("An error occurred when removing the offer from the P2P network.\n" +
"Error message:\n"
+ errorMessage);
failed(errorMessage);
});
}
if (model.getTransaction().getHashAsString().equals(transaction.getHashAsString())) {
model.offer.setState(Offer.State.OFFER_FEE_PAID);
// No tx malleability happened after broadcast (still not in blockchain)
complete();
} else {
log.warn("Tx malleability happened after broadcast. We publish the changed offer to the P2P network again.");
// Tx malleability happened after broadcast. We first remove the malleable offer.
// Then we publish the changed offer to the P2P network again after setting the new TxId.
// Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out
model.offerBookService.removeOffer(model.offer,
() -> {
log.info("We store now the changed txID to the offer and add that again.");
// We store now the changed txID to the offer and add that again.
model.offer.setOfferFeePaymentTxID(transaction.getHashAsString());
model.setTransaction(transaction);
model.offerBookService.addOffer(model.offer,
() -> complete(),
errorMessage -> {
log.error("addOffer failed");
addOfferFailed = true;
updateStateOnFault();
model.offer.setErrorMessage("An error occurred when adding the offer to the P2P network.\n" +
"Error message:\n"
+ errorMessage);
failed(errorMessage);
});
},
errorMessage -> {
log.error("removeOffer failed");
removeOfferFailed = true;
updateStateOnFault();
model.offer.setErrorMessage("An error occurred when removing the offer from the P2P network.\n" +
"Error message:\n"
+ errorMessage);
failed(errorMessage);
});
}
}
@Override
public void onFailure(@NotNull Throwable t) {
updateStateOnFault();
model.offer.setErrorMessage("An error occurred.\n" +
"Error message:\n"
+ t.getMessage());
failed(t);
}
});
} else {
updateStateOnFault();
model.offer.setErrorMessage("You don't have enough balance in your wallet for placing the offer.");
}
@Override
public void onFailure(@NotNull Throwable t) {
updateStateOnFault();
model.offer.setErrorMessage("An error occurred.\n" +
"Error message:\n"
+ t.getMessage());
failed(t);
}
});
} catch (Throwable t) {
model.offer.setErrorMessage("An error occurred.\n" +
"Error message:\n"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -64,7 +64,7 @@ public final class TradingPeer implements Persistable {
try {
in.defaultReadObject();
} 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;
public class CreateAndSignDepositTxAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateAndSignDepositTxAsBuyer.class);
public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(OffererCreatesAndSignsDepositTxAsBuyer.class);
public CreateAndSignDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) {
public OffererCreatesAndSignsDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -60,6 +60,7 @@ public class CreateAndSignDepositTxAsBuyer extends TradeTask {
processModel.tradingPeer.getChangeOutputValue(),
processModel.tradingPeer.getChangeOutputAddress(),
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()));

View File

@ -26,10 +26,10 @@ import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CreateDepositTxInputsAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(CreateDepositTxInputsAsBuyer.class);
public class TakerCreatesDepositTxInputsAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(TakerCreatesDepositTxInputsAsBuyer.class);
public CreateDepositTxInputsAsBuyer(TaskRunner taskHandler, Trade trade) {
public TakerCreatesDepositTxInputsAsBuyer(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -38,7 +38,9 @@ public class CreateDepositTxInputsAsBuyer extends TradeTask {
try {
runInterceptHook();
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.setChangeOutputValue(result.changeOutputValue);
processModel.setChangeOutputAddress(result.changeOutputAddress);

View File

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

View File

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

View File

@ -29,6 +29,8 @@ import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
public class CreateTakeOfferFeeTx extends TradeTask {
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());
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx");
Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx(
processModel.getAddressEntry(),
processModel.getUnusedSavingsAddress(),
processModel.getFundsNeededForTrade(),
processModel.useSavingsWallet,
FeePolicy.getTakeOfferFee(),
selectedArbitrator.getBtcAddress());

View File

@ -75,7 +75,7 @@ import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY;
public class BitsquareApp extends Application {
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;
private static Environment env;

View File

@ -201,6 +201,12 @@ bg color of non edit textFields: fafafa
-fx-cursor: hand;
}
.internal-funds-icon {
-fx-text-fill: #999;
-fx-cursor: hand;
}
/*******************************************************************************
* *
* Tooltip *
@ -1000,3 +1006,7 @@ textfield */
-fx-text-fill: white;
-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 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.setLayoutY(3);
directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon");
AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT);
if (isInternal) {
directionIcon.getStyleClass().add("internal-funds-icon");
AwesomeDude.setIcon(directionIcon, AwesomeIcon.REPEAT);
} else {
directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon");
AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT);
}
directionIcon.setMouseTransparent(true);
HBox hBox = new HBox();
@ -52,7 +57,7 @@ public class AddressWithIconAndDirection extends AnchorPane {
AnchorPane.setLeftAnchor(directionIcon, 3.0);
AnchorPane.setTopAnchor(directionIcon, 2.0);
AnchorPane.setLeftAnchor(hBox, 20.0);
AnchorPane.setLeftAnchor(hBox, 22.0);
AnchorPane.setRightAnchor(hBox, 15.0);
AnchorPane.setRightAnchor(openLinkIcon, 4.0);
AnchorPane.setTopAnchor(openLinkIcon, 3.0);

View File

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

View File

@ -473,7 +473,7 @@ public class MainViewModel implements ViewModel {
if (tuple.payload instanceof Ping &&
((Ping) tuple.payload).nonce == payload.nonce &&
((Ping) tuple.payload).lastRoundTripTime == payload.lastRoundTripTime)
log.trace("Crypto test succeeded");
log.debug("Crypto test succeeded");
else
throw new CryptoException("Payload not correct after decryption");
} 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.
// TODO investigate
UserThread.runAfter(() -> checkCryptoThread.start(), 3);
checkCryptoThread.start();
}

View File

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

View File

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

View File

@ -90,7 +90,9 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
private final List<Attachment> tempAttachments = new ArrayList<>();
private TableView<Dispute> disputesTable;
private TableView<Dispute> tableView;
private SortedList<Dispute> sortedList;
private Dispute selectedDispute;
private ListView<DisputeCommunicationMessage> messageListView;
private TextArea inputTextArea;
@ -133,28 +135,32 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
@Override
public void initialize() {
disputesTable = new TableView<>();
VBox.setVgrow(disputesTable, Priority.SOMETIMES);
disputesTable.setMinHeight(150);
root.getChildren().add(disputesTable);
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);
tableView = new TableView<>();
VBox.setVgrow(tableView, Priority.SOMETIMES);
tableView.setMinHeight(150);
root.getChildren().add(tableView);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("There are no open tickets");
placeholder.setWrapText(true);
disputesTable.setPlaceholder(placeholder);
disputesTable.getSelectionModel().clearSelection();
tableView.setPlaceholder(placeholder);
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) ->
sendButton.setDisable(newValue.length() == 0
@ -183,24 +189,27 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
@Override
protected void activate() {
FilteredList<Dispute> filteredList = new FilteredList<>(disputeManager.getDisputesAsObservableList());
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)
disputesTable.getSelectionModel().select(selectedItem);
tableView.getSelectionModel().select(selectedItem);
scrollToBottom();
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
selectedDisputeSubscription.unsubscribe();
removeListenersOnSelectDispute();
}
@ -752,6 +761,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
setMinWidth(130);
}
};
column.setSortable(false);
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
column.setCellFactory(
new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() {
@ -809,6 +819,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
setSortable(false);
}
};
column.setSortable(false);
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
column.setCellFactory(
new Callback<TableColumn<Dispute, Dispute>, TableCell<Dispute, Dispute>>() {
@ -846,6 +857,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
setMinWidth(50);
}
};
column.setSortable(false);
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
column.setCellFactory(
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 StringProperty balance = new SimpleStringProperty();
private final WalletService walletService;
private Coin balanceAsCoin;
private BSFormatter formatter;
private final ConfidenceProgressIndicator progressIndicator;
private final Tooltip tooltip;
@ -44,8 +46,9 @@ public class DepositListItem {
private String balanceString;
private String addressString;
private String status = "Unused";
private String usage = "-";
private TxConfidenceListener txConfidenceListener;
private int numTxOutputs = 0;
// public DepositListItem(AddressEntry addressEntry, Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
public DepositListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) {
@ -67,17 +70,17 @@ public class DepositListItem {
walletService.addBalanceListener(new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
DepositListItem.this.balanceAsCoin = balanceAsCoin;
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
updateConfidence(walletService.getConfidenceForTxId(tx.getHashAsString()));
if (balanceAsCoin.isPositive())
status = "Funded";
updateUsage(address);
}
});
Coin balanceAsCoin = walletService.getBalanceForAddress(address);
balanceAsCoin = walletService.getBalanceForAddress(address);
balance.set(formatter.formatCoin(balanceAsCoin));
if (balanceAsCoin.isPositive())
status = "Funded";
updateUsage(address);
TransactionConfidence transactionConfidence = walletService.getConfidenceForAddress(address);
if (transactionConfidence != null) {
@ -93,8 +96,9 @@ public class DepositListItem {
}
}
public void setStatus(String status) {
this.status = status;
private void updateUsage(Address address) {
numTxOutputs = walletService.getNumTxOutputsForAddress(address);
usage = numTxOutputs == 0 ? "Unused" : "Used in " + numTxOutputs + " transactions";
}
public void cleanup() {
@ -134,8 +138,8 @@ public class DepositListItem {
return addressString;
}
public String getStatus() {
return status;
public String getUsage() {
return usage;
}
public final StringProperty balanceProperty() {
@ -146,4 +150,11 @@ public class DepositListItem {
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.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<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"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn text="Select" fx:id="selectColumn" minWidth="110" maxWidth="110" sortable="false"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="260"/>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="150" maxWidth="150">
<cellValueFactory>
<PropertyValueFactory property="balance"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="150" maxWidth="150"/>
<TableColumn text="Status" fx:id="statusColumn" minWidth="150" maxWidth="150"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="320"/>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="150"/>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="150"/>
<TableColumn text="Usage" fx:id="usageColumn" minWidth="200"/>
</columns>
</TableView>

View File

@ -18,7 +18,9 @@
package io.bitsquare.gui.main.funds.deposit;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
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.components.AddressTextField;
import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.TitledGroupBg;
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.TradeDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
import io.bitsquare.gui.util.BSFormatter;
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 javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@ -58,6 +54,8 @@ import net.glxn.qrgen.image.ImageType;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.uri.BitcoinURI;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
@ -72,30 +70,27 @@ public class DepositView extends ActivatableView<VBox, Void> {
GridPane gridPane;
@FXML
TableView<DepositListItem> table;
TableView<DepositListItem> tableView;
@FXML
TableColumn<DepositListItem, DepositListItem> selectColumn, addressColumn, balanceColumn, confidenceColumn, statusColumn;
TableColumn<DepositListItem, DepositListItem> selectColumn, addressColumn, balanceColumn, confidenceColumn, usageColumn;
private ImageView qrCodeImageView;
private int gridRow = 0;
private AddressTextField addressTextField;
Button generateNewAddressButton;
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 Preferences preferences;
private final BtcAddressValidator btcAddressValidator;
private final WalletPasswordWindow walletPasswordWindow;
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
private final ObservableList<DepositListItem> depositAddresses = FXCollections.observableArrayList();
private final ObservableList<DepositListItem> observableList = FXCollections.observableArrayList();
private final SortedList<DepositListItem> sortedList = new SortedList<>(observableList);
private BalanceListener balanceListener;
private TitledGroupBg titledGroupBg;
private Label addressLabel;
private Label addressLabel, amountLabel;
private Label qrCodeLabel;
private InputTextField amountTextField;
private Subscription amountTextFieldSubscription;
private String paymentLabel;
///////////////////////////////////////////////////////////////////////////////////////////
@ -103,36 +98,34 @@ public class DepositView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private DepositView(WalletService walletService, TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager,
BSFormatter formatter, Preferences preferences,
BtcAddressValidator btcAddressValidator, WalletPasswordWindow walletPasswordWindow,
OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
private DepositView(WalletService walletService,
BSFormatter formatter,
Preferences preferences) {
this.walletService = walletService;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
this.openOfferManager = openOfferManager;
this.formatter = formatter;
this.preferences = preferences;
this.btcAddressValidator = btcAddressValidator;
this.walletPasswordWindow = walletPasswordWindow;
this.offerDetailsWindow = offerDetailsWindow;
this.tradeDetailsWindow = tradeDetailsWindow;
}
@Override
public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No deposit addresses are generated yet"));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No deposit addresses are generated yet"));
setSelectColumnCellFactory();
setAddressColumnCellFactory();
setStatusColumnCellFactory();
setBalanceColumnCellFactory();
setUsageColumnCellFactory();
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);
//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:");
addressLabel = addressTuple.first;
GridPane.setValignment(addressLabel, VPos.TOP);
GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0));
//GridPane.setValignment(addressLabel, VPos.TOP);
//GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0));
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.setManaged(false);
@ -162,16 +164,18 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressLabel.setManaged(false);
addressTextField.setVisible(false);
addressTextField.setManaged(false);
amountLabel.setVisible(false);
amountTextField.setManaged(false);
generateNewAddressButton = addButton(gridPane, ++gridRow, "Generate new address", -20);
GridPane.setColumnIndex(generateNewAddressButton, 0);
GridPane.setHalignment(generateNewAddressButton, HPos.LEFT);
generateNewAddressButton.setOnAction(event -> {
boolean hasUnUsedAddress = walletService.getSavingsAddressEntryList().stream()
.filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
.findAny().isPresent();
boolean hasUnUsedAddress = observableList.stream().filter(e -> e.getNumTxOutputs() == 0).findAny().isPresent();
if (hasUnUsedAddress) {
new Popup().warning("You have already addresses generated which are still not used.\n" +
"Please select in the address table an unused address.").show();
new Popup().warning("You have addresses which are not used in any transaction.\n" +
"Please select in the address table any unused address.").show();
} else {
AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry();
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
private String getBitcoinURI() {
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
null,
null,
getAmountAsCoin(),
paymentLabel,
null);
}
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
walletService.addBalanceListener(balanceListener);
amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> {
addressTextField.setAmountAsCoin(formatter.parseToCoin(t));
updateQRCode();
});
}
@Override
protected void deactivate() {
depositAddresses.forEach(DepositListItem::cleanup);
sortedList.comparatorProperty().unbind();
observableList.forEach(DepositListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
amountTextFieldSubscription.unsubscribe();
}
@ -225,22 +248,30 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressLabel.setManaged(true);
addressTextField.setVisible(true);
addressTextField.setManaged(true);
amountLabel.setVisible(true);
amountTextField.setManaged(true);
GridPane.setMargin(generateNewAddressButton, new Insets(15, 0, 0, 0));
addressTextField.setAddress(address);
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage);
updateQRCode();
}
private void updateQRCode() {
if (addressTextField.getAddress() != null && !addressTextField.getAddress().isEmpty()) {
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage);
}
}
private void openBlockExplorer(DepositListItem item) {
if (item.getAddressString() != null) {
try {
@ -258,10 +289,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
depositAddresses.clear();
observableList.clear();
walletService.getSavingsAddressEntryList().stream()
.forEach(e -> depositAddresses.add(new DepositListItem(e, walletService, formatter)));
table.setItems(depositAddresses);
.forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter)));
}
@ -269,9 +299,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
// ColumnCellFactories
///////////////////////////////////////////////////////////////////////////////////////////
private void setStatusColumnCellFactory() {
statusColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
statusColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>,
private void setUsageColumnCellFactory() {
usageColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
usageColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>,
TableCell<DepositListItem, DepositListItem>>() {
@Override
@ -283,7 +313,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(new Label(item.getStatus()));
setGraphic(new Label(item.getUsage()));
} else {
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() {
confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));

View File

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

View File

@ -18,7 +18,6 @@
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?>
<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"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="180" maxWidth="180"/>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="260"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="320">
<cellValueFactory>
<PropertyValueFactory property="addressString"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Address" fx:id="addressColumn" minWidth="320"/>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="110"/>
</columns>
</TableView>

View File

@ -38,6 +38,7 @@ import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
@ -53,7 +54,7 @@ import java.util.stream.Stream;
@FxmlView
public class ReservedView extends ActivatableView<VBox, Void> {
@FXML
TableView<ReservedListItem> table;
TableView<ReservedListItem> tableView;
@FXML
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 OfferDetailsWindow offerDetailsWindow;
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;
@ -87,13 +89,21 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@Override
public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No funds are reserved in open offers or trades"));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No funds are reserved in open offers or trades"));
setDateColumnCellFactory();
setDetailsColumnCellFactory();
setAddressColumnCellFactory();
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() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
@ -104,6 +114,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
walletService.addBalanceListener(balanceListener);
@ -111,7 +123,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@Override
protected void deactivate() {
reservedAddresses.forEach(ReservedListItem::cleanup);
sortedList.comparatorProperty().unbind();
observableList.forEach(ReservedListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
}
@ -121,13 +134,12 @@ public class ReservedView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
reservedAddresses.forEach(ReservedListItem::cleanup);
reservedAddresses.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
observableList.forEach(ReservedListItem::cleanup);
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))
.collect(Collectors.toList()));
reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()));
table.setItems(reservedAddresses);
}
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.Trade;
import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Tooltip;
import org.bitcoinj.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Optional;
public class TransactionsListItem {
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 WalletService walletService;
private final ConfidenceProgressIndicator progressIndicator;
private final Tooltip tooltip;
private boolean isInternal;
@Nullable
private Tradable tradable;
private String details;
private String addressString;
@ -49,18 +49,19 @@ public class TransactionsListItem {
private TxConfidenceListener txConfidenceListener;
private boolean received;
private boolean detailsAvailable;
private Coin amountAsCoin = Coin.ZERO;
private BSFormatter formatter;
public TransactionsListItem(Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
this.formatter = formatter;
txId = transaction.getHashAsString();
this.walletService = walletService;
Coin valueSentToMe = transaction.getValueSentToMe(walletService.getWallet());
Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet());
Coin amountAsCoin;
Address address = null;
if (valueSentToMe.isZero()) {
amountAsCoin = valueSentFromMe;
amount.set("-" + formatter.formatCoin(amountAsCoin));
amountAsCoin = valueSentFromMe.multiply(-1);
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
if (!transactionOutput.isMine(walletService.getWallet())) {
@ -75,7 +76,7 @@ public class TransactionsListItem {
}
} else if (valueSentFromMe.isZero()) {
amountAsCoin = valueSentToMe;
amount.set(formatter.formatCoin(amountAsCoin));
direction = "Received with:";
received = true;
@ -88,9 +89,8 @@ public class TransactionsListItem {
}
}
}
} else {
} else/* if (tradableOptional.isPresent())*/ {
amountAsCoin = valueSentToMe.subtract(valueSentFromMe);
amount.set(formatter.formatCoin(amountAsCoin));
boolean outgoing = false;
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
if (!transactionOutput.isMine(walletService.getWallet())) {
@ -107,7 +107,25 @@ public class TransactionsListItem {
direction = "Sent to:";
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()) {
@ -141,11 +159,11 @@ public class TransactionsListItem {
} else {
if (amountAsCoin.isZero())
details = "No refund from dispute";
else
else if (!isInternal)
details = received ? "Received funds" : "Withdrawn from wallet";
}
date.set(formatter.formatDateTime(transaction.getUpdateTime()));
date = formatter.formatDateTime(transaction.getUpdateTime());
// confidence
progressIndicator = new ConfidenceProgressIndicator();
@ -202,14 +220,20 @@ public class TransactionsListItem {
return progressIndicator;
}
public final StringProperty dateProperty() {
return this.date;
public final String getDate() {
return date;
}
public final StringProperty amountProperty() {
return this.amount;
public String getAmount() {
return formatter.formatCoin(amountAsCoin);
}
public Coin getAmountAsCoin() {
return amountAsCoin;
}
public String getAddressString() {
return addressString;
}
@ -218,6 +242,10 @@ public class TransactionsListItem {
return direction;
}
public boolean isInternal() {
return isInternal;
}
public String getTxId() {
return txId;
}
@ -234,6 +262,7 @@ public class TransactionsListItem {
return detailsAvailable;
}
@Nullable
public Tradable getTradable() {
return tradable;
}

View File

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

View File

@ -40,6 +40,7 @@ import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
@ -58,12 +59,12 @@ import java.util.stream.Stream;
public class TransactionsView extends ActivatableView<VBox, Void> {
@FXML
TableView<TransactionsListItem> table;
TableView<TransactionsListItem> tableView;
@FXML
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, typeColumn,
confidenceColumn;
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, 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 TradeManager tradeManager;
@ -102,13 +103,31 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override
public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No transactions available"));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No transactions available"));
setDateColumnCellFactory();
setDetailsColumnCellFactory();
setAddressColumnCellFactory();
setTransactionColumnCellFactory();
setAmountColumnCellFactory();
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() {
@Override
@ -149,13 +168,17 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
walletService.getWallet().addEventListener(walletEventListener);
}
@Override
protected void deactivate() {
transactionsListItems.forEach(TransactionsListItem::cleanup);
sortedList.comparatorProperty().unbind();
observableList.forEach(TransactionsListItem::cleanup);
walletService.getWallet().removeEventListener(walletEventListener);
}
@ -170,7 +193,40 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream());
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 -> {
Optional<Tradable> tradableOptional = all.stream()
.filter(tradable -> {
@ -202,10 +258,15 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
})
.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
transactionsListItems.forEach(TransactionsListItem::cleanup);
transactionsListItems.setAll(listItems);
table.setItems(transactionsListItems);
observableList.forEach(TransactionsListItem::cleanup);
observableList.setAll(transactionsListItems);
}
private void openBlockExplorer(TransactionsListItem item) {
@ -232,6 +293,33 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
// 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() {
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
detailsColumn.setCellFactory(
@ -289,7 +377,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
if (item != null && !empty) {
String addressString = item.getAddressString();
field = new AddressWithIconAndDirection(item.getDirection(), addressString,
AwesomeIcon.EXTERNAL_LINK, item.getReceived());
AwesomeIcon.EXTERNAL_LINK, item.getReceived(), item.isInternal());
field.setOnAction(event -> openBlockExplorer(item));
field.setTooltip(new Tooltip("Open external blockchain explorer for " +
"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() {
confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));

View File

@ -19,7 +19,6 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.withdrawal.WithdrawalView"
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"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<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="Details" fx:id="detailsColumn" minWidth="160"/>
<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="Address" fx:id="addressColumn" minWidth="320"/>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="310" maxWidth="310"/>
</columns>
</TableView>

View File

@ -20,7 +20,6 @@ package io.bitsquare.gui.main.funds.withdrawal;
import com.google.common.util.concurrent.FutureCallback;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
@ -46,6 +45,7 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
@ -59,10 +59,7 @@ import org.jetbrains.annotations.NotNull;
import org.spongycastle.crypto.params.KeyParameter;
import javax.inject.Inject;
import java.util.Date;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -72,11 +69,11 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML
Button withdrawButton;
@FXML
TableView<WithdrawalListItem> table;
TableView<WithdrawalListItem> tableView;
@FXML
TextField withdrawFromTextField, withdrawToTextField, amountTextField;
@FXML
TableColumn<WithdrawalListItem, WithdrawalListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, selectColumn;
TableColumn<WithdrawalListItem, WithdrawalListItem> addressColumn, balanceColumn, selectColumn;
private final WalletService walletService;
private final TradeManager tradeManager;
@ -89,7 +86,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private final WalletPasswordWindow walletPasswordWindow;
private final OfferDetailsWindow offerDetailsWindow;
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 BalanceListener balanceListener;
private Set<String> fromAddresses;
@ -121,15 +119,18 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@Override
public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No funds for withdrawal are available"));
setDateColumnCellFactory();
setDetailsColumnCellFactory();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No funds for withdrawal are available"));
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
setAddressColumnCellFactory();
setBalanceColumnCellFactory();
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() {
@Override
@ -141,6 +142,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
reset();
@ -152,7 +155,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@Override
protected void deactivate() {
fundedAddresses.forEach(WithdrawalListItem::cleanup);
sortedList.comparatorProperty().unbind();
observableList.forEach(WithdrawalListItem::cleanup);
withdrawButton.disableProperty().unbind();
walletService.removeBalanceListener(balanceListener);
}
@ -174,6 +178,14 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
} else {
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
@ -283,32 +295,16 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private void updateList() {
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())
.collect(Collectors.toSet());
fundedAddresses.forEach(WithdrawalListItem::cleanup);
fundedAddresses.setAll(walletService.getAddressEntryList().stream()
observableList.forEach(WithdrawalListItem::cleanup);
observableList.setAll(walletService.getAddressEntryList().stream()
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
.filter(e -> !reservedTrades.contains(e.getOfferId()))
.map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, formatter))
.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) {
@ -335,7 +331,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private void reset() {
selectedItems = new HashSet<>();
table.getSelectionModel().clearSelection();
tableView.getSelectionModel().clearSelection();
withdrawFromTextField.setText("");
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");
if (BitsquareApp.DEV_MODE)
withdrawToTextField.setText("mi8k5f9L972VgDaT4LgjAhriC9hHEPL7EW");
withdrawToTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi");
}
private Optional<Tradable> getTradable(WithdrawalListItem item) {
@ -374,92 +370,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
// 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() {
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
addressColumn.setCellFactory(

View File

@ -23,6 +23,7 @@ import io.bitsquare.gui.components.TableGroupHeadline;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CurrencyUtil;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
@ -38,7 +39,8 @@ import javax.inject.Inject;
public class MarketsStatisticsView extends ActivatableViewAndModel<GridPane, MarketsStatisticViewModel> {
private final BSFormatter formatter;
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));
root.getChildren().add(header);
statisticsTableView = new TableView<>();
GridPane.setRowIndex(statisticsTableView, gridRow);
GridPane.setMargin(statisticsTableView, new Insets(20, -10, -10, -10));
GridPane.setVgrow(statisticsTableView, Priority.ALWAYS);
GridPane.setHgrow(statisticsTableView, Priority.ALWAYS);
root.getChildren().add(statisticsTableView);
statisticsTableView.getColumns().add(getCurrencyColumn());
statisticsTableView.getColumns().add(getNumberOfOffersColumn());
statisticsTableView.getColumns().add(getTotalAmountColumn());
statisticsTableView.getColumns().add(getSpreadColumn());
statisticsTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView = new TableView<>();
GridPane.setRowIndex(tableView, gridRow);
GridPane.setMargin(tableView, new Insets(20, -10, -10, -10));
GridPane.setVgrow(tableView, Priority.ALWAYS);
GridPane.setHgrow(tableView, Priority.ALWAYS);
root.getChildren().add(tableView);
Label placeholder = new Label("Currently there is no data available");
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
protected void activate() {
statisticsTableView.setItems(model.marketStatisticItems);
sortedList = new SortedList<>(model.marketStatisticItems);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
}
@Override
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.common.UserThread;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.overlays.notifications.Notification;
import io.bitsquare.gui.main.overlays.popups.Popup;
@ -64,13 +65,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
class CreateOfferDataModel extends ActivatableDataModel {
private final OpenOfferManager openOfferManager;
private final WalletService walletService;
final WalletService walletService;
private final TradeWalletService tradeWalletService;
private final Preferences preferences;
private final User user;
private final KeyRing keyRing;
private final P2PService p2PService;
private final PriceFeed priceFeed;
private Navigation navigation;
private final WalletPasswordWindow walletPasswordWindow;
private final BlockchainService blockchainService;
private final BSFormatter formatter;
@ -98,12 +100,16 @@ class CreateOfferDataModel extends ActivatableDataModel {
final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> volumeAsFiat = 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();
PaymentAccount paymentAccount;
private boolean isTabSelected;
private Notification walletFundedNotification;
boolean useSavingsWallet;
Coin totalAvailableBalance;
///////////////////////////////////////////////////////////////////////////////////////////
@ -113,6 +119,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
@Inject
CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService,
Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeed priceFeed,
Navigation navigation,
WalletPasswordWindow walletPasswordWindow, BlockchainService blockchainService, BSFormatter formatter) {
this.openOfferManager = openOfferManager;
this.walletService = walletService;
@ -122,6 +129,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
this.keyRing = keyRing;
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.navigation = navigation;
this.walletPasswordWindow = walletPasswordWindow;
this.blockchainService = blockchainService;
this.formatter = formatter;
@ -135,7 +143,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
updateBalance();
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
@ -172,7 +180,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
addListeners();
paymentAccounts.setAll(user.getPaymentAccounts());
updateBalance(walletService.getBalanceForAddress(getAddressEntry().getAddress()));
calculateTotalToPay();
updateBalance();
if (direction == Offer.Direction.BUY)
calculateTotalToPay();
@ -290,7 +299,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
}
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) {
@ -311,6 +320,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
}
}
void useSavingsWalletForFunding() {
useSavingsWallet = true;
updateBalance();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
@ -380,16 +394,30 @@ class CreateOfferDataModel extends ActivatableDataModel {
void calculateTotalToPay() {
if (securityDepositAsCoin != null) {
if (direction == Offer.Direction.BUY)
totalToPayAsCoin.set(offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin));
else
totalToPayAsCoin.set(offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get()));
Coin feeAndSecDeposit = 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);
}
}
private void updateBalance(Coin balance) {
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
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
balance.set(totalAvailableBalance);
} else {
balance.set(tradeWalletBalance);
}
missingCoin.set(totalToPayAsCoin.get().subtract(balance.get()));
isWalletFunded.set(isBalanceSufficient(balance.get()));
if (isWalletFunded.get()) {
walletService.removeBalanceListener(balanceListener);
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() {
return offerFeeAsCoin;
}
@ -423,4 +455,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
public Preferences getPreferences() {
return preferences;
}
public void swapTradeToSavings() {
walletService.swapTradeToSavings(getOfferId());
}
}

View File

@ -69,6 +69,7 @@ import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
@ -85,13 +86,12 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private ImageView imageView;
private AddressTextField addressTextField;
private BalanceTextField balanceTextField;
private ProgressIndicator spinner;
private TitledGroupBg payFundsPane;
private Button nextButton, cancelButton1, cancelButton2, placeOfferButton;
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, placeOfferButton;
private InputTextField amountTextField, minAmountTextField, priceTextField, volumeTextField;
private TextField currencyTextField;
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel, amountBtcLabel, priceCurrencyLabel,
volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, spinnerInfoLabel, currencyTextFieldLabel,
volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel, volumeDescriptionLabel, currencyTextFieldLabel,
currencyComboBoxLabel;
private TextFieldWithCopyIcon totalToPayTextField;
private ComboBox<PaymentAccount> paymentAccountsComboBox;
@ -108,7 +108,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private ChangeListener<Boolean> showWarningInvalidFiatDecimalPlacesPlacesListener;
private ChangeListener<Boolean> showWarningAdjustedVolumeListener;
private ChangeListener<String> errorMessageListener;
private ChangeListener<Boolean> isSpinnerVisibleListener;
private ChangeListener<Boolean> placeOfferCompletedListener;
private ChangeListener<Coin> feeFromFundingTxListener;
private EventHandler<ActionEvent> paymentAccountsComboBoxSelectionHandler;
@ -118,6 +117,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private final Preferences preferences;
private ChangeListener<String> tradeCurrencyCodeListener;
private ImageView qrCodeImageView;
private ChangeListener<Coin> balanceListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -143,7 +143,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
createListeners();
balanceTextField.setup(model.address.get(), model.getFormatter());
balanceTextField.setFormatter(model.getFormatter());
balanceListener = (observable, oldValue, newValue) -> balanceTextField.setBalance(newValue);
paymentAccountsComboBox.setConverter(new StringConverter<PaymentAccount>() {
@Override
public String toString(PaymentAccount paymentAccount) {
@ -173,8 +175,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
onPaymentAccountsComboBoxSelected();
if (spinner != null && placeOfferButton.isVisible())
spinner.setProgress(-1);
balanceTextField.setBalance(model.dataModel.balance.get());
}
@Override
@ -183,9 +184,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
removeListeners();
if (balanceTextField != null)
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
public void onClose() {
// 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" +
"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) {
@ -264,8 +267,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
currencyComboBox.setMouseTransparent(true);
paymentAccountsComboBox.setMouseTransparent(true);
spinner.setProgress(-1);
if (!BitsquareApp.DEV_MODE) {
String key = "securityDepositInfo";
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);
balanceLabel.setVisible(true);
balanceTextField.setVisible(true);
fundFromSavingsWalletButton.setVisible(true);
fundFromExternalWalletButton.setVisible(true);
placeOfferButton.setVisible(true);
cancelButton2.setVisible(true);
//root.requestFocus();
@ -392,7 +395,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
totalToPayTextField.textProperty().bind(model.totalToPay);
addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin);
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
// Validation
amountTextField.validationResultProperty().bind(model.amountValidationResult);
@ -403,10 +406,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
// buttons
placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled);
cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
spinner.visibleProperty().bind(model.isSpinnerVisible);
spinnerInfoLabel.visibleProperty().bind(model.isSpinnerVisible);
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
fundFromSavingsWalletButton.disableProperty().bind(model.dataModel.isWalletFunded);
fundFromExternalWalletButton.disableProperty().bind(model.dataModel.isWalletFunded);
// payment account
currencyComboBox.prefWidthProperty().bind(paymentAccountsComboBox.widthProperty());
@ -438,9 +439,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
volumeTextField.validationResultProperty().unbind();
placeOfferButton.disableProperty().unbind();
cancelButton2.disableProperty().unbind();
spinner.visibleProperty().unbind();
spinnerInfoLabel.visibleProperty().unbind();
spinnerInfoLabel.textProperty().unbind();
currencyComboBox.managedProperty().unbind();
currencyComboBoxLabel.visibleProperty().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.")
.show(), 100, TimeUnit.MILLISECONDS);
};
isSpinnerVisibleListener = (ov, oldValue, newValue) -> spinner.setProgress(newValue ? -1 : 0);
feeFromFundingTxListener = (observable, oldValue, newValue) -> {
log.debug("feeFromFundingTxListener " + newValue);
@ -514,6 +511,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
.closeButtonText("Close")
.onClose(() -> {
close();
model.dataModel.swapTradeToSavings();
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
})
.show();
@ -558,6 +556,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void addListeners() {
model.tradeCurrencyCode.addListener(tradeCurrencyCodeListener);
model.dataModel.balance.addListener(balanceListener);
// focus out
amountTextField.focusedProperty().addListener(amountFocusedListener);
@ -570,7 +569,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
model.showWarningInvalidFiatDecimalPlaces.addListener(showWarningInvalidFiatDecimalPlacesPlacesListener);
model.showWarningAdjustedVolume.addListener(showWarningAdjustedVolumeListener);
model.errorMessage.addListener(errorMessageListener);
model.isSpinnerVisible.addListener(isSpinnerVisibleListener);
model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
model.placeOfferCompleted.addListener(placeOfferCompletedListener);
@ -582,6 +580,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void removeListeners() {
model.tradeCurrencyCode.removeListener(tradeCurrencyCodeListener);
model.dataModel.balance.removeListener(balanceListener);
// focus out
amountTextField.focusedProperty().removeListener(amountFocusedListener);
@ -594,7 +593,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
model.showWarningInvalidFiatDecimalPlaces.removeListener(showWarningInvalidFiatDecimalPlacesPlacesListener);
model.showWarningAdjustedVolume.removeListener(showWarningAdjustedVolumeListener);
model.errorMessage.removeListener(errorMessageListener);
model.isSpinnerVisible.removeListener(isSpinnerVisibleListener);
model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
model.placeOfferCompleted.removeListener(placeOfferCompletedListener);
@ -698,7 +696,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
//UserThread.runAfter(() -> nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS);
cancelButton1 = tuple.second;
cancelButton1.setDefaultButton(false);
cancelButton1.setOnAction(e -> close());
cancelButton1.setOnAction(e -> {
close();
model.dataModel.swapTradeToSavings();
});
cancelButton1.setId("cancel-button");
GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0));
@ -757,27 +758,47 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
balanceTextField = balanceTuple.second;
balanceTextField.setVisible(false);
Tuple3<Button, ProgressIndicator, Label> placeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "");
placeOfferButton = placeOfferTuple.first;
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, "Transfer from Bitsquare wallet", "Fund from external wallet");
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.setOnAction(e -> onPlaceOffer());
placeOfferButton.setMinHeight(40);
placeOfferButton.setPadding(new Insets(0, 20, 0, 20));
spinner = placeOfferTuple.second;
spinnerInfoLabel = placeOfferTuple.third;
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
cancelButton2.setOnAction(e -> {
if (model.dataModel.isWalletFunded.get())
if (model.dataModel.isWalletFunded.get()) {
new Popup().warning("You have already paid in the funds.\n" +
"Are you sure you want to cancel.")
.actionButtonText("No")
.closeButtonText("Yes, close")
.onClose(() -> close())
.onClose(() -> {
close();
model.dataModel.swapTradeToSavings();
})
.show();
else
} else {
close();
model.dataModel.swapTradeToSavings();
}
});
cancelButton2.setDefaultButton(false);
cancelButton2.setVisible(false);
@ -786,7 +807,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
@NotNull
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) : "";
}

View File

@ -18,7 +18,6 @@
package io.bitsquare.gui.main.offer.createoffer;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed;
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.ViewModel;
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.settings.SettingsView;
import io.bitsquare.gui.main.settings.preferences.PreferencesView;
@ -77,12 +78,10 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final StringProperty errorMessage = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty();
final StringProperty tradeCurrencyCode = new SimpleStringProperty();
final StringProperty spinnerInfoText = new SimpleStringProperty("");
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty cancelButtonDisabled = new SimpleBooleanProperty();
final BooleanProperty isNextButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty();
final BooleanProperty showWarningAdjustedVolume = new SimpleBooleanProperty();
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
@ -95,7 +94,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
// Those are needed for the addressTextField
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
private ChangeListener<String> amountListener;
@ -108,7 +106,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<Fiat> volumeAsFiatListener;
private ChangeListener<Boolean> isWalletFundedListener;
private ChangeListener<Coin> feeFromFundingTxListener;
private ChangeListener<String> requestPlaceOfferErrorMessageListener;
private ChangeListener<String> errorMessageListener;
private Offer offer;
private Timer timeoutTimer;
@ -170,8 +167,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
directionLabel = BSResources.get("shared.sellBitcoin");
amountDescription = BSResources.get("createOffer.amountPriceBox.amountDescription", BSResources.get("shared.sell"));
}
updateSpinnerInfo();
}
@Override
@ -181,16 +176,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
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() {
if (dataModel.getDirection() == Offer.Direction.BUY) {
volumeDescriptionLabel.bind(createStringBinding(
@ -212,7 +197,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
tradeAmount.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.amountAsCoin.get()),
dataModel.amountAsCoin));
totalToPayAsCoin.bind(dataModel.totalToPayAsCoin);
btcCode.bind(dataModel.btcCode);
tradeCurrencyCode.bind(dataModel.tradeCurrencyCode);
@ -221,7 +205,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private void removeBindings() {
totalToPay.unbind();
tradeAmount.unbind();
totalToPayAsCoin.unbind();
btcCode.unbind();
tradeCurrencyCode.unbind();
volumeDescriptionLabel.unbind();
@ -265,20 +248,9 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
isWalletFundedListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
spinnerInfoText.set("Checking funding tx miner fee...");
};
feeFromFundingTxListener = (ov, oldValue, newValue) -> {
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.isWalletFunded.addListener(isWalletFundedListener);
errorMessage.addListener(requestPlaceOfferErrorMessageListener);
}
private void removeListeners() {
@ -315,7 +286,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
dataModel.isWalletFunded.removeListener(isWalletFundedListener);
errorMessage.removeListener(requestPlaceOfferErrorMessageListener);
if (offer != null && errorMessageListener != null)
offer.errorMessageProperty().removeListener(errorMessageListener);
@ -420,9 +390,27 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
}
}
public void onShowPayFundsScreen() {
void onShowPayFundsScreen() {
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);
isPlaceOfferButtonDisabled.set(!(inputDataValid &&
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<PaymentMethod> paymentMethodComboBox;
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 OfferView.OfferActionHandler offerActionHandler;
@ -155,7 +155,8 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
paymentMethodColumn = getPaymentMethodColumn();
tableView.getColumns().add(paymentMethodColumn);
tableView.getColumns().add(getActionColumn());
tableView.getColumns().add(getAvatarColumn());
avatarColumn = getAvatarColumn();
tableView.getColumns().add(avatarColumn);
tableView.getSortOrder().add(priceColumn);
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()));
volumeColumn.setComparator((o1, o2) -> o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume()));
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.setMinHeight(40);
@ -218,7 +220,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
tableView.setItems(model.getOfferList());
priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
tableView.sort();
}
@Override
@ -572,7 +573,10 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
if (button != null)
button.setOnAction(null);
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) {
tradeManager.onTakeOffer(amountAsCoin.get(),
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
offer,
paymentAccount.getId(),
tradeResultHandler

View File

@ -353,7 +353,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
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;
paymentAccountsLabel.setVisible(showComboBox);

View File

@ -625,7 +625,6 @@ public abstract class Overlay<T extends Overlay> {
messageLabel = new Label(truncatedMessage);
messageLabel.setMouseTransparent(true);
messageLabel.setWrapText(true);
messageLabel.setId("popup-message");
GridPane.setHalignment(messageLabel, HPos.LEFT);
GridPane.setHgrow(messageLabel, Priority.ALWAYS);
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 javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;
import org.slf4j.Logger;
@ -15,9 +18,10 @@ import java.io.ByteArrayInputStream;
public class QRCodeWindow extends Overlay<QRCodeWindow> {
private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class);
private final ImageView qrCodeImageView;
private String bitcoinURI;
public QRCodeWindow(String bitcoinURI) {
this.bitcoinURI = bitcoinURI;
final byte[] imageBytes = QRCode
.from(bitcoinURI)
.withSize(250, 250)
@ -45,6 +49,18 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
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();
applyStyles();
display();

View File

@ -26,15 +26,15 @@
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<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="Trade amount in BTC" fx:id="amountColumn" minWidth="130"/>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
<TableColumn text="Trade amount" fx:id="volumeColumn" minWidth="130"/>
<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"/>
</columns>
</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.util.BSFormatter;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import javax.inject.Inject;
@ -40,13 +44,14 @@ import javax.inject.Inject;
public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTradesViewModel> {
@FXML
TableView<ClosedTradableListItem> table;
TableView<ClosedTradableListItem> tableView;
@FXML
TableColumn<ClosedTradableListItem, ClosedTradableListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn;
private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
private SortedList<ClosedTradableListItem> sortedList;
@Inject
public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
@ -58,6 +63,9 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
@Override
public void initialize() {
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No closed trades available"));
setTradeIdColumnCellFactory();
setDirectionColumnCellFactory();
setAmountColumnCellFactory();
@ -67,13 +75,55 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
setStateColumnCellFactory();
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
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"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<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="Trade amount in BTC" fx:id="amountColumn" minWidth="130"/>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
<TableColumn text="Trade amount in EUR" fx:id="volumeColumn" minWidth="130"/>
<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>
</TableView>

View File

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

View File

@ -26,9 +26,9 @@
<Insets bottom="0.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<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="Amount in BTC (Min.)" fx:id="amountColumn" minWidth="130"/>
<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.trade.offer.OpenOffer;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
@ -40,12 +41,13 @@ import javax.inject.Inject;
public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersViewModel> {
@FXML
TableView<OpenOfferListItem> table;
TableView<OpenOfferListItem> tableView;
@FXML
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, offerIdColumn, removeItemColumn;
private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow;
private SortedList<OpenOfferListItem> sortedList;
@Inject
public OpenOffersView(OpenOffersViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow) {
@ -64,13 +66,30 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
setDateColumnCellFactory();
setRemoveColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No open offers available"));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
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
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) {

View File

@ -18,7 +18,6 @@
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?>
<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"/>
</padding>
<TableView fx:id="table" VBox.vgrow="SOMETIMES">
<TableView fx:id="tableView" VBox.vgrow="SOMETIMES">
<columns>
<TableColumn text="Trade ID" fx:id="idColumn" minWidth="100" sortable="false">
<cellValueFactory>
<PropertyValueFactory property="id"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Trade ID" fx:id="idColumn" minWidth="100"/>
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="130"/>
<TableColumn text="Trade amount in BTC" fx:id="tradeAmountColumn" minWidth="130">
<cellValueFactory>
<PropertyValueFactory property="tradeAmount"/>
</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="Trade amount in BTC" fx:id="tradeAmountColumn" minWidth="130"/>
<TableColumn text="Price" fx:id="priceColumn" minWidth="100"/>
<TableColumn text="Trade amount" fx:id="tradeVolumeColumn" minWidth="130"/>
<TableColumn text="Payment method" fx:id="paymentMethodColumn" minWidth="120"/>
<TableColumn text="My role" fx:id="roleColumn" minWidth="120" maxWidth="120"/>
<TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/>

View File

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

View File

@ -48,7 +48,7 @@ public class BuyerStep5View extends TradeStepView {
protected Label btcTradeAmountLabel;
protected Label fiatTradeAmountLabel;
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);
addLabelTextField(gridPane, gridRow, "Amount to withdraw:", model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
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) {
withdrawAddressTextField.setText("mi8k5f9L972VgDaT4LgjAhriC9hHEPL7EW");
withdrawAddressTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi");
} else {
String key = "tradeCompleted" + trade.getId();
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() {
Coin senderAmount = trade.getPayoutAmount();
WalletService walletService = model.dataModel.walletService;
@ -174,11 +159,11 @@ public class BuyerStep5View extends TradeStepView {
validateWithdrawAddress();
} else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
try {
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null);
Coin receiverAmount = senderAmount.subtract(requiredFee);
if (BitsquareApp.DEV_MODE) {
doWithdrawal();
} else {
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null);
Coin receiverAmount = senderAmount.subtract(requiredFee);
BSFormatter formatter = model.formatter;
String key = "reviewWithdrawalAtTradeComplete";
if (preferences.showAgain(key)) {
@ -190,9 +175,12 @@ public class BuyerStep5View extends TradeStepView {
"The recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" +
"Are you sure you want to proceed with the withdrawal?")
.closeButtonText("Cancel")
.onClose(() -> withdrawButton.setDisable(false))
.onClose(() -> {
useSavingsWalletButton.setDisable(false);
withdrawToExternalWalletButton.setDisable(false);
})
.actionButtonText("Yes")
.onAction(this::doWithdrawal)
.onAction(() -> doWithdrawal())
.dontShowAgainId(key, preferences)
.show();
} 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() {
withdrawAddressTextField.setValidator(model.btcAddressValidator);
withdrawAddressTextField.requestFocus();
withdrawButton.requestFocus();
useSavingsWalletButton.requestFocus();
}
protected String getBtcTradeAmountLabel() {

View File

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

View File

@ -838,17 +838,6 @@ public class FormBuilder {
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
@ -913,4 +902,21 @@ public class FormBuilder {
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
result = expirableMailboxStoragePayload.receiverPubKeyForRemoveOperation.equals(protectedStorageEntry.ownerPubKey);
} 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());
}

View File

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

View File

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

View File

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