Improve locked funds screen, add total balance for locked funds

This commit is contained in:
Manfred Karrer 2016-02-03 18:06:34 +01:00
parent c70df863d6
commit 6b3df246a1
21 changed files with 361 additions and 366 deletions

View file

@ -171,7 +171,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
transient private ObjectProperty<Coin> tradeAmountProperty; transient private ObjectProperty<Coin> tradeAmountProperty;
transient private ObjectProperty<Fiat> tradeVolumeProperty; transient private ObjectProperty<Fiat> tradeVolumeProperty;
@Nullable @Nullable
private Transaction takeOfferFeeTx; private String takeOfferFeeTxId;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -562,12 +562,12 @@ abstract public class Trade implements Tradable, Model, Serializable {
return contractHash; return contractHash;
} }
public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) { public void setTakeOfferFeeTxId(String takeOfferFeeTxId) {
this.takeOfferFeeTx = takeOfferFeeTx; this.takeOfferFeeTxId = takeOfferFeeTxId;
} }
public Transaction getTakeOfferFeeTx() { public String getTakeOfferFeeTxId() {
return takeOfferFeeTx; return takeOfferFeeTxId;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -628,7 +628,7 @@ 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\ttakeOfferFeeTxId=" + takeOfferFeeTxId +
"\n\tcontract=" + contract + "\n\tcontract=" + contract +
"\n\ttakerContractSignature.hashCode()='" + (takerContractSignature != null ? takerContractSignature.hashCode() : "") + '\'' + "\n\ttakerContractSignature.hashCode()='" + (takerContractSignature != null ? takerContractSignature.hashCode() : "") + '\'' +
"\n\toffererContractSignature.hashCode()='" + (offererContractSignature != null ? offererContractSignature.hashCode() : "") + '\'' + "\n\toffererContractSignature.hashCode()='" + (offererContractSignature != null ? offererContractSignature.hashCode() : "") + '\'' +

View file

@ -36,6 +36,7 @@ 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;
@ -65,7 +66,6 @@ public class ProcessModel implements Model, Serializable {
// Mutable // Mutable
public final TradingPeer tradingPeer; public final TradingPeer tradingPeer;
transient private TradeMessage tradeMessage; transient private TradeMessage tradeMessage;
private String takeOfferFeeTxId;
private byte[] payoutTxSignature; private byte[] payoutTxSignature;
private List<NodeAddress> takerAcceptedArbitratorNodeAddresses; private List<NodeAddress> takerAcceptedArbitratorNodeAddresses;
@ -78,6 +78,7 @@ public class ProcessModel implements Model, Serializable {
private long changeOutputValue; private long changeOutputValue;
@Nullable @Nullable
private String changeOutputAddress; private String changeOutputAddress;
private Transaction takeOfferFeeTx;
public ProcessModel() { public ProcessModel() {
tradingPeer = new TradingPeer(); tradingPeer = new TradingPeer();
@ -194,14 +195,6 @@ public class ProcessModel implements Model, Serializable {
this.payoutTxSignature = payoutTxSignature; this.payoutTxSignature = payoutTxSignature;
} }
public String getTakeOfferFeeTxId() {
return takeOfferFeeTxId;
}
public void setTakeOfferFeeTxId(String takeOfferFeeTxId) {
this.takeOfferFeeTxId = takeOfferFeeTxId;
}
@Override @Override
public void persist() { public void persist() {
} }
@ -274,4 +267,12 @@ public class ProcessModel implements Model, Serializable {
public String getChangeOutputAddress() { public String getChangeOutputAddress() {
return changeOutputAddress; return changeOutputAddress;
} }
public void setTakeOfferFeeTx(Transaction takeOfferFeeTx) {
this.takeOfferFeeTx = takeOfferFeeTx;
}
public Transaction getTakeOfferFeeTx() {
return takeOfferFeeTx;
}
} }

View file

@ -43,7 +43,7 @@ public class CreateAndSignContract extends TradeTask {
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
checkNotNull(processModel.getTakeOfferFeeTxId(), "processModel.getTakeOfferFeeTxId() must not be null"); checkNotNull(trade.getTakeOfferFeeTxId(), "trade.getTakeOfferFeeTxId() must not be null");
TradingPeer taker = processModel.tradingPeer; TradingPeer taker = processModel.tradingPeer;
PaymentAccountContractData offererPaymentAccountContractData = processModel.getPaymentAccountContractData(trade); PaymentAccountContractData offererPaymentAccountContractData = processModel.getPaymentAccountContractData(trade);
@ -58,7 +58,7 @@ public class CreateAndSignContract extends TradeTask {
Contract contract = new Contract( Contract contract = new Contract(
processModel.getOffer(), processModel.getOffer(),
trade.getTradeAmount(), trade.getTradeAmount(),
processModel.getTakeOfferFeeTxId(), trade.getTakeOfferFeeTxId(),
buyerNodeAddress, buyerNodeAddress,
sellerNodeAddress, sellerNodeAddress,
trade.getArbitratorNodeAddress(), trade.getArbitratorNodeAddress(),

View file

@ -71,7 +71,7 @@ public class ProcessPayDepositRequest extends TradeTask {
} }
processModel.tradingPeer.setAccountId(nonEmptyStringOf(payDepositRequest.takerAccountId)); processModel.tradingPeer.setAccountId(nonEmptyStringOf(payDepositRequest.takerAccountId));
processModel.setTakeOfferFeeTxId(nonEmptyStringOf(payDepositRequest.takeOfferFeeTxId)); trade.setTakeOfferFeeTxId(nonEmptyStringOf(payDepositRequest.takeOfferFeeTxId));
processModel.setTakerAcceptedArbitratorNodeAddresses(checkNotNull(payDepositRequest.acceptedArbitratorNodeAddresses)); processModel.setTakerAcceptedArbitratorNodeAddresses(checkNotNull(payDepositRequest.acceptedArbitratorNodeAddresses));
if (payDepositRequest.acceptedArbitratorNodeAddresses.size() < 1) if (payDepositRequest.acceptedArbitratorNodeAddresses.size() < 1)
failed("acceptedArbitratorNames size must be at least 1"); failed("acceptedArbitratorNames size must be at least 1");

View file

@ -37,7 +37,7 @@ public class BroadcastTakeOfferFeeTx extends TradeTask {
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
processModel.getTradeWalletService().broadcastTx(trade.getTakeOfferFeeTx(), processModel.getTradeWalletService().broadcastTx(processModel.getTakeOfferFeeTx(),
new FutureCallback<Transaction>() { new FutureCallback<Transaction>() {
@Override @Override
public void onSuccess(Transaction transaction) { public void onSuccess(Transaction transaction) {

View file

@ -50,10 +50,8 @@ public class CreateTakeOfferFeeTx extends TradeTask {
FeePolicy.getTakeOfferFee(), FeePolicy.getTakeOfferFee(),
selectedArbitrator.getBtcAddress()); selectedArbitrator.getBtcAddress());
trade.setTakeOfferFeeTx(createTakeOfferFeeTx); processModel.setTakeOfferFeeTx(createTakeOfferFeeTx);
trade.setTakeOfferFeeTxId(createTakeOfferFeeTx.getHashAsString());
// TODO check if needed as we have stored tx already at setTakeOfferFeeTx
processModel.setTakeOfferFeeTxId(createTakeOfferFeeTx.getHashAsString());
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View file

@ -38,8 +38,11 @@ public class SendPayDepositRequest extends TradeTask {
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
if (trade.getTakeOfferFeeTx() != null) {
checkNotNull(trade.getTradeAmount()); checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null");
checkNotNull(trade.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be null");
checkNotNull(processModel.getAddressEntry(), "AddressEntry must not be null");
PayDepositRequest payDepositRequest = new PayDepositRequest( PayDepositRequest payDepositRequest = new PayDepositRequest(
processModel.getMyAddress(), processModel.getMyAddress(),
processModel.getId(), processModel.getId(),
@ -52,7 +55,7 @@ public class SendPayDepositRequest extends TradeTask {
processModel.getPubKeyRing(), processModel.getPubKeyRing(),
processModel.getPaymentAccountContractData(trade), processModel.getPaymentAccountContractData(trade),
processModel.getAccountId(), processModel.getAccountId(),
trade.getTakeOfferFeeTx().getHashAsString(), trade.getTakeOfferFeeTxId(),
processModel.getUser().getAcceptedArbitratorAddresses(), processModel.getUser().getAcceptedArbitratorAddresses(),
trade.getArbitratorNodeAddress() trade.getArbitratorNodeAddress()
); );
@ -81,10 +84,6 @@ public class SendPayDepositRequest extends TradeTask {
} }
} }
); );
} else {
log.error("trade.getTakeOfferFeeTx() = " + trade.getTakeOfferFeeTx());
failed("TakeOfferFeeTx is null");
}
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} }

View file

@ -30,6 +30,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 VerifyAndSignContract extends TradeTask { public class VerifyAndSignContract extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(VerifyAndSignContract.class); private static final Logger log = LoggerFactory.getLogger(VerifyAndSignContract.class);
@ -42,7 +44,8 @@ public class VerifyAndSignContract extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
if (trade.getTakeOfferFeeTx() != null) { checkNotNull(trade.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be 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 +60,7 @@ public class VerifyAndSignContract extends TradeTask {
Contract contract = new Contract( Contract contract = new Contract(
processModel.getOffer(), processModel.getOffer(),
trade.getTradeAmount(), trade.getTradeAmount(),
trade.getTakeOfferFeeTx().getHashAsString(), trade.getTakeOfferFeeTxId(),
buyerNodeAddress, buyerNodeAddress,
sellerNodeAddress, sellerNodeAddress,
trade.getArbitratorNodeAddress(), trade.getArbitratorNodeAddress(),
@ -88,9 +91,6 @@ public class VerifyAndSignContract extends TradeTask {
} }
complete(); complete();
} else {
failed("processModel.getTakeOfferFeeTx() = null");
}
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} }

View file

@ -24,7 +24,7 @@ public class HyperlinkWithIcon extends AnchorPane {
AnchorPane.setLeftAnchor(hyperlink, 0.0); AnchorPane.setLeftAnchor(hyperlink, 0.0);
AnchorPane.setRightAnchor(hyperlink, 15.0); AnchorPane.setRightAnchor(hyperlink, 15.0);
AnchorPane.setRightAnchor(openLinkIcon, 4.0); AnchorPane.setRightAnchor(openLinkIcon, 4.0);
AnchorPane.setTopAnchor(openLinkIcon, 3.0); AnchorPane.setTopAnchor(openLinkIcon, awesomeIcon == AwesomeIcon.INFO_SIGN ? 2.0 : 3.0);
getChildren().addAll(hyperlink, openLinkIcon); getChildren().addAll(hyperlink, openLinkIcon);
} }

View file

@ -124,10 +124,14 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
Tuple2<TextField, VBox> availableBalanceBox = getBalanceBox("Available balance"); Tuple2<TextField, VBox> availableBalanceBox = getBalanceBox("Available balance");
availableBalanceBox.first.textProperty().bind(model.availableBalance); availableBalanceBox.first.textProperty().bind(model.availableBalance);
Tuple2<TextField, VBox> reservedBalanceBox = getBalanceBox("Reserved balance");
reservedBalanceBox.first.textProperty().bind(model.reservedBalance);
Tuple2<TextField, VBox> lockedBalanceBox = getBalanceBox("Locked balance"); Tuple2<TextField, VBox> lockedBalanceBox = getBalanceBox("Locked balance");
lockedBalanceBox.first.textProperty().bind(model.lockedBalance); lockedBalanceBox.first.textProperty().bind(model.lockedBalance);
HBox rightNavPane = new HBox(availableBalanceBox.second, lockedBalanceBox.second, settingsButton, accountButton) {{ HBox rightNavPane = new HBox(availableBalanceBox.second, reservedBalanceBox.second, lockedBalanceBox.second,
settingsButton, accountButton) {{
setSpacing(10); setSpacing(10);
setRightAnchor(this, 10d); setRightAnchor(this, 10d);
setTopAnchor(this, 0d); setTopAnchor(this, 0d);

View file

@ -26,10 +26,7 @@ import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager; import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.arbitration.Dispute; import io.bitsquare.arbitration.Dispute;
import io.bitsquare.arbitration.DisputeManager; import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.*;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.model.ViewModel; import io.bitsquare.gui.common.model.ViewModel;
@ -94,6 +91,7 @@ public class MainViewModel implements ViewModel {
final StringProperty walletServiceErrorMsg = new SimpleStringProperty(); final StringProperty walletServiceErrorMsg = new SimpleStringProperty();
final StringProperty btcSplashSyncIconId = new SimpleStringProperty(); final StringProperty btcSplashSyncIconId = new SimpleStringProperty();
final StringProperty availableBalance = new SimpleStringProperty(); final StringProperty availableBalance = new SimpleStringProperty();
final StringProperty reservedBalance = new SimpleStringProperty();
final StringProperty lockedBalance = new SimpleStringProperty(); final StringProperty lockedBalance = new SimpleStringProperty();
// P2P network // P2P network
@ -493,21 +491,42 @@ public class MainViewModel implements ViewModel {
private void updateBalance() { private void updateBalance() {
updateAvailableBalance(); updateAvailableBalance();
updateReservedBalance();
updateLockedBalance(); updateLockedBalance();
} }
private void updateReservedBalance() {
Coin sum = Coin.valueOf(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
.map(tradable -> walletService.getAddressEntryByOfferId(tradable.getId()))
.map(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()))
.mapToLong(Coin::getValue)
.sum());
reservedBalance.set(formatter.formatCoinWithCode(sum));
}
private void updateLockedBalance() { private void updateLockedBalance() {
List<AddressEntry> result = new ArrayList<>(); Coin sum = Coin.valueOf(tradeManager.getTrades().stream()
.map(trade -> {
result.addAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) switch (trade.getState().getPhase()) {
.map(tradable -> walletService.getAddressEntryByOfferId(tradable.getOffer().getId())) case DEPOSIT_REQUESTED:
.collect(Collectors.toList())); case DEPOSIT_PAID:
case FIAT_SENT:
Optional<Coin> totalLockedOptional = result.stream().map(e -> walletService.getBalanceForAddress(e.getAddress())).reduce((a, b) -> a.add(b)); case FIAT_RECEIVED:
if (totalLockedOptional.isPresent()) Coin balanceInDeposit = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb());
lockedBalance.set(formatter.formatCoinWithCode(totalLockedOptional.get())); if (trade.getContract() != null &&
else trade.getTradeAmount() != null &&
lockedBalance.set(formatter.formatCoinWithCode(Coin.ZERO)); trade.getContract().getSellerPayoutAddressString()
.equals(walletService.getAddressEntryByOfferId(trade.getId()).getAddressString())) {
balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount());
}
return balanceInDeposit;
default:
return Coin.ZERO;
}
})
.mapToLong(Coin::getValue)
.sum());
lockedBalance.set(formatter.formatCoinWithCode(sum));
} }
private void updateAvailableBalance() { private void updateAvailableBalance() {

View file

@ -24,8 +24,8 @@
AnchorPane.topAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml"> xmlns:fx="http://javafx.com/fxml">
<Tab fx:id="reservedTab" text="Reserved for trades" closable="false"></Tab> <Tab fx:id="reservedTab" text="Reserved/Locked funds" closable="false"></Tab>
<Tab fx:id="withdrawalTab" text="Open for withdrawal" closable="false"/> <Tab fx:id="withdrawalTab" text="Available for withdrawal" closable="false"/>
<Tab fx:id="transactionsTab" text="Transactions" closable="false"/> <Tab fx:id="transactionsTab" text="Transactions" closable="false"/>
</TabPane> </TabPane>

View file

@ -20,19 +20,16 @@ package io.bitsquare.gui.main.funds.reserved;
import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
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 io.bitsquare.trade.Tradable; import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
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;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -41,19 +38,12 @@ public class ReservedListItem {
private final StringProperty date = new SimpleStringProperty(); private final StringProperty date = new SimpleStringProperty();
private final BalanceListener balanceListener; private final BalanceListener balanceListener;
private final Label balanceLabel; private final Label balanceLabel;
private String fundsInfo;
private final Tradable tradable; private final Tradable tradable;
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 final String addressString; private final String addressString;
private Coin balance; private Coin balance;
@ -64,25 +54,7 @@ public class ReservedListItem {
this.formatter = formatter; this.formatter = formatter;
addressString = addressEntry.getAddressString(); addressString = addressEntry.getAddressString();
// confidence date.set(formatter.formatDateTime(tradable.getDate()));
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()));
//date.set(formatter.formatDateTime(transaction.getUpdateTime()));
// balance // balance
balanceLabel = new Label(); balanceLabel = new Label();
@ -97,113 +69,67 @@ public class ReservedListItem {
} }
public void cleanup() { public void cleanup() {
walletService.removeAddressConfidenceListener(confidenceListener);
walletService.removeBalanceListener(balanceListener); walletService.removeBalanceListener(balanceListener);
} }
private void updateBalance(Coin balance) { private void updateBalance(Coin balance) {
this.balance = balance; this.balance = balance;
if (balance != null) { if (balance != null) {
balanceLabel.setText(formatter.formatCoin(balance));
if (tradable instanceof Trade) { if (tradable instanceof Trade) {
Trade trade = (Trade) tradable; Trade trade = (Trade) tradable;
Trade.Phase phase = trade.getState().getPhase(); Trade.Phase phase = trade.getState().getPhase();
switch (phase) { switch (phase) {
case PREPARATION: case PREPARATION:
case TAKER_FEE_PAID: case TAKER_FEE_PAID:
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (locally reserved)"); fundsInfo = "Reserved in local wallet";
break; break;
case DEPOSIT_REQUESTED: case DEPOSIT_REQUESTED:
case DEPOSIT_PAID: case DEPOSIT_PAID:
case FIAT_SENT: case FIAT_SENT:
case FIAT_RECEIVED: case FIAT_RECEIVED:
fundsInfo = "Locked in MultiSig";
// We ignore the tx fee as it will be paid by both (once deposit, once payout) // We ignore the tx fee as it will be paid by both (once deposit, once payout)
Coin balanceInDeposit = FeePolicy.getSecurityDeposit(); Coin balanceInDeposit = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb());
// For the seller we add the trade amount // For the seller we add the trade amount
if (trade.getContract() != null && if (trade.getContract() != null &&
trade.getTradeAmount() != null && trade.getTradeAmount() != null &&
trade.getContract().getSellerPayoutAddressString().equals(addressString)) trade.getContract().getSellerPayoutAddressString().equals(addressString))
balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount()); balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount());
balanceLabel.setText(formatter.formatCoinWithCode(balanceInDeposit) + " (in MS escrow)"); balanceLabel.setText(formatter.formatCoin(balanceInDeposit));
break; break;
case PAYOUT_PAID: case PAYOUT_PAID:
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (in local wallet)"); fundsInfo = "Received in local wallet";
break; break;
case WITHDRAWN: case WITHDRAWN:
log.error("Invalid state at updateBalance (WITHDRAWN)"); log.error("Invalid state at updateBalance (WITHDRAWN)");
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " already withdrawn");
break; break;
case DISPUTE: case DISPUTE:
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " open dispute/ticket"); log.error("Invalid state at updateBalance (DISPUTE)");
break; break;
default: default:
log.warn("Not supported tradePhase: " + phase); log.warn("Not supported tradePhase: " + phase);
} }
} else if (tradable instanceof OpenOffer) {
} else fundsInfo = "Reserved in local wallet";
balanceLabel.setText(formatter.formatCoin(balance));
} }
} }
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() {
switch (addressEntry.getContext()) {
case TRADE:
if (tradable instanceof Trade)
return "Trade ID: " + addressEntry.getShortOfferId();
else
return "Offer ID: " + addressEntry.getShortOfferId();
case ARBITRATOR:
return "Arbitration deposit";
}
return "";
} }
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;
} }
@ -211,4 +137,8 @@ public class ReservedListItem {
public String getAddressString() { public String getAddressString() {
return addressString; return addressString;
} }
public String getFundsInfo() {
return fundsInfo;
}
} }

View file

@ -29,15 +29,14 @@
<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="170" maxWidth="170"/>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="260"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="320" maxWidth="320">
<cellValueFactory> <cellValueFactory>
<PropertyValueFactory property="date"/> <PropertyValueFactory property="addressString"/>
</cellValueFactory> </cellValueFactory>
</TableColumn> </TableColumn>
<TableColumn text="Details" fx:id="detailsColumn" minWidth="100" sortable="false"/> <TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="110"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="240" sortable="false"/>
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="90" sortable="false"/>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="30" sortable="false"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -17,15 +17,19 @@
package io.bitsquare.gui.main.funds.reserved; package io.bitsquare.gui.main.funds.reserved;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.btc.AddressEntry;
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;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.offer.OpenOffer; import io.bitsquare.trade.offer.OpenOffer;
@ -59,7 +63,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
private final BSFormatter formatter; private final BSFormatter formatter;
private final OfferDetailsPopup offerDetailsPopup; private final OfferDetailsPopup offerDetailsPopup;
private final TradeDetailsPopup tradeDetailsPopup; private final TradeDetailsPopup tradeDetailsPopup;
private final ObservableList<ReservedListItem> addressList = FXCollections.observableArrayList(); private final ObservableList<ReservedListItem> reservedAddresses = FXCollections.observableArrayList();
private BalanceListener balanceListener;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -82,30 +87,31 @@ public class ReservedView extends ActivatableView<VBox, Void> {
@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 funded are reserved in open offers or trades")); table.setPlaceholder(new Label("No funds are reserved in open offers or trades"));
setDateColumnCellFactory();
setLabelColumnCellFactory(); setDetailsColumnCellFactory();
setAddressColumnCellFactory(); setAddressColumnCellFactory();
setBalanceColumnCellFactory(); setBalanceColumnCellFactory();
setConfidenceColumnCellFactory(); table.getSortOrder().add(dateColumn);
balanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance) {
updateList();
}
};
} }
@Override @Override
protected void activate() { protected void activate() {
updateList(); updateList();
table.setItems(addressList);
walletService.addBalanceListener(new BalanceListener() { walletService.addBalanceListener(balanceListener);
@Override
public void onBalanceChanged(Coin balance) {
updateList();
}
});
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
addressList.forEach(ReservedListItem::cleanup); reservedAddresses.forEach(ReservedListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
} }
@ -114,10 +120,13 @@ public class ReservedView extends ActivatableView<VBox, Void> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() { private void updateList() {
addressList.forEach(ReservedListItem::cleanup); reservedAddresses.forEach(ReservedListItem::cleanup);
addressList.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) reservedAddresses.setAll(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()));
reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()));
table.setItems(reservedAddresses);
} }
private void openBlockExplorer(ReservedListItem item) { private void openBlockExplorer(ReservedListItem item) {
@ -130,47 +139,115 @@ public class ReservedView extends ActivatableView<VBox, Void> {
} }
} }
private Optional<Tradable> getTradable(ReservedListItem item) {
String offerId = item.getAddressEntry().getOfferId();
Optional<Trade> tradeOptional = tradeManager.getTradeById(offerId);
if (tradeOptional.isPresent()) {
return Optional.of(tradeOptional.get());
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {
return Optional.of(openOfferManager.getOpenOfferById(offerId).get());
} else {
return Optional.empty();
}
}
private void openDetailPopup(ReservedListItem 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());
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories // ColumnCellFactories
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setLabelColumnCellFactory() { private void setDateColumnCellFactory() {
detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); dateColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
detailsColumn.setCellFactory(new Callback<TableColumn<ReservedListItem, ReservedListItem>, dateColumn.setCellFactory(new Callback<TableColumn<ReservedListItem, ReservedListItem>,
TableCell<ReservedListItem, TableCell<ReservedListItem, ReservedListItem>>() {
ReservedListItem>>() {
@Override @Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem, public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) { ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() { return new TableCell<ReservedListItem, ReservedListItem>() {
private Hyperlink hyperlink; @Override
public void updateItem(final ReservedListItem 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<ReservedListItem, ReservedListItem>,
TableCell<ReservedListItem, ReservedListItem>>() {
@Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() {
private HyperlinkWithIcon field;
@Override @Override
public void updateItem(final ReservedListItem item, boolean empty) { public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (item != null && !empty) {
hyperlink = new Hyperlink(item.getLabel()); Optional<Tradable> tradableOptional = getTradable(item);
if (item.getAddressEntry().getOfferId() != null) { if (tradableOptional.isPresent()) {
Tooltip tooltip = new Tooltip(item.getAddressEntry().getOfferId()); AddressEntry addressEntry = item.getAddressEntry();
Tooltip.install(hyperlink, tooltip); String details;
if (addressEntry.getContext() == AddressEntry.Context.TRADE) {
String prefix;
Tradable tradable = tradableOptional.get();
if (tradable instanceof Trade)
prefix = "Trade ID: ";
else if (tradable instanceof OpenOffer)
prefix = "Offer ID: ";
else
prefix = "";
hyperlink.setOnAction(event -> { details = prefix + addressEntry.getShortOfferId();
Optional<Trade> tradeOptional = tradeManager.getTradeById(item.getAddressEntry().getOfferId()); } else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(item.getAddressEntry().getOfferId()); details = "Arbitration fee";
if (tradeOptional.isPresent()) } else {
tradeDetailsPopup.show(tradeOptional.get()); details = "-";
else if (openOfferOptional.isPresent())
offerDetailsPopup.show(openOfferOptional.get().getOffer());
});
} }
setGraphic(hyperlink);
field = new HyperlinkWithIcon(details + " (" + item.getFundsInfo() + ")",
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);
} }
} }
}; };
@ -188,19 +265,23 @@ public class ReservedView extends ActivatableView<VBox, Void> {
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem, public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) { ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() { return new TableCell<ReservedListItem, ReservedListItem>() {
private Hyperlink hyperlink; private HyperlinkWithIcon hyperlinkWithIcon;
@Override @Override
public void updateItem(final ReservedListItem item, boolean empty) { public void updateItem(final ReservedListItem 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 -> openBlockExplorer(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);
} }
} }
}; };
@ -228,33 +309,6 @@ public class ReservedView extends ActivatableView<VBox, Void> {
}); });
} }
private void setConfidenceColumnCellFactory() {
confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
confidenceColumn.setCellFactory(
new Callback<TableColumn<ReservedListItem, ReservedListItem>, TableCell<ReservedListItem,
ReservedListItem>>() {
@Override
public TableCell<ReservedListItem, ReservedListItem> call(TableColumn<ReservedListItem,
ReservedListItem> column) {
return new TableCell<ReservedListItem, ReservedListItem>() {
@Override
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getProgressIndicator());
} else {
setGraphic(null);
}
}
};
}
});
}
} }

