diff --git a/common/src/main/java/io/bitsquare/app/Log.java b/common/src/main/java/io/bitsquare/app/Log.java index c1153b4ac7..fa3b12abba 100644 --- a/common/src/main/java/io/bitsquare/app/Log.java +++ b/common/src/main/java/io/bitsquare/app/Log.java @@ -62,6 +62,22 @@ public class Log { logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.INFO); 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() { diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java b/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java index 74cf206aec..cb62887a8a 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java @@ -499,7 +499,7 @@ public class DisputeManager { contract.getBuyerPayoutAddressString(), contract.getSellerPayoutAddressString(), disputeResult.getArbitratorAddressAsString(), - walletService.getAddressEntryByOfferId(dispute.getTradeId()), + walletService.getTradeAddressEntry(dispute.getTradeId()), contract.getBuyerBtcPubKey(), contract.getSellerBtcPubKey(), disputeResult.getArbitratorPubKey() diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntry.java b/core/src/main/java/io/bitsquare/btc/AddressEntry.java index 1a6bc01ce6..4dbe1bdc02 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntry.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntry.java @@ -44,8 +44,10 @@ public final class AddressEntry implements Persistable { private static final Logger log = LoggerFactory.getLogger(AddressEntry.class); public enum Context { + SAVINGS, TRADE, - ARBITRATOR + ARBITRATOR, + DAO } // keyPair can be null in case the object is created from deserialization as it is transient. diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java index 24953b25d0..7fb7f70284 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java @@ -66,9 +66,16 @@ public final class AddressEntryList extends ArrayList implements P } } - public AddressEntry getNewAddressEntry(AddressEntry.Context context, String offerId) { + public AddressEntry getNewTradeAddressEntry(String 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); storage.queueUpForSave(); return addressEntry; diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 235d24caf5..05bdd6408b 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -312,18 +312,28 @@ public class WalletService { return ImmutableList.copyOf(addressEntryList); } + public List getSavingsAddressEntryList() { + return getAddressEntryList().stream() + .filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS)) + .collect(Collectors.toList()); + } + public AddressEntry getArbitratorAddressEntry() { return arbitratorAddressEntry; } - public AddressEntry getAddressEntryByOfferId(String offerId) { + public AddressEntry getTradeAddressEntry(String offerId) { Optional addressEntry = getAddressEntryList().stream() .filter(e -> offerId.equals(e.getOfferId())) .findAny(); if (addressEntry.isPresent()) return addressEntry.get(); else - return addressEntryList.getNewAddressEntry(AddressEntry.Context.TRADE, offerId); + return addressEntryList.getNewTradeAddressEntry(offerId); + } + + public AddressEntry getNewSavingsAddressEntry() { + return addressEntryList.getNewSavingsAddressEntry(); } private Optional getAddressEntryByAddress(String address) { diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index 4cb5c169d1..e23fef20f2 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -302,7 +302,7 @@ public class TradeManager { /////////////////////////////////////////////////////////////////////////////////////////// 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(); FutureCallback callback = new FutureCallback() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java index 074ccab662..f5204d8c84 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java @@ -45,7 +45,7 @@ public class BroadcastCreateOfferFeeTx extends Task { try { runInterceptHook(); 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()); if (balance.compareTo(totalsNeeded) >= 0) { model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java index ecc17a1c76..213a9dd186 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java @@ -44,7 +44,7 @@ public class CreateOfferFeeTx extends Task { log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); Transaction transaction = model.tradeWalletService.createTradingFeeTx( - model.walletService.getAddressEntryByOfferId(model.offer.getId()), + model.walletService.getTradeAddressEntry(model.offer.getId()), FeePolicy.getCreateOfferFee(), selectedArbitrator.getBtcAddress()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java index a33ad72e89..dc910b9612 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java @@ -180,7 +180,7 @@ public class ProcessModel implements Model, Serializable { } public AddressEntry getAddressEntry() { - return walletService.getAddressEntryByOfferId(offer.getId()); + return walletService.getTradeAddressEntry(offer.getId()); } public byte[] getTradeWalletPubKey() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java index ad93ad182e..6196688250 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java @@ -51,7 +51,7 @@ public class SetupDepositBalanceListener extends TradeTask { runInterceptHook(); WalletService walletService = processModel.getWalletService(); - Address address = walletService.getAddressEntryByOfferId(trade.getId()).getAddress(); + Address address = walletService.getTradeAddressEntry(trade.getId()).getAddress(); balanceListener = new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { diff --git a/gui/src/main/java/io/bitsquare/gui/components/TextFieldWithCopyIcon.java b/gui/src/main/java/io/bitsquare/gui/components/TextFieldWithCopyIcon.java index d828ff3809..fc5ef9028e 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/TextFieldWithCopyIcon.java +++ b/gui/src/main/java/io/bitsquare/gui/components/TextFieldWithCopyIcon.java @@ -82,7 +82,7 @@ public class TextFieldWithCopyIcon extends AnchorPane { // Getter/Setter /////////////////////////////////////////////////////////////////////////////////////////// - private String getText() { + public String getText() { return text.get(); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index e3bac2e616..1853417547 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -684,7 +684,7 @@ public class MainViewModel implements ViewModel { private void updateReservedBalance() { 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())) .mapToLong(Coin::getValue) .sum()); @@ -703,7 +703,7 @@ public class MainViewModel implements ViewModel { if (trade.getContract() != null && trade.getTradeAmount() != null && trade.getContract().getSellerPayoutAddressString() - .equals(walletService.getAddressEntryByOfferId(trade.getId()).getAddressString())) { + .equals(walletService.getTradeAddressEntry(trade.getId()).getAddressString())) { balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount()); } return balanceInDeposit; diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml index 2936e46cd3..a66fd45d48 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml @@ -16,16 +16,16 @@ ~ along with Bitsquare. If not, see . --> - - + - + + \ No newline at end of file diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java index 99dc3a50cb..cd231ad389 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java @@ -17,17 +17,14 @@ 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.common.model.Activatable; import io.bitsquare.gui.common.view.*; 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.transactions.TransactionsView; 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.fxml.FXML; import javafx.scene.control.Tab; @@ -39,7 +36,7 @@ import javax.inject.Inject; public class FundsView extends ActivatableViewAndModel { @FXML - Tab reservedTab, withdrawalTab, transactionsTab; + Tab depositTab, withdrawalTab, reservedTab, transactionsTab; private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; @@ -47,13 +44,11 @@ public class FundsView extends ActivatableViewAndModel { private final ViewLoader viewLoader; private final Navigation navigation; - private final Preferences preferences; @Inject - public FundsView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) { + public FundsView(CachingViewLoader viewLoader, Navigation navigation) { this.viewLoader = viewLoader; this.navigation = navigation; - this.preferences = preferences; } @Override @@ -64,10 +59,12 @@ public class FundsView extends ActivatableViewAndModel { }; tabChangeListener = (ov, oldValue, newValue) -> { - if (newValue == reservedTab) - navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); + if (newValue == depositTab) + navigation.navigateTo(MainView.class, FundsView.class, DepositView.class); else if (newValue == withdrawalTab) navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); + else if (newValue == reservedTab) + navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); else if (newValue == transactionsTab) navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); }; @@ -78,15 +75,17 @@ public class FundsView extends ActivatableViewAndModel { root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); navigation.addListener(navigationListener); - if (root.getSelectionModel().getSelectedItem() == reservedTab) - navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); + if (root.getSelectionModel().getSelectedItem() == depositTab) + navigation.navigateTo(MainView.class, FundsView.class, DepositView.class); else if (root.getSelectionModel().getSelectedItem() == withdrawalTab) 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) navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); 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" + "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" + @@ -96,7 +95,7 @@ public class FundsView extends ActivatableViewAndModel { .onAction(() -> Utilities.openWebPage("https://bitsquare.io/faq")) .closeButtonText("I understand") .dontShowAgainId(key, preferences) - .show(); + .show();*/ } @Override @@ -113,10 +112,12 @@ public class FundsView extends ActivatableViewAndModel { View view = viewLoader.load(viewClass); - if (view instanceof ReservedView) - currentTab = reservedTab; + if (view instanceof DepositView) + currentTab = depositTab; else if (view instanceof WithdrawalView) currentTab = withdrawalTab; + else if (view instanceof ReservedView) + currentTab = reservedTab; else if (view instanceof TransactionsView) currentTab = transactionsTab; diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java new file mode 100644 index 0000000000..6019f81a4b --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java @@ -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 . + */ + +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 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(); + } + +} diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml new file mode 100644 index 0000000000..8b2bc84b39 --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java new file mode 100644 index 0000000000..d314a94bd8 --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java @@ -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 . + */ + +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 { + + @FXML + GridPane gridPane; + + @FXML + TableView table; + @FXML + TableColumn 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 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 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, + TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @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, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + 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, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + 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, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final DepositListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + setGraphic(item.getProgressIndicator()); + } else { + setGraphic(null); + } + } + }; + } + }); + } +} + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java index 99970161ed..e1c277e9b5 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java @@ -123,7 +123,7 @@ public class ReservedView extends ActivatableView { private void updateList() { reservedAddresses.forEach(ReservedListItem::cleanup); 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())); reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate())); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index 87c988fa83..b966369f25 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -127,7 +127,7 @@ class CreateOfferDataModel extends ActivatableDataModel { this.formatter = formatter; offerId = UUID.randomUUID().toString(); - addressEntry = walletService.getAddressEntryByOfferId(offerId); + addressEntry = walletService.getTradeAddressEntry(offerId); offerFeeAsCoin = FeePolicy.getCreateOfferFee(); networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); securityDepositAsCoin = FeePolicy.getSecurityDeposit(); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index 2219e8fc29..55f3061cf7 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -734,6 +734,7 @@ public class CreateOfferView extends ActivatableViewAndModel new QRCodeWindow(getBitcoinURI()).show()); GridPane.setRowIndex(qrCodeImageView, gridRow); GridPane.setColumnIndex(qrCodeImageView, 2); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java index c271dfb0cb..f20ae6430b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java @@ -155,7 +155,7 @@ class TakeOfferDataModel extends ActivatableDataModel { void initWithData(Offer offer) { this.offer = offer; - addressEntry = walletService.getAddressEntryByOfferId(offer.getId()); + addressEntry = walletService.getTradeAddressEntry(offer.getId()); checkNotNull(addressEntry, "addressEntry must not be null"); ObservableList possiblePaymentAccounts = getPossiblePaymentAccounts(); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java index bf0c109fed..8cb2b956fb 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferView.java @@ -668,6 +668,7 @@ public class TakeOfferView extends ActivatableViewAndModel new QRCodeWindow(getBitcoinURI()).show()); GridPane.setRowIndex(qrCodeImageView, gridRow); GridPane.setColumnIndex(qrCodeImageView, 2); diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java index ee5dc9dec7..4150cd6668 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java @@ -159,7 +159,7 @@ public class BuyerStep5View extends TradeStepView { private void reviewWithdrawal() { Coin senderAmount = trade.getPayoutAmount(); WalletService walletService = model.dataModel.walletService; - AddressEntry fromAddressesEntry = walletService.getAddressEntryByOfferId(trade.getId()); + AddressEntry fromAddressesEntry = walletService.getTradeAddressEntry(trade.getId()); String fromAddresses = fromAddressesEntry.getAddressString(); String toAddresses = withdrawAddressTextField.getText();