remove input selection from withdraw view

This commit is contained in:
woodser 2022-07-14 11:31:41 -04:00
parent fb3745c6df
commit e17b0f8ec8
3 changed files with 10 additions and 291 deletions

View file

@ -127,7 +127,7 @@ shared.noDateAvailable=No date available
shared.noDetailsAvailable=No details available
shared.notUsedYet=Not used yet
shared.date=Date
shared.sendFundsDetailsWithFee=Sending: {0}\nFrom address: {1}\nTo receiving address: {2}.\nRequired mining fee is: {3}\n\nThe recipient will receive: {4}\n\nAre you sure you want to withdraw this amount?
shared.sendFundsDetailsWithFee=Sending: {0}\nTo receiving address: {1}.\nRequired mining fee is: {2}\n\nThe recipient will receive: {3}\n\nAre you sure you want to withdraw this amount?
# suppress inspection "TrailingSpacesInProperty"
shared.sendFundsDetailsDust=Haveno detected that this transaction would create a change output which is below the minimum dust threshold (and therefore not allowed by Monero consensus rules). Instead, this dust ({0} satoshi{1}) will be added to the mining fee.\n\n\n
shared.copyToClipboard=Copy to clipboard

View file

@ -29,14 +29,6 @@
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="selectColumn" minWidth="60" maxWidth="60" sortable="false"/>
<TableColumn fx:id="addressColumn" minWidth="320"/>
<TableColumn fx:id="balanceColumn" minWidth="310" maxWidth="310"/>
</columns>
</TableView>
<GridPane fx:id="gridPane" hgap="5.0" vgap="5.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>

View file

