Add deposit funds screen

This commit is contained in:
Manfred Karrer 2016-03-22 00:33:05 +01:00
parent 605cf27eaf
commit fa11cb4a6b
23 changed files with 675 additions and 37 deletions

View file

@ -62,6 +62,22 @@ public class Log {
logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.INFO); logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.INFO);
logbackLogger.addAppender(appender); logbackLogger.addAppender(appender);
// log errors in separate file
// not working as expected still.... damn logback...
/* FileAppender errorAppender = new FileAppender();
errorAppender.setEncoder(encoder);
errorAppender.setName("Error");
errorAppender.setContext(loggerContext);
errorAppender.setFile(fileName + "_error.log");
LevelFilter levelFilter = new LevelFilter();
levelFilter.setLevel(Level.ERROR);
levelFilter.setOnMatch(FilterReply.ACCEPT);
levelFilter.setOnMismatch(FilterReply.DENY);
levelFilter.start();
errorAppender.addFilter(levelFilter);
errorAppender.start();
logbackLogger.addAppender(errorAppender);*/
} }
public static void traceCall() { public static void traceCall() {

View file

@ -499,7 +499,7 @@ public class DisputeManager {
contract.getBuyerPayoutAddressString(), contract.getBuyerPayoutAddressString(),
contract.getSellerPayoutAddressString(), contract.getSellerPayoutAddressString(),
disputeResult.getArbitratorAddressAsString(), disputeResult.getArbitratorAddressAsString(),
walletService.getAddressEntryByOfferId(dispute.getTradeId()), walletService.getTradeAddressEntry(dispute.getTradeId()),
contract.getBuyerBtcPubKey(), contract.getBuyerBtcPubKey(),
contract.getSellerBtcPubKey(), contract.getSellerBtcPubKey(),
disputeResult.getArbitratorPubKey() disputeResult.getArbitratorPubKey()

View file

@ -44,8 +44,10 @@ public final class AddressEntry implements Persistable {
private static final Logger log = LoggerFactory.getLogger(AddressEntry.class); private static final Logger log = LoggerFactory.getLogger(AddressEntry.class);
public enum Context { public enum Context {
SAVINGS,
TRADE, TRADE,
ARBITRATOR ARBITRATOR,
DAO
} }
// keyPair can be null in case the object is created from deserialization as it is transient. // keyPair can be null in case the object is created from deserialization as it is transient.

View file

@ -66,9 +66,16 @@ public final class AddressEntryList extends ArrayList<AddressEntry> implements P
} }
} }
public AddressEntry getNewAddressEntry(AddressEntry.Context context, String offerId) { public AddressEntry getNewTradeAddressEntry(String offerId) {
log.trace("getNewAddressEntry called with offerId " + offerId); log.trace("getNewAddressEntry called with offerId " + offerId);
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId); AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.TRADE, offerId);
add(addressEntry);
storage.queueUpForSave();
return addressEntry;
}
public AddressEntry getNewSavingsAddressEntry() {
AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.SAVINGS);
add(addressEntry); add(addressEntry);
storage.queueUpForSave(); storage.queueUpForSave();
return addressEntry; return addressEntry;

View file

