mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-29 01:38:39 -04:00
Improve tx and withdrawal screens, make multiple address selection possible at withdrawal
This commit is contained in:
parent
85b2cb1d44
commit
c70df863d6
31 changed files with 930 additions and 354 deletions
|
@ -99,8 +99,8 @@ public class PubKeyRing implements Serializable {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PubKeyRing{" +
|
return "PubKeyRing{" +
|
||||||
"signaturePubKey.hashCode()=\n" + signaturePubKey.hashCode() +
|
"signaturePubKey.hashCode()=" + signaturePubKey.hashCode() +
|
||||||
"encryptionPubKey.hashCode()=\n" + encryptionPubKey.hashCode() +
|
", encryptionPubKey.hashCode()=" + encryptionPubKey.hashCode() +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import io.bitsquare.trade.Contract;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import org.bitcoinj.core.Transaction;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ public class Dispute implements Serializable {
|
||||||
|
|
||||||
private boolean isClosed;
|
private boolean isClosed;
|
||||||
private DisputeResult disputeResult;
|
private DisputeResult disputeResult;
|
||||||
|
private Transaction disputePayoutTx;
|
||||||
|
|
||||||
transient private Storage<DisputeList<Dispute>> storage;
|
transient private Storage<DisputeList<Dispute>> storage;
|
||||||
transient private ObservableList<DisputeDirectMessage> disputeDirectMessagesAsObservableList = FXCollections.observableArrayList(disputeDirectMessages);
|
transient private ObservableList<DisputeDirectMessage> disputeDirectMessagesAsObservableList = FXCollections.observableArrayList(disputeDirectMessages);
|
||||||
|
@ -166,6 +168,11 @@ public class Dispute implements Serializable {
|
||||||
storage.queueUpForSave();
|
storage.queueUpForSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDisputePayoutTx(Transaction disputePayoutTx) {
|
||||||
|
this.disputePayoutTx = disputePayoutTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -266,6 +273,9 @@ public class Dispute implements Serializable {
|
||||||
return new Date(tradeDate);
|
return new Date(tradeDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Transaction getDisputePayoutTx() {
|
||||||
|
return disputePayoutTx;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
|
|
@ -510,6 +510,7 @@ public class DisputeManager {
|
||||||
|
|
||||||
// after successful publish we send peer the tx
|
// after successful publish we send peer the tx
|
||||||
|
|
||||||
|
dispute.setDisputePayoutTx(transaction);
|
||||||
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
|
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,7 +547,8 @@ public class DisputeManager {
|
||||||
|
|
||||||
// losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer
|
// losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer
|
||||||
private void onDisputedPayoutTxMessage(PeerPublishedPayoutTxMessage peerPublishedPayoutTxMessage) {
|
private void onDisputedPayoutTxMessage(PeerPublishedPayoutTxMessage peerPublishedPayoutTxMessage) {
|
||||||
tradeWalletService.addTransactionToWallet(peerPublishedPayoutTxMessage.transaction);
|
Transaction transaction = tradeWalletService.addTransactionToWallet(peerPublishedPayoutTxMessage.transaction);
|
||||||
|
findOwnDispute(peerPublishedPayoutTxMessage.tradeId).ifPresent(dispute -> dispute.setDisputePayoutTx(transaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,9 @@ import org.bitcoinj.wallet.CoinSelector;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
@ -39,7 +37,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
class AddressBasedCoinSelector implements CoinSelector {
|
class AddressBasedCoinSelector implements CoinSelector {
|
||||||
private static final Logger log = LoggerFactory.getLogger(AddressBasedCoinSelector.class);
|
private static final Logger log = LoggerFactory.getLogger(AddressBasedCoinSelector.class);
|
||||||
private final NetworkParameters params;
|
private final NetworkParameters params;
|
||||||
private final AddressEntry addressEntry;
|
@Nullable
|
||||||
|
private Set<AddressEntry> addressEntries;
|
||||||
|
@Nullable
|
||||||
|
private AddressEntry addressEntry;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor
|
// Constructor
|
||||||
|
@ -50,6 +51,11 @@ class AddressBasedCoinSelector implements CoinSelector {
|
||||||
this.addressEntry = addressEntry;
|
this.addressEntry = addressEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AddressBasedCoinSelector(NetworkParameters params, Set<AddressEntry> addressEntries) {
|
||||||
|
this.params = params;
|
||||||
|
this.addressEntries = addressEntries;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static void sortOutputs(ArrayList<TransactionOutput> outputs) {
|
static void sortOutputs(ArrayList<TransactionOutput> outputs) {
|
||||||
Collections.sort(outputs, (a, b) -> {
|
Collections.sort(outputs, (a, b) -> {
|
||||||
|
@ -94,16 +100,27 @@ class AddressBasedCoinSelector implements CoinSelector {
|
||||||
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash
|
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash
|
||||||
()) {
|
()) {
|
||||||
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
|
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
|
||||||
log.trace("matchesRequiredAddress?");
|
log.trace("matchesRequiredAddress(es)?");
|
||||||
log.trace(addressOutput.toString());
|
log.trace(addressOutput.toString());
|
||||||
|
if (addressEntry != null && addressEntry.getAddress() != null) {
|
||||||
log.trace(addressEntry.getAddress().toString());
|
log.trace(addressEntry.getAddress().toString());
|
||||||
|
if (addressOutput.equals(addressEntry.getAddress()))
|
||||||
if (addressOutput.equals(addressEntry.getAddress())) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
else {
|
||||||
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " + addressOutput.toString
|
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " + addressOutput.toString
|
||||||
() + " / " + addressEntry.getAddress().toString());
|
() + " / " + addressEntry.getAddress().toString());
|
||||||
}
|
}
|
||||||
|
} else if (addressEntries != null) {
|
||||||
|
log.trace(addressEntries.toString());
|
||||||
|
for (AddressEntry entry : addressEntries) {
|
||||||
|
if (addressOutput.equals(entry.getAddress()))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString
|
||||||
|
() + " / " + addressEntries.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,8 @@ import java.util.concurrent.TimeoutException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WalletService handles all non trade specific wallet and bitcoin related services.
|
* WalletService handles all non trade specific wallet and bitcoin related services.
|
||||||
* It startup the wallet app kit and initialized the wallet.
|
* It startup the wallet app kit and initialized the wallet.
|
||||||
|
@ -325,7 +327,9 @@ public class WalletService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddressEntry getAddressEntryByOfferId(String offerId) {
|
public AddressEntry getAddressEntryByOfferId(String offerId) {
|
||||||
Optional<AddressEntry> addressEntry = getAddressEntryList().stream().filter(e -> offerId.equals(e.getOfferId())).findFirst();
|
Optional<AddressEntry> addressEntry = getAddressEntryList().stream()
|
||||||
|
.filter(e -> offerId.equals(e.getOfferId()))
|
||||||
|
.findAny();
|
||||||
if (addressEntry.isPresent())
|
if (addressEntry.isPresent())
|
||||||
return addressEntry.get();
|
return addressEntry.get();
|
||||||
else
|
else
|
||||||
|
@ -333,7 +337,9 @@ public class WalletService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<AddressEntry> getAddressEntryByAddress(String address) {
|
private Optional<AddressEntry> getAddressEntryByAddress(String address) {
|
||||||
return getAddressEntryList().stream().filter(e -> address.equals(e.getAddressString())).findFirst();
|
return getAddressEntryList().stream()
|
||||||
|
.filter(e -> e.getAddressString() != null && e.getAddressString().equals(address))
|
||||||
|
.findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -450,7 +456,8 @@ public class WalletService {
|
||||||
public Coin getRequiredFee(String fromAddress,
|
public Coin getRequiredFee(String fromAddress,
|
||||||
String toAddress,
|
String toAddress,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
|
@Nullable KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException,
|
||||||
|
InsufficientMoneyException {
|
||||||
Coin fee;
|
Coin fee;
|
||||||
try {
|
try {
|
||||||
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey));
|
wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey));
|
||||||
|
@ -463,10 +470,28 @@ public class WalletService {
|
||||||
return fee;
|
return fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Coin getRequiredFeeForMultipleAddresses(Set<String> fromAddresses,
|
||||||
|
String toAddress,
|
||||||
|
Coin amount,
|
||||||
|
@Nullable KeyParameter aesKey) throws AddressFormatException,
|
||||||
|
IllegalArgumentException, InsufficientMoneyException {
|
||||||
|
Coin fee;
|
||||||
|
try {
|
||||||
|
wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey));
|
||||||
|
fee = Coin.ZERO;
|
||||||
|
} catch (InsufficientMoneyException e) {
|
||||||
|
log.info("The amount to be transferred is not enough to pay the transaction fees of {}. " +
|
||||||
|
"We subtract that fee from the receivers amount to make the transaction possible.");
|
||||||
|
fee = e.missing;
|
||||||
|
}
|
||||||
|
return fee;
|
||||||
|
}
|
||||||
|
|
||||||
public Wallet.SendRequest getSendRequest(String fromAddress,
|
public Wallet.SendRequest getSendRequest(String fromAddress,
|
||||||
String toAddress,
|
String toAddress,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
@Nullable KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
|
@Nullable KeyParameter aesKey) throws AddressFormatException,
|
||||||
|
IllegalArgumentException, InsufficientMoneyException {
|
||||||
Transaction tx = new Transaction(params);
|
Transaction tx = new Transaction(params);
|
||||||
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
|
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
|
||||||
"You cannot send an amount which are smaller than 546 satoshis.");
|
"You cannot send an amount which are smaller than 546 satoshis.");
|
||||||
|
@ -485,11 +510,53 @@ public class WalletService {
|
||||||
return sendRequest;
|
return sendRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Wallet.SendRequest getSendRequestForMultipleAddresses(Set<String> fromAddresses,
|
||||||
|
String toAddress,
|
||||||
|
Coin amount,
|
||||||
|
@Nullable String changeAddress,
|
||||||
|
@Nullable KeyParameter aesKey) throws
|
||||||
|
AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
|
||||||
|
Transaction tx = new Transaction(params);
|
||||||
|
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
|
||||||
|
"You cannot send an amount which are smaller than 546 satoshis.");
|
||||||
|
tx.addOutput(amount, new Address(params, toAddress));
|
||||||
|
|
||||||
|
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
|
||||||
|
sendRequest.aesKey = aesKey;
|
||||||
|
sendRequest.shuffleOutputs = false;
|
||||||
|
Set<AddressEntry> addressEntries = fromAddresses.stream()
|
||||||
|
.map(e -> getAddressEntryByAddress(e))
|
||||||
|
.filter(e -> e.isPresent())
|
||||||
|
.map(e -> e.get()).collect(Collectors.toSet());
|
||||||
|
if (addressEntries.isEmpty())
|
||||||
|
throw new IllegalArgumentException("No withdrawFromAddresses not found in our wallets.\n\t" +
|
||||||
|
"fromAddresses=" + fromAddresses);
|
||||||
|
|
||||||
|
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntries);
|
||||||
|
Optional<AddressEntry> addressEntryOptional = Optional.empty();
|
||||||
|
AddressEntry changeAddressAddressEntry = null;
|
||||||
|
if (changeAddress != null)
|
||||||
|
addressEntryOptional = getAddressEntryByAddress(changeAddress);
|
||||||
|
|
||||||
|
if (addressEntryOptional.isPresent()) {
|
||||||
|
changeAddressAddressEntry = addressEntryOptional.get();
|
||||||
|
} else {
|
||||||
|
ArrayList<AddressEntry> list = new ArrayList<>(addressEntries);
|
||||||
|
if (!list.isEmpty())
|
||||||
|
changeAddressAddressEntry = list.get(0);
|
||||||
|
}
|
||||||
|
checkNotNull(changeAddressAddressEntry, "change address must not be null");
|
||||||
|
sendRequest.changeAddress = changeAddressAddressEntry.getAddress();
|
||||||
|
sendRequest.feePerKb = FeePolicy.getFeePerKb();
|
||||||
|
return sendRequest;
|
||||||
|
}
|
||||||
|
|
||||||
public String sendFunds(String fromAddress,
|
public String sendFunds(String fromAddress,
|
||||||
String toAddress,
|
String toAddress,
|
||||||
Coin amount,
|
Coin amount,
|
||||||
KeyParameter aesKey,
|
@Nullable KeyParameter aesKey,
|
||||||
FutureCallback<Transaction> callback) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
|
FutureCallback<Transaction> callback) throws AddressFormatException,
|
||||||
|
IllegalArgumentException, InsufficientMoneyException {
|
||||||
Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey);
|
Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey);
|
||||||
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey));
|
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey));
|
||||||
Futures.addCallback(sendResult.broadcastComplete, callback);
|
Futures.addCallback(sendResult.broadcastComplete, callback);
|
||||||
|
@ -498,6 +565,22 @@ public class WalletService {
|
||||||
return sendResult.tx.getHashAsString();
|
return sendResult.tx.getHashAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String sendFundsForMultipleAddresses(Set<String> fromAddresses,
|
||||||
|
String toAddress,
|
||||||
|
Coin amount,
|
||||||
|
@Nullable String changeAddress,
|
||||||
|
@Nullable KeyParameter aesKey,
|
||||||
|
FutureCallback<Transaction> callback) throws AddressFormatException,
|
||||||
|
IllegalArgumentException, InsufficientMoneyException {
|
||||||
|
Coin fee = getRequiredFeeForMultipleAddresses(fromAddresses, toAddress, amount, aesKey);
|
||||||
|
Wallet.SendResult sendResult = wallet.sendCoins(getSendRequestForMultipleAddresses(fromAddresses, toAddress,
|
||||||
|
amount.subtract(fee), changeAddress, aesKey));
|
||||||
|
Futures.addCallback(sendResult.broadcastComplete, callback);
|
||||||
|
|
||||||
|
printTxWithInputs("sendFunds", sendResult.tx);
|
||||||
|
return sendResult.tx.getHashAsString();
|
||||||
|
}
|
||||||
|
|
||||||
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
|
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
|
||||||
throws InsufficientMoneyException, AddressFormatException {
|
throws InsufficientMoneyException, AddressFormatException {
|
||||||
Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress));
|
Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress));
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.google.common.base.Throwables;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import io.bitsquare.app.Log;
|
||||||
import io.bitsquare.app.Version;
|
import io.bitsquare.app.Version;
|
||||||
import io.bitsquare.arbitration.ArbitratorManager;
|
import io.bitsquare.arbitration.ArbitratorManager;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
import io.bitsquare.btc.FeePolicy;
|
||||||
|
@ -125,12 +126,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
|
||||||
TRADE_PERIOD_OVER
|
TRADE_PERIOD_OVER
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutable
|
|
||||||
private Coin tradeAmount;
|
|
||||||
private NodeAddress tradingPeerNodeAddress;
|
|
||||||
transient private ObjectProperty<Coin> tradeAmountProperty;
|
|
||||||
transient private ObjectProperty<Fiat> tradeVolumeProperty;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Fields
|
// Fields
|
||||||
|
@ -152,7 +147,8 @@ abstract public class Trade implements Tradable, Model, Serializable {
|
||||||
// Mutable
|
// Mutable
|
||||||
private DecryptedMsgWithPubKey decryptedMsgWithPubKey;
|
private DecryptedMsgWithPubKey decryptedMsgWithPubKey;
|
||||||
private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default
|
private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default
|
||||||
|
private Coin tradeAmount;
|
||||||
|
private NodeAddress tradingPeerNodeAddress;
|
||||||
protected State state;
|
protected State state;
|
||||||
private DisputeState disputeState = DisputeState.NONE;
|
private DisputeState disputeState = DisputeState.NONE;
|
||||||
private TradePeriodState tradePeriodState = TradePeriodState.NORMAL;
|
private TradePeriodState tradePeriodState = TradePeriodState.NORMAL;
|
||||||
|
@ -172,6 +168,10 @@ abstract public class Trade implements Tradable, Model, Serializable {
|
||||||
private boolean tradePeriodOverWarningDisplayed;
|
private boolean tradePeriodOverWarningDisplayed;
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
transient private StringProperty errorMessageProperty;
|
transient private StringProperty errorMessageProperty;
|
||||||
|
transient private ObjectProperty<Coin> tradeAmountProperty;
|
||||||
|
transient private ObjectProperty<Fiat> tradeVolumeProperty;
|
||||||
|
@Nullable
|
||||||
|
private Transaction takeOfferFeeTx;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -306,6 +306,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDisputeState(DisputeState disputeState) {
|
public void setDisputeState(DisputeState disputeState) {
|
||||||
|
Log.traceCall("disputeState=" + disputeState + "\n\ttrade=" + this);
|
||||||
this.disputeState = disputeState;
|
this.disputeState = disputeState;
|
||||||
disputeStateProperty.set(disputeState);
|
disputeStateProperty.set(disputeState);
|
||||||
persist();
|
persist();
|
||||||
|
@ -561,6 +562,13 @@ abstract public class Trade implements Tradable, Model, Serializable {
|
||||||
return contractHash;
|
return contractHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) {
|
||||||
|
this.takeOfferFeeTx = takeOfferFeeTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transaction getTakeOfferFeeTx() {
|
||||||
|
return takeOfferFeeTx;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Private
|
// Private
|
||||||
|
@ -620,11 +628,10 @@ abstract public class Trade implements Tradable, Model, Serializable {
|
||||||
"\n\tdisputeState=" + disputeState +
|
"\n\tdisputeState=" + disputeState +
|
||||||
"\n\ttradePeriodState=" + tradePeriodState +
|
"\n\ttradePeriodState=" + tradePeriodState +
|
||||||
"\n\tdepositTx=" + depositTx +
|
"\n\tdepositTx=" + depositTx +
|
||||||
|
"\n\ttakeOfferFeeTx.getHashAsString()=" + (takeOfferFeeTx != null ? takeOfferFeeTx.getHashAsString() : "") +
|
||||||
"\n\tcontract=" + contract +
|
"\n\tcontract=" + contract +
|
||||||
/* "\n\tcontractAsJson='" + contractAsJson + '\'' +*/
|
"\n\ttakerContractSignature.hashCode()='" + (takerContractSignature != null ? takerContractSignature.hashCode() : "") + '\'' +
|
||||||
/* "\n\tcontractHash=" + Arrays.toString(contractHash) +*/
|
"\n\toffererContractSignature.hashCode()='" + (offererContractSignature != null ? offererContractSignature.hashCode() : "") + '\'' +
|
||||||
"\n\ttakerContractSignature.hashCode()='" + takerContractSignature.hashCode() + '\'' +
|
|
||||||
"\n\toffererContractSignature.hashCode()='" + offererContractSignature.hashCode() + '\'' +
|
|
||||||
"\n\tpayoutTx=" + payoutTx +
|
"\n\tpayoutTx=" + payoutTx +
|
||||||
"\n\tlockTimeAsBlockHeight=" + lockTimeAsBlockHeight +
|
"\n\tlockTimeAsBlockHeight=" + lockTimeAsBlockHeight +
|
||||||
"\n\topenDisputeTimeAsBlockHeight=" + openDisputeTimeAsBlockHeight +
|
"\n\topenDisputeTimeAsBlockHeight=" + openDisputeTimeAsBlockHeight +
|
||||||
|
|
|
@ -36,7 +36,6 @@ import io.bitsquare.trade.offer.Offer;
|
||||||
import io.bitsquare.trade.offer.OpenOfferManager;
|
import io.bitsquare.trade.offer.OpenOfferManager;
|
||||||
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
|
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
|
||||||
import io.bitsquare.user.User;
|
import io.bitsquare.user.User;
|
||||||
import org.bitcoinj.core.Transaction;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -68,7 +67,6 @@ public class ProcessModel implements Model, Serializable {
|
||||||
transient private TradeMessage tradeMessage;
|
transient private TradeMessage tradeMessage;
|
||||||
private String takeOfferFeeTxId;
|
private String takeOfferFeeTxId;
|
||||||
private byte[] payoutTxSignature;
|
private byte[] payoutTxSignature;
|
||||||
private Transaction takeOfferFeeTx;
|
|
||||||
|
|
||||||
private List<NodeAddress> takerAcceptedArbitratorNodeAddresses;
|
private List<NodeAddress> takerAcceptedArbitratorNodeAddresses;
|
||||||
|
|
||||||
|
@ -168,15 +166,6 @@ public class ProcessModel implements Model, Serializable {
|
||||||
return tradeMessage;
|
return tradeMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Transaction getTakeOfferFeeTx() {
|
|
||||||
return takeOfferFeeTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) {
|
|
||||||
this.takeOfferFeeTx = takeOfferFeeTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentAccountContractData getPaymentAccountContractData(Trade trade) {
|
public PaymentAccountContractData getPaymentAccountContractData(Trade trade) {
|
||||||
if (trade instanceof OffererTrade)
|
if (trade instanceof OffererTrade)
|
||||||
return user.getPaymentAccount(offer.getOffererPaymentAccountId()).getContractData();
|
return user.getPaymentAccount(offer.getOffererPaymentAccountId()).getContractData();
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class BroadcastTakeOfferFeeTx extends TradeTask {
|
||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
processModel.getTradeWalletService().broadcastTx(processModel.getTakeOfferFeeTx(),
|
processModel.getTradeWalletService().broadcastTx(trade.getTakeOfferFeeTx(),
|
||||||
new FutureCallback<Transaction>() {
|
new FutureCallback<Transaction>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Transaction transaction) {
|
public void onSuccess(Transaction transaction) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class CreateTakeOfferFeeTx extends TradeTask {
|
||||||
FeePolicy.getTakeOfferFee(),
|
FeePolicy.getTakeOfferFee(),
|
||||||
selectedArbitrator.getBtcAddress());
|
selectedArbitrator.getBtcAddress());
|
||||||
|
|
||||||
processModel.setTakeOfferFeeTx(createTakeOfferFeeTx);
|
trade.setTakeOfferFeeTx(createTakeOfferFeeTx);
|
||||||
|
|
||||||
// TODO check if needed as we have stored tx already at setTakeOfferFeeTx
|
// TODO check if needed as we have stored tx already at setTakeOfferFeeTx
|
||||||
processModel.setTakeOfferFeeTxId(createTakeOfferFeeTx.getHashAsString());
|
processModel.setTakeOfferFeeTxId(createTakeOfferFeeTx.getHashAsString());
|
||||||
|
|
|
@ -25,6 +25,8 @@ import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
public class SendPayDepositRequest extends TradeTask {
|
public class SendPayDepositRequest extends TradeTask {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SendPayDepositRequest.class);
|
private static final Logger log = LoggerFactory.getLogger(SendPayDepositRequest.class);
|
||||||
|
|
||||||
|
@ -36,7 +38,8 @@ public class SendPayDepositRequest extends TradeTask {
|
||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
if (processModel.getTakeOfferFeeTx() != null) {
|
if (trade.getTakeOfferFeeTx() != null) {
|
||||||
|
checkNotNull(trade.getTradeAmount());
|
||||||
PayDepositRequest payDepositRequest = new PayDepositRequest(
|
PayDepositRequest payDepositRequest = new PayDepositRequest(
|
||||||
processModel.getMyAddress(),
|
processModel.getMyAddress(),
|
||||||
processModel.getId(),
|
processModel.getId(),
|
||||||
|
@ -49,7 +52,7 @@ public class SendPayDepositRequest extends TradeTask {
|
||||||
processModel.getPubKeyRing(),
|
processModel.getPubKeyRing(),
|
||||||
processModel.getPaymentAccountContractData(trade),
|
processModel.getPaymentAccountContractData(trade),
|
||||||
processModel.getAccountId(),
|
processModel.getAccountId(),
|
||||||
processModel.getTakeOfferFeeTx().getHashAsString(),
|
trade.getTakeOfferFeeTx().getHashAsString(),
|
||||||
processModel.getUser().getAcceptedArbitratorAddresses(),
|
processModel.getUser().getAcceptedArbitratorAddresses(),
|
||||||
trade.getArbitratorNodeAddress()
|
trade.getArbitratorNodeAddress()
|
||||||
);
|
);
|
||||||
|
@ -79,7 +82,7 @@ public class SendPayDepositRequest extends TradeTask {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
log.error("processModel.getTakeOfferFeeTx() = " + processModel.getTakeOfferFeeTx());
|
log.error("trade.getTakeOfferFeeTx() = " + trade.getTakeOfferFeeTx());
|
||||||
failed("TakeOfferFeeTx is null");
|
failed("TakeOfferFeeTx is null");
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class VerifyAndSignContract extends TradeTask {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
if (processModel.getTakeOfferFeeTx() != null) {
|
if (trade.getTakeOfferFeeTx() != null) {
|
||||||
TradingPeer offerer = processModel.tradingPeer;
|
TradingPeer offerer = processModel.tradingPeer;
|
||||||
PaymentAccountContractData offererPaymentAccountContractData = offerer.getPaymentAccountContractData();
|
PaymentAccountContractData offererPaymentAccountContractData = offerer.getPaymentAccountContractData();
|
||||||
PaymentAccountContractData takerPaymentAccountContractData = processModel.getPaymentAccountContractData(trade);
|
PaymentAccountContractData takerPaymentAccountContractData = processModel.getPaymentAccountContractData(trade);
|
||||||
|
@ -57,7 +57,7 @@ public class VerifyAndSignContract extends TradeTask {
|
||||||
Contract contract = new Contract(
|
Contract contract = new Contract(
|
||||||
processModel.getOffer(),
|
processModel.getOffer(),
|
||||||
trade.getTradeAmount(),
|
trade.getTradeAmount(),
|
||||||
processModel.getTakeOfferFeeTx().getHashAsString(),
|
trade.getTakeOfferFeeTx().getHashAsString(),
|
||||||
buyerNodeAddress,
|
buyerNodeAddress,
|
||||||
sellerNodeAddress,
|
sellerNodeAddress,
|
||||||
trade.getArbitratorNodeAddress(),
|
trade.getArbitratorNodeAddress(),
|
||||||
|
|
|
@ -140,6 +140,31 @@ bg color of non edit textFields: fafafa
|
||||||
-fx-text-fill: black;
|
-fx-text-fill: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.external-link-icon {
|
||||||
|
-fx-text-fill: -fx-accent;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-funds-icon {
|
||||||
|
-fx-text-fill: -bs-green;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-funds-icon:hover {
|
||||||
|
-fx-text-fill: black;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sent-funds-icon {
|
||||||
|
-fx-text-fill: -bs-error-red;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sent-funds-icon:hover {
|
||||||
|
-fx-text-fill: black;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* Tooltip *
|
* Tooltip *
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package io.bitsquare.gui.components;
|
||||||
|
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Hyperlink;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class AddressWithIconAndDirection extends AnchorPane {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AddressWithIconAndDirection.class);
|
||||||
|
private final Hyperlink hyperlink;
|
||||||
|
private final Label openLinkIcon;
|
||||||
|
private final Label directionIcon;
|
||||||
|
private final Label label;
|
||||||
|
|
||||||
|
public AddressWithIconAndDirection(String text, String address, AwesomeIcon awesomeIcon, boolean received) {
|
||||||
|
directionIcon = new Label();
|
||||||
|
directionIcon.setLayoutY(3);
|
||||||
|
directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon");
|
||||||
|
AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT);
|
||||||
|
directionIcon.setOpacity(0.7);
|
||||||
|
directionIcon.setMouseTransparent(true);
|
||||||
|
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
hBox.setSpacing(-1);
|
||||||
|
label = new Label(text);
|
||||||
|
label.setMouseTransparent(true);
|
||||||
|
HBox.setMargin(label, new Insets(4, 0, 0, 0));
|
||||||
|
HBox.setHgrow(label, Priority.ALWAYS);
|
||||||
|
|
||||||
|
hyperlink = new Hyperlink(address);
|
||||||
|
HBox.setHgrow(hyperlink, Priority.SOMETIMES);
|
||||||
|
// You need to set max width to Double.MAX_VALUE to make HBox.setHgrow working like expected!
|
||||||
|
// also pref width needs to be not default (-1)
|
||||||
|
hyperlink.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
hyperlink.setPrefWidth(0);
|
||||||
|
|
||||||
|
hBox.getChildren().addAll(label, hyperlink);
|
||||||
|
|
||||||
|
openLinkIcon = new Label();
|
||||||
|
openLinkIcon.setLayoutY(3);
|
||||||
|
openLinkIcon.getStyleClass().add("external-link-icon");
|
||||||
|
AwesomeDude.setIcon(openLinkIcon, awesomeIcon);
|
||||||
|
|
||||||
|
AnchorPane.setLeftAnchor(directionIcon, 3.0);
|
||||||
|
AnchorPane.setTopAnchor(directionIcon, 2.0);
|
||||||
|
AnchorPane.setLeftAnchor(hBox, 20.0);
|
||||||
|
AnchorPane.setRightAnchor(hBox, 15.0);
|
||||||
|
AnchorPane.setRightAnchor(openLinkIcon, 4.0);
|
||||||
|
AnchorPane.setTopAnchor(openLinkIcon, 3.0);
|
||||||
|
|
||||||
|
getChildren().addAll(directionIcon, hBox, openLinkIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnAction(EventHandler<ActionEvent> handler) {
|
||||||
|
hyperlink.setOnAction(handler);
|
||||||
|
openLinkIcon.setOnMouseClicked(e -> handler.handle(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTooltip(Tooltip tooltip) {
|
||||||
|
hyperlink.setTooltip(tooltip);
|
||||||
|
openLinkIcon.setTooltip(tooltip);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package io.bitsquare.gui.components;
|
||||||
|
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.scene.control.Hyperlink;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
|
||||||
|
public class HyperlinkWithIcon extends AnchorPane {
|
||||||
|
private final Hyperlink hyperlink;
|
||||||
|
private final Label openLinkIcon;
|
||||||
|
|
||||||
|
public HyperlinkWithIcon(String text, AwesomeIcon awesomeIcon) {
|
||||||
|
hyperlink = new Hyperlink(text);
|
||||||
|
|
||||||
|
openLinkIcon = new Label();
|
||||||
|
openLinkIcon.setLayoutY(3);
|
||||||
|
openLinkIcon.getStyleClass().add("external-link-icon");
|
||||||
|
AwesomeDude.setIcon(openLinkIcon, awesomeIcon);
|
||||||
|
|
||||||
|
AnchorPane.setLeftAnchor(hyperlink, 0.0);
|
||||||
|
AnchorPane.setRightAnchor(hyperlink, 15.0);
|
||||||
|
AnchorPane.setRightAnchor(openLinkIcon, 4.0);
|
||||||
|
AnchorPane.setTopAnchor(openLinkIcon, 3.0);
|
||||||
|
|
||||||
|
getChildren().addAll(hyperlink, openLinkIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnAction(EventHandler<ActionEvent> handler) {
|
||||||
|
hyperlink.setOnAction(handler);
|
||||||
|
openLinkIcon.setOnMouseClicked(e -> handler.handle(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTooltip(Tooltip tooltip) {
|
||||||
|
hyperlink.setTooltip(tooltip);
|
||||||
|
openLinkIcon.setTooltip(tooltip);
|
||||||
|
}
|
||||||
|
}
|
|
@ -200,3 +200,7 @@
|
||||||
-fx-image: url("../../../images/bubble_arrow_blue_right.png");
|
-fx-image: url("../../../images/bubble_arrow_blue_right.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#link {
|
||||||
|
-fx-image: url("../../../images/link.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.bitsquare.gui.main.disputes.trader;
|
package io.bitsquare.gui.main.disputes;
|
||||||
|
|
||||||
import io.bitsquare.arbitration.Dispute;
|
import io.bitsquare.arbitration.Dispute;
|
||||||
import io.bitsquare.arbitration.DisputeManager;
|
import io.bitsquare.arbitration.DisputeManager;
|
|
@ -21,7 +21,7 @@ import io.bitsquare.arbitration.Dispute;
|
||||||
import io.bitsquare.arbitration.DisputeManager;
|
import io.bitsquare.arbitration.DisputeManager;
|
||||||
import io.bitsquare.common.crypto.KeyRing;
|
import io.bitsquare.common.crypto.KeyRing;
|
||||||
import io.bitsquare.gui.common.view.FxmlView;
|
import io.bitsquare.gui.common.view.FxmlView;
|
||||||
import io.bitsquare.gui.main.disputes.trader.DisputeSummaryPopup;
|
import io.bitsquare.gui.main.disputes.DisputeSummaryPopup;
|
||||||
import io.bitsquare.gui.main.disputes.trader.TraderDisputeView;
|
import io.bitsquare.gui.main.disputes.trader.TraderDisputeView;
|
||||||
import io.bitsquare.gui.popups.ContractPopup;
|
import io.bitsquare.gui.popups.ContractPopup;
|
||||||
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import io.bitsquare.common.crypto.KeyRing;
|
||||||
import io.bitsquare.gui.common.view.ActivatableView;
|
import io.bitsquare.gui.common.view.ActivatableView;
|
||||||
import io.bitsquare.gui.common.view.FxmlView;
|
import io.bitsquare.gui.common.view.FxmlView;
|
||||||
import io.bitsquare.gui.components.TableGroupHeadline;
|
import io.bitsquare.gui.components.TableGroupHeadline;
|
||||||
|
import io.bitsquare.gui.main.disputes.DisputeSummaryPopup;
|
||||||
import io.bitsquare.gui.popups.ContractPopup;
|
import io.bitsquare.gui.popups.ContractPopup;
|
||||||
import io.bitsquare.gui.popups.Popup;
|
import io.bitsquare.gui.popups.Popup;
|
||||||
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
||||||
|
|
|
@ -26,6 +26,8 @@ import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.trade.Tradable;
|
import io.bitsquare.trade.Tradable;
|
||||||
import io.bitsquare.trade.Trade;
|
import io.bitsquare.trade.Trade;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
|
@ -37,6 +39,7 @@ import org.slf4j.LoggerFactory;
|
||||||
public class ReservedListItem {
|
public class ReservedListItem {
|
||||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private final StringProperty date = new SimpleStringProperty();
|
||||||
private final BalanceListener balanceListener;
|
private final BalanceListener balanceListener;
|
||||||
|
|
||||||
private final Label balanceLabel;
|
private final Label balanceLabel;
|
||||||
|
@ -78,6 +81,8 @@ public class ReservedListItem {
|
||||||
|
|
||||||
updateConfidence(walletService.getConfidenceForAddress(getAddress()));
|
updateConfidence(walletService.getConfidenceForAddress(getAddress()));
|
||||||
|
|
||||||
|
//date.set(formatter.formatDateTime(transaction.getUpdateTime()));
|
||||||
|
|
||||||
|
|
||||||
// balance
|
// balance
|
||||||
balanceLabel = new Label();
|
balanceLabel = new Label();
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.cell.PropertyValueFactory?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.reserved.ReservedView"
|
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.reserved.ReservedView"
|
||||||
|
@ -28,6 +29,11 @@
|
||||||
|
|
||||||
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
|
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="100" sortable="false">
|
||||||
|
<cellValueFactory>
|
||||||
|
<PropertyValueFactory property="date"/>
|
||||||
|
</cellValueFactory>
|
||||||
|
</TableColumn>
|
||||||
<TableColumn text="Details" fx:id="detailsColumn" minWidth="100" sortable="false"/>
|
<TableColumn text="Details" fx:id="detailsColumn" minWidth="100" sortable="false"/>
|
||||||
<TableColumn text="Address" fx:id="addressColumn" minWidth="240" sortable="false"/>
|
<TableColumn text="Address" fx:id="addressColumn" minWidth="240" sortable="false"/>
|
||||||
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="90" sortable="false"/>
|
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="90" sortable="false"/>
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||||
@FXML
|
@FXML
|
||||||
TableView<ReservedListItem> table;
|
TableView<ReservedListItem> table;
|
||||||
@FXML
|
@FXML
|
||||||
TableColumn<ReservedListItem, ReservedListItem> detailsColumn, addressColumn, balanceColumn, confidenceColumn;
|
TableColumn<ReservedListItem, ReservedListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, confidenceColumn;
|
||||||
|
|
||||||
private final WalletService walletService;
|
private final WalletService walletService;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
|
@ -61,6 +61,11 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||||
private final TradeDetailsPopup tradeDetailsPopup;
|
private final TradeDetailsPopup tradeDetailsPopup;
|
||||||
private final ObservableList<ReservedListItem> addressList = FXCollections.observableArrayList();
|
private final ObservableList<ReservedListItem> addressList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor, lifecycle
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ReservedView(WalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences,
|
private ReservedView(WalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences,
|
||||||
BSFormatter formatter, OfferDetailsPopup offerDetailsPopup, TradeDetailsPopup tradeDetailsPopup) {
|
BSFormatter formatter, OfferDetailsPopup offerDetailsPopup, TradeDetailsPopup tradeDetailsPopup) {
|
||||||
|
@ -87,13 +92,13 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void activate() {
|
protected void activate() {
|
||||||
fillList();
|
updateList();
|
||||||
table.setItems(addressList);
|
table.setItems(addressList);
|
||||||
|
|
||||||
walletService.addBalanceListener(new BalanceListener() {
|
walletService.addBalanceListener(new BalanceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(Coin balance) {
|
public void onBalanceChanged(Coin balance) {
|
||||||
fillList();
|
updateList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -104,10 +109,13 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void fillList() {
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void updateList() {
|
||||||
addressList.forEach(ReservedListItem::cleanup);
|
addressList.forEach(ReservedListItem::cleanup);
|
||||||
addressList.clear();
|
addressList.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
||||||
addressList.addAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
|
||||||
.map(tradable -> new ReservedListItem(tradable, walletService.getAddressEntryByOfferId(tradable.getOffer().getId()), walletService, formatter))
|
.map(tradable -> new ReservedListItem(tradable, walletService.getAddressEntryByOfferId(tradable.getOffer().getId()), walletService, formatter))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
@ -122,6 +130,11 @@ public class ReservedView extends ActivatableView<VBox, Void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ColumnCellFactories
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private void setLabelColumnCellFactory() {
|
private void setLabelColumnCellFactory() {
|
||||||
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||||
detailsColumn.setCellFactory(new Callback<TableColumn<ReservedListItem, ReservedListItem>,
|
detailsColumn.setCellFactory(new Callback<TableColumn<ReservedListItem, ReservedListItem>,
|
||||||
|
|
|
@ -18,30 +18,40 @@
|
||||||
package io.bitsquare.gui.main.funds.transactions;
|
package io.bitsquare.gui.main.funds.transactions;
|
||||||
|
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.btc.listeners.AddressConfidenceListener;
|
import io.bitsquare.btc.listeners.TxConfidenceListener;
|
||||||
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
|
import io.bitsquare.trade.Tradable;
|
||||||
|
import io.bitsquare.trade.Trade;
|
||||||
|
import io.bitsquare.trade.offer.OpenOffer;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import org.bitcoinj.core.*;
|
import org.bitcoinj.core.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class TransactionsListItem {
|
public class TransactionsListItem {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
private final StringProperty date = new SimpleStringProperty();
|
private final StringProperty date = new SimpleStringProperty();
|
||||||
private final StringProperty amount = new SimpleStringProperty();
|
private final StringProperty amount = new SimpleStringProperty();
|
||||||
private final StringProperty type = new SimpleStringProperty();
|
private final String txId;
|
||||||
|
|
||||||
private final WalletService walletService;
|
private final WalletService walletService;
|
||||||
|
|
||||||
private final ConfidenceProgressIndicator progressIndicator;
|
private final ConfidenceProgressIndicator progressIndicator;
|
||||||
|
|
||||||
private final Tooltip tooltip;
|
private final Tooltip tooltip;
|
||||||
|
private Tradable tradable;
|
||||||
|
private String details;
|
||||||
private String addressString;
|
private String addressString;
|
||||||
private AddressConfidenceListener confidenceListener;
|
private String direction;
|
||||||
|
private TxConfidenceListener txConfidenceListener;
|
||||||
|
private boolean received;
|
||||||
|
private boolean detailsAvailable;
|
||||||
|
|
||||||
public TransactionsListItem(Transaction transaction, WalletService walletService, BSFormatter formatter) {
|
public TransactionsListItem(Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
|
||||||
|
txId = transaction.getHashAsString();
|
||||||
this.walletService = walletService;
|
this.walletService = walletService;
|
||||||
|
|
||||||
Coin valueSentToMe = transaction.getValueSentToMe(walletService.getWallet());
|
Coin valueSentToMe = transaction.getValueSentToMe(walletService.getWallet());
|
||||||
|
@ -52,8 +62,8 @@ public class TransactionsListItem {
|
||||||
|
|
||||||
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
||||||
if (!transactionOutput.isMine(walletService.getWallet())) {
|
if (!transactionOutput.isMine(walletService.getWallet())) {
|
||||||
type.set("Sent to");
|
direction = "Sent to:";
|
||||||
|
received = false;
|
||||||
if (transactionOutput.getScriptPubKey().isSentToAddress()
|
if (transactionOutput.getScriptPubKey().isSentToAddress()
|
||||||
|| transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
|| transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||||
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getWallet().getParams());
|
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getWallet().getParams());
|
||||||
|
@ -63,7 +73,8 @@ public class TransactionsListItem {
|
||||||
}
|
}
|
||||||
} else if (valueSentFromMe.isZero()) {
|
} else if (valueSentFromMe.isZero()) {
|
||||||
amount.set(formatter.formatCoin(valueSentToMe));
|
amount.set(formatter.formatCoin(valueSentToMe));
|
||||||
type.set("Received with");
|
direction = "Received with:";
|
||||||
|
received = true;
|
||||||
|
|
||||||
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
||||||
if (transactionOutput.isMine(walletService.getWallet())) {
|
if (transactionOutput.isMine(walletService.getWallet())) {
|
||||||
|
@ -80,8 +91,8 @@ public class TransactionsListItem {
|
||||||
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
for (TransactionOutput transactionOutput : transaction.getOutputs()) {
|
||||||
if (!transactionOutput.isMine(walletService.getWallet())) {
|
if (!transactionOutput.isMine(walletService.getWallet())) {
|
||||||
outgoing = true;
|
outgoing = true;
|
||||||
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey()
|
if (transactionOutput.getScriptPubKey().isSentToAddress() ||
|
||||||
.isPayToScriptHash()) {
|
transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||||
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getWallet().getParams());
|
address = transactionOutput.getScriptPubKey().getToAddress(walletService.getWallet().getParams());
|
||||||
addressString = address.toString();
|
addressString = address.toString();
|
||||||
}
|
}
|
||||||
|
@ -89,13 +100,41 @@ public class TransactionsListItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outgoing) {
|
if (outgoing) {
|
||||||
type.set("Sent to");
|
direction = "Sent to:";
|
||||||
} else {
|
received = false;
|
||||||
type.set("Internal (TX Fee)");
|
|
||||||
//addressString = "Internal swap between addresses.";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (tradableOptional.isPresent()) {
|
||||||
|
tradable = tradableOptional.get();
|
||||||
|
detailsAvailable = true;
|
||||||
|
if (tradable instanceof OpenOffer) {
|
||||||
|
details = "Create offer fee: " + tradable.getShortId();
|
||||||
|
} else if (tradable instanceof Trade) {
|
||||||
|
Trade trade = (Trade) tradable;
|
||||||
|
if (trade.getTakeOfferFeeTx() != null && trade.getTakeOfferFeeTx().getHashAsString().equals(txId)) {
|
||||||
|
details = "Take offer fee: " + tradable.getShortId();
|
||||||
|
} else if (trade.getOffer() != null &&
|
||||||
|
trade.getOffer().getOfferFeePaymentTxID() != null &&
|
||||||
|
trade.getOffer().getOfferFeePaymentTxID().equals(txId)) {
|
||||||
|
details = "Create offer fee: " + tradable.getShortId();
|
||||||
|
} else if (trade.getDepositTx() != null &&
|
||||||
|
trade.getDepositTx().getHashAsString().equals(txId)) {
|
||||||
|
details = "MultiSig deposit: " + tradable.getShortId();
|
||||||
|
} else if (trade.getPayoutTx() != null &&
|
||||||
|
trade.getPayoutTx().getHashAsString().equals(txId)) {
|
||||||
|
details = "MultiSig payout: " + tradable.getShortId();
|
||||||
|
} else if (trade.getDisputeState() == Trade.DisputeState.DISPUTE_CLOSED) {
|
||||||
|
details = "Payout after dispute: " + tradable.getShortId();
|
||||||
|
} else {
|
||||||
|
details = "Unknown reason: " + tradable.getShortId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
details = received ? "Funded to wallet" : "Withdrawn from wallet";
|
||||||
|
}
|
||||||
|
|
||||||
date.set(formatter.formatDateTime(transaction.getUpdateTime()));
|
date.set(formatter.formatDateTime(transaction.getUpdateTime()));
|
||||||
|
|
||||||
// confidence
|
// confidence
|
||||||
|
@ -108,26 +147,24 @@ public class TransactionsListItem {
|
||||||
Tooltip.install(progressIndicator, tooltip);
|
Tooltip.install(progressIndicator, tooltip);
|
||||||
|
|
||||||
if (address != null) {
|
if (address != null) {
|
||||||
confidenceListener = walletService.addAddressConfidenceListener(new AddressConfidenceListener(address) {
|
txConfidenceListener = walletService.addTxConfidenceListener(new TxConfidenceListener(txId) {
|
||||||
@Override
|
@Override
|
||||||
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
||||||
updateConfidence(confidence);
|
updateConfidence(confidence);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateConfidence(walletService.getConfidenceForAddress(address));
|
updateConfidence(transaction.getConfidence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
walletService.removeAddressConfidenceListener(confidenceListener);
|
walletService.removeTxConfidenceListener(txConfidenceListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateConfidence(TransactionConfidence confidence) {
|
private void updateConfidence(TransactionConfidence confidence) {
|
||||||
if (confidence != null) {
|
if (confidence != null) {
|
||||||
//log.debug("Type numBroadcastPeers getDepthInBlocks " + confidence.getConfidenceType() + " / " +
|
|
||||||
// confidence.numBroadcastPeers() + " / " + confidence.getDepthInBlocks());
|
|
||||||
switch (confidence.getConfidenceType()) {
|
switch (confidence.getConfidenceType()) {
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
tooltip.setText("Unknown transaction status");
|
tooltip.setText("Unknown transaction status");
|
||||||
|
@ -151,32 +188,44 @@ public class TransactionsListItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ConfidenceProgressIndicator getProgressIndicator() {
|
public ConfidenceProgressIndicator getProgressIndicator() {
|
||||||
return progressIndicator;
|
return progressIndicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public final StringProperty dateProperty() {
|
public final StringProperty dateProperty() {
|
||||||
return this.date;
|
return this.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public final StringProperty amountProperty() {
|
public final StringProperty amountProperty() {
|
||||||
return this.amount;
|
return this.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public final StringProperty typeProperty() {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddressString() {
|
public String getAddressString() {
|
||||||
return addressString;
|
return addressString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNotAnAddress() {
|
public String getDirection() {
|
||||||
return addressString == null;
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTxId() {
|
||||||
|
return txId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getReceived() {
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetails() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getDetailsAvailable() {
|
||||||
|
return detailsAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tradable getTradable() {
|
||||||
|
return tradable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,24 +28,21 @@
|
||||||
</padding>
|
</padding>
|
||||||
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="100" sortable="false">
|
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="160" maxWidth="160">
|
||||||
<cellValueFactory>
|
<cellValueFactory>
|
||||||
<PropertyValueFactory property="date"/>
|
<PropertyValueFactory property="date"/>
|
||||||
</cellValueFactory>
|
</cellValueFactory>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn text="Type" fx:id="typeColumn" minWidth="70" sortable="false">
|
<TableColumn text="Details" fx:id="detailsColumn" minWidth="210" maxWidth="210"/>
|
||||||
<cellValueFactory>
|
<TableColumn text="Address" fx:id="addressColumn" minWidth="180"/>
|
||||||
<PropertyValueFactory property="type"/>
|
<TableColumn text="Transaction" fx:id="transactionColumn" minWidth="100"/>
|
||||||
</cellValueFactory>
|
<TableColumn text="Amount" fx:id="amountColumn" minWidth="110" maxWidth="110">
|
||||||
</TableColumn>
|
|
||||||
<TableColumn text="Address" fx:id="addressColumn" minWidth="240" sortable="false"/>
|
|
||||||
<TableColumn text="Amount" fx:id="amountColumn" minWidth="70" sortable="false">
|
|
||||||
<cellValueFactory>
|
<cellValueFactory>
|
||||||
<PropertyValueFactory property="amount"/>
|
<PropertyValueFactory property="amount"/>
|
||||||
</cellValueFactory>
|
</cellValueFactory>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
|
|
||||||
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="30" sortable="false"/>
|
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="110" maxWidth="110"/>
|
||||||
</columns>
|
</columns>
|
||||||
</TableView>
|
</TableView>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
@ -17,12 +17,25 @@
|
||||||
|
|
||||||
package io.bitsquare.gui.main.funds.transactions;
|
package io.bitsquare.gui.main.funds.transactions;
|
||||||
|
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||||
|
import io.bitsquare.arbitration.DisputeManager;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.common.util.Utilities;
|
import io.bitsquare.common.util.Utilities;
|
||||||
import io.bitsquare.gui.common.view.ActivatableView;
|
import io.bitsquare.gui.common.view.ActivatableView;
|
||||||
import io.bitsquare.gui.common.view.FxmlView;
|
import io.bitsquare.gui.common.view.FxmlView;
|
||||||
|
import io.bitsquare.gui.components.AddressWithIconAndDirection;
|
||||||
|
import io.bitsquare.gui.components.HyperlinkWithIcon;
|
||||||
|
import io.bitsquare.gui.popups.OfferDetailsPopup;
|
||||||
import io.bitsquare.gui.popups.Popup;
|
import io.bitsquare.gui.popups.Popup;
|
||||||
|
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
|
import io.bitsquare.trade.Tradable;
|
||||||
|
import io.bitsquare.trade.Trade;
|
||||||
|
import io.bitsquare.trade.TradeManager;
|
||||||
|
import io.bitsquare.trade.closed.ClosedTradableManager;
|
||||||
|
import io.bitsquare.trade.failed.FailedTradesManager;
|
||||||
|
import io.bitsquare.trade.offer.OpenOffer;
|
||||||
|
import io.bitsquare.trade.offer.OpenOfferManager;
|
||||||
import io.bitsquare.user.Preferences;
|
import io.bitsquare.user.Preferences;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
@ -36,7 +49,10 @@ import org.bitcoinj.script.Script;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@FxmlView
|
@FxmlView
|
||||||
public class TransactionsView extends ActivatableView<VBox, Void> {
|
public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
|
@ -44,30 +60,55 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
@FXML
|
@FXML
|
||||||
TableView<TransactionsListItem> table;
|
TableView<TransactionsListItem> table;
|
||||||
@FXML
|
@FXML
|
||||||
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, addressColumn, amountColumn, typeColumn,
|
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, typeColumn,
|
||||||
confidenceColumn;
|
confidenceColumn;
|
||||||
|
|
||||||
private final ObservableList<TransactionsListItem> transactionsListItems = FXCollections.observableArrayList();
|
private final ObservableList<TransactionsListItem> transactionsListItems = FXCollections.observableArrayList();
|
||||||
|
|
||||||
private final WalletService walletService;
|
private final WalletService walletService;
|
||||||
|
private final TradeManager tradeManager;
|
||||||
|
private final OpenOfferManager openOfferManager;
|
||||||
|
private final ClosedTradableManager closedTradableManager;
|
||||||
|
private final FailedTradesManager failedTradesManager;
|
||||||
private final BSFormatter formatter;
|
private final BSFormatter formatter;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
|
private final TradeDetailsPopup tradeDetailsPopup;
|
||||||
|
private DisputeManager disputeManager;
|
||||||
|
private final OfferDetailsPopup offerDetailsPopup;
|
||||||
private WalletEventListener walletEventListener;
|
private WalletEventListener walletEventListener;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor, lifecycle
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private TransactionsView(WalletService walletService, BSFormatter formatter, Preferences preferences) {
|
private TransactionsView(WalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager,
|
||||||
|
ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager,
|
||||||
|
BSFormatter formatter, Preferences preferences, TradeDetailsPopup tradeDetailsPopup,
|
||||||
|
DisputeManager disputeManager,
|
||||||
|
OfferDetailsPopup offerDetailsPopup) {
|
||||||
this.walletService = walletService;
|
this.walletService = walletService;
|
||||||
|
this.tradeManager = tradeManager;
|
||||||
|
this.openOfferManager = openOfferManager;
|
||||||
|
this.closedTradableManager = closedTradableManager;
|
||||||
|
this.failedTradesManager = failedTradesManager;
|
||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
|
this.tradeDetailsPopup = tradeDetailsPopup;
|
||||||
|
this.disputeManager = disputeManager;
|
||||||
|
this.offerDetailsPopup = offerDetailsPopup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
table.setPlaceholder(new Label("No transactions available"));
|
table.setPlaceholder(new Label("No transactions available"));
|
||||||
|
setDetailsColumnCellFactory();
|
||||||
setAddressColumnCellFactory();
|
setAddressColumnCellFactory();
|
||||||
|
setTransactionColumnCellFactory();
|
||||||
setConfidenceColumnCellFactory();
|
setConfidenceColumnCellFactory();
|
||||||
|
table.getSortOrder().add(dateColumn);
|
||||||
|
|
||||||
walletEventListener = new WalletEventListener() {
|
walletEventListener = new WalletEventListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,18 +147,10 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateList() {
|
|
||||||
List<Transaction> transactions = walletService.getWallet().getRecentTransactions(10000, true);
|
|
||||||
transactionsListItems.clear();
|
|
||||||
transactionsListItems.addAll(transactions.stream().map(transaction ->
|
|
||||||
new TransactionsListItem(transaction, walletService, formatter)).collect(Collectors.toList()));
|
|
||||||
|
|
||||||
table.setItems(transactionsListItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void activate() {
|
protected void activate() {
|
||||||
updateList();
|
updateList();
|
||||||
|
table.setItems(transactionsListItems);
|
||||||
walletService.getWallet().addEventListener(walletEventListener);
|
walletService.getWallet().addEventListener(walletEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +160,55 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
walletService.getWallet().removeEventListener(walletEventListener);
|
walletService.getWallet().removeEventListener(walletEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openTxDetails(TransactionsListItem item) {
|
|
||||||
if (!item.isNotAnAddress()) {
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void updateList() {
|
||||||
|
Stream<Tradable> concat1 = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream());
|
||||||
|
Stream<Tradable> concat2 = Stream.concat(concat1, closedTradableManager.getClosedTrades().stream());
|
||||||
|
Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream());
|
||||||
|
Set<Tradable> allTradables = concat3.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
List<TransactionsListItem> listItems = walletService.getWallet().getRecentTransactions(1000, true).stream()
|
||||||
|
.map(transaction -> {
|
||||||
|
Optional<Tradable> tradableOptional = allTradables.stream()
|
||||||
|
.filter(e -> {
|
||||||
|
String txId = transaction.getHashAsString();
|
||||||
|
if (e instanceof OpenOffer)
|
||||||
|
return e.getOffer().getOfferFeePaymentTxID().equals(txId);
|
||||||
|
else if (e instanceof Trade) {
|
||||||
|
Trade trade = (Trade) e;
|
||||||
|
return (trade.getTakeOfferFeeTx() != null &&
|
||||||
|
trade.getTakeOfferFeeTx().getHashAsString().equals(txId)) ||
|
||||||
|
(trade.getOffer() != null &&
|
||||||
|
trade.getOffer().getOfferFeePaymentTxID() != null &&
|
||||||
|
trade.getOffer().getOfferFeePaymentTxID().equals(txId)) ||
|
||||||
|
(trade.getDepositTx() != null &&
|
||||||
|
trade.getDepositTx().getHashAsString().equals(txId)) ||
|
||||||
|
(trade.getPayoutTx() != null &&
|
||||||
|
trade.getPayoutTx().getHashAsString().equals(txId)) ||
|
||||||
|
(disputeManager.getDisputesAsObservableList().stream()
|
||||||
|
.filter(dispute -> dispute.getDisputePayoutTx() != null &&
|
||||||
|
dispute.getDisputePayoutTx().getHashAsString().equals(txId))
|
||||||
|
.findAny()
|
||||||
|
.isPresent());
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.findAny();
|
||||||
|
return new TransactionsListItem(transaction, walletService, tradableOptional, formatter);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// are sorted by getRecentTransactions
|
||||||
|
transactionsListItems.forEach(TransactionsListItem::cleanup);
|
||||||
|
transactionsListItems.setAll(listItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openBlockExplorer(TransactionsListItem item) {
|
||||||
|
if (item.getAddressString() != null) {
|
||||||
try {
|
try {
|
||||||
Utilities.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString());
|
Utilities.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -139,6 +219,55 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openDetailPopup(TransactionsListItem item) {
|
||||||
|
if (item.getTradable() instanceof OpenOffer)
|
||||||
|
offerDetailsPopup.show(item.getTradable().getOffer());
|
||||||
|
else if (item.getTradable() instanceof Trade)
|
||||||
|
tradeDetailsPopup.show((Trade) item.getTradable());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ColumnCellFactories
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void setDetailsColumnCellFactory() {
|
||||||
|
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||||
|
detailsColumn.setCellFactory(
|
||||||
|
new Callback<TableColumn<TransactionsListItem, TransactionsListItem>, TableCell<TransactionsListItem,
|
||||||
|
TransactionsListItem>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
|
||||||
|
TransactionsListItem> column) {
|
||||||
|
return new TableCell<TransactionsListItem, TransactionsListItem>() {
|
||||||
|
|
||||||
|
private HyperlinkWithIcon field;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateItem(final TransactionsListItem item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
|
if (item != null && !empty) {
|
||||||
|
if (item.getDetailsAvailable()) {
|
||||||
|
field = new HyperlinkWithIcon(item.getDetails(), AwesomeIcon.INFO_SIGN);
|
||||||
|
field.setOnAction(event -> openDetailPopup(item));
|
||||||
|
field.setTooltip(new Tooltip("Open popup for details"));
|
||||||
|
setGraphic(field);
|
||||||
|
} else {
|
||||||
|
setGraphic(new Label(item.getDetails()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setGraphic(null);
|
||||||
|
if (field != null)
|
||||||
|
field.setOnAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setAddressColumnCellFactory() {
|
private void setAddressColumnCellFactory() {
|
||||||
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||||
addressColumn.setCellFactory(
|
addressColumn.setCellFactory(
|
||||||
|
@ -149,19 +278,59 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
|
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
|
||||||
TransactionsListItem> column) {
|
TransactionsListItem> column) {
|
||||||
return new TableCell<TransactionsListItem, TransactionsListItem>() {
|
return new TableCell<TransactionsListItem, TransactionsListItem>() {
|
||||||
private Hyperlink hyperlink;
|
|
||||||
|
private AddressWithIconAndDirection field;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateItem(final TransactionsListItem item, boolean empty) {
|
public void updateItem(final TransactionsListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
if (item != null && !empty) {
|
if (item != null && !empty) {
|
||||||
hyperlink = new Hyperlink(item.getAddressString());
|
String addressString = item.getAddressString();
|
||||||
hyperlink.setOnAction(event -> openTxDetails(item));
|
field = new AddressWithIconAndDirection(item.getDirection(), addressString,
|
||||||
setGraphic(hyperlink);
|
AwesomeIcon.EXTERNAL_LINK, item.getReceived());
|
||||||
|
field.setOnAction(event -> openBlockExplorer(item));
|
||||||
|
field.setTooltip(new Tooltip("Open external blockchain explorer for " +
|
||||||
|
"address: " + addressString));
|
||||||
|
setGraphic(field);
|
||||||
} else {
|
} else {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
setId(null);
|
if (field != null)
|
||||||
|
field.setOnAction(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTransactionColumnCellFactory() {
|
||||||
|
transactionColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||||
|
transactionColumn.setCellFactory(
|
||||||
|
new Callback<TableColumn<TransactionsListItem, TransactionsListItem>, TableCell<TransactionsListItem,
|
||||||
|
TransactionsListItem>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
|
||||||
|
TransactionsListItem> column) {
|
||||||
|
return new TableCell<TransactionsListItem, TransactionsListItem>() {
|
||||||
|
private HyperlinkWithIcon hyperlinkWithIcon;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateItem(final TransactionsListItem item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
|
if (item != null && !empty) {
|
||||||
|
String transactionId = item.getTxId();
|
||||||
|
hyperlinkWithIcon = new HyperlinkWithIcon(transactionId, AwesomeIcon.EXTERNAL_LINK);
|
||||||
|
hyperlinkWithIcon.setOnAction(event -> openBlockExplorer(item));
|
||||||
|
hyperlinkWithIcon.setTooltip(new Tooltip("Open external blockchain explorer for " +
|
||||||
|
"transaction: " + transactionId));
|
||||||
|
setGraphic(hyperlinkWithIcon);
|
||||||
|
} else {
|
||||||
|
setGraphic(null);
|
||||||
|
if (hyperlinkWithIcon != null)
|
||||||
|
hyperlinkWithIcon.setOnAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,31 +19,18 @@ package io.bitsquare.gui.main.funds.withdrawal;
|
||||||
|
|
||||||
import io.bitsquare.btc.AddressEntry;
|
import io.bitsquare.btc.AddressEntry;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.btc.listeners.AddressConfidenceListener;
|
|
||||||
import io.bitsquare.btc.listeners.BalanceListener;
|
import io.bitsquare.btc.listeners.BalanceListener;
|
||||||
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
|
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.TransactionConfidence;
|
|
||||||
|
|
||||||
public class WithdrawalListItem {
|
public class WithdrawalListItem {
|
||||||
private final BalanceListener balanceListener;
|
private final BalanceListener balanceListener;
|
||||||
|
|
||||||
private final Label balanceLabel;
|
private final Label balanceLabel;
|
||||||
|
|
||||||
private final AddressEntry addressEntry;
|
private final AddressEntry addressEntry;
|
||||||
|
|
||||||
private final WalletService walletService;
|
private final WalletService walletService;
|
||||||
private final BSFormatter formatter;
|
private final BSFormatter formatter;
|
||||||
private final AddressConfidenceListener confidenceListener;
|
|
||||||
|
|
||||||
private final ConfidenceProgressIndicator progressIndicator;
|
|
||||||
|
|
||||||
private final Tooltip tooltip;
|
|
||||||
|
|
||||||
private Coin balance;
|
private Coin balance;
|
||||||
private final String addressString;
|
private final String addressString;
|
||||||
|
|
||||||
|
@ -53,24 +40,6 @@ public class WithdrawalListItem {
|
||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
addressString = addressEntry.getAddressString();
|
addressString = addressEntry.getAddressString();
|
||||||
|
|
||||||
// confidence
|
|
||||||
progressIndicator = new ConfidenceProgressIndicator();
|
|
||||||
progressIndicator.setId("funds-confidence");
|
|
||||||
tooltip = new Tooltip("Not used yet");
|
|
||||||
progressIndicator.setProgress(0);
|
|
||||||
progressIndicator.setPrefSize(24, 24);
|
|
||||||
Tooltip.install(progressIndicator, tooltip);
|
|
||||||
|
|
||||||
confidenceListener = walletService.addAddressConfidenceListener(new AddressConfidenceListener(getAddress()) {
|
|
||||||
@Override
|
|
||||||
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
|
|
||||||
updateConfidence(confidence);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateConfidence(walletService.getConfidenceForAddress(getAddress()));
|
|
||||||
|
|
||||||
|
|
||||||
// balance
|
// balance
|
||||||
balanceLabel = new Label();
|
balanceLabel = new Label();
|
||||||
balanceListener = walletService.addBalanceListener(new BalanceListener(getAddress()) {
|
balanceListener = walletService.addBalanceListener(new BalanceListener(getAddress()) {
|
||||||
|
@ -84,7 +53,6 @@ public class WithdrawalListItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
walletService.removeAddressConfidenceListener(confidenceListener);
|
|
||||||
walletService.removeBalanceListener(balanceListener);
|
walletService.removeBalanceListener(balanceListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,62 +63,44 @@ public class WithdrawalListItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateConfidence(TransactionConfidence confidence) {
|
|
||||||
if (confidence != null) {
|
|
||||||
//log.debug("Type numBroadcastPeers getDepthInBlocks " + confidence.getConfidenceType() + " / " +
|
|
||||||
// confidence.numBroadcastPeers() + " / " + confidence.getDepthInBlocks());
|
|
||||||
switch (confidence.getConfidenceType()) {
|
|
||||||
case UNKNOWN:
|
|
||||||
tooltip.setText("Unknown transaction status");
|
|
||||||
progressIndicator.setProgress(0);
|
|
||||||
break;
|
|
||||||
case PENDING:
|
|
||||||
tooltip.setText("Seen by " + confidence.numBroadcastPeers() + " peer(s) / 0 confirmations");
|
|
||||||
progressIndicator.setProgress(-1.0);
|
|
||||||
break;
|
|
||||||
case BUILDING:
|
|
||||||
tooltip.setText("Confirmed in " + confidence.getDepthInBlocks() + " block(s)");
|
|
||||||
progressIndicator.setProgress(Math.min(1, (double) confidence.getDepthInBlocks() / 6.0));
|
|
||||||
break;
|
|
||||||
case DEAD:
|
|
||||||
tooltip.setText("Transaction is invalid.");
|
|
||||||
progressIndicator.setProgress(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final String getLabel() {
|
public final String getLabel() {
|
||||||
switch (addressEntry.getContext()) {
|
switch (addressEntry.getContext()) {
|
||||||
case TRADE:
|
case TRADE:
|
||||||
return "Offer ID: " + addressEntry.getShortOfferId();
|
return addressEntry.getShortOfferId();
|
||||||
case ARBITRATOR:
|
case ARBITRATOR:
|
||||||
return "Arbitration fee";
|
return "Arbitration fee";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof WithdrawalListItem)) return false;
|
||||||
|
|
||||||
|
WithdrawalListItem that = (WithdrawalListItem) o;
|
||||||
|
|
||||||
|
return !(addressEntry != null ? !addressEntry.equals(that.addressEntry) : that.addressEntry != null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return addressEntry != null ? addressEntry.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private Address getAddress() {
|
private Address getAddress() {
|
||||||
return addressEntry.getAddress();
|
return addressEntry.getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public AddressEntry getAddressEntry() {
|
public AddressEntry getAddressEntry() {
|
||||||
return addressEntry;
|
return addressEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ConfidenceProgressIndicator getProgressIndicator() {
|
|
||||||
return progressIndicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Label getBalanceLabel() {
|
public Label getBalanceLabel() {
|
||||||
return balanceLabel;
|
return balanceLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Coin getBalance() {
|
public Coin getBalance() {
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,15 +29,15 @@
|
||||||
|
|
||||||
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
<TableView fx:id="table" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn text="Details" fx:id="detailsColumn" minWidth="100" sortable="false"/>
|
<TableColumn text="Date/Time" fx:id="dateColumn" minWidth="170" maxWidth="170"/>
|
||||||
<TableColumn text="Address" fx:id="addressColumn" minWidth="240" sortable="false">
|
<TableColumn text="Details" fx:id="detailsColumn" minWidth="160" maxWidth="160"/>
|
||||||
|
<TableColumn text="Address" fx:id="addressColumn" minWidth="320" maxWidth="320">
|
||||||
<cellValueFactory>
|
<cellValueFactory>
|
||||||
<PropertyValueFactory property="addressString"/>
|
<PropertyValueFactory property="addressString"/>
|
||||||
</cellValueFactory>
|
</cellValueFactory>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="50" sortable="false"/>
|
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="110"/>
|
||||||
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="30" sortable="false"/>
|
<TableColumn text="Selection" fx:id="selectColumn" minWidth="160" sortable="false"/>
|
||||||
<TableColumn text="" fx:id="selectColumn" prefWidth="80" sortable="false"/>
|
|
||||||
</columns>
|
</columns>
|
||||||
</TableView>
|
</TableView>
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
<TextField fx:id="amountTextField" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
|
<TextField fx:id="amountTextField" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
|
||||||
|
|
||||||
<Label text="Withdraw from address:" GridPane.rowIndex="1"/>
|
<Label text="Withdraw from address:" GridPane.rowIndex="1"/>
|
||||||
<TextField fx:id="withdrawFromTextField" promptText="Select a source address from the table"
|
<TextField fx:id="withdrawFromTextField" editable="false" focusTraversable="false"
|
||||||
GridPane.rowIndex="1" GridPane.columnIndex="1"/>
|
GridPane.rowIndex="1" GridPane.columnIndex="1"/>
|
||||||
|
|
||||||
<Label text="Withdraw to address:" GridPane.rowIndex="2"/>
|
<Label text="Withdraw to address:" GridPane.rowIndex="2"/>
|
||||||
|
|
|
@ -18,14 +18,17 @@
|
||||||
package io.bitsquare.gui.main.funds.withdrawal;
|
package io.bitsquare.gui.main.funds.withdrawal;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||||
import io.bitsquare.app.BitsquareApp;
|
import io.bitsquare.app.BitsquareApp;
|
||||||
import io.bitsquare.btc.AddressEntry;
|
import io.bitsquare.btc.AddressEntry;
|
||||||
|
import io.bitsquare.btc.FeePolicy;
|
||||||
import io.bitsquare.btc.Restrictions;
|
import io.bitsquare.btc.Restrictions;
|
||||||
import io.bitsquare.btc.WalletService;
|
import io.bitsquare.btc.WalletService;
|
||||||
import io.bitsquare.btc.listeners.BalanceListener;
|
import io.bitsquare.btc.listeners.BalanceListener;
|
||||||
import io.bitsquare.common.util.Utilities;
|
import io.bitsquare.common.util.Utilities;
|
||||||
import io.bitsquare.gui.common.view.ActivatableView;
|
import io.bitsquare.gui.common.view.ActivatableView;
|
||||||
import io.bitsquare.gui.common.view.FxmlView;
|
import io.bitsquare.gui.common.view.FxmlView;
|
||||||
|
import io.bitsquare.gui.components.HyperlinkWithIcon;
|
||||||
import io.bitsquare.gui.popups.OfferDetailsPopup;
|
import io.bitsquare.gui.popups.OfferDetailsPopup;
|
||||||
import io.bitsquare.gui.popups.Popup;
|
import io.bitsquare.gui.popups.Popup;
|
||||||
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
import io.bitsquare.gui.popups.TradeDetailsPopup;
|
||||||
|
@ -48,6 +51,7 @@ import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
import org.bitcoinj.core.InsufficientMoneyException;
|
||||||
|
@ -56,8 +60,10 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -71,7 +77,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
@FXML
|
@FXML
|
||||||
TextField withdrawFromTextField, withdrawToTextField, amountTextField;
|
TextField withdrawFromTextField, withdrawToTextField, amountTextField;
|
||||||
@FXML
|
@FXML
|
||||||
TableColumn<WithdrawalListItem, WithdrawalListItem> detailsColumn, addressColumn, balanceColumn, confidenceColumn, selectColumn;
|
TableColumn<WithdrawalListItem, WithdrawalListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, selectColumn;
|
||||||
|
|
||||||
private final WalletService walletService;
|
private final WalletService walletService;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
|
@ -84,11 +90,19 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
private final WalletPasswordPopup walletPasswordPopup;
|
private final WalletPasswordPopup walletPasswordPopup;
|
||||||
private final OfferDetailsPopup offerDetailsPopup;
|
private final OfferDetailsPopup offerDetailsPopup;
|
||||||
private final TradeDetailsPopup tradeDetailsPopup;
|
private final TradeDetailsPopup tradeDetailsPopup;
|
||||||
private final ObservableList<WithdrawalListItem> addressList = FXCollections.observableArrayList();
|
private final ObservableList<WithdrawalListItem> fundedAddresses = FXCollections.observableArrayList();
|
||||||
|
private Set<WithdrawalListItem> selectedItems = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Constructor, lifecycle
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private WithdrawalView(WalletService walletService, TradeManager tradeManager, ClosedTradableManager closedTradableManager,
|
private WithdrawalView(WalletService walletService, TradeManager tradeManager,
|
||||||
FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager, BSFormatter formatter, Preferences preferences,
|
ClosedTradableManager closedTradableManager,
|
||||||
|
FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager,
|
||||||
|
BSFormatter formatter, Preferences preferences,
|
||||||
BtcAddressValidator btcAddressValidator, WalletPasswordPopup walletPasswordPopup,
|
BtcAddressValidator btcAddressValidator, WalletPasswordPopup walletPasswordPopup,
|
||||||
OfferDetailsPopup offerDetailsPopup, TradeDetailsPopup tradeDetailsPopup) {
|
OfferDetailsPopup offerDetailsPopup, TradeDetailsPopup tradeDetailsPopup) {
|
||||||
this.walletService = walletService;
|
this.walletService = walletService;
|
||||||
|
@ -104,72 +118,45 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
this.tradeDetailsPopup = tradeDetailsPopup;
|
this.tradeDetailsPopup = tradeDetailsPopup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
table.setPlaceholder(new Label("No funds for withdrawal available"));
|
table.setPlaceholder(new Label("No funds for withdrawal available"));
|
||||||
|
setDateColumnCellFactory();
|
||||||
setLabelColumnCellFactory();
|
setDetailsColumnCellFactory();
|
||||||
setAddressColumnCellFactory();
|
setAddressColumnCellFactory();
|
||||||
setBalanceColumnCellFactory();
|
setBalanceColumnCellFactory();
|
||||||
setConfidenceColumnCellFactory();
|
|
||||||
setSelectColumnCellFactory();
|
setSelectColumnCellFactory();
|
||||||
|
table.getSortOrder().add(dateColumn);
|
||||||
if (BitsquareApp.DEV_MODE)
|
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean areInputsValid() {
|
|
||||||
return btcAddressValidator.validate(withdrawFromTextField.getText()).and(
|
|
||||||
btcAddressValidator.validate(withdrawToTextField.getText())).isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openTxDetails(WithdrawalListItem item) {
|
|
||||||
try {
|
|
||||||
Utilities.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(e.getMessage());
|
|
||||||
new Popup().warning("Opening browser failed. Please check your internet " +
|
|
||||||
"connection.").show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void activate() {
|
protected void activate() {
|
||||||
withdrawButton.disableProperty().bind(Bindings.createBooleanBinding(() -> !areInputsValid(),
|
updateList();
|
||||||
withdrawFromTextField.textProperty(), amountTextField.textProperty(), withdrawToTextField.textProperty()));
|
table.setItems(fundedAddresses);
|
||||||
table.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
|
reset();
|
||||||
if (newValue != null) {
|
|
||||||
if (Coin.ZERO.compareTo(newValue.getBalance()) <= 0) {
|
|
||||||
amountTextField.setText(newValue.getBalance().toPlainString());
|
|
||||||
withdrawFromTextField.setText(newValue.getAddressEntry().getAddressString());
|
|
||||||
} else {
|
|
||||||
withdrawFromTextField.setText("");
|
|
||||||
withdrawFromTextField.setPromptText("No fund to withdrawal on that address.");
|
|
||||||
amountTextField.setText("");
|
|
||||||
amountTextField.setPromptText("Invalid amount");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fillList();
|
|
||||||
table.setItems(addressList);
|
|
||||||
|
|
||||||
walletService.addBalanceListener(new BalanceListener() {
|
walletService.addBalanceListener(new BalanceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(Coin balance) {
|
public void onBalanceChanged(Coin balance) {
|
||||||
fillList();
|
updateList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
withdrawButton.disableProperty().bind(Bindings.createBooleanBinding(() -> !areInputsValid(),
|
||||||
|
amountTextField.textProperty(), withdrawToTextField.textProperty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void deactivate() {
|
protected void deactivate() {
|
||||||
addressList.forEach(WithdrawalListItem::cleanup);
|
fundedAddresses.forEach(WithdrawalListItem::cleanup);
|
||||||
withdrawButton.disableProperty().unbind();
|
withdrawButton.disableProperty().unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// UI handlers
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void onWithdraw() {
|
public void onWithdraw() {
|
||||||
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
|
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
|
||||||
|
@ -189,9 +176,12 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
log.error("onWithdraw onFailure");
|
log.error("onWithdraw onFailure");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
// try {
|
||||||
Coin requiredFee = walletService.getRequiredFee(withdrawFromTextField.getText(),
|
/* Coin requiredFee = walletService.getRequiredFee(withdrawFromTextField.getText(),
|
||||||
withdrawToTextField.getText(), senderAmount, null);
|
withdrawToTextField.getText(), senderAmount, null);*/
|
||||||
|
// TODO static fee might be not enough when using many inputs, but for now its high enough to get probably into the blockchain
|
||||||
|
// Use bitcoinJ fee calculation instead....
|
||||||
|
Coin requiredFee = FeePolicy.getFeePerKb();
|
||||||
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
Coin receiverAmount = senderAmount.subtract(requiredFee);
|
||||||
if (BitsquareApp.DEV_MODE) {
|
if (BitsquareApp.DEV_MODE) {
|
||||||
doWithdraw(receiverAmount, callback);
|
doWithdraw(receiverAmount, callback);
|
||||||
|
@ -207,90 +197,267 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
.show();
|
.show();
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (AddressFormatException | InsufficientMoneyException e) {
|
/*} catch (AddressFormatException | InsufficientMoneyException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
}
|
}*/
|
||||||
} else {
|
} else {
|
||||||
new Popup().warning("The amount to transfer is lower than the transaction fee and the min. possible tx value.").show();
|
new Popup().warning("The amount to transfer is lower than the transaction fee and the min. possible tx value.").show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void selectForWithdrawal(WithdrawalListItem item, boolean isSelected) {
|
||||||
|
if (isSelected)
|
||||||
|
selectedItems.add(item);
|
||||||
|
else
|
||||||
|
selectedItems.remove(item);
|
||||||
|
|
||||||
|
if (!selectedItems.isEmpty()) {
|
||||||
|
Coin sum = Coin.valueOf(selectedItems.stream().mapToLong(e -> e.getBalance().getValue()).sum());
|
||||||
|
if (sum.isPositive()) {
|
||||||
|
amountTextField.setText(formatter.formatCoin(sum));
|
||||||
|
} else {
|
||||||
|
amountTextField.setText("");
|
||||||
|
withdrawFromTextField.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItems.size() == 1) {
|
||||||
|
withdrawFromTextField.setText(selectedItems.stream().findAny().get().getAddressEntry().getAddressString());
|
||||||
|
withdrawFromTextField.setTooltip(null);
|
||||||
|
} else {
|
||||||
|
//selectedItems.stream().
|
||||||
|
String tooltipText = "Withdraw from multiple addresses:\n" +
|
||||||
|
selectedItems.stream()
|
||||||
|
.map(e -> e.getAddressString())
|
||||||
|
.collect(Collectors.joining(",\n"));
|
||||||
|
int abbr = Math.max(10, 66 / selectedItems.size());
|
||||||
|
String text = "Withdraw from multiple addresses (" +
|
||||||
|
selectedItems.stream()
|
||||||
|
.map(e -> StringUtils.abbreviate(e.getAddressString(), abbr))
|
||||||
|
.collect(Collectors.joining(", ")) +
|
||||||
|
")";
|
||||||
|
withdrawFromTextField.setText(text);
|
||||||
|
withdrawFromTextField.setTooltip(new Tooltip(tooltipText));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openBlockExplorer(WithdrawalListItem item) {
|
||||||
|
if (item.getAddressString() != null) {
|
||||||
|
try {
|
||||||
|
Utilities.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
new Popup().warning("Opening browser failed. Please check your internet " +
|
||||||
|
"connection.").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openDetailPopup(WithdrawalListItem item) {
|
||||||
|
Optional<Tradable> tradableOptional = getTradable(item);
|
||||||
|
if (tradableOptional.isPresent()) {
|
||||||
|
Tradable tradable = tradableOptional.get();
|
||||||
|
if (tradable instanceof Trade) {
|
||||||
|
tradeDetailsPopup.show((Trade) tradable);
|
||||||
|
} else if (tradable instanceof OpenOffer) {
|
||||||
|
offerDetailsPopup.show(tradable.getOffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Private
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void updateList() {
|
||||||
|
Set<String> reservedTrades = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
||||||
|
.map(tradable -> tradable.getOffer().getId())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
fundedAddresses.forEach(WithdrawalListItem::cleanup);
|
||||||
|
fundedAddresses.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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void doWithdraw(Coin amount, FutureCallback<Transaction> callback) {
|
private void doWithdraw(Coin amount, FutureCallback<Transaction> callback) {
|
||||||
if (walletService.getWallet().isEncrypted())
|
if (walletService.getWallet().isEncrypted())
|
||||||
walletPasswordPopup.show().onAesKey(aesKey -> sendFunds(amount, aesKey, callback));
|
walletPasswordPopup.show().onAesKey(aesKey -> sendFunds(amount, aesKey, callback));
|
||||||
else
|
else
|
||||||
sendFunds(amount, null, callback);
|
sendFunds(amount, null, callback);
|
||||||
fillList();
|
updateList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendFunds(Coin amount, KeyParameter aesKey, FutureCallback<Transaction> callback) {
|
private void sendFunds(Coin amount, KeyParameter aesKey, FutureCallback<Transaction> callback) {
|
||||||
try {
|
try {
|
||||||
walletService.sendFunds(withdrawFromTextField.getText(), withdrawToTextField.getText(), amount, aesKey, callback);
|
Set<String> fromAddresses = selectedItems.stream()
|
||||||
|
.map(e -> e.getAddressString())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
if (!fromAddresses.isEmpty()) {
|
||||||
|
walletService.sendFundsForMultipleAddresses(fromAddresses, withdrawToTextField.getText(), amount, null, aesKey, callback);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
} catch (AddressFormatException e) {
|
} catch (AddressFormatException e) {
|
||||||
new Popup().error("The address is not correct. Please check the address format.").show();
|
new Popup().error("The address is not correct. Please check the address format.").show();
|
||||||
} catch (InsufficientMoneyException e) {
|
} catch (InsufficientMoneyException e) {
|
||||||
log.warn(e.getMessage());
|
log.warn(e.getMessage());
|
||||||
new Popup().error("You don't have enough fund in your wallet.").show();
|
new Popup().error("You don't have enough fund in your wallet.").show();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
selectedItems = new HashSet<>();
|
||||||
|
|
||||||
|
table.getSelectionModel().clearSelection();
|
||||||
|
|
||||||
withdrawFromTextField.setText("");
|
withdrawFromTextField.setText("");
|
||||||
withdrawFromTextField.setPromptText("Select a source address from the table");
|
withdrawFromTextField.setPromptText("Select a source address from the table");
|
||||||
|
withdrawFromTextField.setTooltip(null);
|
||||||
|
|
||||||
amountTextField.setText("");
|
amountTextField.setText("");
|
||||||
amountTextField.setPromptText("");
|
amountTextField.setPromptText("Set the amount to withdraw");
|
||||||
|
|
||||||
withdrawToTextField.setText("");
|
withdrawToTextField.setText("");
|
||||||
withdrawToTextField.setPromptText("");
|
withdrawToTextField.setPromptText("Fill in your destination address");
|
||||||
|
|
||||||
|
if (BitsquareApp.DEV_MODE)
|
||||||
|
withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillList() {
|
|
||||||
addressList.clear();
|
|
||||||
|
|
||||||
List<AddressEntry> addressEntryList = walletService.getAddressEntryList();
|
private Optional<Tradable> getTradable(WithdrawalListItem item) {
|
||||||
|
String offerId = item.getAddressEntry().getOfferId();
|
||||||
List<String> reservedTrades = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
|
Optional<Tradable> tradableOptional = closedTradableManager.getTradableById(offerId);
|
||||||
.map(tradable -> tradable.getOffer().getId())
|
if (tradableOptional.isPresent()) {
|
||||||
.collect(Collectors.toList());
|
return tradableOptional;
|
||||||
|
} else if (failedTradesManager.getTradeById(offerId).isPresent()) {
|
||||||
addressList.addAll(addressEntryList.stream()
|
return Optional.of(failedTradesManager.getTradeById(offerId).get());
|
||||||
.filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive())
|
} else {
|
||||||
.filter(e -> !reservedTrades.contains(e.getOfferId()))
|
return Optional.empty();
|
||||||
.map(anAddressEntryList -> new WithdrawalListItem(anAddressEntryList, walletService, formatter))
|
}
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLabelColumnCellFactory() {
|
private boolean isTradableAvailable(WithdrawalListItem item) {
|
||||||
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
String offerId = item.getAddressEntry().getOfferId();
|
||||||
detailsColumn.setCellFactory(new Callback<TableColumn<WithdrawalListItem, WithdrawalListItem>,
|
return closedTradableManager.getTradableById(offerId).isPresent() ||
|
||||||
TableCell<WithdrawalListItem,
|
failedTradesManager.getTradeById(offerId).isPresent();
|
||||||
WithdrawalListItem>>() {
|
}
|
||||||
|
|
||||||
|
private boolean areInputsValid() {
|
||||||
|
return btcAddressValidator.validate(withdrawToTextField.getText()).isValid &&
|
||||||
|
amountTextField.getText().length() > 0 &&
|
||||||
|
Restrictions.isAboveFixedTxFeeAndDust(formatter.parseToCoin(amountTextField.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ColumnCellFactories
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private void setDateColumnCellFactory() {
|
||||||
|
dateColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||||
|
dateColumn.setCellFactory(new Callback<TableColumn<WithdrawalListItem, WithdrawalListItem>,
|
||||||
|
TableCell<WithdrawalListItem, WithdrawalListItem>>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
|
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
|
||||||
WithdrawalListItem> column) {
|
WithdrawalListItem> column) {
|
||||||
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
|
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
|
@Override
|
||||||
public void updateItem(final WithdrawalListItem item, boolean empty) {
|
public void updateItem(final WithdrawalListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
if (item != null && !empty) {
|
if (item != null && !empty) {
|
||||||
if (detailsAvailable(item)) {
|
if (isTradableAvailable(item)) {
|
||||||
Hyperlink hyperlink = new Hyperlink(item.getLabel());
|
AddressEntry addressEntry = item.getAddressEntry();
|
||||||
if (item.getAddressEntry().getOfferId() != null) {
|
String details;
|
||||||
Tooltip tooltip = new Tooltip(item.getAddressEntry().getShortOfferId());
|
if (addressEntry.getContext() == AddressEntry.Context.TRADE) {
|
||||||
Tooltip.install(hyperlink, tooltip);
|
String prefix;
|
||||||
|
if (getTradable(item).isPresent()) {
|
||||||
hyperlink.setOnAction(event -> openDetails(item));
|
Tradable tradable = getTradable(item).get();
|
||||||
setGraphic(hyperlink);
|
if (tradable instanceof Trade)
|
||||||
}
|
prefix = "Trade ID: ";
|
||||||
|
else if (tradable instanceof OpenOffer)
|
||||||
|
prefix = "Offer ID: ";
|
||||||
|
else
|
||||||
|
prefix = "";
|
||||||
} else {
|
} else {
|
||||||
Label label = new Label("No info available");
|
prefix = "";
|
||||||
setGraphic(label);
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
setId(null);
|
if (field != null)
|
||||||
|
field.setOnAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -308,19 +475,23 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
|
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
|
||||||
WithdrawalListItem> column) {
|
WithdrawalListItem> column) {
|
||||||
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
|
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
|
||||||
private Hyperlink hyperlink;
|
private HyperlinkWithIcon hyperlinkWithIcon;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateItem(final WithdrawalListItem item, boolean empty) {
|
public void updateItem(final WithdrawalListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
|
|
||||||
if (item != null && !empty) {
|
if (item != null && !empty) {
|
||||||
hyperlink = new Hyperlink(item.getAddressString());
|
String address = item.getAddressString();
|
||||||
hyperlink.setOnAction(event -> openTxDetails(item));
|
hyperlinkWithIcon = new HyperlinkWithIcon(address, AwesomeIcon.EXTERNAL_LINK);
|
||||||
setGraphic(hyperlink);
|
hyperlinkWithIcon.setOnAction(event -> openBlockExplorer(item));
|
||||||
|
hyperlinkWithIcon.setTooltip(new Tooltip("Open external blockchain explorer for " +
|
||||||
|
"address: " + address));
|
||||||
|
setGraphic(hyperlinkWithIcon);
|
||||||
} else {
|
} else {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
setId(null);
|
if (hyperlinkWithIcon != null)
|
||||||
|
hyperlinkWithIcon.setOnAction(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -348,33 +519,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setConfidenceColumnCellFactory() {
|
|
||||||
confidenceColumn.setCellValueFactory((addressListItem) ->
|
|
||||||
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
|
||||||
confidenceColumn.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) {
|
|
||||||
setGraphic(item.getProgressIndicator());
|
|
||||||
} else {
|
|
||||||
setGraphic(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSelectColumnCellFactory() {
|
private void setSelectColumnCellFactory() {
|
||||||
selectColumn.setCellValueFactory((addressListItem) ->
|
selectColumn.setCellValueFactory((addressListItem) ->
|
||||||
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||||
|
@ -387,46 +531,29 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
||||||
WithdrawalListItem> column) {
|
WithdrawalListItem> column) {
|
||||||
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
|
return new TableCell<WithdrawalListItem, WithdrawalListItem>() {
|
||||||
|
|
||||||
Button button = new Button("Select");
|
CheckBox checkBox;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateItem(final WithdrawalListItem item, boolean empty) {
|
public void updateItem(final WithdrawalListItem item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
if (item != null && !empty) {
|
if (item != null && !empty) {
|
||||||
button.setDefaultButton(true);
|
if (checkBox == null) {
|
||||||
button.setMouseTransparent(true);
|
checkBox = new CheckBox("Select for withdrawal");
|
||||||
setGraphic(button);
|
checkBox.setOnAction(e -> selectForWithdrawal(item, checkBox.isSelected()));
|
||||||
|
setGraphic(checkBox);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
|
if (checkBox != null) {
|
||||||
|
checkBox.setOnAction(null);
|
||||||
|
checkBox = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean detailsAvailable(WithdrawalListItem item) {
|
|
||||||
String offerId = item.getAddressEntry().getOfferId();
|
|
||||||
return closedTradableManager.getTradableById(offerId).isPresent() ||
|
|
||||||
failedTradesManager.getTradeById(offerId).isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openDetails(WithdrawalListItem item) {
|
|
||||||
String offerId = item.getAddressEntry().getOfferId();
|
|
||||||
Optional<Tradable> tradableOptional = closedTradableManager.getTradableById(offerId);
|
|
||||||
if (tradableOptional.isPresent()) {
|
|
||||||
Tradable tradable = tradableOptional.get();
|
|
||||||
if (tradable instanceof Trade) {
|
|
||||||
tradeDetailsPopup.show((Trade) tradable);
|
|
||||||
} else if (tradable instanceof OpenOffer) {
|
|
||||||
offerDetailsPopup.show(tradable.getOffer());
|
|
||||||
}
|
|
||||||
} else if (failedTradesManager.getTradeById(offerId).isPresent()) {
|
|
||||||
tradeDetailsPopup.show(failedTradesManager.getTradeById(offerId).get());
|
|
||||||
} else {
|
|
||||||
log.warn("no details available. A test with detailsAvailable() is missing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
// Constructor, lifecycle
|
// Constructor, lifecycle
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService,
|
CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService,
|
||||||
Preferences preferences, User user, KeyRing keyRing, P2PService p2PService,
|
Preferences preferences, User user, KeyRing keyRing, P2PService p2PService,
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package io.bitsquare.gui.popups;
|
package io.bitsquare.gui.popups;
|
||||||
|
|
||||||
|
import io.bitsquare.arbitration.DisputeManager;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.gui.util.Layout;
|
import io.bitsquare.gui.util.Layout;
|
||||||
import io.bitsquare.locale.BSResources;
|
import io.bitsquare.locale.BSResources;
|
||||||
|
@ -43,6 +44,7 @@ public class TradeDetailsPopup extends Popup {
|
||||||
protected static final Logger log = LoggerFactory.getLogger(TradeDetailsPopup.class);
|
protected static final Logger log = LoggerFactory.getLogger(TradeDetailsPopup.class);
|
||||||
|
|
||||||
private final BSFormatter formatter;
|
private final BSFormatter formatter;
|
||||||
|
private DisputeManager disputeManager;
|
||||||
private Trade trade;
|
private Trade trade;
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,8 +53,9 @@ public class TradeDetailsPopup extends Popup {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TradeDetailsPopup(BSFormatter formatter) {
|
public TradeDetailsPopup(BSFormatter formatter, DisputeManager disputeManager) {
|
||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
|
this.disputeManager = disputeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeDetailsPopup show(Trade trade) {
|
public TradeDetailsPopup show(Trade trade) {
|
||||||
|
@ -119,7 +122,7 @@ public class TradeDetailsPopup extends Popup {
|
||||||
if (buyerPaymentAccountContractData == null && sellerPaymentAccountContractData == null)
|
if (buyerPaymentAccountContractData == null && sellerPaymentAccountContractData == null)
|
||||||
rows++;
|
rows++;
|
||||||
|
|
||||||
if (contract.takeOfferFeeTxID != null)
|
if (trade.getTakeOfferFeeTx() != null)
|
||||||
rows++;
|
rows++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +130,8 @@ public class TradeDetailsPopup extends Popup {
|
||||||
rows++;
|
rows++;
|
||||||
if (trade.getPayoutTx() != null)
|
if (trade.getPayoutTx() != null)
|
||||||
rows++;
|
rows++;
|
||||||
|
if (disputeManager.findOwnDispute(trade.getId()).isPresent())
|
||||||
|
rows++;
|
||||||
if (trade.errorMessageProperty().get() != null)
|
if (trade.errorMessageProperty().get() != null)
|
||||||
rows += 2;
|
rows += 2;
|
||||||
|
|
||||||
|
@ -151,13 +156,15 @@ public class TradeDetailsPopup extends Popup {
|
||||||
}
|
}
|
||||||
|
|
||||||
addLabelTxIdTextField(gridPane, ++rowIndex, "Offer fee transaction ID:", offer.getOfferFeePaymentTxID());
|
addLabelTxIdTextField(gridPane, ++rowIndex, "Offer fee transaction ID:", offer.getOfferFeePaymentTxID());
|
||||||
if (contract != null && contract.takeOfferFeeTxID != null)
|
if (contract != null && trade.getTakeOfferFeeTx() != null)
|
||||||
addLabelTxIdTextField(gridPane, ++rowIndex, "Trading fee transaction ID:", contract.takeOfferFeeTxID);
|
addLabelTxIdTextField(gridPane, ++rowIndex, "Taker fee transaction ID:", trade.getTakeOfferFeeTx().getHashAsString());
|
||||||
|
|
||||||
if (trade.getDepositTx() != null)
|
if (trade.getDepositTx() != null)
|
||||||
addLabelTxIdTextField(gridPane, ++rowIndex, "Deposit transaction ID:", trade.getDepositTx().getHashAsString());
|
addLabelTxIdTextField(gridPane, ++rowIndex, "Deposit transaction ID:", trade.getDepositTx().getHashAsString());
|
||||||
if (trade.getPayoutTx() != null)
|
if (trade.getPayoutTx() != null)
|
||||||
addLabelTxIdTextField(gridPane, ++rowIndex, "Payout transaction ID:", trade.getPayoutTx().getHashAsString());
|
addLabelTxIdTextField(gridPane, ++rowIndex, "Payout transaction ID:", trade.getPayoutTx().getHashAsString());
|
||||||
|
if (disputeManager.findOwnDispute(trade.getId()).isPresent() && disputeManager.findOwnDispute(trade.getId()).get().getDisputePayoutTx() != null)
|
||||||
|
addLabelTxIdTextField(gridPane, ++rowIndex, "Disputed payout transaction ID:", disputeManager.findOwnDispute(trade.getId()).get().getDisputePayoutTx().getHashAsString());
|
||||||
|
|
||||||
if (contract != null) {
|
if (contract != null) {
|
||||||
TextArea textArea = addLabelTextArea(gridPane, ++rowIndex, "Contract in JSON format:", trade.getContractAsJson()).second;
|
TextArea textArea = addLabelTextArea(gridPane, ++rowIndex, "Contract in JSON format:", trade.getContractAsJson()).second;
|
||||||
|
|
BIN
gui/src/main/resources/images/link.png
Normal file
BIN
gui/src/main/resources/images/link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 338 B |
BIN
gui/src/main/resources/images/link@2x.png
Normal file
BIN
gui/src/main/resources/images/link@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 587 B |
Loading…
Add table
Add a link
Reference in a new issue