@ -19,10 +19,6 @@ package bisq.desktop.main.funds.withdrawal;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipCheckBox;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.overlays.popups.Popup;
@ -30,7 +26,6 @@ import bisq.desktop.main.overlays.windows.TxDetails;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.setup.WalletsSetup;
@ -40,7 +35,6 @@ import bisq.core.provider.fee.FeeService;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
@ -48,7 +42,6 @@ import bisq.core.util.validation.BtcAddressValidator;
import bisq.network.p2p.P2PService;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin;
@ -57,46 +50,19 @@ import javax.inject.Named;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet;
import org.apache.commons.lang3.StringUtils;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Pos;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static bisq.desktop.util.FormBuilder.*;
@ -105,31 +71,18 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML
GridPane gridPane;
@FXML
TableView<WithdrawalListItem> tableView;
@FXML
TableColumn<WithdrawalListItem, WithdrawalListItem> addressColumn, balanceColumn, selectColumn;
private RadioButton useAllInputsRadioButton, useCustomInputsRadioButton;
private Label amountLabel;
private TextField amountTextField, withdrawFromTextField, withdrawToTextField, withdrawMemoTextField;
private TextField amountTextField, withdrawToTextField, withdrawMemoTextField;
private final XmrWalletService xmrWalletService;
private final TradeManager tradeManager;
private final P2PService p2PService;
private final CoinFormatter formatter;
private final Preferences preferences;
private final ObservableList<WithdrawalListItem> observableList = FXCollections.observableArrayList();
private final SortedList<WithdrawalListItem> sortedList = new SortedList<>(observableList);
private final Set<WithdrawalListItem> selectedItems = new HashSet<>();
private XmrBalanceListener balanceListener;
private Coin totalAvailableAmountOfSelectedItems = Coin.ZERO;
private Coin amountAsCoin = Coin.ZERO;
private ChangeListener<String> amountListener;
private ChangeListener<Boolean> amountFocusListener;
private ChangeListener<Toggle> inputsToggleGroupListener;
private ToggleGroup inputsToggleGroup;
private final BooleanProperty useAllInputs = new SimpleBooleanProperty(true);
private int rowIndex = 0;
///////////////////////////////////////////////////////////////////////////////////////////
@ -142,7 +95,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
P2PService p2PService,
WalletsSetup walletsSetup,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
Preferences preferences,
BtcAddressValidator btcAddressValidator,
WalletPasswordWindow walletPasswordWindow,
FeeService feeService) {
@ -150,7 +102,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
this.tradeManager = tradeManager;
this.p2PService = p2PService;
this.formatter = formatter;
this.preferences = preferences;
}
@Override
@ -159,34 +110,14 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
final TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, rowIndex, 4, Res.get("funds.deposit.withdrawFromWallet"));
titledGroupBg.getStyleClass().add("last");
inputsToggleGroup = new ToggleGroup();
inputsToggleGroupListener = (observable, oldValue, newValue) -> {
useAllInputs.set(newValue == useAllInputsRadioButton);
updateInputSelection();
};
final Tuple3<Label, RadioButton, RadioButton> labelRadioButtonRadioButtonTuple3 =
addTopLabelRadioButtonRadioButton(gridPane, rowIndex, inputsToggleGroup,
Res.get("funds.withdrawal.inputs"),
Res.get("funds.withdrawal.useAllInputs"),
Res.get("funds.withdrawal.useCustomInputs"),
Layout.FIRST_ROW_DISTANCE);
useAllInputsRadioButton = labelRadioButtonRadioButtonTuple3.second;
useCustomInputsRadioButton = labelRadioButtonRadioButtonTuple3.third;
final Tuple2<Label, InputTextField> feeTuple3 = addTopLabelInputTextField(gridPane, ++rowIndex,
final Tuple2<Label, InputTextField> amountTuple3 = addTopLabelInputTextField(gridPane, ++rowIndex,
Res.get("funds.withdrawal.receiverAmount", Res.getBaseCurrencyCode()),
0);
Layout.COMPACT_FIRST_ROW_DISTANCE);
amountLabel = feeTuple3.first;
amountTextField = feeTuple3.second;
amountLabel = amountTuple3.first;
amountTextField = amountTuple3.second;
amountTextField.setMinWidth(180);
withdrawFromTextField = addTopLabelTextField(gridPane, ++rowIndex,
Res.get("funds.withdrawal.fromLabel", Res.getBaseCurrencyCode())).second;
withdrawToTextField = addTopLabelInputTextField(gridPane, ++rowIndex,
Res.get("funds.withdrawal.toLabel", Res.getBaseCurrencyCode())).second;
@ -197,28 +128,10 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawButton.setOnAction(event -> onWithdraw());
addressColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.address")));
balanceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.balanceWithCur", Res.getBaseCurrencyCode())));
selectColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.select")));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setMaxHeight(Double.MAX_VALUE);
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.withdrawal.noFundsAvailable")));
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
setAddressColumnCellFactory();
setBalanceColumnCellFactory();
setSelectColumnCellFactory();
addressColumn.setComparator(Comparator.comparing(WithdrawalListItem::getAddressString));
balanceColumn.setComparator(Comparator.comparing(WithdrawalListItem::getBalance));
balanceColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(balanceColumn);
balanceListener = new XmrBalanceListener() {
@Override
public void onBalanceChanged(BigInteger balance) {
updateList();
}
};
amountListener = (observable, oldValue, newValue) -> {
@ -241,42 +154,22 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
amountLabel.setText(Res.get("funds.withdrawal.receiverAmount"));
}
private void updateInputSelection() {
observableList.forEach(item -> {
item.setSelected(useAllInputs.get());
selectForWithdrawal(item);
});
tableView.refresh();
}
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
reset();
amountTextField.textProperty().addListener(amountListener);
amountTextField.focusedProperty().addListener(amountFocusListener);
xmrWalletService.addBalanceListener(balanceListener);
inputsToggleGroup.selectedToggleProperty().addListener(inputsToggleGroupListener);
if (inputsToggleGroup.getSelectedToggle() == null)
inputsToggleGroup.selectToggle(useAllInputsRadioButton);
updateInputSelection();
GUIUtil.requestFocus(withdrawToTextField);
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
observableList.forEach(WithdrawalListItem::cleanup);
xmrWalletService.removeBalanceListener(balanceListener);
amountTextField.textProperty().removeListener(amountListener);
amountTextField.focusedProperty().removeListener(amountFocusListener);
inputsToggleGroup.selectedToggleProperty().removeListener(inputsToggleGroupListener);
}
@ -299,14 +192,14 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
MoneroTxWallet tx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount)) // TODO: rename to centinerosToAtomicUnits()?
.setAddress(withdrawToAddress));
.setAddress(withdrawToAddress)
.setNote(withdrawMemoTextField.getText()));
// create confirmation message
Coin fee = ParsingUtils.atomicUnitsToCoin(tx.getFee());
Coin sendersAmount = receiverAmount.add(fee);
String messageText = Res.get("shared.sendFundsDetailsWithFee",
formatter.formatCoinWithCode(sendersAmount),
withdrawFromTextField.getText(),
withdrawToAddress,
formatter.formatCoinWithCode(fee),
formatter.formatCoinWithCode(receiverAmount));
@ -343,7 +236,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (Throwable e) {
if (e.getMessage().contains("enough")) new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show();
if (e.getMessage().contains("enough")) new Popup().warning(Res.get("funds.withdrawal.warn.amountExceeds")).show();
else {
e.printStackTrace();
log.error(e.toString());
@ -353,71 +246,12 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
}
}
private void selectForWithdrawal(WithdrawalListItem item) {
if (item.isSelected())
selectedItems.add(item);
else
selectedItems.remove(item);
if (!selectedItems.isEmpty()) {
totalAvailableAmountOfSelectedItems = Coin.valueOf(selectedItems.stream().mapToLong(e -> e.getBalance().getValue()).sum());
if (totalAvailableAmountOfSelectedItems.isPositive()) {
amountAsCoin = totalAvailableAmountOfSelectedItems;
amountTextField.setText(formatter.formatCoin(amountAsCoin));
} else {
amountAsCoin = Coin.ZERO;
totalAvailableAmountOfSelectedItems = Coin.ZERO;
amountTextField.setText("");
withdrawFromTextField.setText("");
}
if (selectedItems.size() == 1) {
withdrawFromTextField.setText(selectedItems.stream().findAny().get().getAddressEntry().getAddressString());
withdrawFromTextField.setTooltip(null);
} else {
int abbr = Math.max(10, 66 / selectedItems.size());
String addressesShortened = selectedItems.stream()
.map(e -> StringUtils.abbreviate(e.getAddressString(), abbr))
.collect(Collectors.joining(", "));
String text = Res.get("funds.withdrawal.withdrawMultipleAddresses", addressesShortened);
withdrawFromTextField.setText(text);
String addresses = selectedItems.stream()
.map(WithdrawalListItem::getAddressString)
.collect(Collectors.joining(",\n"));
String tooltipText = Res.get("funds.withdrawal.withdrawMultipleAddresses.tooltip", addresses);
withdrawFromTextField.setTooltip(new Tooltip(tooltipText));
}
} else {
reset();
}
}
private void openBlockExplorer(WithdrawalListItem item) {
if (item.getAddressString() != null)
GUIUtil.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString(), false);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
observableList.forEach(WithdrawalListItem::cleanup);
observableList.setAll(xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.map(addressEntry -> new WithdrawalListItem(addressEntry, xmrWalletService, formatter))
.collect(Collectors.toList()));
updateInputSelection();
}
private void reset() {
withdrawFromTextField.setText("");
withdrawFromTextField.setPromptText(Res.get("funds.withdrawal.selectAddress"));
withdrawFromTextField.setTooltip(null);
totalAvailableAmountOfSelectedItems = Coin.ZERO;
amountAsCoin = Coin.ZERO;
amountTextField.setText("");
amountTextField.setPromptText(Res.get("funds.withdrawal.setAmount"));
@ -427,113 +261,6 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
withdrawMemoTextField.setText("");
withdrawMemoTextField.setPromptText(Res.get("funds.withdrawal.memo"));
selectedItems.clear();
tableView.getSelectionModel().clearSelection();
}
///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories
///////////////////////////////////////////////////////////////////////////////////////////
private void setAddressColumnCellFactory() {
addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
addressColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
WithdrawalListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final WithdrawalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String address = item.getAddressString();
hyperlinkWithIcon = new ExternalHyperlink(address);
hyperlinkWithIcon.setOnAction(event -> openBlockExplorer(item));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForAddress", address)));
setAlignment(Pos.CENTER);
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
}
private void setBalanceColumnCellFactory() {
balanceColumn.getStyleClass().add("last-column");
balanceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
balanceColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
WithdrawalListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final WithdrawalListItem item, boolean empty) {
super.updateItem(item, empty);
setGraphic((item != null && !empty) ? item.getBalanceLabel() : null);
}
};
}
});
}
private void setSelectColumnCellFactory() {
selectColumn.getStyleClass().add("first-column");
selectColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
selectColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<WithdrawalListItem, WithdrawalListItem> call(TableColumn<WithdrawalListItem,
WithdrawalListItem> column) {
return new TableCell<>() {
CheckBox checkBox = new AutoTooltipCheckBox();
@Override
public void updateItem(final WithdrawalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
checkBox.setOnAction(e -> {
item.setSelected(checkBox.isSelected());
selectForWithdrawal(item);
// If all are selected we select useAllInputsRadioButton
if (observableList.size() == selectedItems.size()) {
inputsToggleGroup.selectToggle(useAllInputsRadioButton);
} else {
// We don't want to get deselected all when we activate the useCustomInputsRadioButton
// so we temporarily disable the listener
inputsToggleGroup.selectedToggleProperty().removeListener(inputsToggleGroupListener);
inputsToggleGroup.selectToggle(useCustomInputsRadioButton);
useAllInputs.set(false);
inputsToggleGroup.selectedToggleProperty().addListener(inputsToggleGroupListener);
}
});
setGraphic(checkBox);
checkBox.setSelected(item.isSelected());
} else {
checkBox.setOnAction(null);
setGraphic(null);
}
}
};
}
});
}
}