Change comp request view

This commit is contained in:
Manfred Karrer 2018-03-14 00:49:44 -05:00
parent 2d89453736
commit 526e81bb7c
No known key found for this signature in database
GPG Key ID: 401250966A6B2C46
48 changed files with 1954 additions and 2361 deletions

View File

@ -48,8 +48,7 @@ import bisq.core.btc.wallet.WalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.btc.wallet.WalletsSetup;
import bisq.core.dao.DaoManager;
import bisq.core.dao.request.compensation.CompensationRequestManager;
import bisq.core.dao.vote.VotingManager;
import bisq.core.dao.proposal.ProposalCollectionsManager;
import bisq.core.filter.FilterManager;
import bisq.core.offer.OpenOfferManager;
import bisq.core.trade.TradeManager;
@ -228,8 +227,7 @@ public class BisqApp extends Application {
persistedDataHosts.add(injector.getInstance(FailedTradesManager.class));
persistedDataHosts.add(injector.getInstance(DisputeManager.class));
persistedDataHosts.add(injector.getInstance(P2PService.class));
persistedDataHosts.add(injector.getInstance(VotingManager.class));
persistedDataHosts.add(injector.getInstance(CompensationRequestManager.class));
persistedDataHosts.add(injector.getInstance(ProposalCollectionsManager.class));
// we apply at startup the reading of persisted data but don't want to get it triggered in the constructor
persistedDataHosts.forEach(e -> {

View File

@ -25,7 +25,6 @@ import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.AnchorPane;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
@ -68,9 +67,9 @@ public class FundsTextField extends InfoTextField {
}
});
AnchorPane.setRightAnchor(copyIcon, 30.0);
AnchorPane.setRightAnchor(infoIcon, 62.0);
AnchorPane.setRightAnchor(textField, 55.0);
setRightAnchor(copyIcon, 30.0);
setRightAnchor(infoIcon, 62.0);
setRightAnchor(textField, 55.0);
getChildren().add(copyIcon);
}

View File