View file

@ -113,7 +113,7 @@ public class TransactionsListItem {
details = "Create offer fee: " + tradable.getShortId(); details = "Create offer fee: " + tradable.getShortId();
} else if (tradable instanceof Trade) { } else if (tradable instanceof Trade) {
Trade trade = (Trade) tradable; Trade trade = (Trade) tradable;
if (trade.getTakeOfferFeeTx() != null && trade.getTakeOfferFeeTx().getHashAsString().equals(txId)) { if (trade.getTakeOfferFeeTxId() != null && trade.getTakeOfferFeeTxId().equals(txId)) {
details = "Take offer fee: " + tradable.getShortId(); details = "Take offer fee: " + tradable.getShortId();
} else if (trade.getOffer() != null && } else if (trade.getOffer() != null &&
trade.getOffer().getOfferFeePaymentTxID() != null && trade.getOffer().getOfferFeePaymentTxID() != null &&

View file

@ -36,7 +36,7 @@
<TableColumn text="Details" fx:id="detailsColumn" minWidth="210" maxWidth="210"/> <TableColumn text="Details" fx:id="detailsColumn" minWidth="210" maxWidth="210"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="180"/> <TableColumn text="Address" fx:id="addressColumn" minWidth="180"/>
<TableColumn text="Transaction" fx:id="transactionColumn" minWidth="100"/> <TableColumn text="Transaction" fx:id="transactionColumn" minWidth="100"/>
<TableColumn text="Amount" fx:id="amountColumn" minWidth="110" maxWidth="110"> <TableColumn text="Amount (BTC)" fx:id="amountColumn" minWidth="110" maxWidth="110">
<cellValueFactory> <cellValueFactory>
<PropertyValueFactory property="amount"/> <PropertyValueFactory property="amount"/>
</cellValueFactory> </cellValueFactory>

View file

@ -73,7 +73,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final BSFormatter formatter; private final BSFormatter formatter;
private final Preferences preferences; private final Preferences preferences;
private final TradeDetailsPopup tradeDetailsPopup; private final TradeDetailsPopup tradeDetailsPopup;
private DisputeManager disputeManager; private final DisputeManager disputeManager;
private final OfferDetailsPopup offerDetailsPopup; private final OfferDetailsPopup offerDetailsPopup;
private WalletEventListener walletEventListener; private WalletEventListener walletEventListener;
@ -150,7 +150,6 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Override @Override
protected void activate() { protected void activate() {
updateList(); updateList();
table.setItems(transactionsListItems);
walletService.getWallet().addEventListener(walletEventListener); walletService.getWallet().addEventListener(walletEventListener);
} }
@ -169,19 +168,19 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
Stream<Tradable> concat1 = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()); Stream<Tradable> concat1 = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream());
Stream<Tradable> concat2 = Stream.concat(concat1, closedTradableManager.getClosedTrades().stream()); Stream<Tradable> concat2 = Stream.concat(concat1, closedTradableManager.getClosedTrades().stream());
Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream()); Stream<Tradable> concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream());
Set<Tradable> allTradables = concat3.collect(Collectors.toSet()); Set<Tradable> concated = concat3.collect(Collectors.toSet());
List<TransactionsListItem> listItems = walletService.getWallet().getRecentTransactions(1000, true).stream() List<TransactionsListItem> listItems = walletService.getWallet().getRecentTransactions(1000, true).stream()
.map(transaction -> { .map(transaction -> {
Optional<Tradable> tradableOptional = allTradables.stream() Optional<Tradable> tradableOptional = concated.stream()
.filter(e -> { .filter(e -> {
String txId = transaction.getHashAsString(); String txId = transaction.getHashAsString();
if (e instanceof OpenOffer) if (e instanceof OpenOffer)
return e.getOffer().getOfferFeePaymentTxID().equals(txId); return e.getOffer().getOfferFeePaymentTxID().equals(txId);
else if (e instanceof Trade) { else if (e instanceof Trade) {
Trade trade = (Trade) e; Trade trade = (Trade) e;
return (trade.getTakeOfferFeeTx() != null && return (trade.getTakeOfferFeeTxId() != null &&
trade.getTakeOfferFeeTx().getHashAsString().equals(txId)) || trade.getTakeOfferFeeTxId().equals(txId)) ||
(trade.getOffer() != null && (trade.getOffer() != null &&
trade.getOffer().getOfferFeePaymentTxID() != null && trade.getOffer().getOfferFeePaymentTxID() != null &&
trade.getOffer().getOfferFeePaymentTxID().equals(txId)) || trade.getOffer().getOfferFeePaymentTxID().equals(txId)) ||
@ -205,6 +204,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
// are sorted by getRecentTransactions // are sorted by getRecentTransactions
transactionsListItems.forEach(TransactionsListItem::cleanup); transactionsListItems.forEach(TransactionsListItem::cleanup);
transactionsListItems.setAll(listItems); transactionsListItems.setAll(listItems);
table.setItems(transactionsListItems);
} }
private void openBlockExplorer(TransactionsListItem item) { private void openBlockExplorer(TransactionsListItem item) {

View file

@ -36,7 +36,7 @@
<PropertyValueFactory property="addressString"/> <PropertyValueFactory property="addressString"/>
</cellValueFactory> </cellValueFactory>
</TableColumn> </TableColumn>
<TableColumn text="Balance" fx:id="balanceColumn" minWidth="110"/> <TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="110"/>
<TableColumn text="Selection" fx:id="selectColumn" minWidth="160" sortable="false"/> <TableColumn text="Selection" fx:id="selectColumn" minWidth="160" sortable="false"/>
</columns> </columns>
</TableView> </TableView>

View file

@ -21,7 +21,6 @@ import com.google.common.util.concurrent.FutureCallback;
import de.jensd.fx.fontawesome.AwesomeIcon; import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.app.BitsquareApp; import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.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;
@ -92,6 +91,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
private final TradeDetailsPopup tradeDetailsPopup; private final TradeDetailsPopup tradeDetailsPopup;
private final ObservableList<WithdrawalListItem> fundedAddresses = FXCollections.observableArrayList(); private final ObservableList<WithdrawalListItem> fundedAddresses = FXCollections.observableArrayList();
private Set<WithdrawalListItem> selectedItems = new HashSet<>(); private Set<WithdrawalListItem> selectedItems = new HashSet<>();
private BalanceListener balanceListener;
private Set<String> fromAddresses;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -121,7 +122,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@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 are available"));
setDateColumnCellFactory(); setDateColumnCellFactory();
setDetailsColumnCellFactory(); setDetailsColumnCellFactory();
setAddressColumnCellFactory(); setAddressColumnCellFactory();
@ -129,19 +130,22 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
setSelectColumnCellFactory(); setSelectColumnCellFactory();
table.getSortOrder().add(dateColumn); table.getSortOrder().add(dateColumn);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
balanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance) {
updateList();
}
};
} }
@Override @Override
protected void activate() { protected void activate() {
updateList(); updateList();
table.setItems(fundedAddresses);
reset(); reset();
walletService.addBalanceListener(new BalanceListener() {
@Override walletService.addBalanceListener(balanceListener);
public void onBalanceChanged(Coin balance) {
updateList();
}
});
withdrawButton.disableProperty().bind(Bindings.createBooleanBinding(() -> !areInputsValid(), withdrawButton.disableProperty().bind(Bindings.createBooleanBinding(() -> !areInputsValid(),
amountTextField.textProperty(), withdrawToTextField.textProperty())); amountTextField.textProperty(), withdrawToTextField.textProperty()));
} }
@ -150,6 +154,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
protected void deactivate() { protected void deactivate() {
fundedAddresses.forEach(WithdrawalListItem::cleanup); fundedAddresses.forEach(WithdrawalListItem::cleanup);
withdrawButton.disableProperty().unbind(); withdrawButton.disableProperty().unbind();
walletService.removeBalanceListener(balanceListener);
} }
@ -160,7 +165,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML @FXML
public void onWithdraw() { public void onWithdraw() {
Coin senderAmount = formatter.parseToCoin(amountTextField.getText()); Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
if (Restrictions.isAboveDust(senderAmount)) { if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() { FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
@Override @Override
public void onSuccess(@javax.annotation.Nullable Transaction transaction) { public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
@ -176,12 +181,9 @@ 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.getRequiredFeeForMultipleAddresses(fromAddresses,
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);
@ -197,12 +199,14 @@ 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 (dust).")
.show();
} }
} }
@ -212,6 +216,10 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
else else
selectedItems.remove(item); selectedItems.remove(item);
fromAddresses = selectedItems.stream()
.map(WithdrawalListItem::getAddressString)
.collect(Collectors.toSet());
if (!selectedItems.isEmpty()) { if (!selectedItems.isEmpty()) {
Coin sum = Coin.valueOf(selectedItems.stream().mapToLong(e -> e.getBalance().getValue()).sum()); Coin sum = Coin.valueOf(selectedItems.stream().mapToLong(e -> e.getBalance().getValue()).sum());
if (sum.isPositive()) { if (sum.isPositive()) {
@ -225,10 +233,9 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawFromTextField.setText(selectedItems.stream().findAny().get().getAddressEntry().getAddressString()); withdrawFromTextField.setText(selectedItems.stream().findAny().get().getAddressEntry().getAddressString());
withdrawFromTextField.setTooltip(null); withdrawFromTextField.setTooltip(null);
} else { } else {
//selectedItems.stream().
String tooltipText = "Withdraw from multiple addresses:\n" + String tooltipText = "Withdraw from multiple addresses:\n" +
selectedItems.stream() selectedItems.stream()
.map(e -> e.getAddressString()) .map(WithdrawalListItem::getAddressString)
.collect(Collectors.joining(",\n")); .collect(Collectors.joining(",\n"));
int abbr = Math.max(10, 66 / selectedItems.size()); int abbr = Math.max(10, 66 / selectedItems.size());
String text = "Withdraw from multiple addresses (" + String text = "Withdraw from multiple addresses (" +
@ -299,6 +306,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
return date2.compareTo(date1); return date2.compareTo(date1);
}); });
table.setItems(fundedAddresses);
} }
private void doWithdraw(Coin amount, FutureCallback<Transaction> callback) { private void doWithdraw(Coin amount, FutureCallback<Transaction> callback) {
@ -306,19 +314,13 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
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);
updateList();
} }
private void sendFunds(Coin amount, KeyParameter aesKey, FutureCallback<Transaction> callback) { private void sendFunds(Coin amount, KeyParameter aesKey, FutureCallback<Transaction> callback) {
try { try {
Set<String> fromAddresses = selectedItems.stream()
.map(e -> e.getAddressString())
.collect(Collectors.toSet());
if (!fromAddresses.isEmpty()) {
walletService.sendFundsForMultipleAddresses(fromAddresses, withdrawToTextField.getText(), amount, null, aesKey, callback); walletService.sendFundsForMultipleAddresses(fromAddresses, withdrawToTextField.getText(), amount, null, aesKey, callback);
reset(); reset();
} updateList();
} 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) {
@ -346,7 +348,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq"); withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
} }
private Optional<Tradable> getTradable(WithdrawalListItem item) { private Optional<Tradable> getTradable(WithdrawalListItem item) {
String offerId = item.getAddressEntry().getOfferId(); String offerId = item.getAddressEntry().getOfferId();
Optional<Tradable> tradableOptional = closedTradableManager.getTradableById(offerId); Optional<Tradable> tradableOptional = closedTradableManager.getTradableById(offerId);
@ -359,12 +360,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
} }
} }
private boolean isTradableAvailable(WithdrawalListItem item) {
String offerId = item.getAddressEntry().getOfferId();
return closedTradableManager.getTradableById(offerId).isPresent() ||
failedTradesManager.getTradeById(offerId).isPresent();
}
private boolean areInputsValid() { private boolean areInputsValid() {
return btcAddressValidator.validate(withdrawToTextField.getText()).isValid && return btcAddressValidator.validate(withdrawToTextField.getText()).isValid &&
amountTextField.getText().length() > 0 && amountTextField.getText().length() > 0 &&
@ -420,22 +415,19 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (item != null && !empty) {
if (isTradableAvailable(item)) { Optional<Tradable> tradableOptional = getTradable(item);
if (tradableOptional.isPresent()) {
AddressEntry addressEntry = item.getAddressEntry(); AddressEntry addressEntry = item.getAddressEntry();
String details; String details;
if (addressEntry.getContext() == AddressEntry.Context.TRADE) { if (addressEntry.getContext() == AddressEntry.Context.TRADE) {
String prefix; String prefix;
if (getTradable(item).isPresent()) { Tradable tradable = tradableOptional.get();
Tradable tradable = getTradable(item).get();
if (tradable instanceof Trade) if (tradable instanceof Trade)
prefix = "Trade ID: "; prefix = "Trade ID: ";
else if (tradable instanceof OpenOffer) else if (tradable instanceof OpenOffer)
prefix = "Offer ID: "; prefix = "Offer ID: ";
else else
prefix = ""; prefix = "";
} else {
prefix = "";
}
details = prefix + addressEntry.getShortOfferId(); details = prefix + addressEntry.getShortOfferId();
} else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) { } else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) {

View file

@ -121,11 +121,10 @@ public class TradeDetailsPopup extends Popup {
if (buyerPaymentAccountContractData == null && sellerPaymentAccountContractData == null) if (buyerPaymentAccountContractData == null && sellerPaymentAccountContractData == null)
rows++; rows++;
if (trade.getTakeOfferFeeTx() != null)
rows++;
} }
if (trade.getTakeOfferFeeTxId() != null)
rows++;
if (trade.getDepositTx() != null) if (trade.getDepositTx() != null)
rows++; rows++;
if (trade.getPayoutTx() != null) if (trade.getPayoutTx() != null)
@ -156,8 +155,8 @@ 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 && trade.getTakeOfferFeeTx() != null) if (trade.getTakeOfferFeeTxId() != null)
addLabelTxIdTextField(gridPane, ++rowIndex, "Taker fee transaction ID:", trade.getTakeOfferFeeTx().getHashAsString()); addLabelTxIdTextField(gridPane, ++rowIndex, "Taker fee transaction ID:", trade.getTakeOfferFeeTxId());
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());