@ -312,18 +312,28 @@ public class WalletService {
return ImmutableList.copyOf(addressEntryList); return ImmutableList.copyOf(addressEntryList);
} }
public List<AddressEntry> getSavingsAddressEntryList() {
return getAddressEntryList().stream()
.filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS))
.collect(Collectors.toList());
}
public AddressEntry getArbitratorAddressEntry() { public AddressEntry getArbitratorAddressEntry() {
return arbitratorAddressEntry; return arbitratorAddressEntry;
} }
public AddressEntry getAddressEntryByOfferId(String offerId) { public AddressEntry getTradeAddressEntry(String offerId) {
Optional<AddressEntry> addressEntry = getAddressEntryList().stream() Optional<AddressEntry> addressEntry = getAddressEntryList().stream()
.filter(e -> offerId.equals(e.getOfferId())) .filter(e -> offerId.equals(e.getOfferId()))
.findAny(); .findAny();
if (addressEntry.isPresent()) if (addressEntry.isPresent())
return addressEntry.get(); return addressEntry.get();
else else
return addressEntryList.getNewAddressEntry(AddressEntry.Context.TRADE, offerId); return addressEntryList.getNewTradeAddressEntry(offerId);
}
public AddressEntry getNewSavingsAddressEntry() {
return addressEntryList.getNewSavingsAddressEntry();
} }
private Optional<AddressEntry> getAddressEntryByAddress(String address) { private Optional<AddressEntry> getAddressEntryByAddress(String address) {

View file

@ -302,7 +302,7 @@ public class TradeManager {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) { public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
AddressEntry addressEntry = walletService.getAddressEntryByOfferId(trade.getId()); AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId());
String fromAddress = addressEntry.getAddressString(); String fromAddress = addressEntry.getAddressString();
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() { FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {

View file

@ -45,7 +45,7 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
try { try {
runInterceptHook(); runInterceptHook();
Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades()); Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades());
AddressEntry addressEntry = model.walletService.getAddressEntryByOfferId(model.offer.getId()); AddressEntry addressEntry = model.walletService.getTradeAddressEntry(model.offer.getId());
Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress()); Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress());
if (balance.compareTo(totalsNeeded) >= 0) { if (balance.compareTo(totalsNeeded) >= 0) {
model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() { model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() {

View file

@ -44,7 +44,7 @@ public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
Transaction transaction = model.tradeWalletService.createTradingFeeTx( Transaction transaction = model.tradeWalletService.createTradingFeeTx(
model.walletService.getAddressEntryByOfferId(model.offer.getId()), model.walletService.getTradeAddressEntry(model.offer.getId()),
FeePolicy.getCreateOfferFee(), FeePolicy.getCreateOfferFee(),
selectedArbitrator.getBtcAddress()); selectedArbitrator.getBtcAddress());

View file

@ -180,7 +180,7 @@ public class ProcessModel implements Model, Serializable {
} }
public AddressEntry getAddressEntry() { public AddressEntry getAddressEntry() {
return walletService.getAddressEntryByOfferId(offer.getId()); return walletService.getTradeAddressEntry(offer.getId());
} }
public byte[] getTradeWalletPubKey() { public byte[] getTradeWalletPubKey() {

View file

@ -51,7 +51,7 @@ public class SetupDepositBalanceListener extends TradeTask {
runInterceptHook(); runInterceptHook();
WalletService walletService = processModel.getWalletService(); WalletService walletService = processModel.getWalletService();
Address address = walletService.getAddressEntryByOfferId(trade.getId()).getAddress(); Address address = walletService.getTradeAddressEntry(trade.getId()).getAddress();
balanceListener = new BalanceListener(address) { balanceListener = new BalanceListener(address) {
@Override @Override
public void onBalanceChanged(Coin balance, Transaction tx) { public void onBalanceChanged(Coin balance, Transaction tx) {

View file

@ -82,7 +82,7 @@ public class TextFieldWithCopyIcon extends AnchorPane {
// Getter/Setter // Getter/Setter
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private String getText() { public String getText() {
return text.get(); return text.get();
} }

View file

@ -684,7 +684,7 @@ public class MainViewModel implements ViewModel {
private void updateReservedBalance() { private void updateReservedBalance() {
Coin sum = Coin.valueOf(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) Coin sum = Coin.valueOf(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream())
.map(tradable -> walletService.getAddressEntryByOfferId(tradable.getId())) .map(tradable -> walletService.getTradeAddressEntry(tradable.getId()))
.map(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress())) .map(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()))
.mapToLong(Coin::getValue) .mapToLong(Coin::getValue)
.sum()); .sum());
@ -703,7 +703,7 @@ public class MainViewModel implements ViewModel {
if (trade.getContract() != null && if (trade.getContract() != null &&
trade.getTradeAmount() != null && trade.getTradeAmount() != null &&
trade.getContract().getSellerPayoutAddressString() trade.getContract().getSellerPayoutAddressString()
.equals(walletService.getAddressEntryByOfferId(trade.getId()).getAddressString())) { .equals(walletService.getTradeAddressEntry(trade.getId()).getAddressString())) {
balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount()); balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount());
} }
return balanceInDeposit; return balanceInDeposit;

View file

@ -16,16 +16,16 @@
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. ~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
--> -->
<?import javafx.scene.control.Tab?> <?import javafx.scene.control.*?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
<TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.funds.FundsView" <TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.funds.FundsView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
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/Locked funds" closable="false"/> <Tab fx:id="depositTab" text="Deposit funds" closable="false"/>
<Tab fx:id="withdrawalTab" text="Available for withdrawal" closable="false"/> <Tab fx:id="withdrawalTab" text="Available for withdrawal" closable="false"/>
<Tab fx:id="reservedTab" text="Reserved/Locked funds" closable="false"/>
<Tab fx:id="transactionsTab" text="Transactions" closable="false"/> <Tab fx:id="transactionsTab" text="Transactions" closable="false"/>
</TabPane> </TabPane>

View file

@ -17,17 +17,14 @@
package io.bitsquare.gui.main.funds; package io.bitsquare.gui.main.funds;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.Activatable; import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.*; import io.bitsquare.gui.common.view.*;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.funds.deposit.DepositView;
import io.bitsquare.gui.main.funds.reserved.ReservedView; import io.bitsquare.gui.main.funds.reserved.ReservedView;
import io.bitsquare.gui.main.funds.transactions.TransactionsView; import io.bitsquare.gui.main.funds.transactions.TransactionsView;
import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView; import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
@ -39,7 +36,7 @@ import javax.inject.Inject;
public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> { public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
@FXML @FXML
Tab reservedTab, withdrawalTab, transactionsTab; Tab depositTab, withdrawalTab, reservedTab, transactionsTab;
private Navigation.Listener navigationListener; private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener; private ChangeListener<Tab> tabChangeListener;
@ -47,13 +44,11 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
private final ViewLoader viewLoader; private final ViewLoader viewLoader;
private final Navigation navigation; private final Navigation navigation;
private final Preferences preferences;
@Inject @Inject
public FundsView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) { public FundsView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader; this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
this.preferences = preferences;
} }
@Override @Override
@ -64,10 +59,12 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
}; };
tabChangeListener = (ov, oldValue, newValue) -> { tabChangeListener = (ov, oldValue, newValue) -> {
if (newValue == reservedTab) if (newValue == depositTab)
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); navigation.navigateTo(MainView.class, FundsView.class, DepositView.class);
else if (newValue == withdrawalTab) else if (newValue == withdrawalTab)
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
else if (newValue == reservedTab)
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
else if (newValue == transactionsTab) else if (newValue == transactionsTab)
navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class);
}; };
@ -78,15 +75,17 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
navigation.addListener(navigationListener); navigation.addListener(navigationListener);
if (root.getSelectionModel().getSelectedItem() == reservedTab) if (root.getSelectionModel().getSelectedItem() == depositTab)
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); navigation.navigateTo(MainView.class, FundsView.class, DepositView.class);
else if (root.getSelectionModel().getSelectedItem() == withdrawalTab) else if (root.getSelectionModel().getSelectedItem() == withdrawalTab)
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
else if (root.getSelectionModel().getSelectedItem() == reservedTab)
navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class);
else if (root.getSelectionModel().getSelectedItem() == transactionsTab) else if (root.getSelectionModel().getSelectedItem() == transactionsTab)
navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class);
String key = "tradeWalletInfoAtFunds"; String key = "tradeWalletInfoAtFunds";
if (!BitsquareApp.DEV_MODE) /* if (!BitsquareApp.DEV_MODE)
new Popup().backgroundInfo("Bitsquare does not use a single application wallet, but dedicated wallets for every trade.\n\n" + new Popup().backgroundInfo("Bitsquare does not use a single application wallet, but dedicated wallets for every trade.\n\n" +
"Funding of the wallet will be done when needed, for instance when you create or take an offer.\n" + "Funding of the wallet will be done when needed, for instance when you create or take an offer.\n" +
"Withdrawing funds can be done after a trade is completed.\n\n" + "Withdrawing funds can be done after a trade is completed.\n\n" +
@ -96,7 +95,7 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
.onAction(() -> Utilities.openWebPage("https://bitsquare.io/faq")) .onAction(() -> Utilities.openWebPage("https://bitsquare.io/faq"))
.closeButtonText("I understand") .closeButtonText("I understand")
.dontShowAgainId(key, preferences) .dontShowAgainId(key, preferences)
.show(); .show();*/
} }
@Override @Override
@ -113,10 +112,12 @@ public class FundsView extends ActivatableViewAndModel<TabPane, Activatable> {
View view = viewLoader.load(viewClass); View view = viewLoader.load(viewClass);
if (view instanceof ReservedView) if (view instanceof DepositView)
currentTab = reservedTab; currentTab = depositTab;
else if (view instanceof WithdrawalView) else if (view instanceof WithdrawalView)
currentTab = withdrawalTab; currentTab = withdrawalTab;
else if (view instanceof ReservedView)
currentTab = reservedTab;
else if (view instanceof TransactionsView) else if (view instanceof TransactionsView)
currentTab = transactionsTab; currentTab = transactionsTab;

View file

@ -0,0 +1,149 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.funds.deposit;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.gui.components.confidence.ConfidenceProgressIndicator;
import io.bitsquare.gui.util.BSFormatter;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.Tooltip;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DepositListItem {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final StringProperty balance = new SimpleStringProperty();
private final WalletService walletService;
private BSFormatter formatter;
private final ConfidenceProgressIndicator progressIndicator;
private final Tooltip tooltip;
private String balanceString;
private String addressString;
private String status = "Unused";
private TxConfidenceListener txConfidenceListener;
// public DepositListItem(AddressEntry addressEntry, Transaction transaction, WalletService walletService, Optional<Tradable> tradableOptional, BSFormatter formatter) {
public DepositListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) {
this.walletService = walletService;
this.formatter = formatter;
addressString = addressEntry.getAddressString();
// confidence
progressIndicator = new ConfidenceProgressIndicator();
progressIndicator.setId("funds-confidence");
tooltip = new Tooltip("Not used yet");
progressIndicator.setProgress(0);
progressIndicator.setPrefHeight(30);
progressIndicator.setPrefWidth(30);
Tooltip.install(progressIndicator, tooltip);
final Address address = addressEntry.getAddress();
walletService.addBalanceListener(new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin));
updateConfidence(walletService.getConfidenceForTxId(tx.getHashAsString()));
if (balanceAsCoin.isPositive())
status = "Funded";
}
});
Coin balanceAsCoin = walletService.getBalanceForAddress(address);
balance.set(formatter.formatCoin(balanceAsCoin));
if (balanceAsCoin.isPositive())
status = "Funded";
TransactionConfidence transactionConfidence = walletService.getConfidenceForAddress(address);
if (transactionConfidence != null) {
updateConfidence(transactionConfidence);
txConfidenceListener = new TxConfidenceListener(transactionConfidence.getTransactionHash().toString()) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
};
walletService.addTxConfidenceListener(txConfidenceListener);
}
}
public void setStatus(String status) {
this.status = status;
}
public void cleanup() {
walletService.removeTxConfidenceListener(txConfidenceListener);
}
private void updateConfidence(TransactionConfidence confidence) {
if (confidence != null) {
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;
}
progressIndicator.setPrefSize(24, 24);
}
}
public ConfidenceProgressIndicator getProgressIndicator() {
return progressIndicator;
}
public String getAddressString() {
return addressString;
}
public String getStatus() {
return status;
}
public final StringProperty balanceProperty() {
return this.balance;
}
public String getBalance() {
return balance.get();
}
}

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bitsquare.
~
~ Bitsquare is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.funds.deposit.DepositView"
spacing="10" xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<TableView fx:id="table" VBox.vgrow="ALWAYS">
<columns>
<TableColumn text="Select" fx:id="selectColumn" minWidth="110" maxWidth="110" sortable="false"/>
<TableColumn text="Address" fx:id="addressColumn" minWidth="260"/>
<TableColumn text="Balance (BTC)" fx:id="balanceColumn" minWidth="150" maxWidth="150">
<cellValueFactory>
<PropertyValueFactory property="balance"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Confirmations" fx:id="confidenceColumn" minWidth="150" maxWidth="150"/>
<TableColumn text="Status" fx:id="statusColumn" minWidth="150" maxWidth="150"/>
</columns>
</TableView>
<GridPane fx:id="gridPane" hgap="5.0" vgap="5.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER"/>
<ColumnConstraints hgrow="ALWAYS"/>
<ColumnConstraints hgrow="NEVER"/>
</columnConstraints>
</GridPane>
</VBox>