@ -0,0 +1,99 @@
/*
* This file is part of bisq.
*
* bisq 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.
*
* bisq 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 bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.components;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.View;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.proposal.ProposalView;
import bisq.desktop.util.Colors;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleGroup;
import javafx.scene.paint.Paint;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.beans.value.ChangeListener;
public class MenuItem extends AutoTooltipToggleButton {
private final ChangeListener<Boolean> selectedPropertyChangeListener;
private final ChangeListener<Boolean> disablePropertyChangeListener;
private final Navigation navigation;
private final Class<? extends View> viewClass;
public MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
this.navigation = navigation;
this.viewClass = viewClass;
setToggleGroup(toggleGroup);
setText(title);
setId("account-settings-item-background-active");
setPrefHeight(40);
setPrefWidth(240);
setAlignment(Pos.CENTER_LEFT);
Label icon = new Label();
AwesomeDude.setIcon(icon, awesomeIcon);
icon.setTextFill(Paint.valueOf("#333"));
icon.setPadding(new Insets(0, 5, 0, 0));
icon.setAlignment(Pos.CENTER);
icon.setMinWidth(25);
icon.setMaxWidth(25);
setGraphic(icon);
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-selected");
icon.setTextFill(Colors.BLUE);
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-disabled");
icon.setTextFill(Paint.valueOf("#ccc"));
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
}
public void activate() {
//noinspection unchecked
setOnAction((event) -> navigation.navigateTo(MainView.class, DaoView.class, ProposalView.class, viewClass));
selectedProperty().addListener(selectedPropertyChangeListener);
disableProperty().addListener(disablePropertyChangeListener);
}
public void deactivate() {
setOnAction(null);
selectedProperty().removeListener(selectedPropertyChangeListener);
disableProperty().removeListener(disablePropertyChangeListener);
}
}

View File

@ -100,14 +100,14 @@ public class SeparatedPhaseBars extends HBox {
}
private void addLabels() {
Label titleLabel = new Label(Res.get("dao.compensation.active.phase"));
Label titleLabel = new Label(Res.get("dao.proposal.active.phase"));
Label startLabel = new Label(Res.get("dao.compensation.active.startBlock"));
Label startLabel = new Label(Res.get("dao.proposal.active.startBlock"));
AnchorPane startLabelPane = new AnchorPane();
AnchorPane.setLeftAnchor(startLabel, 0d);
startLabelPane.getChildren().add(startLabel);
Label endLabel = new Label(Res.get("dao.compensation.active.endBlock"));
Label endLabel = new Label(Res.get("dao.proposal.active.endBlock"));
AnchorPane endLabelPane = new AnchorPane();
AnchorPane.setRightAnchor(endLabel, 0d);
endLabelPane.getChildren().add(endLabel);

View File

@ -25,7 +25,7 @@ import bisq.desktop.common.view.FxmlView;
import bisq.desktop.common.view.View;
import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.compensation.CompensationView;
import bisq.desktop.main.dao.proposal.ProposalView;
import bisq.desktop.main.dao.voting.VotingView;
import bisq.desktop.main.dao.wallet.BsqWalletView;
import bisq.desktop.main.dao.wallet.dashboard.BsqDashboardView;
@ -67,7 +67,7 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
@Override
public void initialize() {
compensationTab = new Tab(Res.get("dao.tab.compensation"));
compensationTab = new Tab(Res.get("dao.tab.proposals"));
votingTab = new Tab(Res.get("dao.tab.voting"));
compensationTab.setClosable(false);
votingTab.setClosable(false);
@ -101,7 +101,7 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, selectedViewClass);
} else if (newValue == compensationTab) {
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, CompensationView.class);
navigation.navigateTo(MainView.class, DaoView.class, ProposalView.class);
} else if (newValue == votingTab) {
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class);
@ -121,7 +121,7 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class);
else if (selectedItem == compensationTab)
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, CompensationView.class);
navigation.navigateTo(MainView.class, DaoView.class, ProposalView.class);
else if (selectedItem == votingTab)
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class);
@ -139,7 +139,7 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
if (view instanceof BsqWalletView) {
selectedTab = bsqWalletTab;
bsqWalletView = (BsqWalletView) view;
} else if (view instanceof CompensationView) {
} else if (view instanceof ProposalView) {
selectedTab = compensationTab;
} else if (view instanceof VotingView) {
selectedTab = votingTab;

View File

@ -1,349 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TableGroupHeadline;
import bisq.desktop.components.TxIdTextField;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqAddressValidator;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.request.compensation.CompensationRequestPayload;
import bisq.core.dao.request.compensation.consensus.Restrictions;
import bisq.core.provider.fee.FeeService;
import bisq.common.locale.Res;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.util.Callback;
import java.util.UUID;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.*;
public class CompensationRequestDisplay {
private final GridPane gridPane;
private BsqFormatter bsqFormatter;
private BsqWalletService bsqWalletService;
public InputTextField uidTextField, nameTextField, titleTextField, linkInputTextField,
requestedBsqTextField, bsqAddressTextField;
private int gridRow = 0;
public TextArea descriptionTextArea;
private HyperlinkWithIcon linkHyperlinkWithIcon;
public TxIdTextField txIdTextField;
private FeeService feeService;
public CompensationRequestDisplay(GridPane gridPane, BsqFormatter bsqFormatter, BsqWalletService bsqWalletService, @Nullable FeeService feeService) {
this.gridPane = gridPane;
this.bsqFormatter = bsqFormatter;
this.bsqWalletService = bsqWalletService;
this.feeService = feeService;
}
public void createAllFields(String title, double top) {
addTitledGroupBg(gridPane, gridRow, 8, title, top);
uidTextField = addLabelInputTextField(gridPane, gridRow, Res.getWithCol("shared.id"), top == Layout.GROUP_DISTANCE ? Layout.FIRST_ROW_AND_GROUP_DISTANCE : Layout.FIRST_ROW_DISTANCE).second;
uidTextField.setEditable(false);
nameTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.compensation.display.name")).second;
titleTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.compensation.display.title")).second;
descriptionTextArea = addLabelTextArea(gridPane, ++gridRow, Res.get("dao.compensation.display.description"), Res.get("dao.compensation.display.description.prompt")).second;
linkInputTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.compensation.display.link")).second;
linkHyperlinkWithIcon = addLabelHyperlinkWithIcon(gridPane, gridRow, Res.get("dao.compensation.display.link"), "", "").second;
linkHyperlinkWithIcon.setVisible(false);
linkHyperlinkWithIcon.setManaged(false);
linkInputTextField.setPromptText(Res.get("dao.compensation.display.link.prompt"));
requestedBsqTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.compensation.display.requestedBsq")).second;
if (feeService != null) {
BsqValidator bsqValidator = new BsqValidator(bsqFormatter);
//TODO should we use the BSQ or a BTC validator? Technically it is BTC at that stage...
//bsqValidator.setMinValue(feeService.getCreateCompensationRequestFee());
bsqValidator.setMinValue(Restrictions.getMinCompensationRequestAmount());
requestedBsqTextField.setValidator(bsqValidator);
}
// TODO validator, addressTF
bsqAddressTextField = addLabelInputTextField(gridPane, ++gridRow,
Res.get("dao.compensation.display.bsqAddress")).second;
bsqAddressTextField.setText("B" + bsqWalletService.getUnusedAddress().toBase58());
bsqAddressTextField.setValidator(new BsqAddressValidator(bsqFormatter));
txIdTextField = addLabelTxIdTextField(gridPane, ++gridRow,
Res.get("dao.compensation.display.txId"), "").second;
}
public void fillWithData(CompensationRequestPayload data) {
uidTextField.setText(data.getUid());
nameTextField.setText(data.getName());
titleTextField.setText(data.getTitle());
descriptionTextArea.setText(data.getDescription());
linkInputTextField.setVisible(false);
linkInputTextField.setManaged(false);
linkHyperlinkWithIcon.setVisible(true);
linkHyperlinkWithIcon.setManaged(true);
linkHyperlinkWithIcon.setText(data.getLink());
linkHyperlinkWithIcon.setOnAction(e -> GUIUtil.openWebPage(data.getLink()));
requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(data.getRequestedBsq()));
bsqAddressTextField.setText(data.getBsqAddress());
txIdTextField.setup(data.getTxId());
}
public void clearForm() {
uidTextField.clear();
nameTextField.clear();
titleTextField.clear();
descriptionTextArea.clear();
linkInputTextField.clear();
linkHyperlinkWithIcon.clear();
requestedBsqTextField.clear();
bsqAddressTextField.clear();
txIdTextField.cleanup();
}
public void fillWithMock() {
uidTextField.setText(UUID.randomUUID().toString());
nameTextField.setText("Manfred Karrer");
titleTextField.setText("Development work November 2017");
descriptionTextArea.setText("Development work");
linkInputTextField.setText("https://github.com/bisq-network/compensation/issues/12");
requestedBsqTextField.setText("14000");
bsqAddressTextField.setText("B" + bsqWalletService.getUnusedAddress().toBase58());
}
public void setAllFieldsEditable(boolean isEditable) {
nameTextField.setEditable(isEditable);
titleTextField.setEditable(isEditable);
descriptionTextArea.setEditable(isEditable);
linkInputTextField.setEditable(isEditable);
requestedBsqTextField.setEditable(isEditable);
bsqAddressTextField.setEditable(isEditable);
linkInputTextField.setVisible(true);
linkInputTextField.setManaged(true);
linkHyperlinkWithIcon.setVisible(false);
linkHyperlinkWithIcon.setManaged(false);
linkHyperlinkWithIcon.setOnAction(null);
}
public void removeAllFields() {
gridPane.getChildren().clear();
gridRow = 0;
}
public int incrementAndGetGridRow() {
return ++gridRow;
}
public GridPane createCompensationList(TableView<CompensationRequestListItem> tableView, String headerString) {
GridPane compensationList = new GridPane();
TableGroupHeadline header = new TableGroupHeadline(headerString);
GridPane.setMargin(header, new Insets(10, 0, 0, 0));
GridPane.setRowIndex(header, 0);
compensationList.getChildren().add(header);
header.setMinHeight(20);
header.setMaxHeight(20);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
tableView.setMinHeight(90);
GridPane.setRowIndex(tableView, 1);
GridPane.setColumnSpan(tableView, 1);
GridPane.setMargin(tableView, new Insets(5, 10, -10, 10));
GridPane.setVgrow(tableView, Priority.ALWAYS);
GridPane.setHgrow(tableView, Priority.ALWAYS);
compensationList.getChildren().add(tableView);
createColumns(tableView);
return compensationList;
}
public ScrollPane createCompensationRequestDisplay() {
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
scrollPane.setMinHeight(100);
AnchorPane bottomAnchorPane = new AnchorPane();
scrollPane.setContent(bottomAnchorPane);
gridPane.setHgap(5);
gridPane.setVgap(5);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setHalignment(HPos.RIGHT);
columnConstraints1.setHgrow(Priority.SOMETIMES);
columnConstraints1.setMinWidth(140);
ColumnConstraints columnConstraints2 = new ColumnConstraints();
columnConstraints2.setHgrow(Priority.ALWAYS);
columnConstraints2.setMinWidth(300);
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
AnchorPane.setBottomAnchor(gridPane, 20d);
AnchorPane.setRightAnchor(gridPane, 10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
AnchorPane.setTopAnchor(gridPane, -20d);
bottomAnchorPane.getChildren().add(gridPane);
return scrollPane;
}
public SplitPane createCompensationRequestPane(TableView<CompensationRequestListItem> tableView, String headerString) {
SplitPane compensationRequestPane = new SplitPane();
compensationRequestPane.setOrientation(Orientation.VERTICAL);
compensationRequestPane.setDividerPositions(0.2, 0.7);
compensationRequestPane.setStyle("-fx-padding: 0; -fx-box-border: transparent;");
compensationRequestPane.getItems().add(createCompensationList(tableView, headerString));
compensationRequestPane.getItems().add(createCompensationRequestDisplay());
return compensationRequestPane;
}
private void createColumns(TableView<CompensationRequestListItem> tableView) {
TableColumn<CompensationRequestListItem, CompensationRequestListItem> dateColumn = new AutoTooltipTableColumn<CompensationRequestListItem, CompensationRequestListItem>(Res.get("shared.dateTime")) {
{
setMinWidth(190);
setMaxWidth(190);
}
};
dateColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<CompensationRequestListItem, CompensationRequestListItem>, TableCell<CompensationRequestListItem,
CompensationRequestListItem>>() {
@Override
public TableCell<CompensationRequestListItem, CompensationRequestListItem> call(
TableColumn<CompensationRequestListItem, CompensationRequestListItem> column) {
return new TableCell<CompensationRequestListItem, CompensationRequestListItem>() {
@Override
public void updateItem(final CompensationRequestListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(bsqFormatter.formatDateTime(item.getCompensationRequest().getPayload().getCreationDate()));
else
setText("");
}
};
}
});
dateColumn.setComparator((o1, o2) -> o1.getCompensationRequest().getPayload().getCreationDate().compareTo(o2.getCompensationRequest().getPayload().getCreationDate()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getColumns().add(dateColumn);
tableView.getSortOrder().add(dateColumn);
TableColumn<CompensationRequestListItem, CompensationRequestListItem> nameColumn = new AutoTooltipTableColumn<>(Res.get("shared.name"));
nameColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
nameColumn.setCellFactory(
new Callback<TableColumn<CompensationRequestListItem, CompensationRequestListItem>, TableCell<CompensationRequestListItem,
CompensationRequestListItem>>() {
@Override
public TableCell<CompensationRequestListItem, CompensationRequestListItem> call(
TableColumn<CompensationRequestListItem, CompensationRequestListItem> column) {
return new TableCell<CompensationRequestListItem, CompensationRequestListItem>() {
@Override
public void updateItem(final CompensationRequestListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getCompensationRequest().getPayload().getName());
else
setText("");
}
};
}
});
nameColumn.setComparator((o1, o2) -> o1.getCompensationRequest().getPayload().getName().compareTo(o2.getCompensationRequest().getPayload().getName()));
tableView.getColumns().add(nameColumn);
TableColumn<CompensationRequestListItem, CompensationRequestListItem> uidColumn = new AutoTooltipTableColumn<>(Res.get("shared.id"));
uidColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
uidColumn.setCellFactory(
new Callback<TableColumn<CompensationRequestListItem, CompensationRequestListItem>, TableCell<CompensationRequestListItem,
CompensationRequestListItem>>() {
@Override
public TableCell<CompensationRequestListItem, CompensationRequestListItem> call(
TableColumn<CompensationRequestListItem, CompensationRequestListItem> column) {
return new TableCell<CompensationRequestListItem, CompensationRequestListItem>() {
@Override
public void updateItem(final CompensationRequestListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getCompensationRequest().getPayload().getUid());
else
setText("");
}
};
}
});
uidColumn.setComparator((o1, o2) -> o1.getCompensationRequest().getPayload().getUid().compareTo(o2.getCompensationRequest().getPayload().getUid()));
tableView.getColumns().add(uidColumn);
TableColumn<CompensationRequestListItem, CompensationRequestListItem> confidenceColumn = new TableColumn<>(Res.get("shared.confirmations"));
confidenceColumn.setMinWidth(130);
confidenceColumn.setMaxWidth(confidenceColumn.getMinWidth());
confidenceColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
confidenceColumn.setCellFactory(new Callback<TableColumn<CompensationRequestListItem, CompensationRequestListItem>,
TableCell<CompensationRequestListItem, CompensationRequestListItem>>() {
@Override
public TableCell<CompensationRequestListItem, CompensationRequestListItem> call(TableColumn<CompensationRequestListItem,
CompensationRequestListItem> column) {
return new TableCell<CompensationRequestListItem, CompensationRequestListItem>() {
@Override
public void updateItem(final CompensationRequestListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getTxConfidenceIndicator());
} else {
setGraphic(null);
}
}
};
}
});
confidenceColumn.setComparator((o1, o2) -> o1.getConfirmations().compareTo(o2.getConfirmations()));
tableView.getColumns().add(confidenceColumn);
}
}

View File

@ -1,173 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.blockchain.vo.Tx;
import bisq.core.dao.request.compensation.CompensationRequest;
import bisq.common.locale.Res;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import javafx.scene.control.Tooltip;
import javafx.beans.value.ChangeListener;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@ToString
@Slf4j
@EqualsAndHashCode
public class CompensationRequestListItem implements BsqBlockChainListener {
@Getter
private final CompensationRequest compensationRequest;
private final BsqWalletService bsqWalletService;
private final BsqBlockChain bsqBlockChain;
private final BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher;
private final BsqFormatter bsqFormatter;
private final ChangeListener<Number> chainHeightListener;
@Getter
private TxConfidenceIndicator txConfidenceIndicator;
@Getter
private Integer confirmations = 0;
private TxConfidenceListener txConfidenceListener;
private Tooltip tooltip = new Tooltip(Res.get("confidence.unknown"));
private Transaction walletTransaction;
public CompensationRequestListItem(CompensationRequest compensationRequest,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
BsqFormatter bsqFormatter) {
this.compensationRequest = compensationRequest;
this.bsqWalletService = bsqWalletService;
this.bsqBlockChain = bsqBlockChain;
this.bsqBlockChainChangeDispatcher = bsqBlockChainChangeDispatcher;
this.bsqFormatter = bsqFormatter;
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
txConfidenceIndicator.setProgress(-1);
txConfidenceIndicator.setPrefSize(24, 24);
txConfidenceIndicator.setTooltip(tooltip);
chainHeightListener = (observable, oldValue, newValue) -> setupConfidence();
bsqWalletService.getChainHeightProperty().addListener(chainHeightListener);
setupConfidence();
bsqBlockChainChangeDispatcher.addBsqBlockChainListener(this);
}
@Override
public void onBsqBlockChainChanged() {
setupConfidence();
}
private void setupConfidence() {
final Tx tx = bsqBlockChain.getTxMap().get(compensationRequest.getPayload().getTxId());
if (tx != null) {
final String txId = tx.getId();
// We cache the walletTransaction once found
if (walletTransaction == null) {
final Optional<Transaction> transactionOptional = bsqWalletService.isWalletTransaction(txId);
if (transactionOptional.isPresent())
walletTransaction = transactionOptional.get();
}
if (walletTransaction != null) {
// It is our tx so we get confidence updates
if (txConfidenceListener == null) {
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence.getConfidenceType(), confidence.getDepthInBlocks(), confidence.numBroadcastPeers());
}
};
bsqWalletService.addTxConfidenceListener(txConfidenceListener);
}
} else {
// tx from other users, we dont get confidence updates but as we have the bsq tx we can calculate it
// we get setupConfidence called at each new block from above listener so no need to register a new listener
int depth = bsqWalletService.getChainHeightProperty().get() - tx.getBlockHeight() + 1;
if (depth > 0)
updateConfidence(TransactionConfidence.ConfidenceType.BUILDING, depth, -1);
//log.error("name={}, id ={}, depth={}", compensationRequest.getPayload().getName(), compensationRequest.getPayload().getUid(), depth);
}
final TransactionConfidence confidence = bsqWalletService.getConfidenceForTxId(txId);
if (confidence != null)
updateConfidence(confidence, confidence.getDepthInBlocks());
}
}
private void updateConfidence(TransactionConfidence confidence, int depthInBlocks) {
if (confidence != null) {
updateConfidence(confidence.getConfidenceType(), confidence.getDepthInBlocks(), confidence.numBroadcastPeers());
confirmations = depthInBlocks;
}
}
public void cleanup() {
bsqBlockChainChangeDispatcher.removeBsqBlockChainListener(this);
bsqWalletService.getChainHeightProperty().removeListener(chainHeightListener);
if (txConfidenceListener != null)
bsqWalletService.removeTxConfidenceListener(txConfidenceListener);
}
public void updateConfidence(TransactionConfidence.ConfidenceType confidenceType, int depthInBlocks, int numBroadcastPeers) {
switch (confidenceType) {
case UNKNOWN:
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
break;
case PENDING:
tooltip.setText(Res.get("confidence.seen", numBroadcastPeers > -1 ? numBroadcastPeers : Res.get("shared.na")));
txConfidenceIndicator.setProgress(-1.0);
break;
case BUILDING:
tooltip.setText(Res.get("confidence.confirmed", depthInBlocks));
txConfidenceIndicator.setProgress(Math.min(1, (double) depthInBlocks / 6.0));
break;
case DEAD:
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
break;
}
txConfidenceIndicator.setPrefSize(24, 24);
}
}

View File

@ -1,157 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.request.compensation.CompensationRequest;
import bisq.core.dao.request.compensation.CompensationRequestManager;
import bisq.common.UserThread;
import bisq.common.locale.Res;
import javax.inject.Inject;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableView;
import javafx.scene.layout.GridPane;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import java.util.stream.Collectors;
@FxmlView
public abstract class CompensationRequestView extends ActivatableView<GridPane, Void> implements BsqBlockChainListener {
protected final CompensationRequestManager compensationRequestManger;
protected final BsqBlockChain bsqBlockChain;
protected final ObservableList<CompensationRequestListItem> observableList = FXCollections.observableArrayList();
protected TableView<CompensationRequestListItem> tableView;
protected final BsqWalletService bsqWalletService;
protected final BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher;
protected final BsqFormatter bsqFormatter;
protected SortedList<CompensationRequestListItem> sortedList = new SortedList<>(observableList);
protected Subscription selectedCompensationRequestSubscription;
protected CompensationRequestDisplay compensationRequestDisplay;
protected int gridRow = 0;
protected GridPane detailsGridPane, gridPane;
protected SplitPane compensationRequestPane;
protected CompensationRequestListItem selectedCompensationRequest;
protected ChangeListener<Number> chainHeightChangeListener;
protected ListChangeListener<CompensationRequest> compensationRequestListChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
protected CompensationRequestView(CompensationRequestManager compensationRequestManger,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
BsqFormatter bsqFormatter) {
this.compensationRequestManger = compensationRequestManger;
this.bsqWalletService = bsqWalletService;
this.bsqBlockChain = bsqBlockChain;
this.bsqBlockChainChangeDispatcher = bsqBlockChainChangeDispatcher;
this.bsqFormatter = bsqFormatter;
}
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
selectedCompensationRequestSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectCompensationRequest);
bsqWalletService.getChainHeightProperty().addListener(chainHeightChangeListener);
bsqBlockChainChangeDispatcher.addBsqBlockChainListener(this);
compensationRequestManger.getAllRequests().addListener(compensationRequestListChangeListener);
updateList();
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
selectedCompensationRequestSubscription.unsubscribe();
bsqWalletService.getChainHeightProperty().removeListener(chainHeightChangeListener);
bsqBlockChainChangeDispatcher.removeBsqBlockChainListener(this);
compensationRequestManger.getAllRequests().removeListener(compensationRequestListChangeListener);
observableList.forEach(CompensationRequestListItem::cleanup);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onBsqBlockChainChanged() {
// Need delay otherwise we modify list while dispatching and cause a ConcurrentModificationException
UserThread.execute(this::updateList);
}
abstract protected void updateList();
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
protected void doUpdateList(FilteredList<CompensationRequest> list) {
observableList.forEach(CompensationRequestListItem::cleanup);
observableList.setAll(list.stream()
.map(e -> new CompensationRequestListItem(e, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, bsqFormatter))
.collect(Collectors.toSet()));
if (list.isEmpty() && compensationRequestDisplay != null)
compensationRequestDisplay.removeAllFields();
}
protected void onSelectCompensationRequest(CompensationRequestListItem item) {
selectedCompensationRequest = item;
if (item != null) {
final CompensationRequest compensationRequest = item.getCompensationRequest();
compensationRequestDisplay.removeAllFields();
compensationRequestDisplay.createAllFields(Res.get("dao.compensation.selectedRequest"), Layout.GROUP_DISTANCE);
compensationRequestDisplay.setAllFieldsEditable(false);
compensationRequestDisplay.fillWithData(compensationRequest.getPayload());
}
}
}

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.compensation.CompensationView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
AnchorPane.topAnchor="30"/>
</AnchorPane>

View File

@ -1,200 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.common.view.CachingViewLoader;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.common.view.View;
import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.common.view.ViewPath;
import bisq.desktop.components.AutoTooltipToggleButton;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.compensation.active.ActiveCompensationRequestView;
import bisq.desktop.main.dao.compensation.create.CreateCompensationRequestView;
import bisq.desktop.main.dao.compensation.past.PastCompensationRequestView;
import bisq.desktop.util.Colors;
import bisq.common.locale.Res;
import javax.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Paint;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.beans.value.ChangeListener;
@FxmlView
public class CompensationView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem create, active, past;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
@Inject
private CompensationView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = viewPath -> {
if (viewPath.size() != 4 || viewPath.indexOf(CompensationView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
ToggleGroup toggleGroup = new ToggleGroup();
create = new MenuItem(navigation, toggleGroup, Res.get("dao.compensation.menuItem.createRequest"), CreateCompensationRequestView.class, AwesomeIcon.EDIT);
active = new MenuItem(navigation, toggleGroup, Res.get("dao.compensation.menuItem.activeRequests"), ActiveCompensationRequestView.class, AwesomeIcon.ARROW_RIGHT);
past = new MenuItem(navigation, toggleGroup, Res.get("dao.compensation.menuItem.pastRequests"), PastCompensationRequestView.class, AwesomeIcon.LIST);
leftVBox.getChildren().addAll(create, active, past);
}
@Override
protected void activate() {
create.activate();
active.activate();
past.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(CompensationView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = CreateCompensationRequestView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(CompensationView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
create.deactivate();
active.deactivate();
past.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof CreateCompensationRequestView) create.setSelected(true);
else if (view instanceof ActiveCompensationRequestView) active.setSelected(true);
else if (view instanceof PastCompensationRequestView) past.setSelected(true);
}
public Class<? extends View> getSelectedViewClass() {
return selectedViewClass;
}
}
class MenuItem extends AutoTooltipToggleButton {
private final ChangeListener<Boolean> selectedPropertyChangeListener;
private final ChangeListener<Boolean> disablePropertyChangeListener;
private final Navigation navigation;
private final Class<? extends View> viewClass;
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
this.navigation = navigation;
this.viewClass = viewClass;
setToggleGroup(toggleGroup);
setText(title);
setId("account-settings-item-background-active");
setPrefHeight(40);
setPrefWidth(240);
setAlignment(Pos.CENTER_LEFT);
Label icon = new Label();
AwesomeDude.setIcon(icon, awesomeIcon);
icon.setTextFill(Paint.valueOf("#333"));
icon.setPadding(new Insets(0, 5, 0, 0));
icon.setAlignment(Pos.CENTER);
icon.setMinWidth(25);
icon.setMaxWidth(25);
setGraphic(icon);
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-selected");
icon.setTextFill(Colors.BLUE);
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-disabled");
icon.setTextFill(Paint.valueOf("#ccc"));
} else {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
};
}
public void activate() {
//noinspection unchecked
setOnAction((event) -> navigation.navigateTo(MainView.class, DaoView.class, CompensationView.class, viewClass));
selectedProperty().addListener(selectedPropertyChangeListener);
disableProperty().addListener(disablePropertyChangeListener);
}
public void deactivate() {
setOnAction(null);
selectedProperty().removeListener(selectedPropertyChangeListener);
disableProperty().removeListener(disablePropertyChangeListener);
}
}

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.compensation.active.ActiveCompensationRequestView"
AnchorPane.bottomAnchor="-20.0" AnchorPane.leftAnchor="-10.0"
AnchorPane.rightAnchor="-10.0" AnchorPane.topAnchor="-20.0"
xmlns:fx="http://javafx.com/fxml">
</GridPane>

View File

@ -1,279 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation.active;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.SeparatedPhaseBars;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.compensation.CompensationRequestDisplay;
import bisq.desktop.main.dao.compensation.CompensationRequestListItem;
import bisq.desktop.main.dao.compensation.CompensationRequestView;
import bisq.desktop.main.dao.voting.VotingView;
import bisq.desktop.main.dao.voting.vote.VoteView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.request.compensation.CompensationRequest;
import bisq.core.dao.request.compensation.CompensationRequestManager;
import bisq.core.provider.fee.FeeService;
import bisq.common.locale.Res;
import javax.inject.Inject;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.geometry.Insets;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javafx.beans.value.ChangeListener;
import java.util.Arrays;
import java.util.List;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class ActiveCompensationRequestView extends CompensationRequestView implements BsqBlockChainListener {
private List<SeparatedPhaseBars.SeparatedPhaseBarsItem> phaseBarsItems;
private Button removeButton, voteButton;
private final Navigation navigation;
private final DaoPeriodService daoPeriodService;
private DaoPeriodService.Phase currentPhase;
private ChangeListener<DaoPeriodService.Phase> phaseChangeListener;
private Subscription phaseSubscription;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ActiveCompensationRequestView(CompensationRequestManager compensationRequestManger,
DaoPeriodService daoPeriodService,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
FeeService feeService,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
Navigation navigation,
BsqFormatter bsqFormatter) {
super(compensationRequestManger, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, bsqFormatter);
this.daoPeriodService = daoPeriodService;
this.navigation = navigation;
}
@Override
public void initialize() {
root.getStyleClass().add("compensation-root");
AnchorPane topAnchorPane = new AnchorPane();
root.getChildren().add(topAnchorPane);
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
AnchorPane.setBottomAnchor(gridPane, 10d);
AnchorPane.setRightAnchor(gridPane, 10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
AnchorPane.setTopAnchor(gridPane, 10d);
topAnchorPane.getChildren().add(gridPane);
// Add phase info
addTitledGroupBg(gridPane, gridRow, 1, Res.get("dao.compensation.active.phase.header"));
SeparatedPhaseBars separatedPhaseBars = createSeparatedPhaseBars();
GridPane.setColumnSpan(separatedPhaseBars, 2);
GridPane.setColumnIndex(separatedPhaseBars, 0);
GridPane.setMargin(separatedPhaseBars, new Insets(Layout.FIRST_ROW_DISTANCE - 6, 0, 0, 0));
GridPane.setRowIndex(separatedPhaseBars, gridRow);
gridPane.getChildren().add(separatedPhaseBars);
/* final Tuple2<Label, TextField> tuple2 = addLabelTextField(gridPane, ++gridRow, Res.get("dao.compensation.active.cycle"));
final Label label = tuple2.first;
GridPane.setHalignment(label, HPos.RIGHT);
cycleTextField = tuple2.second;*/
// Add compensationrequest pane
tableView = new TableView<>();
detailsGridPane = new GridPane();
compensationRequestDisplay = new CompensationRequestDisplay(detailsGridPane, bsqFormatter, bsqWalletService, null);
compensationRequestPane = compensationRequestDisplay.createCompensationRequestPane(tableView, Res.get("dao.compensation.active.header"));
GridPane.setColumnSpan(compensationRequestPane, 2);
GridPane.setMargin(compensationRequestPane, new Insets(Layout.FIRST_ROW_DISTANCE - 6, -10, 0, -10));
GridPane.setRowIndex(compensationRequestPane, ++gridRow);
gridPane.getChildren().add(compensationRequestPane);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
chainHeightChangeListener = (observable, oldValue, newValue) -> {
onChainHeightChanged((int) newValue);
};
compensationRequestListChangeListener = c -> updateList();
phaseChangeListener = (observable, oldValue, newValue) -> onPhaseChanged(newValue);
}
private SeparatedPhaseBars createSeparatedPhaseBars() {
phaseBarsItems = Arrays.asList(
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.COMPENSATION_REQUESTS, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.BREAK1, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.OPEN_FOR_VOTING, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.BREAK2, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.VOTE_CONFIRMATION, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.BREAK3, false));
SeparatedPhaseBars separatedPhaseBars = new SeparatedPhaseBars(phaseBarsItems);
return separatedPhaseBars;
}
@Override
protected void activate() {
super.activate();
phaseSubscription = EasyBind.subscribe(daoPeriodService.getPhaseProperty(), phase -> {
if (!phase.equals(this.currentPhase)) {
this.currentPhase = phase;
onSelectCompensationRequest(selectedCompensationRequest);
}
phaseBarsItems.stream().forEach(item -> {
if (item.getPhase() == phase) {
item.setActive();
} else {
item.setInActive();
}
});
});
daoPeriodService.getPhaseProperty().addListener(phaseChangeListener);
onChainHeightChanged(bsqWalletService.getChainHeightProperty().get());
}
@Override
protected void deactivate() {
super.deactivate();
phaseSubscription.unsubscribe();
daoPeriodService.getPhaseProperty().removeListener(phaseChangeListener);
}
@Override
protected void updateList() {
doUpdateList(compensationRequestManger.getActiveRequests());
}
private void onChainHeightChanged(int height) {
phaseBarsItems.stream().forEach(item -> {
int startBlock = daoPeriodService.getAbsoluteStartBlockOfPhase(height, item.getPhase());
int endBlock = daoPeriodService.getAbsoluteEndBlockOfPhase(height, item.getPhase());
item.setStartAndEnd(startBlock, endBlock);
double progress = 0;
if (height >= startBlock && height <= endBlock) {
progress = (double) (height - startBlock + 1) / (double) item.getPhase().getDurationInBlocks();
} else if (height < startBlock) {
progress = 0;
} else if (height > endBlock) {
progress = 1;
}
item.getProgressProperty().set(progress);
});
}
protected void onSelectCompensationRequest(CompensationRequestListItem item) {
super.onSelectCompensationRequest(item);
if (item != null) {
if (removeButton != null) {
removeButton.setManaged(false);
removeButton.setVisible(false);
removeButton = null;
}
if (voteButton != null) {
voteButton.setManaged(false);
voteButton.setVisible(false);
voteButton = null;
}
onPhaseChanged(daoPeriodService.getPhaseProperty().get());
}
}
protected void onPhaseChanged(DaoPeriodService.Phase phase) {
if (removeButton != null) {
removeButton.setManaged(false);
removeButton.setVisible(false);
removeButton = null;
}
if (selectedCompensationRequest != null && compensationRequestDisplay != null) {
final CompensationRequest compensationRequest = selectedCompensationRequest.getCompensationRequest();
switch (phase) {
case COMPENSATION_REQUESTS:
if (compensationRequestManger.isMine(compensationRequest)) {
if (removeButton == null) {
removeButton = addButtonAfterGroup(detailsGridPane, compensationRequestDisplay.incrementAndGetGridRow(), Res.get("dao.compensation.active.remove"));
removeButton.setOnAction(event -> {
if (compensationRequestManger.removeCompensationRequest(compensationRequest))
compensationRequestDisplay.removeAllFields();
else
new Popup<>().warning(Res.get("dao.compensation.active.remove.failed")).show();
});
} else {
removeButton.setManaged(true);
removeButton.setVisible(true);
}
}
break;
case BREAK1:
break;
case OPEN_FOR_VOTING:
if (voteButton == null) {
voteButton = addButtonAfterGroup(detailsGridPane, compensationRequestDisplay.incrementAndGetGridRow(), Res.get("dao.compensation.active.vote"));
voteButton.setOnAction(event -> {
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class, VoteView.class);
});
} else {
voteButton.setManaged(true);
voteButton.setVisible(true);
}
break;
case BREAK2:
break;
case VOTE_CONFIRMATION:
//TODO
log.warn("VOTE_CONFIRMATION");
break;
case BREAK3:
break;
case UNDEFINED:
default:
log.warn("Undefined phase: " + daoPeriodService.getPhaseProperty());
break;
}
}
}
}

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.compensation.create.CreateCompensationRequestView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View File

