Merge pull request #4933 from chimp1984/add-num-items-to-tables

Improve funds and portfolio screens
This commit is contained in:
Christoph Atteneder 2020-12-14 19:48:55 +01:00 committed by GitHub
commit 7d12b94c6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 772 additions and 173 deletions

View File

@ -377,11 +377,15 @@ public class OfferUtil {
tradeStatisticsManager,
30);
Price bsqPrice = tuple.second;
String inputValue = bsqFormatter.formatCoin(makerFee);
Volume makerFeeAsVolume = Volume.parse(inputValue, "BSQ");
Coin requiredBtc = bsqPrice.getAmountByVolume(makerFeeAsVolume);
Volume volumeByAmount = userCurrencyPrice.getVolumeByAmount(requiredBtc);
return Optional.of(volumeByAmount);
if (bsqPrice.isPositive()) {
String inputValue = bsqFormatter.formatCoin(makerFee);
Volume makerFeeAsVolume = Volume.parse(inputValue, "BSQ");
Coin requiredBtc = bsqPrice.getAmountByVolume(makerFeeAsVolume);
Volume volumeByAmount = userCurrencyPrice.getVolumeByAmount(requiredBtc);
return Optional.of(volumeByAmount);
} else {
return Optional.empty();
}
}
} else {
return Optional.empty();

View File

@ -335,6 +335,10 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
return id.compareTo(other.id);
}
public String getDisplayString() {
return Res.get(id);
}
public boolean isAsset() {
return this.equals(BLOCK_CHAINS_INSTANT) || this.equals(BLOCK_CHAINS);
}

View File

@ -40,8 +40,6 @@ public class TradePresentation {
long numPendingTrades = (long) newValue;
if (numPendingTrades > 0)
this.numPendingTrades.set(String.valueOf(numPendingTrades));
if (numPendingTrades > 9)
this.numPendingTrades.set("");
showPendingTradesNotification.set(numPendingTrades > 0);
});

View File

@ -219,6 +219,9 @@ shared.refundAgentForSupportStaff=Refund agent
shared.delayedPayoutTxId=Delayed payout transaction ID
shared.delayedPayoutTxReceiverAddress=Delayed payout transaction sent to
shared.unconfirmedTransactionsLimitReached=You have too many unconfirmed transactions at the moment. Please try again later.
shared.numItemsLabel=Number of entries: {0}
shared.filter=Filter
shared.enabled=Enabled
####################################################################
@ -1053,7 +1056,6 @@ funds.tx.dustAttackTx.popup=This transaction is sending a very small BTC amount
To protect your privacy the Bisq wallet ignores such dust outputs for spending purposes and in the balance display. \
You can set the threshold amount when an output is considered dust in the settings.
####################################################################
# Support
####################################################################

View File

