/*
* 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.reserved;
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.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.offer.OpenOffer;
import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import java.util.Optional;
import java.util.stream.Collectors;
@FxmlView
public class ReservedView extends ActivatableView<VBox, Void> {
@FXML
TableView<ReservedListItem> tableView;
@FXML
TableColumn<ReservedListItem, ReservedListItem> dateColumn, detailsColumn, addressColumn, balanceColumn, confidenceColumn;
private final WalletService walletService;
private final TradeManager tradeManager;
private final OpenOfferManager openOfferManager;
private final Preferences preferences;
private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
private final ObservableList<ReservedListItem> observableList = FXCollections.observableArrayList();
private final SortedList<ReservedListItem> sortedList = new SortedList<>(observableList);
private BalanceListener balanceListener;
private ListChangeListener<OpenOffer> openOfferListChangeListener;
private ListChangeListener<Trade> tradeListChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ReservedView(WalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences,
BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
this.walletService = walletService;
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
this.preferences = preferences;
this.formatter = formatter;
this.offerDetailsWindow = offerDetailsWindow;
this.tradeDetailsWindow = tradeDetailsWindow;
}
@Override
public void initialize() {
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No funds are reserved in open offers"));
setDateColumnCellFactory();
setDetailsColumnCellFactory();
setAddressColumnCellFactory();
setBalanceColumnCellFactory();
addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString()));
detailsColumn.setComparator((o1, o2) -> o1.getOpenOffer().getId().compareTo(o2.getOpenOffer().getId()));
balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance()));
dateColumn.setComparator((o1, o2) -> {
if (getTradable(o1).isPresent() && getTradable(o2).isPresent())
return getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate());
else
return 0;
});
tableView.getSortOrder().add(dateColumn);
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
balanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateList();
}
};
openOfferListChangeListener = c -> updateList();
tradeListChangeListener = c -> updateList();
}
@Override
protected void activate() {
openOfferManager.getOpenOffers().addListener(openOfferListChangeListener);
tradeManager.getTrades().addListener(tradeListChangeListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
walletService.addBalanceListener(balanceListener);
}
@Override
protected void deactivate() {
openOfferManager.getOpenOffers().removeListener(openOfferListChangeListener);
tradeManager.getTrades().removeListener(tradeListChangeListener);
sortedList.comparatorProperty().unbind();
observableList.forEach(ReservedListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
observableList.forEach(ReservedListItem::cleanup);
observableList.setAll(openOfferManager.getOpenOffers().stream()
.map(openOffer -> new ReservedListItem(openOffer,
walletService.getOrCreateAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE),
walletService,
formatter))
.collect(Collectors.toList()));
}
private void openBlockExplorer(ReservedListItem item) {
try {
GUIUtil.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 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) {
tradeDetailsWindow.show((Trade) tradable);
} else if (tradable instanceof OpenOffer) {
offerDetailsWindow.show(tradable.getOffer());
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories
///////////////////////////////////////////////////////////////////////////////////////////
private void setDateColumnCellFactory() {
dateColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
dateColumn.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) {
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
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
Optional<Tradable> tradableOptional = getTradable(item);
if (tradableOptional.isPresent()) {
field = new HyperlinkWithIcon("Reserved in local wallet for offer with ID: " + item.getAddressEntry().getShortOfferId(),
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("Arbitrator's fee"));
} else {
setGraphic(new Label("No details available"));
}
} else {
setGraphic(null);
if (field != null)
field.setOnAction(null);
}
}
};
}
});
}
private void setAddressColumnCellFactory() {
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
addressColumn.setCellFactory(
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 hyperlinkWithIcon;
@Override
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String address = item.getAddressString();
hyperlinkWithIcon = new HyperlinkWithIcon(address, AwesomeIcon.EXTERNAL_LINK);
hyperlinkWithIcon.setOnAction(event -> openBlockExplorer(item));
hyperlinkWithIcon.setTooltip(new Tooltip("Open external blockchain explorer for " +
"address: " + address));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
}
private void setBalanceColumnCellFactory() {
balanceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
balanceColumn.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);
setGraphic((item != null && !empty) ? item.getBalanceLabel() : null);
}
};
}
});
}
}