@ -1,193 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation.create;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.dao.compensation.CompensationRequestDisplay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.BSFormatter;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.InsufficientBsqException;
import bisq.core.btc.wallet.WalletsSetup;
import bisq.core.dao.request.compensation.CompensationAmountException;
import bisq.core.dao.request.compensation.CompensationRequest;
import bisq.core.dao.request.compensation.CompensationRequestManager;
import bisq.core.dao.request.compensation.CompensationRequestPayload;
import bisq.core.provider.fee.FeeService;
import bisq.core.util.CoinUtil;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
import bisq.common.crypto.KeyRing;
import bisq.common.locale.Res;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import java.security.PublicKey;
import java.util.Date;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static com.google.common.base.Preconditions.checkNotNull;
@FxmlView
public class CreateCompensationRequestView extends ActivatableView<GridPane, Void> {
private CompensationRequestDisplay compensationRequestDisplay;
private Button createButton;
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final FeeService feeService;
private final CompensationRequestManager compensationRequestManager;
private final BSFormatter btcFormatter;
private final BsqFormatter bsqFormatter;
private final PublicKey p2pStorageSignaturePubKey;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private CreateCompensationRequestView(BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
WalletsSetup walletsSetup,
P2PService p2PService,
FeeService feeService,
CompensationRequestManager compensationRequestManager,
KeyRing keyRing,
BSFormatter btcFormatter,
BsqFormatter bsqFormatter) {
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.feeService = feeService;
this.compensationRequestManager = compensationRequestManager;
this.btcFormatter = btcFormatter;
this.bsqFormatter = bsqFormatter;
p2pStorageSignaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();
}
@Override
public void initialize() {
compensationRequestDisplay = new CompensationRequestDisplay(root, bsqFormatter, bsqWalletService, feeService);
compensationRequestDisplay.createAllFields(Res.get("dao.compensation.create.createNew"), 0);
createButton = addButtonAfterGroup(root, compensationRequestDisplay.incrementAndGetGridRow(), Res.get("dao.compensation.create.create.button"));
}
@Override
protected void activate() {
compensationRequestDisplay.fillWithMock();
createButton.setOnAction(event -> {
// TODO break up in methods
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
NodeAddress nodeAddress = p2PService.getAddress();
checkNotNull(nodeAddress, "nodeAddress must not be null");
CompensationRequestPayload compensationRequestPayload = new CompensationRequestPayload(
UUID.randomUUID().toString(),
compensationRequestDisplay.nameTextField.getText(),
compensationRequestDisplay.titleTextField.getText(),
compensationRequestDisplay.descriptionTextArea.getText(),
compensationRequestDisplay.linkInputTextField.getText(),
bsqFormatter.parseToCoin(compensationRequestDisplay.requestedBsqTextField.getText()),
compensationRequestDisplay.bsqAddressTextField.getText(),
nodeAddress,
p2pStorageSignaturePubKey,
new Date()
);
try {
CompensationRequest compensationRequest = compensationRequestManager.prepareCompensationRequest(compensationRequestPayload);
Coin miningFee = compensationRequest.getTx().getFee();
int txSize = compensationRequest.getTx().bitcoinSerialize().length;
new Popup<>().headLine(Res.get("dao.compensation.create.confirm"))
.confirmation(Res.get("dao.compensation.create.confirm.info",
bsqFormatter.formatCoinWithCode(compensationRequest.getRequestedBsq()),
bsqFormatter.formatCoinWithCode(compensationRequest.getCompensationRequestFee()),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
(txSize / 1000d)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
compensationRequestManager.commitCompensationRequest(compensationRequest, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {
compensationRequestDisplay.clearForm();
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error(t.toString());
new Popup<>().warning(t.toString()).show();
}
});
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (InsufficientMoneyException e) {
BSFormatter formatter = e instanceof InsufficientBsqException ? bsqFormatter : btcFormatter;
new Popup<>().warning(Res.get("dao.compensation.create.missingFunds", formatter.formatCoinWithCode(e.missing))).show();
} catch (CompensationAmountException e) {
new Popup<>().warning(Res.get("validation.bsq.amountBelowMinAmount", bsqFormatter.formatCoinWithCode(e.required))).show();
} catch (TransactionVerificationException | WalletException e) {
log.error(e.toString());
e.printStackTrace();
new Popup<>().warning(e.toString()).show();
}
} else {
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
}
});
}
@Override
protected void deactivate() {
createButton.setOnAction(null);
}
}

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.compensation.past.PastCompensationRequestView"
AnchorPane.bottomAnchor="-20.0" AnchorPane.leftAnchor="-10.0"
AnchorPane.rightAnchor="-10.0" AnchorPane.topAnchor="-20.0"
xmlns:fx="http://javafx.com/fxml">
</GridPane>

View File

@ -1,107 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.compensation.past;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.SeparatedPhaseBars;
import bisq.desktop.main.dao.compensation.CompensationRequestDisplay;
import bisq.desktop.main.dao.compensation.CompensationRequestView;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.request.compensation.CompensationRequestManager;
import bisq.common.locale.Res;
import javax.inject.Inject;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.geometry.Insets;
import java.util.List;
@FxmlView
public class PastCompensationRequestView extends CompensationRequestView implements BsqBlockChainListener {
private List<SeparatedPhaseBars.SeparatedPhaseBarsItem> phaseBarsItems;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private PastCompensationRequestView(CompensationRequestManager compensationRequestManger,
DaoPeriodService daoPeriodService,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
BsqFormatter bsqFormatter) {
super(compensationRequestManger, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, bsqFormatter);
}
@Override
public void initialize() {
root.getStyleClass().add("compensation-root");
AnchorPane topAnchorPane = new AnchorPane();
root.getChildren().add(topAnchorPane);
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
AnchorPane.setBottomAnchor(gridPane, 10d);
AnchorPane.setRightAnchor(gridPane, 10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
AnchorPane.setTopAnchor(gridPane, 0d);
topAnchorPane.getChildren().add(gridPane);
// Add compensationrequest pane
tableView = new TableView<>();
detailsGridPane = new GridPane();
compensationRequestDisplay = new CompensationRequestDisplay(detailsGridPane, bsqFormatter, bsqWalletService, null);
compensationRequestPane = compensationRequestDisplay.createCompensationRequestPane(tableView, Res.get("dao.compensation.past.header"));
compensationRequestPane.setMinWidth(800);
GridPane.setColumnSpan(compensationRequestPane, 2);
GridPane.setColumnIndex(compensationRequestPane, 0);
GridPane.setMargin(compensationRequestPane, new Insets(0, -10, 0, -10));
GridPane.setRowIndex(compensationRequestPane, gridRow);
gridPane.getChildren().add(compensationRequestPane);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
compensationRequestListChangeListener = c -> updateList();
chainHeightChangeListener = (observable, oldValue, newValue) -> {
updateList();
};
}
@Override
protected void updateList() {
doUpdateList(compensationRequestManger.getPastRequests());
}
}

View File

@ -0,0 +1,327 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.TableGroupHeadline;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.proposal.Proposal;
import bisq.core.dao.proposal.ProposalCollectionsManager;
import bisq.common.UserThread;
import bisq.common.locale.Res;
import javax.inject.Inject;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.GridPane;
import javafx.geometry.Insets;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.stream.Collectors;
@FxmlView
public abstract class BaseProposalView extends ActivatableView<GridPane, Void> implements BsqBlockChainListener {
protected final ProposalCollectionsManager proposalCollectionsManager;
protected final BsqBlockChain bsqBlockChain;
protected final ObservableList<ProposalListItem> proposalListItems = FXCollections.observableArrayList();
protected TableView<ProposalListItem> tableView;
protected final BsqWalletService bsqWalletService;
protected final BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher;
protected final BsqFormatter bsqFormatter;
protected SortedList<ProposalListItem> sortedList = new SortedList<>(proposalListItems);
protected Subscription selectedProposalSubscription;
protected ProposalDisplay proposalDisplay;
protected int gridRow = 0;
protected GridPane detailsGridPane, gridPane;
protected ProposalListItem selectedProposalListItem;
protected ListChangeListener<Proposal> proposalListChangeListener;
protected ChangeListener<DaoPeriodService.Phase> phaseChangeListener;
protected final DaoPeriodService daoPeriodService;
protected DaoPeriodService.Phase currentPhase;
protected Subscription phaseSubscription;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
protected BaseProposalView(ProposalCollectionsManager proposalCollectionsManager,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
DaoPeriodService daoPeriodService,
BsqFormatter bsqFormatter) {
this.proposalCollectionsManager = proposalCollectionsManager;
this.bsqWalletService = bsqWalletService;
this.bsqBlockChain = bsqBlockChain;
this.bsqBlockChainChangeDispatcher = bsqBlockChainChangeDispatcher;
this.daoPeriodService = daoPeriodService;
this.bsqFormatter = bsqFormatter;
}
@Override
public void initialize() {
super.initialize();
root.getStyleClass().add("vote-root");
TableGroupHeadline headline = new TableGroupHeadline(Res.get("dao.proposal.active.header"));
GridPane.setRowIndex(headline, ++gridRow);
GridPane.setMargin(headline, new Insets(-10, -10, -10, -10));
root.getChildren().add(headline);
tableView = new TableView<>();
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setMinHeight(90);
createColumns(tableView);
GridPane.setRowIndex(tableView, gridRow);
GridPane.setMargin(tableView, new Insets(10, -10, 5, -10));
root.getChildren().add(tableView);
detailsGridPane = new GridPane();
proposalDisplay = new ProposalDisplay(detailsGridPane, bsqFormatter, bsqWalletService, null);
final ScrollPane proposalDisplayView = proposalDisplay.getView();
GridPane.setMargin(proposalDisplayView, new Insets(10, -10, 0, -10));
GridPane.setRowIndex(proposalDisplayView, ++gridRow);
root.getChildren().add(proposalDisplayView);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
proposalListChangeListener = c -> updateList();
phaseChangeListener = (observable, oldValue, newValue) -> onPhaseChanged(newValue);
}
@Override
protected void activate() {
phaseSubscription = EasyBind.subscribe(daoPeriodService.getPhaseProperty(), phase -> {
if (!phase.equals(this.currentPhase)) {
this.currentPhase = phase;
onSelectProposal(selectedProposalListItem);
}
});
daoPeriodService.getPhaseProperty().addListener(phaseChangeListener);
onPhaseChanged(daoPeriodService.getPhaseProperty().get());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
selectedProposalSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectProposal);
bsqBlockChainChangeDispatcher.addBsqBlockChainListener(this);
proposalCollectionsManager.getAllProposals().addListener(proposalListChangeListener);
updateList();
}
@Override
protected void deactivate() {
phaseSubscription.unsubscribe();
daoPeriodService.getPhaseProperty().removeListener(phaseChangeListener);
sortedList.comparatorProperty().unbind();
selectedProposalSubscription.unsubscribe();
bsqBlockChainChangeDispatcher.removeBsqBlockChainListener(this);
proposalCollectionsManager.getAllProposals().removeListener(proposalListChangeListener);
proposalListItems.forEach(ProposalListItem::cleanup);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onBsqBlockChainChanged() {
// Need delay otherwise we modify list while dispatching and cause a ConcurrentModificationException
UserThread.execute(this::updateList);
}
abstract protected void updateList();
protected void onPhaseChanged(DaoPeriodService.Phase phase) {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
protected void doUpdateList(FilteredList<Proposal> list) {
proposalListItems.forEach(ProposalListItem::cleanup);
proposalListItems.setAll(list.stream()
.map(e -> new ProposalListItem(e, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, bsqFormatter))
.collect(Collectors.toSet()));
if (list.isEmpty() && proposalDisplay != null)
proposalDisplay.removeAllFields();
}
protected void onSelectProposal(ProposalListItem item) {
selectedProposalListItem = item;
if (item != null) {
final Proposal proposal = item.getProposal();
proposalDisplay.removeAllFields();
proposalDisplay.createAllFields(Res.get("dao.proposal.selectedProposal"), 0, 0, item.getProposal().getType(), false);
proposalDisplay.setAllFieldsEditable(false);
proposalDisplay.fillWithData(proposal.getProposalPayload());
}
}
protected void createColumns(TableView<ProposalListItem> tableView) {
TableColumn<ProposalListItem, ProposalListItem> dateColumn = new AutoTooltipTableColumn<ProposalListItem, ProposalListItem>(Res.get("shared.dateTime")) {
{
setMinWidth(190);
setMaxWidth(190);
}
};
dateColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<ProposalListItem, ProposalListItem>, TableCell<ProposalListItem,
ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(
TableColumn<ProposalListItem, ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(bsqFormatter.formatDateTime(item.getProposal().getProposalPayload().getCreationDate()));
else
setText("");
}
};
}
});
dateColumn.setComparator(Comparator.comparing(o3 -> o3.getProposal().getProposalPayload().getCreationDate()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getColumns().add(dateColumn);
tableView.getSortOrder().add(dateColumn);
TableColumn<ProposalListItem, ProposalListItem> nameColumn = new AutoTooltipTableColumn<>(Res.get("shared.name"));
nameColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
nameColumn.setCellFactory(
new Callback<TableColumn<ProposalListItem, ProposalListItem>, TableCell<ProposalListItem,
ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(
TableColumn<ProposalListItem, ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getProposal().getProposalPayload().getName());
else
setText("");
}
};
}
});
nameColumn.setComparator(Comparator.comparing(o2 -> o2.getProposal().getProposalPayload().getName()));
tableView.getColumns().add(nameColumn);
TableColumn<ProposalListItem, ProposalListItem> uidColumn = new AutoTooltipTableColumn<>(Res.get("shared.id"));
uidColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
uidColumn.setCellFactory(
new Callback<TableColumn<ProposalListItem, ProposalListItem>, TableCell<ProposalListItem,
ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(
TableColumn<ProposalListItem, ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getProposal().getProposalPayload().getUid());
else
setText("");
}
};
}
});
uidColumn.setComparator(Comparator.comparing(o -> o.getProposal().getProposalPayload().getUid()));
tableView.getColumns().add(uidColumn);
TableColumn<ProposalListItem, ProposalListItem> confidenceColumn = new TableColumn<>(Res.get("shared.confirmations"));
confidenceColumn.setMinWidth(130);
confidenceColumn.setMaxWidth(confidenceColumn.getMinWidth());
confidenceColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
confidenceColumn.setCellFactory(new Callback<TableColumn<ProposalListItem, ProposalListItem>,
TableCell<ProposalListItem, ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(TableColumn<ProposalListItem,
ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getTxConfidenceIndicator());
} else {
setGraphic(null);
}
}
};
}
});
confidenceColumn.setComparator(Comparator.comparing(ProposalListItem::getConfirmations));
tableView.getColumns().add(confidenceColumn);
}
}