@ -353,10 +353,16 @@ public class PeerInfoIcon extends Group {
if (!tag.isEmpty())
tagLabel.setText(tag.substring(0, 1));
if (numTrades < 10)
if (numTrades > 0) {
numTradesLabel.setText(String.valueOf(numTrades));
else
numTradesLabel.setText("");
double scaleFactor = getScaleFactor();
if (numTrades > 9) {
numTradesLabel.relocate(scaleFactor * 2, scaleFactor * 1);
} else {
numTradesLabel.relocate(scaleFactor * 5, scaleFactor * 1);
}
}
numTradesPane.setVisible(numTrades > 0);

View File

@ -18,12 +18,13 @@
package bisq.desktop.main.funds.locked;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.util.DisplayUtils;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletService;
import bisq.core.trade.Tradable;
import bisq.core.locale.Res;
import bisq.core.trade.Trade;
import bisq.core.util.coin.CoinFormatter;
@ -33,21 +34,34 @@ import org.bitcoinj.core.Transaction;
import javafx.scene.control.Label;
import lombok.Getter;
import javax.annotation.Nullable;
class LockedListItem {
private final BalanceListener balanceListener;
private final Label balanceLabel;
private final Trade trade;
private final AddressEntry addressEntry;
private final BtcWalletService btcWalletService;
private final CoinFormatter formatter;
@Getter
private final Label balanceLabel;
@Getter
private final Trade trade;
@Getter
private final AddressEntry addressEntry;
@Getter
private final String addressString;
@Nullable
private final Address address;
@Getter
private Coin balance;
@Getter
private String balanceString;
public LockedListItem(Trade trade, AddressEntry addressEntry, BtcWalletService btcWalletService, CoinFormatter formatter) {
public LockedListItem(Trade trade,
AddressEntry addressEntry,
BtcWalletService btcWalletService,
CoinFormatter formatter) {
this.trade = trade;
this.addressEntry = addressEntry;
this.btcWalletService = btcWalletService;
@ -55,15 +69,13 @@ class LockedListItem {
if (trade.getDepositTx() != null && !trade.getDepositTx().getOutputs().isEmpty()) {
address = WalletService.getAddressFromOutput(trade.getDepositTx().getOutput(0));
addressString = address.toString();
addressString = address != null ? address.toString() : "";
} else {
address = null;
addressString = "";
}
// balance
balanceLabel = new AutoTooltipLabel();
balanceListener = new BalanceListener(getAddress()) {
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
@ -73,38 +85,36 @@ class LockedListItem {
updateBalance();
}
LockedListItem() {
this.trade = null;
this.addressEntry = null;
this.btcWalletService = null;
this.formatter = null;
addressString = null;
address = null;
balanceLabel = null;
balanceListener = null;
}
public void cleanup() {
btcWalletService.removeBalanceListener(balanceListener);
}
private void updateBalance() {
balance = addressEntry.getCoinLockedInMultiSigAsCoin();
balanceLabel.setText(formatter.formatCoin(this.balance));
balanceString = formatter.formatCoin(this.balance);
balanceLabel.setText(balanceString);
}
@Nullable
private Address getAddress() {
return address;
public String getDetails() {
return trade != null ?
Res.get("funds.locked.locked", trade.getShortId()) :
Res.get("shared.noDetailsAvailable");
}
public AddressEntry getAddressEntry() {
return addressEntry;
public String getDateString() {
return trade != null ?
DisplayUtils.formatDateTime(trade.getDate()) :
Res.get("shared.noDateAvailable");
}
public Label getBalanceLabel() {
return balanceLabel;
}
public Coin getBalance() {
return balance;
}
public String getAddressString() {
return addressString;
}
public Tradable getTrade() {
return trade;
}
}

View File

@ -17,8 +17,12 @@
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import bisq.desktop.components.AutoTooltipButton?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<VBox fx:id="root" fx:controller="bisq.desktop.main.funds.locked.LockedView"
@ -35,5 +39,9 @@
<TableColumn fx:id="balanceColumn" minWidth="110"/>
</columns>
</TableView>
<HBox spacing="10">
<Label fx:id="numItems"/>
<Region fx:id="spacer"/>
<AutoTooltipButton fx:id="exportButton"/>
</HBox>
</VBox>

View File

@ -19,12 +19,12 @@ package bisq.desktop.main.funds.locked;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.BalanceListener;
@ -43,6 +43,8 @@ import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import javax.inject.Inject;
import javax.inject.Named;
@ -50,12 +52,20 @@ import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
@ -77,6 +87,12 @@ public class LockedView extends ActivatableView<VBox, Void> {
TableView<LockedListItem> tableView;
@FXML
TableColumn<LockedListItem, LockedListItem> dateColumn, detailsColumn, addressColumn, balanceColumn;
@FXML
Label numItems;
@FXML
Region spacer;
@FXML
AutoTooltipButton exportButton;
private final BtcWalletService btcWalletService;
private final TradeManager tradeManager;
@ -97,8 +113,13 @@ public class LockedView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private LockedView(BtcWalletService btcWalletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
private LockedView(BtcWalletService btcWalletService,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
Preferences preferences,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
OfferDetailsWindow offerDetailsWindow,
TradeDetailsWindow tradeDetailsWindow) {
this.btcWalletService = btcWalletService;
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
@ -138,6 +159,10 @@ public class LockedView extends ActivatableView<VBox, Void> {
};
openOfferListChangeListener = c -> updateList();
tradeListChangeListener = c -> updateList();
HBox.setHgrow(spacer, Priority.ALWAYS);
numItems.setPadding(new Insets(-5, 0, 0, 10));
exportButton.updateText(Res.get("shared.exportCSV"));
}
@Override
@ -149,6 +174,33 @@ public class LockedView extends ActivatableView<VBox, Void> {
updateList();
btcWalletService.addBalanceListener(balanceListener);
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
exportButton.setOnAction(event -> {
ObservableList<TableColumn<LockedListItem, ?>> tableColumns = tableView.getColumns();
int reportColumns = tableColumns.size();
CSVEntryConverter<LockedListItem> headerConverter = transactionsListItem -> {
String[] columns = new String[reportColumns];
for (int i = 0; i < columns.length; i++)
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
return columns;
};
CSVEntryConverter<LockedListItem> contentConverter = item -> {
String[] columns = new String[reportColumns];
columns[0] = item.getDateString();
columns[1] = item.getDetails();
columns[2] = item.getAddressString();
columns[3] = item.getBalanceString();
return columns;
};
GUIUtil.exportCSV("lockedInTradesFunds.csv",
headerConverter,
contentConverter,
new LockedListItem(),
sortedList,
(Stage) root.getScene().getWindow());
});
}
@Override
@ -158,6 +210,7 @@ public class LockedView extends ActivatableView<VBox, Void> {
sortedList.comparatorProperty().unbind();
observableList.forEach(LockedListItem::cleanup);
btcWalletService.removeBalanceListener(balanceListener);
exportButton.setOnAction(null);
}
@ -169,7 +222,8 @@ public class LockedView extends ActivatableView<VBox, Void> {
observableList.forEach(LockedListItem::cleanup);
observableList.setAll(tradeManager.getTradesStreamWithFundsLockedIn()
.map(trade -> {
final Optional<AddressEntry> addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG);
Optional<AddressEntry> addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(),
AddressEntry.Context.MULTI_SIG);
return addressEntryOptional.map(addressEntry -> new LockedListItem(trade,
addressEntry,
btcWalletService,
@ -227,9 +281,9 @@ public class LockedView extends ActivatableView<VBox, Void> {
super.updateItem(item, empty);
if (item != null && !empty) {
if (getTradable(item).isPresent())
setGraphic(new AutoTooltipLabel(DisplayUtils.formatDateTime(getTradable(item).get().getDate())));
setGraphic(new AutoTooltipLabel(item.getDateString()));
else
setGraphic(new AutoTooltipLabel(Res.get("shared.noDateAvailable")));
setGraphic(new AutoTooltipLabel(item.getDateString()));
} else {
setGraphic(null);
}
@ -257,13 +311,13 @@ public class LockedView extends ActivatableView<VBox, Void> {
if (item != null && !empty) {
Optional<Tradable> tradableOptional = getTradable(item);
if (tradableOptional.isPresent()) {
field = new HyperlinkWithIcon(Res.get("funds.locked.locked", item.getTrade().getShortId()),
field = new HyperlinkWithIcon(item.getDetails(),
AwesomeIcon.INFO_SIGN);
field.setOnAction(event -> openDetailPopup(item));
field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails")));
setGraphic(field);
} else {
setGraphic(new AutoTooltipLabel(Res.get("shared.noDetailsAvailable")));
setGraphic(new AutoTooltipLabel(item.getDetails()));
}
} else {

View File

@ -18,12 +18,13 @@
package bisq.desktop.main.funds.reserved;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.util.DisplayUtils;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.trade.Tradable;
import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Address;
@ -34,26 +35,40 @@ import javafx.scene.control.Label;
import java.util.Optional;
import lombok.Getter;
class ReservedListItem {
private final BalanceListener balanceListener;
private final Label balanceLabel;
private final OpenOffer openOffer;
private final AddressEntry addressEntry;
private final BtcWalletService btcWalletService;
private final CoinFormatter formatter;
private final String addressString;
private Coin balance;
public ReservedListItem(OpenOffer openOffer, AddressEntry addressEntry, BtcWalletService btcWalletService, CoinFormatter formatter) {
@Getter
private final Label balanceLabel;
@Getter
private final OpenOffer openOffer;
@Getter
private final AddressEntry addressEntry;
@Getter
private final String addressString;
@Getter
private final Address address;
@Getter
private Coin balance;
@Getter
private String balanceString;
public ReservedListItem(OpenOffer openOffer,
AddressEntry addressEntry,
BtcWalletService btcWalletService,
CoinFormatter formatter) {
this.openOffer = openOffer;
this.addressEntry = addressEntry;
this.btcWalletService = btcWalletService;
this.formatter = formatter;
addressString = addressEntry.getAddressString();
// balance
address = addressEntry.getAddress();
balanceLabel = new AutoTooltipLabel();
balanceListener = new BalanceListener(getAddress()) {
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
@ -63,41 +78,40 @@ class ReservedListItem {
updateBalance();
}
ReservedListItem() {
this.openOffer = null;
this.addressEntry = null;
this.btcWalletService = null;
this.formatter = null;
addressString = null;
address = null;
balanceLabel = null;
balanceListener = null;
}
public void cleanup() {
btcWalletService.removeBalanceListener(balanceListener);
}
private void updateBalance() {
final Optional<AddressEntry> addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE);
Optional<AddressEntry> addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(),
AddressEntry.Context.RESERVED_FOR_TRADE);
addressEntryOptional.ifPresent(addressEntry -> {
balance = btcWalletService.getBalanceForAddress(addressEntry.getAddress());
if (balance != null)
balanceLabel.setText(formatter.formatCoin(balance));
if (balance != null) {
balanceString = formatter.formatCoin(balance);
balanceLabel.setText(balanceString);
}
});
}
private Address getAddress() {
return addressEntry.getAddress();
public String getDateAsString() {
return DisplayUtils.formatDateTime(openOffer.getDate());
}
public AddressEntry getAddressEntry() {
return addressEntry;
public String getDetails() {
return openOffer != null ?
Res.get("funds.reserved.reserved", openOffer.getShortId()) :
Res.get("shared.noDetailsAvailable");
}
public Label getBalanceLabel() {
return balanceLabel;
}
public Coin getBalance() {
return balance;
}
public String getAddressString() {
return addressString;
}
public Tradable getOpenOffer() {
return openOffer;
}
}

View File

@ -17,8 +17,12 @@
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import bisq.desktop.components.AutoTooltipButton?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<VBox fx:id="root" fx:controller="bisq.desktop.main.funds.reserved.ReservedView"
@ -35,5 +39,9 @@
<TableColumn fx:id="balanceColumn" minWidth="110"/>
</columns>
</TableView>
<HBox spacing="10">
<Label fx:id="numItems"/>
<Region fx:id="spacer"/>
<AutoTooltipButton fx:id="exportButton"/>
</HBox>
</VBox>

View File

@ -19,12 +19,12 @@ package bisq.desktop.main.funds.reserved;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.BalanceListener;
@ -43,6 +43,8 @@ import bisq.core.util.coin.CoinFormatter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import javax.inject.Inject;
import javax.inject.Named;
@ -50,12 +52,20 @@ import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
@ -77,6 +87,12 @@ public class ReservedView extends ActivatableView<VBox, Void> {
TableView<ReservedListItem> tableView;
@FXML
TableColumn<ReservedListItem, ReservedListItem> dateColumn, detailsColumn, addressColumn, balanceColumn;
@FXML
Label numItems;
@FXML
Region spacer;
@FXML
AutoTooltipButton exportButton;
private final BtcWalletService btcWalletService;
private final TradeManager tradeManager;
@ -97,8 +113,13 @@ public class ReservedView extends ActivatableView<VBox, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ReservedView(BtcWalletService btcWalletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) {
private ReservedView(BtcWalletService btcWalletService,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
Preferences preferences,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
OfferDetailsWindow offerDetailsWindow,
TradeDetailsWindow tradeDetailsWindow) {
this.btcWalletService = btcWalletService;
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
@ -138,6 +159,10 @@ public class ReservedView extends ActivatableView<VBox, Void> {
};
openOfferListChangeListener = c -> updateList();
tradeListChangeListener = c -> updateList();
HBox.setHgrow(spacer, Priority.ALWAYS);
numItems.setPadding(new Insets(-5, 0, 0, 10));
exportButton.updateText(Res.get("shared.exportCSV"));
}
@Override
@ -149,6 +174,33 @@ public class ReservedView extends ActivatableView<VBox, Void> {
updateList();
btcWalletService.addBalanceListener(balanceListener);
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
exportButton.setOnAction(event -> {
ObservableList<TableColumn<ReservedListItem, ?>> tableColumns = tableView.getColumns();
int reportColumns = tableColumns.size();
CSVEntryConverter<ReservedListItem> headerConverter = transactionsListItem -> {
String[] columns = new String[reportColumns];
for (int i = 0; i < columns.length; i++)
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
return columns;
};
CSVEntryConverter<ReservedListItem> contentConverter = item -> {
String[] columns = new String[reportColumns];
columns[0] = item.getDateAsString();
columns[1] = item.getDetails();
columns[2] = item.getAddressString();
columns[3] = item.getBalanceString();
return columns;
};
GUIUtil.exportCSV("reservedInOffersFunds.csv",
headerConverter,
contentConverter,
new ReservedListItem(),
sortedList,
(Stage) root.getScene().getWindow());
});
}
@Override
@ -158,6 +210,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
sortedList.comparatorProperty().unbind();
observableList.forEach(ReservedListItem::cleanup);
btcWalletService.removeBalanceListener(balanceListener);
exportButton.setOnAction(null);
}
@ -226,9 +279,9 @@ public class ReservedView extends ActivatableView<VBox, Void> {
public void updateItem(final ReservedListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (getTradable(item).isPresent())
setGraphic(new AutoTooltipLabel(DisplayUtils.formatDateTime(getTradable(item).get().getDate())));
else
if (getTradable(item).isPresent()) {
setGraphic(new AutoTooltipLabel(item.getDateAsString()));
} else
setGraphic(new AutoTooltipLabel(Res.get("shared.noDateAvailable")));
} else {
setGraphic(null);
@ -257,13 +310,12 @@ public class ReservedView extends ActivatableView<VBox, Void> {
if (item != null && !empty) {
Optional<Tradable> tradableOptional = getTradable(item);
if (tradableOptional.isPresent()) {
field = new HyperlinkWithIcon(Res.get("funds.reserved.reserved", item.getOpenOffer().getShortId()),
AwesomeIcon.INFO_SIGN);
field = new HyperlinkWithIcon(item.getDetails(), AwesomeIcon.INFO_SIGN);
field.setOnAction(event -> openDetailPopup(item));
field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails")));
setGraphic(field);
} else {
setGraphic(new AutoTooltipLabel(Res.get("shared.noDetailsAvailable")));
setGraphic(new AutoTooltipLabel(item.getDetails()));
}
} else {

View File

@ -18,8 +18,11 @@
-->
<?import bisq.desktop.components.AutoTooltipButton?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<VBox fx:id="root" fx:controller="bisq.desktop.main.funds.transactions.TransactionsView"
@ -34,10 +37,14 @@
<TableColumn fx:id="addressColumn" minWidth="260"/>
<TableColumn fx:id="transactionColumn" minWidth="180"/>
<TableColumn fx:id="amountColumn" minWidth="130" maxWidth="130"/>
<TableColumn fx:id="memoColumn" minWidth="50" maxWidth="50"/>
<TableColumn fx:id="confidenceColumn" minWidth="130" maxWidth="130"/>
<TableColumn fx:id="memoColumn" minWidth="40"/>
<TableColumn fx:id="confidenceColumn" minWidth="120" maxWidth="130"/>
<TableColumn fx:id="revertTxColumn" sortable="false" minWidth="110" maxWidth="110" visible="false"/>
</columns>
</TableView>
<AutoTooltipButton fx:id="exportButton"/>
<HBox spacing="10">
<Label fx:id="numItems"/>
<Region fx:id="spacer"/>
<AutoTooltipButton fx:id="exportButton"/>
</HBox>
</VBox>

View File

@ -56,14 +56,20 @@ import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.event.EventHandler;
@ -80,11 +86,16 @@ import javax.annotation.Nullable;
@FxmlView
public class TransactionsView extends ActivatableView<VBox, Void> {
@FXML
TableView<TransactionsListItem> tableView;
@FXML
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, memoColumn, confidenceColumn, revertTxColumn;
@FXML
Label numItems;
@FXML
Region spacer;
@FXML
AutoTooltipButton exportButton;
private final DisplayedTransactions displayedTransactions;
@ -180,6 +191,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
}
};
HBox.setHgrow(spacer, Priority.ALWAYS);
numItems.setPadding(new Insets(-5, 0, 0, 10));
exportButton.updateText(Res.get("shared.exportCSV"));
}
@ -195,6 +208,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
if (scene != null)
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
numItems.setText(Res.get("shared.numItemsLabel", sortedDisplayedTransactions.size()));
exportButton.setOnAction(event -> {
final ObservableList<TableColumn<TransactionsListItem, ?>> tableColumns = tableView.getColumns();
final int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon)

View File

@ -20,10 +20,12 @@
<?import bisq.desktop.components.AutoTooltipButton?>
<?import bisq.desktop.components.AutoTooltipLabel?>
<?import bisq.desktop.components.InputTextField?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<VBox fx:id="root" fx:controller="bisq.desktop.main.portfolio.closedtrades.ClosedTradesView"
@ -32,6 +34,12 @@
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
<HBox fx:id="searchBox">
<AutoTooltipLabel fx:id="filterLabel"/>
<InputTextField fx:id="filterTextField" minWidth="500"/>
<Pane fx:id="searchBoxSpacer"/>
</HBox>
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="tradeIdColumn" minWidth="110" maxWidth="120"/>
@ -50,10 +58,10 @@
<TableColumn fx:id="avatarColumn" minWidth="40" maxWidth="40"/>
</columns>
</TableView>
<HBox fx:id="footerBox">
<AutoTooltipLabel fx:id="filterLabel"/>
<InputTextField fx:id="filterTextField" minWidth="300"/>
<Pane fx:id="spacer"/>
<HBox spacing="10">
<Label fx:id="numItems"/>
<Region fx:id="footerSpacer"/>
<AutoTooltipButton fx:id="exportButton"/>
</HBox>
</VBox>

View File

@ -27,7 +27,6 @@ import bisq.desktop.components.InputTextField;
import bisq.desktop.components.PeerInfoIcon;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import bisq.core.alert.PrivateNotificationManager;
@ -53,6 +52,7 @@ import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
@ -60,6 +60,7 @@ import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
@ -114,15 +115,19 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
txFeeColumn, tradeFeeColumn, buyerSecurityDepositColumn, sellerSecurityDepositColumn,
marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn;
@FXML
HBox footerBox;
HBox searchBox;
@FXML
AutoTooltipLabel filterLabel;
@FXML
InputTextField filterTextField;
@FXML
Pane spacer;
Pane searchBoxSpacer;
@FXML
AutoTooltipButton exportButton;
@FXML
Label numItems;
@FXML
Region footerSpacer;
private final OfferDetailsWindow offerDetailsWindow;
private final Preferences preferences;
@ -225,14 +230,16 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
filterLabel.setText(Res.getWithCol("support.filter"));
filterTextField.setPromptText(Res.get("support.filter.prompt"));
filterLabel.setText(Res.get("shared.filter"));
HBox.setMargin(filterLabel, new Insets(5, 0, 0, 10));
filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText());
footerBox.setSpacing(5);
HBox.setHgrow(spacer, Priority.ALWAYS);
exportButton.updateText(Res.get("shared.exportCSV"));
searchBox.setSpacing(5);
HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS);
numItems.setPadding(new Insets(-5, 0, 0, 10));
HBox.setHgrow(footerSpacer, Priority.ALWAYS);
HBox.setMargin(exportButton, new Insets(0, 10, 0, 0));
exportButton.updateText(Res.get("shared.exportCSV"));
}
@Override
@ -244,6 +251,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
tableView.setItems(sortedList);
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
exportButton.setOnAction(event -> {
final ObservableList<TableColumn<ClosedTradableListItem, ?>> tableColumns = tableView.getColumns();
CSVEntryConverter<ClosedTradableListItem> headerConverter = transactionsListItem -> {
@ -317,31 +325,82 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
if (filterString.isEmpty())
return true;
Offer offer = item.getTradable().getOffer();
boolean matchesId = offer.getId().contains(filterString);
boolean matchesOfferDate = DisplayUtils.formatDate(offer.getDate()).contains(filterString);
boolean isMakerOnion = offer.getMakerNodeAddress().getFullAddress().contains(filterString);
Tradable tradable = item.getTradable();
Offer offer = tradable.getOffer();
if (offer.getId().contains(filterString)) {
return true;
}
if (model.getDate(item).contains(filterString)) {
return true;
}
if (model.getMarketLabel(item).contains(filterString)) {
return true;
}
if (model.getPrice(item).contains(filterString)) {
return true;
}
if (model.getPriceDeviation(item).contains(filterString)) {
return true;
}
if (item.getTradable() instanceof Trade) {
if (model.getVolume(item).contains(filterString)) {
return true;
}
if (model.getAmount(item).contains(filterString)) {
return true;
}
if (model.getTradeFee(item).contains(filterString)) {
return true;
}
if (model.getTxFee(item).contains(filterString)) {
return true;
}
if (model.getBuyerSecurityDeposit(item).contains(filterString)) {
return true;
}
if (model.getSellerSecurityDeposit(item).contains(filterString)) {
return true;
}
if (model.getState(item).contains(filterString)) {
return true;
}
if (model.getDirectionLabel(item).contains(filterString)) {
return true;
}
if (offer.getPaymentMethod().getDisplayString().contains(filterString)) {
return true;
}
if (offer.getOfferFeePaymentTxId().contains(filterString)) {
return true;
}
if (tradable instanceof Trade) {
Trade trade = (Trade) tradable;
if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().contains(filterString)) {
return true;
}
if (trade.getDepositTxId() != null && trade.getDepositTxId().contains(filterString)) {
return true;
}
if (trade.getPayoutTxId() != null && trade.getPayoutTxId().contains(filterString)) {
return true;
}
Contract contract = trade.getContract();
boolean isBuyerOnion = false;
boolean isSellerOnion = false;
boolean matchesBuyersPaymentAccountData = false;
boolean matchesSellersPaymentAccountData = false;
Trade trade = (Trade) item.getTradable();
boolean matchesTradeDate = DisplayUtils.formatDate(trade.getTakeOfferDate()).contains(filterString);
Contract contract = trade.getContract();
if (contract != null) {
isBuyerOnion = contract.getBuyerNodeAddress().getFullAddress().contains(filterString);
isSellerOnion = contract.getSellerNodeAddress().getFullAddress().contains(filterString);
matchesBuyersPaymentAccountData = contract.getBuyerPaymentAccountPayload().getPaymentDetails().contains(filterString);
matchesSellersPaymentAccountData = contract.getSellerPaymentAccountPayload().getPaymentDetails().contains(filterString);
}
return matchesId || matchesOfferDate || isMakerOnion ||
matchesTradeDate || isBuyerOnion || isSellerOnion ||
return isBuyerOnion || isSellerOnion ||
matchesBuyersPaymentAccountData || matchesSellersPaymentAccountData;
} else {
return matchesId || matchesOfferDate || isMakerOnion;
return false;
}
});
}

View File

@ -19,18 +19,17 @@ package bisq.desktop.main.portfolio.failedtrades;
import bisq.core.trade.Trade;
/**
* We could remove that wrapper if it is not needed for additional UI only fields.
*/
class FailedTradesListItem {
import lombok.Getter;
class FailedTradesListItem {
@Getter
private final Trade trade;
FailedTradesListItem(Trade trade) {
this.trade = trade;
}
Trade getTrade() {
return trade;
FailedTradesListItem() {
this.trade = null;
}
}

View File

@ -17,8 +17,15 @@
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import bisq.desktop.components.AutoTooltipButton?>
<?import bisq.desktop.components.AutoTooltipLabel?>
<?import bisq.desktop.components.InputTextField?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<VBox fx:id="root" fx:controller="bisq.desktop.main.portfolio.failedtrades.FailedTradesView"
@ -26,7 +33,11 @@
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
<HBox fx:id="searchBox">
<AutoTooltipLabel fx:id="filterLabel"/>
<InputTextField fx:id="filterTextField" minWidth="500"/>
<Pane fx:id="searchBoxSpacer"/>
</HBox>
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="tradeIdColumn" minWidth="120" maxWidth="120"/>
@ -40,4 +51,9 @@
<TableColumn fx:id="removeTradeColumn" minWidth="40" maxWidth="40"/>
</columns>
</TableView>
<HBox spacing="10">
<Label fx:id="numItems"/>
<Region fx:id="footerSpacer"/>
<AutoTooltipButton fx:id="exportButton"/>
</HBox>
</VBox>

View File

@ -19,18 +19,25 @@ package bisq.desktop.main.portfolio.failedtrades;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.common.config.Config;
import bisq.common.util.Utilities;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import javax.inject.Inject;
import javax.inject.Named;
@ -40,6 +47,8 @@ import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
@ -48,12 +57,21 @@ import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
@ -68,9 +86,26 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
@FXML
TableColumn<FailedTradesListItem, FailedTradesListItem> priceColumn, amountColumn, volumeColumn,
marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, removeTradeColumn;
@FXML
HBox searchBox;
@FXML
AutoTooltipLabel filterLabel;
@FXML
InputTextField filterTextField;
@FXML
Pane searchBoxSpacer;
@FXML
Label numItems;
@FXML
Region footerSpacer;
@FXML
AutoTooltipButton exportButton;
private final TradeDetailsWindow tradeDetailsWindow;
private SortedList<FailedTradesListItem> sortedList;
private FilteredList<FailedTradesListItem> filteredList;
private EventHandler<KeyEvent> keyEventEventHandler;
private ChangeListener<String> filterTextFieldListener;
private Scene scene;
private final boolean allowFaultyDelayedTxs;
@ -137,6 +172,137 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
}
}
};
filterLabel.setText(Res.get("shared.filter"));
HBox.setMargin(filterLabel, new Insets(5, 0, 0, 10));
filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText());
searchBox.setSpacing(5);
HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS);
numItems.setPadding(new Insets(-5, 0, 0, 10));
HBox.setHgrow(footerSpacer, Priority.ALWAYS);
HBox.setMargin(exportButton, new Insets(0, 10, 0, 0));
exportButton.updateText(Res.get("shared.exportCSV"));
}
@Override
protected void activate() {
scene = root.getScene();
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
}
filteredList = new FilteredList<>(model.getList());
sortedList = new SortedList<>(filteredList);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
exportButton.setOnAction(event -> {
ObservableList<TableColumn<FailedTradesListItem, ?>> tableColumns = tableView.getColumns();
int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon)
CSVEntryConverter<FailedTradesListItem> headerConverter = transactionsListItem -> {
String[] columns = new String[reportColumns];
for (int i = 0; i < columns.length; i++)
columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText();
return columns;
};
CSVEntryConverter<FailedTradesListItem> contentConverter = item -> {
String[] columns = new String[reportColumns];
columns[0] = model.getTradeId(item);
columns[1] = model.getDate(item);
columns[2] = model.getMarketLabel(item);
columns[3] = model.getPrice(item);
columns[4] = model.getAmount(item);
columns[5] = model.getVolume(item);
columns[6] = model.getDirectionLabel(item);
columns[7] = model.getState(item);
return columns;
};
GUIUtil.exportCSV("failedTrades.csv",
headerConverter,
contentConverter,
new FailedTradesListItem(),
sortedList,
(Stage) root.getScene().getWindow());
});
filterTextField.textProperty().addListener(filterTextFieldListener);
applyFilteredListPredicate(filterTextField.getText());
}
@Override
protected void deactivate() {
if (scene != null) {
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
}
sortedList.comparatorProperty().unbind();
exportButton.setOnAction(null);
filterTextField.textProperty().removeListener(filterTextFieldListener);
}
private void applyFilteredListPredicate(String filterString) {
filteredList.setPredicate(item -> {
if (filterString.isEmpty())
return true;
Offer offer = item.getTrade().getOffer();
if (offer.getId().contains(filterString)) {
return true;
}
if (model.getDate(item).contains(filterString)) {
return true;
}
if (model.getMarketLabel(item).contains(filterString)) {
return true;
}
if (model.getPrice(item).contains(filterString)) {
return true;
}
if (model.getVolume(item).contains(filterString)) {
return true;
}
if (model.getAmount(item).contains(filterString)) {
return true;
}
if (model.getDirectionLabel(item).contains(filterString)) {
return true;
}
if (offer.getOfferFeePaymentTxId().contains(filterString)) {
return true;
}
Trade trade = item.getTrade();
if (trade.getTakerFeeTxId() != null && trade.getTakerFeeTxId().contains(filterString)) {
return true;
}
if (trade.getDepositTxId() != null && trade.getDepositTxId().contains(filterString)) {
return true;
}
if (trade.getPayoutTxId() != null && trade.getPayoutTxId().contains(filterString)) {
return true;
}
Contract contract = trade.getContract();
boolean isBuyerOnion = false;
boolean isSellerOnion = false;
boolean matchesBuyersPaymentAccountData = false;
boolean matchesSellersPaymentAccountData = false;
if (contract != null) {
isBuyerOnion = contract.getBuyerNodeAddress().getFullAddress().contains(filterString);
isSellerOnion = contract.getSellerNodeAddress().getFullAddress().contains(filterString);
matchesBuyersPaymentAccountData = contract.getBuyerPaymentAccountPayload().getPaymentDetails().contains(filterString);
matchesSellersPaymentAccountData = contract.getSellerPaymentAccountPayload().getPaymentDetails().contains(filterString);
}
return isBuyerOnion || isSellerOnion ||
matchesBuyersPaymentAccountData || matchesSellersPaymentAccountData;
});
}
private void onUnfail() {
@ -165,25 +331,6 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
return "";
}
@Override
protected void activate() {
scene = root.getScene();
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
}
sortedList = new SortedList<>(model.getList());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
}
@Override
protected void deactivate() {
if (scene != null) {
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
}
sortedList.comparatorProperty().unbind();
}
private void onRevertTrade(Trade trade) {
new Popup().attention(Res.get("portfolio.failed.revertToPending.popup"))
.onAction(() -> onMoveTradeToPendingTrades(trade))

View File

@ -20,19 +20,21 @@ package bisq.desktop.main.portfolio.openoffer;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import lombok.Getter;
/**
* We could remove that wrapper if it is not needed for additional UI only fields.
*/
class OpenOfferListItem {
@Getter
private final OpenOffer openOffer;
public OpenOfferListItem(OpenOffer openOffer) {
OpenOfferListItem(OpenOffer openOffer) {
this.openOffer = openOffer;
}
public OpenOffer getOpenOffer() {
return openOffer;
OpenOfferListItem() {
openOffer = null;
}
public Offer getOffer() {

View File

@ -17,8 +17,16 @@
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import bisq.desktop.components.AutoTooltipButton?>
<?import bisq.desktop.components.AutoTooltipLabel?>
<?import bisq.desktop.components.AutoTooltipSlideToggleButton?>
<?import bisq.desktop.components.InputTextField?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.geometry.Insets?>
<VBox fx:id="root" fx:controller="bisq.desktop.main.portfolio.openoffer.OpenOffersView"
@ -26,7 +34,12 @@
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
<HBox fx:id="searchBox">
<AutoTooltipLabel fx:id="filterLabel"/>
<InputTextField fx:id="filterTextField" minWidth="500"/>
<Pane fx:id="searchBoxSpacer"/>
<AutoTooltipSlideToggleButton fx:id="selectToggleButton"/>
</HBox>
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="offerIdColumn" minWidth="110" maxWidth="120"/>
@ -43,5 +56,9 @@
<TableColumn fx:id="removeItemColumn" minWidth="50" maxWidth="60" sortable="false"/>
</columns>
</TableView>
<HBox spacing="10">
<Label fx:id="numItems"/>
<Region fx:id="footerSpacer"/>
<AutoTooltipButton fx:id="exportButton"/>
</HBox>
</VBox>

View File

@ -20,38 +20,57 @@ package bisq.desktop.main.portfolio.openoffer;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipCheckBox;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipSlideToggleButton;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.MainView;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.funds.withdrawal.WithdrawalView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.util.GUIUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.user.DontShowAgainLookup;
import com.googlecode.jcsv.writer.CSVEntryConverter;
import javax.inject.Inject;
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
@ -71,9 +90,28 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, deviationColumn, amountColumn, volumeColumn,
marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn,
removeItemColumn, editItemColumn, paymentMethodColumn;
@FXML
HBox searchBox;
@FXML
AutoTooltipLabel filterLabel;
@FXML
InputTextField filterTextField;
@FXML
Pane searchBoxSpacer;
@FXML
Label numItems;
@FXML
Region footerSpacer;
@FXML
AutoTooltipButton exportButton;
@FXML
AutoTooltipSlideToggleButton selectToggleButton;
private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow;
private SortedList<OpenOfferListItem> sortedList;
private FilteredList<OpenOfferListItem> filteredList;
private ChangeListener<String> filterTextFieldListener;
private PortfolioView.OpenOfferActionHandler openOfferActionHandler;
@Inject
@ -95,7 +133,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
directionColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.offerType")));
dateColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.dateTime")));
offerIdColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.offerId")));
deactivateItemColumn.setGraphic(new AutoTooltipLabel("Enabled"));
deactivateItemColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.enabled")));
editItemColumn.setGraphic(new AutoTooltipLabel(""));
removeItemColumn.setGraphic(new AutoTooltipLabel(""));
@ -121,7 +159,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
amountColumn.setComparator(Comparator.comparing(o -> o.getOffer().getAmount()));
priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder())));
deviationColumn.setComparator(Comparator.comparing(o ->
o.getOffer().isUseMarketBasedPrice() ? o.getOffer().getMarketPriceMargin() : 1,
o.getOffer().isUseMarketBasedPrice() ? o.getOffer().getMarketPriceMargin() : 1,
Comparator.nullsFirst(Comparator.naturalOrder())));
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
dateColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDate()));
@ -129,18 +167,151 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
filterLabel.setText(Res.get("shared.filter"));
HBox.setMargin(filterLabel, new Insets(5, 0, 0, 10));
filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText());
searchBox.setSpacing(5);
HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS);
selectToggleButton.setPadding(new Insets(0, 60, -20, 0));
selectToggleButton.setText(Res.get("shared.enabled"));
selectToggleButton.setDisable(true);
numItems.setPadding(new Insets(-5, 0, 0, 10));
HBox.setHgrow(footerSpacer, Priority.ALWAYS);
HBox.setMargin(exportButton, new Insets(0, 10, 0, 0));
exportButton.updateText(Res.get("shared.exportCSV"));
}
@Override
protected void activate() {
sortedList = new SortedList<>(model.getList());
filteredList = new FilteredList<>(model.getList());
sortedList = new SortedList<>(filteredList);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateSelectToggleButtonState();
selectToggleButton.setOnAction(event -> {
if (selectToggleButton.isSelected()) {
sortedList.forEach(openOfferListItem -> onActivateOpenOffer(openOfferListItem.getOpenOffer()));
} else {
sortedList.forEach(openOfferListItem -> onDeactivateOpenOffer(openOfferListItem.getOpenOffer()));
}
tableView.refresh();
});
numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
exportButton.setOnAction(event -> {
ObservableList<TableColumn<OpenOfferListItem, ?>> tableColumns = tableView.getColumns();
int reportColumns = tableColumns.size() - 2; // CSV report excludes the last columns (icons)
CSVEntryConverter<OpenOfferListItem> headerConverter = transactionsListItem -> {
String[] columns = new String[reportColumns];
for (int i = 0; i < columns.length; i++) {
Node graphic = tableColumns.get(i).getGraphic();
if (graphic instanceof AutoTooltipLabel) {
columns[i] = ((AutoTooltipLabel) graphic).getText();
} else if (graphic instanceof HBox) {
// Deviation has a Hbox with AutoTooltipLabel as first child in header
columns[i] = ((AutoTooltipLabel) ((Parent) graphic).getChildrenUnmodifiable().get(0)).getText();
} else {
// Not expected
columns[i] = "N/A";
}
}
return columns;
};
CSVEntryConverter<OpenOfferListItem> contentConverter = item -> {
String[] columns = new String[reportColumns];
columns[0] = model.getOfferId(item);
columns[1] = model.getDate(item);
columns[2] = model.getMarketLabel(item);
columns[3] = model.getPrice(item);
columns[4] = model.getPriceDeviation(item);
columns[5] = model.getAmount(item);
columns[6] = model.getVolume(item);
columns[7] = model.getPaymentMethod(item);
columns[8] = model.getDirectionLabel(item);
columns[9] = String.valueOf(!item.getOpenOffer().isDeactivated());
return columns;
};
GUIUtil.exportCSV("openOffers.csv",
headerConverter,
contentConverter,
new OpenOfferListItem(),
sortedList,
(Stage) root.getScene().getWindow());
});
filterTextField.textProperty().addListener(filterTextFieldListener);
applyFilteredListPredicate(filterTextField.getText());
}
private void updateSelectToggleButtonState() {
if (sortedList.size() == 0) {
selectToggleButton.setDisable(true);
selectToggleButton.setSelected(false);
} else {
selectToggleButton.setDisable(false);
long numDeactivated = sortedList.stream()
.filter(openOfferListItem -> openOfferListItem.getOpenOffer().isDeactivated())
.count();
if (numDeactivated == sortedList.size()) {
selectToggleButton.setSelected(false);
} else if (numDeactivated == 0) {
selectToggleButton.setSelected(true);
}
}
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
exportButton.setOnAction(null);
filterTextField.textProperty().removeListener(filterTextFieldListener);
}
private void applyFilteredListPredicate(String filterString) {
filteredList.setPredicate(item -> {
if (filterString.isEmpty())
return true;
Offer offer = item.getOpenOffer().getOffer();
if (offer.getId().contains(filterString)) {
return true;
}
if (model.getDate(item).contains(filterString)) {
return true;
}
if (model.getMarketLabel(item).contains(filterString)) {
return true;
}
if (model.getPrice(item).contains(filterString)) {
return true;
}
if (model.getPriceDeviation(item).contains(filterString)) {
return true;
}
if (model.getPaymentMethod(item).contains(filterString)) {
return true;
}
if (model.getVolume(item).contains(filterString)) {
return true;
}
if (model.getAmount(item).contains(filterString)) {
return true;
}
if (model.getDirectionLabel(item).contains(filterString)) {
return true;
}
if (offer.getOfferFeePaymentTxId().contains(filterString)) {
return true;
}
return false;
});
}
private void onDeactivateOpenOffer(OpenOffer openOffer) {
@ -151,6 +322,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
log.error(message);
new Popup().warning(Res.get("offerbook.deactivateOffer.failed", message)).show();
});
updateSelectToggleButtonState();
}
}
@ -162,6 +334,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
log.error(message);
new Popup().warning(Res.get("offerbook.activateOffer.failed", message)).show();
});
updateSelectToggleButtonState();
}
}
@ -178,6 +351,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
} else {
doRemoveOpenOffer(openOffer);
}
updateSelectToggleButtonState();
}
}
@ -226,7 +400,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
super.updateItem(item, empty);
if (item != null && !empty) {
field = new HyperlinkWithIcon(model.getTradeId(item));
field = new HyperlinkWithIcon(model.getOfferId(item));
field.setOnAction(event -> offerDetailsWindow.show(item.getOffer()));
field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails")));
setGraphic(field);
@ -448,14 +622,10 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
public TableCell<OpenOfferListItem, OpenOfferListItem> call(TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
return new TableCell<>() {
final ImageView iconView = new ImageView();
CheckBox checkBox;
AutoTooltipSlideToggleButton checkBox;
private void updateState(@NotNull OpenOffer openOffer) {
if (openOffer.isDeactivated()) {
checkBox.setSelected(false);
} else {
checkBox.setSelected(true);
}
checkBox.setSelected(!openOffer.isDeactivated());
}
@Override
@ -464,7 +634,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
if (item != null && !empty) {
if (checkBox == null) {
checkBox = new AutoTooltipCheckBox();
checkBox = new AutoTooltipSlideToggleButton();
checkBox.setGraphic(iconView);
}
checkBox.setOnAction(event -> {
@ -528,10 +698,10 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
private void setEditColumnCellFactory() {
editItemColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
editItemColumn.setCellFactory(
new Callback<TableColumn<OpenOfferListItem, OpenOfferListItem>, TableCell<OpenOfferListItem, OpenOfferListItem>>() {
new Callback<>() {
@Override
public TableCell<OpenOfferListItem, OpenOfferListItem> call(TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
return new TableCell<OpenOfferListItem, OpenOfferListItem>() {
return new TableCell<>() {
Button button;
@Override

View File

@ -27,8 +27,8 @@ import bisq.core.locale.Res;
import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
import bisq.core.offer.OpenOffer;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.network.p2p.P2PService;
@ -78,7 +78,7 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
return dataModel.getList();
}
String getTradeId(OpenOfferListItem item) {
String getOfferId(OpenOfferListItem item) {
return item.getOffer().getShortId();
}