remove DAO

Co-authored-by: premek <1145361+premek@users.noreply.github.com>
This commit is contained in:
l0nelyc0w 2021-10-19 20:45:55 +03:00 committed by woodser
parent f9f2cd07c3
commit cefba8e4b5
621 changed files with 583 additions and 68805 deletions

View file

@ -23,7 +23,6 @@ import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.main.MainView;
import bisq.desktop.main.debug.DebugView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.BsqEmptyWalletWindow;
import bisq.desktop.main.overlays.windows.BtcEmptyWalletWindow;
import bisq.desktop.main.overlays.windows.FilterWindow;
import bisq.desktop.main.overlays.windows.ManualPayoutTxWindow;
@ -34,7 +33,6 @@ import bisq.desktop.util.ImageUtil;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.dao.governance.voteresult.MissingDataRequestService;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
@ -313,16 +311,12 @@ public class BisqApp extends Application implements UncaughtExceptionHandler {
} else {
if (Utilities.isAltOrCtrlPressed(KeyCode.E, keyEvent)) {
injector.getInstance(BtcEmptyWalletWindow.class).show();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.B, keyEvent)) {
injector.getInstance(BsqEmptyWalletWindow.class).show();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.M, keyEvent)) {
injector.getInstance(SendAlertMessageWindow.class).show();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.F, keyEvent)) {
injector.getInstance(FilterWindow.class).show();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.H, keyEvent)) {
log.warn("We re-published all proposalPayloads and blindVotePayloads to the P2P network.");
injector.getInstance(MissingDataRequestService.class).reRepublishAllGovernanceData();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.T, keyEvent)) {
}
else if (Utilities.isAltOrCtrlPressed(KeyCode.T, keyEvent)) {
// Toggle between show tor logs and only show warnings. Helpful in case of connection problems
String pattern = "org.berndpruenster.netlayer";
Level logLevel = ((Logger) LoggerFactory.getLogger(pattern)).getLevel();

View file

@ -1,135 +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.components;
import bisq.desktop.main.overlays.notifications.Notification;
import bisq.desktop.util.GUIUtil;
import bisq.core.locale.Res;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.AnchorPane;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class BsqAddressTextField extends AnchorPane {
private final StringProperty address = new SimpleStringProperty();
private final StringProperty paymentLabel = new SimpleStringProperty();
private final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>(Coin.ZERO);
private boolean wasPrimaryButtonDown;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public BsqAddressTextField() {
TextField textField = new BisqTextField();
textField.setId("address-text-field");
textField.setEditable(false);
textField.textProperty().bind(address);
String tooltipText = Res.get("addressTextField.copyToClipboard");
textField.setTooltip(new Tooltip(tooltipText));
textField.setOnMousePressed(event -> wasPrimaryButtonDown = event.isPrimaryButtonDown());
textField.setOnMouseReleased(event -> {
if (wasPrimaryButtonDown && address.get() != null && address.get().length() > 0) {
Utilities.copyToClipboard(address.get());
Notification walletFundedNotification = new Notification()
.notification(Res.get("addressTextField.addressCopiedToClipboard"))
.hideCloseButton()
.autoClose();
walletFundedNotification.show();
}
wasPrimaryButtonDown = false;
});
textField.focusTraversableProperty().set(focusTraversableProperty().get());
Label copyIcon = new Label();
copyIcon.setLayoutY(3);
copyIcon.getStyleClass().addAll("icon", "highlight");
copyIcon.setTooltip(new Tooltip(Res.get("addressTextField.copyToClipboard")));
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
copyIcon.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(() -> {
if (address.get() != null && address.get().length() > 0)
Utilities.copyToClipboard(address.get());
}));
AnchorPane.setRightAnchor(copyIcon, 5.0);
AnchorPane.setRightAnchor(textField, 30.0);
AnchorPane.setLeftAnchor(textField, 0.0);
getChildren().addAll(textField, copyIcon);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters/Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setAddress(String address) {
this.address.set(address);
}
public String getAddress() {
return address.get();
}
public StringProperty addressProperty() {
return address;
}
public Coin getAmountAsCoin() {
return amountAsCoin.get();
}
public ObjectProperty<Coin> amountAsCoinProperty() {
return amountAsCoin;
}
public void setAmountAsCoin(Coin amountAsCoin) {
this.amountAsCoin.set(amountAsCoin);
}
public String getPaymentLabel() {
return paymentLabel.get();
}
public StringProperty paymentLabelProperty() {
return paymentLabel;
}
public void setPaymentLabel(String paymentLabel) {
this.paymentLabel.set(paymentLabel);
}
}

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.components;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.locale.Res;
import com.jfoenix.controls.JFXProgressBar;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.geometry.Pos;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SeparatedPhaseBars extends VBox {
// Last day for creating github compensation request issue, as decided by general consensus
private static final double LAST_COMP_REQ_GH_ISSUE = (double) 18 / 25;
private double labelMinWidth = 150;
private double breakMinWidth = 20;
private int totalDuration;
private List<SeparatedPhaseBarsItem> items;
public SeparatedPhaseBars(List<SeparatedPhaseBarsItem> items) {
this.items = items;
setSpacing(10);
HBox titlesBars = new HBox();
titlesBars.setSpacing(5);
getChildren().add(titlesBars);
HBox progressBars = new HBox();
progressBars.setSpacing(5);
getChildren().add(progressBars);
items.forEach(item -> {
String text = item.phase.name().startsWith("BREAK") ? "" : Res.get("dao.phase.separatedPhaseBar." + item.phase);
Label titleLabel = new Label(text);
titleLabel.setEllipsisString("");
titleLabel.setAlignment(Pos.CENTER);
item.setTitleLabel(titleLabel);
titlesBars.getChildren().addAll(titleLabel);
JFXProgressBar progressBar = new JFXProgressBar();
progressBar.setMinHeight(9);
progressBar.setMaxHeight(9);
progressBar.progressProperty().bind(item.progressProperty);
progressBar.setOpacity(item.isShowBlocks() ? 1 : 0.25);
if (item.phase.name().startsWith("PROPOSAL")) {
progressBar.setSecondaryProgress(LAST_COMP_REQ_GH_ISSUE);
}
progressBars.getChildren().add(progressBar);
item.setProgressBar(progressBar);
});
widthProperty().addListener((observable, oldValue, newValue) -> {
updateWidth((double) newValue);
});
}
public void updateWidth() {
updateWidth(getWidth());
}
private void updateWidth(double availableWidth) {
if (availableWidth > 0) {
totalDuration = items.stream().mapToInt(SeparatedPhaseBarsItem::getDuration).sum();
if (totalDuration > 0) {
// We want to have a min. width for the breaks and for the phases which are important to the user but
// quite short (blind vote, vote reveal, result). If we display it correctly most of the space is
// consumed by the proposal phase. We apply a min and max width and adjust the available width so
// we have all phases displayed so that the text is fully readable. The proposal phase is shorter as
// it would be with correct display but we take that into account to have a better overall overview.
final double finalAvailableWidth = availableWidth;
AtomicReference<Double> adjustedAvailableWidth = new AtomicReference<>(availableWidth);
items.forEach(item -> {
double calculatedWidth = (double) item.duration / (double) totalDuration * finalAvailableWidth;
double minWidth = item.phase.name().startsWith("BREAK") ? breakMinWidth : labelMinWidth;
double maxWidth = item.phase.name().startsWith("BREAK") ? breakMinWidth : calculatedWidth;
if (calculatedWidth < minWidth) {
double missing = minWidth - calculatedWidth;
adjustedAvailableWidth.set(adjustedAvailableWidth.get() - missing);
} else if (calculatedWidth > maxWidth) {
double remaining = calculatedWidth - maxWidth;
adjustedAvailableWidth.set(adjustedAvailableWidth.get() + remaining);
}
});
items.forEach(item -> {
double calculatedWidth = (double) item.duration / (double) totalDuration * adjustedAvailableWidth.get();
double minWidth = item.phase.name().startsWith("BREAK") ? breakMinWidth : labelMinWidth;
double maxWidth = item.phase.name().startsWith("BREAK") ? breakMinWidth : calculatedWidth;
double width = calculatedWidth;
if (calculatedWidth < minWidth) {
width = minWidth;
} else if (calculatedWidth > maxWidth) {
width = maxWidth;
}
item.getTitleLabel().setPrefWidth(width);
item.getProgressBar().setPrefWidth(width);
});
}
}
}
@Getter
public static class SeparatedPhaseBarsItem {
private final DaoPhase.Phase phase;
private final boolean showBlocks;
private final IntegerProperty startBlockProperty = new SimpleIntegerProperty();
private final IntegerProperty lastBlockProperty = new SimpleIntegerProperty();
private final DoubleProperty progressProperty = new SimpleDoubleProperty();
private int duration;
@Setter
private javafx.scene.layout.VBox progressVBox;
@Setter
private Label titleLabel;
@Setter
private ProgressBar progressBar;
@Setter
private int indicatorBlock;
private ProgressBar indicatorBar;
public SeparatedPhaseBarsItem(DaoPhase.Phase phase, boolean showBlocks) {
this.phase = phase;
this.showBlocks = showBlocks;
}
public void setInActive() {
titleLabel.getStyleClass().add("separated-phase-bar-inactive");
}
public void setActive() {
titleLabel.getStyleClass().add("separated-phase-bar-active");
}
public void setPeriodRange(int firstBlock, int lastBlock, int duration) {
startBlockProperty.set(firstBlock);
lastBlockProperty.set(lastBlock);
this.duration = duration;
}
}
}

View file

@ -21,7 +21,6 @@ import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
@ -33,15 +32,12 @@ import lombok.Data;
@Data
public class TxConfidenceListItem {
protected final BsqWalletService bsqWalletService;
protected final String txId;
protected int confirmations = 0;
protected TxConfidenceIndicator txConfidenceIndicator;
protected TxConfidenceListener txConfidenceListener;
protected TxConfidenceListItem(Transaction transaction,
BsqWalletService bsqWalletService) {
this.bsqWalletService = bsqWalletService;
protected TxConfidenceListItem(Transaction transaction) {
txId = transaction.getTxId().toString();
txConfidenceIndicator = new TxConfidenceIndicator();
@ -57,12 +53,9 @@ public class TxConfidenceListItem {
updateConfidence(confidence, tooltip);
}
};
bsqWalletService.addTxConfidenceListener(txConfidenceListener);
updateConfidence(bsqWalletService.getConfidenceForTxId(txId), tooltip);
}
protected TxConfidenceListItem() {
this.bsqWalletService = null;
this.txId = null;
}
@ -74,7 +67,6 @@ public class TxConfidenceListItem {
}
public void cleanup() {
bsqWalletService.removeTxConfidenceListener(txConfidenceListener);
}
}

View file

@ -64,8 +64,6 @@ public class TxIdTextField extends AnchorPane {
private final TxConfidenceIndicator txConfidenceIndicator;
private final Label copyIcon, blockExplorerIcon, missingTxWarningIcon;
private TxConfidenceListener txConfidenceListener;
@Setter
private boolean isBsq;
///////////////////////////////////////////////////////////////////////////////////////////
@ -170,9 +168,7 @@ public class TxIdTextField extends AnchorPane {
private void openBlockExplorer(String txId) {
if (preferences != null) {
BlockChainExplorer blockChainExplorer = isBsq ?
preferences.getBsqBlockChainExplorer() :
preferences.getBlockChainExplorer();
BlockChainExplorer blockChainExplorer = preferences.getBlockChainExplorer();
GUIUtil.openWebPage(blockChainExplorer.txUrl + txId, false);
}
}

View file

@ -1,774 +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.components.chart;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.components.AutoTooltipSlideToggleButton;
import bisq.desktop.components.AutoTooltipToggleButton;
import bisq.core.locale.Res;
import bisq.common.UserThread;
import javafx.stage.PopupWindow;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.Duration;
import java.time.temporal.TemporalAdjuster;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
public abstract class ChartView<T extends ChartViewModel<? extends ChartDataModel>> extends ActivatableViewAndModel<VBox, T> {
private Pane center;
private SplitPane timelineNavigation;
protected NumberAxis xAxis, yAxis;
protected LineChart<Number, Number> chart;
private HBox timelineLabels, legendBox2;
private final ToggleGroup timeIntervalToggleGroup = new ToggleGroup();
protected final Set<XYChart.Series<Number, Number>> activeSeries = new HashSet<>();
protected final Map<String, Integer> seriesIndexMap = new HashMap<>();
protected final Map<String, AutoTooltipSlideToggleButton> legendToggleBySeriesName = new HashMap<>();
private final List<Node> dividerNodes = new ArrayList<>();
private final List<Tooltip> dividerNodesTooltips = new ArrayList<>();
private ChangeListener<Number> widthListener;
private ChangeListener<Toggle> timeIntervalChangeListener;
private ListChangeListener<Node> nodeListChangeListener;
private int maxSeriesSize;
private boolean centerPanePressed;
private double x;
@Setter
protected boolean isRadioButtonBehaviour;
@Setter
private int maxDataPointsForShowingSymbols = 100;
private ChangeListener<Number> yAxisWidthListener;
private EventHandler<MouseEvent> dividerMouseDraggedEventHandler;
private StringProperty fromProperty = new SimpleStringProperty();
private StringProperty toProperty = new SimpleStringProperty();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public ChartView(T model) {
super(model);
root = new VBox();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize() {
// We need to call prepareInitialize as we are not using FXMLLoader
prepareInitialize();
maxSeriesSize = 0;
centerPanePressed = false;
x = 0;
// Series
createSeries();
// Time interval
HBox timeIntervalBox = getTimeIntervalBox();
// chart
xAxis = getXAxis();
yAxis = getYAxis();
chart = getChart();
// Timeline navigation
addTimelineNavigation();
// Legend
HBox legendBox1 = initLegendsAndGetLegendBox(getSeriesForLegend1());
Collection<XYChart.Series<Number, Number>> seriesForLegend2 = getSeriesForLegend2();
if (seriesForLegend2 != null && !seriesForLegend2.isEmpty()) {
legendBox2 = initLegendsAndGetLegendBox(seriesForLegend2);
}
// Set active series/legends
defineAndAddActiveSeries();
// Put all together
VBox timelineNavigationBox = new VBox();
double paddingLeft = 15;
double paddingRight = 89;
// Y-axis width depends on data so we register a listener to get correct value
yAxisWidthListener = (observable, oldValue, newValue) -> {
double width = newValue.doubleValue();
if (width > 0) {
double rightPadding = width + 14;
VBox.setMargin(timeIntervalBox, new Insets(0, rightPadding, 0, paddingLeft));
VBox.setMargin(timelineNavigation, new Insets(0, rightPadding, 0, paddingLeft));
VBox.setMargin(timelineLabels, new Insets(0, rightPadding, 0, paddingLeft));
VBox.setMargin(legendBox1, new Insets(10, rightPadding, 0, paddingLeft));
if (legendBox2 != null) {
VBox.setMargin(legendBox2, new Insets(-20, rightPadding, 0, paddingLeft));
}
if (model.getDividerPositions()[0] == 0 && model.getDividerPositions()[1] == 1) {
resetTimeNavigation();
}
}
};
VBox.setMargin(timeIntervalBox, new Insets(0, paddingRight, 0, paddingLeft));
VBox.setMargin(timelineNavigation, new Insets(0, paddingRight, 0, paddingLeft));
VBox.setMargin(timelineLabels, new Insets(0, paddingRight, 0, paddingLeft));
VBox.setMargin(legendBox1, new Insets(0, paddingRight, 0, paddingLeft));
timelineNavigationBox.getChildren().addAll(timelineNavigation, timelineLabels, legendBox1);
if (legendBox2 != null) {
VBox.setMargin(legendBox2, new Insets(-20, paddingRight, 0, paddingLeft));
timelineNavigationBox.getChildren().add(legendBox2);
}
root.getChildren().addAll(timeIntervalBox, chart, timelineNavigationBox);
// Listeners
widthListener = (observable, oldValue, newValue) -> {
timelineNavigation.setDividerPosition(0, model.getDividerPositions()[0]);
timelineNavigation.setDividerPosition(1, model.getDividerPositions()[1]);
};
timeIntervalChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
onTimeIntervalChanged(newValue);
}
};
nodeListChangeListener = c -> {
while (c.next()) {
if (c.wasAdded()) {
c.getAddedSubList().stream()
.filter(node -> node instanceof Text)
.forEach(node -> node.getStyleClass().add("axis-tick-mark-text-node"));
}
}
};
}
@Override
public void activate() {
timelineNavigation.setDividerPositions(model.getDividerPositions()[0], model.getDividerPositions()[1]);
UserThread.execute(this::applyTimeLineNavigationLabels);
UserThread.execute(this::onTimelineChanged);
TemporalAdjuster temporalAdjuster = model.getTemporalAdjuster();
applyTemporalAdjuster(temporalAdjuster);
findTimeIntervalToggleByTemporalAdjuster(temporalAdjuster).ifPresent(timeIntervalToggleGroup::selectToggle);
defineAndAddActiveSeries();
applyData();
initBoundsForTimelineNavigation();
updateChartAfterDataChange();
// Apply listeners and handlers
root.widthProperty().addListener(widthListener);
xAxis.getChildrenUnmodifiable().addListener(nodeListChangeListener);
yAxis.widthProperty().addListener(yAxisWidthListener);
timeIntervalToggleGroup.selectedToggleProperty().addListener(timeIntervalChangeListener);
timelineNavigation.setOnMousePressed(this::onMousePressedSplitPane);
timelineNavigation.setOnMouseDragged(this::onMouseDragged);
center.setOnMousePressed(this::onMousePressedCenter);
center.setOnMouseReleased(this::onMouseReleasedCenter);
addLegendToggleActionHandlers(getSeriesForLegend1());
addLegendToggleActionHandlers(getSeriesForLegend2());
addActionHandlersToDividers();
}
@Override
public void deactivate() {
root.widthProperty().removeListener(widthListener);
xAxis.getChildrenUnmodifiable().removeListener(nodeListChangeListener);
yAxis.widthProperty().removeListener(yAxisWidthListener);
timeIntervalToggleGroup.selectedToggleProperty().removeListener(timeIntervalChangeListener);
timelineNavigation.setOnMousePressed(null);
timelineNavigation.setOnMouseDragged(null);
center.setOnMousePressed(null);
center.setOnMouseReleased(null);
removeLegendToggleActionHandlers(getSeriesForLegend1());
removeLegendToggleActionHandlers(getSeriesForLegend2());
removeActionHandlersToDividers();
// clear data, reset states. We keep timeInterval state though
activeSeries.clear();
chart.getData().clear();
legendToggleBySeriesName.values().forEach(e -> e.setSelected(false));
dividerNodes.clear();
dividerNodesTooltips.clear();
model.invalidateCache();
}
///////////////////////////////////////////////////////////////////////////////////////////
// TimeInterval/TemporalAdjuster
///////////////////////////////////////////////////////////////////////////////////////////
protected HBox getTimeIntervalBox() {
ToggleButton year = getTimeIntervalToggleButton(Res.get("time.year"), TemporalAdjusterModel.Interval.YEAR,
timeIntervalToggleGroup, "toggle-left");
ToggleButton month = getTimeIntervalToggleButton(Res.get("time.month"), TemporalAdjusterModel.Interval.MONTH,
timeIntervalToggleGroup, "toggle-center");
ToggleButton week = getTimeIntervalToggleButton(Res.get("time.week"), TemporalAdjusterModel.Interval.WEEK,
timeIntervalToggleGroup, "toggle-center");
ToggleButton day = getTimeIntervalToggleButton(Res.get("time.day"), TemporalAdjusterModel.Interval.DAY,
timeIntervalToggleGroup, "toggle-center");
HBox toggleBox = new HBox();
toggleBox.setSpacing(0);
toggleBox.setAlignment(Pos.CENTER_LEFT);
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
toggleBox.getChildren().addAll(spacer, year, month, week, day);
return toggleBox;
}
private ToggleButton getTimeIntervalToggleButton(String label,
TemporalAdjusterModel.Interval interval,
ToggleGroup toggleGroup,
String style) {
ToggleButton toggleButton = new AutoTooltipToggleButton(label);
toggleButton.setUserData(interval);
toggleButton.setToggleGroup(toggleGroup);
toggleButton.setId(style);
return toggleButton;
}
protected void applyTemporalAdjuster(TemporalAdjuster temporalAdjuster) {
model.applyTemporalAdjuster(temporalAdjuster);
findTimeIntervalToggleByTemporalAdjuster(temporalAdjuster)
.map(e -> (TemporalAdjusterModel.Interval) e.getUserData())
.ifPresent(model::setDateFormatPattern);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart
///////////////////////////////////////////////////////////////////////////////////////////
protected NumberAxis getXAxis() {
NumberAxis xAxis = new NumberAxis();
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(true);
xAxis.setTickLabelFormatter(model.getTimeAxisStringConverter());
return xAxis;
}
protected NumberAxis getYAxis() {
NumberAxis yAxis = new NumberAxis();
yAxis.setForceZeroInRange(true);
yAxis.setSide(Side.RIGHT);
yAxis.setTickLabelFormatter(model.getYAxisStringConverter());
return yAxis;
}
// Add implementation if update of the y axis is required at series change
protected void onSetYAxisFormatter(XYChart.Series<Number, Number> series) {
}
protected LineChart<Number, Number> getChart() {
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setAnimated(false);
chart.setLegendVisible(false);
chart.setMinHeight(200);
chart.setId("charts-dao");
return chart;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////////////////////////////////////
protected HBox initLegendsAndGetLegendBox(Collection<XYChart.Series<Number, Number>> collection) {
HBox hBox = new HBox();
hBox.setSpacing(10);
collection.forEach(series -> {
AutoTooltipSlideToggleButton toggle = new AutoTooltipSlideToggleButton();
toggle.setMinWidth(200);
toggle.setAlignment(Pos.TOP_LEFT);
String seriesId = getSeriesId(series);
legendToggleBySeriesName.put(seriesId, toggle);
toggle.setText(seriesId);
toggle.setId("charts-legend-toggle" + seriesIndexMap.get(seriesId));
toggle.setSelected(false);
hBox.getChildren().add(toggle);
});
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().add(spacer);
return hBox;
}
private void addLegendToggleActionHandlers(@Nullable Collection<XYChart.Series<Number, Number>> collection) {
if (collection != null) {
collection.forEach(series ->
legendToggleBySeriesName.get(getSeriesId(series)).setOnAction(e -> onSelectLegendToggle(series)));
}
}
private void removeLegendToggleActionHandlers(@Nullable Collection<XYChart.Series<Number, Number>> collection) {
if (collection != null) {
collection.forEach(series ->
legendToggleBySeriesName.get(getSeriesId(series)).setOnAction(null));
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Timeline navigation
///////////////////////////////////////////////////////////////////////////////////////////
private void addTimelineNavigation() {
Pane left = new Pane();
center = new Pane();
center.setId("chart-navigation-center-pane");
Pane right = new Pane();
timelineNavigation = new SplitPane(left, center, right);
timelineNavigation.setDividerPositions(model.getDividerPositions()[0], model.getDividerPositions()[1]);
timelineNavigation.setMinHeight(25);
timelineLabels = new HBox();
}
// After initial chart data are created we apply the text from the x-axis ticks to our timeline navigation.
protected void applyTimeLineNavigationLabels() {
timelineLabels.getChildren().clear();
ObservableList<Axis.TickMark<Number>> tickMarks = xAxis.getTickMarks();
int size = tickMarks.size();
for (int i = 0; i < size; i++) {
Axis.TickMark<Number> tickMark = tickMarks.get(i);
Number xValue = tickMark.getValue();
String xValueString;
if (xAxis.getTickLabelFormatter() != null) {
xValueString = xAxis.getTickLabelFormatter().toString(xValue);
} else {
xValueString = String.valueOf(xValue);
}
Label label = new Label(xValueString);
label.setMinHeight(30);
label.setId("chart-navigation-label");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
if (i < size - 1) {
timelineLabels.getChildren().addAll(label, spacer);
} else {
// After last label we don't add a spacer
timelineLabels.getChildren().add(label);
}
}
}
private void onMousePressedSplitPane(MouseEvent e) {
x = e.getX();
applyFromToDates();
showDividerTooltips();
}
private void onMousePressedCenter(MouseEvent e) {
centerPanePressed = true;
applyFromToDates();
showDividerTooltips();
}
private void onMouseReleasedCenter(MouseEvent e) {
centerPanePressed = false;
onTimelineChanged();
hideDividerTooltips();
}
private void onMouseDragged(MouseEvent e) {
if (centerPanePressed) {
double newX = e.getX();
double width = timelineNavigation.getWidth();
double relativeDelta = (x - newX) / width;
double leftPos = timelineNavigation.getDividerPositions()[0] - relativeDelta;
double rightPos = timelineNavigation.getDividerPositions()[1] - relativeDelta;
// Model might limit application of new values if we hit a boundary
model.onTimelineMouseDrag(leftPos, rightPos);
timelineNavigation.setDividerPositions(model.getDividerPositions()[0], model.getDividerPositions()[1]);
x = newX;
applyFromToDates();
showDividerTooltips();
}
}
private void addActionHandlersToDividers() {
// No API access to dividers ;-( only via css lookup hack (https://stackoverflow.com/questions/40707295/how-to-add-listener-to-divider-position?rq=1)
// Need to be done after added to scene and call requestLayout and applyCss. We keep it in a list atm
// and set action handler in activate.
timelineNavigation.requestLayout();
timelineNavigation.applyCss();
dividerMouseDraggedEventHandler = event -> {
applyFromToDates();
showDividerTooltips();
};
for (Node node : timelineNavigation.lookupAll(".split-pane-divider")) {
dividerNodes.add(node);
node.setOnMouseReleased(e -> {
hideDividerTooltips();
onTimelineChanged();
});
node.addEventHandler(MouseEvent.MOUSE_DRAGGED, dividerMouseDraggedEventHandler);
Tooltip tooltip = new Tooltip("");
dividerNodesTooltips.add(tooltip);
tooltip.setShowDelay(Duration.millis(300));
tooltip.setShowDuration(Duration.seconds(3));
tooltip.textProperty().bind(dividerNodes.size() == 1 ? fromProperty : toProperty);
Tooltip.install(node, tooltip);
}
}
private void removeActionHandlersToDividers() {
dividerNodes.forEach(node -> {
node.setOnMouseReleased(null);
node.removeEventHandler(MouseEvent.MOUSE_DRAGGED, dividerMouseDraggedEventHandler);
});
for (int i = 0; i < dividerNodesTooltips.size(); i++) {
Tooltip tooltip = dividerNodesTooltips.get(i);
tooltip.textProperty().unbind();
Tooltip.uninstall(dividerNodes.get(i), tooltip);
}
}
private void resetTimeNavigation() {
timelineNavigation.setDividerPositions(0d, 1d);
model.onTimelineNavigationChanged(0, 1);
}
private void showDividerTooltips() {
showDividerTooltip(0);
showDividerTooltip(1);
}
private void hideDividerTooltips() {
dividerNodesTooltips.forEach(PopupWindow::hide);
}
private void showDividerTooltip(int index) {
Node divider = dividerNodes.get(index);
Bounds bounds = divider.localToScene(divider.getBoundsInLocal());
Tooltip tooltip = dividerNodesTooltips.get(index);
double xOffset = index == 0 ? -90 : 10;
Stage stage = (Stage) root.getScene().getWindow();
tooltip.show(stage, stage.getX() + bounds.getMaxX() + xOffset,
stage.getY() + bounds.getMaxY() - 40);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Series
///////////////////////////////////////////////////////////////////////////////////////////
protected abstract void createSeries();
protected abstract Collection<XYChart.Series<Number, Number>> getSeriesForLegend1();
// If a second legend is used this has to be overridden
protected Collection<XYChart.Series<Number, Number>> getSeriesForLegend2() {
return null;
}
protected abstract void defineAndAddActiveSeries();
protected void activateSeries(XYChart.Series<Number, Number> series) {
if (activeSeries.contains(series)) {
return;
}
chart.getData().add(series);
activeSeries.add(series);
legendToggleBySeriesName.get(getSeriesId(series)).setSelected(true);
updateChartAfterDataChange();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
protected abstract void applyData();
/**
* Implementations define which series will be used for setBoundsForTimelineNavigation
*/
protected abstract void initBoundsForTimelineNavigation();
/**
* @param data The series data which determines the min/max x values for the time line navigation.
* If not applicable initBoundsForTimelineNavigation requires custom implementation.
*/
protected void setBoundsForTimelineNavigation(ObservableList<XYChart.Data<Number, Number>> data) {
model.initBounds(data);
xAxis.setLowerBound(model.getLowerBound().doubleValue());
xAxis.setUpperBound(model.getUpperBound().doubleValue());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handlers triggering a data/chart update
///////////////////////////////////////////////////////////////////////////////////////////
private void onTimeIntervalChanged(Toggle newValue) {
TemporalAdjusterModel.Interval interval = (TemporalAdjusterModel.Interval) newValue.getUserData();
applyTemporalAdjuster(interval.getAdjuster());
model.invalidateCache();
applyData();
updateChartAfterDataChange();
}
private void onTimelineChanged() {
updateTimeLinePositions();
model.invalidateCache();
applyData();
updateChartAfterDataChange();
}
private void updateTimeLinePositions() {
double leftPos = timelineNavigation.getDividerPositions()[0];
double rightPos = timelineNavigation.getDividerPositions()[1];
model.onTimelineNavigationChanged(leftPos, rightPos);
// We need to update as model might have adjusted the values
timelineNavigation.setDividerPositions(model.getDividerPositions()[0], model.getDividerPositions()[1]);
fromProperty.set(model.getTimeAxisStringConverter().toString(model.getFromDate()).replace("\n", " "));
toProperty.set(model.getTimeAxisStringConverter().toString(model.getToDate()).replace("\n", " "));
}
private void applyFromToDates() {
double leftPos = timelineNavigation.getDividerPositions()[0];
double rightPos = timelineNavigation.getDividerPositions()[1];
model.applyFromToDates(leftPos, rightPos);
fromProperty.set(model.getTimeAxisStringConverter().toString(model.getFromDate()).replace("\n", " "));
toProperty.set(model.getTimeAxisStringConverter().toString(model.getToDate()).replace("\n", " "));
}
private void onSelectLegendToggle(XYChart.Series<Number, Number> series) {
boolean isSelected = legendToggleBySeriesName.get(getSeriesId(series)).isSelected();
// If we have set that flag we deselect all other toggles
if (isRadioButtonBehaviour) {
new ArrayList<>(chart.getData()).stream() // We need to copy to a new list to avoid ConcurrentModificationException
.filter(activeSeries::contains)
.forEach(seriesToRemove -> {
chart.getData().remove(seriesToRemove);
String seriesId = getSeriesId(seriesToRemove);
activeSeries.remove(seriesToRemove);
legendToggleBySeriesName.get(seriesId).setSelected(false);
});
}
if (isSelected) {
chart.getData().add(series);
activeSeries.add(series);
//model.invalidateCache();
applyData();
if (isRadioButtonBehaviour) {
// We support different y-axis formats only if isRadioButtonBehaviour is set, otherwise we would get
// mixed data on y-axis
onSetYAxisFormatter(series);
}
} else if (!isRadioButtonBehaviour) { // if isRadioButtonBehaviour we have removed it already via the code above
chart.getData().remove(series);
activeSeries.remove(series);
}
updateChartAfterDataChange();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart update after data change
///////////////////////////////////////////////////////////////////////////////////////////
// Update of the chart data can be triggered by:
// 1. activate()
// 2. TimeInterval toggle change
// 3. Timeline navigation change
// 4. Legend/series toggle change
// Timeline navigation and legend/series toggles get reset at activate.
// Time interval toggle keeps its state at screen changes.
protected void updateChartAfterDataChange() {
// If a series got no data points after update we need to clear it from the chart
cleanupDanglingSeries();
// Hides symbols if too many data points are created
updateSymbolsVisibility();
// When series gets added/removed the JavaFx charts framework would try to apply styles by the index of
// addition, but we want to use a static color assignment which is synced with the legend color.
applySeriesStyles();
// Set tooltip on symbols
applyTooltip();
}
private void cleanupDanglingSeries() {
List<XYChart.Series<Number, Number>> activeSeriesList = new ArrayList<>(activeSeries);
activeSeriesList.forEach(series -> {
ObservableList<XYChart.Series<Number, Number>> seriesOnChart = chart.getData();
if (series.getData().isEmpty()) {
seriesOnChart.remove(series);
} else if (!seriesOnChart.contains(series)) {
seriesOnChart.add(series);
}
});
}
private void updateSymbolsVisibility() {
maxDataPointsForShowingSymbols = 100;
long numDataPoints = chart.getData().stream()
.map(XYChart.Series::getData)
.mapToLong(List::size)
.max()
.orElse(0);
boolean prevValue = chart.getCreateSymbols();
boolean newValue = numDataPoints < maxDataPointsForShowingSymbols;
if (prevValue != newValue) {
chart.setCreateSymbols(newValue);
}
}
// The chart framework assigns the colored depending on the order it got added, but want to keep colors
// the same so they match with the legend toggle.
private void applySeriesStyles() {
for (int index = 0; index < chart.getData().size(); index++) {
XYChart.Series<Number, Number> series = chart.getData().get(index);
int staticIndex = seriesIndexMap.get(getSeriesId(series));
Set<Node> lines = getNodesForStyle(series.getNode(), ".default-color%d.chart-series-line");
Stream<Node> symbols = series.getData().stream().map(XYChart.Data::getNode)
.flatMap(node -> getNodesForStyle(node, ".default-color%d.chart-line-symbol").stream());
Stream.concat(lines.stream(), symbols).forEach(node -> {
removeStyles(node);
node.getStyleClass().add("default-color" + staticIndex);
});
}
}
private void applyTooltip() {
chart.getData().forEach(series -> {
series.getData().forEach(data -> {
Node node = data.getNode();
if (node == null) {
return;
}
String xValue = model.getTooltipDateConverter(data.getXValue());
String yValue = model.getYAxisStringConverter().toString(data.getYValue());
Tooltip.install(node, new Tooltip(Res.get("dao.factsAndFigures.supply.chart.tradeFee.toolTip", yValue, xValue)));
});
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private void removeStyles(Node node) {
for (int i = 0; i < getMaxSeriesSize(); i++) {
node.getStyleClass().remove("default-color" + i);
}
}
private Set<Node> getNodesForStyle(Node node, String style) {
Set<Node> result = new HashSet<>();
if (node != null) {
for (int i = 0; i < getMaxSeriesSize(); i++) {
result.addAll(node.lookupAll(String.format(style, i)));
}
}
return result;
}
private int getMaxSeriesSize() {
maxSeriesSize = Math.max(maxSeriesSize, chart.getData().size());
return maxSeriesSize;
}
private Optional<Toggle> findTimeIntervalToggleByTemporalAdjuster(TemporalAdjuster adjuster) {
return timeIntervalToggleGroup.getToggles().stream()
.filter(toggle -> ((TemporalAdjusterModel.Interval) toggle.getUserData()).getAdjuster().equals(adjuster))
.findAny();
}
// We use the name as id as there is no other suitable data inside series
protected String getSeriesId(XYChart.Series<Number, Number> series) {
return series.getName();
}
}

View file

@ -24,7 +24,6 @@ import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.dao.governance.asset.AssetService;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
@ -63,7 +62,6 @@ public class AssetsForm extends PaymentMethodForm {
public static final String INSTANT_TRADE_NEWS = "instantTradeNews0.9.5";
private final AssetAccount assetAccount;
private final AltCoinAddressValidator altCoinAddressValidator;
private final AssetService assetService;
private final FilterManager filterManager;
private InputTextField addressInputTextField;
@ -86,12 +84,10 @@ public class AssetsForm extends PaymentMethodForm {
GridPane gridPane,
int gridRow,
CoinFormatter formatter,
AssetService assetService,
FilterManager filterManager) {
super(paymentAccount, accountAgeWitnessService, inputValidator, gridPane, gridRow, formatter);
this.assetAccount = (AssetAccount) paymentAccount;
this.altCoinAddressValidator = altCoinAddressValidator;
this.assetService = assetService;
this.filterManager = filterManager;
tradeInstant = paymentAccount instanceof InstantCryptoCurrencyAccount;
@ -216,7 +212,7 @@ public class AssetsForm extends PaymentMethodForm {
currencyComboBox.setPromptText(""));
((AutocompleteComboBox<TradeCurrency>) currencyComboBox).setAutocompleteItems(
CurrencyUtil.getActiveSortedCryptoCurrencies(assetService, filterManager));
CurrencyUtil.getActiveSortedCryptoCurrencies(filterManager));
currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 10));
currencyComboBox.setConverter(new StringConverter<>() {

View file

@ -40,7 +40,6 @@ import bisq.desktop.main.support.SupportView;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.Transitions;
import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.locale.GlobalSettings;
import bisq.core.locale.LanguageUtil;
import bisq.core.locale.Res;
@ -108,8 +107,7 @@ import static javafx.scene.layout.AnchorPane.setTopAnchor;
@FxmlView
@Slf4j
public class MainView extends InitializableView<StackPane, MainViewModel>
implements DaoStateMonitoringService.Listener {
public class MainView extends InitializableView<StackPane, MainViewModel> {
// If after 30 sec we have not got connected we show "open network settings" button
private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 90;
@Setter
@ -153,19 +151,16 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
private ProgressBar btcSyncIndicator, p2pNetworkProgressBar;
private Label btcSplashInfo;
private Popup p2PNetworkWarnMsgPopup, btcNetworkWarnMsgPopup;
private final DaoStateMonitoringService daoStateMonitoringService;
@Inject
public MainView(MainViewModel model,
CachingViewLoader viewLoader,
Navigation navigation,
Transitions transitions,
DaoStateMonitoringService daoStateMonitoringService) {
Transitions transitions) {
super(model);
this.viewLoader = viewLoader;
this.navigation = navigation;
MainView.transitions = transitions;
this.daoStateMonitoringService = daoStateMonitoringService;
}
@Override
@ -183,7 +178,6 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
ToggleButton supportButton = new NavButton(SupportView.class, Res.get("mainView.menu.support"));
ToggleButton settingsButton = new NavButton(SettingsView.class, Res.get("mainView.menu.settings"));
ToggleButton accountButton = new NavButton(AccountView.class, Res.get("mainView.menu.account"));
// ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao"));
JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton);
JFXBadge supportButtonWithBadge = new JFXBadge(supportButton);
@ -214,9 +208,6 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
settingsButton.fire();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT8, keyEvent)) {
accountButton.fire();
// } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) {
// if (daoButton.isVisible())
// daoButton.fire();
}
});
}
@ -398,28 +389,10 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
}
});
daoStateMonitoringService.addListener(this);
// Delay a bit to give time for rendering the splash screen
UserThread.execute(() -> onApplicationStartedHandler.run());
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateMonitoringService.Listener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onChangeAfterBatchProcessing() {
}
@Override
public void onCheckpointFail() {
new Popup().attention(Res.get("dao.monitor.daoState.checkpoint.popup"))
.useShutDownButton()
.show();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -31,7 +31,6 @@ import bisq.desktop.main.overlays.windows.UpdateRevolutAccountWindow;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow;
import bisq.desktop.main.presentation.AccountPresentation;
import bisq.desktop.main.presentation.DaoPresentation;
import bisq.desktop.main.presentation.MarketPricePresentation;
import bisq.desktop.main.presentation.SettingsPresentation;
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
@ -116,7 +115,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
private final TradePresentation tradePresentation;
private final SupportTicketsPresentation supportTicketsPresentation;
private final MarketPricePresentation marketPricePresentation;
private final DaoPresentation daoPresentation;
private final AccountPresentation accountPresentation;
private final SettingsPresentation settingsPresentation;
private final P2PService p2PService;
@ -162,7 +160,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
TradePresentation tradePresentation,
SupportTicketsPresentation supportTicketsPresentation,
MarketPricePresentation marketPricePresentation,
DaoPresentation daoPresentation,
AccountPresentation accountPresentation,
SettingsPresentation settingsPresentation,
P2PService p2PService,
@ -187,7 +184,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
this.tradePresentation = tradePresentation;
this.supportTicketsPresentation = supportTicketsPresentation;
this.marketPricePresentation = marketPricePresentation;
this.daoPresentation = daoPresentation;
this.accountPresentation = accountPresentation;
this.settingsPresentation = settingsPresentation;
this.p2PService = p2PService;
@ -265,7 +261,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
setupBtcNumPeersWatcher();
marketPricePresentation.setup();
daoPresentation.setup();
accountPresentation.setup();
settingsPresentation.setup();
@ -335,7 +330,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.actionButtonText(Res.get("settings.net.reSyncSPVChainButton"))
.onAction(() -> GUIUtil.reSyncSPVChain(preferences))
.show());
bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.warn(voteResultException.toString()));
bisqSetup.setChainFileLockedExceptionHandler(msg -> new Popup().warning(msg)
.useShutDownButton()
@ -368,8 +362,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.onClose(privateNotificationManager::removePrivateNotification)
.useIUnderstandButton()
.show());
bisqSetup.setDaoErrorMessageHandler(errorMessage -> new Popup().error(errorMessage).show());
bisqSetup.setDaoWarnMessageHandler(warnMessage -> new Popup().warning(warnMessage).show());
bisqSetup.setDisplaySecurityRecommendationHandler(key ->
new Popup().headLine(Res.get("popup.securityRecommendation.headline"))
.information(Res.get("popup.securityRecommendation.msg"))
@ -432,10 +424,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.show();
});
bisqSetup.setDaoRequiresRestartHandler(() -> new Popup().warning("popup.warn.daoRequiresRestart")
.useShutDownButton()
.hideCloseButton()
.show());
corruptedStorageFileHandler.getFiles().ifPresent(files -> new Popup()
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))
@ -447,7 +435,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.show());
bisqSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress());
daoPresentation.getBsqSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress());
bisqSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show());
@ -615,11 +602,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
private void updateBtcSyncProgress() {
final DoubleProperty btcSyncProgress = bisqSetup.getBtcSyncProgress();
if (btcSyncProgress.doubleValue() < 1) {
combinedSyncProgress.set(btcSyncProgress.doubleValue());
} else {
combinedSyncProgress.set(daoPresentation.getBsqSyncProgress().doubleValue());
}
}
private void setupInvalidOpenOffersHandler() {
@ -697,7 +680,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
StringProperty getCombinedFooterInfo() {
final StringProperty combinedInfo = new SimpleStringProperty();
combinedInfo.bind(Bindings.concat(this.footerVersionInfo, " ", daoPresentation.getBsqInfo()));
combinedInfo.bind(Bindings.concat(this.footerVersionInfo, " "));
return combinedInfo;
}
@ -775,14 +758,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
return marketPricePresentation.getPriceFeedComboBoxItems();
}
// We keep daoPresentation and accountPresentation support even it is not used atm. But if we add a new feature and
// add a badge again it will be needed.
@SuppressWarnings({"unused"})
public BooleanProperty getShowDaoUpdatesNotification() {
return daoPresentation.getShowDaoUpdatesNotification();
}
// We keep daoPresentation and accountPresentation support even it is not used atm. But if we add a new feature and
// We keep accountPresentation support even it is not used atm. But if we add a new feature and
// add a badge again it will be needed.
@SuppressWarnings("unused")
public BooleanProperty getShowAccountUpdatesNotification() {

View file

@ -31,7 +31,6 @@ import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.util.AveragePriceUtil;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.validation.InputValidator;
@ -53,18 +52,12 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Singleton
public class PriceUtil {
private final PriceFeedService priceFeedService;
private final TradeStatisticsManager tradeStatisticsManager;
private final Preferences preferences;
@Nullable
private Price bsq30DayAveragePrice;
@Inject
public PriceUtil(PriceFeedService priceFeedService,
TradeStatisticsManager tradeStatisticsManager,
Preferences preferences) {
this.priceFeedService = priceFeedService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.preferences = preferences;
}
public static MonetaryValidator getPriceValidator(boolean isFiatCurrency) {
@ -117,19 +110,6 @@ public class PriceUtil {
return Price.valueOf(currencyCode, roundedToLong);
}
public void recalculateBsq30DayAveragePrice() {
bsq30DayAveragePrice = null;
bsq30DayAveragePrice = getBsq30DayAveragePrice();
}
public Price getBsq30DayAveragePrice() {
if (bsq30DayAveragePrice == null) {
bsq30DayAveragePrice = AveragePriceUtil.getAveragePriceTuple(preferences,
tradeStatisticsManager, 30).second;
}
return bsq30DayAveragePrice;
}
public boolean hasMarketPrice(Offer offer) {
String currencyCode = offer.getCurrencyCode();
checkNotNull(priceFeedService, "priceFeed must not be null");
@ -145,19 +125,9 @@ public class PriceUtil {
}
if (!hasMarketPrice(offer)) {
if (offer.getCurrencyCode().equals("BSQ")) {
Price bsq30DayAveragePrice = getBsq30DayAveragePrice();
if (bsq30DayAveragePrice.isPositive()) {
double scaled = MathUtils.scaleDownByPowerOf10(bsq30DayAveragePrice.getValue(), 8);
return calculatePercentage(offer, scaled, direction);
} else {
return Optional.empty();
}
} else {
log.trace("We don't have a market price. " +
"That case could only happen if you don't have a price feed.");
return Optional.empty();
}
log.trace("We don't have a market price. " +
"That case could only happen if you don't have a price feed.");
return Optional.empty();
}
String currencyCode = offer.getCurrencyCode();

View file

@ -27,7 +27,6 @@ import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.dao.governance.asset.AssetService;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CryptoCurrency;
import bisq.core.locale.CurrencyUtil;
@ -75,7 +74,6 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
private final InputValidator inputValidator;
private final AltCoinAddressValidator altCoinAddressValidator;
private final AssetService assetService;
private final FilterManager filterManager;
private final CoinFormatter formatter;
private final Preferences preferences;
@ -90,7 +88,6 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
InputValidator inputValidator,
AltCoinAddressValidator altCoinAddressValidator,
AccountAgeWitnessService accountAgeWitnessService,
AssetService assetService,
FilterManager filterManager,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
Preferences preferences) {
@ -98,7 +95,6 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
this.inputValidator = inputValidator;
this.altCoinAddressValidator = altCoinAddressValidator;
this.assetService = assetService;
this.filterManager = filterManager;
this.formatter = formatter;
this.preferences = preferences;
@ -242,7 +238,7 @@ public class AltCoinAccountsView extends PaymentAccountsView<GridPane, AltCoinAc
private PaymentMethodForm getPaymentMethodForm(PaymentAccount paymentAccount) {
return new AssetsForm(paymentAccount, accountAgeWitnessService, altCoinAddressValidator,
inputValidator, root, gridRow, formatter, assetService, filterManager);
inputValidator, root, gridRow, formatter, filterManager);
}
private void removeNewAccountForm() {

View file

@ -24,13 +24,11 @@ import bisq.desktop.main.overlays.windows.ShowWalletDataWindow;
import bisq.desktop.util.Layout;
import bisq.core.btc.listeners.BalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Res;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.common.config.Config;
@ -58,14 +56,11 @@ public class WalletInfoView extends ActivatableView<GridPane, Void> {
private final WalletsManager walletsManager;
private final BtcWalletService btcWalletService;
private final BsqWalletService bsqWalletService;
private final CoinFormatter btcFormatter;
private final BsqFormatter bsqFormatter;
private int gridRow = 0;
private Button openDetailsButton;
private TextField btcTextField, bsqTextField;
private TextField btcTextField;
private BalanceListener btcWalletBalanceListener;
private BalanceListener bsqWalletBalanceListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -75,14 +70,10 @@ public class WalletInfoView extends ActivatableView<GridPane, Void> {
@Inject
private WalletInfoView(WalletsManager walletsManager,
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
BsqFormatter bsqFormatter) {
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
this.walletsManager = walletsManager;
this.btcWalletService = btcWalletService;
this.bsqWalletService = bsqWalletService;
this.btcFormatter = btcFormatter;
this.bsqFormatter = bsqFormatter;
}
@Override
@ -90,18 +81,15 @@ public class WalletInfoView extends ActivatableView<GridPane, Void> {
addTitledGroupBg(root, gridRow, 3, Res.get("account.menu.walletInfo.balance.headLine"));
addMultilineLabel(root, gridRow, Res.get("account.menu.walletInfo.balance.info"), Layout.FIRST_ROW_DISTANCE, Double.MAX_VALUE);
btcTextField = addTopLabelTextField(root, ++gridRow, "BTC", -Layout.FLOATING_LABEL_DISTANCE).second;
bsqTextField = addTopLabelTextField(root, ++gridRow, "BSQ", -Layout.FLOATING_LABEL_DISTANCE).second;
addTitledGroupBg(root, ++gridRow, 3, Res.get("account.menu.walletInfo.xpub.headLine"), Layout.GROUP_DISTANCE);
addXpubKeys(btcWalletService, "BTC", gridRow, Layout.FIRST_ROW_AND_GROUP_DISTANCE);
++gridRow; // update gridRow
addXpubKeys(bsqWalletService, "BSQ", ++gridRow, -Layout.FLOATING_LABEL_DISTANCE);
addTitledGroupBg(root, ++gridRow, 4, Res.get("account.menu.walletInfo.path.headLine"), Layout.GROUP_DISTANCE);
addMultilineLabel(root, gridRow, Res.get("account.menu.walletInfo.path.info"), Layout.FIRST_ROW_AND_GROUP_DISTANCE, Double.MAX_VALUE);
addTopLabelTextField(root, ++gridRow, Res.get("account.menu.walletInfo.walletSelector", "BTC", "legacy"), "44'/0'/0'", -Layout.FLOATING_LABEL_DISTANCE);
addTopLabelTextField(root, ++gridRow, Res.get("account.menu.walletInfo.walletSelector", "BTC", "segwit"), "44'/0'/1'", -Layout.FLOATING_LABEL_DISTANCE);
addTopLabelTextField(root, ++gridRow, Res.get("account.menu.walletInfo.walletSelector", "BSQ", ""), "44'/142'/0'", -Layout.FLOATING_LABEL_DISTANCE);
openDetailsButton = addButtonAfterGroup(root, ++gridRow, Res.get("account.menu.walletInfo.openDetails"));
@ -111,21 +99,13 @@ public class WalletInfoView extends ActivatableView<GridPane, Void> {
updateBalances(btcWalletService);
}
};
bsqWalletBalanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) {
updateBalances(bsqWalletService);
}
};
}
@Override
protected void activate() {
btcWalletService.addBalanceListener(btcWalletBalanceListener);
bsqWalletService.addBalanceListener(bsqWalletBalanceListener);
updateBalances(btcWalletService);
updateBalances(bsqWalletService);
openDetailsButton.setOnAction(e -> {
if (walletsManager.areWalletsAvailable()) {
@ -139,7 +119,6 @@ public class WalletInfoView extends ActivatableView<GridPane, Void> {
@Override
protected void deactivate() {
btcWalletService.removeBalanceListener(btcWalletBalanceListener);
bsqWalletService.removeBalanceListener(bsqWalletBalanceListener);
openDetailsButton.setOnAction(null);
}
@ -161,8 +140,6 @@ public class WalletInfoView extends ActivatableView<GridPane, Void> {
private void updateBalances(WalletService walletService) {
if (walletService instanceof BtcWalletService) {
btcTextField.setText(btcFormatter.formatCoinWithCode(walletService.getBalance(ESTIMATED_SPENDABLE)));
} else {
bsqTextField.setText(bsqFormatter.formatCoinWithCode(walletService.getBalance(ESTIMATED_SPENDABLE)));
}
}

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 com.jfoenix.controls.JFXTabPane?>
<?import javafx.scene.layout.AnchorPane?>
<JFXTabPane fx:id="root" fx:controller="bisq.desktop.main.dao.DaoView"
AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"
AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0"
xmlns:fx="http://javafx.com/fxml">
</JFXTabPane>

View file

@ -1,212 +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;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.main.MainView;
import bisq.desktop.main.dao.bonding.BondingView;
import bisq.desktop.main.dao.burnbsq.BurnBsqView;
import bisq.desktop.main.dao.economy.EconomyView;
import bisq.desktop.main.dao.governance.GovernanceView;
import bisq.desktop.main.dao.monitor.MonitorView;
import bisq.desktop.main.dao.news.NewsView;
import bisq.desktop.main.dao.wallet.BsqWalletView;
import bisq.desktop.main.dao.wallet.send.BsqSendView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.presentation.DaoPresentation;
import bisq.core.dao.governance.votereveal.VoteRevealService;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.common.app.DevEnv;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.beans.value.ChangeListener;
@FxmlView
public class DaoView extends ActivatableView<TabPane, Void> {
@FXML
private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab, monitorTab, factsAndFiguresTab;
private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener;
private final ViewLoader viewLoader;
private final Navigation navigation;
private Preferences preferences;
private Tab selectedTab;
private BsqWalletView bsqWalletView;
@Inject
private DaoView(CachingViewLoader viewLoader, VoteRevealService voteRevealService, Navigation navigation,
Preferences preferences) {
this.viewLoader = viewLoader;
this.navigation = navigation;
this.preferences = preferences;
voteRevealService.addVoteRevealTxPublishedListener(txId -> {
new Popup().headLine(Res.get("dao.voteReveal.txPublished.headLine"))
.feedback(Res.get("dao.voteReveal.txPublished", txId))
.show();
});
}
@Override
public void initialize() {
factsAndFiguresTab = new Tab(Res.get("dao.tab.factsAndFigures").toUpperCase());
bsqWalletTab = new Tab(Res.get("dao.tab.bsqWallet").toUpperCase());
proposalsTab = new Tab(Res.get("dao.tab.proposals").toUpperCase());
bondingTab = new Tab(Res.get("dao.tab.bonding").toUpperCase());
burnBsqTab = new Tab(Res.get("dao.tab.proofOfBurn").toUpperCase());
monitorTab = new Tab(Res.get("dao.tab.monitor").toUpperCase());
factsAndFiguresTab.setClosable(false);
bsqWalletTab.setClosable(false);
proposalsTab.setClosable(false);
bondingTab.setClosable(false);
burnBsqTab.setClosable(false);
monitorTab.setClosable(false);
if (!DevEnv.isDaoActivated()) {
factsAndFiguresTab.setDisable(true);
bsqWalletTab.setDisable(true);
proposalsTab.setDisable(true);
bondingTab.setDisable(true);
burnBsqTab.setDisable(true);
monitorTab.setDisable(true);
daoNewsTab = new Tab(Res.get("dao.tab.news").toUpperCase());
root.getTabs().add(daoNewsTab);
} else {
root.getTabs().addAll(factsAndFiguresTab, bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, monitorTab);
}
navigationListener = (viewPath, data) -> {
if (viewPath.size() == 3 && viewPath.indexOf(DaoView.class) == 1) {
if (proposalsTab == null && viewPath.get(2).equals(EconomyView.class))
navigation.navigateTo(MainView.class, DaoView.class, EconomyView.class);
else
loadView(viewPath.tip());
}
};
tabChangeListener = (ov, oldValue, newValue) -> {
if (newValue == bsqWalletTab) {
Class<? extends View> selectedViewClass = bsqWalletView != null ? bsqWalletView.getSelectedViewClass() : null;
if (selectedViewClass == null)
navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, BsqSendView.class);
else
navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, selectedViewClass);
} else if (newValue == proposalsTab) {
navigation.navigateTo(MainView.class, DaoView.class, GovernanceView.class);
} else if (newValue == bondingTab) {
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
} else if (newValue == burnBsqTab) {
navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class);
} else if (newValue == factsAndFiguresTab) {
navigation.navigateTo(MainView.class, DaoView.class, EconomyView.class);
} else if (newValue == monitorTab) {
navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class);
}
};
}
@Override
protected void activate() {
if (DevEnv.isDaoActivated()) {
// Hide dao new badge if user saw this section
preferences.dontShowAgain(DaoPresentation.DAO_NEWS, true);
navigation.addListener(navigationListener);
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
if (navigation.getCurrentPath().size() == 2 && navigation.getCurrentPath().get(1) == DaoView.class) {
Tab selectedItem = root.getSelectionModel().getSelectedItem();
if (selectedItem == bsqWalletTab)
navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class);
else if (selectedItem == proposalsTab)
navigation.navigateTo(MainView.class, DaoView.class, GovernanceView.class);
else if (selectedItem == bondingTab)
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
else if (selectedItem == burnBsqTab)
navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class);
else if (selectedItem == factsAndFiguresTab)
navigation.navigateTo(MainView.class, DaoView.class, EconomyView.class);
else if (selectedItem == monitorTab)
navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class);
}
} else {
loadView(NewsView.class);
}
}
@Override
protected void deactivate() {
navigation.removeListener(navigationListener);
root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener);
}
private void loadView(Class<? extends View> viewClass) {
if (selectedTab != null && selectedTab.getContent() != null) {
if (selectedTab.getContent() instanceof ScrollPane) {
((ScrollPane) selectedTab.getContent()).setContent(null);
} else {
selectedTab.setContent(null);
}
}
View view = viewLoader.load(viewClass);
if (view instanceof BsqWalletView) {
selectedTab = bsqWalletTab;
bsqWalletView = (BsqWalletView) view;
} else if (view instanceof GovernanceView) {
selectedTab = proposalsTab;
} else if (view instanceof BondingView) {
selectedTab = bondingTab;
} else if (view instanceof BurnBsqView) {
selectedTab = burnBsqTab;
} else if (view instanceof MonitorView) {
selectedTab = monitorTab;
} else if (view instanceof NewsView) {
selectedTab = daoNewsTab;
} else if (view instanceof EconomyView) {
selectedTab = factsAndFiguresTab;
}
selectedTab.setContent(view.getRoot());
root.getSelectionModel().select(selectedTab);
}
}

View file

@ -1,38 +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.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.bonding.BondingView"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="15"/>
<ScrollPane fitToWidth="true" hbarPolicy="NEVER"
fitToHeight="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</ScrollPane>
</AnchorPane>

View file

@ -1,145 +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.bonding;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.bonding.bonds.BondsView;
import bisq.desktop.main.dao.bonding.dashboard.BondingDashboardView;
import bisq.desktop.main.dao.bonding.reputation.MyReputationView;
import bisq.desktop.main.dao.bonding.roles.RolesView;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
@FxmlView
public class BondingView extends ActivatableView<AnchorPane, Void> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem dashboard, bondedRoles, reputation, bonds;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
private ToggleGroup toggleGroup;
@Inject
private BondingView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = (viewPath, data) -> {
if (viewPath.size() != 4 || viewPath.indexOf(bisq.desktop.main.dao.bonding.BondingView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass, data);
};
toggleGroup = new ToggleGroup();
final List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, bisq.desktop.main.dao.bonding.BondingView.class);
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"),
BondingDashboardView.class, baseNavPath);
bondedRoles = new MenuItem(navigation, toggleGroup, Res.get("dao.bond.menuItem.bondedRoles"),
RolesView.class, baseNavPath);
reputation = new MenuItem(navigation, toggleGroup, Res.get("dao.bond.menuItem.reputation"),
MyReputationView.class, baseNavPath);
bonds = new MenuItem(navigation, toggleGroup, Res.get("dao.bond.menuItem.bonds"),
BondsView.class, baseNavPath);
leftVBox.getChildren().addAll(dashboard, bondedRoles, reputation, bonds);
}
@Override
protected void activate() {
dashboard.activate();
bondedRoles.activate();
reputation.activate();
bonds.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(BondingView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = RolesView.class;
loadView(selectedViewClass, null);
} else if (viewPath.size() == 4 && viewPath.indexOf(BondingView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass, null);
}
}
@SuppressWarnings("Duplicates")
@Override
protected void deactivate() {
navigation.removeListener(listener);
dashboard.deactivate();
bondedRoles.deactivate();
reputation.deactivate();
bonds.deactivate();
}
private void loadView(Class<? extends View> viewClass, @Nullable Object data) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof BondingDashboardView) toggleGroup.selectToggle(dashboard);
else if (view instanceof RolesView) toggleGroup.selectToggle(bondedRoles);
else if (view instanceof MyReputationView) toggleGroup.selectToggle(reputation);
else if (view instanceof BondsView) {
toggleGroup.selectToggle(bonds);
if (data instanceof Bond)
((BondsView) view).setSelectedBond((Bond) data);
}
}
}

View file

@ -1,226 +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.bonding;
import bisq.desktop.Navigation;
import bisq.desktop.main.MainView;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.funds.deposit.DepositView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.lockup.LockupReason;
import bisq.core.dao.governance.bond.reputation.MyReputation;
import bisq.core.dao.governance.bond.reputation.MyReputationListService;
import bisq.core.dao.governance.bond.role.BondedRolesRepository;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.core.util.FormattingUtils;
import bisq.network.p2p.P2PService;
import bisq.common.app.DevEnv;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
@Singleton
public class BondingViewUtils {
private final P2PService p2PService;
private final MyReputationListService myReputationListService;
private final BondedRolesRepository bondedRolesRepository;
private final WalletsSetup walletsSetup;
private final DaoFacade daoFacade;
private final Navigation navigation;
private final BsqFormatter bsqFormatter;
@Inject
public BondingViewUtils(P2PService p2PService,
MyReputationListService myReputationListService,
BondedRolesRepository bondedRolesRepository,
WalletsSetup walletsSetup,
DaoFacade daoFacade,
Navigation navigation,
BsqFormatter bsqFormatter) {
this.p2PService = p2PService;
this.myReputationListService = myReputationListService;
this.bondedRolesRepository = bondedRolesRepository;
this.walletsSetup = walletsSetup;
this.daoFacade = daoFacade;
this.navigation = navigation;
this.bsqFormatter = bsqFormatter;
}
public void lockupBondForBondedRole(Role role, Consumer<String> resultHandler) {
Optional<RoleProposal> roleProposal = getAcceptedBondedRoleProposal(role);
checkArgument(roleProposal.isPresent(), "roleProposal must be present");
long requiredBond = daoFacade.getRequiredBond(roleProposal);
Coin lockupAmount = Coin.valueOf(requiredBond);
int lockupTime = roleProposal.get().getUnlockTime();
if (!bondedRolesRepository.isBondedAssetAlreadyInBond(role)) {
lockupBond(role.getHash(), lockupAmount, lockupTime, LockupReason.BONDED_ROLE, resultHandler);
} else {
handleError(new RuntimeException("The role has been used already for a lockup tx."));
}
}
public void lockupBondForReputation(Coin lockupAmount, int lockupTime, byte[] salt, Consumer<String> resultHandler) {
MyReputation myReputation = new MyReputation(salt);
lockupBond(myReputation.getHash(), lockupAmount, lockupTime, LockupReason.REPUTATION, resultHandler);
myReputationListService.addReputation(myReputation);
}
private void lockupBond(byte[] hash, Coin lockupAmount, int lockupTime, LockupReason lockupReason,
Consumer<String> resultHandler) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
if (!DevEnv.isDevMode()) {
try {
Tuple2<Coin, Integer> miningFeeAndTxVsize = daoFacade.getLockupTxMiningFeeAndTxVsize(lockupAmount, lockupTime, lockupReason, hash);
Coin miningFee = miningFeeAndTxVsize.first;
int txVsize = miningFeeAndTxVsize.second;
String duration = FormattingUtils.formatDurationAsWords(lockupTime * 10 * 60 * 1000L, false, false);
new Popup().headLine(Res.get("dao.bond.reputation.lockup.headline"))
.confirmation(Res.get("dao.bond.reputation.lockup.details",
bsqFormatter.formatCoinWithCode(lockupAmount),
lockupTime,
duration,
bsqFormatter.formatBTCWithCode(miningFee),
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d
))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> publishLockupTx(lockupAmount, lockupTime, lockupReason, hash, resultHandler))
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (Throwable e) {
log.error(e.toString());
e.printStackTrace();
new Popup().warning(e.getMessage()).show();
}
} else {
publishLockupTx(lockupAmount, lockupTime, lockupReason, hash, resultHandler);
}
}
}
private void publishLockupTx(Coin lockupAmount, int lockupTime, LockupReason lockupReason, byte[] hash, Consumer<String> resultHandler) {
daoFacade.publishLockupTx(lockupAmount,
lockupTime,
lockupReason,
hash,
txId -> {
if (!DevEnv.isDevMode())
new Popup().feedback(Res.get("dao.tx.published.success")).show();
if (resultHandler != null)
resultHandler.accept(txId);
},
this::handleError
);
}
public Optional<RoleProposal> getAcceptedBondedRoleProposal(Role role) {
return bondedRolesRepository.getAcceptedBondedRoleProposal(role);
}
public void unLock(String lockupTxId, Consumer<String> resultHandler) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
Optional<TxOutput> lockupTxOutput = daoFacade.getLockupTxOutput(lockupTxId);
checkArgument(lockupTxOutput.isPresent(), "Lockup output must be present. TxId=" + lockupTxId);
Coin unlockAmount = Coin.valueOf(lockupTxOutput.get().getValue());
Optional<Integer> opLockTime = daoFacade.getLockTime(lockupTxId);
int lockTime = opLockTime.orElse(-1);
try {
if (!DevEnv.isDevMode()) {
Tuple2<Coin, Integer> miningFeeAndTxVsize = daoFacade.getUnlockTxMiningFeeAndTxVsize(lockupTxId);
Coin miningFee = miningFeeAndTxVsize.first;
int txVsize = miningFeeAndTxVsize.second;
String duration = FormattingUtils.formatDurationAsWords(lockTime * 10 * 60 * 1000L, false, false);
new Popup().headLine(Res.get("dao.bond.reputation.unlock.headline"))
.confirmation(Res.get("dao.bond.reputation.unlock.details",
bsqFormatter.formatCoinWithCode(unlockAmount),
lockTime,
duration,
bsqFormatter.formatBTCWithCode(miningFee),
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d
))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> publishUnlockTx(lockupTxId, resultHandler))
.closeButtonText(Res.get("shared.cancel"))
.show();
} else {
publishUnlockTx(lockupTxId, resultHandler);
}
} catch (Throwable t) {
log.error(t.toString());
t.printStackTrace();
new Popup().warning(t.getMessage()).show();
}
}
log.info("unlock tx: {}", lockupTxId);
}
private void publishUnlockTx(String lockupTxId, Consumer<String> resultHandler) {
daoFacade.publishUnlockTx(lockupTxId,
txId -> {
if (!DevEnv.isDevMode())
new Popup().confirmation(Res.get("dao.tx.published.success")).show();
if (resultHandler != null)
resultHandler.accept(txId);
},
errorMessage -> new Popup().warning(errorMessage.toString()).show()
);
}
private void handleError(Throwable throwable) {
if (throwable instanceof InsufficientMoneyException) {
final Coin missingCoin = ((InsufficientMoneyException) throwable).missing;
final String missing = missingCoin != null ? missingCoin.toFriendlyString() : "null";
new Popup().warning(Res.get("popup.warning.insufficientBtcFundsForBsqTx", missing))
.actionButtonTextWithGoTo("navigation.funds.depositFunds")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
.show();
} else {
log.error(throwable.toString());
throwable.printStackTrace();
new Popup().warning(throwable.toString()).show();
}
}
}

View file

@ -1,67 +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.bonding.bonds;
import bisq.desktop.util.DisplayUtils;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import java.util.Date;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Value
@Slf4j
class BondListItem {
private final Bond bond;
private final String bondType;
private final String lockupTxId;
private final String amount;
private final String lockupDateString;
private final String lockTime;
private final String bondDetails;
private final BondState bondState;
private final String bondStateString;
BondListItem(Bond bond, BsqFormatter bsqFormatter) {
this.bond = bond;
amount = bsqFormatter.formatCoin(Coin.valueOf(bond.getAmount()));
lockTime = Integer.toString(bond.getLockTime());
if (bond instanceof BondedRole) {
bondType = Res.get("dao.bond.bondedRoles");
bondDetails = bond.getBondedAsset().getDisplayString();
} else {
bondType = Res.get("dao.bond.bondedReputation");
bondDetails = Utilities.bytesAsHexString(bond.getBondedAsset().getHash());
}
lockupTxId = bond.getLockupTxId();
lockupDateString = bond.getLockupDate() > 0 ? DisplayUtils.formatDateTime(new Date(bond.getLockupDate())) : "-";
bondState = bond.getBondState();
bondStateString = Res.get("dao.bond.bondState." + bond.getBondState().name());
}
}

View file

@ -1,33 +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.bonding.bonds.BondsView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

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.bonding.bonds;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InfoAutoTooltipLabel;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.reputation.BondedReputation;
import bisq.core.dao.governance.bond.reputation.BondedReputationRepository;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.governance.bond.role.BondedRolesRepository;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import javax.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@FxmlView
public class BondsView extends ActivatableView<GridPane, Void> {
private TableView<BondListItem> tableView;
private final BsqFormatter bsqFormatter;
private final BondedRolesRepository bondedRolesRepository;
private final BondedReputationRepository bondedReputationRepository;
private final Preferences preferences;
private int gridRow = 0;
private final ObservableList<BondListItem> observableList = FXCollections.observableArrayList();
private final SortedList<BondListItem> sortedList = new SortedList<>(observableList);
private ListChangeListener<BondedRole> bondedRolesListener;
private ListChangeListener<BondedReputation> bondedReputationListener;
private Bond selectedBond;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BondsView(BsqFormatter bsqFormatter,
BondedRolesRepository bondedRolesRepository,
BondedReputationRepository bondedReputationRepository,
Preferences preferences) {
this.bsqFormatter = bsqFormatter;
this.bondedRolesRepository = bondedRolesRepository;
this.bondedReputationRepository = bondedReputationRepository;
this.preferences = preferences;
}
@Override
public void initialize() {
tableView = FormBuilder.addTableViewWithHeader(root, ++gridRow, Res.get("dao.bond.allBonds.header"), "last");
tableView.setItems(sortedList);
GridPane.setVgrow(tableView, Priority.ALWAYS);
addColumns();
bondedReputationListener = c -> updateList();
bondedRolesListener = c -> updateList();
}
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
bondedReputationRepository.getBonds().addListener(bondedReputationListener);
bondedRolesRepository.getBonds().addListener(bondedRolesListener);
updateList();
GUIUtil.setFitToRowsForTableView(tableView, 37, 28, 2, 30);
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
bondedReputationRepository.getBonds().removeListener(bondedReputationListener);
bondedRolesRepository.getBonds().removeListener(bondedRolesListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void setSelectedBond(Bond bond) {
// Set the selected bond if it's found in the tableView, which listens to sortedList.
// If this is called before the sortedList has been populated the selected bond is stored and
// we try to apply again after the next update.
tableView.getItems().stream()
.filter(item -> item.getBond() == bond)
.findFirst()
.ifPresentOrElse(item -> tableView.getSelectionModel().select(item),
() -> this.selectedBond = bond);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
List<Bond> combined = new ArrayList<>(bondedReputationRepository.getBonds());
combined.addAll(bondedRolesRepository.getBonds());
observableList.setAll(combined.stream()
.map(bond -> new BondListItem(bond, bsqFormatter))
.sorted(Comparator.comparing(BondListItem::getLockupDateString).reversed())
.collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(tableView, 37, 28, 2, 30);
if (selectedBond != null) {
Bond bond = selectedBond;
selectedBond = null;
setSelectedBond(bond);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void addColumns() {
TableColumn<BondListItem, BondListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", "BSQ"));
column.setMinWidth(80);
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem, BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmount());
} else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getBond().getAmount()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockTime"));
column.setMinWidth(40);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem, BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockTime());
} else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getBond().getLockTime()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondState"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getBondStateString());
} else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(BondListItem::getBondStateString));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondType"));
column.setMinWidth(100);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem, BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getBondType());
} else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(BondListItem::getBondType));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.details"));
column.setMinWidth(100);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem, BondListItem> column) {
return new TableCell<>() {
private InfoAutoTooltipLabel infoTextField;
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String info = Res.get("shared.id") + ": " + item.getBond().getBondedAsset().getUid();
if (item.getBond() instanceof BondedRole) {
info = item.getBondDetails() + "\n" + info;
}
infoTextField = new InfoAutoTooltipLabel(item.getBondDetails(),
AwesomeIcon.INFO_SIGN,
ContentDisplay.LEFT,
info,
350
);
setGraphic(infoTextField);
} else
setGraphic(null);
}
};
}
});
column.setComparator(Comparator.comparing(BondListItem::getBondDetails));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupDate"));
column.setMinWidth(140);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem, BondListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockupDateString());
} else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getBond().getLockupDate()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupTxId"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.getStyleClass().add("last-column");
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BondListItem, BondListItem> call(TableColumn<BondListItem,
BondListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final BondListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String lockupTxId = item.getLockupTxId();
hyperlinkWithIcon = new ExternalHyperlink(lockupTxId);
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openTxInBsqBlockExplorer(lockupTxId, preferences));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", lockupTxId)));
if (item.getLockupDateString().equals("-")) hyperlinkWithIcon.hideIcon();
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
column.setComparator(Comparator.comparing(BondListItem::getLockupTxId));
tableView.getColumns().add(column);
}
}

View file

@ -1,33 +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.bonding.dashboard.BondingDashboardView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="50"/>
<ColumnConstraints minWidth="10" maxWidth="5"/>
<ColumnConstraints percentWidth="50"/>
</columnConstraints>
</GridPane>

View file

@ -1,58 +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.bonding.dashboard;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.dao.wallet.BsqBalanceUtil;
import javax.inject.Inject;
import javafx.scene.layout.GridPane;
@FxmlView
public class BondingDashboardView extends ActivatableView<GridPane, Void> {
private final BsqBalanceUtil bsqBalanceUtil;
private int gridRow = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BondingDashboardView(BsqBalanceUtil bsqBalanceUtil) {
this.bsqBalanceUtil = bsqBalanceUtil;
}
public void initialize() {
gridRow = bsqBalanceUtil.addGroup(root, gridRow);
gridRow = bsqBalanceUtil.addBondBalanceGroup(root, gridRow, "last");
}
@Override
protected void activate() {
bsqBalanceUtil.activate();
}
@Override
protected void deactivate() {
bsqBalanceUtil.deactivate();
}
}

View file

@ -1,70 +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.bonding.reputation;
import bisq.desktop.util.DisplayUtils;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
import bisq.core.dao.governance.bond.reputation.MyReputation;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import java.util.Date;
import lombok.Value;
@Value
class MyReputationListItem {
private final MyBondedReputation myBondedReputation;
private final String hash, salt;
private final String txId;
private final String amount;
private final String lockupDateString;
private final String lockTime;
private final String buttonText;
private final boolean showButton;
private final BondState bondState;
private final String bondStateString;
private final String lockupTxId;
private final Date lockupDate;
MyReputationListItem(MyBondedReputation myBondedReputation,
BsqFormatter bsqFormatter) {
this.myBondedReputation = myBondedReputation;
MyReputation myReputation = myBondedReputation.getBondedAsset();
hash = Utilities.bytesAsHexString(myReputation.getHash());
salt = Utilities.bytesAsHexString(myReputation.getSalt());
txId = myBondedReputation.getLockupTxId();
amount = bsqFormatter.formatCoin(Coin.valueOf(myBondedReputation.getAmount()));
lockupDate = new Date(myBondedReputation.getLockupDate());
lockupDateString = DisplayUtils.formatDateTime(lockupDate);
lockTime = Integer.toString(myBondedReputation.getLockTime());
lockupTxId = myBondedReputation.getLockupTxId();
bondState = myBondedReputation.getBondState();
bondStateString = Res.get("dao.bond.bondState." + myBondedReputation.getBondState().name());
showButton = myBondedReputation.getBondState() == BondState.LOCKUP_TX_CONFIRMED;
buttonText = Res.get("dao.bond.table.button.unlock");
}
}

View file

@ -1,33 +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.bonding.reputation.MyReputationView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,494 +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.bonding.reputation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.dao.bonding.BondingViewUtils;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondConsensus;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.reputation.MyBondedReputation;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.validation.HexStringValidator;
import bisq.core.util.validation.IntegerValidator;
import bisq.common.crypto.Hash;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import com.google.common.base.Charsets;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
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.SortedList;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.UUID;
import java.util.stream.Collectors;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class MyReputationView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
private InputTextField amountInputTextField, timeInputTextField, saltInputTextField;
private Button lockupButton;
private TableView<MyReputationListItem> tableView;
private final BsqFormatter bsqFormatter;
private final BsqWalletService bsqWalletService;
private final BondingViewUtils bondingViewUtils;
private final HexStringValidator hexStringValidator;
private final BsqValidator bsqValidator;
private final DaoFacade daoFacade;
private final Preferences preferences;
private final IntegerValidator timeInputTextFieldValidator;
private final ObservableList<MyReputationListItem> observableList = FXCollections.observableArrayList();
private final SortedList<MyReputationListItem> sortedList = new SortedList<>(observableList);
private int gridRow = 0;
private ChangeListener<Boolean> amountFocusOutListener, timeFocusOutListener, saltFocusOutListener;
private ChangeListener<String> amountInputTextFieldListener, timeInputTextFieldListener, saltInputTextFieldListener;
private ListChangeListener<MyBondedReputation> myBondedReputationsChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private MyReputationView(BsqFormatter bsqFormatter,
BsqWalletService bsqWalletService,
BondingViewUtils bondingViewUtils,
HexStringValidator hexStringValidator,
BsqValidator bsqValidator,
DaoFacade daoFacade,
Preferences preferences) {
this.bsqFormatter = bsqFormatter;
this.bsqWalletService = bsqWalletService;
this.bondingViewUtils = bondingViewUtils;
this.hexStringValidator = hexStringValidator;
this.bsqValidator = bsqValidator;
this.daoFacade = daoFacade;
this.preferences = preferences;
timeInputTextFieldValidator = new IntegerValidator();
timeInputTextFieldValidator.setMinValue(BondConsensus.getMinLockTime());
timeInputTextFieldValidator.setMaxValue(BondConsensus.getMaxLockTime());
}
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 3, Res.get("dao.bond.reputation.header"));
amountInputTextField = addInputTextField(root, gridRow, Res.get("dao.bond.reputation.amount"),
Layout.FIRST_ROW_DISTANCE);
amountInputTextField.setValidator(bsqValidator);
timeInputTextField = FormBuilder.addInputTextField(root, ++gridRow, Res.get("dao.bond.reputation.time"));
timeInputTextField.setValidator(timeInputTextFieldValidator);
saltInputTextField = FormBuilder.addInputTextField(root, ++gridRow, Res.get("dao.bond.reputation.salt"));
saltInputTextField.setValidator(hexStringValidator);
lockupButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.bond.reputation.lockupButton"));
tableView = FormBuilder.addTableViewWithHeader(root, ++gridRow, Res.get("dao.bond.reputation.table.header"), 20, "last");
createColumns();
tableView.setItems(sortedList);
GridPane.setVgrow(tableView, Priority.ALWAYS);
createListeners();
}
@Override
protected void activate() {
amountInputTextField.textProperty().addListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().addListener(amountFocusOutListener);
timeInputTextField.textProperty().addListener(timeInputTextFieldListener);
timeInputTextField.focusedProperty().addListener(timeFocusOutListener);
saltInputTextField.textProperty().addListener(saltInputTextFieldListener);
saltInputTextField.focusedProperty().addListener(saltFocusOutListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
daoFacade.getMyBondedReputations().addListener(myBondedReputationsChangeListener);
bsqWalletService.addBsqBalanceListener(this);
lockupButton.setOnAction((event) -> {
Coin lockupAmount = ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
int lockupTime = Integer.parseInt(timeInputTextField.getText());
byte[] salt = Utilities.decodeFromHex(saltInputTextField.getText());
bondingViewUtils.lockupBondForReputation(lockupAmount,
lockupTime,
salt,
txId -> {
});
amountInputTextField.setText("");
timeInputTextField.setText("");
setNewRandomSalt();
});
amountInputTextField.resetValidation();
timeInputTextField.resetValidation();
setNewRandomSalt();
updateList();
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
}
@Override
protected void deactivate() {
amountInputTextField.textProperty().removeListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().removeListener(amountFocusOutListener);
timeInputTextField.textProperty().removeListener(timeInputTextFieldListener);
timeInputTextField.focusedProperty().removeListener(timeFocusOutListener);
saltInputTextField.textProperty().removeListener(saltInputTextFieldListener);
saltInputTextField.focusedProperty().removeListener(saltFocusOutListener);
daoFacade.getMyBondedReputations().removeListener(myBondedReputationsChangeListener);
bsqWalletService.removeBsqBalanceListener(this);
sortedList.comparatorProperty().unbind();
lockupButton.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqBalanceListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUpdateBalances(Coin availableConfirmedBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
bsqValidator.setAvailableBalance(availableConfirmedBalance);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void createListeners() {
amountFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
}
};
timeFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
}
};
saltFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
}
};
amountInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
timeInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
saltInputTextFieldListener = (observable, oldValue, newValue) -> updateButtonState();
myBondedReputationsChangeListener = c -> updateList();
}
private void updateList() {
observableList.setAll(daoFacade.getMyBondedReputations().stream()
.map(myBondedReputation -> new MyReputationListItem(myBondedReputation, bsqFormatter))
.sorted(Comparator.comparing(MyReputationListItem::getLockupDateString).reversed())
.collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
}
private void setNewRandomSalt() {
byte[] randomBytes = UUID.randomUUID().toString().getBytes(Charsets.UTF_8);
// We want to limit it to 20 bytes
byte[] hashOfRandomBytes = Hash.getSha256Ripemd160hash(randomBytes);
// bytesAsHexString results in 40 chars
String bytesAsHexString = Utilities.bytesAsHexString(hashOfRandomBytes);
saltInputTextField.setText(bytesAsHexString);
saltInputTextField.resetValidation();
}
private void updateButtonState() {
boolean isValid = bsqValidator.validate(amountInputTextField.getText()).isValid &&
timeInputTextFieldValidator.validate(timeInputTextField.getText()).isValid &&
hexStringValidator.validate(saltInputTextField.getText()).isValid;
lockupButton.setDisable(!isValid);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void createColumns() {
TableColumn<MyReputationListItem, MyReputationListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", "BSQ"));
column.setMinWidth(120);
column.setMaxWidth(column.getMinWidth());
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmount());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockTime"));
column.setMinWidth(60);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockTime());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondState"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(120);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getBondStateString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupDate"));
column.setMinWidth(140);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLockupDateString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupTxId"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
//noinspection Duplicates
if (item != null && !empty) {
String transactionId = item.getTxId();
hyperlinkWithIcon = new ExternalHyperlink(transactionId);
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openTxInBsqBlockExplorer(item.getTxId(), preferences));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId)));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.reputation.salt"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getSalt());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.reputation.hash"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getHash());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new TableColumn<>();
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.getStyleClass().add("last-column");
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyReputationListItem, MyReputationListItem> call(TableColumn<MyReputationListItem,
MyReputationListItem> column) {
return new TableCell<>() {
AutoTooltipButton button;
@Override
public void updateItem(final MyReputationListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty && item.isShowButton()) {
button = new AutoTooltipButton(item.getButtonText());
button.setOnAction(e -> {
if (item.getBondState() == BondState.LOCKUP_TX_CONFIRMED) {
bondingViewUtils.unLock(item.getLockupTxId(),
txId -> {
});
}
});
setGraphic(button);
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
tableView.getColumns().add(column);
}
}

View file

@ -1,103 +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.bonding.roles;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import org.bitcoinj.core.Coin;
import javafx.geometry.Insets;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class RoleDetailsWindow extends Overlay<RoleDetailsWindow> {
private final BondedRoleType bondedRoleType;
private final Optional<RoleProposal> roleProposal;
private final DaoFacade daoFacade;
private final BsqFormatter bsqFormatter;
RoleDetailsWindow(BondedRoleType bondedRoleType, Optional<RoleProposal> roleProposal, DaoFacade daoFacade,
BsqFormatter bsqFormatter) {
this.bondedRoleType = bondedRoleType;
this.roleProposal = roleProposal;
this.daoFacade = daoFacade;
this.bsqFormatter = bsqFormatter;
width = 968;
type = Type.Confirmation;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void show() {
headLine = Res.get("dao.bond.details.header");
createGridPane();
addHeadLine();
addContent();
addButtons();
applyStyles();
display();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createGridPane() {
super.createGridPane();
gridPane.setPadding(new Insets(70, 80, 60, 80));
gridPane.getStyleClass().add("grid-pane");
}
private void addContent() {
FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.bond.details.role"),
bondedRoleType.getDisplayString());
long requiredBond = daoFacade.getRequiredBond(roleProposal);
int unlockTime = roleProposal.map(RoleProposal::getUnlockTime).orElse(bondedRoleType.getUnlockTimeInBlocks());
FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.bond.details.requiredBond"),
bsqFormatter.formatCoinWithCode(Coin.valueOf(requiredBond)));
FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.bond.details.unlockTime"),
Res.get("dao.bond.details.blocks", unlockTime));
FormBuilder.addTopLabelHyperlinkWithIcon(gridPane, ++rowIndex, Res.get("dao.bond.details.link"),
bondedRoleType.getLink(), bondedRoleType.getLink(), 0);
FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.bond.details.isSingleton"),
DisplayUtils.booleanToYesNo(bondedRoleType.isAllowMultipleHolders()));
}
}

View file

@ -1,66 +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.bonding.roles;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.locale.Res;
import java.util.Date;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
class RolesListItem {
private final BondedRole bondedRole;
private final Role role;
private final String buttonText;
private final boolean isButtonVisible;
private final BondState bondState;
private final String bondStateString;
private final String lockupTxId;
private final Date lockupDate;
RolesListItem(BondedRole bondedRole,
DaoFacade daoFacade) {
this.bondedRole = bondedRole;
role = bondedRole.getBondedAsset();
boolean isMyRole = daoFacade.isMyRole(role);
bondState = bondedRole.getBondState();
lockupTxId = bondedRole.getLockupTxId();
lockupDate = new Date(bondedRole.getLockupDate());
bondStateString = Res.get("dao.bond.bondState." + bondedRole.getBondState().name());
boolean showLockup = bondedRole.getBondState() == BondState.READY_FOR_LOCKUP;
boolean showRevoke = bondedRole.getBondState() == BondState.LOCKUP_TX_CONFIRMED;
if (showLockup) {
buttonText = Res.get("dao.bond.table.button.lockup");
} else if (showRevoke) {
buttonText = Res.get("dao.bond.table.button.revoke");
} else {
buttonText = "";
}
isButtonVisible = isMyRole && (showLockup || showRevoke);
}
}

View file

@ -1,33 +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.bonding.roles.RolesView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,335 +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.bonding.roles;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.main.dao.bonding.BondingViewUtils;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.BondState;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import javax.inject.Inject;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.Collectors;
@FxmlView
public class RolesView extends ActivatableView<GridPane, Void> {
private TableView<RolesListItem> tableView;
private final BondingViewUtils bondingViewUtils;
private final BsqFormatter bsqFormatter;
private final DaoFacade daoFacade;
private final Preferences preferences;
private final ObservableList<RolesListItem> observableList = FXCollections.observableArrayList();
private final SortedList<RolesListItem> sortedList = new SortedList<>(observableList);
private ListChangeListener<BondedRole> bondedRoleListChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private RolesView(BsqFormatter bsqFormatter,
BondingViewUtils bondingViewUtils,
DaoFacade daoFacade,
Preferences preferences) {
this.bsqFormatter = bsqFormatter;
this.bondingViewUtils = bondingViewUtils;
this.daoFacade = daoFacade;
this.preferences = preferences;
}
@Override
public void initialize() {
int gridRow = 0;
tableView = FormBuilder.addTableViewWithHeader(root, gridRow, Res.get("dao.bond.bondedRoles"), "last");
createColumns();
tableView.setItems(sortedList);
GridPane.setVgrow(tableView, Priority.ALWAYS);
bondedRoleListChangeListener = c -> updateList();
}
@Override
protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
daoFacade.getBondedRoles().addListener(bondedRoleListChangeListener);
updateList();
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
daoFacade.getBondedRoles().removeListener(bondedRoleListChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateList() {
observableList.setAll(daoFacade.getAcceptedBondedRoles().stream()
.map(bond -> new RolesListItem(bond, daoFacade))
.sorted(Comparator.comparing(RolesListItem::getLockupDate).reversed())
.collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void createColumns() {
TableColumn<RolesListItem, RolesListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.name"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.getStyleClass().add("first-column");
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<RolesListItem, RolesListItem> call(TableColumn<RolesListItem,
RolesListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final RolesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getRole().getName());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.link"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<RolesListItem, RolesListItem> call(TableColumn<RolesListItem,
RolesListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final RolesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String link = item.getRole().getLink();
hyperlinkWithIcon = new ExternalHyperlink(link);
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openWebPage(link));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("shared.openURL", link)));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondType"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<RolesListItem, RolesListItem> call(TableColumn<RolesListItem,
RolesListItem> column) {
return new TableCell<>() {
private Hyperlink hyperlink;
@Override
public void updateItem(final RolesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
BondedRoleType bondedRoleType = item.getRole().getBondedRoleType();
String type = bondedRoleType.getDisplayString();
hyperlink = new Hyperlink(type);
hyperlink.setOnAction(event -> {
Optional<RoleProposal> roleProposal = bondingViewUtils.getAcceptedBondedRoleProposal(item.getRole());
new RoleDetailsWindow(bondedRoleType, roleProposal, daoFacade, bsqFormatter).show();
});
hyperlink.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails", type)));
setGraphic(hyperlink);
} else {
setGraphic(null);
if (hyperlink != null)
hyperlink.setOnAction(null);
}
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.lockupTxId"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<RolesListItem, RolesListItem> call(TableColumn<RolesListItem,
RolesListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
private Label label;
@Override
public void updateItem(final RolesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String transactionId = item.getBondedRole().getLockupTxId();
if (transactionId != null) {
hyperlinkWithIcon = new ExternalHyperlink(transactionId);
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openTxInBsqBlockExplorer(transactionId, preferences));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId)));
setGraphic(hyperlinkWithIcon);
} else {
label = new Label("-");
setGraphic(label);
}
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
if (label != null)
label = null;
}
}
};
}
});
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.bond.table.column.bondState"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(120);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<RolesListItem, RolesListItem> call(TableColumn<RolesListItem,
RolesListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final RolesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getBondStateString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column = new TableColumn<>();
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.getStyleClass().add("last-column");
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<RolesListItem, RolesListItem> call(TableColumn<RolesListItem,
RolesListItem> column) {
return new TableCell<>() {
AutoTooltipButton button;
@Override
public void updateItem(final RolesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty && item.isButtonVisible()) {
if (button == null) {
button = new AutoTooltipButton(item.getButtonText());
button.setMinWidth(70);
button.setOnAction(e -> {
if (item.getBondState() == BondState.READY_FOR_LOCKUP) {
bondingViewUtils.lockupBondForBondedRole(item.getRole(),
txId -> {
});
} else if (item.getBondState() == BondState.LOCKUP_TX_CONFIRMED) {
bondingViewUtils.unLock(item.getLockupTxId(),
txId -> {
});
}
});
setGraphic(button);
}
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
tableView.getColumns().add(column);
}
}

View file

@ -1,38 +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.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.burnbsq.BurnBsqView"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="15"/>
<ScrollPane fitToWidth="true" hbarPolicy="NEVER"
fitToHeight="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</ScrollPane>
</AnchorPane>

View file

@ -1,125 +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.burnbsq;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.burnbsq.assetfee.AssetFeeView;
import bisq.desktop.main.dao.burnbsq.proofofburn.ProofOfBurnView;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
@FxmlView
public class BurnBsqView extends ActivatableView<AnchorPane, Void> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem assetFee, proofOfBurn;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
private ToggleGroup toggleGroup;
@Inject
private BurnBsqView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = (viewPath, data) -> {
if (viewPath.size() != 4 || viewPath.indexOf(BurnBsqView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
toggleGroup = new ToggleGroup();
final List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, BurnBsqView.class);
assetFee = new MenuItem(navigation, toggleGroup, Res.get("dao.burnBsq.menuItem.assetFee"),
AssetFeeView.class, baseNavPath);
proofOfBurn = new MenuItem(navigation, toggleGroup, Res.get("dao.burnBsq.menuItem.proofOfBurn"),
ProofOfBurnView.class, baseNavPath);
leftVBox.getChildren().addAll(assetFee, proofOfBurn);
}
@Override
protected void activate() {
assetFee.activate();
proofOfBurn.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(BurnBsqView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = AssetFeeView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(BurnBsqView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@SuppressWarnings("Duplicates")
@Override
protected void deactivate() {
navigation.removeListener(listener);
assetFee.deactivate();
proofOfBurn.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof AssetFeeView) toggleGroup.selectToggle(assetFee);
else if (view instanceof ProofOfBurnView) toggleGroup.selectToggle(proofOfBurn);
}
}

View file

@ -1,33 +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.burnbsq.assetfee.AssetFeeView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,471 +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.burnbsq.assetfee;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.governance.asset.AssetService;
import bisq.core.dao.governance.asset.StatefulAsset;
import bisq.core.dao.governance.proposal.TxException;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import javafx.util.StringConverter;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class AssetFeeView extends ActivatableView<GridPane, Void> implements BsqBalanceListener, DaoStateListener {
private ComboBox<StatefulAsset> assetComboBox;
private InputTextField feeAmountInputTextField;
private TextField trialPeriodTextField;
private Button payFeeButton;
private TableView<AssetListItem> tableView;
private final BsqFormatter bsqFormatter;
private final BsqWalletService bsqWalletService;
private final BsqValidator bsqValidator;
private final AssetService assetService;
private final DaoStateService daoStateService;
private final CoinFormatter btcFormatter;
private final ObservableList<AssetListItem> observableList = FXCollections.observableArrayList();
private final SortedList<AssetListItem> sortedList = new SortedList<>(observableList);
private int gridRow = 0;
private ChangeListener<Boolean> amountFocusOutListener;
private ChangeListener<String> amountInputTextFieldListener;
@Nullable
private StatefulAsset selectedAsset;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public AssetFeeView(BsqFormatter bsqFormatter,
BsqWalletService bsqWalletService,
BsqValidator bsqValidator,
AssetService assetService,
DaoStateService daoStateService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
this.bsqFormatter = bsqFormatter;
this.bsqWalletService = bsqWalletService;
this.bsqValidator = bsqValidator;
this.assetService = assetService;
this.daoStateService = daoStateService;
this.btcFormatter = btcFormatter;
}
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 3, Res.get("dao.burnBsq.header"));
assetComboBox = FormBuilder.addComboBox(root, gridRow,
Res.get("dao.burnBsq.selectAsset"), Layout.FIRST_ROW_DISTANCE);
assetComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(StatefulAsset statefulAsset) {
return CurrencyUtil.getNameAndCode(statefulAsset.getAsset().getTickerSymbol());
}
@Override
public StatefulAsset fromString(String string) {
return null;
}
});
feeAmountInputTextField = addInputTextField(root, ++gridRow, Res.get("dao.burnBsq.fee"));
feeAmountInputTextField.setValidator(bsqValidator);
trialPeriodTextField = FormBuilder.addTopLabelTextField(root, ++gridRow, Res.get("dao.burnBsq.trialPeriod")).second;
payFeeButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.burnBsq.payFee"));
tableView = FormBuilder.addTableViewWithHeader(root, ++gridRow, Res.get("dao.burnBsq.allAssets"), 20, "last");
createColumns();
tableView.setItems(sortedList);
createListeners();
}
@Override
protected void activate() {
assetComboBox.setOnAction(e -> {
selectedAsset = assetComboBox.getSelectionModel().getSelectedItem();
});
feeAmountInputTextField.textProperty().addListener(amountInputTextFieldListener);
feeAmountInputTextField.focusedProperty().addListener(amountFocusOutListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
daoStateService.addDaoStateListener(this);
bsqWalletService.addBsqBalanceListener(this);
assetService.updateAssetStates();
updateList();
onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance());
payFeeButton.setOnAction((event) -> {
Coin listingFee = getListingFee();
long days = getDays();
// We don't allow shorter periods as it would allow an attacker to try to deactivate other coins by making a
// small fee payment to reduce the trial period and look back period.
// Still not a perfect solution but should be good enough for now.
long minDays = 30;
if (days >= minDays) {
try {
Transaction transaction = assetService.payFee(selectedAsset, listingFee.value);
Coin miningFee = transaction.getFee();
int txVsize = transaction.getVsize();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(listingFee, miningFee, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.burnBsq.assetFee"), () -> doPublishFeeTx(transaction));
} else {
doPublishFeeTx(transaction);
}
} catch (InsufficientMoneyException | TxException e) {
e.printStackTrace();
new Popup().error(e.toString()).show();
}
} else {
new Popup().warning(Res.get("dao.burnBsq.assets.toFewDays", minDays)).show();
}
});
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 100);
updateButtonState();
feeAmountInputTextField.resetValidation();
}
@Override
protected void deactivate() {
assetComboBox.setOnAction(null);
feeAmountInputTextField.textProperty().removeListener(amountInputTextFieldListener);
feeAmountInputTextField.focusedProperty().removeListener(amountFocusOutListener);
daoStateService.removeDaoStateListener(this);
bsqWalletService.removeBsqBalanceListener(this);
sortedList.comparatorProperty().unbind();
payFeeButton.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqBalanceListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUpdateBalances(Coin availableConfirmedBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
onUpdateAvailableConfirmedBalance(availableConfirmedBalance);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
// Delay a bit to reduce load at onParseBlockCompleteAfterBatchProcessing event
UserThread.runAfter(() -> {
assetService.updateAssetStates();
updateList();
}, 300, TimeUnit.MILLISECONDS);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void createListeners() {
amountFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
}
};
amountInputTextFieldListener = (observable, oldValue, newValue) -> {
trialPeriodTextField.setText(Res.get("dao.burnBsq.assets.days", getDays()));
updateButtonState();
};
}
private void onUpdateAvailableConfirmedBalance(Coin availableConfirmedBalance) {
bsqValidator.setAvailableBalance(availableConfirmedBalance);
updateButtonState();
}
private long getDays() {
return getListingFee().value / assetService.getFeePerDay().value;
}
// We only update on new BSQ blocks and at view activation. We do not update at each trade statistics change as
// that would cause too much CPU load. The assetService.updateAssetStates() call takes about 22 ms.
private void updateList() {
// Here we exclude the assets which have been removed by voting. Paying a fee would not change the state.
List<StatefulAsset> statefulAssets = assetService.getStatefulAssets();
ObservableList<StatefulAsset> nonRemovedStatefulAssets = FXCollections.observableArrayList(statefulAssets.stream()
.filter(e -> !e.wasRemovedByVoting())
.collect(Collectors.toList()));
assetComboBox.setItems(nonRemovedStatefulAssets);
// In the table we want to show all including removed assets.
observableList.setAll(statefulAssets.stream()
.map(statefulAsset -> new AssetListItem(statefulAsset, bsqFormatter))
.collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 100);
}
private void updateButtonState() {
boolean isValid = bsqValidator.validate(feeAmountInputTextField.getText()).isValid &&
selectedAsset != null;
payFeeButton.setDisable(!isValid);
}
private Coin getListingFee() {
return ParsingUtils.parseToCoin(feeAmountInputTextField.getText(), bsqFormatter);
}
private void doPublishFeeTx(Transaction transaction) {
assetService.publishTransaction(transaction,
() -> {
assetComboBox.getSelectionModel().clearSelection();
if (!DevEnv.isDevMode())
new Popup().confirmation(Res.get("dao.tx.published.success")).show();
},
errorMessage -> new Popup().warning(errorMessage).show());
feeAmountInputTextField.clear();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void createColumns() {
TableColumn<AssetListItem, AssetListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.nameAndCode"));
column.setMinWidth(120);
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<AssetListItem, AssetListItem> call(TableColumn<AssetListItem,
AssetListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final AssetListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getNameAndCode());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column.setComparator(Comparator.comparing(AssetListItem::getNameAndCode));
column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.state"));
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<AssetListItem, AssetListItem> call(TableColumn<AssetListItem,
AssetListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final AssetListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAssetStateString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column.setComparator(Comparator.comparing(AssetListItem::getAssetStateString));
column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.tradeVolume"));
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<AssetListItem, AssetListItem> call(TableColumn<AssetListItem,
AssetListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final AssetListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getTradedVolumeAsString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column.setComparator(Comparator.comparing(AssetListItem::getTradedVolume));
column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.lookBackPeriod"));
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<AssetListItem, AssetListItem> call(TableColumn<AssetListItem,
AssetListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final AssetListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getLookBackPeriodInDays());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column.setComparator(Comparator.comparing(AssetListItem::getLookBackPeriodInDays));
column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.trialFee"));
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<AssetListItem, AssetListItem> call(TableColumn<AssetListItem,
AssetListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final AssetListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getFeeOfTrialPeriodAsString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column.setComparator(Comparator.comparing(AssetListItem::getFeeOfTrialPeriod));
column = new AutoTooltipTableColumn<>(Res.get("dao.burnBsq.assets.totalFee"));
column.setMinWidth(120);
column.getStyleClass().add("last-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<AssetListItem, AssetListItem> call(TableColumn<AssetListItem,
AssetListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final AssetListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getTotalFeesPaidAsString());
} else
setText("");
}
};
}
});
tableView.getColumns().add(column);
column.setComparator(Comparator.comparing(AssetListItem::getTotalFeesPaid));
}
}

View file

@ -1,57 +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.burnbsq.assetfee;
import bisq.core.dao.governance.asset.StatefulAsset;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import lombok.Value;
@Value
class AssetListItem {
private final StatefulAsset statefulAsset;
private final String tickerSymbol;
private final String assetStateString;
private final int trialPeriodInBlocks;
private final String nameAndCode;
private final long totalFeesPaid;
private final String totalFeesPaidAsString;
private final long feeOfTrialPeriod;
private final String feeOfTrialPeriodAsString;
private final String tradedVolumeAsString;
private final String lookBackPeriodInDays;
private final long tradedVolume;
AssetListItem(StatefulAsset statefulAsset,
BsqFormatter bsqFormatter) {
this.statefulAsset = statefulAsset;
tickerSymbol = statefulAsset.getTickerSymbol();
nameAndCode = statefulAsset.getNameAndCode();
assetStateString = Res.get("dao.assetState." + statefulAsset.getAssetState());
feeOfTrialPeriod = statefulAsset.getFeeOfTrialPeriod();
feeOfTrialPeriodAsString = bsqFormatter.formatCoinWithCode(feeOfTrialPeriod);
totalFeesPaid = statefulAsset.getTotalFeesPaid();
totalFeesPaidAsString = bsqFormatter.formatCoinWithCode(totalFeesPaid);
trialPeriodInBlocks = (int) totalFeesPaid * 144;
tradedVolume = statefulAsset.getTradeVolume();
tradedVolumeAsString = bsqFormatter.formatBTCWithCode(tradedVolume);
lookBackPeriodInDays = Res.get("dao.burnBsq.assets.days", statefulAsset.getLookBackPeriodInDays());
}
}

View file

@ -1,74 +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.burnbsq.proofofburn;
import bisq.desktop.util.DisplayUtils;
import bisq.core.dao.governance.proofofburn.MyProofOfBurn;
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import java.util.Date;
import java.util.Optional;
import lombok.Value;
@Value
class MyProofOfBurnListItem {
private final MyProofOfBurn myProofOfBurn;
private final long amount;
private final String amountAsString;
private final String txId;
private final String hashAsHex;
private final String preImage;
private final String pubKey;
private final Date date;
private final String dateAsString;
MyProofOfBurnListItem(MyProofOfBurn myProofOfBurn, ProofOfBurnService proofOfBurnService, BsqFormatter bsqFormatter) {
this.myProofOfBurn = myProofOfBurn;
preImage = myProofOfBurn.getPreImage();
Optional<Tx> optionalTx = proofOfBurnService.getTx(myProofOfBurn.getTxId());
if (optionalTx.isPresent()) {
Tx tx = optionalTx.get();
date = new Date(tx.getTime());
dateAsString = DisplayUtils.formatDateTime(date);
amount = proofOfBurnService.getAmount(tx);
amountAsString = bsqFormatter.formatCoinWithCode(Coin.valueOf(amount));
txId = tx.getId();
hashAsHex = Utilities.bytesAsHexString(proofOfBurnService.getHashFromOpReturnData(tx));
pubKey = Utilities.bytesAsHexString(proofOfBurnService.getPubKey(txId));
} else {
amount = 0;
amountAsString = Res.get("shared.na");
txId = Res.get("shared.na");
hashAsHex = Res.get("shared.na");
pubKey = Res.get("shared.na");
dateAsString = Res.get("shared.na");
date = new Date(0);
}
}
}

View file

@ -1,53 +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.burnbsq.proofofburn;
import bisq.desktop.util.DisplayUtils;
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Utilities;
import org.bitcoinj.core.Coin;
import java.util.Date;
import lombok.Value;
@Value
class ProofOfBurnListItem {
private final long amount;
private final String amountAsString;
private final String txId;
private final String hashAsHex;
private final String pubKey;
private final Date date;
private final String dateAsString;
ProofOfBurnListItem(Tx tx, ProofOfBurnService proofOfBurnService, BsqFormatter bsqFormatter) {
amount = proofOfBurnService.getAmount(tx);
amountAsString = bsqFormatter.formatCoinWithCode(Coin.valueOf(amount));
txId = tx.getId();
hashAsHex = Utilities.bytesAsHexString(proofOfBurnService.getHashFromOpReturnData(tx));
pubKey = Utilities.bytesAsHexString(proofOfBurnService.getPubKey(txId));
date = new Date(tx.getTime());
dateAsString = DisplayUtils.formatDateTime(date);
}
}

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.burnbsq.proofofburn;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
import bisq.core.locale.Res;
import bisq.common.util.Tuple3;
import bisq.common.util.Utilities;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import java.util.Optional;
import static bisq.desktop.util.FormBuilder.addInputTextField;
class ProofOfBurnSignatureWindow extends Overlay<ProofOfBurnSignatureWindow> {
private final ProofOfBurnService proofOfBurnService;
private final String proofOfBurnTxId;
private final String pubKey;
private TextField sigTextField;
private VBox sigTextFieldBox;
ProofOfBurnSignatureWindow(ProofOfBurnService proofOfBurnService, String proofOfBurnTxId) {
this.proofOfBurnService = proofOfBurnService;
this.proofOfBurnTxId = proofOfBurnTxId;
this.pubKey = proofOfBurnService.getPubKeyAsHex(proofOfBurnTxId);
type = Type.Attention;
}
public void show() {
if (headLine == null)
headLine = Res.get("dao.proofOfBurn.signature.window.title");
width = 800;
createGridPane();
addHeadLine();
addContent();
addButtons();
applyStyles();
display();
}
@SuppressWarnings("Duplicates")
@Override
protected void createGridPane() {
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
gridPane.setPadding(new Insets(64, 64, 64, 64));
gridPane.setPrefWidth(width);
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(100);
gridPane.getColumnConstraints().add(columnConstraints);
}
private void addContent() {
FormBuilder.addTopLabelTextField(gridPane, rowIndex, Res.get("dao.proofOfBurn.pubKey"), pubKey, 40);
InputTextField messageInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.message"));
Button signButton = FormBuilder.addButton(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.sign"), 10);
signButton.setOnAction(e -> {
proofOfBurnService.sign(proofOfBurnTxId, messageInputTextField.getText()).ifPresent(sig -> {
sigTextFieldBox.setVisible(true);
sigTextField.setText(sig);
});
});
Tuple3<Label, TextField, VBox> tuple = FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.sig"));
sigTextFieldBox = tuple.third;
sigTextField = tuple.second;
sigTextFieldBox.setVisible(false);
actionHandlerOptional = Optional.of(() -> {
Utilities.copyToClipboard(sigTextField.getText());
});
actionButtonText = Res.get("dao.proofOfBurn.copySig");
}
}

View file

@ -1,103 +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.burnbsq.proofofburn;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
import bisq.core.locale.Res;
import bisq.common.util.Tuple3;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import java.security.SignatureException;
import static bisq.desktop.util.FormBuilder.addInputTextField;
class ProofOfBurnVerificationWindow extends Overlay<ProofOfBurnVerificationWindow> {
private final ProofOfBurnService proofOfBurnService;
private final String pubKey;
private TextField verificationResultTextField;
private VBox verificationResultBox;
ProofOfBurnVerificationWindow(ProofOfBurnService proofOfBurnService, String proofOfBurnTxId) {
this.proofOfBurnService = proofOfBurnService;
this.pubKey = proofOfBurnService.getPubKeyAsHex(proofOfBurnTxId);
type = Type.Attention;
}
@SuppressWarnings("Duplicates")
@Override
protected void createGridPane() {
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
gridPane.setPadding(new Insets(64, 64, 64, 64));
gridPane.setPrefWidth(width);
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(100);
gridPane.getColumnConstraints().add(columnConstraints);
}
public void show() {
if (headLine == null)
headLine = Res.get("dao.proofOfBurn.verify.window.title");
width = 800;
createGridPane();
addHeadLine();
addContent();
addButtons();
applyStyles();
display();
}
private void addContent() {
FormBuilder.addTopLabelTextField(gridPane, rowIndex, Res.get("dao.proofOfBurn.pubKey"), pubKey, 40);
InputTextField messageInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.message"));
InputTextField signatureInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.sig"));
Button verifyButton = FormBuilder.addButton(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.verify"), 10);
verifyButton.setOnAction(e -> {
try {
verificationResultBox.setVisible(true);
proofOfBurnService.verify(messageInputTextField.getText(), pubKey, signatureInputTextField.getText());
verificationResultTextField.setText(Res.get("dao.proofOfBurn.verificationResult.ok"));
} catch (SignatureException e1) {
verificationResultTextField.setText(Res.get("dao.proofOfBurn.verificationResult.failed"));
}
});
Tuple3<Label, TextField, VBox> tuple = FormBuilder.addTopLabelTextField(gridPane, ++rowIndex, Res.get("dao.proofOfBurn.sig"));
verificationResultBox = tuple.third;
verificationResultTextField = tuple.second;
verificationResultBox.setVisible(false);
}
}

View file

@ -1,33 +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.burnbsq.proofofburn.ProofOfBurnView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,652 +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.burnbsq.proofofburn;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.ExternalHyperlink;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.governance.proofofburn.MyProofOfBurnListService;
import bisq.core.dao.governance.proofofburn.ProofOfBurnService;
import bisq.core.dao.governance.proposal.TxException;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.validation.InputValidator;
import bisq.common.app.DevEnv;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.stream.Collectors;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelTextField;
@FxmlView
public class ProofOfBurnView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
private final ProofOfBurnService proofOfBurnService;
private final MyProofOfBurnListService myProofOfBurnListService;
private final Preferences preferences;
private final CoinFormatter btcFormatter;
private final BsqFormatter bsqFormatter;
private final BsqWalletService bsqWalletService;
private final BsqValidator bsqValidator;
private InputTextField amountInputTextField, preImageTextField;
private TextField hashTextField;
private Button burnButton;
private TableView<MyProofOfBurnListItem> myItemsTableView;
private TableView<ProofOfBurnListItem> allTxsTableView;
private final ObservableList<MyProofOfBurnListItem> myItemsObservableList = FXCollections.observableArrayList();
private final SortedList<MyProofOfBurnListItem> myItemsSortedList = new SortedList<>(myItemsObservableList);
private final ObservableList<ProofOfBurnListItem> allItemsObservableList = FXCollections.observableArrayList();
private final SortedList<ProofOfBurnListItem> allItemsSortedList = new SortedList<>(allItemsObservableList);
private int gridRow = 0;
private ChangeListener<Boolean> amountFocusOutListener, preImageFocusOutListener;
private ChangeListener<String> amountInputTextFieldListener, preImageInputTextFieldListener;
private InvalidationListener updateListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ProofOfBurnView(BsqFormatter bsqFormatter,
BsqWalletService bsqWalletService,
BsqValidator bsqValidator,
ProofOfBurnService proofOfBurnService,
MyProofOfBurnListService myProofOfBurnListService,
Preferences preferences,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
this.bsqFormatter = bsqFormatter;
this.bsqWalletService = bsqWalletService;
this.bsqValidator = bsqValidator;
this.proofOfBurnService = proofOfBurnService;
this.myProofOfBurnListService = myProofOfBurnListService;
this.preferences = preferences;
this.btcFormatter = btcFormatter;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 4, Res.get("dao.proofOfBurn.header"));
amountInputTextField = addInputTextField(root, ++gridRow, Res.get("dao.proofOfBurn.amount"), Layout.FIRST_ROW_DISTANCE);
preImageTextField = addInputTextField(root, ++gridRow, Res.get("dao.proofOfBurn.preImage"));
hashTextField = addTopLabelTextField(root, ++gridRow, Res.get("dao.proofOfBurn.hash")).second;
burnButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.proofOfBurn.burn"));
myItemsTableView = FormBuilder.addTableViewWithHeader(root, ++gridRow, Res.get("dao.proofOfBurn.myItems"), 30);
createColumnsForMyItems();
myItemsTableView.setItems(myItemsSortedList);
allTxsTableView = FormBuilder.addTableViewWithHeader(root, ++gridRow, Res.get("dao.proofOfBurn.allTxs"), 30, "last");
createColumnsForAllTxs();
allTxsTableView.setItems(allItemsSortedList);
createListeners();
}
@Override
protected void activate() {
amountInputTextField.textProperty().addListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().addListener(amountFocusOutListener);
preImageTextField.textProperty().addListener(preImageInputTextFieldListener);
preImageTextField.focusedProperty().addListener(preImageFocusOutListener);
allItemsSortedList.comparatorProperty().bind(allTxsTableView.comparatorProperty());
proofOfBurnService.getUpdateFlag().addListener(updateListener);
bsqWalletService.addBsqBalanceListener(this);
onUpdateAvailableConfirmedBalance(bsqWalletService.getAvailableConfirmedBalance());
burnButton.setOnAction((event) -> {
Coin amount = getAmountFee();
try {
String preImageAsString = preImageTextField.getText();
Transaction transaction = proofOfBurnService.burn(preImageAsString, amount.value);
Coin miningFee = transaction.getFee();
int txVsize = transaction.getVsize();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(amount, miningFee, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.proofOfBurn.header"), () -> doPublishFeeTx(transaction, preImageAsString));
} else {
doPublishFeeTx(transaction, preImageAsString);
}
} catch (InsufficientMoneyException | TxException e) {
e.printStackTrace();
new Popup().error(e.toString()).show();
}
});
amountInputTextField.setValidator(bsqValidator);
preImageTextField.setValidator(new InputValidator());
updateList();
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 4, 6);
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
updateButtonState();
}
@Override
protected void deactivate() {
amountInputTextField.textProperty().removeListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().removeListener(amountFocusOutListener);
amountInputTextField.textProperty().removeListener(amountInputTextFieldListener);
amountInputTextField.focusedProperty().removeListener(amountFocusOutListener);
allItemsSortedList.comparatorProperty().unbind();
proofOfBurnService.getUpdateFlag().removeListener(updateListener);
bsqWalletService.removeBsqBalanceListener(this);
burnButton.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqBalanceListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUpdateBalances(Coin availableConfirmedBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
onUpdateAvailableConfirmedBalance(availableConfirmedBalance);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void createListeners() {
amountFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
}
};
amountInputTextFieldListener = (observable, oldValue, newValue) -> {
updateButtonState();
};
preImageFocusOutListener = (observable, oldValue, newValue) -> {
if (!newValue) {
updateButtonState();
}
};
preImageInputTextFieldListener = (observable, oldValue, newValue) -> {
hashTextField.setText(proofOfBurnService.getHashAsString(newValue));
updateButtonState();
};
updateListener = observable -> updateList();
}
private void onUpdateAvailableConfirmedBalance(Coin availableConfirmedBalance) {
bsqValidator.setAvailableBalance(availableConfirmedBalance);
updateButtonState();
}
private void updateList() {
myItemsObservableList.setAll(myProofOfBurnListService.getMyProofOfBurnList().stream()
.map(myProofOfBurn -> new MyProofOfBurnListItem(myProofOfBurn, proofOfBurnService, bsqFormatter))
.sorted(Comparator.comparing(MyProofOfBurnListItem::getDate).reversed())
.collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 4, 6);
allItemsObservableList.setAll(proofOfBurnService.getProofOfBurnTxList().stream()
.map(tx -> new ProofOfBurnListItem(tx, proofOfBurnService, bsqFormatter))
.collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
}
private void updateButtonState() {
boolean isValid = bsqValidator.validate(amountInputTextField.getText()).isValid &&
preImageTextField.validate();
burnButton.setDisable(!isValid);
}
private Coin getAmountFee() {
return ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
}
private void doPublishFeeTx(Transaction transaction, String preImageAsString) {
proofOfBurnService.publishTransaction(transaction, preImageAsString,
() -> {
if (!DevEnv.isDevMode())
new Popup().confirmation(Res.get("dao.tx.published.success")).show();
},
errorMessage -> new Popup().warning(errorMessage).show());
amountInputTextField.clear();
preImageTextField.clear();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table columns
///////////////////////////////////////////////////////////////////////////////////////////
private void createColumnsForMyItems() {
TableColumn<MyProofOfBurnListItem, MyProofOfBurnListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.amount"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.getStyleClass().add("first-column");
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmountAsString());
} else
setText("");
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(MyProofOfBurnListItem::getAmount));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.date"));
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getDateAsString());
} else
setText("");
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(MyProofOfBurnListItem::getDate));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.preImage"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getPreImage());
} else
setText("");
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(MyProofOfBurnListItem::getPreImage));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.hash"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getHashAsHex());
} else
setText("");
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(MyProofOfBurnListItem::getHashAsHex));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.txs"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
//noinspection Duplicates
if (item != null && !empty) {
String transactionId = item.getTxId();
hyperlinkWithIcon = new ExternalHyperlink(transactionId);
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openTxInBsqBlockExplorer(item.getTxId(), preferences));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId)));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(MyProofOfBurnListItem::getTxId));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.pubKey"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getPubKey());
} else
setText("");
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(MyProofOfBurnListItem::getPubKey));
column = new AutoTooltipTableColumn<>("");
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(60);
column.getStyleClass().add("last-column");
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<MyProofOfBurnListItem, MyProofOfBurnListItem> call(TableColumn<MyProofOfBurnListItem,
MyProofOfBurnListItem> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final MyProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = new AutoTooltipButton(Res.get("dao.proofOfBurn.sign"));
setGraphic(button);
}
button.setOnAction(e -> new ProofOfBurnSignatureWindow(proofOfBurnService, item.getTxId()).show());
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
myItemsTableView.getColumns().add(column);
column.setSortable(false);
}
private void createColumnsForAllTxs() {
TableColumn<ProofOfBurnListItem, ProofOfBurnListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.amount"));
column.setMinWidth(80);
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<ProofOfBurnListItem, ProofOfBurnListItem> call(TableColumn<ProofOfBurnListItem,
ProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getAmountAsString());
} else
setText("");
}
};
}
});
allTxsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(ProofOfBurnListItem::getAmount));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.date"));
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<ProofOfBurnListItem, ProofOfBurnListItem> call(TableColumn<ProofOfBurnListItem,
ProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getDateAsString());
} else
setText("");
}
};
}
});
allTxsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(ProofOfBurnListItem::getDate));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.hash"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<ProofOfBurnListItem, ProofOfBurnListItem> call(TableColumn<ProofOfBurnListItem,
ProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getHashAsHex());
} else
setText("");
}
};
}
});
allTxsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(ProofOfBurnListItem::getHashAsHex));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.txs"));
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProofOfBurnListItem, ProofOfBurnListItem> call(TableColumn<ProofOfBurnListItem,
ProofOfBurnListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon hyperlinkWithIcon;
@Override
public void updateItem(final ProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
//noinspection Duplicates
if (item != null && !empty) {
String transactionId = item.getTxId();
hyperlinkWithIcon = new ExternalHyperlink(transactionId);
hyperlinkWithIcon.setOnAction(event -> GUIUtil.openTxInBsqBlockExplorer(item.getTxId(), preferences));
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", transactionId)));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
if (hyperlinkWithIcon != null)
hyperlinkWithIcon.setOnAction(null);
}
}
};
}
});
allTxsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(ProofOfBurnListItem::getTxId));
column = new AutoTooltipTableColumn<>(Res.get("dao.proofOfBurn.pubKey"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<ProofOfBurnListItem, ProofOfBurnListItem> call(TableColumn<ProofOfBurnListItem,
ProofOfBurnListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setText(item.getPubKey());
} else
setText("");
}
};
}
});
allTxsTableView.getColumns().add(column);
column.setComparator(Comparator.comparing(ProofOfBurnListItem::getPubKey));
column = new AutoTooltipTableColumn<>("");
column.setCellValueFactory(item -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setMinWidth(80);
column.getStyleClass().add("last-column");
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProofOfBurnListItem, ProofOfBurnListItem> call(TableColumn<ProofOfBurnListItem,
ProofOfBurnListItem> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final ProofOfBurnListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = new AutoTooltipButton(Res.get("dao.proofOfBurn.verify"));
setGraphic(button);
}
button.setOnAction(e -> new ProofOfBurnVerificationWindow(proofOfBurnService, item.getTxId()).show());
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
allTxsTableView.getColumns().add(column);
column.setSortable(false);
}
}

View file

@ -1,36 +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.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.economy.EconomyView"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="15"/>
<ScrollPane fitToWidth="true" fitToHeight="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="content"/>
</ScrollPane>
</AnchorPane>

View file

@ -1,135 +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 supply.
*
* 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.economy;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.economy.dashboard.BsqDashboardView;
import bisq.desktop.main.dao.economy.supply.SupplyView;
import bisq.desktop.main.dao.economy.transactions.BSQTransactionsView;
import bisq.core.locale.Res;
import bisq.common.app.DevEnv;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
@FxmlView
public class EconomyView extends ActivatableView<AnchorPane, Void> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem dashboard, supply, transactions;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
private ToggleGroup toggleGroup;
@Inject
private EconomyView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = (viewPath, data) -> {
if (viewPath.size() != 4 || viewPath.indexOf(EconomyView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
toggleGroup = new ToggleGroup();
List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, EconomyView.class);
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), BsqDashboardView.class, baseNavPath);
supply = new MenuItem(navigation, toggleGroup, Res.get("dao.factsAndFigures.menuItem.supply"), SupplyView.class, baseNavPath);
transactions = new MenuItem(navigation, toggleGroup, Res.get("dao.factsAndFigures.menuItem.transactions"), BSQTransactionsView.class, baseNavPath);
leftVBox.getChildren().addAll(dashboard, supply, transactions);
if (!DevEnv.isDaoActivated()) {
dashboard.setDisable(true);
supply.setDisable(true);
transactions.setDisable(true);
}
}
@Override
protected void activate() {
dashboard.activate();
supply.activate();
transactions.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(EconomyView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = BsqDashboardView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(EconomyView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@SuppressWarnings("Duplicates")
@Override
protected void deactivate() {
navigation.removeListener(listener);
dashboard.deactivate();
supply.deactivate();
transactions.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof BsqDashboardView) toggleGroup.selectToggle(dashboard);
else if (view instanceof SupplyView) toggleGroup.selectToggle(supply);
else if (view instanceof BSQTransactionsView) toggleGroup.selectToggle(transactions);
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.economy.dashboard.BsqDashboardView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="50"/>
<ColumnConstraints percentWidth="50"/>
</columnConstraints>
</GridPane>

View file

@ -1,366 +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.economy.dashboard;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.TextFieldWithIcon;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.economy.dashboard.price.PriceChartView;
import bisq.desktop.main.dao.economy.dashboard.volume.VolumeChartView;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.IssuanceType;
import bisq.core.locale.GlobalSettings;
import bisq.core.locale.Res;
import bisq.core.monetary.Price;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.util.AveragePriceUtil;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import java.text.DecimalFormat;
import java.util.Optional;
import static bisq.desktop.util.FormBuilder.addLabelWithSubText;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField;
import static bisq.desktop.util.FormBuilder.addTopLabelTextFieldWithIcon;
@FxmlView
public class BsqDashboardView extends ActivatableView<GridPane, Void> implements DaoStateListener {
private final PriceChartView priceChartView;
private final VolumeChartView volumeChartView;
private final DaoFacade daoFacade;
private final TradeStatisticsManager tradeStatisticsManager;
private final PriceFeedService priceFeedService;
private final Preferences preferences;
private final BsqFormatter bsqFormatter;
private TextField avgPrice90TextField, avgUSDPrice90TextField, marketCapTextField, availableAmountTextField,
usdVolumeTextField, btcVolumeTextField, averageBsqUsdPriceTextField, averageBsqBtcPriceTextField;
private TextFieldWithIcon avgPrice30TextField, avgUSDPrice30TextField;
private Label marketPriceLabel;
private ChangeListener<Number> priceChangeListener;
private int gridRow = 0;
private Coin availableAmount;
private Price avg30DayUSDPrice;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BsqDashboardView(PriceChartView priceChartView,
VolumeChartView volumeChartView,
DaoFacade daoFacade,
TradeStatisticsManager tradeStatisticsManager,
PriceFeedService priceFeedService,
Preferences preferences,
BsqFormatter bsqFormatter) {
this.priceChartView = priceChartView;
this.volumeChartView = volumeChartView;
this.daoFacade = daoFacade;
this.tradeStatisticsManager = tradeStatisticsManager;
this.priceFeedService = priceFeedService;
this.preferences = preferences;
this.bsqFormatter = bsqFormatter;
}
@Override
public void initialize() {
createTextFields();
createPriceChart();
createTradeChart();
priceChangeListener = (observable, oldValue, newValue) -> {
updatePrice();
updateAveragePriceFields(avgPrice90TextField, avgPrice30TextField, false);
updateAveragePriceFields(avgUSDPrice90TextField, avgUSDPrice30TextField, true);
updateMarketCap();
};
}
@Override
protected void activate() {
daoFacade.addBsqStateListener(this);
priceFeedService.updateCounterProperty().addListener(priceChangeListener);
updateWithBsqBlockChainData();
updatePrice();
updateAveragePriceFields(avgPrice90TextField, avgPrice30TextField, false);
updateAveragePriceFields(avgUSDPrice90TextField, avgUSDPrice30TextField, true);
updateMarketCap();
averageBsqUsdPriceTextField.textProperty().bind(Bindings.createStringBinding(
() -> {
DecimalFormat priceFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
priceFormat.setMaximumFractionDigits(4);
return priceFormat.format(priceChartView.averageBsqUsdPriceProperty().get()) + " BSQ/USD";
},
priceChartView.averageBsqUsdPriceProperty()));
averageBsqBtcPriceTextField.textProperty().bind(Bindings.createStringBinding(
() -> {
DecimalFormat priceFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
priceFormat.setMaximumFractionDigits(8);
/* yAxisFormatter = value -> {
value = MathUtils.scaleDownByPowerOf10(value.longValue(), 8);
return priceFormat.format(value) + " BSQ/BTC";
};*/
double scaled = MathUtils.scaleDownByPowerOf10(priceChartView.averageBsqBtcPriceProperty().get(), 8);
return priceFormat.format(scaled) + " BSQ/BTC";
},
priceChartView.averageBsqBtcPriceProperty()));
usdVolumeTextField.textProperty().bind(Bindings.createStringBinding(
() -> {
DecimalFormat volumeFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
volumeFormat.setMaximumFractionDigits(0);
double scaled = MathUtils.scaleDownByPowerOf10(volumeChartView.usdVolumeProperty().get(), 4);
return volumeFormat.format(scaled) + " USD";
},
volumeChartView.usdVolumeProperty()));
btcVolumeTextField.textProperty().bind(Bindings.createStringBinding(
() -> {
DecimalFormat volumeFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
volumeFormat.setMaximumFractionDigits(4);
double scaled = MathUtils.scaleDownByPowerOf10(volumeChartView.btcVolumeProperty().get(), 8);
return volumeFormat.format(scaled) + " BTC";
},
volumeChartView.btcVolumeProperty()));
}
@Override
protected void deactivate() {
daoFacade.removeBsqStateListener(this);
priceFeedService.updateCounterProperty().removeListener(priceChangeListener);
averageBsqUsdPriceTextField.textProperty().unbind();
averageBsqBtcPriceTextField.textProperty().unbind();
usdVolumeTextField.textProperty().unbind();
btcVolumeTextField.textProperty().unbind();
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
updateWithBsqBlockChainData();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Build UI
///////////////////////////////////////////////////////////////////////////////////////////
private void createTextFields() {
Tuple3<Label, Label, VBox> marketPriceBox = addLabelWithSubText(root, gridRow++, "", "");
marketPriceLabel = marketPriceBox.first;
marketPriceLabel.getStyleClass().add("dao-kpi-big");
marketPriceBox.second.getStyleClass().add("dao-kpi-subtext");
avgUSDPrice90TextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.dashboard.avgUSDPrice90"), -20).second;
avgUSDPrice30TextField = addTopLabelTextFieldWithIcon(root, gridRow, 1,
Res.get("dao.factsAndFigures.dashboard.avgUSDPrice30"), -35).second;
AnchorPane.setRightAnchor(avgUSDPrice30TextField.getIconLabel(), 10d);
avgPrice90TextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.dashboard.avgPrice90")).second;
avgPrice30TextField = addTopLabelTextFieldWithIcon(root, gridRow, 1,
Res.get("dao.factsAndFigures.dashboard.avgPrice30"), -15).second;
AnchorPane.setRightAnchor(avgPrice30TextField.getIconLabel(), 10d);
marketCapTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.dashboard.marketCap")).second;
availableAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.dashboard.availableAmount")).second;
}
private void createPriceChart() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 2,
Res.get("dao.factsAndFigures.supply.priceChat"), Layout.FLOATING_LABEL_DISTANCE);
titledGroupBg.getStyleClass().add("last");
priceChartView.initialize();
VBox chartContainer = priceChartView.getRoot();
AnchorPane chartPane = new AnchorPane();
chartPane.getStyleClass().add("chart-pane");
AnchorPane.setTopAnchor(chartContainer, 15d);
AnchorPane.setBottomAnchor(chartContainer, 0d);
AnchorPane.setLeftAnchor(chartContainer, 25d);
AnchorPane.setRightAnchor(chartContainer, 10d);
GridPane.setColumnSpan(chartPane, 2);
GridPane.setRowIndex(chartPane, ++gridRow);
GridPane.setMargin(chartPane, new Insets(Layout.COMPACT_FIRST_ROW_AND_COMPACT_GROUP_DISTANCE, 0, 0, 0));
chartPane.getChildren().add(chartContainer);
root.getChildren().add(chartPane);
averageBsqUsdPriceTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.dashboard.averageBsqUsdPriceFromSelection")).second;
averageBsqBtcPriceTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.dashboard.averageBsqBtcPriceFromSelection")).second;
}
private void createTradeChart() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 2,
Res.get("dao.factsAndFigures.supply.volumeChat"), Layout.FLOATING_LABEL_DISTANCE);
titledGroupBg.getStyleClass().add("last"); // hides separator as we add a second TitledGroupBg
volumeChartView.initialize();
VBox chartContainer = volumeChartView.getRoot();
AnchorPane chartPane = new AnchorPane();
chartPane.getStyleClass().add("chart-pane");
AnchorPane.setTopAnchor(chartContainer, 15d);
AnchorPane.setBottomAnchor(chartContainer, 0d);
AnchorPane.setLeftAnchor(chartContainer, 25d);
AnchorPane.setRightAnchor(chartContainer, 10d);
GridPane.setColumnSpan(chartPane, 2);
GridPane.setRowIndex(chartPane, ++gridRow);
GridPane.setMargin(chartPane, new Insets(Layout.COMPACT_FIRST_ROW_AND_COMPACT_GROUP_DISTANCE, 0, 0, 0));
chartPane.getChildren().add(chartContainer);
root.getChildren().add(chartPane);
usdVolumeTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.dashboard.volumeUsd")).second;
btcVolumeTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.dashboard.volumeBtc")).second;
}
private void updateWithBsqBlockChainData() {
Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply();
Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.COMPENSATION));
Coin issuedAmountFromReimbursementRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT));
Coin totalConfiscatedAmount = Coin.valueOf(daoFacade.getTotalAmountOfConfiscatedTxOutputs());
Coin totalAmountOfBurntBsq = Coin.valueOf(daoFacade.getTotalAmountOfBurntBsq());
availableAmount = issuedAmountFromGenesis
.add(issuedAmountFromCompRequests)
.add(issuedAmountFromReimbursementRequests)
.subtract(totalAmountOfBurntBsq)
.subtract(totalConfiscatedAmount);
availableAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(availableAmount));
}
private void updatePrice() {
Optional<Price> optionalBsqPrice = priceFeedService.getBsqPrice();
if (optionalBsqPrice.isPresent()) {
Price bsqPrice = optionalBsqPrice.get();
marketPriceLabel.setText(FormattingUtils.formatPrice(bsqPrice) + " BSQ/BTC");
} else {
marketPriceLabel.setText(Res.get("shared.na"));
}
}
private void updateMarketCap() {
if (avg30DayUSDPrice != null) {
marketCapTextField.setText(bsqFormatter.formatMarketCap(avg30DayUSDPrice, availableAmount));
} else {
marketCapTextField.setText(Res.get("shared.na"));
}
}
private void updateAveragePriceFields(TextField field90, TextFieldWithIcon field30, boolean isUSDField) {
long average90 = updateAveragePriceField(field90, 90, isUSDField);
long average30 = updateAveragePriceField(field30.getTextField(), 30, isUSDField);
boolean trendUp = average30 > average90;
boolean trendDown = average30 < average90;
Label iconLabel = field30.getIconLabel();
ObservableList<String> styleClass = iconLabel.getStyleClass();
if (trendUp) {
field30.setVisible(true);
field30.setIcon(AwesomeIcon.CIRCLE_ARROW_UP);
styleClass.remove("price-trend-down");
styleClass.add("price-trend-up");
} else if (trendDown) {
field30.setVisible(true);
field30.setIcon(AwesomeIcon.CIRCLE_ARROW_DOWN);
styleClass.remove("price-trend-up");
styleClass.add("price-trend-down");
} else {
iconLabel.setVisible(false);
}
}
private long updateAveragePriceField(TextField textField, int days, boolean isUSDField) {
Tuple2<Price, Price> tuple = AveragePriceUtil.getAveragePriceTuple(preferences, tradeStatisticsManager, days);
Price usdPrice = tuple.first;
Price bsqPrice = tuple.second;
if (isUSDField) {
textField.setText(usdPrice + " BSQ/USD");
if (days == 30) {
avg30DayUSDPrice = usdPrice;
}
} else {
textField.setText(bsqPrice + " BSQ/BTC");
}
Price average = isUSDField ? usdPrice : bsqPrice;
return average.getValue();
}
}

View file

@ -1,223 +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.economy.dashboard.price;
import bisq.desktop.components.chart.ChartDataModel;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.util.MathUtils;
import javax.inject.Inject;
import java.time.Instant;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PriceChartDataModel extends ChartDataModel {
private final TradeStatisticsManager tradeStatisticsManager;
private Map<Long, Double> bsqUsdPriceByInterval, bsqBtcPriceByInterval, btcUsdPriceByInterval;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PriceChartDataModel(TradeStatisticsManager tradeStatisticsManager) {
super();
this.tradeStatisticsManager = tradeStatisticsManager;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void invalidateCache() {
bsqUsdPriceByInterval = null;
bsqBtcPriceByInterval = null;
btcUsdPriceByInterval = null;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Average price from timeline selection
///////////////////////////////////////////////////////////////////////////////////////////
double averageBsqUsdPrice() {
return getAveragePriceFromDateFilter(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ") ||
tradeStatistics.getCurrency().equals("USD"),
PriceChartDataModel::getAverageBsqUsdPrice);
}
double averageBsqBtcPrice() {
return getAveragePriceFromDateFilter(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ"),
PriceChartDataModel::getAverageBsqBtcPrice);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart data
///////////////////////////////////////////////////////////////////////////////////////////
Map<Long, Double> getBsqUsdPriceByInterval() {
if (bsqUsdPriceByInterval != null) {
return bsqUsdPriceByInterval;
}
bsqUsdPriceByInterval = getPriceByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ") ||
tradeStatistics.getCurrency().equals("USD"),
PriceChartDataModel::getAverageBsqUsdPrice);
return bsqUsdPriceByInterval;
}
Map<Long, Double> getBsqBtcPriceByInterval() {
if (bsqBtcPriceByInterval != null) {
return bsqBtcPriceByInterval;
}
bsqBtcPriceByInterval = getPriceByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("BSQ"),
PriceChartDataModel::getAverageBsqBtcPrice);
return bsqBtcPriceByInterval;
}
Map<Long, Double> getBtcUsdPriceByInterval() {
if (btcUsdPriceByInterval != null) {
return btcUsdPriceByInterval;
}
btcUsdPriceByInterval = getPriceByInterval(tradeStatistics -> tradeStatistics.getCurrency().equals("USD"),
PriceChartDataModel::getAverageBtcUsdPrice);
return btcUsdPriceByInterval;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Average price functions
///////////////////////////////////////////////////////////////////////////////////////////
private static double getAverageBsqUsdPrice(List<TradeStatistics3> list) {
double sumBsq = 0;
double sumBtcFromBsqTrades = 0;
double sumBtcFromUsdTrades = 0;
double sumUsd = 0;
for (TradeStatistics3 tradeStatistics : list) {
if (tradeStatistics.getCurrency().equals("BSQ")) {
sumBsq += getBsqAmount(tradeStatistics);
sumBtcFromBsqTrades += getBtcAmount(tradeStatistics);
} else if (tradeStatistics.getCurrency().equals("USD")) {
sumUsd += getUsdAmount(tradeStatistics);
sumBtcFromUsdTrades += getBtcAmount(tradeStatistics);
}
}
if (sumBsq == 0 || sumBtcFromBsqTrades == 0 || sumBtcFromUsdTrades == 0 || sumUsd == 0) {
return 0d;
}
double averageUsdPrice = sumUsd / sumBtcFromUsdTrades;
return sumBtcFromBsqTrades * averageUsdPrice / sumBsq;
}
private static double getAverageBsqBtcPrice(List<TradeStatistics3> list) {
double sumBsq = 0;
double sumBtc = 0;
for (TradeStatistics3 tradeStatistics : list) {
sumBsq += getBsqAmount(tradeStatistics);
sumBtc += getBtcAmount(tradeStatistics);
}
if (sumBsq == 0 || sumBtc == 0) {
return 0d;
}
return MathUtils.scaleUpByPowerOf10(sumBtc / sumBsq, 8);
}
private static double getAverageBtcUsdPrice(List<TradeStatistics3> list) {
double sumUsd = 0;
double sumBtc = 0;
for (TradeStatistics3 tradeStatistics : list) {
sumUsd += getUsdAmount(tradeStatistics);
sumBtc += getBtcAmount(tradeStatistics);
}
if (sumUsd == 0 || sumBtc == 0) {
return 0d;
}
return sumUsd / sumBtc;
}
private static long getBtcAmount(TradeStatistics3 tradeStatistics) {
return tradeStatistics.getAmount();
}
private static double getUsdAmount(TradeStatistics3 tradeStatistics) {
return MathUtils.scaleUpByPowerOf10(tradeStatistics.getTradeVolume().getValue(), 4);
}
private static long getBsqAmount(TradeStatistics3 tradeStatistics) {
return tradeStatistics.getTradeVolume().getValue();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Aggregated collection data by interval
///////////////////////////////////////////////////////////////////////////////////////////
private Map<Long, Double> getPriceByInterval(Predicate<TradeStatistics3> collectionFilter,
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
return getPriceByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
collectionFilter,
tradeStatistics -> toTimeInterval(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
dateFilter,
getAveragePriceFunction);
}
private Map<Long, Double> getPriceByInterval(Collection<TradeStatistics3> collection,
Predicate<TradeStatistics3> collectionFilter,
Function<TradeStatistics3, Long> groupByDateFunction,
Predicate<Long> dateFilter,
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
return collection.stream()
.filter(collectionFilter)
.collect(Collectors.groupingBy(groupByDateFunction))
.entrySet()
.stream()
.filter(entry -> dateFilter.test(entry.getKey()))
.map(entry -> new AbstractMap.SimpleEntry<>(
entry.getKey(),
getAveragePriceFunction.apply(entry.getValue())))
.filter(e -> e.getValue() > 0d)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
private double getAveragePriceFromDateFilter(Predicate<TradeStatistics3> collectionFilter,
Function<List<TradeStatistics3>, Double> getAveragePriceFunction) {
return getAveragePriceFunction.apply(tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(collectionFilter)
.filter(tradeStatistics -> dateFilter.test(tradeStatistics.getDateAsLong() / 1000))
.collect(Collectors.toList()));
}
}

View file

@ -1,158 +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.economy.dashboard.price;
import bisq.desktop.components.chart.ChartView;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.scene.chart.XYChart;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PriceChartView extends ChartView<PriceChartViewModel> {
private XYChart.Series<Number, Number> seriesBsqUsdPrice, seriesBsqBtcPrice, seriesBtcUsdPrice;
private DoubleProperty averageBsqUsdPriceProperty = new SimpleDoubleProperty();
private DoubleProperty averageBsqBtcPriceProperty = new SimpleDoubleProperty();
@Inject
public PriceChartView(PriceChartViewModel model) {
super(model);
setRadioButtonBehaviour(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public ReadOnlyDoubleProperty averageBsqUsdPriceProperty() {
return averageBsqUsdPriceProperty;
}
public ReadOnlyDoubleProperty averageBsqBtcPriceProperty() {
return averageBsqBtcPriceProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onSetYAxisFormatter(XYChart.Series<Number, Number> series) {
if (series == seriesBsqUsdPrice) {
model.setBsqUsdPriceFormatter();
} else if (series == seriesBsqBtcPrice) {
model.setBsqBtcPriceFormatter();
} else {
model.setBtcUsdPriceFormatter();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected Collection<XYChart.Series<Number, Number>> getSeriesForLegend1() {
return List.of(seriesBsqUsdPrice, seriesBsqBtcPrice, seriesBtcUsdPrice);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Timeline navigation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void initBoundsForTimelineNavigation() {
setBoundsForTimelineNavigation(seriesBsqUsdPrice.getData());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Series
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createSeries() {
seriesBsqUsdPrice = new XYChart.Series<>();
seriesBsqUsdPrice.setName(Res.get("dao.factsAndFigures.supply.bsqUsdPrice"));
seriesIndexMap.put(getSeriesId(seriesBsqUsdPrice), 0);
seriesBsqBtcPrice = new XYChart.Series<>();
seriesBsqBtcPrice.setName(Res.get("dao.factsAndFigures.supply.bsqBtcPrice"));
seriesIndexMap.put(getSeriesId(seriesBsqBtcPrice), 1);
seriesBtcUsdPrice = new XYChart.Series<>();
seriesBtcUsdPrice.setName(Res.get("dao.factsAndFigures.supply.btcUsdPrice"));
seriesIndexMap.put(getSeriesId(seriesBtcUsdPrice), 2);
}
@Override
protected void defineAndAddActiveSeries() {
activateSeries(seriesBsqUsdPrice);
onSetYAxisFormatter(seriesBsqUsdPrice);
}
@Override
protected void activateSeries(XYChart.Series<Number, Number> series) {
super.activateSeries(series);
String seriesId = getSeriesId(series);
if (seriesId.equals(getSeriesId(seriesBsqUsdPrice))) {
seriesBsqUsdPrice.getData().setAll(model.getBsqUsdPriceChartData());
} else if (seriesId.equals(getSeriesId(seriesBsqBtcPrice))) {
seriesBsqBtcPrice.getData().setAll(model.getBsqBtcPriceChartData());
} else if (seriesId.equals(getSeriesId(seriesBtcUsdPrice))) {
seriesBtcUsdPrice.getData().setAll(model.getBtcUsdPriceChartData());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void applyData() {
if (activeSeries.contains(seriesBsqUsdPrice)) {
seriesBsqUsdPrice.getData().setAll(model.getBsqUsdPriceChartData());
}
if (activeSeries.contains(seriesBsqBtcPrice)) {
seriesBsqBtcPrice.getData().setAll(model.getBsqBtcPriceChartData());
}
if (activeSeries.contains(seriesBtcUsdPrice)) {
seriesBtcUsdPrice.getData().setAll(model.getBtcUsdPriceChartData());
}
averageBsqBtcPriceProperty.set(model.averageBsqBtcPrice());
averageBsqUsdPriceProperty.set(model.averageBsqUsdPrice());
}
}

View file

@ -1,118 +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.economy.dashboard.price;
import bisq.desktop.components.chart.ChartViewModel;
import bisq.core.locale.GlobalSettings;
import bisq.common.util.MathUtils;
import javax.inject.Inject;
import javafx.scene.chart.XYChart;
import javafx.util.StringConverter;
import java.text.DecimalFormat;
import java.util.List;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PriceChartViewModel extends ChartViewModel<PriceChartDataModel> {
private Function<Number, String> yAxisFormatter = value -> value + " BSQ/USD";
private final DecimalFormat priceFormat;
@Inject
public PriceChartViewModel(PriceChartDataModel dataModel) {
super(dataModel);
priceFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Average price from timeline selection
///////////////////////////////////////////////////////////////////////////////////////////
double averageBsqUsdPrice() {
return dataModel.averageBsqUsdPrice();
}
double averageBsqBtcPrice() {
return dataModel.averageBsqBtcPrice();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart data
///////////////////////////////////////////////////////////////////////////////////////////
List<XYChart.Data<Number, Number>> getBsqUsdPriceChartData() {
return toChartDoubleData(dataModel.getBsqUsdPriceByInterval());
}
List<XYChart.Data<Number, Number>> getBsqBtcPriceChartData() {
return toChartDoubleData(dataModel.getBsqBtcPriceByInterval());
}
List<XYChart.Data<Number, Number>> getBtcUsdPriceChartData() {
return toChartDoubleData(dataModel.getBtcUsdPriceByInterval());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Formatters/Converters
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected StringConverter<Number> getYAxisStringConverter() {
return new StringConverter<>() {
@Override
public String toString(Number value) {
return yAxisFormatter.apply(value);
}
@Override
public Number fromString(String string) {
return null;
}
};
}
void setBsqUsdPriceFormatter() {
priceFormat.setMaximumFractionDigits(4);
yAxisFormatter = value -> priceFormat.format(value) + " BSQ/USD";
}
void setBsqBtcPriceFormatter() {
priceFormat.setMaximumFractionDigits(8);
yAxisFormatter = value -> {
value = MathUtils.scaleDownByPowerOf10(value.longValue(), 8);
return priceFormat.format(value) + " BSQ/BTC";
};
}
void setBtcUsdPriceFormatter() {
priceFormat.setMaximumFractionDigits(0);
yAxisFormatter = value -> priceFormat.format(value) + " BTC/USD";
}
}

View file

@ -1,158 +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.economy.dashboard.volume;
import bisq.desktop.components.chart.ChartDataModel;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
import javax.inject.Inject;
import java.time.Instant;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VolumeChartDataModel extends ChartDataModel {
private final TradeStatisticsManager tradeStatisticsManager;
private Map<Long, Long> usdVolumeByInterval, btcVolumeByInterval;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public VolumeChartDataModel(TradeStatisticsManager tradeStatisticsManager) {
super();
this.tradeStatisticsManager = tradeStatisticsManager;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Total amounts
///////////////////////////////////////////////////////////////////////////////////////////
long getUsdVolume() {
return getUsdVolumeByInterval().values().stream()
.mapToLong(e -> e)
.sum();
}
long getBtcVolume() {
return getBtcVolumeByInterval().values().stream()
.mapToLong(e -> e)
.sum();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void invalidateCache() {
usdVolumeByInterval = null;
btcVolumeByInterval = null;
}
public Map<Long, Long> getUsdVolumeByInterval() {
if (usdVolumeByInterval != null) {
return usdVolumeByInterval;
}
usdVolumeByInterval = getVolumeByInterval(VolumeChartDataModel::getVolumeInUsd);
return usdVolumeByInterval;
}
public Map<Long, Long> getBtcVolumeByInterval() {
if (btcVolumeByInterval != null) {
return btcVolumeByInterval;
}
btcVolumeByInterval = getVolumeByInterval(VolumeChartDataModel::getVolumeInBtc);
return btcVolumeByInterval;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Get volume functions
///////////////////////////////////////////////////////////////////////////////////////////
private static long getVolumeInUsd(List<TradeStatistics3> list) {
double sumBtcFromAllTrades = 0;
double sumBtcFromUsdTrades = 0;
double sumUsd = 0;
for (TradeStatistics3 tradeStatistics : list) {
long amount = tradeStatistics.getAmount();
if (tradeStatistics.getCurrency().equals("USD")) {
sumUsd += tradeStatistics.getTradeVolume().getValue();
sumBtcFromUsdTrades += amount;
}
sumBtcFromAllTrades += amount;
}
if (sumBtcFromAllTrades == 0 || sumBtcFromUsdTrades == 0 || sumUsd == 0) {
return 0L;
}
double averageUsdPrice = sumUsd / sumBtcFromUsdTrades;
// We truncate to 4 decimals
return (long) (sumBtcFromAllTrades * averageUsdPrice);
}
private static long getVolumeInBtc(List<TradeStatistics3> list) {
return list.stream().mapToLong(TradeStatistics3::getAmount).sum();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Aggregated collection data by interval
///////////////////////////////////////////////////////////////////////////////////////////
private Map<Long, Long> getVolumeByInterval(Function<List<TradeStatistics3>, Long> getVolumeFunction) {
return getVolumeByInterval(tradeStatisticsManager.getObservableTradeStatisticsSet(),
tradeStatistics -> toTimeInterval(Instant.ofEpochMilli(tradeStatistics.getDateAsLong())),
dateFilter,
getVolumeFunction);
}
private Map<Long, Long> getVolumeByInterval(Collection<TradeStatistics3> collection,
Function<TradeStatistics3, Long> groupByDateFunction,
Predicate<Long> dateFilter,
Function<List<TradeStatistics3>, Long> getVolumeFunction) {
return collection.stream()
.collect(Collectors.groupingBy(groupByDateFunction))
.entrySet()
.stream()
.filter(entry -> dateFilter.test(entry.getKey()))
.map(entry -> new AbstractMap.SimpleEntry<>(
entry.getKey(),
getVolumeFunction.apply(entry.getValue())))
.filter(e -> e.getValue() > 0L)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}

View file

@ -1,148 +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.economy.dashboard.volume;
import bisq.desktop.components.chart.ChartView;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.scene.chart.XYChart;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.SimpleLongProperty;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VolumeChartView extends ChartView<VolumeChartViewModel> {
private XYChart.Series<Number, Number> seriesUsdVolume, seriesBtcVolume;
private LongProperty usdVolumeProperty = new SimpleLongProperty();
private LongProperty btcVolumeProperty = new SimpleLongProperty();
@Inject
public VolumeChartView(VolumeChartViewModel model) {
super(model);
setRadioButtonBehaviour(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public ReadOnlyLongProperty usdVolumeProperty() {
return usdVolumeProperty;
}
public ReadOnlyLongProperty btcVolumeProperty() {
return btcVolumeProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onSetYAxisFormatter(XYChart.Series<Number, Number> series) {
if (series == seriesUsdVolume) {
model.setUsdVolumeFormatter();
} else {
model.setBtcVolumeFormatter();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected Collection<XYChart.Series<Number, Number>> getSeriesForLegend1() {
return List.of(seriesUsdVolume, seriesBtcVolume);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Timeline navigation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void initBoundsForTimelineNavigation() {
setBoundsForTimelineNavigation(seriesUsdVolume.getData());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Series
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createSeries() {
seriesUsdVolume = new XYChart.Series<>();
seriesUsdVolume.setName(Res.get("dao.factsAndFigures.supply.tradeVolumeInUsd"));
seriesIndexMap.put(getSeriesId(seriesUsdVolume), 0);
seriesBtcVolume = new XYChart.Series<>();
seriesBtcVolume.setName(Res.get("dao.factsAndFigures.supply.tradeVolumeInBtc"));
seriesIndexMap.put(getSeriesId(seriesBtcVolume), 1);
}
@Override
protected void defineAndAddActiveSeries() {
activateSeries(seriesUsdVolume);
onSetYAxisFormatter(seriesUsdVolume);
}
@Override
protected void activateSeries(XYChart.Series<Number, Number> series) {
super.activateSeries(series);
if (getSeriesId(series).equals(getSeriesId(seriesUsdVolume))) {
seriesUsdVolume.getData().setAll(model.getUsdVolumeChartData());
} else if (getSeriesId(series).equals(getSeriesId(seriesBtcVolume))) {
seriesBtcVolume.getData().setAll(model.getBtcVolumeChartData());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void applyData() {
if (activeSeries.contains(seriesUsdVolume)) {
seriesUsdVolume.getData().setAll(model.getUsdVolumeChartData());
}
if (activeSeries.contains(seriesBtcVolume)) {
seriesBtcVolume.getData().setAll(model.getBtcVolumeChartData());
}
usdVolumeProperty.set(model.getUsdVolume());
btcVolumeProperty.set(model.getBtcVolume());
}
}

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.economy.dashboard.volume;
import bisq.desktop.components.chart.ChartViewModel;
import bisq.core.locale.GlobalSettings;
import bisq.common.util.MathUtils;
import javax.inject.Inject;
import javafx.scene.chart.XYChart;
import javafx.util.StringConverter;
import java.text.DecimalFormat;
import java.util.List;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VolumeChartViewModel extends ChartViewModel<VolumeChartDataModel> {
private Function<Number, String> yAxisFormatter = value -> value + " USD";
private final DecimalFormat volumeFormat;
@Inject
public VolumeChartViewModel(VolumeChartDataModel dataModel) {
super(dataModel);
volumeFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Total amounts
///////////////////////////////////////////////////////////////////////////////////////////
long getUsdVolume() {
return dataModel.getUsdVolume();
}
long getBtcVolume() {
return dataModel.getBtcVolume();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart data
///////////////////////////////////////////////////////////////////////////////////////////
List<XYChart.Data<Number, Number>> getUsdVolumeChartData() {
return toChartLongData(dataModel.getUsdVolumeByInterval());
}
List<XYChart.Data<Number, Number>> getBtcVolumeChartData() {
return toChartLongData(dataModel.getBtcVolumeByInterval());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Formatters/Converters
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected StringConverter<Number> getYAxisStringConverter() {
return new StringConverter<>() {
@Override
public String toString(Number value) {
return yAxisFormatter.apply(value);
}
@Override
public Number fromString(String string) {
return null;
}
};
}
void setUsdVolumeFormatter() {
volumeFormat.setMaximumFractionDigits(0);
yAxisFormatter = value -> volumeFormat.format(MathUtils.scaleDownByPowerOf10(value.longValue(), 4)) + " USD";
}
void setBtcVolumeFormatter() {
volumeFormat.setMaximumFractionDigits(4);
yAxisFormatter = value -> volumeFormat.format(MathUtils.scaleDownByPowerOf10(value.longValue(), 8)) + " BTC";
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.economy.supply.SupplyView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="50"/>
<ColumnConstraints percentWidth="50"/>
</columnConstraints>
</GridPane>

View file

@ -1,213 +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.economy.supply;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.economy.supply.dao.DaoChartView;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyLongProperty;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField;
@FxmlView
public class SupplyView extends ActivatableView<GridPane, Void> implements DaoStateListener {
private final DaoFacade daoFacade;
private final DaoChartView daoChartView;
private final BsqFormatter bsqFormatter;
private TextField genesisIssueAmountTextField, compensationAmountTextField, reimbursementAmountTextField,
bsqTradeFeeAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField,
totalUnlockedAmountTextField, totalConfiscatedAmountTextField, proofOfBurnAmountTextField;
private int gridRow = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private SupplyView(DaoFacade daoFacade,
DaoChartView daoChartView,
BsqFormatter bsqFormatter) {
this.daoFacade = daoFacade;
this.daoChartView = daoChartView;
this.bsqFormatter = bsqFormatter;
}
@Override
public void initialize() {
createDaoChart();
createIssuedAndBurnedFields();
createLockedBsqFields();
}
@Override
protected void activate() {
daoFacade.addBsqStateListener(this);
compensationAmountTextField.textProperty().bind(Bindings.createStringBinding(
() -> getFormattedValue(daoChartView.compensationAmountProperty()),
daoChartView.compensationAmountProperty()));
reimbursementAmountTextField.textProperty().bind(Bindings.createStringBinding(
() -> getFormattedValue(daoChartView.reimbursementAmountProperty()),
daoChartView.reimbursementAmountProperty()));
bsqTradeFeeAmountTextField.textProperty().bind(Bindings.createStringBinding(
() -> getFormattedValue(daoChartView.bsqTradeFeeAmountProperty()),
daoChartView.bsqTradeFeeAmountProperty()));
proofOfBurnAmountTextField.textProperty().bind(Bindings.createStringBinding(
() -> getFormattedValue(daoChartView.proofOfBurnAmountProperty()),
daoChartView.proofOfBurnAmountProperty()));
Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply();
genesisIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromGenesis));
updateWithBsqBlockChainData();
}
@Override
protected void deactivate() {
daoFacade.removeBsqStateListener(this);
compensationAmountTextField.textProperty().unbind();
reimbursementAmountTextField.textProperty().unbind();
bsqTradeFeeAmountTextField.textProperty().unbind();
proofOfBurnAmountTextField.textProperty().unbind();
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
updateWithBsqBlockChainData();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Build UI
///////////////////////////////////////////////////////////////////////////////////////////
private void createDaoChart() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 2, Res.get("dao.factsAndFigures.supply.issuedVsBurnt"));
titledGroupBg.getStyleClass().add("last"); // hides separator as we add a second TitledGroupBg
daoChartView.initialize();
VBox chartContainer = daoChartView.getRoot();
AnchorPane chartPane = new AnchorPane();
chartPane.getStyleClass().add("chart-pane");
AnchorPane.setTopAnchor(chartContainer, 15d);
AnchorPane.setBottomAnchor(chartContainer, 0d);
AnchorPane.setLeftAnchor(chartContainer, 25d);
AnchorPane.setRightAnchor(chartContainer, 10d);
GridPane.setColumnSpan(chartPane, 2);
GridPane.setRowIndex(chartPane, ++gridRow);
GridPane.setMargin(chartPane, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0));
chartPane.getChildren().add(chartContainer);
root.getChildren().add(chartPane);
}
private void createIssuedAndBurnedFields() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.factsAndFigures.supply.issued"), Layout.FLOATING_LABEL_DISTANCE);
titledGroupBg.getStyleClass().add("last"); // hides separator as we add a second TitledGroupBg
Tuple3<Label, TextField, VBox> genesisAmountTuple = addTopLabelReadOnlyTextField(root, gridRow,
Res.get("dao.factsAndFigures.supply.genesisIssueAmount"), Layout.COMPACT_FIRST_ROW_AND_COMPACT_GROUP_DISTANCE);
genesisIssueAmountTextField = genesisAmountTuple.second;
GridPane.setColumnSpan(genesisAmountTuple.third, 2);
compensationAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.supply.compRequestIssueAmount")).second;
reimbursementAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.supply.reimbursementAmount")).second;
addTitledGroupBg(root, ++gridRow, 1, Res.get("dao.factsAndFigures.supply.burnt"), Layout.GROUP_DISTANCE_WITHOUT_SEPARATOR);
bsqTradeFeeAmountTextField = addTopLabelReadOnlyTextField(root, gridRow,
Res.get("dao.factsAndFigures.supply.bsqTradeFee"), Layout.COMPACT_FIRST_ROW_AND_COMPACT_GROUP_DISTANCE).second;
proofOfBurnAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.supply.proofOfBurn"), Layout.COMPACT_FIRST_ROW_AND_COMPACT_GROUP_DISTANCE).second;
}
private void createLockedBsqFields() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.factsAndFigures.supply.locked"), Layout.GROUP_DISTANCE);
titledGroupBg.getStyleClass().add("last");
totalLockedUpAmountTextField = addTopLabelReadOnlyTextField(root, gridRow,
Res.get("dao.factsAndFigures.supply.totalLockedUpAmount"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
totalUnlockingAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.supply.totalUnlockingAmount"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
totalUnlockedAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.supply.totalUnlockedAmount")).second;
totalConfiscatedAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.supply.totalConfiscatedAmount")).second;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
private void updateWithBsqBlockChainData() {
Coin totalLockedUpAmount = Coin.valueOf(daoFacade.getTotalLockupAmount());
totalLockedUpAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalLockedUpAmount));
Coin totalUnlockingAmount = Coin.valueOf(daoFacade.getTotalAmountOfUnLockingTxOutputs());
totalUnlockingAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockingAmount));
Coin totalUnlockedAmount = Coin.valueOf(daoFacade.getTotalAmountOfUnLockedTxOutputs());
totalUnlockedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockedAmount));
Coin totalConfiscatedAmount = Coin.valueOf(daoFacade.getTotalAmountOfConfiscatedTxOutputs());
totalConfiscatedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalConfiscatedAmount));
}
private String getFormattedValue(ReadOnlyLongProperty property) {
return bsqFormatter.formatAmountWithGroupSeparatorAndCode(Coin.valueOf(property.get()));
}
}

View file

@ -1,276 +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.economy.supply.dao;
import bisq.desktop.components.chart.ChartDataModel;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.governance.Issuance;
import bisq.core.dao.state.model.governance.IssuanceType;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.time.Instant;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class DaoChartDataModel extends ChartDataModel {
private final DaoStateService daoStateService;
private final Function<Issuance, Long> blockTimeOfIssuanceFunction;
private Map<Long, Long> totalIssuedByInterval, compensationByInterval, reimbursementByInterval,
totalBurnedByInterval, bsqTradeFeeByInterval, proofOfBurnByInterval;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public DaoChartDataModel(DaoStateService daoStateService) {
super();
this.daoStateService = daoStateService;
// TODO getBlockTime is the bottleneck. Add a lookup map to daoState to fix that in a dedicated PR.
blockTimeOfIssuanceFunction = memoize(issuance -> {
int height = daoStateService.getStartHeightOfCurrentCycle(issuance.getChainHeight()).orElse(0);
return daoStateService.getBlockTime(height);
});
}
@Override
protected void invalidateCache() {
totalIssuedByInterval = null;
compensationByInterval = null;
reimbursementByInterval = null;
totalBurnedByInterval = null;
bsqTradeFeeByInterval = null;
proofOfBurnByInterval = null;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Total amounts
///////////////////////////////////////////////////////////////////////////////////////////
long getCompensationAmount() {
return getCompensationByInterval().values().stream()
.mapToLong(e -> e)
.sum();
}
long getReimbursementAmount() {
return getReimbursementByInterval().values().stream()
.mapToLong(e -> e)
.sum();
}
long getBsqTradeFeeAmount() {
return getBsqTradeFeeByInterval().values().stream()
.mapToLong(e -> e)
.sum();
}
long getProofOfBurnAmount() {
return getProofOfBurnByInterval().values().stream()
.mapToLong(e -> e)
.sum();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data for chart
///////////////////////////////////////////////////////////////////////////////////////////
Map<Long, Long> getTotalIssuedByInterval() {
if (totalIssuedByInterval != null) {
return totalIssuedByInterval;
}
Map<Long, Long> compensationMap = getCompensationByInterval();
Map<Long, Long> reimbursementMap = getReimbursementByInterval();
totalIssuedByInterval = getMergedMap(compensationMap, reimbursementMap, Long::sum);
return totalIssuedByInterval;
}
Map<Long, Long> getCompensationByInterval() {
if (compensationByInterval != null) {
return compensationByInterval;
}
Set<Issuance> issuanceSetForType = daoStateService.getIssuanceSetForType(IssuanceType.COMPENSATION);
Map<Long, Long> issuedBsqByInterval = getIssuedBsqByInterval(issuanceSetForType, getDateFilter());
Map<Long, Long> historicalIssuanceByInterval = getHistoricalIssuedBsqByInterval(DaoEconomyHistoricalData.COMPENSATIONS_BY_CYCLE_DATE, getDateFilter());
compensationByInterval = getMergedMap(issuedBsqByInterval, historicalIssuanceByInterval, (daoDataValue, staticDataValue) -> staticDataValue);
return compensationByInterval;
}
Map<Long, Long> getReimbursementByInterval() {
if (reimbursementByInterval != null) {
return reimbursementByInterval;
}
Map<Long, Long> issuedBsqByInterval = getIssuedBsqByInterval(daoStateService.getIssuanceSetForType(IssuanceType.REIMBURSEMENT), getDateFilter());
Map<Long, Long> historicalIssuanceByInterval = getHistoricalIssuedBsqByInterval(DaoEconomyHistoricalData.REIMBURSEMENTS_BY_CYCLE_DATE, getDateFilter());
reimbursementByInterval = getMergedMap(issuedBsqByInterval, historicalIssuanceByInterval, (daoDataValue, staticDataValue) -> staticDataValue);
return reimbursementByInterval;
}
Map<Long, Long> getTotalBurnedByInterval() {
if (totalBurnedByInterval != null) {
return totalBurnedByInterval;
}
Map<Long, Long> tradeFee = getBsqTradeFeeByInterval();
Map<Long, Long> proofOfBurn = getProofOfBurnByInterval();
totalBurnedByInterval = getMergedMap(tradeFee, proofOfBurn, Long::sum);
return totalBurnedByInterval;
}
Map<Long, Long> getBsqTradeFeeByInterval() {
if (bsqTradeFeeByInterval != null) {
return bsqTradeFeeByInterval;
}
bsqTradeFeeByInterval = getBurntBsqByInterval(daoStateService.getTradeFeeTxs(), getDateFilter());
return bsqTradeFeeByInterval;
}
Map<Long, Long> getProofOfBurnByInterval() {
if (proofOfBurnByInterval != null) {
return proofOfBurnByInterval;
}
proofOfBurnByInterval = getBurntBsqByInterval(daoStateService.getProofOfBurnTxs(), getDateFilter());
return proofOfBurnByInterval;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Aggregated collection data by interval
///////////////////////////////////////////////////////////////////////////////////////////
private Map<Long, Long> getIssuedBsqByInterval(Set<Issuance> issuanceSet, Predicate<Long> dateFilter) {
return issuanceSet.stream()
.collect(Collectors.groupingBy(issuance ->
toTimeInterval(Instant.ofEpochMilli(blockTimeOfIssuanceFunction.apply(issuance)))))
.entrySet()
.stream()
.filter(entry -> dateFilter.test(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> entry.getValue().stream()
.mapToLong(Issuance::getAmount)
.sum()));
}
private Map<Long, Long> getHistoricalIssuedBsqByInterval(Map<Long, Long> historicalData,
Predicate<Long> dateFilter) {
return historicalData.entrySet().stream()
.filter(e -> dateFilter.test(e.getKey()))
.collect(Collectors.toMap(e -> toTimeInterval(Instant.ofEpochSecond(e.getKey())),
Map.Entry::getValue,
(a, b) -> a + b));
}
private Map<Long, Long> getBurntBsqByInterval(Collection<Tx> txs, Predicate<Long> dateFilter) {
return txs.stream()
.collect(Collectors.groupingBy(tx -> toTimeInterval(Instant.ofEpochMilli(tx.getTime()))))
.entrySet()
.stream()
.filter(entry -> dateFilter.test(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey,
entry -> entry.getValue().stream()
.mapToLong(Tx::getBurntBsq)
.sum()));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private static <T, R> Function<T, R> memoize(Function<T, R> fn) {
Map<T, R> map = new ConcurrentHashMap<>();
return x -> map.computeIfAbsent(x, fn);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Historical data
///////////////////////////////////////////////////////////////////////////////////////////
// We did not use the reimbursement requests initially (but the compensation requests) because the limits
// have been too low. Over time it got mixed in compensation requests and reimbursement requests.
// To reflect that we use static data derived from the Github data. For new data we do not need that anymore
// as we have clearly separated that now. In case we have duplicate data for a months we use the static data.
private static class DaoEconomyHistoricalData {
// Key is start date of the cycle in epoch seconds, value is reimbursement amount
public final static Map<Long, Long> REIMBURSEMENTS_BY_CYCLE_DATE = new HashMap<>();
public final static Map<Long, Long> COMPENSATIONS_BY_CYCLE_DATE = new HashMap<>();
static {
REIMBURSEMENTS_BY_CYCLE_DATE.put(1571349571L, 60760L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1574180991L, 2621000L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1576966522L, 4769100L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1579613568L, 0L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1582399054L, 9186600L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1585342220L, 12089400L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1588025030L, 5420700L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1591004931L, 9138760L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1593654027L, 10821807L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1596407074L, 2160157L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1599175867L, 8769408L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1601861442L, 4956585L);
REIMBURSEMENTS_BY_CYCLE_DATE.put(1604845863L, 2121664L);
COMPENSATIONS_BY_CYCLE_DATE.put(1555340856L, 6931863L);
COMPENSATIONS_BY_CYCLE_DATE.put(1558083590L, 2287000L);
COMPENSATIONS_BY_CYCLE_DATE.put(1560771266L, 2273000L);
COMPENSATIONS_BY_CYCLE_DATE.put(1563347672L, 2943772L);
COMPENSATIONS_BY_CYCLE_DATE.put(1566009595L, 10040170L);
COMPENSATIONS_BY_CYCLE_DATE.put(1568643566L, 8685115L);
COMPENSATIONS_BY_CYCLE_DATE.put(1571349571L, 7315879L);
COMPENSATIONS_BY_CYCLE_DATE.put(1574180991L, 12508300L);
COMPENSATIONS_BY_CYCLE_DATE.put(1576966522L, 5884500L);
COMPENSATIONS_BY_CYCLE_DATE.put(1579613568L, 8206000L);
COMPENSATIONS_BY_CYCLE_DATE.put(1582399054L, 3518364L);
COMPENSATIONS_BY_CYCLE_DATE.put(1585342220L, 6231700L);
COMPENSATIONS_BY_CYCLE_DATE.put(1588025030L, 4391400L);
COMPENSATIONS_BY_CYCLE_DATE.put(1591004931L, 3636463L);
COMPENSATIONS_BY_CYCLE_DATE.put(1593654027L, 6156631L);
COMPENSATIONS_BY_CYCLE_DATE.put(1596407074L, 5838368L);
COMPENSATIONS_BY_CYCLE_DATE.put(1599175867L, 6086442L);
COMPENSATIONS_BY_CYCLE_DATE.put(1601861442L, 5615973L);
COMPENSATIONS_BY_CYCLE_DATE.put(1604845863L, 7782667L);
}
}
}

View file

@ -1,211 +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.economy.supply.dao;
import bisq.desktop.components.chart.ChartView;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.scene.chart.XYChart;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.SimpleLongProperty;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DaoChartView extends ChartView<DaoChartViewModel> {
private LongProperty compensationAmountProperty = new SimpleLongProperty();
private LongProperty reimbursementAmountProperty = new SimpleLongProperty();
private LongProperty bsqTradeFeeAmountProperty = new SimpleLongProperty();
private LongProperty proofOfBurnAmountProperty = new SimpleLongProperty();
private XYChart.Series<Number, Number> seriesBsqTradeFee, seriesProofOfBurn, seriesCompensation,
seriesReimbursement, seriesTotalIssued, seriesTotalBurned;
@Inject
public DaoChartView(DaoChartViewModel model) {
super(model);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API Total amounts
///////////////////////////////////////////////////////////////////////////////////////////
public ReadOnlyLongProperty compensationAmountProperty() {
return compensationAmountProperty;
}
public ReadOnlyLongProperty reimbursementAmountProperty() {
return reimbursementAmountProperty;
}
public ReadOnlyLongProperty bsqTradeFeeAmountProperty() {
return bsqTradeFeeAmountProperty;
}
public ReadOnlyLongProperty proofOfBurnAmountProperty() {
return proofOfBurnAmountProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Legend
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected Collection<XYChart.Series<Number, Number>> getSeriesForLegend1() {
return List.of(seriesTotalIssued, seriesCompensation, seriesReimbursement);
}
@Override
protected Collection<XYChart.Series<Number, Number>> getSeriesForLegend2() {
return List.of(seriesTotalBurned, seriesBsqTradeFee, seriesProofOfBurn);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Timeline navigation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void initBoundsForTimelineNavigation() {
setBoundsForTimelineNavigation(seriesTotalBurned.getData());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Series
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createSeries() {
seriesTotalIssued = new XYChart.Series<>();
seriesTotalIssued.setName(Res.get("dao.factsAndFigures.supply.totalIssued"));
seriesIndexMap.put(getSeriesId(seriesTotalIssued), 0);
seriesTotalBurned = new XYChart.Series<>();
seriesTotalBurned.setName(Res.get("dao.factsAndFigures.supply.totalBurned"));
seriesIndexMap.put(getSeriesId(seriesTotalBurned), 1);
seriesCompensation = new XYChart.Series<>();
seriesCompensation.setName(Res.get("dao.factsAndFigures.supply.compReq"));
seriesIndexMap.put(getSeriesId(seriesCompensation), 2);
seriesReimbursement = new XYChart.Series<>();
seriesReimbursement.setName(Res.get("dao.factsAndFigures.supply.reimbursement"));
seriesIndexMap.put(getSeriesId(seriesReimbursement), 3);
seriesBsqTradeFee = new XYChart.Series<>();
seriesBsqTradeFee.setName(Res.get("dao.factsAndFigures.supply.bsqTradeFee"));
seriesIndexMap.put(getSeriesId(seriesBsqTradeFee), 4);
seriesProofOfBurn = new XYChart.Series<>();
seriesProofOfBurn.setName(Res.get("dao.factsAndFigures.supply.proofOfBurn"));
seriesIndexMap.put(getSeriesId(seriesProofOfBurn), 5);
}
@Override
protected void defineAndAddActiveSeries() {
activateSeries(seriesTotalIssued);
activateSeries(seriesTotalBurned);
}
@Override
protected void activateSeries(XYChart.Series<Number, Number> series) {
super.activateSeries(series);
if (getSeriesId(series).equals(getSeriesId(seriesTotalIssued))) {
applyTotalIssued();
} else if (getSeriesId(series).equals(getSeriesId(seriesCompensation))) {
applyCompensation();
} else if (getSeriesId(series).equals(getSeriesId(seriesReimbursement))) {
applyReimbursement();
} else if (getSeriesId(series).equals(getSeriesId(seriesTotalBurned))) {
applyTotalBurned();
} else if (getSeriesId(series).equals(getSeriesId(seriesBsqTradeFee))) {
applyBsqTradeFee();
} else if (getSeriesId(series).equals(getSeriesId(seriesProofOfBurn))) {
applyProofOfBurn();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Data
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void applyData() {
if (activeSeries.contains(seriesTotalIssued)) {
applyTotalIssued();
}
if (activeSeries.contains(seriesCompensation)) {
applyCompensation();
}
if (activeSeries.contains(seriesReimbursement)) {
applyReimbursement();
}
if (activeSeries.contains(seriesTotalBurned)) {
applyTotalBurned();
}
if (activeSeries.contains(seriesBsqTradeFee)) {
applyBsqTradeFee();
}
if (activeSeries.contains(seriesProofOfBurn)) {
applyProofOfBurn();
}
compensationAmountProperty.set(model.getCompensationAmount());
reimbursementAmountProperty.set(model.getReimbursementAmount());
bsqTradeFeeAmountProperty.set(model.getBsqTradeFeeAmount());
proofOfBurnAmountProperty.set(model.getProofOfBurnAmount());
}
private void applyTotalIssued() {
seriesTotalIssued.getData().setAll(model.getTotalIssuedChartData());
}
private void applyCompensation() {
seriesCompensation.getData().setAll(model.getCompensationChartData());
}
private void applyReimbursement() {
seriesReimbursement.getData().setAll(model.getReimbursementChartData());
}
private void applyTotalBurned() {
seriesTotalBurned.getData().setAll(model.getTotalBurnedChartData());
}
private void applyBsqTradeFee() {
seriesBsqTradeFee.getData().setAll(model.getBsqTradeFeeChartData());
}
private void applyProofOfBurn() {
seriesProofOfBurn.getData().setAll(model.getProofOfBurnChartData());
}
}

View file

@ -1,125 +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.economy.supply.dao;
import bisq.desktop.components.chart.ChartViewModel;
import bisq.core.locale.GlobalSettings;
import bisq.core.util.coin.BsqFormatter;
import javax.inject.Inject;
import javafx.scene.chart.XYChart;
import javafx.util.StringConverter;
import java.text.DecimalFormat;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DaoChartViewModel extends ChartViewModel<DaoChartDataModel> {
private final DecimalFormat priceFormat;
private final BsqFormatter bsqFormatter;
@Inject
public DaoChartViewModel(DaoChartDataModel dataModel, BsqFormatter bsqFormatter) {
super(dataModel);
this.bsqFormatter = bsqFormatter;
priceFormat = (DecimalFormat) DecimalFormat.getNumberInstance(GlobalSettings.getLocale());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart data
///////////////////////////////////////////////////////////////////////////////////////////
List<XYChart.Data<Number, Number>> getTotalIssuedChartData() {
return toChartData(dataModel.getTotalIssuedByInterval());
}
List<XYChart.Data<Number, Number>> getCompensationChartData() {
return toChartData(dataModel.getCompensationByInterval());
}
List<XYChart.Data<Number, Number>> getReimbursementChartData() {
return toChartData(dataModel.getReimbursementByInterval());
}
List<XYChart.Data<Number, Number>> getTotalBurnedChartData() {
return toChartData(dataModel.getTotalBurnedByInterval());
}
List<XYChart.Data<Number, Number>> getBsqTradeFeeChartData() {
return toChartData(dataModel.getBsqTradeFeeByInterval());
}
List<XYChart.Data<Number, Number>> getProofOfBurnChartData() {
return toChartData(dataModel.getProofOfBurnByInterval());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Formatters/Converters
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected StringConverter<Number> getYAxisStringConverter() {
return new StringConverter<>() {
@Override
public String toString(Number value) {
return priceFormat.format(Double.parseDouble(bsqFormatter.formatBSQSatoshis(value.longValue()))) + " BSQ";
}
@Override
public Number fromString(String string) {
return null;
}
};
}
@Override
protected String getTooltipValueConverter(Number value) {
return bsqFormatter.formatBSQSatoshisWithCode(value.longValue());
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoChartDataModel delegates
///////////////////////////////////////////////////////////////////////////////////////////
long getCompensationAmount() {
return dataModel.getCompensationAmount();
}
long getReimbursementAmount() {
return dataModel.getReimbursementAmount();
}
long getBsqTradeFeeAmount() {
return dataModel.getBsqTradeFeeAmount();
}
long getProofOfBurnAmount() {
return dataModel.getProofOfBurnAmount();
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.economy.transactions.BSQTransactionsView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="50"/>
<ColumnConstraints percentWidth="50"/>
</columnConstraints>
</GridPane>

View file

@ -1,155 +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.economy.transactions;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.IssuanceType;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.common.util.Tuple3;
import javax.inject.Inject;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelHyperlinkWithIcon;
import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField;
@FxmlView
public class BSQTransactionsView extends ActivatableView<GridPane, Void> implements DaoStateListener {
private final DaoFacade daoFacade;
private final Preferences preferences;
private int gridRow = 0;
private TextField allTxTextField, burntFeeTxsTextField,
utxoTextField, compensationIssuanceTxTextField,
reimbursementIssuanceTxTextField, invalidTxsTextField, irregularTxsTextField;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BSQTransactionsView(DaoFacade daoFacade,
Preferences preferences) {
this.daoFacade = daoFacade;
this.preferences = preferences;
}
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 2, Res.get("dao.factsAndFigures.transactions.genesis"));
String genTxHeight = String.valueOf(daoFacade.getGenesisBlockHeight());
String genesisTxId = daoFacade.getGenesisTxId();
String url = preferences.getBsqBlockChainExplorer().txUrl + genesisTxId;
GridPane.setColumnSpan(addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.factsAndFigures.transactions.genesisBlockHeight"),
genTxHeight, Layout.FIRST_ROW_DISTANCE).third, 2);
// TODO use addTopLabelTxIdTextField
Tuple3<Label, HyperlinkWithIcon, VBox> tuple = addTopLabelHyperlinkWithIcon(root, ++gridRow,
Res.get("dao.factsAndFigures.transactions.genesisTxId"), genesisTxId, url, 0);
HyperlinkWithIcon hyperlinkWithIcon = tuple.second;
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", genesisTxId)));
GridPane.setColumnSpan(tuple.third, 2);
int startRow = ++gridRow;
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 3, Res.get("dao.factsAndFigures.transactions.txDetails"), Layout.GROUP_DISTANCE);
titledGroupBg.getStyleClass().add("last");
allTxTextField = addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.factsAndFigures.transactions.allTx"),
genTxHeight, Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
utxoTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.factsAndFigures.transactions.utxo")).second;
compensationIssuanceTxTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.transactions.compensationIssuanceTx")).second;
reimbursementIssuanceTxTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.transactions.reimbursementIssuanceTx")).second;
int columnIndex = 1;
gridRow = startRow;
titledGroupBg = addTitledGroupBg(root, startRow, columnIndex, 3, "", Layout.GROUP_DISTANCE);
titledGroupBg.getStyleClass().add("last");
burntFeeTxsTextField = addTopLabelReadOnlyTextField(root, gridRow, columnIndex,
Res.get("dao.factsAndFigures.transactions.burntTx"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
invalidTxsTextField = addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex,
Res.get("dao.factsAndFigures.transactions.invalidTx")).second;
irregularTxsTextField = addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex,
Res.get("dao.factsAndFigures.transactions.irregularTx")).second;
gridRow++;
}
@Override
protected void activate() {
daoFacade.addBsqStateListener(this);
updateWithBsqBlockChainData();
}
@Override
protected void deactivate() {
daoFacade.removeBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
updateWithBsqBlockChainData();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateWithBsqBlockChainData() {
allTxTextField.setText(String.valueOf(daoFacade.getNumTxs()));
utxoTextField.setText(String.valueOf(daoFacade.getUnspentTxOutputs().size()));
compensationIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.COMPENSATION)));
reimbursementIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.REIMBURSEMENT)));
burntFeeTxsTextField.setText(String.valueOf(daoFacade.getBurntFeeTxs().size()));
invalidTxsTextField.setText(String.valueOf(daoFacade.getInvalidTxs().size()));
irregularTxsTextField.setText(String.valueOf(daoFacade.getIrregularTxs().size()));
}
}

View file

@ -1,36 +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.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.governance.GovernanceView"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="15"/>
<ScrollPane fitToWidth="true" hbarPolicy="NEVER"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</ScrollPane>
</AnchorPane>

View file

@ -1,182 +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.governance;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.governance.dashboard.GovernanceDashboardView;
import bisq.desktop.main.dao.governance.make.MakeProposalView;
import bisq.desktop.main.dao.governance.proposals.ProposalsView;
import bisq.desktop.main.dao.governance.result.VoteResultView;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.beans.value.ChangeListener;
import java.util.Arrays;
import java.util.List;
@FxmlView
public class GovernanceView extends ActivatableView<AnchorPane, Void> implements DaoStateListener {
private final ViewLoader viewLoader;
private final Navigation navigation;
private final DaoFacade daoFacade;
private final DaoStateService daoStateService;
private MenuItem dashboard, make, open, result;
private Navigation.Listener navigationListener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
private ChangeListener<DaoPhase.Phase> phaseChangeListener;
private ToggleGroup toggleGroup;
@Inject
private GovernanceView(CachingViewLoader viewLoader, Navigation navigation, DaoFacade daoFacade,
DaoStateService daoStateService) {
this.viewLoader = viewLoader;
this.navigation = navigation;
this.daoFacade = daoFacade;
this.daoStateService = daoStateService;
}
@Override
public void initialize() {
navigationListener = (viewPath, data) -> {
if (viewPath.size() != 4 || viewPath.indexOf(GovernanceView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
phaseChangeListener = (observable, oldValue, newValue) -> {
if (newValue == DaoPhase.Phase.BLIND_VOTE)
open.setLabelText(Res.get("dao.proposal.menuItem.vote"));
else
open.setLabelText(Res.get("dao.proposal.menuItem.browse"));
};
toggleGroup = new ToggleGroup();
List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, GovernanceView.class);
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"),
GovernanceDashboardView.class, baseNavPath);
make = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.make"),
MakeProposalView.class, baseNavPath);
open = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.browse"),
ProposalsView.class, baseNavPath);
result = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.result"),
VoteResultView.class, baseNavPath);
leftVBox.getChildren().addAll(dashboard, make, open, result);
}
@Override
protected void activate() {
if (daoStateService.isParseBlockChainComplete()) {
daoFacade.phaseProperty().addListener(phaseChangeListener);
} else {
daoStateService.addDaoStateListener(this);
}
dashboard.activate();
make.activate();
open.activate();
result.activate();
navigation.addListener(navigationListener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(GovernanceView.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(GovernanceView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@SuppressWarnings("Duplicates")
@Override
protected void deactivate() {
daoFacade.phaseProperty().removeListener(phaseChangeListener);
daoStateService.removeDaoStateListener(this);
navigation.removeListener(navigationListener);
dashboard.deactivate();
make.deactivate();
open.deactivate();
result.deactivate();
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockChainComplete() {
daoFacade.phaseProperty().addListener(phaseChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof GovernanceDashboardView) toggleGroup.selectToggle(dashboard);
else if (view instanceof MakeProposalView) toggleGroup.selectToggle(make);
else if (view instanceof ProposalsView) toggleGroup.selectToggle(open);
else if (view instanceof VoteResultView) toggleGroup.selectToggle(result);
}
}

View file

@ -1,136 +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.governance;
import bisq.desktop.components.SeparatedPhaseBars;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.scene.layout.GridPane;
import javafx.geometry.Insets;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@Slf4j
public class PhasesView implements DaoStateListener {
private final DaoFacade daoFacade;
private final PeriodService periodService;
private SeparatedPhaseBars separatedPhaseBars;
private List<SeparatedPhaseBars.SeparatedPhaseBarsItem> phaseBarsItems;
@Inject
private PhasesView(DaoFacade daoFacade, PeriodService periodService) {
this.daoFacade = daoFacade;
this.periodService = periodService;
}
public int addGroup(GridPane gridPane, int gridRow) {
addTitledGroupBg(gridPane, gridRow, 1, Res.get("dao.cycle.headline"));
separatedPhaseBars = createSeparatedPhaseBars();
GridPane.setMargin(separatedPhaseBars, new Insets(Layout.FIRST_ROW_DISTANCE + 5, 0, 0, 0));
GridPane.setRowIndex(separatedPhaseBars, gridRow);
gridPane.getChildren().add(separatedPhaseBars);
return gridRow;
}
public void activate() {
daoFacade.addBsqStateListener(this);
applyData(daoFacade.getChainHeight());
}
public void deactivate() {
daoFacade.removeBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
applyData(block.getHeight());
phaseBarsItems.forEach(item -> {
DaoPhase.Phase phase = item.getPhase();
// Last block is considered for the break as we must not publish a tx there (would get confirmed in next
// block which would be a break). Only at result phase we don't have that situation ans show the last block
// as valid block in the phase.
if (periodService.isInPhaseButNotLastBlock(phase) ||
(phase == DaoPhase.Phase.RESULT && periodService.isInPhase(block.getHeight(), phase))) {
item.setActive();
} else {
item.setInActive();
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private SeparatedPhaseBars createSeparatedPhaseBars() {
phaseBarsItems = Arrays.asList(
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.PROPOSAL, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.BREAK1, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.BLIND_VOTE, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.BREAK2, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.VOTE_REVEAL, true),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.BREAK3, false),
new SeparatedPhaseBars.SeparatedPhaseBarsItem(DaoPhase.Phase.RESULT, false));
return new SeparatedPhaseBars(phaseBarsItems);
}
private void applyData(int height) {
if (height > 0) {
phaseBarsItems.forEach(item -> {
int firstBlock = daoFacade.getFirstBlockOfPhaseForDisplay(height, item.getPhase());
int lastBlock = daoFacade.getLastBlockOfPhaseForDisplay(height, item.getPhase());
int duration = daoFacade.getDurationForPhaseForDisplay(item.getPhase());
item.setPeriodRange(firstBlock, lastBlock, duration);
double progress = 0;
if (height >= firstBlock && height <= lastBlock) {
progress = (double) (height - firstBlock + 1) / (double) duration;
} else if (height < firstBlock) {
progress = 0;
} else if (height > lastBlock) {
progress = 1;
}
item.getProgressProperty().set(progress);
});
separatedPhaseBars.updateWidth();
}
}
}

View file

@ -1,711 +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.governance;
import bisq.desktop.Navigation;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.bonding.BondingView;
import bisq.desktop.main.dao.bonding.bonds.BondsView;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.governance.proposal.param.ChangeParamInputValidator;
import bisq.core.dao.governance.proposal.param.ChangeParamValidator;
import bisq.core.dao.state.model.blockchain.BaseTx;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.ChangeParamProposal;
import bisq.core.dao.state.model.governance.CompensationProposal;
import bisq.core.dao.state.model.governance.ConfiscateBondProposal;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.GenericProposal;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.ProposalVoteResult;
import bisq.core.dao.state.model.governance.ReimbursementProposal;
import bisq.core.dao.state.model.governance.RemoveAssetProposal;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.dao.state.model.governance.Vote;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.validation.InputValidator;
import bisq.core.util.validation.RegexValidator;
import bisq.asset.Asset;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.util.StringConverter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.*;
import static com.google.common.base.Preconditions.checkNotNull;
@SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"})
@Slf4j
public class ProposalDisplay {
private final GridPane gridPane;
private final BsqFormatter bsqFormatter;
private final DaoFacade daoFacade;
// Nullable because if we are in result view mode (readonly) we don't need to set the input validator)
@Nullable
private final ChangeParamValidator changeParamValidator;
private final Navigation navigation;
private final Preferences preferences;
@Nullable
private TextField proposalFeeTextField, comboBoxValueTextField, requiredBondForRoleTextField;
private TextField proposalTypeTextField, myVoteTextField, voteResultTextField;
public InputTextField nameTextField;
public InputTextField linkInputTextField;
@Nullable
public InputTextField requestedBsqTextField, paramValueTextField;
@Nullable
public ComboBox<Param> paramComboBox;
@Nullable
public ComboBox<Bond> confiscateBondComboBox;
@Nullable
public ComboBox<BondedRoleType> bondedRoleTypeComboBox;
@Nullable
public ComboBox<Asset> assetComboBox;
@Getter
private int gridRow;
private HyperlinkWithIcon linkHyperlinkWithIcon;
private HyperlinkWithIcon txHyperlinkWithIcon;
private int gridRowStartIndex;
private final List<Runnable> inputChangedListeners = new ArrayList<>();
@Getter
private List<TextInputControl> inputControls = new ArrayList<>();
@Getter
private List<ComboBox<?>> comboBoxes = new ArrayList<>();
private final ChangeListener<Boolean> focusOutListener;
private final ChangeListener<Object> inputListener;
private ChangeListener<Param> paramChangeListener;
private ChangeListener<BondedRoleType> requiredBondForRoleListener;
private TitledGroupBg myVoteTitledGroup;
private VBox linkWithIconContainer, comboBoxValueContainer, myVoteBox, voteResultBox;
private int votingBoxRowSpan;
private Optional<Runnable> navigateHandlerOptional = Optional.empty();
public ProposalDisplay(GridPane gridPane,
BsqFormatter bsqFormatter,
DaoFacade daoFacade,
@Nullable ChangeParamValidator changeParamValidator,
Navigation navigation,
@Nullable Preferences preferences) {
this.gridPane = gridPane;
this.bsqFormatter = bsqFormatter;
this.daoFacade = daoFacade;
this.changeParamValidator = changeParamValidator;
this.navigation = navigation;
this.preferences = preferences;
// focusOutListener = observable -> inputChangedListeners.forEach(Runnable::run);
focusOutListener = (observable, oldValue, newValue) -> {
if (oldValue && !newValue)
inputChangedListeners.forEach(Runnable::run);
};
inputListener = (observable, oldValue, newValue) -> inputChangedListeners.forEach(Runnable::run);
}
public void addInputChangedListener(Runnable listener) {
inputChangedListeners.add(listener);
}
public void removeInputChangedListener(Runnable listener) {
inputChangedListeners.remove(listener);
}
public void createAllFields(String title, int gridRowStartIndex, double top, ProposalType proposalType,
boolean isMakeProposalScreen) {
createAllFields(title, gridRowStartIndex, top, proposalType, isMakeProposalScreen, null);
}
public void createAllFields(String title, int gridRowStartIndex, double top, ProposalType proposalType,
boolean isMakeProposalScreen, String titledGroupStyle) {
removeAllFields();
this.gridRowStartIndex = gridRowStartIndex;
this.gridRow = gridRowStartIndex;
int titledGroupBgRowSpan = 5;
switch (proposalType) {
case COMPENSATION_REQUEST:
case REIMBURSEMENT_REQUEST:
case CONFISCATE_BOND:
case REMOVE_ASSET:
break;
case CHANGE_PARAM:
case BONDED_ROLE:
titledGroupBgRowSpan = 6;
break;
case GENERIC:
titledGroupBgRowSpan = 4;
break;
}
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, gridRow, titledGroupBgRowSpan, title, top);
if (titledGroupStyle != null) titledGroupBg.getStyleClass().add(titledGroupStyle);
double proposalTypeTop;
if (top == Layout.GROUP_DISTANCE_WITHOUT_SEPARATOR) {
proposalTypeTop = Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE_WITHOUT_SEPARATOR;
} else if (top == Layout.GROUP_DISTANCE) {
proposalTypeTop = Layout.FIRST_ROW_AND_GROUP_DISTANCE;
} else if (top == 0) {
proposalTypeTop = Layout.FIRST_ROW_DISTANCE;
} else {
proposalTypeTop = Layout.FIRST_ROW_DISTANCE + top;
}
proposalTypeTextField = addTopLabelTextField(gridPane, gridRow,
Res.get("dao.proposal.display.type"), proposalType.getDisplayName(), proposalTypeTop).second;
nameTextField = addInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.name"));
if (isMakeProposalScreen)
nameTextField.setValidator(new InputValidator());
inputControls.add(nameTextField);
linkInputTextField = addInputTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.link"));
linkInputTextField.setPromptText(Res.get("dao.proposal.display.link.prompt"));
if (isMakeProposalScreen) {
RegexValidator validator = new RegexValidator();
if (proposalType == ProposalType.COMPENSATION_REQUEST) {
validator.setPattern("https://bisq.network/dao-compensation/\\d+");
linkInputTextField.setText("https://bisq.network/dao-compensation/#");
} else if (proposalType == ProposalType.REIMBURSEMENT_REQUEST) {
validator.setPattern("https://bisq.network/dao-reimbursement/\\d+");
linkInputTextField.setText("https://bisq.network/dao-reimbursement/#");
} else {
validator.setPattern("https://bisq.network/dao-proposals/\\d+");
linkInputTextField.setText("https://bisq.network/dao-proposals/#");
}
linkInputTextField.setValidator(validator);
}
inputControls.add(linkInputTextField);
Tuple3<Label, HyperlinkWithIcon, VBox> tuple = addTopLabelHyperlinkWithIcon(gridPane, gridRow,
Res.get("dao.proposal.display.link"), "", "", 0);
linkHyperlinkWithIcon = tuple.second;
linkWithIconContainer = tuple.third;
// TODO HyperlinkWithIcon does not scale automatically (button base, -> make anchorpane as base)
linkHyperlinkWithIcon.prefWidthProperty().bind(nameTextField.widthProperty());
linkWithIconContainer.setVisible(false);
linkWithIconContainer.setManaged(false);
if (!isMakeProposalScreen) {
Tuple3<Label, HyperlinkWithIcon, VBox> uidTuple = addTopLabelHyperlinkWithIcon(gridPane, ++gridRow,
Res.get("dao.proposal.display.txId"), "", "", 0);
txHyperlinkWithIcon = uidTuple.second;
// TODO HyperlinkWithIcon does not scale automatically (button base, -> make anchorPane as base)
txHyperlinkWithIcon.prefWidthProperty().bind(nameTextField.widthProperty());
}
int comboBoxValueTextFieldIndex = -1;
switch (proposalType) {
case COMPENSATION_REQUEST:
case REIMBURSEMENT_REQUEST:
requestedBsqTextField = addInputTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.requestedBsq"));
checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
inputControls.add(requestedBsqTextField);
if (isMakeProposalScreen) {
BsqValidator bsqValidator = new BsqValidator(bsqFormatter);
if (proposalType == ProposalType.COMPENSATION_REQUEST) {
bsqValidator.setMinValue(daoFacade.getMinCompensationRequestAmount());
bsqValidator.setMaxValue(daoFacade.getMaxCompensationRequestAmount());
} else if (proposalType == ProposalType.REIMBURSEMENT_REQUEST) {
bsqValidator.setMinValue(daoFacade.getMinReimbursementRequestAmount());
bsqValidator.setMaxValue(daoFacade.getMaxReimbursementRequestAmount());
}
requestedBsqTextField.setValidator(bsqValidator);
}
break;
case CHANGE_PARAM:
checkNotNull(gridPane, "gridPane must not be null");
paramComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
Res.get("dao.proposal.display.paramComboBox.label"));
comboBoxValueTextFieldIndex = gridRow;
checkNotNull(paramComboBox, "paramComboBox must not be null");
List<Param> list = Arrays.stream(Param.values())
.filter(e -> e != Param.UNDEFINED && e != Param.PHASE_UNDEFINED)
.collect(Collectors.toList());
paramComboBox.setItems(FXCollections.observableArrayList(list));
paramComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Param param) {
return param != null ? param.getDisplayString() : "";
}
@Override
public Param fromString(String string) {
return null;
}
});
comboBoxes.add(paramComboBox);
paramValueTextField = addInputTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.paramValue"));
inputControls.add(paramValueTextField);
paramChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
paramValueTextField.clear();
String currentValue = bsqFormatter.formatParamValue(newValue, daoFacade.getParamValue(newValue));
paramValueTextField.setPromptText(Res.get("dao.param.currentValue", currentValue));
if (changeParamValidator != null && isMakeProposalScreen) {
ChangeParamInputValidator validator = new ChangeParamInputValidator(newValue, changeParamValidator);
paramValueTextField.setValidator(validator);
}
}
};
paramComboBox.getSelectionModel().selectedItemProperty().addListener(paramChangeListener);
break;
case BONDED_ROLE:
bondedRoleTypeComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
Res.get("dao.proposal.display.bondedRoleComboBox.label"));
comboBoxValueTextFieldIndex = gridRow;
checkNotNull(bondedRoleTypeComboBox, "bondedRoleTypeComboBox must not be null");
List<BondedRoleType> bondedRoleTypes = Arrays.stream(BondedRoleType.values())
.filter(e -> e != BondedRoleType.UNDEFINED)
.collect(Collectors.toList());
bondedRoleTypeComboBox.setItems(FXCollections.observableArrayList(bondedRoleTypes));
bondedRoleTypeComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(BondedRoleType bondedRoleType) {
return bondedRoleType != null ? bondedRoleType.getDisplayString() : "";
}
@Override
public BondedRoleType fromString(String string) {
return null;
}
});
comboBoxes.add(bondedRoleTypeComboBox);
requiredBondForRoleTextField = addTopLabelReadOnlyTextField(gridPane, ++gridRow,
Res.get("dao.proposal.display.requiredBondForRole.label")).second;
requiredBondForRoleListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
requiredBondForRoleTextField.setText(bsqFormatter.formatCoinWithCode(Coin.valueOf(daoFacade.getRequiredBond(newValue))));
}
};
bondedRoleTypeComboBox.getSelectionModel().selectedItemProperty().addListener(requiredBondForRoleListener);
break;
case CONFISCATE_BOND:
confiscateBondComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
Res.get("dao.proposal.display.confiscateBondComboBox.label"));
comboBoxValueTextFieldIndex = gridRow;
checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null");
confiscateBondComboBox.setItems(FXCollections.observableArrayList(daoFacade.getAllActiveBonds()));
confiscateBondComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Bond bond) {
String details = " (" + Res.get("dao.bond.table.column.lockupTxId") + ": " + bond.getLockupTxId() + ")";
if (bond instanceof BondedRole) {
return bond.getBondedAsset().getDisplayString() + details;
} else {
return Res.get("dao.bond.bondedReputation") + details;
}
}
@Override
public Bond fromString(String string) {
return null;
}
});
comboBoxes.add(confiscateBondComboBox);
break;
case GENERIC:
break;
case REMOVE_ASSET:
assetComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
Res.get("dao.proposal.display.assetComboBox.label"));
comboBoxValueTextFieldIndex = gridRow;
checkNotNull(assetComboBox, "assetComboBox must not be null");
List<Asset> assetList = CurrencyUtil.getSortedAssetStream()
.filter(e -> !e.getTickerSymbol().equals("BSQ"))
.collect(Collectors.toList());
assetComboBox.setItems(FXCollections.observableArrayList(assetList));
assetComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Asset asset) {
return asset != null ? CurrencyUtil.getNameAndCode(asset.getTickerSymbol()) : "";
}
@Override
public Asset fromString(String string) {
return null;
}
});
comboBoxes.add(assetComboBox);
break;
}
if (comboBoxValueTextFieldIndex > -1) {
Tuple3<Label, TextField, VBox> tuple3 = addTopLabelReadOnlyTextField(gridPane, comboBoxValueTextFieldIndex,
Res.get("dao.proposal.display.option"));
comboBoxValueTextField = tuple3.second;
comboBoxValueContainer = tuple3.third;
comboBoxValueContainer.setVisible(false);
comboBoxValueContainer.setManaged(false);
}
if (isMakeProposalScreen) {
proposalFeeTextField = addTopLabelTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.proposalFee")).second;
proposalFeeTextField.setText(bsqFormatter.formatCoinWithCode(daoFacade.getProposalFee(daoFacade.getChainHeight())));
}
votingBoxRowSpan = 4;
myVoteTitledGroup = addTitledGroupBg(gridPane, ++gridRow, 4, Res.get("dao.proposal.myVote.title"), Layout.COMPACT_FIRST_ROW_DISTANCE);
Tuple3<Label, TextField, VBox> tuple3 = addTopLabelTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.myVote"), Layout.COMPACT_FIRST_ROW_DISTANCE);
myVoteBox = tuple3.third;
setMyVoteBoxVisibility(false);
myVoteTextField = tuple3.second;
tuple3 = addTopLabelReadOnlyTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.voteResult"));
voteResultBox = tuple3.third;
voteResultBox.setVisible(false);
voteResultBox.setManaged(false);
voteResultTextField = tuple3.second;
addListeners();
}
public void applyBallot(@Nullable Ballot ballot) {
String myVote = Res.get("dao.proposal.display.myVote.ignored");
boolean isNotNull = ballot != null;
Vote vote = isNotNull ? ballot.getVote() : null;
if (vote != null) {
myVote = vote.isAccepted() ? Res.get("dao.proposal.display.myVote.accepted") :
Res.get("dao.proposal.display.myVote.rejected");
}
myVoteTextField.setText(myVote);
setMyVoteBoxVisibility(isNotNull);
}
public void applyEvaluatedProposal(@Nullable EvaluatedProposal evaluatedProposal) {
boolean isEvaluatedProposalNotNull = evaluatedProposal != null;
if (isEvaluatedProposalNotNull) {
String result = evaluatedProposal.isAccepted() ? Res.get("dao.proposal.voteResult.success") :
Res.get("dao.proposal.voteResult.failed");
ProposalVoteResult proposalVoteResult = evaluatedProposal.getProposalVoteResult();
String threshold = (proposalVoteResult.getThreshold() / 100D) + "%";
String requiredThreshold = (daoFacade.getRequiredThreshold(evaluatedProposal.getProposal()) * 100D) + "%";
String quorum = bsqFormatter.formatCoinWithCode(Coin.valueOf(proposalVoteResult.getQuorum()));
String requiredQuorum = bsqFormatter.formatCoinWithCode(daoFacade.getRequiredQuorum(evaluatedProposal.getProposal()));
String summary = Res.get("dao.proposal.voteResult.summary", result,
threshold, requiredThreshold, quorum, requiredQuorum);
voteResultTextField.setText(summary);
}
voteResultBox.setVisible(isEvaluatedProposalNotNull);
voteResultBox.setManaged(isEvaluatedProposalNotNull);
}
public void applyBallotAndVoteWeight(@Nullable Ballot ballot, long merit, long stake) {
applyBallotAndVoteWeight(ballot, merit, stake, true);
}
public void applyBallotAndVoteWeight(@Nullable Ballot ballot, long merit, long stake, boolean ballotIncluded) {
boolean ballotIsNotNull = ballot != null;
boolean hasVoted = stake > 0;
if (hasVoted) {
String myVote = Res.get("dao.proposal.display.myVote.ignored");
Vote vote = ballotIsNotNull ? ballot.getVote() : null;
if (vote != null) {
myVote = vote.isAccepted() ? Res.get("dao.proposal.display.myVote.accepted") :
Res.get("dao.proposal.display.myVote.rejected");
}
String voteIncluded = ballotIncluded ? "" : " - " + Res.get("dao.proposal.display.myVote.unCounted");
String meritString = bsqFormatter.formatCoinWithCode(Coin.valueOf(merit));
String stakeString = bsqFormatter.formatCoinWithCode(Coin.valueOf(stake));
String weight = bsqFormatter.formatCoinWithCode(Coin.valueOf(merit + stake));
String myVoteSummary = Res.get("dao.proposal.myVote.summary", myVote,
weight, meritString, stakeString, voteIncluded);
myVoteTextField.setText(myVoteSummary);
GridPane.setRowSpan(myVoteTitledGroup, votingBoxRowSpan - 1);
}
boolean show = ballotIsNotNull && hasVoted;
setMyVoteBoxVisibility(show);
}
public void setIsVoteIncludedInResult(boolean isVoteIncludedInResult) {
if (!isVoteIncludedInResult && myVoteTextField != null && !myVoteTextField.getText().isEmpty()) {
String text = myVoteTextField.getText();
myVoteTextField.setText(Res.get("dao.proposal.myVote.invalid") + " - " + text);
myVoteTextField.getStyleClass().add("error-text");
}
}
public void applyProposalPayload(Proposal proposal) {
proposalTypeTextField.setText(proposal.getType().getDisplayName());
nameTextField.setText(proposal.getName());
linkInputTextField.setVisible(false);
linkInputTextField.setManaged(false);
if (linkWithIconContainer != null) {
linkWithIconContainer.setVisible(true);
linkWithIconContainer.setManaged(true);
linkHyperlinkWithIcon.setText(proposal.getLink());
linkHyperlinkWithIcon.setOnAction(e -> GUIUtil.openWebPage(proposal.getLink()));
}
if (txHyperlinkWithIcon != null) {
txHyperlinkWithIcon.setText(proposal.getTxId());
txHyperlinkWithIcon.setOnAction(e ->
GUIUtil.openTxInBsqBlockExplorer(proposal.getTxId(), preferences));
}
if (proposal instanceof CompensationProposal) {
CompensationProposal compensationProposal = (CompensationProposal) proposal;
checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(compensationProposal.getRequestedBsq()));
} else if (proposal instanceof ReimbursementProposal) {
ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(reimbursementProposal.getRequestedBsq()));
} else if (proposal instanceof ChangeParamProposal) {
ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal;
checkNotNull(paramComboBox, "paramComboBox must not be null");
paramComboBox.getSelectionModel().select(changeParamProposal.getParam());
comboBoxValueTextField.setText(paramComboBox.getConverter().toString(changeParamProposal.getParam()));
checkNotNull(paramValueTextField, "paramValueTextField must not be null");
paramValueTextField.setText(bsqFormatter.formatParamValue(changeParamProposal.getParam(), changeParamProposal.getParamValue()));
String currentValue = bsqFormatter.formatParamValue(changeParamProposal.getParam(),
daoFacade.getParamValue(changeParamProposal.getParam()));
int height = daoFacade.getTx(changeParamProposal.getTxId())
.map(BaseTx::getBlockHeight)
.orElse(daoFacade.getGenesisBlockHeight());
String valueAtProposal = bsqFormatter.formatParamValue(changeParamProposal.getParam(),
daoFacade.getParamValue(changeParamProposal.getParam(), height));
paramValueTextField.setPromptText(Res.get("dao.param.currentAndPastValue", currentValue, valueAtProposal));
} else if (proposal instanceof RoleProposal) {
RoleProposal roleProposal = (RoleProposal) proposal;
checkNotNull(bondedRoleTypeComboBox, "bondedRoleComboBox must not be null");
Role role = roleProposal.getRole();
bondedRoleTypeComboBox.getSelectionModel().select(role.getBondedRoleType());
comboBoxValueTextField.setText(bondedRoleTypeComboBox.getConverter().toString(role.getBondedRoleType()));
requiredBondForRoleTextField.setText(bsqFormatter.formatCoin(Coin.valueOf(daoFacade.getRequiredBond(roleProposal))));
// TODO maybe show also unlock time?
} else if (proposal instanceof ConfiscateBondProposal) {
ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal;
checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null");
daoFacade.getBondByLockupTxId(confiscateBondProposal.getLockupTxId())
.ifPresent(bond -> {
confiscateBondComboBox.getSelectionModel().select(bond);
comboBoxValueTextField.setText(confiscateBondComboBox.getConverter().toString(bond));
comboBoxValueTextField.setOnMouseClicked(e -> {
navigateHandlerOptional.ifPresent(Runnable::run);
navigation.navigateToWithData(bond, MainView.class, DaoView.class, BondingView.class,
BondsView.class);
});
comboBoxValueTextField.getStyleClass().addAll("hyperlink", "force-underline", "show-hand");
});
} else if (proposal instanceof GenericProposal) {
// do nothing
} else if (proposal instanceof RemoveAssetProposal) {
RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal;
checkNotNull(assetComboBox, "assetComboBox must not be null");
CurrencyUtil.findAsset(removeAssetProposal.getTickerSymbol(), BaseCurrencyNetwork.XMR_MAINNET)
.ifPresent(asset -> {
assetComboBox.getSelectionModel().select(asset);
comboBoxValueTextField.setText(assetComboBox.getConverter().toString(asset));
});
}
int chainHeight = daoFacade.getTx(proposal.getTxId()).map(Tx::getBlockHeight).orElse(daoFacade.getChainHeight());
if (proposalFeeTextField != null)
proposalFeeTextField.setText(bsqFormatter.formatCoinWithCode(daoFacade.getProposalFee(chainHeight)));
}
private void addListeners() {
inputControls.stream()
.filter(Objects::nonNull).forEach(inputControl -> {
inputControl.textProperty().addListener(inputListener);
inputControl.focusedProperty().addListener(focusOutListener);
});
comboBoxes.stream()
.filter(Objects::nonNull)
.forEach(comboBox -> comboBox.getSelectionModel().selectedItemProperty().addListener(inputListener));
}
public void removeListeners() {
inputControls.stream()
.filter(Objects::nonNull).forEach(inputControl -> {
inputControl.textProperty().removeListener(inputListener);
inputControl.focusedProperty().removeListener(focusOutListener);
});
comboBoxes.stream()
.filter(Objects::nonNull)
.forEach(comboBox -> comboBox.getSelectionModel().selectedItemProperty().removeListener(inputListener));
if (paramComboBox != null && paramChangeListener != null)
paramComboBox.getSelectionModel().selectedItemProperty().removeListener(paramChangeListener);
if (bondedRoleTypeComboBox != null && requiredBondForRoleListener != null)
bondedRoleTypeComboBox.getSelectionModel().selectedItemProperty().removeListener(requiredBondForRoleListener);
}
public void clearForm() {
inputControls.stream().filter(Objects::nonNull).forEach(TextInputControl::clear);
if (linkHyperlinkWithIcon != null)
linkHyperlinkWithIcon.clear();
comboBoxes.stream().filter(Objects::nonNull).forEach(comboBox -> comboBox.getSelectionModel().clearSelection());
}
public void setEditable(boolean isEditable) {
inputControls.stream().filter(Objects::nonNull).forEach(e -> e.setEditable(isEditable));
comboBoxes.stream().filter(Objects::nonNull).forEach(comboBox -> {
comboBox.setVisible(isEditable);
comboBox.setManaged(isEditable);
if (comboBoxValueContainer != null) {
comboBoxValueContainer.setVisible(!isEditable);
comboBoxValueContainer.setManaged(!isEditable);
}
});
linkInputTextField.setVisible(true);
linkInputTextField.setManaged(true);
if (linkWithIconContainer != null) {
linkWithIconContainer.setVisible(false);
linkWithIconContainer.setManaged(false);
linkHyperlinkWithIcon.setOnAction(null);
}
}
public void removeAllFields() {
if (gridRow > 0) {
clearForm();
GUIUtil.removeChildrenFromGridPaneRows(gridPane, gridRowStartIndex, gridRow);
gridRow = gridRowStartIndex;
}
if (linkHyperlinkWithIcon != null)
linkHyperlinkWithIcon.prefWidthProperty().unbind();
inputControls.clear();
comboBoxes.clear();
}
public void onNavigate(Runnable navigateHandler) {
navigateHandlerOptional = Optional.of(navigateHandler);
}
public int incrementAndGetGridRow() {
return ++gridRow;
}
@SuppressWarnings("Duplicates")
public ScrollPane getView() {
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
AnchorPane anchorPane = new AnchorPane();
scrollPane.setContent(anchorPane);
gridPane.setHgap(5);
gridPane.setVgap(5);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setPercentWidth(100);
gridPane.getColumnConstraints().addAll(columnConstraints1);
AnchorPane.setBottomAnchor(gridPane, 10d);
AnchorPane.setRightAnchor(gridPane, 10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
AnchorPane.setTopAnchor(gridPane, 10d);
anchorPane.getChildren().add(gridPane);
return scrollPane;
}
private void setMyVoteBoxVisibility(boolean visibility) {
myVoteTitledGroup.setVisible(visibility);
myVoteTitledGroup.setManaged(visibility);
myVoteBox.setVisible(visibility);
myVoteBox.setManaged(visibility);
}
}

View file

@ -1,31 +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.governance.dashboard.GovernanceDashboardView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,130 +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.governance.dashboard;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.governance.PhasesView;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.presentation.DaoUtil;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField;
// We use here ChainHeightListener because we are interested in period changes not in the result of a completed
// block. The event from the ChainHeightListener is sent before parsing starts.
// The event from the ChainHeightListener would notify after parsing a new block.
@FxmlView
public class GovernanceDashboardView extends ActivatableView<GridPane, Void> implements DaoStateListener {
private final DaoFacade daoFacade;
private final PeriodService periodService;
private final PhasesView phasesView;
private int gridRow = 0;
private TextField currentPhaseTextField, currentBlockHeightTextField, proposalTextField, blindVoteTextField, voteRevealTextField, voteResultTextField;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public GovernanceDashboardView(DaoFacade daoFacade, PeriodService periodService, PhasesView phasesView) {
this.daoFacade = daoFacade;
this.periodService = periodService;
this.phasesView = phasesView;
}
@Override
public void initialize() {
gridRow = phasesView.addGroup(root, gridRow);
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 6, Res.get("dao.cycle.overview.headline"), Layout.GROUP_DISTANCE);
titledGroupBg.getStyleClass().add("last");
currentBlockHeightTextField = addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.cycle.currentBlockHeight"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
currentPhaseTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.cycle.currentPhase")).second;
proposalTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.cycle.proposal")).second;
blindVoteTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.cycle.blindVote")).second;
voteRevealTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.cycle.voteReveal")).second;
voteResultTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.cycle.voteResult")).second;
}
@Override
protected void activate() {
super.activate();
phasesView.activate();
daoFacade.addBsqStateListener(this);
applyData(daoFacade.getChainHeight());
}
@Override
protected void deactivate() {
super.deactivate();
phasesView.deactivate();
daoFacade.removeBsqStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
applyData(block.getHeight());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void applyData(int height) {
currentBlockHeightTextField.setText(String.valueOf(daoFacade.getChainHeight()));
DaoPhase.Phase phase = daoFacade.phaseProperty().get();
// If we are in last block of proposal, blindVote or voteReveal phase we show following break.
if (!periodService.isInPhaseButNotLastBlock(phase) &&
(phase == DaoPhase.Phase.PROPOSAL || phase == DaoPhase.Phase.BLIND_VOTE || phase == DaoPhase.Phase.VOTE_REVEAL)) {
phase = periodService.getPhaseForHeight(height + 1);
}
currentPhaseTextField.setText(Res.get("dao.phase." + phase.name()));
proposalTextField.setText(DaoUtil.getPhaseDuration(height, DaoPhase.Phase.PROPOSAL, daoFacade));
blindVoteTextField.setText(DaoUtil.getPhaseDuration(height, DaoPhase.Phase.BLIND_VOTE, daoFacade));
voteRevealTextField.setText(DaoUtil.getPhaseDuration(height, DaoPhase.Phase.VOTE_REVEAL, daoFacade));
voteResultTextField.setText(DaoUtil.getPhaseDuration(height, DaoPhase.Phase.RESULT, daoFacade));
}
}

View file

@ -1,31 +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.governance.make.MakeProposalView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,525 +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.governance.make;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.governance.PhasesView;
import bisq.desktop.main.dao.governance.ProposalDisplay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.exceptions.InsufficientBsqException;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.governance.proposal.ProposalValidationException;
import bisq.core.dao.governance.proposal.ProposalWithTransaction;
import bisq.core.dao.governance.proposal.TxException;
import bisq.core.dao.governance.proposal.param.ChangeParamValidator;
import bisq.core.dao.governance.voteresult.VoteResultException;
import bisq.core.dao.presentation.DaoUtil;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.locale.Res;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.asset.Asset;
import bisq.network.p2p.P2PService;
import bisq.common.app.DevEnv;
import bisq.common.util.Tuple3;
import bisq.common.util.Tuple4;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.util.StringConverter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup;
import static bisq.desktop.util.FormBuilder.addComboBox;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField;
import static com.google.common.base.Preconditions.checkNotNull;
@FxmlView
public class MakeProposalView extends ActivatableView<GridPane, Void> implements DaoStateListener {
private final DaoFacade daoFacade;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final PhasesView phasesView;
private final ChangeParamValidator changeParamValidator;
private final CoinFormatter btcFormatter;
private final BsqFormatter bsqFormatter;
private final Navigation navigation;
private final BsqWalletService bsqWalletService;
@Nullable
private ProposalDisplay proposalDisplay;
private Button makeProposalButton;
private ComboBox<ProposalType> proposalTypeComboBox;
private ChangeListener<ProposalType> proposalTypeChangeListener;
private TextField nextProposalTextField;
private TitledGroupBg proposalTitledGroup;
private VBox nextProposalBox;
private BusyAnimation busyAnimation;
private Label busyLabel;
private final BooleanProperty isProposalPhase = new SimpleBooleanProperty(false);
private final StringProperty proposalGroupTitle = new SimpleStringProperty(Res.get("dao.proposal.create.phase.inactive"));
@Nullable
private ProposalType selectedProposalType;
private int gridRow;
private int alwaysVisibleGridRowIndex;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private MakeProposalView(DaoFacade daoFacade,
WalletsSetup walletsSetup,
P2PService p2PService,
BsqWalletService bsqWalletService,
PhasesView phasesView,
ChangeParamValidator changeParamValidator,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
BsqFormatter bsqFormatter,
Navigation navigation) {
this.daoFacade = daoFacade;
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.bsqWalletService = bsqWalletService;
this.phasesView = phasesView;
this.changeParamValidator = changeParamValidator;
this.btcFormatter = btcFormatter;
this.bsqFormatter = bsqFormatter;
this.navigation = navigation;
}
@Override
public void initialize() {
gridRow = phasesView.addGroup(root, gridRow);
proposalTitledGroup = addTitledGroupBg(root, ++gridRow, 2, proposalGroupTitle.get(), Layout.GROUP_DISTANCE);
proposalTitledGroup.getStyleClass().add("last");
final Tuple3<Label, TextField, VBox> nextProposalPhaseTuple = addTopLabelReadOnlyTextField(root, gridRow,
Res.get("dao.cycle.proposal.next"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE);
nextProposalBox = nextProposalPhaseTuple.third;
nextProposalTextField = nextProposalPhaseTuple.second;
proposalTypeComboBox = addComboBox(root, gridRow,
Res.get("dao.proposal.create.proposalType"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
proposalTypeComboBox.setMaxWidth(300);
proposalTypeComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(ProposalType proposalType) {
return proposalType.getDisplayName();
}
@Override
public ProposalType fromString(String string) {
return null;
}
});
proposalTypeChangeListener = (observable, oldValue, newValue) -> {
selectedProposalType = newValue;
removeProposalDisplay();
addProposalDisplay();
};
alwaysVisibleGridRowIndex = gridRow + 1;
List<ProposalType> proposalTypes = Arrays.stream(ProposalType.values())
.filter(e -> e != ProposalType.UNDEFINED)
.collect(Collectors.toList());
proposalTypeComboBox.setItems(FXCollections.observableArrayList(proposalTypes));
}
@Override
protected void activate() {
addBindings();
phasesView.activate();
daoFacade.addBsqStateListener(this);
proposalTypeComboBox.getSelectionModel().selectedItemProperty().addListener(proposalTypeChangeListener);
if (makeProposalButton != null)
setMakeProposalButtonHandler();
Optional<Block> blockAtChainHeight = daoFacade.getBlockAtChainHeight();
blockAtChainHeight.ifPresent(this::onParseBlockCompleteAfterBatchProcessing);
}
@Override
protected void deactivate() {
removeBindings();
phasesView.deactivate();
daoFacade.removeBsqStateListener(this);
proposalTypeComboBox.getSelectionModel().selectedItemProperty().removeListener(proposalTypeChangeListener);
if (makeProposalButton != null)
makeProposalButton.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Bindings, Listeners
///////////////////////////////////////////////////////////////////////////////////////////
private void addBindings() {
proposalTypeComboBox.managedProperty().bind(isProposalPhase);
proposalTypeComboBox.visibleProperty().bind(isProposalPhase);
nextProposalBox.managedProperty().bind(isProposalPhase.not());
nextProposalBox.visibleProperty().bind(isProposalPhase.not());
proposalTitledGroup.textProperty().bind(proposalGroupTitle);
}
private void removeBindings() {
proposalTypeComboBox.managedProperty().unbind();
proposalTypeComboBox.visibleProperty().unbind();
nextProposalBox.managedProperty().unbind();
nextProposalBox.visibleProperty().unbind();
proposalTitledGroup.textProperty().unbind();
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
isProposalPhase.set(daoFacade.isInPhaseButNotLastBlock(DaoPhase.Phase.PROPOSAL));
if (isProposalPhase.get()) {
proposalGroupTitle.set(Res.get("dao.proposal.create.selectProposalType"));
} else {
proposalGroupTitle.set(Res.get("dao.proposal.create.phase.inactive"));
proposalTypeComboBox.getSelectionModel().clearSelection();
updateTimeUntilNextProposalPhase(block.getHeight());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateTimeUntilNextProposalPhase(int height) {
nextProposalTextField.setText(DaoUtil.getNextPhaseDuration(height, DaoPhase.Phase.PROPOSAL, daoFacade));
}
private void publishMyProposal(ProposalType type) {
try {
ProposalWithTransaction proposalWithTransaction = getProposalWithTransaction(type);
if (proposalWithTransaction == null)
return;
Proposal proposal = proposalWithTransaction.getProposal();
Transaction transaction = proposalWithTransaction.getTransaction();
Coin miningFee = transaction.getFee();
int txVsize = transaction.getVsize();
Coin fee = daoFacade.getProposalFee(daoFacade.getChainHeight());
if (type.equals(ProposalType.BONDED_ROLE)) {
checkNotNull(proposalDisplay, "proposalDisplay must not be null");
checkNotNull(proposalDisplay.bondedRoleTypeComboBox, "proposalDisplay.bondedRoleTypeComboBox must not be null");
BondedRoleType bondedRoleType = proposalDisplay.bondedRoleTypeComboBox.getSelectionModel().getSelectedItem();
long requiredBond = daoFacade.getRequiredBond(bondedRoleType);
long availableBalance = bsqWalletService.getAvailableConfirmedBalance().value;
if (requiredBond > availableBalance) {
long missing = requiredBond - availableBalance;
new Popup().warning(Res.get("dao.proposal.create.missingBsqFundsForBond",
bsqFormatter.formatCoinWithCode(missing)))
.actionButtonText(Res.get("dao.proposal.create.publish"))
.onAction(() -> showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txVsize, fee))
.show();
} else {
showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txVsize, fee);
}
} else {
showFeeInfoAndPublishMyProposal(proposal, transaction, miningFee, txVsize, fee);
}
} catch (InsufficientMoneyException e) {
if (e instanceof InsufficientBsqException) {
new Popup().warning(Res.get("dao.proposal.create.missingBsqFunds",
bsqFormatter.formatCoinWithCode(e.missing))).show();
} else {
if (type.equals(ProposalType.COMPENSATION_REQUEST) || type.equals(ProposalType.REIMBURSEMENT_REQUEST)) {
new Popup().warning(Res.get("dao.proposal.create.missingIssuanceFunds",
100,
btcFormatter.formatCoinWithCode(e.missing))).show();
} else {
new Popup().warning(Res.get("dao.proposal.create.missingMinerFeeFunds",
btcFormatter.formatCoinWithCode(e.missing))).show();
}
}
} catch (ProposalValidationException e) {
String message;
if (e.getMinRequestAmount() != null) {
message = Res.get("validation.bsq.amountBelowMinAmount",
bsqFormatter.formatCoinWithCode(e.getMinRequestAmount()));
} else {
message = e.getMessage();
}
new Popup().warning(message).show();
} catch (IllegalArgumentException e) {
log.error(e.toString());
e.printStackTrace();
new Popup().warning(e.getMessage()).show();
} catch (Throwable e) {
log.error(e.toString());
e.printStackTrace();
new Popup().warning(e.toString()).show();
}
}
private void showFeeInfoAndPublishMyProposal(Proposal proposal, Transaction transaction, Coin miningFee, int txVsize, Coin fee) {
if (!DevEnv.isDevMode()) {
Coin btcForIssuance = null;
if (proposal instanceof IssuanceProposal) btcForIssuance = ((IssuanceProposal) proposal).getRequestedBsq();
GUIUtil.showBsqFeeInfoPopup(fee, miningFee, btcForIssuance, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.proposal"), () -> doPublishMyProposal(proposal, transaction));
} else {
doPublishMyProposal(proposal, transaction);
}
}
private void doPublishMyProposal(Proposal proposal, Transaction transaction) {
//TODO it still happens that the user can click twice. Not clear why that can happen. Maybe we get updateButtonState
// called in between which re-enables the button?
makeProposalButton.setDisable(true);
busyLabel.setVisible(true);
busyAnimation.play();
daoFacade.publishMyProposal(proposal,
transaction,
() -> {
if (!DevEnv.isDevMode())
new Popup().feedback(Res.get("dao.tx.published.success")).show();
if (proposalDisplay != null)
proposalDisplay.clearForm();
proposalTypeComboBox.getSelectionModel().clearSelection();
busyAnimation.stop();
busyLabel.setVisible(false);
makeProposalButton.setDisable(false);
},
errorMessage -> {
new Popup().warning(errorMessage).show();
busyAnimation.stop();
busyLabel.setVisible(false);
makeProposalButton.setDisable(false);
});
}
@Nullable
private ProposalWithTransaction getProposalWithTransaction(ProposalType proposalType)
throws InsufficientMoneyException, ProposalValidationException, TxException, VoteResultException.ValidationException {
checkNotNull(proposalDisplay, "proposalDisplay must not be null");
String link = proposalDisplay.linkInputTextField.getText();
String name = proposalDisplay.nameTextField.getText();
switch (proposalType) {
case COMPENSATION_REQUEST:
checkNotNull(proposalDisplay.requestedBsqTextField,
"proposalDisplay.requestedBsqTextField must not be null");
return daoFacade.getCompensationProposalWithTransaction(name,
link,
ParsingUtils.parseToCoin(proposalDisplay.requestedBsqTextField.getText(), bsqFormatter));
case REIMBURSEMENT_REQUEST:
checkNotNull(proposalDisplay.requestedBsqTextField,
"proposalDisplay.requestedBsqTextField must not be null");
return daoFacade.getReimbursementProposalWithTransaction(name,
link,
ParsingUtils.parseToCoin(proposalDisplay.requestedBsqTextField.getText(), bsqFormatter));
case CHANGE_PARAM:
checkNotNull(proposalDisplay.paramComboBox,
"proposalDisplay.paramComboBox must not be null");
checkNotNull(proposalDisplay.paramValueTextField,
"proposalDisplay.paramValueTextField must not be null");
Param selectedParam = proposalDisplay.paramComboBox.getSelectionModel().getSelectedItem();
if (selectedParam == null)
throw new ProposalValidationException("selectedParam is null");
String paramValueAsString = proposalDisplay.paramValueTextField.getText();
if (paramValueAsString == null || paramValueAsString.isEmpty())
throw new ProposalValidationException("paramValue is null or empty");
try {
String paramValue = bsqFormatter.parseParamValueToString(selectedParam, paramValueAsString);
proposalDisplay.paramValueTextField.setText(paramValue);
log.info("Change param: paramValue={}, paramValueAsString={}", paramValue, paramValueAsString);
changeParamValidator.validateParamValue(selectedParam, paramValue);
return daoFacade.getParamProposalWithTransaction(name,
link,
selectedParam,
paramValue);
} catch (Throwable e) {
new Popup().warning(e.getMessage()).show();
return null;
}
case BONDED_ROLE:
checkNotNull(proposalDisplay.bondedRoleTypeComboBox,
"proposalDisplay.bondedRoleTypeComboBox must not be null");
Role role = new Role(name,
link,
proposalDisplay.bondedRoleTypeComboBox.getSelectionModel().getSelectedItem());
return daoFacade.getBondedRoleProposalWithTransaction(role);
case CONFISCATE_BOND:
checkNotNull(proposalDisplay.confiscateBondComboBox,
"proposalDisplay.confiscateBondComboBox must not be null");
Bond bond = proposalDisplay.confiscateBondComboBox.getSelectionModel().getSelectedItem();
if (!bond.isActive())
throw new VoteResultException.ValidationException("Bond is not locked and can't be confiscated");
return daoFacade.getConfiscateBondProposalWithTransaction(name, link, bond.getLockupTxId());
case GENERIC:
return daoFacade.getGenericProposalWithTransaction(name, link);
case REMOVE_ASSET:
checkNotNull(proposalDisplay.assetComboBox,
"proposalDisplay.assetComboBox must not be null");
Asset asset = proposalDisplay.assetComboBox.getSelectionModel().getSelectedItem();
return daoFacade.getRemoveAssetProposalWithTransaction(name, link, asset);
default:
final String msg = "Undefined ProposalType " + selectedProposalType;
log.error(msg);
throw new RuntimeException(msg);
}
}
private void addProposalDisplay() {
if (selectedProposalType != null) {
proposalDisplay = new ProposalDisplay(root, bsqFormatter, daoFacade, changeParamValidator, navigation, null);
proposalDisplay.createAllFields(Res.get("dao.proposal.create.new"), alwaysVisibleGridRowIndex, Layout.GROUP_DISTANCE_WITHOUT_SEPARATOR,
selectedProposalType, true);
final Tuple4<Button, BusyAnimation, Label, HBox> makeProposalTuple = addButtonBusyAnimationLabelAfterGroup(root,
proposalDisplay.getGridRow(), 0, Res.get("dao.proposal.create.button"));
makeProposalButton = makeProposalTuple.first;
busyAnimation = makeProposalTuple.second;
busyLabel = makeProposalTuple.third;
busyLabel.setVisible(false);
busyLabel.setText(Res.get("dao.proposal.create.publishing"));
setMakeProposalButtonHandler();
proposalDisplay.addInputChangedListener(this::updateButtonState);
updateButtonState();
}
}
private void removeProposalDisplay() {
if (proposalDisplay != null) {
proposalDisplay.removeAllFields();
GUIUtil.removeChildrenFromGridPaneRows(root, alwaysVisibleGridRowIndex, proposalDisplay.getGridRow());
proposalDisplay.removeInputChangedListener(this::updateButtonState);
proposalDisplay.removeListeners();
proposalDisplay = null;
}
}
private void setMakeProposalButtonHandler() {
makeProposalButton.setOnAction(event -> {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
publishMyProposal(selectedProposalType);
}
});
}
private void updateButtonState() {
AtomicBoolean inputsValid = new AtomicBoolean(true);
if (proposalDisplay != null) {
proposalDisplay.getInputControls().stream()
.filter(Objects::nonNull).forEach(e -> {
if (e instanceof InputTextField) {
InputTextField inputTextField = (InputTextField) e;
inputsValid.set(inputsValid.get() &&
inputTextField.getValidator() != null &&
inputTextField.getValidator().validate(e.getText()).isValid);
}
});
proposalDisplay.getComboBoxes().stream()
.filter(Objects::nonNull).forEach(comboBox -> inputsValid.set(inputsValid.get() &&
comboBox.getSelectionModel().getSelectedItem() != null));
InputTextField linkInputTextField = proposalDisplay.linkInputTextField;
inputsValid.set(inputsValid.get() &&
linkInputTextField.getValidator().validate(linkInputTextField.getText()).isValid);
}
makeProposalButton.setDisable(!inputsValid.get());
}
}

View file

@ -1,187 +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.governance.proposals;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.Vote;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import de.jensd.fx.fontawesome.AwesomeIcon;
import com.jfoenix.controls.JFXButton;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.beans.value.ChangeListener;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@ToString
@Slf4j
@EqualsAndHashCode
//TODO merge with vote result ProposalListItem
public class ProposalsListItem {
enum IconButtonType {
REMOVE_PROPOSAL(Res.get("dao.proposal.table.icon.tooltip.removeProposal")),
ACCEPT(Res.get("dao.proposal.display.myVote.accepted")),
REJECT(Res.get("dao.proposal.display.myVote.rejected")),
IGNORE(Res.get("dao.proposal.display.myVote.ignored"));
@Getter
private String title;
IconButtonType(String title) {
this.title = title;
}
}
@Getter
private final Proposal proposal;
private final DaoFacade daoFacade;
private final BsqFormatter bsqFormatter;
@Getter
@Nullable
private Ballot ballot;
@Getter
private JFXButton iconButton;
private ChangeListener<DaoPhase.Phase> phaseChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
ProposalsListItem(Proposal proposal,
DaoFacade daoFacade,
BsqFormatter bsqFormatter) {
this.proposal = proposal;
this.daoFacade = daoFacade;
this.bsqFormatter = bsqFormatter;
init();
}
ProposalsListItem(Ballot ballot,
DaoFacade daoFacade,
BsqFormatter bsqFormatter) {
this.ballot = ballot;
this.proposal = ballot.getProposal();
this.daoFacade = daoFacade;
this.bsqFormatter = bsqFormatter;
init();
}
private void init() {
phaseChangeListener = (observable, oldValue, newValue) -> onPhaseChanged(newValue);
daoFacade.phaseProperty().addListener(phaseChangeListener);
onPhaseChanged(daoFacade.phaseProperty().get());
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void cleanup() {
daoFacade.phaseProperty().removeListener(phaseChangeListener);
}
public void onPhaseChanged(DaoPhase.Phase phase) {
//noinspection IfCanBeSwitch
Label icon;
if (phase == DaoPhase.Phase.PROPOSAL) {
icon = FormBuilder.getIcon(AwesomeIcon.TRASH);
icon.getStyleClass().addAll("icon", "dao-remove-proposal-icon");
iconButton = new JFXButton("", icon);
boolean isMyProposal = daoFacade.isMyProposal(proposal);
if (isMyProposal) {
iconButton.setUserData(IconButtonType.REMOVE_PROPOSAL);
}
iconButton.setVisible(isMyProposal);
iconButton.setManaged(isMyProposal);
iconButton.getStyleClass().add("hidden-icon-button");
iconButton.setTooltip(new Tooltip(Res.get("dao.proposal.table.icon.tooltip.removeProposal")));
} else if (iconButton != null) {
iconButton.setVisible(true);
iconButton.setManaged(true);
}
// ballot
if (ballot != null) {
Vote vote = ballot.getVote();
if (vote != null) {
if ((vote).isAccepted()) {
icon = FormBuilder.getIcon(AwesomeIcon.THUMBS_UP);
icon.getStyleClass().addAll("icon", "dao-accepted-icon");
iconButton = new JFXButton("", icon);
iconButton.setUserData(IconButtonType.ACCEPT);
} else {
icon = FormBuilder.getIcon(AwesomeIcon.THUMBS_DOWN);
icon.getStyleClass().addAll("icon", "dao-rejected-icon");
iconButton = new JFXButton("", icon);
iconButton.setUserData(IconButtonType.REJECT);
}
} else {
icon = FormBuilder.getIcon(AwesomeIcon.MINUS);
icon.getStyleClass().addAll("icon", "dao-ignored-icon");
iconButton = new JFXButton("", icon);
iconButton.setUserData(IconButtonType.IGNORE);
}
iconButton.setTooltip(new Tooltip(Res.get("dao.proposal.table.icon.tooltip.changeVote",
((IconButtonType) iconButton.getUserData()).getTitle(),
getNext(((IconButtonType) iconButton.getUserData()))
)));
iconButton.getStyleClass().add("hidden-icon-button");
iconButton.layout();
}
}
public String getProposalTypeAsString() {
return Res.get("dao.proposal.type." + proposal.getType().name());
}
private String getNext(IconButtonType iconButtonType) {
switch (iconButtonType) {
case ACCEPT:
return IconButtonType.REJECT.getTitle();
case REJECT:
return IconButtonType.IGNORE.getTitle();
default:
return IconButtonType.ACCEPT.getTitle();
}
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.governance.proposals.ProposalsView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,893 +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.governance.proposals;
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.BusyAnimation;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TableGroupHeadline;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.components.TxIdTextField;
import bisq.desktop.main.dao.governance.PhasesView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.SelectProposalWindow;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.blindvote.BlindVoteConsensus;
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
import bisq.core.dao.governance.myvote.MyVote;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.DaoPhase;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.Vote;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.validation.InputValidator;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import bisq.common.util.Tuple4;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import javax.inject.Inject;
import javax.inject.Named;
import com.jfoenix.controls.JFXButton;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
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.SortedList;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.*;
import static bisq.desktop.util.Layout.INITIAL_WINDOW_HEIGHT;
@FxmlView
public class ProposalsView extends ActivatableView<GridPane, Void> implements BsqBalanceListener, DaoStateListener {
private final DaoFacade daoFacade;
private final BsqWalletService bsqWalletService;
private final PhasesView phasesView;
private final DaoStateService daoStateService;
private final MyBlindVoteListService myBlindVoteListService;
private final Preferences preferences;
private final BsqFormatter bsqFormatter;
private final CoinFormatter btcFormatter;
private final SelectProposalWindow selectProposalWindow;
private final ObservableList<ProposalsListItem> listItems = FXCollections.observableArrayList();
private final SortedList<ProposalsListItem> sortedList = new SortedList<>(listItems);
private final List<Button> voteButtons = new ArrayList<>();
private final List<Node> voteFields = new ArrayList<>();
private TableView<ProposalsListItem> tableView;
private Label voteButtonInfoLabel;
private TxIdTextField revealTxIdTextField, blindVoteTxIdTextField;
private TextField meritTextField;
private VBox blindVoteTxIdContainer, revealTxIdContainer;
private Button voteButton;
private InputTextField stakeInputTextField;
private BusyAnimation voteButtonBusyAnimation;
private int gridRow = 0;
@Nullable
private ProposalsListItem selectedItem;
private DaoPhase.Phase currentPhase;
private ListChangeListener<Proposal> proposalListChangeListener;
private ListChangeListener<Ballot> ballotListChangeListener;
private ChangeListener<String> stakeListener;
private Subscription selectedProposalSubscription, phaseSubscription;
private boolean areVoteButtonsVisible;
private TableColumn<ProposalsListItem, ProposalsListItem> lastColumn;
private String shownVoteOnProposalWindowForTxId = "";
private final Function<Double, Double> proposalTableViewHeight = (screenSize) -> {
double initialProposalTableViewHeight = 180;
double pixelsPerProposalTableRow = (initialProposalTableViewHeight - 28) / 4.0;
int extraRows = screenSize <= INITIAL_WINDOW_HEIGHT ? 0 : (int) ((screenSize - INITIAL_WINDOW_HEIGHT) / pixelsPerProposalTableRow);
return extraRows == 0 ? initialProposalTableViewHeight : Math.ceil(initialProposalTableViewHeight + (extraRows * pixelsPerProposalTableRow));
};
private ChangeListener<Number> sceneHeightListener;
private TableGroupHeadline proposalsHeadline;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ProposalsView(DaoFacade daoFacade,
BsqWalletService bsqWalletService,
PhasesView phasesView,
DaoStateService daoStateService,
MyBlindVoteListService myBlindVoteListService,
Preferences preferences,
BsqFormatter bsqFormatter,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
SelectProposalWindow selectProposalWindow) {
this.daoFacade = daoFacade;
this.bsqWalletService = bsqWalletService;
this.phasesView = phasesView;
this.daoStateService = daoStateService;
this.myBlindVoteListService = myBlindVoteListService;
this.preferences = preferences;
this.bsqFormatter = bsqFormatter;
this.btcFormatter = btcFormatter;
this.selectProposalWindow = selectProposalWindow;
}
@Override
public void initialize() {
super.initialize();
gridRow = phasesView.addGroup(root, gridRow);
createProposalsTableView();
createVoteView();
ballotListChangeListener = c -> updateListItems();
proposalListChangeListener = c -> updateListItems();
sceneHeightListener = (observable, oldValue, newValue) -> updateTableHeight(newValue.doubleValue());
stakeListener = (observable, oldValue, newValue) -> updateViews();
}
@Override
protected void activate() {
phasesView.activate();
selectedProposalSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectProposal);
daoFacade.addBsqStateListener(this);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setPrefHeight(100);
root.getScene().heightProperty().addListener(sceneHeightListener);
UserThread.execute(() -> {
if (root.getScene() != null)
updateTableHeight(root.getScene().getHeight());
});
stakeInputTextField.textProperty().addListener(stakeListener);
voteButton.setOnAction(e -> onVote());
onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(),
bsqWalletService.getAvailableNonBsqBalance(),
bsqWalletService.getUnverifiedBalance(),
bsqWalletService.getUnconfirmedChangeBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockupBondsBalance(),
bsqWalletService.getUnlockingBondsBalance());
if (daoStateService.isParseBlockChainComplete()) {
addListenersAfterParseBlockChainComplete();
updateListItems();
applyMerit();
updateViews();
}
}
@Override
protected void deactivate() {
phasesView.deactivate();
if (phaseSubscription != null)
phaseSubscription.unsubscribe();
selectedProposalSubscription.unsubscribe();
sortedList.comparatorProperty().unbind();
daoFacade.getActiveOrMyUnconfirmedProposals().removeListener(proposalListChangeListener);
daoFacade.getAllBallots().removeListener(ballotListChangeListener);
daoFacade.removeBsqStateListener(this);
bsqWalletService.removeBsqBalanceListener(this);
if (stakeInputTextField != null) {
stakeInputTextField.textProperty().removeListener(stakeListener);
stakeInputTextField.clear();
}
if (voteButton != null)
voteButton.setOnAction(null);
listItems.forEach(ProposalsListItem::cleanup);
tableView.getSelectionModel().clearSelection();
selectedItem = null;
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqBalanceListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUpdateBalances(Coin availableConfirmedBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
Coin blindVoteFee = BlindVoteConsensus.getFee(daoStateService, daoStateService.getChainHeight());
if (isBlindVotePhaseButNotLastBlock()) {
Coin availableForVoting = availableConfirmedBalance.subtract(blindVoteFee);
if (availableForVoting.isNegative())
availableForVoting = Coin.valueOf(0);
stakeInputTextField.setPromptText(Res.get("dao.proposal.myVote.stake.prompt",
bsqFormatter.formatCoinWithCode(availableForVoting)));
BsqValidator stakeInputTextFieldValidator = new BsqValidator(bsqFormatter);
stakeInputTextFieldValidator.setMaxValue(availableForVoting);
stakeInputTextField.setValidator(stakeInputTextFieldValidator);
} else
stakeInputTextField.setPromptText("");
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
updateListItems();
applyMerit();
}
@Override
public void onParseBlockChainComplete() {
addListenersAfterParseBlockChainComplete();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void addListenersAfterParseBlockChainComplete() {
daoFacade.getActiveOrMyUnconfirmedProposals().addListener(proposalListChangeListener);
daoFacade.getAllBallots().addListener(ballotListChangeListener);
bsqWalletService.addBsqBalanceListener(this);
phaseSubscription = EasyBind.subscribe(daoFacade.phaseProperty(), this::onPhaseChanged);
}
private void updateListItems() {
listItems.forEach(ProposalsListItem::cleanup);
listItems.clear();
if (daoFacade.phaseProperty().get().ordinal() < DaoPhase.Phase.BLIND_VOTE.ordinal()) {
// proposal phase
List<Proposal> list = daoFacade.getActiveOrMyUnconfirmedProposals();
listItems.setAll(list.stream()
.map(proposal -> new ProposalsListItem(proposal, daoFacade, bsqFormatter))
.collect(Collectors.toSet()));
} else {
// blind vote phase
List<Ballot> ballotList = daoFacade.getBallotsOfCycle();
listItems.setAll(ballotList.stream()
.map(ballot -> new ProposalsListItem(ballot, daoFacade, bsqFormatter))
.collect(Collectors.toSet()));
}
updateViews();
}
private void showVoteOnProposalWindow(Proposal proposal, @Nullable Ballot ballot,
@Nullable EvaluatedProposal evaluatedProposal) {
if (!shownVoteOnProposalWindowForTxId.equals(proposal.getTxId())) {
shownVoteOnProposalWindowForTxId = proposal.getTxId();
selectProposalWindow.show(proposal, evaluatedProposal, ballot);
selectProposalWindow.onAccept(() -> {
shownVoteOnProposalWindowForTxId = "";
onAccept();
});
selectProposalWindow.onReject(() -> {
shownVoteOnProposalWindowForTxId = "";
onReject();
});
selectProposalWindow.onIgnore(() -> {
shownVoteOnProposalWindowForTxId = "";
onIgnore();
});
selectProposalWindow.onRemove(() -> {
shownVoteOnProposalWindowForTxId = "";
onRemoveProposal();
});
selectProposalWindow.onClose(() -> {
shownVoteOnProposalWindowForTxId = "";
tableView.getSelectionModel().clearSelection();
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handlers
///////////////////////////////////////////////////////////////////////////////////////////
private void onPhaseChanged(DaoPhase.Phase phase) {
if (phase != null && !phase.equals(currentPhase)) {
currentPhase = phase;
stakeInputTextField.clear();
}
updateViews();
}
private void onRemoveProposal() {
if (daoFacade.phaseProperty().get() == DaoPhase.Phase.PROPOSAL) {
Proposal proposal = selectedItem.getProposal();
new Popup().warning(Res.get("dao.proposal.active.remove.confirm"))
.actionButtonText(Res.get("dao.proposal.active.remove.doRemove"))
.onAction(() -> {
if (!daoFacade.removeMyProposal(proposal)) {
new Popup().warning(Res.get("dao.proposal.active.remove.failed")).show();
}
tableView.getSelectionModel().clearSelection();
})
.show();
}
}
private void onSelectProposal(ProposalsListItem item) {
selectedItem = item;
if (selectedItem != null) {
EvaluatedProposal evaluatedProposal = daoStateService.getEvaluatedProposalList().stream()
.filter(e -> daoFacade.isTxInCorrectCycle(e.getProposal().getTxId(),
daoFacade.getChainHeight()))
.filter(e -> e.getProposalTxId().equals(selectedItem.getProposal().getTxId()))
.findAny()
.orElse(null);
applyMerit();
showVoteOnProposalWindow(selectedItem.getProposal(), selectedItem.getBallot(), evaluatedProposal);
}
onPhaseChanged(daoFacade.phaseProperty().get());
}
private void applyMerit() {
// We check if we have voted on that proposal. If so we use the merit used in the vote, otherwise we
// use the merit based on all past issuance with the time decay applied.
// The merit from the vote stays the same over blocks, the merit from daoFacade.getMeritAndStake()
// decreases with every block a bit (over 2 years it goes to zero).
Optional<MyVote> optionalMyVote = daoFacade.getMyVoteListForCycle().stream()
.filter(myVote -> daoFacade.getTx(myVote.getBlindVoteTxId()).isPresent())
.findAny();
boolean hasConfirmedMyVoteInCycle = optionalMyVote.isPresent();
long merit;
if (selectedItem != null && hasConfirmedMyVoteInCycle) {
merit = daoFacade.getMeritAndStakeForProposal(selectedItem.getProposal().getTxId()).first;
} else if (selectedItem == null && hasConfirmedMyVoteInCycle) {
merit = optionalMyVote.get().getMerit(myBlindVoteListService, daoStateService);
} else {
merit = daoFacade.getAvailableMerit();
}
meritTextField.setText(bsqFormatter.formatCoinWithCode(Coin.valueOf(merit)));
}
private void onAccept() {
onVoteOnSingleProposal(new Vote(true));
}
private void onReject() {
onVoteOnSingleProposal(new Vote(false));
}
private void onIgnore() {
onVoteOnSingleProposal(null);
}
private void onVoteOnSingleProposal(Vote vote) {
if (selectedItem != null) {
daoFacade.setVote(selectedItem.getBallot(), vote);
updateStateAfterVote();
showHowToSetStakeForVotingPopup();
}
tableView.getSelectionModel().clearSelection();
}
private void showHowToSetStakeForVotingPopup() {
String id = "explainHowToSetStakeForVoting";
if (preferences.showAgain(id))
new Popup().information(Res.get("dao.proposal.myVote.setStake.description"))
.dontShowAgainId(id).show();
}
private void onVote() {
Coin stake = ParsingUtils.parseToCoin(stakeInputTextField.getText(), bsqFormatter);
try {
// We create a dummy tx to get the miningFee for displaying it at the confirmation popup
Tuple2<Coin, Integer> miningFeeAndTxVsize = daoFacade.getBlindVoteMiningFeeAndTxVsize(stake);
Coin miningFee = miningFeeAndTxVsize.first;
int txVsize = miningFeeAndTxVsize.second;
Coin blindVoteFee = daoFacade.getBlindVoteFeeForCycle();
if (!DevEnv.isDevMode()) {
GUIUtil.showBsqFeeInfoPopup(blindVoteFee, miningFee, txVsize, bsqFormatter, btcFormatter,
Res.get("dao.blindVote"), () -> publishBlindVote(stake));
} else {
publishBlindVote(stake);
}
} catch (InsufficientMoneyException | WalletException | TransactionVerificationException exception) {
new Popup().warning(exception.toString()).show();
}
}
private void publishBlindVote(Coin stake) {
//TODO Starting voteButtonBusyAnimation here does not make sense if we stop it immediately below.
// Check if voteButtonBusyAnimation should stay running until we hear back from publishing and only disable
// button so that the user cannot click twice.
voteButtonBusyAnimation.play();
voteButtonInfoLabel.setText(Res.get("dao.blindVote.startPublishing"));
daoFacade.publishBlindVote(stake,
() -> {
if (!DevEnv.isDevMode())
new Popup().feedback(Res.get("dao.blindVote.success")).show();
}, exception -> {
voteButtonBusyAnimation.stop();
voteButtonInfoLabel.setText("");
updateViews();
new Popup().warning(exception.toString()).show();
});
// We reset UI without waiting for callback as callback might be slow and then the user could click
// multiple times.
voteButtonBusyAnimation.stop();
voteButtonInfoLabel.setText("");
updateViews();
}
private void updateStateAfterVote() {
updateViews();
tableView.refresh();
}
private void updateViews() {
boolean isBlindVotePhaseButNotLastBlock = isBlindVotePhaseButNotLastBlock();
boolean hasVotedOnProposal = hasVotedOnProposal();
voteButton.setDisable(!hasVotedOnProposal ||
!stakeInputTextField.getValidator().validate(stakeInputTextField.getText()).isValid);
List<MyVote> myVoteListForCycle = daoFacade.getMyVoteListForCycle();
boolean hasAlreadyVoted = !myVoteListForCycle.isEmpty();
if (selectedItem != null) {
stakeInputTextField.setMouseTransparent(hasAlreadyVoted || !isBlindVotePhaseButNotLastBlock);
}
boolean hasProposals = !daoFacade.getActiveOrMyUnconfirmedProposals().isEmpty();
boolean showVoteFields = (isBlindVotePhaseButNotLastBlock && hasProposals) || hasAlreadyVoted;
voteFields.forEach(node -> {
node.setVisible(showVoteFields);
node.setManaged(showVoteFields);
});
areVoteButtonsVisible = hasProposals && isBlindVotePhaseButNotLastBlock && !hasAlreadyVoted;
voteButtons.forEach(button -> {
button.setVisible(areVoteButtonsVisible);
button.setManaged(areVoteButtonsVisible);
});
blindVoteTxIdTextField.setup("");
revealTxIdTextField.setup("");
blindVoteTxIdContainer.setVisible(false);
blindVoteTxIdContainer.setManaged(false);
revealTxIdContainer.setVisible(false);
revealTxIdContainer.setManaged(false);
if (hasAlreadyVoted) {
if (myVoteListForCycle.size() == 1) {
Optional<MyVote> optionalMyVote = myVoteListForCycle.stream()
.filter(myVote -> daoFacade.isTxInCorrectCycle(myVote.getHeight(), daoFacade.getChainHeight()))
.findAny();
if (optionalMyVote.isPresent()) {
MyVote myVote = optionalMyVote.get();
Coin stake = Coin.valueOf(myVote.getBlindVote().getStake());
stakeInputTextField.setValidator(new InputValidator());
stakeInputTextField.setText(bsqFormatter.formatCoinWithCode(stake));
if (myVote.getBlindVoteTxId() != null) {
blindVoteTxIdTextField.setup(myVote.getBlindVoteTxId());
blindVoteTxIdContainer.setVisible(true);
blindVoteTxIdContainer.setManaged(true);
}
if (myVote.getRevealTxId() != null) {
revealTxIdTextField.setup(myVote.getRevealTxId());
revealTxIdContainer.setVisible(true);
revealTxIdContainer.setManaged(true);
}
} else {
stakeInputTextField.clear();
}
} else {
String msg = "We found multiple MyVote entries in that cycle. That is not supported by the UI.";
log.warn(msg);
String id = "multipleVotes";
if (preferences.showAgain(id))
new Popup().warning(msg).dontShowAgainId(id).show();
}
voteButton.setVisible(false);
voteButton.setManaged(false);
}
switch (daoFacade.phaseProperty().get()) {
case PROPOSAL:
// We have a bug in removing a proposal which is not trivial to fix (p2p network layer). Until that bug is fixed
// it is better to not show the remove button as it confused users and a removed proposal will reappear with a
// high probability at the vote phase.
//lastColumn.setText(Res.get("dao.proposal.table.header.remove"));
lastColumn.setText("");
break;
case BLIND_VOTE:
lastColumn.setText(Res.get("dao.proposal.table.header.myVote"));
break;
default:
lastColumn.setText("");
break;
}
if (selectedItem == null &&
listItems.size() > 0 &&
selectProposalWindow.isDisplayed() &&
!shownVoteOnProposalWindowForTxId.equals("")) {
Proposal proposal = selectProposalWindow.getProposal();
Optional<ProposalsListItem> proposalsListItem = listItems.stream()
.filter(item -> item.getProposal().equals(proposal))
.findAny();
selectProposalWindow.onHide(() -> proposalsListItem.ifPresent(
listItem -> tableView.getSelectionModel().select(listItem)));
shownVoteOnProposalWindowForTxId = "";
selectProposalWindow.hide();
}
}
private boolean hasVotedOnProposal() {
return listItems.stream()
.filter(e -> e.getBallot() != null)
.map(ProposalsListItem::getBallot)
.anyMatch(e -> e.getVote() != null);
}
private boolean isBlindVotePhaseButNotLastBlock() {
return daoFacade.isInPhaseButNotLastBlock(DaoPhase.Phase.BLIND_VOTE);
}
private void updateTableHeight(double height) {
double newTableViewHeight = proposalTableViewHeight.apply(height);
if (tableView.getHeight() != newTableViewHeight) {
tableView.setMinHeight(newTableViewHeight);
double diff = newTableViewHeight - tableView.getHeight();
proposalsHeadline.setMaxHeight(proposalsHeadline.getHeight() + diff);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Create views
///////////////////////////////////////////////////////////////////////////////////////////
private void createProposalsTableView() {
proposalsHeadline = new TableGroupHeadline(Res.get("dao.proposal.active.header"));
GridPane.setRowIndex(proposalsHeadline, ++gridRow);
GridPane.setMargin(proposalsHeadline, new Insets(Layout.GROUP_DISTANCE, -10, -10, -10));
root.getChildren().add(proposalsHeadline);
tableView = new TableView<>();
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
createProposalColumns();
GridPane.setRowIndex(tableView, gridRow);
GridPane.setHgrow(tableView, Priority.ALWAYS);
GridPane.setVgrow(tableView, Priority.SOMETIMES);
GridPane.setMargin(tableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, 5, -10));
root.getChildren().add(tableView);
tableView.setItems(sortedList);
}
private void createVoteView() {
TitledGroupBg voteTitledGroupBg = addTitledGroupBg(root, ++gridRow, 4,
Res.get("dao.proposal.votes.header"), 20);
voteTitledGroupBg.getStyleClass().add("last");
voteFields.add(voteTitledGroupBg);
Tuple3<Label, TextField, VBox> meritTuple = addTopLabelTextField(root, gridRow,
Res.get("dao.proposal.myVote.merit"), 40);
Label meritLabel = meritTuple.first;
meritTextField = meritTuple.second;
meritTextField.setText(bsqFormatter.formatCoinWithCode(Coin.ZERO));
voteFields.add(meritLabel);
voteFields.add(meritTextField);
voteFields.add(meritTuple.third);
stakeInputTextField = addInputTextField(root, ++gridRow,
Res.get("dao.proposal.myVote.stake"));
stakeInputTextField.setValidator(new BsqValidator(bsqFormatter));
voteFields.add(stakeInputTextField);
Tuple3<Label, TxIdTextField, VBox> tuple = addTopLabelTxIdTextField(root, ++gridRow,
Res.get("shared.blindVoteTxId"), 0);
blindVoteTxIdTextField = tuple.second;
blindVoteTxIdContainer = tuple.third;
blindVoteTxIdTextField.setBsq(true);
voteFields.add(blindVoteTxIdContainer);
tuple = addTopLabelTxIdTextField(root, ++gridRow,
Res.get("dao.proposal.myVote.revealTxId"), 0);
revealTxIdTextField = tuple.second;
revealTxIdTextField.setBsq(true);
revealTxIdContainer = tuple.third;
voteFields.add(revealTxIdContainer);
Tuple4<Button, BusyAnimation, Label, HBox> voteButtonTuple = addButtonBusyAnimationLabelAfterGroup(root, ++gridRow,
Res.get("dao.proposal.myVote.button"));
voteButton = voteButtonTuple.first;
voteButtons.add(voteButton);
voteFields.add(voteButtonTuple.fourth);
voteButtonBusyAnimation = voteButtonTuple.second;
voteButtonInfoLabel = voteButtonTuple.third;
}
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns
///////////////////////////////////////////////////////////////////////////////////////////
private void createProposalColumns() {
TableColumn<ProposalsListItem, ProposalsListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("shared.dateTime"));
column.setMinWidth(190);
column.setMaxWidth(column.getMinWidth());
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProposalsListItem, ProposalsListItem> call(
TableColumn<ProposalsListItem, ProposalsListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProposalsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(DisplayUtils.formatDateTime(item.getProposal().getCreationDateAsDate()));
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(o3 -> o3.getProposal().getCreationDateAsDate()));
column.setSortType(TableColumn.SortType.DESCENDING);
tableView.getColumns().add(column);
tableView.getSortOrder().add(column);
column = new AutoTooltipTableColumn<>(Res.get("shared.name"));
column.setMinWidth(60);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProposalsListItem, ProposalsListItem> call(
TableColumn<ProposalsListItem, ProposalsListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProposalsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getProposal().getName());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(o2 -> o2.getProposal().getName()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.proposal.table.header.link"));
column.setMinWidth(80);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProposalsListItem, ProposalsListItem> call(TableColumn<ProposalsListItem,
ProposalsListItem> column) {
return new TableCell<>() {
private HyperlinkWithIcon field;
@Override
public void updateItem(final ProposalsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
Proposal proposal = item.getProposal();
field = new HyperlinkWithIcon(proposal.getLink());
field.setOnAction(event -> GUIUtil.openWebPage(proposal.getLink()));
field.setTooltip(new Tooltip(proposal.getLink()));
setGraphic(field);
} else {
setGraphic(null);
if (field != null)
field.setOnAction(null);
}
}
};
}
});
column.setComparator(Comparator.comparing(o -> o.getProposal().getLink()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(Res.get("dao.proposal.table.header.proposalType"));
column.setMinWidth(60);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProposalsListItem, ProposalsListItem> call(
TableColumn<ProposalsListItem, ProposalsListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProposalsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getProposalTypeAsString());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(ProposalsListItem::getProposalTypeAsString));
tableView.getColumns().add(column);
column = new TableColumn<>(Res.get("dao.proposal.table.header.myVote"));
column.setMinWidth(60);
column.setMaxWidth(column.getMinWidth());
column.getStyleClass().add("last-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(new Callback<>() {
@Override
public TableCell<ProposalsListItem, ProposalsListItem> call(TableColumn<ProposalsListItem,
ProposalsListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(final ProposalsListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
item.onPhaseChanged(currentPhase);
JFXButton iconButton = item.getIconButton();
if (iconButton != null) {
ProposalsListItem.IconButtonType iconButtonType = (ProposalsListItem.IconButtonType) iconButton.getUserData();
iconButton.setOnAction(e -> {
selectedItem = item;
if (areVoteButtonsVisible) {
if (iconButtonType == ProposalsListItem.IconButtonType.ACCEPT)
onReject();
else if (iconButtonType == ProposalsListItem.IconButtonType.REJECT)
onIgnore();
else if (iconButtonType == ProposalsListItem.IconButtonType.IGNORE)
onAccept();
} else {
if (iconButtonType == ProposalsListItem.IconButtonType.REMOVE_PROPOSAL)
onRemoveProposal();
}
});
if (!areVoteButtonsVisible && iconButtonType != ProposalsListItem.IconButtonType.REMOVE_PROPOSAL) {
iconButton.setMouseTransparent(true);
iconButton.setStyle("-fx-cursor: default;");
}
// We have a bug in removing a proposal which is not trivial to fix (p2p network layer).
// Until that bug is fixed
// it is better to not show the remove button as it confused users and a removed proposal will reappear with a
// high probability at the vote phase. The following lines can be removed once the bug is fixed.
boolean showIcon = iconButtonType != null &&
iconButtonType != ProposalsListItem.IconButtonType.REMOVE_PROPOSAL;
iconButton.setVisible(showIcon);
iconButton.setManaged(showIcon);
setGraphic(iconButton);
} else {
setGraphic(null);
}
} else {
setGraphic(null);
}
}
};
}
});
column.setComparator(Comparator.comparing(item -> ((ProposalsListItem.IconButtonType) item.getIconButton().getUserData()).getTitle()));
tableView.getColumns().add(column);
lastColumn = column;
}
}

View file

@ -1,81 +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.governance.result;
import bisq.core.dao.governance.proposal.IssuanceProposal;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.FormattingUtils;
import org.bitcoinj.core.Coin;
import java.util.Date;
import lombok.Getter;
public class CycleListItem {
private final BsqFormatter bsqFormatter;
@Getter
private ResultsOfCycle resultsOfCycle;
CycleListItem(ResultsOfCycle resultsOfCycle,
BsqFormatter bsqFormatter) {
this.resultsOfCycle = resultsOfCycle;
this.bsqFormatter = bsqFormatter;
}
public String getCycle() {
return Res.get("dao.results.results.table.item.cycle", getCycleIndex(), getCycleDateTime(true));
}
public String getCycleDateTime(boolean useLocaleAndLocalTimezone) {
long cycleStartTime = resultsOfCycle.getCycleStartTime();
return cycleStartTime > 0 ? FormattingUtils.formatDateTime(new Date(cycleStartTime), useLocaleAndLocalTimezone) : Res.get("shared.na");
}
public int getCycleIndex() {
return resultsOfCycle.getCycleIndex() + 1;
}
public String getNumProposals() {
return String.valueOf(resultsOfCycle.getEvaluatedProposals().size());
}
public String getNumVotesAsString() {
return String.valueOf(resultsOfCycle.getNumVotes());
}
public String getMeritAndStake() {
return bsqFormatter.formatCoinWithCode(Coin.valueOf(resultsOfCycle.getMeritAndStake()));
}
public String getIssuance() {
long totalIssuance = resultsOfCycle.getEvaluatedProposals().stream()
.filter(EvaluatedProposal::isAccepted)
.filter(e -> e.getProposal() instanceof IssuanceProposal)
.map(e -> (IssuanceProposal) e.getProposal())
.mapToLong(e -> e.getRequestedBsq().value)
.sum();
return bsqFormatter.formatCoinWithCode(Coin.valueOf(totalIssuance));
}
public Long getCycleStartTime() {
return resultsOfCycle.getCycleStartTime();
}
}

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.governance.result;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.ChangeParamProposal;
import bisq.core.dao.state.model.governance.CompensationProposal;
import bisq.core.dao.state.model.governance.ConfiscateBondProposal;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.ReimbursementProposal;
import bisq.core.dao.state.model.governance.RemoveAssetProposal;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.dao.state.model.governance.Vote;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import org.bitcoinj.core.Coin;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Label;
import javafx.scene.control.TableRow;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.geometry.Insets;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
public class ProposalListItem {
@Getter
private EvaluatedProposal evaluatedProposal;
@Getter
private final Proposal proposal;
private final Vote vote;
private final boolean isMyBallotIncluded;
private final BsqFormatter bsqFormatter;
private TableRow tableRow;
ProposalListItem(EvaluatedProposal evaluatedProposal, Ballot ballot, boolean isMyBallotIncluded,
BsqFormatter bsqFormatter) {
this.evaluatedProposal = evaluatedProposal;
proposal = evaluatedProposal.getProposal();
vote = ballot.getVote();
this.isMyBallotIncluded = isMyBallotIncluded;
this.bsqFormatter = bsqFormatter;
}
// If myVoteIcon would be set in constructor styles are not applied correctly
public Label getMyVoteIcon() {
Label myVoteIcon;
if (vote != null) {
if ((vote).isAccepted()) {
myVoteIcon = getIcon(AwesomeIcon.THUMBS_UP, "dao-accepted-icon");
} else {
myVoteIcon = getIcon(AwesomeIcon.THUMBS_DOWN, "dao-rejected-icon");
}
if (!isMyBallotIncluded) {
Label notIncluded = FormBuilder.getIcon(AwesomeIcon.BAN_CIRCLE);
return new Label("", new HBox(10, new StackPane(myVoteIcon, notIncluded),
getIcon(AwesomeIcon.MINUS, "dao-ignored-icon")));
}
} else {
myVoteIcon = getIcon(AwesomeIcon.MINUS, "dao-ignored-icon");
if (!isMyBallotIncluded) {
myVoteIcon.setPadding(new Insets(0, 0, 0, 25));
return myVoteIcon;
}
}
return myVoteIcon;
}
@NotNull
private Label getIcon(AwesomeIcon awesomeIcon, String s) {
Label myVoteIcon;
myVoteIcon = FormBuilder.getIcon(awesomeIcon);
myVoteIcon.getStyleClass().add(s);
return myVoteIcon;
}
public void setTableRow(TableRow tableRow) {
this.tableRow = tableRow;
}
public void resetTableRow() {
if (tableRow != null) {
tableRow.setStyle(null);
tableRow.requestLayout();
}
}
public String getProposalOwnerName() {
return evaluatedProposal.getProposal().getName();
}
public AwesomeIcon getIcon() {
return evaluatedProposal.isAccepted() ? AwesomeIcon.OK_SIGN : AwesomeIcon.BAN_CIRCLE;
}
public boolean isAccepted() {
return evaluatedProposal.isAccepted();
}
public String getColorStyleClass() {
return evaluatedProposal.isAccepted() ? "dao-accepted-icon" : "dao-rejected-icon";
}
public String getDetails() {
return ProposalListItem.getProposalDetails(evaluatedProposal, bsqFormatter);
}
public long getIssuedAmount() {
if (evaluatedProposal.getProposal().getType() == ProposalType.COMPENSATION_REQUEST) {
CompensationProposal compensationProposal = (CompensationProposal) proposal;
Coin requestedBsq = evaluatedProposal.isAccepted() ? compensationProposal.getRequestedBsq() : Coin.ZERO;
return requestedBsq.value;
}
return 0;
}
public String getThresholdAsString() {
return (evaluatedProposal.getProposalVoteResult().getThreshold() / 100D) + "%";
}
public long getThreshold() {
return evaluatedProposal.getProposalVoteResult().getThreshold();
}
public String getQuorumAsString() {
return bsqFormatter.formatCoinWithCode(Coin.valueOf(evaluatedProposal.getProposalVoteResult().getQuorum()));
}
public long getQuorum() {
return evaluatedProposal.getProposalVoteResult().getQuorum();
}
private static String getProposalDetails(EvaluatedProposal evaluatedProposal, BsqFormatter bsqFormatter) {
return getProposalDetails(evaluatedProposal, bsqFormatter, true);
}
private static String getProposalDetails(EvaluatedProposal evaluatedProposal,
BsqFormatter bsqFormatter,
boolean useDisplayString) {
Proposal proposal = evaluatedProposal.getProposal();
switch (proposal.getType()) {
case COMPENSATION_REQUEST:
CompensationProposal compensationProposal = (CompensationProposal) proposal;
Coin requestedBsq = evaluatedProposal.isAccepted() ? compensationProposal.getRequestedBsq() : Coin.ZERO;
return bsqFormatter.formatCoinWithCode(requestedBsq);
case REIMBURSEMENT_REQUEST:
ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
requestedBsq = evaluatedProposal.isAccepted() ? reimbursementProposal.getRequestedBsq() : Coin.ZERO;
return bsqFormatter.formatCoinWithCode(requestedBsq);
case CHANGE_PARAM:
ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal;
return useDisplayString ? changeParamProposal.getParam().getDisplayString() : changeParamProposal.getParam().name();
case BONDED_ROLE:
RoleProposal roleProposal = (RoleProposal) proposal;
Role role = roleProposal.getRole();
String name = role.getBondedRoleType().name();
return useDisplayString ? Res.get("dao.bond.bondedRoleType." + name) : name;
case CONFISCATE_BOND:
ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal;
// TODO add info to bond
return confiscateBondProposal.getTxId();
case GENERIC:
return proposal.getName();
case REMOVE_ASSET:
RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal;
return CurrencyUtil.getNameAndCode(removeAssetProposal.getTickerSymbol());
}
return "-";
}
}

View file

@ -1,89 +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.governance.result;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.governance.Cycle;
import bisq.core.dao.state.model.governance.DecryptedBallotsWithMerits;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.Proposal;
import java.util.List;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
class ResultsOfCycle {
private final Cycle cycle;
private final int cycleIndex;
private final int numVotes;
private final int numAcceptedVotes;
private final int numRejectedVotes;
private final long meritAndStake;
private final DaoStateService daoStateService;
private long cycleStartTime;
// All available proposals of cycle
private final List<Proposal> proposals;
// Proposals which ended up in voting
private final List<EvaluatedProposal> evaluatedProposals;
private final List<DecryptedBallotsWithMerits> decryptedVotesForCycle;
ResultsOfCycle(Cycle cycle,
int cycleIndex,
long cycleStartTime,
List<Proposal> proposals,
List<EvaluatedProposal> evaluatedProposals,
List<DecryptedBallotsWithMerits> decryptedVotesForCycle,
long meritAndStake,
DaoStateService daoStateService) {
this.cycle = cycle;
this.cycleIndex = cycleIndex;
this.cycleStartTime = cycleStartTime;
this.proposals = proposals;
this.evaluatedProposals = evaluatedProposals;
this.decryptedVotesForCycle = decryptedVotesForCycle;
numVotes = evaluatedProposals.stream()
.mapToInt(e -> e.getProposalVoteResult().getNumActiveVotes())
.sum();
numAcceptedVotes = evaluatedProposals.stream()
.mapToInt(e -> e.getProposalVoteResult().getNumActiveVotes())
.sum();
numRejectedVotes = evaluatedProposals.stream()
.mapToInt(e -> e.getProposalVoteResult().getNumRejectedVotes())
.sum();
this.meritAndStake = meritAndStake;
this.daoStateService = daoStateService;
}
public long getCycleStartTime() {
// At a new cycle we have cycleStartTime 0 as the block is not processed yet.
// To display a correct value we access again from the daoStateService
if (cycleStartTime == 0)
cycleStartTime = daoStateService.getBlockAtHeight(cycle.getHeightOfFirstBlock())
.map(Block::getTime)
.orElse(0L);
return cycleStartTime;
}
}

View file

@ -1,101 +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.governance.result;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.DecryptedBallotsWithMerits;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Coin;
import de.jensd.fx.fontawesome.AwesomeIcon;
import java.util.Date;
import java.util.Optional;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VoteListItem {
@Getter
private final BsqFormatter bsqFormatter;
private final DecryptedBallotsWithMerits decryptedBallotsWithMerits;
private final String proposalTxId;
private long merit;
private long stake;
@Getter
private String blindVoteTxId = "";
@Getter
private String voteRevealTxId = "";
@Getter
private Date blindVoteDate;
VoteListItem(Proposal proposal,
DecryptedBallotsWithMerits decryptedBallotsWithMerits,
DaoStateService daoStateService,
BsqFormatter bsqFormatter) {
this.decryptedBallotsWithMerits = decryptedBallotsWithMerits;
this.bsqFormatter = bsqFormatter;
proposalTxId = proposal.getTxId();
if (decryptedBallotsWithMerits != null) {
merit = decryptedBallotsWithMerits.getMerit(daoStateService);
stake = decryptedBallotsWithMerits.getStake();
blindVoteTxId = decryptedBallotsWithMerits.getBlindVoteTxId();
daoStateService.getTx(blindVoteTxId).ifPresent(tx -> blindVoteDate = new Date(tx.getTime()));
voteRevealTxId = decryptedBallotsWithMerits.getVoteRevealTxId();
}
}
public Tuple2<AwesomeIcon, String> getIconStyleTuple() {
Optional<Boolean> isAccepted;
isAccepted = decryptedBallotsWithMerits.getBallotList().stream()
.filter(ballot -> ballot.getTxId().equals(proposalTxId))
.map(Ballot::getVote)
.map(vote -> vote != null && vote.isAccepted())
.findAny();
if (isAccepted.isPresent()) {
if (isAccepted.get())
return new Tuple2<>(AwesomeIcon.THUMBS_UP, "dao-accepted-icon");
else
return new Tuple2<>(AwesomeIcon.THUMBS_DOWN, "dao-rejected-icon");
} else {
return new Tuple2<>(AwesomeIcon.MINUS, "dao-ignored-icon");
}
}
public String getMerit() {
return bsqFormatter.formatCoinWithCode(Coin.valueOf(merit));
}
public String getStake() {
return bsqFormatter.formatCoinWithCode(Coin.valueOf(stake));
}
public String getMeritAndStake() {
return bsqFormatter.formatCoinWithCode(Coin.valueOf(stake + merit));
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.governance.result.VoteResultView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,37 +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.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.MonitorView"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="15"/>
<ScrollPane fitToWidth="true" hbarPolicy="NEVER"
fitToHeight="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
</ScrollPane>
</AnchorPane>

View file

@ -1,131 +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.monitor;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.monitor.blindvotes.BlindVoteStateMonitorView;
import bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView;
import bisq.desktop.main.dao.monitor.proposals.ProposalStateMonitorView;
import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
@FxmlView
public class MonitorView extends ActivatableView<AnchorPane, Void> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem daoState, proposals, blindVotes;
private Navigation.Listener navigationListener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
private ToggleGroup toggleGroup;
@Inject
private MonitorView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
navigationListener = (viewPath, data) -> {
if (viewPath.size() != 4 || viewPath.indexOf(MonitorView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
toggleGroup = new ToggleGroup();
List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, MonitorView.class);
daoState = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.daoState"),
DaoStateMonitorView.class, baseNavPath);
proposals = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.proposals"),
ProposalStateMonitorView.class, baseNavPath);
blindVotes = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.blindVotes"),
BlindVoteStateMonitorView.class, baseNavPath);
leftVBox.getChildren().addAll(daoState, proposals, blindVotes);
}
@Override
protected void activate() {
proposals.activate();
blindVotes.activate();
daoState.activate();
navigation.addListener(navigationListener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(MonitorView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = DaoStateMonitorView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(MonitorView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@SuppressWarnings("Duplicates")
@Override
protected void deactivate() {
navigation.removeListener(navigationListener);
proposals.deactivate();
blindVotes.deactivate();
daoState.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof DaoStateMonitorView) toggleGroup.selectToggle(daoState);
else if (view instanceof ProposalStateMonitorView) toggleGroup.selectToggle(proposals);
else if (view instanceof BlindVoteStateMonitorView) toggleGroup.selectToggle(blindVotes);
}
}

View file

@ -1,67 +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.monitor;
import bisq.core.dao.monitoring.model.StateBlock;
import bisq.core.dao.monitoring.model.StateHash;
import bisq.core.locale.Res;
import bisq.common.util.Utilities;
import com.google.common.base.Suppliers;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
@EqualsAndHashCode
public abstract class StateBlockListItem<StH extends StateHash, StB extends StateBlock<StH>> {
private final StateBlock<StH> stateBlock;
private final Supplier<String> height;
private final String hash;
private final String prevHash;
private final String numNetworkMessages;
private final String numMisMatches;
private final boolean isInSync;
public String getHeight() {
return height.get();
}
protected StateBlockListItem(StB stateBlock, int cycleIndex) {
this(stateBlock, () -> cycleIndex);
}
protected StateBlockListItem(StB stateBlock, IntSupplier cycleIndexSupplier) {
this.stateBlock = stateBlock;
height = Suppliers.memoize(() ->
Res.get("dao.monitor.table.cycleBlockHeight", cycleIndexSupplier.getAsInt() + 1,
String.valueOf(stateBlock.getHeight())))::get;
hash = Utilities.bytesAsHexString(stateBlock.getHash());
prevHash = stateBlock.getPrevHash().length > 0 ? Utilities.bytesAsHexString(stateBlock.getPrevHash()) : "-";
numNetworkMessages = String.valueOf(stateBlock.getPeersMap().size());
int size = stateBlock.getInConflictMap().size();
numMisMatches = String.valueOf(size);
isInSync = size == 0;
}
}

View file

@ -1,58 +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.monitor;
import bisq.core.dao.monitoring.model.StateHash;
import bisq.core.locale.Res;
import bisq.network.p2p.NodeAddress;
import bisq.common.util.Utilities;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
@EqualsAndHashCode
public abstract class StateInConflictListItem<T extends StateHash> {
private final String peerAddress;
private final String peerAddressString;
private final String height;
private final String hash;
private final String prevHash;
private final T stateHash;
protected StateInConflictListItem(String peerAddress, T stateHash, int cycleIndex,
Set<NodeAddress> seedNodeAddresses) {
this.stateHash = stateHash;
this.peerAddress = peerAddress;
this.peerAddressString = seedNodeAddresses.stream().anyMatch(e -> e.getFullAddress().equals(peerAddress)) ?
Res.get("dao.monitor.table.seedPeers", peerAddress) :
peerAddress;
height = Res.get("dao.monitor.table.cycleBlockHeight",
cycleIndex + 1,
String.valueOf(stateHash.getHeight()));
hash = Utilities.bytesAsHexString(stateHash.getHash());
prevHash = stateHash.getPrevHash().length > 0 ?
Utilities.bytesAsHexString(stateHash.getPrevHash()) : "-";
}
}

View file

@ -1,604 +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.monitor;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.TableGroupHeadline;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.period.CycleService;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.monitoring.model.StateBlock;
import bisq.core.dao.monitoring.model.StateHash;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.locale.Res;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.seed.SeedNodeRepository;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.geometry.Insets;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.io.File;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public abstract class StateMonitorView<StH extends StateHash,
StB extends StateBlock<StH>,
BLI extends StateBlockListItem<StH, StB>,
CLI extends StateInConflictListItem<StH>>
extends ActivatableView<GridPane, Void> implements DaoStateListener {
protected final DaoStateService daoStateService;
protected final DaoFacade daoFacade;
protected final CycleService cycleService;
protected final PeriodService periodService;
protected final Set<NodeAddress> seedNodeAddresses;
private final File storageDir;
protected TextField statusTextField;
protected Button resyncButton;
protected TableView<BLI> tableView;
protected TableView<CLI> conflictTableView;
protected final ObservableList<BLI> listItems = FXCollections.observableArrayList();
private final SortedList<BLI> sortedList = new SortedList<>(listItems);
private final ObservableList<CLI> conflictListItems = FXCollections.observableArrayList();
private final SortedList<CLI> sortedConflictList = new SortedList<>(conflictListItems);
protected int gridRow = 0;
private Subscription selectedItemSubscription;
protected final BooleanProperty isInConflictWithNonSeedNode = new SimpleBooleanProperty();
protected final BooleanProperty isInConflictWithSeedNode = new SimpleBooleanProperty();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
protected StateMonitorView(DaoStateService daoStateService,
DaoFacade daoFacade,
CycleService cycleService,
PeriodService periodService,
SeedNodeRepository seedNodeRepository,
File storageDir) {
this.daoStateService = daoStateService;
this.daoFacade = daoFacade;
this.cycleService = cycleService;
this.periodService = periodService;
this.seedNodeAddresses = new HashSet<>(seedNodeRepository.getSeedNodeAddresses());
this.storageDir = storageDir;
}
@Override
public void initialize() {
createTableView();
createDetailsView();
}
@Override
protected void activate() {
selectedItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectItem);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
sortedConflictList.comparatorProperty().bind(conflictTableView.comparatorProperty());
daoStateService.addDaoStateListener(this);
resyncButton.visibleProperty().bind(isInConflictWithSeedNode);
resyncButton.managedProperty().bind(isInConflictWithSeedNode);
resyncButton.setOnAction(ev -> resyncDaoState());
if (daoStateService.isParseBlockChainComplete()) {
onDataUpdate();
}
GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
GUIUtil.setFitToRowsForTableView(conflictTableView, 38, 28, 2, 4);
}
@Override
protected void deactivate() {
selectedItemSubscription.unsubscribe();
sortedList.comparatorProperty().unbind();
sortedConflictList.comparatorProperty().unbind();
daoStateService.removeDaoStateListener(this);
resyncButton.visibleProperty().unbind();
resyncButton.managedProperty().unbind();
resyncButton.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Abstract
///////////////////////////////////////////////////////////////////////////////////////////
protected abstract BLI getStateBlockListItem(StB e);
protected abstract CLI getStateInConflictListItem(Map.Entry<String, StH> mapEntry);
protected abstract void requestHashesFromGenesisBlockHeight(String peerAddress);
protected abstract String getConflictsTableHeader();
protected abstract String getPeersTableHeader();
protected abstract String getPrevHashTableHeader();
protected abstract String getHashTableHeader();
protected abstract String getBlockHeightTableHeader();
protected abstract String getRequestHashes();
protected abstract String getTableHeadLine();
protected abstract String getConflictTableHeadLine();
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockChainComplete() {
onDataUpdate();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Create table views
///////////////////////////////////////////////////////////////////////////////////////////
private void createTableView() {
TableGroupHeadline headline = new TableGroupHeadline(getTableHeadLine());
GridPane.setRowIndex(headline, ++gridRow);
GridPane.setMargin(headline, new Insets(Layout.GROUP_DISTANCE, -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.setPrefHeight(100);
createColumns();
GridPane.setRowIndex(tableView, gridRow);
GridPane.setHgrow(tableView, Priority.ALWAYS);
GridPane.setVgrow(tableView, Priority.SOMETIMES);
GridPane.setMargin(tableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, -25, -10));
root.getChildren().add(tableView);
tableView.setItems(sortedList);
}
private void createDetailsView() {
TableGroupHeadline conflictTableHeadline = new TableGroupHeadline(getConflictTableHeadLine());
GridPane.setRowIndex(conflictTableHeadline, ++gridRow);
GridPane.setMargin(conflictTableHeadline, new Insets(Layout.GROUP_DISTANCE, -10, -10, -10));
root.getChildren().add(conflictTableHeadline);
conflictTableView = new TableView<>();
conflictTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
conflictTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
conflictTableView.setPrefHeight(100);
createConflictColumns();
GridPane.setRowIndex(conflictTableView, gridRow);
GridPane.setHgrow(conflictTableView, Priority.ALWAYS);
GridPane.setVgrow(conflictTableView, Priority.SOMETIMES);
GridPane.setMargin(conflictTableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, 5, -10));
root.getChildren().add(conflictTableView);
conflictTableView.setItems(sortedConflictList);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handler
///////////////////////////////////////////////////////////////////////////////////////////
private void onSelectItem(BLI item) {
if (item != null) {
conflictListItems.setAll(item.getStateBlock().getInConflictMap().entrySet().stream()
.map(this::getStateInConflictListItem).collect(Collectors.toList()));
GUIUtil.setFitToRowsForTableView(conflictTableView, 38, 28, 2, 4);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
protected void onDataUpdate() {
if (isInConflictWithSeedNode.get()) {
String msg = Res.get("dao.monitor.isInConflictWithSeedNode");
log.warn(msg);
statusTextField.setText(msg);
statusTextField.getStyleClass().add("dao-inConflict");
} else if (isInConflictWithNonSeedNode.get()) {
statusTextField.setText(Res.get("dao.monitor.isInConflictWithNonSeedNode"));
statusTextField.getStyleClass().remove("dao-inConflict");
} else {
statusTextField.setText(Res.get("dao.monitor.daoStateInSync"));
statusTextField.getStyleClass().remove("dao-inConflict");
}
GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
}
private void resyncDaoState() {
try {
daoFacade.resyncDaoStateFromResources(storageDir);
new Popup().attention(Res.get("setting.preferences.dao.resyncFromResources.popup"))
.useShutDownButton()
.hideCloseButton()
.show();
} catch (Throwable t) {
t.printStackTrace();
log.error(t.toString());
new Popup().error(t.toString()).show();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns
///////////////////////////////////////////////////////////////////////////////////////////
protected void createColumns() {
TableColumn<BLI, BLI> column;
column = new AutoTooltipTableColumn<>(getBlockHeightTableHeader());
column.setMinWidth(120);
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BLI, BLI> call(
TableColumn<BLI, BLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final BLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getHeight());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getHeight()));
column.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(column);
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(getHashTableHeader());
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BLI, BLI> call(
TableColumn<BLI, BLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final BLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getHash());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(BLI::getHash));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BLI, BLI> call(TableColumn<BLI,
BLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final BLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getPrevHash());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(BLI::getPrevHash));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(getPeersTableHeader());
column.setMinWidth(80);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BLI, BLI> call(
TableColumn<BLI, BLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final BLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getNumNetworkMessages());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getPeersMap().size()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(getConflictsTableHeader());
column.setMinWidth(80);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BLI, BLI> call(
TableColumn<BLI, BLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final BLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getNumMisMatches());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getInConflictMap().size()));
tableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>("");
column.setMinWidth(40);
column.setMaxWidth(column.getMinWidth());
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BLI, BLI> call(
TableColumn<BLI, BLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final BLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
Label icon;
if (!item.getStateBlock().getPeersMap().isEmpty()) {
if (item.isInSync()) {
icon = FormBuilder.getIcon(AwesomeIcon.OK_CIRCLE);
icon.getStyleClass().addAll("icon", "dao-inSync");
} else {
icon = FormBuilder.getIcon(AwesomeIcon.REMOVE_CIRCLE);
icon.getStyleClass().addAll("icon", "dao-inConflict");
}
setGraphic(icon);
} else {
setGraphic(null);
}
} else {
setGraphic(null);
}
}
};
}
});
column.setSortable(false);
tableView.getColumns().add(column);
}
protected void createConflictColumns() {
TableColumn<CLI, CLI> column;
column = new AutoTooltipTableColumn<>(getBlockHeightTableHeader());
column.setMinWidth(120);
column.getStyleClass().add("first-column");
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<CLI, CLI> call(
TableColumn<CLI, CLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final CLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getHeight());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateHash().getHeight()));
column.setSortType(TableColumn.SortType.DESCENDING);
conflictTableView.getColumns().add(column);
conflictTableView.getSortOrder().add(column);
column = new AutoTooltipTableColumn<>(getPeersTableHeader());
column.setMinWidth(150);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<CLI, CLI> call(
TableColumn<CLI, CLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final CLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getPeerAddressString());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(CLI::getPeerAddress));
conflictTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(getHashTableHeader());
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<CLI, CLI> call(
TableColumn<CLI, CLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final CLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getHash());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(CLI::getHash));
conflictTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<CLI, CLI> call(
TableColumn<CLI, CLI> column) {
return new TableCell<>() {
@Override
public void updateItem(final CLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getPrevHash());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(CLI::getPrevHash));
conflictTableView.getColumns().add(column);
column = new AutoTooltipTableColumn<>("");
column.setMinWidth(120);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<CLI, CLI> call(
TableColumn<CLI, CLI> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final CLI item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = new AutoTooltipButton(getRequestHashes());
setGraphic(button);
}
button.setOnAction(e -> requestHashesFromGenesisBlockHeight(item.getPeerAddress()));
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
column.setSortable(false);
conflictTableView.getColumns().add(column);
}
}

View file

@ -1,40 +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.monitor.blindvotes;
import bisq.desktop.main.dao.monitor.StateBlockListItem;
import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
@EqualsAndHashCode(callSuper = true)
class BlindVoteStateBlockListItem extends StateBlockListItem<BlindVoteStateHash, BlindVoteStateBlock> {
private final String numBlindVotes;
BlindVoteStateBlockListItem(BlindVoteStateBlock stateBlock, int cycleIndex) {
super(stateBlock, cycleIndex);
numBlindVotes = String.valueOf(stateBlock.getNumBlindVotes());
}
}

View file

@ -1,44 +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.monitor.blindvotes;
import bisq.desktop.main.dao.monitor.StateInConflictListItem;
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
import bisq.network.p2p.NodeAddress;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
@EqualsAndHashCode(callSuper = true)
class BlindVoteStateInConflictListItem extends StateInConflictListItem<BlindVoteStateHash> {
private final String numBlindVotes;
BlindVoteStateInConflictListItem(String peerAddress, BlindVoteStateHash stateHash, int cycleIndex,
Set<NodeAddress> seedNodeAddresses) {
super(peerAddress, stateHash, cycleIndex, seedNodeAddresses);
numBlindVotes = String.valueOf(stateHash.getNumBlindVotes());
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.blindvotes.BlindVoteStateMonitorView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,253 +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.monitor.blindvotes;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.main.dao.monitor.StateMonitorView;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.period.CycleService;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.monitoring.BlindVoteStateMonitoringService;
import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
import bisq.core.dao.monitoring.model.BlindVoteStateHash;
import bisq.core.dao.state.DaoStateService;
import bisq.core.locale.Res;
import bisq.network.p2p.seed.SeedNodeRepository;
import bisq.common.config.Config;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.util.Callback;
import java.io.File;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;
@FxmlView
public class BlindVoteStateMonitorView extends StateMonitorView<BlindVoteStateHash, BlindVoteStateBlock, BlindVoteStateBlockListItem, BlindVoteStateInConflictListItem>
implements BlindVoteStateMonitoringService.Listener {
private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BlindVoteStateMonitorView(DaoStateService daoStateService,
DaoFacade daoFacade,
BlindVoteStateMonitoringService blindVoteStateMonitoringService,
CycleService cycleService,
PeriodService periodService,
SeedNodeRepository seedNodeRepository,
@Named(Config.STORAGE_DIR) File storageDir) {
super(daoStateService, daoFacade, cycleService, periodService, seedNodeRepository, storageDir);
this.blindVoteStateMonitoringService = blindVoteStateMonitoringService;
}
@Override
public void initialize() {
FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.blindVote.headline"));
statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
Res.get("dao.monitor.state")).second;
resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
super.initialize();
}
@Override
protected void activate() {
super.activate();
blindVoteStateMonitoringService.addListener(this);
}
@Override
protected void deactivate() {
super.deactivate();
blindVoteStateMonitoringService.removeListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// BlindVoteStateMonitoringService.Listener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onBlindVoteStateBlockChainChanged() {
if (daoStateService.isParseBlockChainComplete()) {
onDataUpdate();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Implementation abstract methods
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected BlindVoteStateBlockListItem getStateBlockListItem(BlindVoteStateBlock daoStateBlock) {
int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
return new BlindVoteStateBlockListItem(daoStateBlock, cycleIndex);
}
@Override
protected BlindVoteStateInConflictListItem getStateInConflictListItem(Map.Entry<String, BlindVoteStateHash> mapEntry) {
BlindVoteStateHash blindVoteStateHash = mapEntry.getValue();
int cycleIndex = periodService.getCycle(blindVoteStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
return new BlindVoteStateInConflictListItem(mapEntry.getKey(), mapEntry.getValue(), cycleIndex, seedNodeAddresses);
}
@Override
protected String getTableHeadLine() {
return Res.get("dao.monitor.blindVote.table.headline");
}
@Override
protected String getConflictTableHeadLine() {
return Res.get("dao.monitor.blindVote.conflictTable.headline");
}
@Override
protected String getConflictsTableHeader() {
return Res.get("dao.monitor.table.conflicts");
}
@Override
protected String getPeersTableHeader() {
return Res.get("dao.monitor.table.peers");
}
@Override
protected String getPrevHashTableHeader() {
return Res.get("dao.monitor.blindVote.table.prev");
}
@Override
protected String getHashTableHeader() {
return Res.get("dao.monitor.blindVote.table.hash");
}
@Override
protected String getBlockHeightTableHeader() {
return Res.get("dao.monitor.table.header.cycleBlockHeight");
}
@Override
protected String getRequestHashes() {
return Res.get("dao.monitor.requestAlHashes");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Override
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onDataUpdate() {
isInConflictWithSeedNode.set(blindVoteStateMonitoringService.isInConflictWithSeedNode());
isInConflictWithNonSeedNode.set(blindVoteStateMonitoringService.isInConflictWithNonSeedNode());
listItems.setAll(blindVoteStateMonitoringService.getBlindVoteStateBlockChain().stream()
.map(this::getStateBlockListItem)
.collect(Collectors.toList()));
super.onDataUpdate();
}
@Override
protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
blindVoteStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
}
@Override
protected void createColumns() {
super.createColumns();
TableColumn<BlindVoteStateBlockListItem, BlindVoteStateBlockListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.blindVote.table.numBlindVotes"));
column.setMinWidth(90);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BlindVoteStateBlockListItem, BlindVoteStateBlockListItem> call(
TableColumn<BlindVoteStateBlockListItem, BlindVoteStateBlockListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(BlindVoteStateBlockListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getNumBlindVotes());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getMyStateHash().getNumBlindVotes()));
tableView.getColumns().add(1, column);
}
protected void createConflictColumns() {
super.createConflictColumns();
TableColumn<BlindVoteStateInConflictListItem, BlindVoteStateInConflictListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.blindVote.table.numBlindVotes"));
column.setMinWidth(90);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<BlindVoteStateInConflictListItem, BlindVoteStateInConflictListItem> call(
TableColumn<BlindVoteStateInConflictListItem, BlindVoteStateInConflictListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(BlindVoteStateInConflictListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getNumBlindVotes());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateHash().getNumBlindVotes()));
conflictTableView.getColumns().add(1, column);
}
}

View file

@ -1,38 +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.monitor.daostate;
import bisq.desktop.main.dao.monitor.StateBlockListItem;
import bisq.core.dao.monitoring.model.DaoStateBlock;
import bisq.core.dao.monitoring.model.DaoStateHash;
import java.util.function.IntSupplier;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
@EqualsAndHashCode(callSuper = true)
class DaoStateBlockListItem extends StateBlockListItem<DaoStateHash, DaoStateBlock> {
DaoStateBlockListItem(DaoStateBlock stateBlock, IntSupplier cycleIndexSupplier) {
super(stateBlock, cycleIndexSupplier);
}
}

View file

@ -1,40 +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.monitor.daostate;
import bisq.desktop.main.dao.monitor.StateInConflictListItem;
import bisq.core.dao.monitoring.model.DaoStateHash;
import bisq.network.p2p.NodeAddress;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
@EqualsAndHashCode(callSuper = true)
class DaoStateInConflictListItem extends StateInConflictListItem<DaoStateHash> {
DaoStateInConflictListItem(String peerAddress, DaoStateHash stateHash, int cycleIndex,
Set<NodeAddress> seedNodeAddresses) {
super(peerAddress, stateHash, cycleIndex, seedNodeAddresses);
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,227 +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.monitor.daostate;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.main.dao.monitor.StateMonitorView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.period.CycleService;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.dao.monitoring.model.DaoStateBlock;
import bisq.core.dao.monitoring.model.DaoStateHash;
import bisq.core.dao.monitoring.model.UtxoMismatch;
import bisq.core.dao.state.DaoStateService;
import bisq.core.locale.Res;
import bisq.network.p2p.seed.SeedNodeRepository;
import bisq.common.config.Config;
import bisq.common.util.Utilities;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.collections.ListChangeListener;
import java.io.File;
import java.util.Map;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
@FxmlView
public class DaoStateMonitorView extends StateMonitorView<DaoStateHash, DaoStateBlock, DaoStateBlockListItem, DaoStateInConflictListItem>
implements DaoStateMonitoringService.Listener {
private final DaoStateMonitoringService daoStateMonitoringService;
private ListChangeListener<UtxoMismatch> utxoMismatchListChangeListener;
private Popup warningPopup;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private DaoStateMonitorView(DaoStateService daoStateService,
DaoFacade daoFacade,
DaoStateMonitoringService daoStateMonitoringService,
CycleService cycleService,
PeriodService periodService,
SeedNodeRepository seedNodeRepository,
@Named(Config.STORAGE_DIR) File storageDir) {
super(daoStateService, daoFacade, cycleService, periodService, seedNodeRepository, storageDir);
this.daoStateMonitoringService = daoStateMonitoringService;
}
@Override
public void initialize() {
utxoMismatchListChangeListener = c -> updateUtxoMismatches();
FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.daoState.headline"));
statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
Res.get("dao.monitor.state")).second;
resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
super.initialize();
}
@Override
protected void activate() {
super.activate();
daoStateMonitoringService.addListener(this);
daoStateMonitoringService.getUtxoMismatches().addListener(utxoMismatchListChangeListener);
updateUtxoMismatches();
}
@Override
protected void deactivate() {
super.deactivate();
daoStateMonitoringService.removeListener(this);
daoStateMonitoringService.getUtxoMismatches().removeListener(utxoMismatchListChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateMonitoringService.Listener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onChangeAfterBatchProcessing() {
if (daoStateService.isParseBlockChainComplete()) {
onDataUpdate();
}
}
@Override
public void onCheckpointFail() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Implementation abstract methods
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected DaoStateBlockListItem getStateBlockListItem(DaoStateBlock daoStateBlock) {
IntSupplier cycleIndexSupplier = () -> periodService.getCycle(daoStateBlock.getHeight())
.map(cycleService::getCycleIndex)
.orElse(0);
return new DaoStateBlockListItem(daoStateBlock, cycleIndexSupplier);
}
@Override
protected DaoStateInConflictListItem getStateInConflictListItem(Map.Entry<String, DaoStateHash> mapEntry) {
DaoStateHash daoStateHash = mapEntry.getValue();
int cycleIndex = periodService.getCycle(daoStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
return new DaoStateInConflictListItem(mapEntry.getKey(), daoStateHash, cycleIndex, seedNodeAddresses);
}
@Override
protected String getTableHeadLine() {
return Res.get("dao.monitor.daoState.table.headline");
}
@Override
protected String getConflictTableHeadLine() {
return Res.get("dao.monitor.daoState.conflictTable.headline");
}
@Override
protected String getConflictsTableHeader() {
return Res.get("dao.monitor.table.conflicts");
}
@Override
protected String getPeersTableHeader() {
return Res.get("dao.monitor.table.peers");
}
@Override
protected String getPrevHashTableHeader() {
return Res.get("dao.monitor.daoState.table.prev");
}
@Override
protected String getHashTableHeader() {
return Res.get("dao.monitor.daoState.table.hash");
}
@Override
protected String getBlockHeightTableHeader() {
return Res.get("dao.monitor.daoState.table.blockHeight");
}
@Override
protected String getRequestHashes() {
return Res.get("dao.monitor.requestAlHashes");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Override
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onDataUpdate() {
isInConflictWithSeedNode.set(daoStateMonitoringService.isInConflictWithSeedNode());
isInConflictWithNonSeedNode.set(daoStateMonitoringService.isInConflictWithNonSeedNode());
listItems.setAll(daoStateMonitoringService.getDaoStateBlockChain().stream()
.map(this::getStateBlockListItem)
.collect(Collectors.toList()));
super.onDataUpdate();
}
@Override
protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
daoStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateUtxoMismatches() {
if (!daoStateMonitoringService.getUtxoMismatches().isEmpty()) {
StringBuilder sb = new StringBuilder();
daoStateMonitoringService.getUtxoMismatches().forEach(e -> sb.append("\n")
.append(Res.get("dao.monitor.daoState.utxoConflicts.blockHeight", e.getHeight())).append("\n")
.append(Res.get("dao.monitor.daoState.utxoConflicts.sumUtxo", e.getSumUtxo() / 100)).append("\n")
.append(Res.get("dao.monitor.daoState.utxoConflicts.sumBsq", e.getSumBsq() / 100))
);
if (warningPopup == null) {
warningPopup = new Popup().headLine(Res.get("dao.monitor.daoState.utxoConflicts"))
.warning(Utilities.toTruncatedString(sb.toString(), 500, false))
.onClose(() -> warningPopup = null);
warningPopup.show();
}
}
}
}

View file

@ -1,40 +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.monitor.proposals;
import bisq.desktop.main.dao.monitor.StateBlockListItem;
import bisq.core.dao.monitoring.model.ProposalStateBlock;
import bisq.core.dao.monitoring.model.ProposalStateHash;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
@EqualsAndHashCode(callSuper = true)
class ProposalStateBlockListItem extends StateBlockListItem<ProposalStateHash, ProposalStateBlock> {
private final String numProposals;
ProposalStateBlockListItem(ProposalStateBlock stateBlock, int cycleIndex) {
super(stateBlock, cycleIndex);
numProposals = String.valueOf(stateBlock.getNumProposals());
}
}

View file

@ -1,44 +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.monitor.proposals;
import bisq.desktop.main.dao.monitor.StateInConflictListItem;
import bisq.core.dao.monitoring.model.ProposalStateHash;
import bisq.network.p2p.NodeAddress;
import java.util.Set;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Value
@EqualsAndHashCode(callSuper = true)
class ProposalStateInConflictListItem extends StateInConflictListItem<ProposalStateHash> {
private final String numProposals;
ProposalStateInConflictListItem(String peerAddress, ProposalStateHash stateHash, int cycleIndex,
Set<NodeAddress> seedNodeAddresses) {
super(peerAddress, stateHash, cycleIndex, seedNodeAddresses);
numProposals = String.valueOf(stateHash.getNumProposals());
}
}

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.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.monitor.proposals.ProposalStateMonitorView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="100"/>
</columnConstraints>
</GridPane>

View file

@ -1,251 +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.monitor.proposals;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.main.dao.monitor.StateMonitorView;
import bisq.desktop.util.FormBuilder;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.period.CycleService;
import bisq.core.dao.governance.period.PeriodService;
import bisq.core.dao.monitoring.ProposalStateMonitoringService;
import bisq.core.dao.monitoring.model.ProposalStateBlock;
import bisq.core.dao.monitoring.model.ProposalStateHash;
import bisq.core.dao.state.DaoStateService;
import bisq.core.locale.Res;
import bisq.network.p2p.seed.SeedNodeRepository;
import bisq.common.config.Config;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.util.Callback;
import java.io.File;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;
@FxmlView
public class ProposalStateMonitorView extends StateMonitorView<ProposalStateHash, ProposalStateBlock, ProposalStateBlockListItem, ProposalStateInConflictListItem>
implements ProposalStateMonitoringService.Listener {
private final ProposalStateMonitoringService proposalStateMonitoringService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ProposalStateMonitorView(DaoStateService daoStateService,
DaoFacade daoFacade,
ProposalStateMonitoringService proposalStateMonitoringService,
CycleService cycleService,
PeriodService periodService,
SeedNodeRepository seedNodeRepository,
@Named(Config.STORAGE_DIR) File storageDir) {
super(daoStateService, daoFacade, cycleService, periodService, seedNodeRepository, storageDir);
this.proposalStateMonitoringService = proposalStateMonitoringService;
}
@Override
public void initialize() {
FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.proposal.headline"));
statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
Res.get("dao.monitor.state")).second;
resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
super.initialize();
}
@Override
protected void activate() {
super.activate();
proposalStateMonitoringService.addListener(this);
}
@Override
protected void deactivate() {
super.deactivate();
proposalStateMonitoringService.removeListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// ProposalStateMonitoringService.Listener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onProposalStateBlockChainChanged() {
if (daoStateService.isParseBlockChainComplete()) {
onDataUpdate();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Implementation abstract methods
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected ProposalStateBlockListItem getStateBlockListItem(ProposalStateBlock daoStateBlock) {
int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
return new ProposalStateBlockListItem(daoStateBlock, cycleIndex);
}
@Override
protected ProposalStateInConflictListItem getStateInConflictListItem(Map.Entry<String, ProposalStateHash> mapEntry) {
ProposalStateHash proposalStateHash = mapEntry.getValue();
int cycleIndex = periodService.getCycle(proposalStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
return new ProposalStateInConflictListItem(mapEntry.getKey(), mapEntry.getValue(), cycleIndex, seedNodeAddresses);
}
@Override
protected String getTableHeadLine() {
return Res.get("dao.monitor.proposal.table.headline");
}
@Override
protected String getConflictTableHeadLine() {
return Res.get("dao.monitor.proposal.conflictTable.headline");
}
@Override
protected String getConflictsTableHeader() {
return Res.get("dao.monitor.table.conflicts");
}
@Override
protected String getPeersTableHeader() {
return Res.get("dao.monitor.table.peers");
}
@Override
protected String getPrevHashTableHeader() {
return Res.get("dao.monitor.proposal.table.prev");
}
@Override
protected String getHashTableHeader() {
return Res.get("dao.monitor.proposal.table.hash");
}
@Override
protected String getBlockHeightTableHeader() {
return Res.get("dao.monitor.table.header.cycleBlockHeight");
}
@Override
protected String getRequestHashes() {
return Res.get("dao.monitor.requestAlHashes");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Override
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onDataUpdate() {
isInConflictWithSeedNode.set(proposalStateMonitoringService.isInConflictWithSeedNode());
isInConflictWithNonSeedNode.set(proposalStateMonitoringService.isInConflictWithNonSeedNode());
listItems.setAll(proposalStateMonitoringService.getProposalStateBlockChain().stream()
.map(this::getStateBlockListItem)
.collect(Collectors.toList()));
super.onDataUpdate();
}
@Override
protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
proposalStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
}
@Override
protected void createColumns() {
super.createColumns();
TableColumn<ProposalStateBlockListItem, ProposalStateBlockListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.proposal.table.numProposals"));
column.setMinWidth(110);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProposalStateBlockListItem, ProposalStateBlockListItem> call(
TableColumn<ProposalStateBlockListItem, ProposalStateBlockListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(ProposalStateBlockListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getNumProposals());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateBlock().getMyStateHash().getNumProposals()));
tableView.getColumns().add(1, column);
}
protected void createConflictColumns() {
super.createConflictColumns();
TableColumn<ProposalStateInConflictListItem, ProposalStateInConflictListItem> column;
column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.proposal.table.numProposals"));
column.setMinWidth(110);
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
column.setCellFactory(
new Callback<>() {
@Override
public TableCell<ProposalStateInConflictListItem, ProposalStateInConflictListItem> call(
TableColumn<ProposalStateInConflictListItem, ProposalStateInConflictListItem> column) {
return new TableCell<>() {
@Override
public void updateItem(ProposalStateInConflictListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getNumProposals());
else
setText("");
}
};
}
});
column.setComparator(Comparator.comparing(e -> e.getStateHash().getNumProposals()));
conflictTableView.getColumns().add(1, column);
}
}

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.HBox?>
<?import javafx.geometry.Insets?>
<HBox fx:id="root" fx:controller="bisq.desktop.main.dao.news.NewsView"
xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
</padding>
</HBox>

View file

@ -1,161 +0,0 @@
package bisq.desktop.main.dao.news;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.BsqAddressTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Tuple3;
import javax.inject.Inject;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.AnchorPane;
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.Insets;
import javafx.geometry.Orientation;
import static bisq.desktop.util.FormBuilder.*;
@FxmlView
public class NewsView extends ActivatableView<HBox, Void> {
private final BsqWalletService bsqWalletService;
private final BsqFormatter bsqFormatter;
private BsqAddressTextField addressTextField;
@Inject
private NewsView(BsqWalletService bsqWalletService, BsqFormatter bsqFormatter) {
this.bsqWalletService = bsqWalletService;
this.bsqFormatter = bsqFormatter;
}
@Override
protected void initialize() {
root.setSpacing(20);
AnchorPane bisqDAOPane = createBisqDAOContent();
HBox.setHgrow(bisqDAOPane, Priority.SOMETIMES);
Separator separator = new Separator();
separator.setOrientation(Orientation.VERTICAL);
HBox.setHgrow(separator, Priority.NEVER);
GridPane bisqDAOOnTestnetPane = createBisqDAOOnTestnetContent();
HBox.setHgrow(bisqDAOOnTestnetPane, Priority.SOMETIMES);
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
root.getChildren().addAll(bisqDAOPane, separator, bisqDAOOnTestnetPane, spacer);
}
private GridPane createBisqDAOOnTestnetContent() {
GridPane gridPane = new GridPane();
gridPane.setMaxWidth(370);
int rowIndex = 0;
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, rowIndex, 14, Res.get("dao.news.DAOOnTestnet.title"));
titledGroupBg.getStyleClass().addAll("last", "dao-news-titled-group");
Label daoTestnetDescription = addMultilineLabel(gridPane, ++rowIndex, Res.get("dao.news.DAOOnTestnet.description"), 0, 370);
GridPane.setMargin(daoTestnetDescription, new Insets(Layout.FLOATING_LABEL_DISTANCE, 0, 8, 0));
daoTestnetDescription.getStyleClass().add("dao-news-content");
rowIndex = addInfoSection(gridPane, rowIndex, Res.get("dao.news.DAOOnTestnet.firstSection.title"),
Res.get("dao.news.DAOOnTestnet.firstSection.content"),
"https://docs.bisq.network/getting-started-dao.html#switch-to-testnet-mode");
rowIndex = addInfoSection(gridPane, rowIndex, Res.get("dao.news.DAOOnTestnet.secondSection.title"),
Res.get("dao.news.DAOOnTestnet.secondSection.content"),
"https://docs.bisq.network/getting-started-dao.html#acquire-some-bsq");
rowIndex = addInfoSection(gridPane, rowIndex, Res.get("dao.news.DAOOnTestnet.thirdSection.title"),
Res.get("dao.news.DAOOnTestnet.thirdSection.content"),
"https://docs.bisq.network/getting-started-dao.html#participate-in-a-voting-cycle");
rowIndex = addInfoSection(gridPane, rowIndex, Res.get("dao.news.DAOOnTestnet.fourthSection.title"),
Res.get("dao.news.DAOOnTestnet.fourthSection.content"),
"https://docs.bisq.network/getting-started-dao.html#explore-a-bsq-block-explorer");
Hyperlink hyperlink = addHyperlinkWithIcon(gridPane, ++rowIndex, Res.get("dao.news.DAOOnTestnet.readMoreLink"),
"https://bisq.network/docs/dao");
hyperlink.getStyleClass().add("dao-news-link");
return gridPane;
}
private int addInfoSection(GridPane gridPane, int rowIndex, String title, String content, String linkURL) {
Label titleLabel = addLabel(gridPane, ++rowIndex, title);
GridPane.setMargin(titleLabel, new Insets(6, 0, 0, 0));
titleLabel.getStyleClass().add("dao-news-section-header");
Label contentLabel = addMultilineLabel(gridPane, ++rowIndex, content, -Layout.FLOATING_LABEL_DISTANCE, 370);
contentLabel.getStyleClass().add("dao-news-section-content");
Hyperlink link = addHyperlinkWithIcon(gridPane, ++rowIndex, "Read More", linkURL);
link.getStyleClass().add("dao-news-section-link");
GridPane.setMargin(link, new Insets(0, 0, 29, 0));
return rowIndex;
}
private AnchorPane createBisqDAOContent() {
AnchorPane anchorPane = new AnchorPane();
anchorPane.setMinWidth(373);
GridPane bisqDAOPane = new GridPane();
AnchorPane.setTopAnchor(bisqDAOPane, 0d);
bisqDAOPane.setVgap(5);
bisqDAOPane.setMaxWidth(373);
int rowIndex = 0;
TitledGroupBg theBisqDaoTitledGroup = addTitledGroupBg(bisqDAOPane, rowIndex, 3, Res.get("dao.news.bisqDAO.title"));
theBisqDaoTitledGroup.getStyleClass().addAll("last", "dao-news-titled-group");
Label daoTeaserContent = addMultilineLabel(bisqDAOPane, ++rowIndex, Res.get("dao.news.bisqDAO.description"));
daoTeaserContent.getStyleClass().add("dao-news-teaser");
Hyperlink hyperlink = addHyperlinkWithIcon(bisqDAOPane, ++rowIndex, Res.get("dao.news.bisqDAO.readMoreLink"), "https://bisq.network/docs/dao");
hyperlink.getStyleClass().add("dao-news-link");
GridPane pastContributorsPane = new GridPane();
AnchorPane.setBottomAnchor(pastContributorsPane, 0d);
pastContributorsPane.setVgap(5);
pastContributorsPane.setMaxWidth(373);
rowIndex = 0;
TitledGroupBg contributorsTitledGroup = addTitledGroupBg(pastContributorsPane, rowIndex, 4, Res.get("dao.news.pastContribution.title"));
contributorsTitledGroup.getStyleClass().addAll("last", "dao-news-titled-group");
Label pastContributionDescription = addMultilineLabel(pastContributorsPane, ++rowIndex, Res.get("dao.news.pastContribution.description"));
pastContributionDescription.getStyleClass().add("dao-news-content");
Tuple3<Label, BsqAddressTextField, VBox> tuple = addLabelBsqAddressTextField(pastContributorsPane, ++rowIndex,
Res.get("dao.news.pastContribution.yourAddress"),
Layout.FIRST_ROW_DISTANCE);
addressTextField = tuple.second;
Button requestNowButton = addPrimaryActionButton(pastContributorsPane, ++rowIndex, Res.get("dao.news.pastContribution.requestNow"), 0);
requestNowButton.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(requestNowButton, Priority.ALWAYS);
requestNowButton.setOnAction(e -> GUIUtil.openWebPage("https://bisq.network/docs/dao/genesis"));
anchorPane.getChildren().addAll(bisqDAOPane, pastContributorsPane);
return anchorPane;
}
@Override
protected void activate() {
addressTextField.setAddress(bsqFormatter.getBsqAddressStringFromAddress(bsqWalletService.getUnusedAddress()));
}
@Override
protected void deactivate() {
}
}

View file

@ -1,202 +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.wallet;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.Layout;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import lombok.extern.slf4j.Slf4j;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@Slf4j
public class BsqBalanceUtil implements BsqBalanceListener, DaoStateListener {
private final BsqWalletService bsqWalletService;
private final DaoStateService daoStateService;
private final BsqFormatter bsqFormatter;
private final DaoFacade daoFacade;
// Displaying general BSQ info
private TextField availableBalanceTextField, verifiedBalanceTextField, availableNonBsqBalanceTextField,
unverifiedBalanceTextField, lockedForVoteBalanceTextField,
lockedInBondsBalanceTextField, unconfirmedChangTextField, reputationBalanceTextField;
// Displaying bond dashboard info
private TextField lockupAmountTextField, unlockingAmountTextField;
private Label availableNonBsqBalanceLabel;
@Inject
private BsqBalanceUtil(BsqWalletService bsqWalletService,
DaoStateService daoStateService,
BsqFormatter bsqFormatter,
DaoFacade daoFacade) {
this.bsqWalletService = bsqWalletService;
this.daoStateService = daoStateService;
this.bsqFormatter = bsqFormatter;
this.daoFacade = daoFacade;
}
public void activate() {
bsqWalletService.addBsqBalanceListener(this);
daoStateService.addDaoStateListener(this);
triggerUpdate();
}
public void deactivate() {
bsqWalletService.removeBsqBalanceListener(this);
daoStateService.removeDaoStateListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// DaoStateListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) {
bsqWalletService.addBsqBalanceListener(this);
triggerUpdate();
}
///////////////////////////////////////////////////////////////////////////////////////////
// BsqBalanceListener
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onUpdateBalances(Coin availableBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
boolean isNonBsqBalanceAvailable = availableNonBsqBalance.value > 0;
availableBalanceTextField.setText(bsqFormatter.formatCoinWithCode(availableBalance));
Coin verified = availableBalance.subtract(unconfirmedChangeBalance);
verifiedBalanceTextField.setText(bsqFormatter.formatCoinWithCode(verified));
unconfirmedChangTextField.setText(bsqFormatter.formatCoinWithCode(unconfirmedChangeBalance));
unverifiedBalanceTextField.setText(bsqFormatter.formatCoinWithCode(unverifiedBalance));
lockedForVoteBalanceTextField.setText(bsqFormatter.formatCoinWithCode(lockedForVotingBalance));
lockedInBondsBalanceTextField.setText(bsqFormatter.formatCoinWithCode(
lockupBondsBalance.add(unlockingBondsBalance)));
if (lockupAmountTextField != null && unlockingAmountTextField != null) {
lockupAmountTextField.setText(bsqFormatter.formatCoinWithCode(lockupBondsBalance));
unlockingAmountTextField.setText(bsqFormatter.formatCoinWithCode(unlockingBondsBalance));
}
availableNonBsqBalanceLabel.setVisible(isNonBsqBalanceAvailable);
availableNonBsqBalanceLabel.setManaged(isNonBsqBalanceAvailable);
availableNonBsqBalanceTextField.setVisible(isNonBsqBalanceAvailable);
availableNonBsqBalanceTextField.setManaged(isNonBsqBalanceAvailable);
availableNonBsqBalanceTextField.setText(bsqFormatter.formatBTCWithCode(availableNonBsqBalance.value));
String bsqSatoshi = bsqFormatter.formatBSQSatoshisWithCode(daoFacade.getAvailableMerit());
reputationBalanceTextField.setText(bsqSatoshi);
}
private void triggerUpdate() {
onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(),
bsqWalletService.getAvailableNonBsqBalance(),
bsqWalletService.getUnverifiedBalance(),
bsqWalletService.getUnconfirmedChangeBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockupBondsBalance(),
bsqWalletService.getUnlockingBondsBalance());
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public int addGroup(GridPane gridPane, int gridRow) {
int startIndex = gridRow;
addTitledGroupBg(gridPane, gridRow, 4, Res.get("dao.wallet.dashboard.myBalance"));
availableBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, gridRow,
Res.get("dao.availableBsqBalance"), Layout.FIRST_ROW_DISTANCE).second;
verifiedBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow,
Res.get("dao.verifiedBsqBalance")).second;
unconfirmedChangTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow,
Res.get("dao.unconfirmedChangeBalance")).second;
unverifiedBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow,
Res.get("dao.unverifiedBsqBalance")).second;
gridRow = startIndex;
int columnIndex = 2;
addTitledGroupBg(gridPane, gridRow, columnIndex, 4, "");
lockedForVoteBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, gridRow, columnIndex,
Res.get("dao.lockedForVoteBalance"), Layout.FIRST_ROW_DISTANCE).second;
lockedInBondsBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow, columnIndex,
Res.get("dao.lockedInBonds")).second;
reputationBalanceTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow, columnIndex,
Res.get("dao.reputationBalance")).second;
Tuple3<Label, TextField, VBox> tuple3 = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow, columnIndex,
Res.get("dao.availableNonBsqBalance"));
// Match left column
++gridRow;
// TODO add unlockingBondsBalanceTextField
availableNonBsqBalanceLabel = tuple3.first;
availableNonBsqBalanceTextField = tuple3.second;
availableNonBsqBalanceTextField.setVisible(false);
availableNonBsqBalanceTextField.setManaged(false);
return gridRow;
}
public int addBondBalanceGroup(GridPane gridPane, int gridRow, String groupStyle) {
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2,
Res.get("dao.bond.dashboard.bondsHeadline"), Layout.GROUP_DISTANCE);
if (groupStyle != null) titledGroupBg.getStyleClass().add(groupStyle);
lockupAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, gridRow,
Res.get("dao.bond.dashboard.lockupAmount"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
unlockingAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(gridPane, ++gridRow,
Res.get("dao.bond.dashboard.unlockingAmount")).second;
return gridRow;
}
}

View file

@ -1,36 +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.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.wallet.BsqWalletView"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="15"/>
<ScrollPane fitToWidth="true" fitToHeight="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="270.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<AnchorPane fx:id="content"/>
</ScrollPane>
</AnchorPane>

View file

@ -1,154 +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.wallet;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
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.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.wallet.receive.BsqReceiveView;
import bisq.desktop.main.dao.wallet.send.BsqSendView;
import bisq.desktop.main.dao.wallet.tx.BsqTxView;
import bisq.core.locale.Res;
import bisq.common.app.DevEnv;
import bisq.common.util.Tuple2;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
@FxmlView
public class BsqWalletView extends ActivatableView<AnchorPane, Void> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem send, receive, transactions;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
private ToggleGroup toggleGroup;
@Inject
private BsqWalletView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = (viewPath, data) -> {
if (viewPath.size() != 4 || viewPath.indexOf(BsqWalletView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass, data);
};
toggleGroup = new ToggleGroup();
List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, BsqWalletView.class);
send = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.send"), BsqSendView.class, baseNavPath);
receive = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.receive"), BsqReceiveView.class, baseNavPath);
transactions = new MenuItem(navigation, toggleGroup, Res.get("dao.wallet.menuItem.transactions"), BsqTxView.class, baseNavPath);
leftVBox.getChildren().addAll(send, receive, transactions);
if (!DevEnv.isDaoActivated()) {
send.setDisable(true);
transactions.setDisable(true);
}
}
@Override
protected void activate() {
send.activate();
receive.activate();
transactions.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(BsqWalletView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = BsqSendView.class;
if (!DevEnv.isDaoActivated())
selectedViewClass = BsqReceiveView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(BsqWalletView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@SuppressWarnings("Duplicates")
@Override
protected void deactivate() {
navigation.removeListener(listener);
send.deactivate();
receive.deactivate();
transactions.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
loadView(viewClass, null);
}
private void loadView(Class<? extends View> viewClass, @Nullable Object data) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof BsqSendView) {
toggleGroup.selectToggle(send);
if (data instanceof Tuple2) {
((BsqSendView) view).fillFromTradeData((Tuple2) data);
}
} else if (view instanceof BsqReceiveView) {
toggleGroup.selectToggle(receive);
} else if (view instanceof BsqTxView) {
toggleGroup.selectToggle(transactions);
}
}
public Class<? extends View> getSelectedViewClass() {
return selectedViewClass;
}
}

View file

@ -1,35 +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.wallet.receive.BsqReceiveView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="50"/>
<ColumnConstraints minWidth="10" maxWidth="5"/>
<ColumnConstraints percentWidth="50"/>
</columnConstraints>
</GridPane>

View file

@ -1,94 +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.wallet.receive;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.BsqAddressTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.wallet.BsqBalanceUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.locale.Res;
import bisq.core.util.coin.BsqFormatter;
import bisq.common.app.DevEnv;
import bisq.common.util.Tuple3;
import javax.inject.Inject;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import static bisq.desktop.util.FormBuilder.addLabelBsqAddressTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class BsqReceiveView extends ActivatableView<GridPane, Void> {
private BsqAddressTextField addressTextField;
private final BsqWalletService bsqWalletService;
private final BsqFormatter bsqFormatter;
private final BsqBalanceUtil bsqBalanceUtil;
private int gridRow = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BsqReceiveView(BsqWalletService bsqWalletService, BsqFormatter bsqFormatter, BsqBalanceUtil bsqBalanceUtil) {
this.bsqWalletService = bsqWalletService;
this.bsqFormatter = bsqFormatter;
this.bsqBalanceUtil = bsqBalanceUtil;
}
@Override
public void initialize() {
if (DevEnv.isDaoActivated()) {
gridRow = bsqBalanceUtil.addGroup(root, gridRow);
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 1,
Res.get("dao.wallet.receive.fundYourWallet"), Layout.GROUP_DISTANCE);
titledGroupBg.getStyleClass().add("last");
GridPane.setColumnSpan(titledGroupBg, 3);
Tuple3<Label, BsqAddressTextField, VBox> tuple = addLabelBsqAddressTextField(root, gridRow,
Res.get("dao.wallet.receive.bsqAddress"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE);
addressTextField = tuple.second;
GridPane.setColumnSpan(tuple.third, 3);
}
}
@Override
protected void activate() {
if (DevEnv.isDaoActivated())
bsqBalanceUtil.activate();
addressTextField.setAddress(bsqFormatter.getBsqAddressStringFromAddress(bsqWalletService.getUnusedAddress()));
}
@Override
protected void deactivate() {
if (DevEnv.isDaoActivated())
bsqBalanceUtil.deactivate();
}
}

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.wallet.send.BsqSendView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="20.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints percentWidth="50"/>
<ColumnConstraints minWidth="10" maxWidth="5"/>
<ColumnConstraints percentWidth="50"/>
</columnConstraints>
</GridPane>

View file

@ -1,541 +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.wallet.send;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.wallet.BsqBalanceUtil;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.funds.deposit.DepositView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.TxDetailsBsq;
import bisq.desktop.main.overlays.windows.TxInputSelectionWindow;
import bisq.desktop.main.overlays.windows.WalletPasswordWindow;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqAddressValidator;
import bisq.desktop.util.validation.BsqValidator;
import bisq.desktop.util.validation.BtcValidator;
import bisq.core.btc.exceptions.BsqChangeBelowDustException;
import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.listeners.BsqBalanceListener;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.dao.state.model.blockchain.TxType;
import bisq.core.locale.Res;
import bisq.core.monetary.Volume;
import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.core.util.validation.BtcAddressValidator;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
import bisq.common.handlers.ResultHandler;
import bisq.common.util.Tuple2;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.beans.value.ChangeListener;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.addInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final BsqFormatter bsqFormatter;
private final CoinFormatter btcFormatter;
private final Navigation navigation;
private final BsqBalanceUtil bsqBalanceUtil;
private final BsqValidator bsqValidator;
private final BtcValidator btcValidator;
private final BsqAddressValidator bsqAddressValidator;
private final BtcAddressValidator btcAddressValidator;
private final Preferences preferences;
private final WalletPasswordWindow walletPasswordWindow;
private int gridRow = 0;
private InputTextField amountInputTextField, btcAmountInputTextField;
private Button sendBsqButton, sendBtcButton, bsqInputControlButton, btcInputControlButton;
private InputTextField receiversAddressInputTextField, receiversBtcAddressInputTextField;
private ChangeListener<Boolean> focusOutListener;
private TitledGroupBg btcTitledGroupBg;
private ChangeListener<String> inputTextFieldListener;
@Nullable
private Set<TransactionOutput> bsqUtxoCandidates;
@Nullable
private Set<TransactionOutput> btcUtxoCandidates;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BsqSendView(BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
WalletsManager walletsManager,
WalletsSetup walletsSetup,
P2PService p2PService,
BsqFormatter bsqFormatter,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
Navigation navigation,
BsqBalanceUtil bsqBalanceUtil,
BsqValidator bsqValidator,
BtcValidator btcValidator,
BsqAddressValidator bsqAddressValidator,
BtcAddressValidator btcAddressValidator,
Preferences preferences,
WalletPasswordWindow walletPasswordWindow) {
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.bsqFormatter = bsqFormatter;
this.btcFormatter = btcFormatter;
this.navigation = navigation;
this.bsqBalanceUtil = bsqBalanceUtil;
this.bsqValidator = bsqValidator;
this.btcValidator = btcValidator;
this.bsqAddressValidator = bsqAddressValidator;
this.btcAddressValidator = btcAddressValidator;
this.preferences = preferences;
this.walletPasswordWindow = walletPasswordWindow;
}
@Override
public void initialize() {
gridRow = bsqBalanceUtil.addGroup(root, gridRow);
addSendBsqGroup();
addSendBtcGroup();
focusOutListener = (observable, oldValue, newValue) -> {
if (!newValue)
onUpdateBalances();
};
inputTextFieldListener = (observable, oldValue, newValue) -> onUpdateBalances();
setSendBtcGroupVisibleState(false);
}
@Override
protected void activate() {
setSendBtcGroupVisibleState(false);
bsqBalanceUtil.activate();
receiversAddressInputTextField.resetValidation();
amountInputTextField.resetValidation();
receiversBtcAddressInputTextField.resetValidation();
btcAmountInputTextField.resetValidation();
sendBsqButton.setOnAction((event) -> onSendBsq());
bsqInputControlButton.setOnAction((event) -> onBsqInputControl());
sendBtcButton.setOnAction((event) -> onSendBtc());
btcInputControlButton.setOnAction((event) -> onBtcInputControl());
receiversAddressInputTextField.focusedProperty().addListener(focusOutListener);
amountInputTextField.focusedProperty().addListener(focusOutListener);
receiversBtcAddressInputTextField.focusedProperty().addListener(focusOutListener);
btcAmountInputTextField.focusedProperty().addListener(focusOutListener);
receiversAddressInputTextField.textProperty().addListener(inputTextFieldListener);
amountInputTextField.textProperty().addListener(inputTextFieldListener);
receiversBtcAddressInputTextField.textProperty().addListener(inputTextFieldListener);
btcAmountInputTextField.textProperty().addListener(inputTextFieldListener);
bsqWalletService.addBsqBalanceListener(this);
// We reset the input selection at activate to have all inputs selected, otherwise the user
// might get confused if he had deselected inputs earlier and cannot spend the full balance.
bsqUtxoCandidates = null;
btcUtxoCandidates = null;
onUpdateBalances();
}
private void onUpdateBalances() {
onUpdateBalances(getSpendableBsqBalance(),
bsqWalletService.getAvailableNonBsqBalance(),
bsqWalletService.getUnverifiedBalance(),
bsqWalletService.getUnconfirmedChangeBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockupBondsBalance(),
bsqWalletService.getUnlockingBondsBalance());
}
@Override
protected void deactivate() {
bsqBalanceUtil.deactivate();
receiversAddressInputTextField.focusedProperty().removeListener(focusOutListener);
amountInputTextField.focusedProperty().removeListener(focusOutListener);
receiversBtcAddressInputTextField.focusedProperty().removeListener(focusOutListener);
btcAmountInputTextField.focusedProperty().removeListener(focusOutListener);
receiversAddressInputTextField.textProperty().removeListener(inputTextFieldListener);
amountInputTextField.textProperty().removeListener(inputTextFieldListener);
receiversBtcAddressInputTextField.textProperty().removeListener(inputTextFieldListener);
btcAmountInputTextField.textProperty().removeListener(inputTextFieldListener);
bsqWalletService.removeBsqBalanceListener(this);
sendBsqButton.setOnAction(null);
btcInputControlButton.setOnAction(null);
sendBtcButton.setOnAction(null);
bsqInputControlButton.setOnAction(null);
}
@Override
public void onUpdateBalances(Coin availableConfirmedBalance,
Coin availableNonBsqBalance,
Coin unverifiedBalance,
Coin unconfirmedChangeBalance,
Coin lockedForVotingBalance,
Coin lockupBondsBalance,
Coin unlockingBondsBalance) {
updateBsqValidator(availableConfirmedBalance);
updateBtcValidator(availableNonBsqBalance);
setSendBtcGroupVisibleState(availableNonBsqBalance.isPositive());
}
public void fillFromTradeData(Tuple2<Volume, String> tuple) {
amountInputTextField.setText(DisplayUtils.formatVolume(tuple.first));
receiversAddressInputTextField.setText(tuple.second);
}
private void updateBsqValidator(Coin availableConfirmedBalance) {
bsqValidator.setAvailableBalance(availableConfirmedBalance);
boolean isValid = bsqAddressValidator.validate(receiversAddressInputTextField.getText()).isValid &&
bsqValidator.validate(amountInputTextField.getText()).isValid;
sendBsqButton.setDisable(!isValid);
}
private void updateBtcValidator(Coin availableConfirmedBalance) {
btcValidator.setMaxValue(availableConfirmedBalance);
boolean isValid = btcAddressValidator.validate(receiversBtcAddressInputTextField.getText()).isValid &&
btcValidator.validate(btcAmountInputTextField.getText()).isValid;
sendBtcButton.setDisable(!isValid);
}
private void addSendBsqGroup() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.wallet.send.sendFunds"), Layout.GROUP_DISTANCE);
GridPane.setColumnSpan(titledGroupBg, 3);
receiversAddressInputTextField = addInputTextField(root, gridRow,
Res.get("dao.wallet.send.receiverAddress"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
receiversAddressInputTextField.setValidator(bsqAddressValidator);
GridPane.setColumnSpan(receiversAddressInputTextField, 3);
amountInputTextField = addInputTextField(root, ++gridRow, Res.get("dao.wallet.send.setAmount", bsqFormatter.formatCoinWithCode(Restrictions.getMinNonDustOutput())));
amountInputTextField.setValidator(bsqValidator);
GridPane.setColumnSpan(amountInputTextField, 3);
focusOutListener = (observable, oldValue, newValue) -> {
if (!newValue)
onUpdateBalances();
};
Tuple2<Button, Button> tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow,
Res.get("dao.wallet.send.send"), Res.get("dao.wallet.send.inputControl"));
sendBsqButton = tuple.first;
bsqInputControlButton = tuple.second;
}
private void onSendBsq() {
if (!GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
return;
}
String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString();
Coin receiverAmount = ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter);
try {
Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString,
receiverAmount, bsqUtxoCandidates);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
Coin miningFee = signedTx.getFee();
int txVsize = signedTx.getVsize();
showPublishTxPopup(receiverAmount,
txWithBtcFee,
TxType.TRANSFER_BSQ,
miningFee,
txVsize,
receiversAddressInputTextField.getText(),
bsqFormatter,
btcFormatter,
() -> {
receiversAddressInputTextField.setText("");
amountInputTextField.setText("");
receiversAddressInputTextField.resetValidation();
amountInputTextField.resetValidation();
});
} catch (BsqChangeBelowDustException e) {
String msg = Res.get("popup.warning.bsqChangeBelowDustException", bsqFormatter.formatCoinWithCode(e.getOutputValue()));
new Popup().warning(msg).show();
} catch (Throwable t) {
handleError(t);
}
}
private void onBsqInputControl() {
List<TransactionOutput> unspentTransactionOutputs = bsqWalletService.getSpendableBsqTransactionOutputs();
if (bsqUtxoCandidates == null) {
bsqUtxoCandidates = new HashSet<>(unspentTransactionOutputs);
} else {
// If we had some selection stored we need to update to already spent entries
bsqUtxoCandidates = bsqUtxoCandidates.stream().
filter(e -> unspentTransactionOutputs.contains(e)).
collect(Collectors.toSet());
}
TxInputSelectionWindow txInputSelectionWindow = new TxInputSelectionWindow(unspentTransactionOutputs,
bsqUtxoCandidates,
preferences,
bsqFormatter);
txInputSelectionWindow.onAction(() -> setBsqUtxoCandidates(txInputSelectionWindow.getCandidates()))
.show();
}
private void setBsqUtxoCandidates(Set<TransactionOutput> candidates) {
this.bsqUtxoCandidates = candidates;
updateBsqValidator(getSpendableBsqBalance());
amountInputTextField.refreshValidation();
}
// We have used input selection it is the sum of our selected inputs, otherwise the availableConfirmedBalance
private Coin getSpendableBsqBalance() {
return bsqUtxoCandidates != null ?
Coin.valueOf(bsqUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) :
bsqWalletService.getAvailableConfirmedBalance();
}
private void setSendBtcGroupVisibleState(boolean visible) {
btcTitledGroupBg.setVisible(visible);
receiversBtcAddressInputTextField.setVisible(visible);
btcAmountInputTextField.setVisible(visible);
sendBtcButton.setVisible(visible);
btcInputControlButton.setVisible(visible);
btcTitledGroupBg.setManaged(visible);
receiversBtcAddressInputTextField.setManaged(visible);
btcAmountInputTextField.setManaged(visible);
sendBtcButton.setManaged(visible);
btcInputControlButton.setManaged(visible);
}
private void addSendBtcGroup() {
btcTitledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.wallet.send.sendBtcFunds"), Layout.GROUP_DISTANCE);
GridPane.setColumnSpan(btcTitledGroupBg, 3);
receiversBtcAddressInputTextField = addInputTextField(root, gridRow,
Res.get("dao.wallet.send.receiverBtcAddress"), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
receiversBtcAddressInputTextField.setValidator(btcAddressValidator);
GridPane.setColumnSpan(receiversBtcAddressInputTextField, 3);
btcAmountInputTextField = addInputTextField(root, ++gridRow, Res.get("dao.wallet.send.btcAmount"));
btcAmountInputTextField.setValidator(btcValidator);
GridPane.setColumnSpan(btcAmountInputTextField, 3);
Tuple2<Button, Button> tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow,
Res.get("dao.wallet.send.sendBtc"), Res.get("dao.wallet.send.inputControl"));
sendBtcButton = tuple.first;
btcInputControlButton = tuple.second;
}
private void onBtcInputControl() {
List<TransactionOutput> unspentTransactionOutputs = bsqWalletService.getSpendableNonBsqTransactionOutputs();
if (btcUtxoCandidates == null) {
btcUtxoCandidates = new HashSet<>(unspentTransactionOutputs);
} else {
// If we had some selection stored we need to update to already spent entries
btcUtxoCandidates = btcUtxoCandidates.stream().
filter(e -> unspentTransactionOutputs.contains(e)).
collect(Collectors.toSet());
}
TxInputSelectionWindow txInputSelectionWindow = new TxInputSelectionWindow(unspentTransactionOutputs,
btcUtxoCandidates,
preferences,
btcFormatter);
txInputSelectionWindow.onAction(() -> setBtcUtxoCandidates(txInputSelectionWindow.getCandidates())).
show();
}
private void setBtcUtxoCandidates(Set<TransactionOutput> candidates) {
this.btcUtxoCandidates = candidates;
updateBtcValidator(getSpendableBtcBalance());
btcAmountInputTextField.refreshValidation();
}
private Coin getSpendableBtcBalance() {
return btcUtxoCandidates != null ?
Coin.valueOf(btcUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) :
bsqWalletService.getAvailableNonBsqBalance();
}
private void onSendBtc() {
if (!GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
return;
}
String receiversAddressString = receiversBtcAddressInputTextField.getText();
Coin receiverAmount = bsqFormatter.parseToBTC(btcAmountInputTextField.getText());
try {
Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount, btcUtxoCandidates);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
Coin miningFee = signedTx.getFee();
if (miningFee.getValue() >= receiverAmount.getValue())
GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter);
else {
int txVsize = signedTx.getVsize();
showPublishTxPopup(receiverAmount,
txWithBtcFee,
TxType.INVALID,
miningFee,
txVsize, receiversBtcAddressInputTextField.getText(),
btcFormatter,
btcFormatter,
() -> {
receiversBtcAddressInputTextField.setText("");
btcAmountInputTextField.setText("");
receiversBtcAddressInputTextField.resetValidation();
btcAmountInputTextField.resetValidation();
});
}
} catch (BsqChangeBelowDustException e) {
String msg = Res.get("popup.warning.btcChangeBelowDustException", btcFormatter.formatCoinWithCode(e.getOutputValue()));
new Popup().warning(msg).show();
} catch (Throwable t) {
handleError(t);
}
}
private void handleError(Throwable t) {
if (t instanceof InsufficientMoneyException) {
final Coin missingCoin = ((InsufficientMoneyException) t).missing;
final String missing = missingCoin != null ? missingCoin.toFriendlyString() : "null";
new Popup().warning(Res.get("popup.warning.insufficientBtcFundsForBsqTx", missing))
.actionButtonTextWithGoTo("navigation.funds.depositFunds")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
.show();
} else {
log.error(t.toString());
t.printStackTrace();
new Popup().warning(t.getMessage()).show();
}
}
private void showPublishTxPopup(Coin receiverAmount,
Transaction txWithBtcFee,
TxType txType,
Coin miningFee,
int txVsize, String address,
CoinFormatter amountFormatter, // can be BSQ or BTC formatter
CoinFormatter feeFormatter,
ResultHandler resultHandler) {
new Popup().headLine(Res.get("dao.wallet.send.sendFunds.headline"))
.confirmation(Res.get("dao.wallet.send.sendFunds.details",
amountFormatter.formatCoinWithCode(receiverAmount),
address,
feeFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerVbyte(miningFee, txVsize),
txVsize / 1000d,
amountFormatter.formatCoinWithCode(receiverAmount)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
doWithdraw(txWithBtcFee, txType, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
log.debug("Successfully sent tx with id {}", txWithBtcFee.getTxId().toString());
String key = "showTransactionSentBsq";
if (DontShowAgainLookup.showAgain(key)) {
new TxDetailsBsq(txWithBtcFee.getTxId().toString(), address, amountFormatter.formatCoinWithCode(receiverAmount))
.dontShowAgainId(key)
.show();
}
}
@Override
public void onFailure(TxBroadcastException exception) {
new Popup().warning(exception.toString());
}
});
resultHandler.handleResult();
})
.closeButtonText(Res.get("shared.cancel"))
.show();
}
private void doWithdraw(Transaction txWithBtcFee, TxType txType, TxBroadcaster.Callback callback) {
if (btcWalletService.isEncrypted()) {
UserThread.runAfter(() -> walletPasswordWindow.onAesKey(aesKey ->
sendFunds(txWithBtcFee, txType, callback))
.show(), 300, TimeUnit.MILLISECONDS);
} else {
sendFunds(txWithBtcFee, txType, callback);
}
}
private void sendFunds(Transaction txWithBtcFee, TxType txType, TxBroadcaster.Callback callback) {
walletsManager.publishAndCommitBsqTx(txWithBtcFee, txType, callback);
}
}

Some files were not shown because too many files have changed in this diff Show more