View File

@ -0,0 +1,223 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TxIdTextField;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqAddressValidator;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.proposal.ProposalPayload;
import bisq.core.dao.proposal.ProposalRestrictions;
import bisq.core.dao.proposal.ProposalType;
import bisq.core.dao.proposal.compensation.CompensationRequestPayload;
import bisq.core.dao.proposal.compensation.consensus.Restrictions;
import bisq.core.provider.fee.FeeService;
import bisq.common.locale.Res;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.geometry.HPos;
import java.util.Objects;
import java.util.UUID;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.*;
// TODO add listener for descriptionTextArea and restrict size of 100 chars. show popup if exceeds.
// we store data locally so we want to keep it small. external link is intended for more info....
// use ProposalRestrictions.getMaxLengthDescriptionText()
public class ProposalDisplay {
private final GridPane gridPane;
private BsqFormatter bsqFormatter;
private BsqWalletService bsqWalletService;
public InputTextField uidTextField, nameTextField, titleTextField, linkInputTextField;
@Nullable
public InputTextField requestedBsqTextField, bsqAddressTextField;
private int gridRow;
public TextArea descriptionTextArea;
private HyperlinkWithIcon linkHyperlinkWithIcon;
@Nullable
private TxIdTextField txIdTextField;
private FeeService feeService;
public ProposalDisplay(GridPane gridPane, BsqFormatter bsqFormatter, BsqWalletService bsqWalletService, @Nullable FeeService feeService) {
this.gridPane = gridPane;
this.bsqFormatter = bsqFormatter;
this.bsqWalletService = bsqWalletService;
this.feeService = feeService;
}
public void createAllFields(String title, int index, double top, ProposalType proposalType, boolean isMakeProposalScreen) {
this.gridRow = index;
int rowSpan = 5;
if (proposalType == ProposalType.COMPENSATION_REQUEST)
rowSpan += 2;
if (!isMakeProposalScreen)
rowSpan += 1;
addTitledGroupBg(gridPane, gridRow, rowSpan, title, top);
uidTextField = addLabelInputTextField(gridPane, gridRow, Res.getWithCol("shared.id"), top == Layout.GROUP_DISTANCE ? Layout.FIRST_ROW_AND_GROUP_DISTANCE : Layout.FIRST_ROW_DISTANCE).second;
uidTextField.setEditable(false);
nameTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.name")).second;
titleTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.title")).second;
descriptionTextArea = addLabelTextArea(gridPane, ++gridRow, Res.get("dao.proposal.display.description"), Res.get("dao.proposal.display.description.prompt", ProposalRestrictions.getMaxLengthDescriptionText())).second;
linkInputTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.link")).second;
linkHyperlinkWithIcon = addLabelHyperlinkWithIcon(gridPane, gridRow, Res.get("dao.proposal.display.link"), "", "").second;
linkHyperlinkWithIcon.setVisible(false);
linkHyperlinkWithIcon.setManaged(false);
linkInputTextField.setPromptText(Res.get("dao.proposal.display.link.prompt"));
if (proposalType == ProposalType.COMPENSATION_REQUEST) {
requestedBsqTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.requestedBsq")).second;
if (feeService != null) {
BsqValidator bsqValidator = new BsqValidator(bsqFormatter);
//TODO should we use the BSQ or a BTC validator? Technically it is BTC at that stage...
//bsqValidator.setMinValue(feeService.getCreateCompensationRequestFee());
bsqValidator.setMinValue(Restrictions.getMinCompensationRequestAmount());
Objects.requireNonNull(requestedBsqTextField).setValidator(bsqValidator);
}
// TODO validator, addressTF
bsqAddressTextField = addLabelInputTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.bsqAddress")).second;
Objects.requireNonNull(bsqAddressTextField).setText("B" + bsqWalletService.getUnusedAddress().toBase58());
bsqAddressTextField.setValidator(new BsqAddressValidator(bsqFormatter));
}
if (!isMakeProposalScreen)
txIdTextField = addLabelTxIdTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.txId"), "").second;
}
public void fillWithData(ProposalPayload proposalPayload) {
uidTextField.setText(proposalPayload.getUid());
nameTextField.setText(proposalPayload.getName());
titleTextField.setText(proposalPayload.getTitle());
descriptionTextArea.setText(proposalPayload.getDescription());
linkInputTextField.setVisible(false);
linkInputTextField.setManaged(false);
linkHyperlinkWithIcon.setVisible(true);
linkHyperlinkWithIcon.setManaged(true);
linkHyperlinkWithIcon.setText(proposalPayload.getLink());
linkHyperlinkWithIcon.setOnAction(e -> GUIUtil.openWebPage(proposalPayload.getLink()));
if (proposalPayload instanceof CompensationRequestPayload) {
CompensationRequestPayload compensationRequestPayload = (CompensationRequestPayload) proposalPayload;
Objects.requireNonNull(requestedBsqTextField).setText(bsqFormatter.formatCoinWithCode(compensationRequestPayload.getRequestedBsq()));
Objects.requireNonNull(bsqAddressTextField).setText(compensationRequestPayload.getBsqAddress());
}
if (txIdTextField != null)
txIdTextField.setup(proposalPayload.getTxId());
}
public void clearForm() {
uidTextField.clear();
nameTextField.clear();
titleTextField.clear();
descriptionTextArea.clear();
linkInputTextField.clear();
linkHyperlinkWithIcon.clear();
if (requestedBsqTextField != null)
requestedBsqTextField.clear();
if (bsqAddressTextField != null)
bsqAddressTextField.clear();
if (txIdTextField != null)
txIdTextField.cleanup();
}
public void fillWithMock() {
uidTextField.setText(UUID.randomUUID().toString());
nameTextField.setText("Manfred Karrer");
titleTextField.setText("Development work November 2017");
descriptionTextArea.setText("Development work");
linkInputTextField.setText("https://github.com/bisq-network/compensation/issues/12");
if (requestedBsqTextField != null)
requestedBsqTextField.setText("14000");
if (bsqAddressTextField != null)
bsqAddressTextField.setText("B" + bsqWalletService.getUnusedAddress().toBase58());
}
public void setAllFieldsEditable(boolean isEditable) {
nameTextField.setEditable(isEditable);
titleTextField.setEditable(isEditable);
descriptionTextArea.setEditable(isEditable);
linkInputTextField.setEditable(isEditable);
if (requestedBsqTextField != null)
requestedBsqTextField.setEditable(isEditable);
if (bsqAddressTextField != null)
bsqAddressTextField.setEditable(isEditable);
linkInputTextField.setVisible(true);
linkInputTextField.setManaged(true);
linkHyperlinkWithIcon.setVisible(false);
linkHyperlinkWithIcon.setManaged(false);
linkHyperlinkWithIcon.setOnAction(null);
}
public void removeAllFields() {
gridPane.getChildren().clear();
gridRow = 0;
}
public int incrementAndGetGridRow() {
return ++gridRow;
}
public ScrollPane getView() {
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
scrollPane.setMinHeight(100);
AnchorPane anchorPane = new AnchorPane();
scrollPane.setContent(anchorPane);
gridPane.setHgap(5);
gridPane.setVgap(5);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setHalignment(HPos.RIGHT);
columnConstraints1.setHgrow(Priority.SOMETIMES);
columnConstraints1.setMinWidth(140);
ColumnConstraints columnConstraints2 = new ColumnConstraints();
columnConstraints2.setHgrow(Priority.ALWAYS);
columnConstraints2.setMinWidth(300);
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
AnchorPane.setBottomAnchor(gridPane, 20d);
AnchorPane.setRightAnchor(gridPane, 10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
AnchorPane.setTopAnchor(gridPane, 20d);
anchorPane.getChildren().add(gridPane);
return scrollPane;
}
}

View File

@ -0,0 +1,172 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.blockchain.vo.Tx;
import bisq.core.dao.proposal.Proposal;
import bisq.common.locale.Res;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import javafx.scene.control.Tooltip;
import javafx.beans.value.ChangeListener;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@ToString
@Slf4j
@EqualsAndHashCode
public class ProposalListItem implements BsqBlockChainListener {
@Getter
private final Proposal proposal;
private final BsqWalletService bsqWalletService;
private final BsqBlockChain bsqBlockChain;
private final BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher;
private final BsqFormatter bsqFormatter;
private final ChangeListener<Number> chainHeightListener;
@Getter
private TxConfidenceIndicator txConfidenceIndicator;
@Getter
private Integer confirmations = 0;
private TxConfidenceListener txConfidenceListener;
private Tooltip tooltip = new Tooltip(Res.get("confidence.unknown"));
private Transaction walletTransaction;
public ProposalListItem(Proposal proposal,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
BsqFormatter bsqFormatter) {
this.proposal = proposal;
this.bsqWalletService = bsqWalletService;
this.bsqBlockChain = bsqBlockChain;
this.bsqBlockChainChangeDispatcher = bsqBlockChainChangeDispatcher;
this.bsqFormatter = bsqFormatter;
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
txConfidenceIndicator.setProgress(-1);
txConfidenceIndicator.setPrefSize(24, 24);
txConfidenceIndicator.setTooltip(tooltip);
chainHeightListener = (observable, oldValue, newValue) -> setupConfidence();
bsqWalletService.getChainHeightProperty().addListener(chainHeightListener);
setupConfidence();
bsqBlockChainChangeDispatcher.addBsqBlockChainListener(this);
}
@Override
public void onBsqBlockChainChanged() {
setupConfidence();
}
private void setupConfidence() {
final Tx tx = bsqBlockChain.getTxMap().get(proposal.getProposalPayload().getTxId());
if (tx != null) {
final String txId = tx.getId();
// We cache the walletTransaction once found
if (walletTransaction == null) {
final Optional<Transaction> transactionOptional = bsqWalletService.isWalletTransaction(txId);
transactionOptional.ifPresent(transaction -> walletTransaction = transaction);
}
if (walletTransaction != null) {
// It is our tx so we get confidence updates
if (txConfidenceListener == null) {
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence.getConfidenceType(), confidence.getDepthInBlocks(), confidence.numBroadcastPeers());
}
};
bsqWalletService.addTxConfidenceListener(txConfidenceListener);
}
} else {
// tx from other users, we dont get confidence updates but as we have the bsq tx we can calculate it
// we get setupConfidence called at each new block from above listener so no need to register a new listener
int depth = bsqWalletService.getChainHeightProperty().get() - tx.getBlockHeight() + 1;
if (depth > 0)
updateConfidence(TransactionConfidence.ConfidenceType.BUILDING, depth, -1);
//log.error("name={}, id ={}, depth={}", compensationRequest.getPayload().getName(), compensationRequest.getPayload().getUid(), depth);
}
final TransactionConfidence confidence = bsqWalletService.getConfidenceForTxId(txId);
if (confidence != null)
updateConfidence(confidence, confidence.getDepthInBlocks());
}
}
private void updateConfidence(TransactionConfidence confidence, int depthInBlocks) {
if (confidence != null) {
updateConfidence(confidence.getConfidenceType(), confidence.getDepthInBlocks(), confidence.numBroadcastPeers());
confirmations = depthInBlocks;
}
}
public void cleanup() {
bsqBlockChainChangeDispatcher.removeBsqBlockChainListener(this);
bsqWalletService.getChainHeightProperty().removeListener(chainHeightListener);
if (txConfidenceListener != null)
bsqWalletService.removeTxConfidenceListener(txConfidenceListener);
}
public void updateConfidence(TransactionConfidence.ConfidenceType confidenceType, int depthInBlocks, int numBroadcastPeers) {
switch (confidenceType) {
case UNKNOWN:
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
break;
case PENDING:
tooltip.setText(Res.get("confidence.seen", numBroadcastPeers > -1 ? numBroadcastPeers : Res.get("shared.na")));
txConfidenceIndicator.setProgress(-1.0);
break;
case BUILDING:
tooltip.setText(Res.get("confidence.confirmed", depthInBlocks));
txConfidenceIndicator.setProgress(Math.min(1, (double) depthInBlocks / 6.0));
break;
case DEAD:
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
break;
}
txConfidenceIndicator.setPrefSize(24, 24);
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.proposal.ProposalView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
AnchorPane.topAnchor="30"/>
</AnchorPane>

View File

@ -0,0 +1,133 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.common.view.CachingViewLoader;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.common.view.View;
import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.common.view.ViewPath;
import bisq.desktop.components.MenuItem;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.proposal.active.ActiveProposalsView;
import bisq.desktop.main.dao.proposal.closed.ClosedProposalsView;
import bisq.desktop.main.dao.proposal.dashboard.ProposalDashboardView;
import bisq.desktop.main.dao.proposal.make.MakeProposalView;
import bisq.common.locale.Res;
import javax.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
@FxmlView
public class ProposalView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem dashboard, create, proposed, past;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
@Inject
private ProposalView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = viewPath -> {
if (viewPath.size() != 4 || viewPath.indexOf(ProposalView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
ToggleGroup toggleGroup = new ToggleGroup();
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), ProposalDashboardView.class, AwesomeIcon.DASHBOARD);
create = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.make"), MakeProposalView.class, AwesomeIcon.EDIT);
proposed = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.active"), ActiveProposalsView.class, AwesomeIcon.STACKEXCHANGE);
past = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.closed"), ClosedProposalsView.class, AwesomeIcon.LIST);
leftVBox.getChildren().addAll(dashboard, create, proposed, past);
}
@Override
protected void activate() {
dashboard.activate();
create.activate();
proposed.activate();
past.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(ProposalView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = MakeProposalView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(ProposalView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
dashboard.deactivate();
create.deactivate();
proposed.deactivate();
past.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof ProposalDashboardView) dashboard.setSelected(true);
else if (view instanceof MakeProposalView) create.setSelected(true);
else if (view instanceof ActiveProposalsView) proposed.setSelected(true);
else if (view instanceof ClosedProposalsView) past.setSelected(true);
}
public Class<? extends View> getSelectedViewClass() {
return selectedViewClass;
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.proposal.active.ActiveProposalsView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View File

@ -0,0 +1,263 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.active;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.proposal.BaseProposalView;
import bisq.desktop.main.dao.proposal.ProposalListItem;
import bisq.desktop.main.dao.voting.VotingView;
import bisq.desktop.main.dao.voting.vote.VoteView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.proposal.Proposal;
import bisq.core.dao.proposal.ProposalCollectionsManager;
import bisq.common.locale.Res;
import javax.inject.Inject;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.image.ImageView;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.util.Callback;
import java.util.Comparator;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
@FxmlView
public class ActiveProposalsView extends BaseProposalView {
private Button removeButton, voteButton;
private final Navigation navigation;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ActiveProposalsView(ProposalCollectionsManager voteRequestManger,
DaoPeriodService daoPeriodService,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
Navigation navigation,
BsqFormatter bsqFormatter) {
super(voteRequestManger, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, daoPeriodService,
bsqFormatter);
this.navigation = navigation;
}
@Override
public void initialize() {
super.initialize();
}
@Override
protected void activate() {
super.activate();
}
@Override
protected void deactivate() {
super.deactivate();
}
@Override
protected void updateList() {
doUpdateList(proposalCollectionsManager.getActiveProposals());
}
protected void onSelectProposal(ProposalListItem item) {
super.onSelectProposal(item);
if (item != null) {
if (removeButton != null) {
removeButton.setManaged(false);
removeButton.setVisible(false);
removeButton = null;
}
if (voteButton != null) {
voteButton.setManaged(false);
voteButton.setVisible(false);
voteButton = null;
}
onPhaseChanged(daoPeriodService.getPhaseProperty().get());
}
}
private void onVote() {
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class, VoteView.class);
}
private void onRemove(Proposal proposal) {
if (proposalCollectionsManager.removeProposal(proposal))
proposalDisplay.removeAllFields();
else
new Popup<>().warning(Res.get("dao.proposal.active.remove.failed")).show();
}
@Override
protected void onPhaseChanged(DaoPeriodService.Phase phase) {
if (removeButton != null) {
removeButton.setManaged(false);
removeButton.setVisible(false);
removeButton = null;
}
if (selectedProposalListItem != null && proposalDisplay != null && !selectedProposalListItem.getProposal().isClosed()) {
final Proposal proposal = selectedProposalListItem.getProposal();
switch (phase) {
case COMPENSATION_REQUESTS:
if (proposalCollectionsManager.isMine(proposal)) {
if (removeButton == null) {
removeButton = addButtonAfterGroup(detailsGridPane, proposalDisplay.incrementAndGetGridRow(), Res.get("dao.proposal.active.remove"));
removeButton.setOnAction(event -> onRemove(proposal));
} else {
removeButton.setManaged(true);
removeButton.setVisible(true);
}
}
break;
case BREAK1:
break;
case OPEN_FOR_VOTING:
if (voteButton == null) {
voteButton = addButtonAfterGroup(detailsGridPane, proposalDisplay.incrementAndGetGridRow(), Res.get("dao.proposal.active.vote"));
voteButton.setOnAction(event -> onVote());
} else {
voteButton.setManaged(true);
voteButton.setVisible(true);
}
break;
case BREAK2:
break;
case VOTE_REVEAL:
break;
case BREAK3:
break;
case UNDEFINED:
default:
log.warn("Undefined phase: " + phase);
break;
}
}
}
@Override
protected void createColumns(TableView<ProposalListItem> tableView) {
super.createColumns(tableView);
TableColumn<ProposalListItem, ProposalListItem> actionColumn = new TableColumn<>();
actionColumn.setMinWidth(130);
actionColumn.setMaxWidth(actionColumn.getMinWidth());
actionColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
actionColumn.setCellFactory(new Callback<TableColumn<ProposalListItem, ProposalListItem>,
TableCell<ProposalListItem, ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(TableColumn<ProposalListItem,
ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
final ImageView iconView = new ImageView();
Button button;
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
final Proposal proposal = item.getProposal();
if (button == null) {
button = new AutoTooltipButton(getActionButtonText());
button.setMinWidth(70);
iconView.setId(getActionButtonIconStyle());
button.setGraphic(iconView);
button.setVisible(getActionButtonVisibility(proposal));
setGraphic(button);
}
button.setOnAction(event -> onActionButton(proposal));
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
actionColumn.setComparator(Comparator.comparing(ProposalListItem::getConfirmations));
tableView.getColumns().add(actionColumn);
}
private void onActionButton(Proposal proposal) {
if (showVoteButton())
onVote();
else if (showRemoveButton())
onRemove(proposal);
}
private boolean getActionButtonVisibility(Proposal proposal) {
return !proposal.isClosed() && (showRemoveButton() || showVoteButton());
}
private String getActionButtonIconStyle() {
// TODO find better icon
return showRemoveButton() ? "image-remove" : "image-tick";
}
private String getActionButtonText() {
return showRemoveButton() ? Res.get("shared.remove") : Res.get("shared.vote");
}
private boolean showVoteButton() {
return isTxInVotePhase();
}
private boolean showRemoveButton() {
return isTxInRequestPhase() && selectedProposalListItem != null && proposalCollectionsManager.isMine(selectedProposalListItem.getProposal());
}
private boolean isTxInRequestPhase() {
return daoPeriodService.getPhaseProperty().get().equals(DaoPeriodService.Phase.COMPENSATION_REQUESTS);
}
private boolean isTxInVotePhase() {
return daoPeriodService.getPhaseProperty().get().equals(DaoPeriodService.Phase.OPEN_FOR_VOTING);
}
}

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.proposal.closed.ClosedProposalsView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View File

@ -0,0 +1,70 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.closed;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.dao.proposal.BaseProposalView;
import bisq.desktop.util.BsqFormatter;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.proposal.ProposalCollectionsManager;
import javax.inject.Inject;
@FxmlView
public class ClosedProposalsView extends BaseProposalView {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ClosedProposalsView(ProposalCollectionsManager proposalCollectionsManager,
DaoPeriodService daoPeriodService,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
BsqFormatter bsqFormatter) {
super(proposalCollectionsManager, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, daoPeriodService,
bsqFormatter);
}
@Override
public void initialize() {
super.initialize();
}
@Override
protected void activate() {
super.activate();
}
@Override
protected void deactivate() {
super.deactivate();
}
@Override
protected void updateList() {
doUpdateList(proposalCollectionsManager.getClosedProposals());
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq 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.
~
~ Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.proposal.dashboard.ProposalDashboardView"
AnchorPane.bottomAnchor="-20.0" AnchorPane.leftAnchor="-10.0"
AnchorPane.rightAnchor="-10.0" AnchorPane.topAnchor="-20.0"
xmlns:fx="http://javafx.com/fxml">
</GridPane>

View File

@ -0,0 +1,157 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.dashboard;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.SeparatedPhaseBars;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.common.locale.Res;
import javax.inject.Inject;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.geometry.Insets;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javafx.beans.value.ChangeListener;
import java.util.Arrays;
import java.util.List;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class ProposalDashboardView extends ActivatableView<GridPane, Void> {
private List<SeparatedPhaseBars.SeparatedPhaseBarsItem> phaseBarsItems;
private final BsqWalletService bsqWalletService;
private final DaoPeriodService daoPeriodService;
private DaoPeriodService.Phase currentPhase;
private Subscription phaseSubscription;
private GridPane gridPane;
private int gridRow = 0;
private ChangeListener<Number> chainHeightChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ProposalDashboardView(DaoPeriodService daoPeriodService,
BsqWalletService bsqWalletService) {
this.daoPeriodService = daoPeriodService;
this.bsqWalletService = bsqWalletService;
}
@Override
public void initialize() {
root.getStyleClass().add("compensation-root");
AnchorPane topAnchorPane = new AnchorPane();
root.getChildren().add(topAnchorPane);
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
AnchorPane.setBottomAnchor(gridPane, 10d);
AnchorPane.setRightAnchor(gridPane, 10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
AnchorPane.setTopAnchor(gridPane, 10d);
topAnchorPane.getChildren().add(gridPane);
// Add phase info
addTitledGroupBg(gridPane, gridRow, 1, Res.get("dao.proposal.active.phase.header"));
SeparatedPhaseBars separatedPhaseBars = createSeparatedPhaseBars();
GridPane.setColumnSpan(separatedPhaseBars, 2);
GridPane.setColumnIndex(separatedPhaseBars, 0);
GridPane.setMargin(separatedPhaseBars, new Insets(Layout.FIRST_ROW_DISTANCE - 6, 0, 0, 0));
GridPane.setRowIndex(separatedPhaseBars, gridRow);
gridPane.getChildren().add(separatedPhaseBars);
chainHeightChangeListener = (observable, oldValue, newValue) -> onChainHeightChanged((int) newValue);
}
private SeparatedPhaseBars createSeparatedPhaseBars() {
phaseBarsItems = Arrays.asList(
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.COMPENSATION_REQUESTS, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.BREAK1, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.OPEN_FOR_VOTING, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.BREAK2, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.VOTE_REVEAL, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPeriodService.Phase.BREAK3, false));
return new SeparatedPhaseBars(phaseBarsItems);
}
@Override
protected void activate() {
super.activate();
bsqWalletService.getChainHeightProperty().addListener(chainHeightChangeListener);
phaseSubscription = EasyBind.subscribe(daoPeriodService.getPhaseProperty(), phase -> {
if (!phase.equals(this.currentPhase)) {
this.currentPhase = phase;
}
phaseBarsItems.forEach(item -> {
if (item.getPhase() == phase) {
item.setActive();
} else {
item.setInActive();
}
});
});
onChainHeightChanged(bsqWalletService.getChainHeightProperty().get());
}
@Override
protected void deactivate() {
super.deactivate();
bsqWalletService.getChainHeightProperty().removeListener(chainHeightChangeListener);
phaseSubscription.unsubscribe();
}
private void onChainHeightChanged(int height) {
phaseBarsItems.forEach(item -> {
int startBlock = daoPeriodService.getAbsoluteStartBlockOfPhase(height, item.getPhase());
int endBlock = daoPeriodService.getAbsoluteEndBlockOfPhase(height, item.getPhase());
item.setStartAndEnd(startBlock, endBlock);
double progress = 0;
if (height >= startBlock && height <= endBlock) {
progress = (double) (height - startBlock + 1) / (double) item.getPhase().getDurationInBlocks();
} else if (height < startBlock) {
progress = 0;
} else if (height > endBlock) {
progress = 1;
}
item.getProgressProperty().set(progress);
});
}
}

View File

@ -20,7 +20,7 @@
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.voting.dashboard.VotingDashboardView"
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.proposal.make.MakeProposalView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"

View File

@ -0,0 +1,298 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.make;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.dao.proposal.ProposalDisplay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.BSFormatter;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.InsufficientBsqException;
import bisq.core.btc.wallet.WalletsSetup;
import bisq.core.dao.proposal.ProposalCollectionsManager;
import bisq.core.dao.proposal.ProposalType;
import bisq.core.dao.proposal.compensation.CompensationAmountException;
import bisq.core.dao.proposal.compensation.CompensationRequest;
import bisq.core.dao.proposal.compensation.CompensationRequestManager;
import bisq.core.dao.proposal.compensation.CompensationRequestPayload;
import bisq.core.dao.proposal.generic.GenericProposal;
import bisq.core.dao.proposal.generic.GenericProposalManager;
import bisq.core.dao.proposal.generic.GenericProposalPayload;
import bisq.core.provider.fee.FeeService;
import bisq.core.util.CoinUtil;
import bisq.network.p2p.P2PService;
import bisq.common.app.DevEnv;
import bisq.common.locale.Res;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.GridPane;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.util.StringConverter;
import java.util.Arrays;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addLabelComboBox;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class MakeProposalView extends ActivatableView<GridPane, Void> {
private ProposalDisplay proposalDisplay;
private Button createButton;
private final BsqWalletService bsqWalletService;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final FeeService feeService;
private final ProposalCollectionsManager proposalCollectionsManager;
private final CompensationRequestManager compensationRequestManager;
private final GenericProposalManager genericProposalManager;
private final BSFormatter btcFormatter;
private final BsqFormatter bsqFormatter;
private ComboBox<ProposalType> proposalTypeComboBox;
private ChangeListener<ProposalType> proposalTypeChangeListener;
private ProposalType selectedProposalType;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private MakeProposalView(BsqWalletService bsqWalletService,
WalletsSetup walletsSetup,
P2PService p2PService,
FeeService feeService,
ProposalCollectionsManager proposalCollectionsManager,
CompensationRequestManager compensationRequestManager,
GenericProposalManager genericProposalManager,
BSFormatter btcFormatter,
BsqFormatter bsqFormatter) {
this.bsqWalletService = bsqWalletService;
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.feeService = feeService;
this.proposalCollectionsManager = proposalCollectionsManager;
this.compensationRequestManager = compensationRequestManager;
this.genericProposalManager = genericProposalManager;
this.btcFormatter = btcFormatter;
this.bsqFormatter = bsqFormatter;
}
@Override
public void initialize() {
addTitledGroupBg(root, 0, 1, Res.get("dao.proposal.create.selectProposalType"));
proposalTypeComboBox = addLabelComboBox(root, 0, Res.getWithCol("dao.proposal.create.proposalType"), Layout.FIRST_ROW_DISTANCE).second;
proposalTypeComboBox.setConverter(new StringConverter<ProposalType>() {
@Override
public String toString(ProposalType object) {
return Res.get(object.name());
}
@Override
public ProposalType fromString(String string) {
return null;
}
});
proposalTypeComboBox.setPromptText(Res.get("shared.select"));
proposalTypeChangeListener = (observable, oldValue, newValue) -> {
selectedProposalType = newValue;
addProposalDisplay();
};
proposalTypeComboBox.setItems(FXCollections.observableArrayList(Arrays.asList(ProposalType.values())));
}
@Override
protected void activate() {
proposalTypeComboBox.getSelectionModel().selectedItemProperty().addListener(proposalTypeChangeListener);
}
@Override
protected void deactivate() {
proposalTypeComboBox.getSelectionModel().selectedItemProperty().removeListener(proposalTypeChangeListener);
if (createButton != null)
createButton.setOnAction(null);
}
private void createCompensationRequest() {
CompensationRequestPayload compensationRequestPayload = compensationRequestManager.getNewCompensationRequestPayload(
proposalDisplay.nameTextField.getText(),
proposalDisplay.titleTextField.getText(),
proposalDisplay.descriptionTextArea.getText(),
proposalDisplay.linkInputTextField.getText(),
bsqFormatter.parseToCoin(Objects.requireNonNull(proposalDisplay.requestedBsqTextField).getText()),
Objects.requireNonNull(proposalDisplay.bsqAddressTextField).getText());
try {
CompensationRequest compensationRequest = compensationRequestManager.prepareCompensationRequest(compensationRequestPayload);
Coin miningFee = compensationRequest.getTx().getFee();
int txSize = compensationRequest.getTx().bitcoinSerialize().length;
new Popup<>().headLine(Res.get("dao.proposal.create.confirm"))
.confirmation(Res.get("dao.proposal.create.confirm.info",
bsqFormatter.formatCoinWithCode(compensationRequest.getRequestedBsq()),
bsqFormatter.formatCoinWithCode(compensationRequest.getFeeAsCoin()),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
(txSize / 1000d)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
proposalCollectionsManager.publishProposal(compensationRequest, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {
proposalDisplay.clearForm();
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error(t.toString());
new Popup<>().warning(t.toString()).show();
}
});
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (InsufficientMoneyException e) {
BSFormatter formatter = e instanceof InsufficientBsqException ? bsqFormatter : btcFormatter;
new Popup<>().warning(Res.get("dao.proposal.create.missingFunds", formatter.formatCoinWithCode(e.missing))).show();
} catch (CompensationAmountException e) {
new Popup<>().warning(Res.get("validation.bsq.amountBelowMinAmount", bsqFormatter.formatCoinWithCode(e.required))).show();
} catch (TransactionVerificationException | WalletException e) {
log.error(e.toString());
e.printStackTrace();
new Popup<>().warning(e.toString()).show();
}
}
private void createGenericProposal() {
GenericProposalPayload genericProposalPayload = genericProposalManager.getNewGenericProposalPayload(
proposalDisplay.nameTextField.getText(),
proposalDisplay.titleTextField.getText(),
proposalDisplay.descriptionTextArea.getText(),
proposalDisplay.linkInputTextField.getText());
try {
GenericProposal genericProposal = genericProposalManager.prepareGenericProposal(genericProposalPayload);
Coin miningFee = genericProposal.getTx().getFee();
int txSize = genericProposal.getTx().bitcoinSerialize().length;
new Popup<>().headLine(Res.get("dao.proposal.create.confirm"))
.confirmation(Res.get("dao.proposal.create.confirm.info",
bsqFormatter.formatCoinWithCode(Coin.valueOf(10000)), // TODO dummy
bsqFormatter.formatCoinWithCode(genericProposal.getFeeAsCoin()),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
(txSize / 1000d)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
proposalCollectionsManager.publishProposal(genericProposal, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {
proposalDisplay.clearForm();
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error(t.toString());
new Popup<>().warning(t.toString()).show();
}
});
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (InsufficientMoneyException e) {
BSFormatter formatter = e instanceof InsufficientBsqException ? bsqFormatter : btcFormatter;
new Popup<>().warning(Res.get("dao.proposal.create.missingFunds", formatter.formatCoinWithCode(e.missing))).show();
} catch (CompensationAmountException e) {
new Popup<>().warning(Res.get("validation.bsq.amountBelowMinAmount", bsqFormatter.formatCoinWithCode(e.required))).show();
} catch (TransactionVerificationException | WalletException e) {
log.error(e.toString());
e.printStackTrace();
new Popup<>().warning(e.toString()).show();
}
}
private void addProposalDisplay() {
// TODO need to update removed fields when switching.
if (proposalDisplay != null) {
root.getChildren().remove(3, root.getChildren().size());
}
proposalDisplay = new ProposalDisplay(root, bsqFormatter, bsqWalletService, feeService);
proposalDisplay.createAllFields(Res.get("dao.proposal.create.createNew"), 1, Layout.GROUP_DISTANCE, selectedProposalType, true);
proposalDisplay.fillWithMock();
createButton = addButtonAfterGroup(root, proposalDisplay.incrementAndGetGridRow(), Res.get("dao.proposal.create.create.button"));
createButton.setOnAction(event -> {
// TODO break up in methods
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
switch (selectedProposalType) {
case COMPENSATION_REQUEST:
createCompensationRequest();
break;
case GENERIC:
createGenericProposal();
break;
case CHANGE_PARAM:
//TODO
break;
case REMOVE_ALTCOIN:
//TODO
break;
default:
final String msg = "Undefined ProposalType " + selectedProposalType;
log.error(msg);
if (DevEnv.isDevMode())
throw new RuntimeException(msg);
break;
}
} else {
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
}
});
}
}

View File

@ -1,214 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.voting.vote;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipCheckBox;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.compensation.CompensationRequestDisplay;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.request.compensation.CompensationRequest;
import bisq.core.dao.request.compensation.CompensationRequestPayload;
import bisq.core.dao.vote.CompensationRequestVoteItem;
import bisq.common.locale.Res;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.beans.property.DoubleProperty;
import java.util.ArrayList;
import java.util.List;
public class CompensationViewItem {
private static final List<CompensationViewItem> instances = new ArrayList<>();
private final Button removeButton;
private final CheckBox acceptCheckBox, declineCheckBox;
public final CompensationRequestVoteItem compensationRequestVoteItem;
private Pane owner;
@SuppressWarnings("UnusedParameters")
public static void attach(CompensationRequestVoteItem compensationRequestVoteItem,
BsqWalletService bsqWalletService,
VBox vBox,
DoubleProperty labelWidth,
BsqFormatter bsqFormatter,
Runnable removeHandler) {
instances.add(new CompensationViewItem(compensationRequestVoteItem, bsqWalletService, vBox, bsqFormatter, removeHandler));
}
public static void cleanupAllInstances() {
instances.forEach(CompensationViewItem::cleanupInstance);
}
public static boolean contains(CompensationRequestVoteItem selectedItem) {
return instances.stream()
.anyMatch(e -> e.compensationRequestVoteItem.compensationRequest.getPayload().getUid().equals(
selectedItem.compensationRequest.getPayload().getUid()));
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isEmpty() {
return instances.isEmpty();
}
private CompensationViewItem(CompensationRequestVoteItem compensationRequestVoteItem,
BsqWalletService bsqWalletService,
VBox vBox,
BsqFormatter bsqFormatter,
Runnable removeHandler) {
this.compensationRequestVoteItem = compensationRequestVoteItem;
CompensationRequest compensationRequest = compensationRequestVoteItem.compensationRequest;
CompensationRequestPayload compensationRequestPayload = compensationRequest.getPayload();
HBox hBox = new HBox();
hBox.setSpacing(5);
vBox.getChildren().add(hBox);
String title = compensationRequestPayload.getTitle() + " (" + compensationRequestPayload.getShortId() + ")";
HyperlinkWithIcon infoLabelWithLink = new HyperlinkWithIcon(title, AwesomeIcon.EXTERNAL_LINK);
infoLabelWithLink.setPrefWidth(220);
HBox.setMargin(infoLabelWithLink, new Insets(2, 0, 0, 0));
infoLabelWithLink.setOnAction(e -> {
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setHalignment(HPos.RIGHT);
columnConstraints1.setHgrow(Priority.SOMETIMES);
columnConstraints1.setMinWidth(140);
ColumnConstraints columnConstraints2 = new ColumnConstraints();
columnConstraints2.setHgrow(Priority.ALWAYS);
columnConstraints2.setMinWidth(300);
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().add(gridPane);
AnchorPane.setBottomAnchor(gridPane, 25d);
AnchorPane.setRightAnchor(gridPane, 25d);
AnchorPane.setLeftAnchor(gridPane, 25d);
AnchorPane.setTopAnchor(gridPane, -20d);
CompensationRequestDisplay compensationRequestDisplay = new CompensationRequestDisplay(gridPane, bsqFormatter, bsqWalletService, null);
compensationRequestDisplay.createAllFields(Res.get("dao.voting.item.title"), Layout.GROUP_DISTANCE);
compensationRequestDisplay.setAllFieldsEditable(false);
compensationRequestDisplay.fillWithData(compensationRequestPayload);
Scene scene = new Scene(anchorPane);
scene.getStylesheets().setAll(
"/bisq/desktop/bisq.css",
"/bisq/desktop/images.css");
Stage stage = new Stage();
stage.setTitle(Res.get("dao.voting.item.stage.title", compensationRequestPayload.getShortId()));
stage.setScene(scene);
if (owner == null)
owner = MainView.getRootContainer();
Scene rootScene = owner.getScene();
stage.initOwner(rootScene.getWindow());
stage.initModality(Modality.NONE);
stage.initStyle(StageStyle.UTILITY);
stage.show();
Window window = rootScene.getWindow();
double titleBarHeight = window.getHeight() - rootScene.getHeight();
stage.setX(Math.round(window.getX() + (owner.getWidth() - stage.getWidth()) / 2) + 200);
stage.setY(Math.round(window.getY() + titleBarHeight + (owner.getHeight() - stage.getHeight()) / 2) + 50);
});
acceptCheckBox = new AutoTooltipCheckBox(Res.get("shared.accept"));
HBox.setMargin(acceptCheckBox, new Insets(5, 0, 0, 0));
declineCheckBox = new AutoTooltipCheckBox(Res.get("shared.decline"));
HBox.setMargin(declineCheckBox, new Insets(5, 0, 0, 0));
acceptCheckBox.setOnAction(event -> {
boolean selected = acceptCheckBox.isSelected();
compensationRequestVoteItem.setAcceptedVote(selected);
if (declineCheckBox.isSelected()) {
declineCheckBox.setSelected(!selected);
compensationRequestVoteItem.setDeclineVote(!selected);
} else if (!selected) {
compensationRequestVoteItem.setHasVoted(false);
}
});
acceptCheckBox.setSelected(compensationRequestVoteItem.isAcceptedVote());
declineCheckBox.setOnAction(event -> {
boolean selected = declineCheckBox.isSelected();
compensationRequestVoteItem.setDeclineVote(selected);
if (acceptCheckBox.isSelected()) {
acceptCheckBox.setSelected(!selected);
compensationRequestVoteItem.setAcceptedVote(!selected);
} else if (!selected) {
compensationRequestVoteItem.setHasVoted(false);
}
});
declineCheckBox.setSelected(compensationRequestVoteItem.isDeclineVote());
removeButton = new AutoTooltipButton(Res.get("shared.remove"));
removeButton.setOnAction(event -> {
vBox.getChildren().remove(hBox);
cleanupInstance();
instances.remove(this);
removeHandler.run();
});
Pane spacer = new Pane();
spacer.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().addAll(infoLabelWithLink, acceptCheckBox, declineCheckBox, spacer, removeButton);
}
public void cleanupInstance() {
acceptCheckBox.setOnAction(null);
declineCheckBox.setOnAction(null);
removeButton.setOnAction(null);
}
}

View File

@ -1,179 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.voting.vote;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.InputTextField;
import bisq.core.dao.vote.VoteItem;
import bisq.core.dao.vote.VotingDefaultValues;
import bisq.common.UserThread;
import bisq.common.locale.Res;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.util.StringConverter;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ParameterViewItem {
private static final Logger log = LoggerFactory.getLogger(ParameterViewItem.class);
private static final List<ParameterViewItem> instances = new ArrayList<>();
private final long originalValue;
private final ChangeListener<String> inputTextFieldChangeListener;
private final ChangeListener<Boolean> inputTextFieldFocusListener;
private final ChangeListener<Number> sliderListener;
private final InputTextField inputTextField;
private final Slider slider;
private final Button resetButton, removeButton;
private final Label label;
private ChangeListener<Number> numberChangeListener;
public final VoteItem voteItem;
public static void attach(VoteItem voteItem, VBox vBox, DoubleProperty labelWidth, VotingDefaultValues votingDefaultValues, Runnable removeHandler) {
instances.add(new ParameterViewItem(voteItem, vBox, labelWidth, votingDefaultValues, removeHandler));
}
public static void cleanupAllInstances() {
instances.stream().forEach(ParameterViewItem::cleanupInstance);
}
public static boolean contains(VoteItem selectedItem) {
return instances.stream().filter(e -> e.voteItem.getVotingType() == selectedItem.getVotingType()).findAny().isPresent();
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isEmpty() {
return instances.isEmpty();
}
private ParameterViewItem(VoteItem voteItem, VBox vBox, DoubleProperty labelWidth, VotingDefaultValues votingDefaultValues, Runnable removeHandler) {
this.voteItem = voteItem;
originalValue = votingDefaultValues.getValueByVotingType(voteItem.getVotingType());
HBox hBox = new HBox();
hBox.setSpacing(5);
vBox.getChildren().add(hBox);
label = new AutoTooltipLabel(voteItem.getName() + ":");
HBox.setMargin(label, new Insets(4, 0, 0, 0));
numberChangeListener = (observable, oldValue, newValue) -> {
if ((double) newValue > 0) {
labelWidth.set(Math.max(labelWidth.get(), (double) newValue));
UserThread.execute(() -> label.prefWidthProperty().bind(labelWidth));
label.widthProperty().removeListener(numberChangeListener);
}
};
label.widthProperty().addListener(numberChangeListener);
inputTextField = new InputTextField();
inputTextField.setPrefWidth(100);
inputTextField.setText(String.valueOf(originalValue));
slider = new Slider();
inputTextFieldChangeListener = (observable, oldValue, newValue) -> {
if (!slider.isFocused()) {
try {
long change = votingDefaultValues.getChange(originalValue, Long.valueOf(inputTextField.getText()));
slider.setValue(change);
voteItem.setValue((byte) change);
} catch (Throwable ignore) {
}
}
};
inputTextField.textProperty().addListener(inputTextFieldChangeListener);
inputTextFieldFocusListener = (observable, oldValue, newValue) -> {
if (oldValue && !newValue) {
// focus out
// We adjust value to our 255 value grid
int change = (int) Math.round(slider.getValue());
long dataValue = votingDefaultValues.getAdjustedValue(originalValue, change);
inputTextField.setText(String.valueOf(dataValue));
}
};
inputTextField.focusedProperty().addListener(inputTextFieldFocusListener);
slider.setPrefWidth(300);
slider.setMin(0);
slider.setMax(254);
slider.setValue(127);
slider.setShowTickLabels(true);
HBox.setMargin(slider, new Insets(-1, 20, 0, 20));
HBox.setHgrow(slider, Priority.ALWAYS);
slider.setLabelFormatter(new StringConverter<Double>() {
@Override
public String toString(Double object) {
return String.valueOf(votingDefaultValues.getAdjustedValue(originalValue, object.intValue()));
}
@Override
public Double fromString(String string) {
return null;
}
});
sliderListener = (observable, oldValue, newValue) -> {
if (!inputTextField.isFocused()) {
int change = (int) Math.round(slider.getValue());
long dataValue = votingDefaultValues.getAdjustedValue(originalValue, change);
inputTextField.setText(String.valueOf(dataValue));
voteItem.setValue((byte) change);
}
};
slider.valueProperty().addListener(sliderListener);
resetButton = new AutoTooltipButton(Res.get("shared.reset"));
resetButton.setOnAction(event -> inputTextField.setText(String.valueOf(originalValue)));
removeButton = new AutoTooltipButton(Res.get("shared.remove"));
removeButton.setOnAction(event -> {
vBox.getChildren().remove(hBox);
cleanupInstance();
instances.remove(this);
removeHandler.run();
});
hBox.getChildren().addAll(label, inputTextField, slider, resetButton, removeButton);
}
public void cleanupInstance() {
label.widthProperty().removeListener(numberChangeListener);
inputTextField.focusedProperty().removeListener(inputTextFieldFocusListener);
inputTextField.textProperty().removeListener(inputTextFieldChangeListener);
slider.valueProperty().removeListener(sliderListener);
resetButton.setOnAction(null);
removeButton.setOnAction(null);
}
}

View File

@ -19,316 +19,32 @@ package bisq.desktop.main.dao.voting.vote;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.BSFormatter;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsSetup;
import bisq.core.dao.request.compensation.CompensationRequest;
import bisq.core.dao.request.compensation.CompensationRequestManager;
import bisq.core.dao.vote.CompensationRequestVoteItem;
import bisq.core.dao.vote.CompensationRequestVoteItemCollection;
import bisq.core.dao.vote.VoteItem;
import bisq.core.dao.vote.VoteItemsList;
import bisq.core.dao.vote.VotingManager;
import bisq.core.provider.fee.FeeService;
import bisq.core.util.CoinUtil;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
import bisq.common.locale.Res;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
import java.io.IOException;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addLabelComboBox;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static com.google.common.base.Preconditions.checkNotNull;
import static javafx.beans.binding.Bindings.createBooleanBinding;
@FxmlView
public class VoteView extends ActivatableView<GridPane, Void> {
private ComboBox<VoteItem> parametersComboBox;
private ComboBox<CompensationRequestVoteItem> compensationRequestsComboBox;
private int gridRow = 0;
private final CompensationRequestManager compensationRequestManager;
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final FeeService feeService;
private final BsqFormatter bsqFormatter;
private final BSFormatter btcFormatter;
private final VotingManager voteManager;
private Button voteButton;
private List<CompensationRequest> compensationRequests;
private TitledGroupBg compensationRequestsTitledGroupBg, parametersTitledGroupBg;
private VoteItemsList voteItemsList;
private VBox parametersVBox, compensationRequestsVBox;
private final DoubleProperty parametersLabelWidth = new SimpleDoubleProperty();
private final DoubleProperty compensationRequestsLabelWidth = new SimpleDoubleProperty();
private ChangeListener<Number> numberChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private VoteView(CompensationRequestManager compensationRequestManager,
BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
WalletsSetup walletsSetup,
P2PService p2PService,
FeeService feeService,
BsqFormatter bsqFormatter,
BSFormatter btcFormatter,
VotingManager voteManager) {
this.compensationRequestManager = compensationRequestManager;
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.feeService = feeService;
this.bsqFormatter = bsqFormatter;
this.btcFormatter = btcFormatter;
this.voteManager = voteManager;
private VoteView() {
}
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 2, Res.get("dao.voting.addItems"));
//noinspection unchecked
compensationRequestsComboBox = addLabelComboBox(root, gridRow, "", Layout.FIRST_ROW_DISTANCE).second;
compensationRequestsComboBox.setPromptText(Res.get("dao.voting.addRequest"));
compensationRequestsComboBox.setConverter(new StringConverter<CompensationRequestVoteItem>() {
@Override
public String toString(CompensationRequestVoteItem item) {
return item.compensationRequest.getPayload().getUid();
}
@Override
public CompensationRequestVoteItem fromString(String s) {
return null;
}
});
compensationRequestsComboBox.setOnAction(event -> {
SingleSelectionModel<CompensationRequestVoteItem> selectionModel = compensationRequestsComboBox.getSelectionModel();
CompensationRequestVoteItem selectedItem = selectionModel.getSelectedItem();
if (selectedItem != null) {
if (!CompensationViewItem.contains(selectedItem)) {
CompensationViewItem.attach(selectedItem,
bsqWalletService,
compensationRequestsVBox,
compensationRequestsLabelWidth,
bsqFormatter,
() -> compensationRequestsTitledGroupBg.setManaged(!CompensationViewItem.isEmpty()));
UserThread.execute(selectionModel::clearSelection);
} else {
new Popup<>().warning(Res.get("dao.voting.requestAlreadyAdded")).show();
}
}
compensationRequestsTitledGroupBg.setManaged(!CompensationViewItem.isEmpty());
});
//noinspection unchecked
parametersComboBox = addLabelComboBox(root, ++gridRow, "").second;
parametersComboBox.setPromptText(Res.get("dao.voting.addParameter"));
parametersComboBox.setConverter(new StringConverter<VoteItem>() {
@Override
public String toString(VoteItem item) {
return item.getName();
}
@Override
public VoteItem fromString(String s) {
return null;
}
});
parametersComboBox.setOnAction(event -> {
SingleSelectionModel<VoteItem> selectionModel = parametersComboBox.getSelectionModel();
VoteItem selectedItem = selectionModel.getSelectedItem();
if (selectedItem != null) {
if (!ParameterViewItem.contains(selectedItem)) {
ParameterViewItem.attach(selectedItem, parametersVBox, parametersLabelWidth, voteManager.getVotingDefaultValues(),
() -> parametersTitledGroupBg.setManaged(!ParameterViewItem.isEmpty()));
UserThread.execute(selectionModel::clearSelection);
} else {
new Popup<>().warning(Res.get("dao.voting.parameterAlreadyAdded")).show();
}
}
parametersTitledGroupBg.setManaged(!ParameterViewItem.isEmpty());
});
compensationRequestsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 1, Res.get("dao.voting.compensationRequests"), Layout.GROUP_DISTANCE);
compensationRequestsTitledGroupBg.setManaged(false);
compensationRequestsTitledGroupBg.visibleProperty().bind(compensationRequestsTitledGroupBg.managedProperty());
compensationRequestsVBox = new VBox();
compensationRequestsVBox.setSpacing(5);
GridPane.setRowIndex(compensationRequestsVBox, gridRow);
GridPane.setColumnSpan(compensationRequestsVBox, 2);
GridPane.setMargin(compensationRequestsVBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
root.getChildren().add(compensationRequestsVBox);
compensationRequestsVBox.managedProperty().bind(compensationRequestsTitledGroupBg.managedProperty());
compensationRequestsVBox.visibleProperty().bind(compensationRequestsVBox.managedProperty());
parametersTitledGroupBg = addTitledGroupBg(root, ++gridRow, 1, Res.get("shared.parameters"), Layout.GROUP_DISTANCE);
parametersTitledGroupBg.setManaged(false);
parametersTitledGroupBg.visibleProperty().bind(parametersTitledGroupBg.managedProperty());
parametersVBox = new VBox();
parametersVBox.setSpacing(5);
GridPane.setRowIndex(parametersVBox, gridRow);
GridPane.setColumnSpan(parametersVBox, 2);
GridPane.setMargin(parametersVBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
root.getChildren().add(parametersVBox);
parametersVBox.managedProperty().bind(parametersTitledGroupBg.managedProperty());
parametersVBox.visibleProperty().bind(parametersVBox.managedProperty());
voteButton = addButtonAfterGroup(root, ++gridRow, Res.get("shared.vote"));
voteButton.managedProperty().bind(createBooleanBinding(() -> compensationRequestsTitledGroupBg.isManaged() || parametersTitledGroupBg.isManaged(),
compensationRequestsTitledGroupBg.managedProperty(), parametersTitledGroupBg.managedProperty()));
voteButton.visibleProperty().bind(voteButton.managedProperty());
voteButton.setOnAction(event -> {
// TODO break up in methods
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
log.info(voteItemsList.toString());
//TODO
if (voteItemsList.isMyVote()) {
new Popup<>().warning(Res.get("dao.voting.votedAlready")).show();
} else if (!voteItemsList.getAllVoteItemList().stream().filter(VoteItem::isHasVoted).findAny().isPresent() &&
!voteItemsList.getAllVoteItemList().stream().filter(e -> e instanceof CompensationRequestVoteItemCollection)
.filter(e -> ((CompensationRequestVoteItemCollection) e).hasVotedOnAnyItem()).findAny().isPresent()) {
new Popup<>().warning(Res.get("dao.voting.notVotedOnAnyEntry")).show();
} else {
try {
byte[] opReturnData = voteManager.calculateOpReturnData(voteItemsList);
try {
Coin votingTxFee = feeService.getVotingTxFee();
Transaction preparedVotingTx = bsqWalletService.getPreparedBurnFeeTx(votingTxFee);
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedVotingTx, false, opReturnData);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
Coin miningFee = signedTx.getFee();
int txSize = signedTx.bitcoinSerialize().length;
new Popup<>().headLine(Res.get("dao.voting.confirmTx"))
.confirmation(Res.get("dao.tx.summary",
btcFormatter.formatCoinWithCode(votingTxFee),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
(txSize / 1000d)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
bsqWalletService.commitTx(txWithBtcFee);
// We need to create another instance, otherwise the tx would trigger an invalid state exception
// if it gets committed 2 times
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {
checkNotNull(transaction, "Transaction must not be null at doSend callback.");
log.error("tx successful published" + transaction.getHashAsString());
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
voteItemsList.setIsMyVote(true);
//TODO send to P2P network
}
@Override
public void onFailure(@NotNull Throwable t) {
new Popup<>().warning(t.toString()).show();
}
}, 15);
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (InsufficientMoneyException | WalletException | TransactionVerificationException e) {
log.error(e.toString());
e.printStackTrace();
new Popup<>().warning(e.toString()).show();
}
} catch (IOException e) {
e.printStackTrace();
new Popup<>().error(e.toString()).show();
}
}
} else {
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
}
});
}
@Override
protected void activate() {
//TODO rename
voteItemsList = voteManager.getActiveVoteItemsList();
//noinspection StatementWithEmptyBody
if (voteItemsList != null) {
CompensationRequestVoteItemCollection compensationRequestVoteItemCollection = voteItemsList.getCompensationRequestVoteItemCollection();
ObservableList<CompensationRequestVoteItem> compensationRequestVoteItems = FXCollections.observableArrayList(compensationRequestVoteItemCollection.getCompensationRequestVoteItems());
compensationRequestsComboBox.setItems(compensationRequestVoteItems);
//TODO move to voteManager.getCurrentVoteItemsList()?
compensationRequestManager.getActiveRequests().stream().forEach(e -> compensationRequestVoteItems.add(new CompensationRequestVoteItem(e)));
parametersComboBox.setItems(FXCollections.observableArrayList(voteItemsList.getVoteItemList()));
} else {
//TODO add listener
}
}
@Override
protected void deactivate() {
compensationRequestsComboBox.setOnAction(null);
parametersComboBox.setOnAction(null);
voteButton.setOnAction(null);
ParameterViewItem.cleanupAllInstances();
CompensationViewItem.cleanupAllInstances();
}
}

View File

@ -133,7 +133,7 @@ class TransactionsListItem {
//
final Optional<TxType> txTypeOptional = bsqBlockChain.getTxType(txId);
if (txTypeOptional.isPresent() && txTypeOptional.get().equals(TxType.COMPENSATION_REQUEST))
details = Res.get("funds.tx.compRequest");
details = Res.get("funds.tx.proposal");
} else {
outgoing = true;
}

View File

@ -1,18 +1,18 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* Bisq 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.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* Bisq 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 bisq. If not, see <http://www.gnu.org/licenses/>.
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

View File

@ -22,7 +22,6 @@ import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.downloadupdate.BisqInstaller.VerifyDescriptor;
import bisq.core.alert.Alert;
@ -237,9 +236,9 @@ public class DisplayUpdateDownloadWindow extends Overlay<DisplayUpdateDownloadWi
statusLabel.setText("");
stopAnimations();
List<VerifyDescriptor> verifyResults = verifyTask.getValue();
List<BisqInstaller.VerifyDescriptor> verifyResults = verifyTask.getValue();
// check that there are no failed verifications
Optional<VerifyDescriptor> verifyFailed = verifyResults.stream()
Optional<BisqInstaller.VerifyDescriptor> verifyFailed = verifyResults.stream()
.filter(verifyDescriptor -> !BisqInstaller.VerifyStatusEnum.OK.equals(verifyDescriptor.getVerifyStatusEnum())).findFirst();
if (verifyResults == null || verifyResults.isEmpty() || verifyFailed.isPresent()) {
showErrorMessage(downloadButton, statusLabel, Res.get("displayUpdateDownloadWindow.verify.failed"));

View File

@ -17,8 +17,6 @@
package bisq.desktop.main.overlays.windows.downloadupdate;
import bisq.desktop.main.overlays.windows.downloadupdate.BisqInstaller.FileDescriptor;
import bisq.common.storage.FileUtil;
import com.google.common.collect.Lists;
@ -46,25 +44,25 @@ import javafx.concurrent.Task;
@Slf4j
@Getter
public class DownloadTask extends Task<List<FileDescriptor>> {
public class DownloadTask extends Task<List<BisqInstaller.FileDescriptor>> {
private static final int EOF = -1;
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private String fileName = null;
private final List<FileDescriptor> fileDescriptors;
private final List<BisqInstaller.FileDescriptor> fileDescriptors;
private final String saveDir;
/**
* Prepares a task to download a file from {@code fileDescriptors} to the system's download dir.
*/
public DownloadTask(final FileDescriptor fileDescriptor) {
public DownloadTask(final BisqInstaller.FileDescriptor fileDescriptor) {
this(Lists.newArrayList(fileDescriptor));
}
public DownloadTask(final FileDescriptor fileDescriptor, final String saveDir) {
public DownloadTask(final BisqInstaller.FileDescriptor fileDescriptor, final String saveDir) {
this(Lists.newArrayList(fileDescriptor), saveDir);
}
public DownloadTask(final List<FileDescriptor> fileDescriptors) {
public DownloadTask(final List<BisqInstaller.FileDescriptor> fileDescriptors) {
this(Lists.newArrayList(fileDescriptors), System.getProperty("java.io.tmpdir"));
}
@ -74,7 +72,7 @@ public class DownloadTask extends Task<List<FileDescriptor>> {
* @param fileDescriptors HTTP URL of the file to be downloaded
* @param saveDir path of the directory to save the file
*/
public DownloadTask(final List<FileDescriptor> fileDescriptors, final String saveDir) {
public DownloadTask(final List<BisqInstaller.FileDescriptor> fileDescriptors, final String saveDir) {
super();
this.fileDescriptors = fileDescriptors;
this.saveDir = saveDir;
@ -88,7 +86,7 @@ public class DownloadTask extends Task<List<FileDescriptor>> {
* @throws IOException Forwarded exceotions from HttpURLConnection and file handling methods
*/
@Override
protected List<FileDescriptor> call() throws IOException {
protected List<BisqInstaller.FileDescriptor> call() throws IOException {
log.debug("DownloadTask started...");
String partialSaveFilePath = saveDir + (saveDir.endsWith(File.separator) ? "" : File.separator);

View File

@ -21,10 +21,6 @@ package bisq.desktop.main.overlays.windows.downloadupdate;
* A Task to verify the downloaded bisq installer against the available keys/signatures.
*/
import bisq.desktop.main.overlays.windows.downloadupdate.BisqInstaller.DownloadType;
import bisq.desktop.main.overlays.windows.downloadupdate.BisqInstaller.FileDescriptor;
import bisq.desktop.main.overlays.windows.downloadupdate.BisqInstaller.VerifyDescriptor;
import com.google.common.collect.Lists;
import java.io.FileReader;
@ -44,15 +40,15 @@ import javafx.concurrent.Task;
@Slf4j
@Getter
public class VerifyTask extends Task<List<VerifyDescriptor>> {
private final List<FileDescriptor> fileDescriptors;
public class VerifyTask extends Task<List<BisqInstaller.VerifyDescriptor>> {
private final List<BisqInstaller.FileDescriptor> fileDescriptors;
/**
* Prepares a task to download a file from {@code fileDescriptors} to {@code saveDir}.
*
* @param fileDescriptors HTTP URL of the file to be downloaded
*/
public VerifyTask(final List<FileDescriptor> fileDescriptors) {
public VerifyTask(final List<BisqInstaller.FileDescriptor> fileDescriptors) {
super();
this.fileDescriptors = fileDescriptors;
log.info("Starting VerifyTask with files:{}", fileDescriptors);
@ -65,23 +61,23 @@ public class VerifyTask extends Task<List<VerifyDescriptor>> {
* @throws IOException Forwarded exceotions from HttpURLConnection and file handling methods
*/
@Override
protected List<VerifyDescriptor> call() throws IOException {
protected List<BisqInstaller.VerifyDescriptor> call() throws IOException {
log.debug("VerifyTask started...");
Optional<FileDescriptor> installer = fileDescriptors.stream()
.filter(fileDescriptor -> DownloadType.INSTALLER.equals(fileDescriptor.getType()))
Optional<BisqInstaller.FileDescriptor> installer = fileDescriptors.stream()
.filter(fileDescriptor -> BisqInstaller.DownloadType.INSTALLER.equals(fileDescriptor.getType()))
.findFirst();
if (!installer.isPresent()) {
log.error("No installer file found.");
return Lists.newArrayList();
}
Optional<FileDescriptor> signingKeyOptional = fileDescriptors.stream()
.filter(fileDescriptor -> DownloadType.SIGNING_KEY.equals(fileDescriptor.getType()))
Optional<BisqInstaller.FileDescriptor> signingKeyOptional = fileDescriptors.stream()
.filter(fileDescriptor -> BisqInstaller.DownloadType.SIGNING_KEY.equals(fileDescriptor.getType()))
.findAny();
List<VerifyDescriptor> verifyDescriptors = Lists.newArrayList();
List<BisqInstaller.VerifyDescriptor> verifyDescriptors = Lists.newArrayList();
if (signingKeyOptional.isPresent()) {
final FileDescriptor signingKeyFD = signingKeyOptional.get();
final BisqInstaller.FileDescriptor signingKeyFD = signingKeyOptional.get();
StringBuilder sb = new StringBuilder();
try {
Scanner scanner = new Scanner(new FileReader(signingKeyFD.getSaveFile()));
@ -92,27 +88,27 @@ public class VerifyTask extends Task<List<VerifyDescriptor>> {
} catch (Exception e) {
log.error(e.toString());
e.printStackTrace();
VerifyDescriptor.VerifyDescriptorBuilder verifyDescriptorBuilder = VerifyDescriptor.builder();
BisqInstaller.VerifyDescriptor.VerifyDescriptorBuilder verifyDescriptorBuilder = BisqInstaller.VerifyDescriptor.builder();
verifyDescriptorBuilder.verifyStatusEnum(BisqInstaller.VerifyStatusEnum.FAIL);
verifyDescriptors.add(verifyDescriptorBuilder.build());
return verifyDescriptors;
}
String signingKey = sb.toString();
List<FileDescriptor> sigs = fileDescriptors.stream()
.filter(fileDescriptor -> DownloadType.SIG.equals(fileDescriptor.getType()))
List<BisqInstaller.FileDescriptor> sigs = fileDescriptors.stream()
.filter(fileDescriptor -> BisqInstaller.DownloadType.SIG.equals(fileDescriptor.getType()))
.collect(Collectors.toList());
// iterate all signatures available to us
for (FileDescriptor sig : sigs) {
VerifyDescriptor.VerifyDescriptorBuilder verifyDescriptorBuilder = VerifyDescriptor.builder().sigFile(sig.getSaveFile());
for (BisqInstaller.FileDescriptor sig : sigs) {
BisqInstaller.VerifyDescriptor.VerifyDescriptorBuilder verifyDescriptorBuilder = BisqInstaller.VerifyDescriptor.builder().sigFile(sig.getSaveFile());
// Sigs are linked to keys, extract all keys which have the same id
List<FileDescriptor> keys = fileDescriptors.stream()
.filter(keyDescriptor -> DownloadType.KEY.equals(keyDescriptor.getType()))
List<BisqInstaller.FileDescriptor> keys = fileDescriptors.stream()
.filter(keyDescriptor -> BisqInstaller.DownloadType.KEY.equals(keyDescriptor.getType()))
.filter(keyDescriptor -> sig.getId().equals(keyDescriptor.getId()))
.collect(Collectors.toList());
// iterate all keys which have the same id
for (FileDescriptor key : keys) {
for (BisqInstaller.FileDescriptor key : keys) {
if (signingKey.equals(key.getId())) {
verifyDescriptorBuilder.keyFile(key.getSaveFile());
try {
@ -133,7 +129,7 @@ public class VerifyTask extends Task<List<VerifyDescriptor>> {
}
} else {
log.error("signingKey is not found");
VerifyDescriptor.VerifyDescriptorBuilder verifyDescriptorBuilder = VerifyDescriptor.builder();
BisqInstaller.VerifyDescriptor.VerifyDescriptorBuilder verifyDescriptorBuilder = BisqInstaller.VerifyDescriptor.builder();
verifyDescriptorBuilder.verifyStatusEnum(BisqInstaller.VerifyStatusEnum.FAIL);
verifyDescriptors.add(verifyDescriptorBuilder.build());
}

View File

@ -432,7 +432,6 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
if (item != null && !empty) {
if (button == null) {
iconView.setId("image-remove");
button = new AutoTooltipButton(Res.get("shared.remove"));
button.setMinWidth(70);
iconView.setId("image-remove");

View File

@ -20,9 +20,11 @@ package bisq.desktop.main.portfolio.pendingtrades.steps;
import bisq.desktop.components.InfoTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.components.TxIdTextField;
import bisq.desktop.components.paymentmethods.PaymentMethodForm;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.TradeSubView;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.core.arbitration.Dispute;
@ -55,8 +57,6 @@ import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static bisq.desktop.components.paymentmethods.PaymentMethodForm.addOpenTradeDuration;
import static bisq.desktop.util.FormBuilder.*;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class TradeStepView extends AnchorPane {
@ -89,7 +89,7 @@ public abstract class TradeStepView extends AnchorPane {
trade = model.dataModel.getTrade();
checkNotNull(trade, "trade must not be null at TradeStepView");
gridPane = addGridPane(this);
gridPane = FormBuilder.addGridPane(this);
AnchorPane.setLeftAnchor(this, 0d);
AnchorPane.setRightAnchor(this, 0d);
@ -180,8 +180,8 @@ public abstract class TradeStepView extends AnchorPane {
}
protected void addTradeInfoBlock() {
tradeInfoTitledGroupBg = addTitledGroupBg(gridPane, gridRow, 4, Res.get("portfolio.pending.tradeInformation"));
txIdTextField = addLabelTxIdTextField(gridPane, gridRow, Res.getWithCol("shared.depositTransactionId"), Layout.FIRST_ROW_DISTANCE).second;
tradeInfoTitledGroupBg = FormBuilder.addTitledGroupBg(gridPane, gridRow, 4, Res.get("portfolio.pending.tradeInformation"));
txIdTextField = FormBuilder.addLabelTxIdTextField(gridPane, gridRow, Res.getWithCol("shared.depositTransactionId"), Layout.FIRST_ROW_DISTANCE).second;
String id = model.dataModel.txId.get();
if (!id.isEmpty())
txIdTextField.setup(id);
@ -189,11 +189,11 @@ public abstract class TradeStepView extends AnchorPane {
txIdTextField.cleanup();
if (model.dataModel.getTrade() != null) {
InfoTextField infoTextField = addOpenTradeDuration(gridPane, ++gridRow, model.dataModel.getTrade().getOffer());
InfoTextField infoTextField = PaymentMethodForm.addOpenTradeDuration(gridPane, ++gridRow, model.dataModel.getTrade().getOffer());
infoTextField.setContentForInfoPopOver(createInfoPopover());
}
timeLeftTextField = addLabelTextField(gridPane, ++gridRow, Res.getWithCol("portfolio.pending.remainingTime")).second;
timeLeftTextField = FormBuilder.addLabelTextField(gridPane, ++gridRow, Res.getWithCol("portfolio.pending.remainingTime")).second;
timeLeftProgressBar = new ProgressBar(0);
timeLeftProgressBar.setOpacity(0.7);
@ -210,8 +210,8 @@ public abstract class TradeStepView extends AnchorPane {
}
protected void addInfoBlock() {
addTitledGroupBg(gridPane, ++gridRow, 1, getInfoBlockTitle(), Layout.GROUP_DISTANCE);
addMultilineLabel(gridPane, gridRow, getInfoText(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
FormBuilder.addTitledGroupBg(gridPane, ++gridRow, 1, getInfoBlockTitle(), Layout.GROUP_DISTANCE);
FormBuilder.addMultilineLabel(gridPane, gridRow, getInfoText(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
}
protected String getInfoText() {
@ -446,7 +446,7 @@ public abstract class TradeStepView extends AnchorPane {
infoGridPane.setHgap(5);
infoGridPane.setVgap(10);
infoGridPane.setPadding(new Insets(10, 10, 10, 10));
Label label = addMultilineLabel(infoGridPane, rowIndex++, Res.get("portfolio.pending.tradePeriodInfo"));
Label label = FormBuilder.addMultilineLabel(infoGridPane, rowIndex++, Res.get("portfolio.pending.tradePeriodInfo"));
label.setMaxWidth(450);
HBox warningBox = new HBox();

View File

@ -45,6 +45,7 @@ import bisq.desktop.components.paymentmethods.WesternUnionForm;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.core.payment.payload.CashDepositAccountPayload;
@ -70,10 +71,6 @@ import javafx.scene.layout.GridPane;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup;
import static bisq.desktop.util.FormBuilder.addLabelTextFieldWithCopyIcon;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
public class BuyerStep2View extends TradeStepView {
private Button confirmButton;
@ -175,10 +172,10 @@ public class BuyerStep2View extends TradeStepView {
PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
String paymentMethodId = paymentAccountPayload != null ? paymentAccountPayload.getPaymentMethodId() : "";
TitledGroupBg accountTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 1,
TitledGroupBg accountTitledGroupBg = FormBuilder.addTitledGroupBg(gridPane, ++gridRow, 1,
Res.get("portfolio.pending.step2_buyer.startPaymentUsing", Res.get(paymentMethodId)),
Layout.GROUP_DISTANCE);
TextFieldWithCopyIcon field = addLabelTextFieldWithCopyIcon(gridPane, gridRow, Res.get("portfolio.pending.step2_buyer.amountToTransfer"),
TextFieldWithCopyIcon field = FormBuilder.addLabelTextFieldWithCopyIcon(gridPane, gridRow, Res.get("portfolio.pending.step2_buyer.amountToTransfer"),
model.getFiatVolume(),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
field.setCopyWithoutCurrencyPostFix(true);
@ -257,12 +254,12 @@ public class BuyerStep2View extends TradeStepView {
}
if (!(paymentAccountPayload instanceof CryptoCurrencyAccountPayload))
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow,
FormBuilder.addLabelTextFieldWithCopyIcon(gridPane, ++gridRow,
Res.getWithCol("shared.reasonForPayment"), model.dataModel.getReference());
GridPane.setRowSpan(accountTitledGroupBg, gridRow - 3);
Tuple3<Button, BusyAnimation, Label> tuple3 = addButtonBusyAnimationLabelAfterGroup(gridPane, ++gridRow,
Tuple3<Button, BusyAnimation, Label> tuple3 = FormBuilder.addButtonBusyAnimationLabelAfterGroup(gridPane, ++gridRow,
Res.get("portfolio.pending.step2_buyer.paymentStarted"));
confirmButton = tuple3.first;
confirmButton.setOnAction(e -> onPaymentStarted());

View File

@ -29,6 +29,7 @@ import bisq.desktop.main.portfolio.closedtrades.ClosedTradesView;
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
import bisq.desktop.util.BSFormatter;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BtcAddressValidator;
@ -63,10 +64,6 @@ import org.spongycastle.crypto.params.KeyParameter;
import java.util.concurrent.TimeUnit;
import static bisq.desktop.util.FormBuilder.addLabelInputTextField;
import static bisq.desktop.util.FormBuilder.addLabelTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
public class BuyerStep4View extends TradeStepView {
// private final ChangeListener<Boolean> focusedPropertyListener;
@ -126,19 +123,19 @@ public class BuyerStep4View extends TradeStepView {
@SuppressWarnings("PointlessBooleanExpression")
@Override
protected void addContent() {
addTitledGroupBg(gridPane, gridRow, 5, Res.get("portfolio.pending.step5_buyer.groupTitle"), 0);
addLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.FIRST_ROW_DISTANCE);
FormBuilder.addTitledGroupBg(gridPane, gridRow, 5, Res.get("portfolio.pending.step5_buyer.groupTitle"), 0);
FormBuilder.addLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.FIRST_ROW_DISTANCE);
addLabelTextField(gridPane, ++gridRow, getFiatTradeAmountLabel(), model.getFiatVolume());
addLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.refunded"), model.getSecurityDeposit());
addLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.tradeFee"), model.getTradeFee());
FormBuilder.addLabelTextField(gridPane, ++gridRow, getFiatTradeAmountLabel(), model.getFiatVolume());
FormBuilder.addLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.refunded"), model.getSecurityDeposit());
FormBuilder.addLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.tradeFee"), model.getTradeFee());
final String miningFee = model.dataModel.isMaker() ?
Res.get("portfolio.pending.step5_buyer.makersMiningFee") :
Res.get("portfolio.pending.step5_buyer.takersMiningFee");
addLabelTextField(gridPane, ++gridRow, miningFee, model.getTxFee());
withdrawTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 1, Res.get("portfolio.pending.step5_buyer.withdrawBTC"), Layout.GROUP_DISTANCE);
addLabelTextField(gridPane, gridRow, Res.get("portfolio.pending.step5_buyer.amount"), model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
final Tuple2<Label, InputTextField> tuple2 = addLabelInputTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.withdrawToAddress"));
FormBuilder.addLabelTextField(gridPane, ++gridRow, miningFee, model.getTxFee());
withdrawTitledGroupBg = FormBuilder.addTitledGroupBg(gridPane, ++gridRow, 1, Res.get("portfolio.pending.step5_buyer.withdrawBTC"), Layout.GROUP_DISTANCE);
FormBuilder.addLabelTextField(gridPane, gridRow, Res.get("portfolio.pending.step5_buyer.amount"), model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
final Tuple2<Label, InputTextField> tuple2 = FormBuilder.addLabelInputTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.withdrawToAddress"));
withdrawAddressLabel = tuple2.first;
withdrawAddressLabel.setManaged(false);
withdrawAddressLabel.setVisible(false);

View File

@ -42,7 +42,6 @@ import static bisq.common.locale.TradeCurrencyMakers.usd;
import static bisq.core.user.PreferenceMakers.empty;
import static bisq.desktop.main.offer.offerbook.OfferBookListItemMaker.btcItem;
import static bisq.desktop.main.offer.offerbook.OfferBookListItemMaker.btcSellItem;
import static bisq.desktop.main.offer.offerbook.OfferBookListItemMaker.useMarketBasedPrice;
import static com.natpryce.makeiteasy.MakeItEasy.make;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static org.junit.Assert.assertEquals;
@ -77,7 +76,7 @@ public class OfferBookChartViewModelTest {
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
final OfferBookListItem item = make(btcItem.but(with(useMarketBasedPrice, true)));
final OfferBookListItem item = make(OfferBookListItemMaker.btcItem.but(with(OfferBookListItemMaker.useMarketBasedPrice, true)));
item.getOffer().setPriceFeedService(priceFeedService);
offerBookListItems.addAll(item);
@ -95,7 +94,7 @@ public class OfferBookChartViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
PriceFeedService service = mock(PriceFeedService.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -124,7 +123,7 @@ public class OfferBookChartViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
PriceFeedService service = mock(PriceFeedService.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -155,7 +154,7 @@ public class OfferBookChartViewModelTest {
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
final OfferBookListItem item = make(btcSellItem.but(with(useMarketBasedPrice, true)));
final OfferBookListItem item = make(OfferBookListItemMaker.btcSellItem.but(with(OfferBookListItemMaker.useMarketBasedPrice, true)));
item.getOffer().setPriceFeedService(priceFeedService);
offerBookListItems.addAll(item);
@ -173,7 +172,7 @@ public class OfferBookChartViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
PriceFeedService service = mock(PriceFeedService.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcSellItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcSellItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -202,7 +201,7 @@ public class OfferBookChartViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
PriceFeedService service = mock(PriceFeedService.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcSellItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcSellItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);

View File

@ -58,7 +58,7 @@ public class SpreadViewModelTest {
public void testMaxCharactersForAmount() {
OfferBook offerBook = mock(OfferBook.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);

View File

@ -21,6 +21,7 @@ import bisq.core.offer.OfferMaker;
import bisq.core.offer.OfferPayload;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.MakeItEasy;
import com.natpryce.makeiteasy.Maker;
import com.natpryce.makeiteasy.Property;
@ -40,7 +41,7 @@ public class OfferBookListItemMaker {
public static final Instantiator<OfferBookListItem> OfferBookListItem = lookup ->
new OfferBookListItem(make(btcUsdOffer.but(
with(OfferMaker.price, lookup.valueOf(price, 100000L)),
MakeItEasy.with(OfferMaker.price, lookup.valueOf(price, 100000L)),
with(OfferMaker.amount, lookup.valueOf(amount, 100000L)),
with(OfferMaker.minAmount, lookup.valueOf(amount, 100000L)),
with(OfferMaker.direction, lookup.valueOf(direction, OfferPayload.Direction.BUY)),
@ -49,7 +50,7 @@ public class OfferBookListItemMaker {
public static final Instantiator<OfferBookListItem> OfferBookListItemWithRange = lookup ->
new OfferBookListItem(make(btcUsdOffer.but(
with(OfferMaker.price, lookup.valueOf(price, 100000L)),
MakeItEasy.with(OfferMaker.price, lookup.valueOf(price, 100000L)),
with(OfferMaker.minAmount, lookup.valueOf(minAmount, 100000L)),
with(OfferMaker.amount, lookup.valueOf(amount, 200000L)))));

View File

@ -243,7 +243,7 @@ public class OfferBookViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
OpenOfferManager openOfferManager = mock(OpenOfferManager.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -262,7 +262,7 @@ public class OfferBookViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
OpenOfferManager openOfferManager = mock(OpenOfferManager.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItemWithRange));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItemWithRange));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -297,7 +297,7 @@ public class OfferBookViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
OpenOfferManager openOfferManager = mock(OpenOfferManager.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -316,7 +316,7 @@ public class OfferBookViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
OpenOfferManager openOfferManager = mock(OpenOfferManager.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItemWithRange));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItemWithRange));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
@ -351,7 +351,7 @@ public class OfferBookViewModelTest {
OfferBook offerBook = mock(OfferBook.class);
OpenOfferManager openOfferManager = mock(OpenOfferManager.class);
final ObservableList<OfferBookListItem> offerBookListItems = FXCollections.observableArrayList();
offerBookListItems.addAll(make(btcItem));
offerBookListItems.addAll(make(OfferBookListItemMaker.btcItem));
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);