View file

@ -0,0 +1,395 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.funds.deposit;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.AddressTextField;
import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.QRCodeWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.gui.util.validation.BtcAddressValidator;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager;
import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.uri.BitcoinURI;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import java.io.ByteArrayInputStream;
import static io.bitsquare.gui.util.FormBuilder.*;
@FxmlView
public class DepositView extends ActivatableView<VBox, Void> {
@FXML
GridPane gridPane;
@FXML
TableView<DepositListItem> table;
@FXML
TableColumn<DepositListItem, DepositListItem> selectColumn, addressColumn, balanceColumn, confidenceColumn, statusColumn;
private ImageView qrCodeImageView;
private int gridRow = 0;
private AddressTextField addressTextField;
Button generateNewAddressButton;
private final WalletService walletService;
private final TradeManager tradeManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
private final OpenOfferManager openOfferManager;
private final BSFormatter formatter;
private final Preferences preferences;
private final BtcAddressValidator btcAddressValidator;
private final WalletPasswordWindow walletPasswordWindow;
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
private final ObservableList<DepositListItem> depositAddresses = FXCollections.observableArrayList();
private BalanceListener balanceListener;
private TitledGroupBg titledGroupBg;
private Label addressLabel;
private Label qrCodeLabel;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private DepositView(WalletService walletService, TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager,
BSFormatter formatter, Preferences preferences,
BtcAddressValidator btcAddressValidator, WalletPasswordWindow walletPasswordWindow,
OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
this.walletService = walletService;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
this.openOfferManager = openOfferManager;
this.formatter = formatter;
this.preferences = preferences;
this.btcAddressValidator = btcAddressValidator;
this.walletPasswordWindow = walletPasswordWindow;
this.offerDetailsWindow = offerDetailsWindow;
this.tradeDetailsWindow = tradeDetailsWindow;
}
@Override
public void initialize() {
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No deposit addresses are generated yet"));
setSelectColumnCellFactory();
setAddressColumnCellFactory();
setStatusColumnCellFactory();
setConfidenceColumnCellFactory();
titledGroupBg = addTitledGroupBg(gridPane, gridRow, 2, "Fund your wallet");
qrCodeLabel = addLabel(gridPane, gridRow, "QR-Code:", 0);
//GridPane.setMargin(qrCodeLabel, new Insets(Layout.FIRST_ROW_DISTANCE - 9, 0, 0, 5));
qrCodeImageView = new ImageView();
qrCodeImageView.setStyle("-fx-cursor: hand;");
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show());
GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setColumnIndex(qrCodeImageView, 1);
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0));
gridPane.getChildren().add(qrCodeImageView);
Tuple2<Label, AddressTextField> addressTuple = addLabelAddressTextField(gridPane, ++gridRow, "Address:");
addressLabel = addressTuple.first;
GridPane.setValignment(addressLabel, VPos.TOP);
GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0));
addressTextField = addressTuple.second;
titledGroupBg.setVisible(false);
titledGroupBg.setManaged(false);
qrCodeLabel.setVisible(false);
qrCodeLabel.setManaged(false);
qrCodeImageView.setVisible(false);
qrCodeImageView.setManaged(false);
addressLabel.setVisible(false);
addressLabel.setManaged(false);
addressTextField.setVisible(false);
addressTextField.setManaged(false);
generateNewAddressButton = addButton(gridPane, ++gridRow, "Generate new address", -20);
generateNewAddressButton.setOnAction(event -> {
boolean hasUnUsedAddress = walletService.getSavingsAddressEntryList().stream()
.filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
.findAny().isPresent();
if (hasUnUsedAddress) {
new Popup().warning("You have already addresses generated which are still not used.\n" +
"Please select in the address table an unused address.").show();
} else {
AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry();
fillForm(newSavingsAddressEntry.getAddressString());
updateList();
}
});
balanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateList();
}
};
}
@NotNull
private String getBitcoinURI() {
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
null,
null,
null);
}
@Override
protected void activate() {
updateList();
walletService.addBalanceListener(balanceListener);
}
@Override
protected void deactivate() {
depositAddresses.forEach(DepositListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI handlers
///////////////////////////////////////////////////////////////////////////////////////////
private void fillForm(String address) {
titledGroupBg.setVisible(true);
titledGroupBg.setManaged(true);
qrCodeLabel.setVisible(true);
qrCodeLabel.setManaged(true);
qrCodeImageView.setVisible(true);
qrCodeImageView.setManaged(true);
addressLabel.setVisible(true);
addressLabel.setManaged(true);
addressTextField.setVisible(true);
addressTextField.setManaged(true);
GridPane.setMargin(generateNewAddressButton, new Insets(15, 0, 0, 0));
addressTextField.setAddress(address);
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage);
}
private void openBlockExplorer(DepositListItem 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
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
depositAddresses.clear();
walletService.getSavingsAddressEntryList().stream()
.forEach(e -> depositAddresses.add(new DepositListItem(e, walletService, formatter)));
table.setItems(depositAddresses);
}
///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories
///////////////////////////////////////////////////////////////////////////////////////////
private void setStatusColumnCellFactory() {
statusColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
statusColumn.setCellFactory(new Callback<TableColumn<DepositListItem, DepositListItem>,
TableCell<DepositListItem, DepositListItem>>() {
@Override
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
DepositListItem> column) {
return new TableCell<DepositListItem, DepositListItem>() {
@Override
public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(new Label(item.getStatus()));
} else {
setGraphic(null);
}
}
};
}
});
}
private void setSelectColumnCellFactory() {
selectColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
selectColumn.setCellFactory(
new Callback<TableColumn<DepositListItem, DepositListItem>, TableCell<DepositListItem,
DepositListItem>>() {
@Override
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
DepositListItem> column) {
return new TableCell<DepositListItem, DepositListItem>() {
Button button;
@Override
public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = new Button("Select");
button.setOnAction(e -> fillForm(item.getAddressString()));
setGraphic(button);
}
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
}
private void setAddressColumnCellFactory() {
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
addressColumn.setCellFactory(
new Callback<TableColumn<DepositListItem, DepositListItem>, TableCell<DepositListItem,
DepositListItem>>() {
@Override
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
DepositListItem> column) {
return new TableCell<DepositListItem, DepositListItem>() {
private HyperlinkWithIcon field;
@Override
public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String addressString = item.getAddressString();
field = new HyperlinkWithIcon(addressString, AwesomeIcon.EXTERNAL_LINK);
field.setOnAction(event -> openBlockExplorer(item));
field.setTooltip(new Tooltip("Open external blockchain explorer for " +
"address: " + addressString));
setGraphic(field);
} else {
setGraphic(null);
if (field != null)
field.setOnAction(null);
}
}
};
}
});
}
private void setConfidenceColumnCellFactory() {
confidenceColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
confidenceColumn.setCellFactory(
new Callback<TableColumn<DepositListItem, DepositListItem>, TableCell<DepositListItem,
DepositListItem>>() {
@Override
public TableCell<DepositListItem, DepositListItem> call(TableColumn<DepositListItem,
DepositListItem> column) {
return new TableCell<DepositListItem, DepositListItem>() {
@Override
public void updateItem(final DepositListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getProgressIndicator());
} else {
setGraphic(null);
}
}
};
}
});
}
}

View file

@ -123,7 +123,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
private void updateList() { private void updateList() {
reservedAddresses.forEach(ReservedListItem::cleanup); reservedAddresses.forEach(ReservedListItem::cleanup);
reservedAddresses.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.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter))
.collect(Collectors.toList())); .collect(Collectors.toList()));
reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate())); reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()));

View file

@ -127,7 +127,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
this.formatter = formatter; this.formatter = formatter;
offerId = UUID.randomUUID().toString(); offerId = UUID.randomUUID().toString();
addressEntry = walletService.getAddressEntryByOfferId(offerId); addressEntry = walletService.getTradeAddressEntry(offerId);
offerFeeAsCoin = FeePolicy.getCreateOfferFee(); offerFeeAsCoin = FeePolicy.getCreateOfferFee();
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
securityDepositAsCoin = FeePolicy.getSecurityDeposit(); securityDepositAsCoin = FeePolicy.getSecurityDeposit();

View file

@ -734,6 +734,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
qrCodeImageView = new ImageView(); qrCodeImageView = new ImageView();
qrCodeImageView.setVisible(false); qrCodeImageView.setVisible(false);
qrCodeImageView.setStyle("-fx-cursor: hand;"); qrCodeImageView.setStyle("-fx-cursor: hand;");
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show()); qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show());
GridPane.setRowIndex(qrCodeImageView, gridRow); GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setColumnIndex(qrCodeImageView, 2); GridPane.setColumnIndex(qrCodeImageView, 2);

View file

@ -155,7 +155,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
void initWithData(Offer offer) { void initWithData(Offer offer) {
this.offer = offer; this.offer = offer;
addressEntry = walletService.getAddressEntryByOfferId(offer.getId()); addressEntry = walletService.getTradeAddressEntry(offer.getId());
checkNotNull(addressEntry, "addressEntry must not be null"); checkNotNull(addressEntry, "addressEntry must not be null");
ObservableList<PaymentAccount> possiblePaymentAccounts = getPossiblePaymentAccounts(); ObservableList<PaymentAccount> possiblePaymentAccounts = getPossiblePaymentAccounts();

View file

@ -668,6 +668,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
qrCodeImageView = new ImageView(); qrCodeImageView = new ImageView();
qrCodeImageView.setVisible(false); qrCodeImageView.setVisible(false);
qrCodeImageView.setStyle("-fx-cursor: hand;"); qrCodeImageView.setStyle("-fx-cursor: hand;");
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show()); qrCodeImageView.setOnMouseClicked(e -> new QRCodeWindow(getBitcoinURI()).show());
GridPane.setRowIndex(qrCodeImageView, gridRow); GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setColumnIndex(qrCodeImageView, 2); GridPane.setColumnIndex(qrCodeImageView, 2);

View file

@ -159,7 +159,7 @@ public class BuyerStep5View extends TradeStepView {
private void reviewWithdrawal() { private void reviewWithdrawal() {
Coin senderAmount = trade.getPayoutAmount(); Coin senderAmount = trade.getPayoutAmount();
WalletService walletService = model.dataModel.walletService; WalletService walletService = model.dataModel.walletService;
AddressEntry fromAddressesEntry = walletService.getAddressEntryByOfferId(trade.getId()); AddressEntry fromAddressesEntry = walletService.getTradeAddressEntry(trade.getId());
String fromAddresses = fromAddressesEntry.getAddressString(); String fromAddresses = fromAddressesEntry.getAddressString();
String toAddresses = withdrawAddressTextField.getText(); String toAddresses = withdrawAddressTextField.getText();