diff --git a/core/src/main/java/bisq/core/util/BSFormatter.java b/core/src/main/java/bisq/core/util/BSFormatter.java index bf0db4618e..8d2501a020 100644 --- a/core/src/main/java/bisq/core/util/BSFormatter.java +++ b/core/src/main/java/bisq/core/util/BSFormatter.java @@ -421,6 +421,10 @@ public class BSFormatter { } } + public String formatPrice(Price price, boolean appendCurrencyCode) { + return formatPrice(price, fiatPriceFormat, true); + } + public String formatPrice(Price price) { return formatPrice(price, fiatPriceFormat, false); } diff --git a/core/src/main/java/bisq/core/util/BsqFormatter.java b/core/src/main/java/bisq/core/util/BsqFormatter.java index 3d3958638b..cba0117212 100644 --- a/core/src/main/java/bisq/core/util/BsqFormatter.java +++ b/core/src/main/java/bisq/core/util/BsqFormatter.java @@ -124,6 +124,10 @@ public class BsqFormatter extends BSFormatter { return super.formatCoin(satoshi, coinFormat); } + public String formatBSQSatoshisWithCode(long satoshi) { + return super.formatCoinWithCode(satoshi, coinFormat); + } + public String formatBTCSatoshis(long satoshi) { return super.formatCoin(satoshi, btcCoinFormat); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index b4b43200ec..360dacb145 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -198,6 +198,7 @@ shared.actions=Actions shared.buyerUpperCase=Buyer shared.sellerUpperCase=Seller shared.new=NEW +shared.new=NEW #################################################################### # UI views @@ -1756,29 +1757,6 @@ dao.wallet.menuItem.receive=Receive dao.wallet.menuItem.transactions=Transactions dao.wallet.dashboard.myBalance=My wallet balance -dao.wallet.dashboard.distribution=Distribution of all BSQ -dao.wallet.dashboard.locked=Global state of locked BSQ -dao.wallet.dashboard.market=Market data -dao.wallet.dashboard.genesis=Genesis transaction -dao.wallet.dashboard.txDetails=BSQ transactions statistics -dao.wallet.dashboard.genesisBlockHeight=Genesis block height -dao.wallet.dashboard.genesisTxId=Genesis transaction ID -dao.wallet.dashboard.genesisIssueAmount=BSQ issued at genesis transaction -dao.wallet.dashboard.compRequestIssueAmount=BSQ issued for compensation requests -dao.wallet.dashboard.reimbursementAmount=BSQ issued for reimbursement requests -dao.wallet.dashboard.availableAmount=Total available BSQ -dao.wallet.dashboard.burntAmount=Burned BSQ (fees) -dao.wallet.dashboard.totalLockedUpAmount=Locked up in bonds -dao.wallet.dashboard.totalUnlockingAmount=Unlocking BSQ from bonds -dao.wallet.dashboard.totalUnlockedAmount=Unlocked BSQ from bonds -dao.wallet.dashboard.totalConfiscatedAmount=Confiscated BSQ from bonds -dao.wallet.dashboard.allTx=No. of all BSQ transactions -dao.wallet.dashboard.utxo=No. of all unspent transaction outputs -dao.wallet.dashboard.compensationIssuanceTx=No. of all compensation request issuance transactions -dao.wallet.dashboard.reimbursementIssuanceTx=No. of all reimbursement request issuance transactions -dao.wallet.dashboard.burntTx=No. of all fee payments transactions -dao.wallet.dashboard.price=Latest BSQ/BTC trade price (in Bisq) -dao.wallet.dashboard.marketCap=Market capitalisation (based on trade price) dao.wallet.receive.fundYourWallet=Your BSQ receive address dao.wallet.receive.bsqAddress=BSQ wallet address (Fresh unused address) @@ -1940,6 +1918,37 @@ dao.monitor.blindVote.table.hash=Hash of blind vote state dao.monitor.blindVote.table.prev=Previous hash dao.monitor.blindVote.table.numBlindVotes=No. blind votes +dao.factsAndFigures.menuItem.supply=Supply +dao.factsAndFigures.menuItem.transactions=Transactions + +dao.factsAndFigures.dashboard.marketPrice=Market data +dao.factsAndFigures.dashboard.price=Latest BSQ/BTC trade price (in Bisq) +dao.factsAndFigures.dashboard.marketCap=Market capitalisation (based on trade price) +dao.factsAndFigures.dashboard.availableAmount=Total available BSQ + +dao.factsAndFigures.supply.issued=BSQ issued +dao.factsAndFigures.supply.genesisIssueAmount=BSQ issued at genesis transaction +dao.factsAndFigures.supply.compRequestIssueAmount=BSQ issued for compensation requests +dao.factsAndFigures.supply.reimbursementAmount=BSQ issued for reimbursement requests + +dao.factsAndFigures.supply.burnt=BSQ burnt + +dao.factsAndFigures.supply.locked=Global state of locked BSQ +dao.factsAndFigures.supply.totalLockedUpAmount=Locked up in bonds +dao.factsAndFigures.supply.totalUnlockingAmount=Unlocking BSQ from bonds +dao.factsAndFigures.supply.totalUnlockedAmount=Unlocked BSQ from bonds +dao.factsAndFigures.supply.totalConfiscatedAmount=Confiscated BSQ from bonds +dao.factsAndFigures.supply.burntAmount=Burned BSQ (fees) + +dao.factsAndFigures.transactions.genesis=Genesis transaction +dao.factsAndFigures.transactions.genesisBlockHeight=Genesis block height +dao.factsAndFigures.transactions.genesisTxId=Genesis transaction ID +dao.factsAndFigures.transactions.txDetails=BSQ transactions statistics +dao.factsAndFigures.transactions.allTx=No. of all BSQ transactions +dao.factsAndFigures.transactions.utxo=No. of all unspent transaction outputs +dao.factsAndFigures.transactions.compensationIssuanceTx=No. of all compensation request issuance transactions +dao.factsAndFigures.transactions.reimbursementIssuanceTx=No. of all reimbursement request issuance transactions +dao.factsAndFigures.transactions.burntTx=No. of all fee payments transactions #################################################################### # Windows diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.java index b3c687a3b4..ae5ce9478c 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.java @@ -9,7 +9,7 @@ * 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. + * License for more supply. * * You should have received a copy of the GNU Affero General Public License * along with Bisq. If not, see . @@ -28,7 +28,8 @@ 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.details.DetailsView; +import bisq.desktop.main.dao.economy.supply.SupplyView; +import bisq.desktop.main.dao.economy.transactions.BSQTransactionsView; import bisq.core.locale.Res; @@ -51,7 +52,7 @@ public class EconomyView extends ActivatableViewAndModel { private final ViewLoader viewLoader; private final Navigation navigation; - private MenuItem dashboard, details; + private MenuItem dashboard, supply, transactions; private Navigation.Listener listener; @FXML @@ -81,20 +82,24 @@ public class EconomyView extends ActivatableViewAndModel { toggleGroup = new ToggleGroup(); List> baseNavPath = Arrays.asList(MainView.class, DaoView.class, EconomyView.class); dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), BsqDashboardView.class, baseNavPath); - details = new MenuItem(navigation, toggleGroup, Res.get("shared.details"), DetailsView.class, baseNavPath); - leftVBox.getChildren().addAll(dashboard, details); + 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); // TODO just until DAO is enabled if (!DevEnv.isDaoActivated()) { dashboard.setDisable(true); - details.setDisable(true); + supply.setDisable(true); + transactions.setDisable(true); } } @Override protected void activate() { dashboard.activate(); - details.activate(); + supply.activate(); + transactions.activate(); navigation.addListener(listener); ViewPath viewPath = navigation.getCurrentPath(); @@ -116,6 +121,8 @@ public class EconomyView extends ActivatableViewAndModel { navigation.removeListener(listener); dashboard.deactivate(); + supply.deactivate(); + transactions.deactivate(); } private void loadView(Class viewClass) { @@ -123,6 +130,7 @@ public class EconomyView extends ActivatableViewAndModel { content.getChildren().setAll(view.getRoot()); if (view instanceof BsqDashboardView) toggleGroup.selectToggle(dashboard); - else if (view instanceof DetailsView) toggleGroup.selectToggle(details); + else if (view instanceof SupplyView) toggleGroup.selectToggle(supply); + else if (view instanceof BSQTransactionsView) toggleGroup.selectToggle(transactions); } } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml index b8a75b09d6..3a541a621a 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml @@ -27,7 +27,6 @@ xmlns:fx="http://javafx.com/fxml"> - diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java index 21c011820c..dd6596d8b3 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java @@ -19,30 +19,94 @@ package bisq.desktop.main.dao.economy.dashboard; import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.util.FormBuilder; 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.dao.state.model.governance.IssuanceType; +import bisq.core.locale.Res; +import bisq.core.monetary.Price; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.statistics.TradeStatistics2; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.user.Preferences; +import bisq.core.util.BSFormatter; import bisq.core.util.BsqFormatter; +import bisq.common.util.Tuple3; + +import org.bitcoinj.core.Coin; + import javax.inject.Inject; +import javafx.scene.chart.AreaChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; +import javafx.geometry.Side; import javafx.beans.value.ChangeListener; +import javafx.util.StringConverter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAdjusters; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; +import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField; +import static bisq.desktop.util.Layout.FIRST_ROW_DISTANCE; + + + +import java.sql.Date; + @FxmlView public class BsqDashboardView extends ActivatableView implements DaoStateListener { + private static final String DAY = "day"; + private static final Map ADJUSTERS = new HashMap<>(); + private final DaoFacade daoFacade; + private final TradeStatisticsManager tradeStatisticsManager; private final PriceFeedService priceFeedService; + private final DaoStateService daoStateService; private final Preferences preferences; private final BsqFormatter bsqFormatter; + private final BSFormatter btcFormatter; private ChangeListener priceChangeListener; + private AreaChart bsqPriceChart; + private XYChart.Series seriesBSQAdded, seriesBSQBurnt; + private XYChart.Series seriesBSQPrice; + + private TextField marketCapTextField, priceTextField, availableAmountTextField; + + private Coin availableAmount; + + private int gridRow = 0; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, lifecycle @@ -50,20 +114,52 @@ public class BsqDashboardView extends ActivatableView implements @Inject private BsqDashboardView(DaoFacade daoFacade, + TradeStatisticsManager tradeStatisticsManager, PriceFeedService priceFeedService, + DaoStateService daoStateService, Preferences preferences, - BsqFormatter bsqFormatter) { + BsqFormatter bsqFormatter, + BSFormatter btcFormatter) { this.daoFacade = daoFacade; + this.tradeStatisticsManager = tradeStatisticsManager; this.priceFeedService = priceFeedService; + this.daoStateService = daoStateService; this.preferences = preferences; this.bsqFormatter = bsqFormatter; + this.btcFormatter = btcFormatter; } @Override public void initialize() { + + ADJUSTERS.put(DAY, TemporalAdjusters.ofDateAdjuster(d -> d)); + + createKPIs(); + createChart(); + priceChangeListener = (observable, oldValue, newValue) -> updatePrice(); } + private void createKPIs() { + + TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 5, Res.get("dao.factsAndFigures.dashboard.marketPrice")); + titledGroupBg.getStyleClass().add("last"); + + Tuple3 marketPriceTuple = addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.factsAndFigures.dashboard.price"), + FIRST_ROW_DISTANCE); + priceTextField = marketPriceTuple.second; + + GridPane.setColumnSpan(marketPriceTuple.third, 2); + + marketCapTextField = addTopLabelReadOnlyTextField(root, ++gridRow, + Res.get("dao.factsAndFigures.dashboard.marketCap")).second; + + availableAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, 1, + Res.get("dao.factsAndFigures.dashboard.availableAmount")).second; + + } + + @Override protected void activate() { daoFacade.addBsqStateListener(this); @@ -71,15 +167,16 @@ public class BsqDashboardView extends ActivatableView implements updateWithBsqBlockChainData(); updatePrice(); + updateChartData(); } + @Override protected void deactivate() { daoFacade.removeBsqStateListener(this); priceFeedService.updateCounterProperty().removeListener(priceChangeListener); } - /////////////////////////////////////////////////////////////////////////////////////////// // DaoStateListener /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,10 +191,123 @@ public class BsqDashboardView extends ActivatableView implements // Private /////////////////////////////////////////////////////////////////////////////////////////// + private void createChart() { + NumberAxis xAxis = new NumberAxis(); + xAxis.setForceZeroInRange(false); + xAxis.setAutoRanging(true); + xAxis.setTickLabelGap(6); + xAxis.setTickMarkVisible(false); + xAxis.setMinorTickVisible(false); + + xAxis.setTickLabelFormatter(new StringConverter<>() { + @Override + public String toString(Number timestamp) { + LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(timestamp.longValue(), + 0, OffsetDateTime.now(ZoneId.systemDefault()).getOffset()); + return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)); + } + + @Override + public Number fromString(String string) { + return 0; + } + }); + + NumberAxis yAxis = new NumberAxis(); + yAxis.setForceZeroInRange(false); + yAxis.setSide(Side.RIGHT); + yAxis.setAutoRanging(true); + yAxis.setTickMarkVisible(false); + yAxis.setMinorTickVisible(false); + yAxis.setTickLabelGap(5); + yAxis.setTickLabelFormatter(new StringConverter<>() { + @Override + public String toString(Number marketPrice) { + return bsqFormatter.formatBTCWithCode(marketPrice.longValue()); + } + + @Override + public Number fromString(String string) { + return 0; + } + }); + + seriesBSQPrice = new XYChart.Series<>(); + seriesBSQPrice.setName("Price in BTC for 1 BSQ"); + + bsqPriceChart = new AreaChart<>(xAxis, yAxis); + bsqPriceChart.setLegendVisible(true); + bsqPriceChart.setAnimated(false); + bsqPriceChart.setId("charts"); + bsqPriceChart.setMinHeight(250); + bsqPriceChart.setPrefHeight(250); + bsqPriceChart.setCreateSymbols(true); + bsqPriceChart.setPadding(new Insets(0)); + bsqPriceChart.getData().addAll(seriesBSQPrice); + + GridPane.setRowIndex(bsqPriceChart, ++gridRow); + GridPane.setColumnSpan(bsqPriceChart, 2); + + root.getChildren().addAll(bsqPriceChart); + } + + private void updateChartData() { + updateBSQPriceData(); + } + + private void updateBSQPriceData() { + seriesBSQPrice.getData().clear(); + + Map> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() + .filter(e -> e.getCurrencyCode().equals("BSQ")) + .sorted(Comparator.comparing(TradeStatistics2::getTradeDate)) + .collect(Collectors.groupingBy(item -> new Date(item.getTradeDate().getTime()).toLocalDate() + .with(ADJUSTERS.get(DAY)))); + + List> updatedBSQPrice = bsqPriceByDate.keySet().stream() + .map(e -> { + ZonedDateTime zonedDateTime = e.atStartOfDay(ZoneId.systemDefault()); + return new XYChart.Data(zonedDateTime.toInstant().getEpochSecond(), bsqPriceByDate.get(e).stream() + .map(TradeStatistics2::getTradePrice) + .mapToDouble(Price::getValue) + .average() + .orElse(Double.NaN) + ); + }) + .collect(Collectors.toList()); + + seriesBSQPrice.getData().setAll(updatedBSQPrice); + } + private void updateWithBsqBlockChainData() { + Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply(); + Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.COMPENSATION)); + Coin issuedAmountFromReimbursementRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT)); + Coin burntFee = Coin.valueOf(daoFacade.getTotalBurntFee()); + Coin totalConfiscatedAmount = Coin.valueOf(daoFacade.getTotalAmountOfConfiscatedTxOutputs()); + + availableAmount = issuedAmountFromGenesis + .add(issuedAmountFromCompRequests) + .add(issuedAmountFromReimbursementRequests) + .subtract(burntFee) + .subtract(totalConfiscatedAmount); + + availableAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(availableAmount)); } private void updatePrice() { + Optional optionalBsqPrice = priceFeedService.getBsqPrice(); + if (optionalBsqPrice.isPresent()) { + Price bsqPrice = optionalBsqPrice.get(); + priceTextField.setText(bsqFormatter.formatPrice(bsqPrice) + " BSQ/BTC"); + + marketCapTextField.setText(bsqFormatter.formatMarketCap(priceFeedService.getMarketPrice("BSQ"), + priceFeedService.getMarketPrice(preferences.getPreferredTradeCurrency().getCode()), + availableAmount)); + } else { + priceTextField.setText(Res.get("shared.na")); + marketCapTextField.setText(Res.get("shared.na")); + } } } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/details/DetailsView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/details/DetailsView.java deleted file mode 100644 index 826285aaeb..0000000000 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/details/DetailsView.java +++ /dev/null @@ -1,225 +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 . - */ - -package bisq.desktop.main.dao.economy.details; - -import bisq.desktop.common.view.ActivatableView; -import bisq.desktop.common.view.FxmlView; -import bisq.desktop.components.HyperlinkWithIcon; -import bisq.desktop.util.FormBuilder; -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.monetary.Price; -import bisq.core.provider.price.PriceFeedService; -import bisq.core.user.Preferences; -import bisq.core.util.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.control.Tooltip; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.VBox; - -import javafx.beans.value.ChangeListener; - -import java.util.Optional; - -import static bisq.desktop.util.FormBuilder.addTitledGroupBg; -import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField; - -@FxmlView -public class DetailsView extends ActivatableView implements DaoStateListener { - - private final DaoFacade daoFacade; - private final PriceFeedService priceFeedService; - private final Preferences preferences; - private final BsqFormatter bsqFormatter; - - private int gridRow = 0; - private TextField genesisIssueAmountTextField, compRequestIssueAmountTextField, reimbursementAmountTextField, availableAmountTextField, - burntAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField, - totalUnlockedAmountTextField, totalConfiscatedAmountTextField, allTxTextField, burntTxTextField, - utxoTextField, compensationIssuanceTxTextField, - reimbursementIssuanceTxTextField, priceTextField, marketCapTextField; - private ChangeListener priceChangeListener; - private Coin availableAmount; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor, lifecycle - /////////////////////////////////////////////////////////////////////////////////////////// - - @Inject - private DetailsView(DaoFacade daoFacade, - PriceFeedService priceFeedService, - Preferences preferences, - BsqFormatter bsqFormatter) { - this.daoFacade = daoFacade; - this.priceFeedService = priceFeedService; - this.preferences = preferences; - this.bsqFormatter = bsqFormatter; - } - - @Override - public void initialize() { - int columnIndex = 2; - - int startRow = gridRow; - addTitledGroupBg(root, ++gridRow, 5, Res.get("dao.wallet.dashboard.distribution")); - genesisIssueAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.wallet.dashboard.genesisIssueAmount")).second; - compRequestIssueAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.compRequestIssueAmount")).second; - reimbursementAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.reimbursementAmount")).second; - burntAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.burntAmount")).second; - availableAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.availableAmount")).second; - - gridRow = startRow; - addTitledGroupBg(root, ++gridRow, columnIndex, 5, Res.get("dao.wallet.dashboard.locked"), Layout.GROUP_DISTANCE); - totalLockedUpAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalLockedUpAmount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - totalUnlockingAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalUnlockingAmount")).second; - totalUnlockedAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalUnlockedAmount")).second; - totalConfiscatedAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalConfiscatedAmount")).second; - gridRow++; - - startRow = gridRow; - addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.wallet.dashboard.market"), Layout.GROUP_DISTANCE); - priceTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.wallet.dashboard.price"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - marketCapTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.marketCap")).second; - - gridRow = startRow; - addTitledGroupBg(root, ++gridRow, columnIndex, 2, Res.get("dao.wallet.dashboard.genesis"), Layout.GROUP_DISTANCE); - String genTxHeight = String.valueOf(daoFacade.getGenesisBlockHeight()); - String genesisTxId = daoFacade.getGenesisTxId(); - String url = preferences.getBsqBlockChainExplorer().txUrl + genesisTxId; - addTopLabelReadOnlyTextField(root, gridRow, columnIndex, Res.get("dao.wallet.dashboard.genesisBlockHeight"), - genTxHeight, Layout.FIRST_ROW_AND_GROUP_DISTANCE); - - // TODO use addTopLabelTxIdTextField - Tuple3 tuple = FormBuilder.addTopLabelHyperlinkWithIcon(root, ++gridRow, columnIndex, - Res.get("dao.wallet.dashboard.genesisTxId"), genesisTxId, url, 0); - HyperlinkWithIcon hyperlinkWithIcon = tuple.second; - hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", genesisTxId))); - - startRow = gridRow; - addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.wallet.dashboard.txDetails"), Layout.GROUP_DISTANCE); - allTxTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.wallet.dashboard.allTx"), - genTxHeight, Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - utxoTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.utxo")).second; - compensationIssuanceTxTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, - Res.get("dao.wallet.dashboard.compensationIssuanceTx")).second; - - gridRow = startRow; - addTitledGroupBg(root, ++gridRow, columnIndex, 3, "", Layout.GROUP_DISTANCE); - reimbursementIssuanceTxTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, columnIndex, - Res.get("dao.wallet.dashboard.reimbursementIssuanceTx"), - Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - burntTxTextField = FormBuilder.addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, - Res.get("dao.wallet.dashboard.burntTx")).second; - ++gridRow; - - priceChangeListener = (observable, oldValue, newValue) -> updatePrice(); - } - - @Override - protected void activate() { - daoFacade.addBsqStateListener(this); - priceFeedService.updateCounterProperty().addListener(priceChangeListener); - - updateWithBsqBlockChainData(); - updatePrice(); - } - - @Override - protected void deactivate() { - daoFacade.removeBsqStateListener(this); - priceFeedService.updateCounterProperty().removeListener(priceChangeListener); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // DaoStateListener - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void onParseBlockCompleteAfterBatchProcessing(Block block) { - updateWithBsqBlockChainData(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private void updateWithBsqBlockChainData() { - Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply(); - genesisIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromGenesis)); - - Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.COMPENSATION)); - compRequestIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromCompRequests)); - Coin issuedAmountFromReimbursementRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT)); - reimbursementAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromReimbursementRequests)); - - Coin burntFee = Coin.valueOf(daoFacade.getTotalBurntFee()); - Coin totalLockedUpAmount = Coin.valueOf(daoFacade.getTotalLockupAmount()); - Coin totalUnlockingAmount = Coin.valueOf(daoFacade.getTotalAmountOfUnLockingTxOutputs()); - Coin totalUnlockedAmount = Coin.valueOf(daoFacade.getTotalAmountOfUnLockedTxOutputs()); - Coin totalConfiscatedAmount = Coin.valueOf(daoFacade.getTotalAmountOfConfiscatedTxOutputs()); - availableAmount = issuedAmountFromGenesis - .add(issuedAmountFromCompRequests) - .add(issuedAmountFromReimbursementRequests) - .subtract(burntFee) - .subtract(totalConfiscatedAmount); - - availableAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(availableAmount)); - burntAmountTextField.setText("-" + bsqFormatter.formatAmountWithGroupSeparatorAndCode(burntFee)); - totalLockedUpAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalLockedUpAmount)); - totalUnlockingAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockingAmount)); - totalUnlockedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockedAmount)); - totalConfiscatedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalConfiscatedAmount)); - allTxTextField.setText(String.valueOf(daoFacade.getTxs().size())); - utxoTextField.setText(String.valueOf(daoFacade.getUnspentTxOutputs().size())); - compensationIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.COMPENSATION))); - reimbursementIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.REIMBURSEMENT))); - burntTxTextField.setText(String.valueOf(daoFacade.getFeeTxs().size())); - } - - private void updatePrice() { - Optional optionalBsqPrice = priceFeedService.getBsqPrice(); - if (optionalBsqPrice.isPresent()) { - Price bsqPrice = optionalBsqPrice.get(); - priceTextField.setText(bsqFormatter.formatPrice(bsqPrice) + " BSQ/BTC"); - - marketCapTextField.setText(bsqFormatter.formatMarketCap(priceFeedService.getMarketPrice("BSQ"), - priceFeedService.getMarketPrice(preferences.getPreferredTradeCurrency().getCode()), - availableAmount)); - } else { - priceTextField.setText(Res.get("shared.na")); - marketCapTextField.setText(Res.get("shared.na")); - } - } -} - diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/details/DetailsView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.fxml similarity index 94% rename from desktop/src/main/java/bisq/desktop/main/dao/economy/details/DetailsView.fxml rename to desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.fxml index 1f2bf7db48..561c3c2121 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/economy/details/DetailsView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.fxml @@ -20,14 +20,13 @@ - - diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java new file mode 100644 index 0000000000..28963b5789 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java @@ -0,0 +1,322 @@ +/* + * 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 . + */ + +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.util.Layout; + +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.dao.state.model.blockchain.Tx; +import bisq.core.dao.state.model.governance.Issuance; +import bisq.core.dao.state.model.governance.IssuanceType; +import bisq.core.locale.Res; +import bisq.core.util.BsqFormatter; + +import bisq.common.util.Tuple3; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; + +import javafx.scene.chart.AreaChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; +import javafx.geometry.Side; + +import javafx.util.StringConverter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAdjusters; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static bisq.desktop.util.FormBuilder.addTitledGroupBg; +import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField; + + + +import java.sql.Date; + +@FxmlView +public class SupplyView extends ActivatableView implements DaoStateListener { + + private static final String MONTH = "month"; + + private final DaoFacade daoFacade; + private DaoStateService daoStateService; + private final BsqFormatter bsqFormatter; + + private int gridRow = 0; + private TextField genesisIssueAmountTextField, compRequestIssueAmountTextField, reimbursementAmountTextField, + burntAmountTextField, totalLockedUpAmountTextField, totalUnlockingAmountTextField, + totalUnlockedAmountTextField, totalConfiscatedAmountTextField; + private XYChart.Series seriesBSQIssued, seriesBSQBurnt; + + private static final Map ADJUSTERS = new HashMap<>(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private SupplyView(DaoFacade daoFacade, + DaoStateService daoStateService, + BsqFormatter bsqFormatter) { + this.daoFacade = daoFacade; + this.daoStateService = daoStateService; + this.bsqFormatter = bsqFormatter; + } + + @Override + public void initialize() { + + ADJUSTERS.put(MONTH, TemporalAdjusters.firstDayOfMonth()); + + createSupplyIncreasedInformation(); + createSupplyReducedInformation(); + createSupplyLockedInformation(); + } + + @Override + protected void activate() { + daoFacade.addBsqStateListener(this); + + updateWithBsqBlockChainData(); + updateBSQTokenData(); + } + + @Override + protected void deactivate() { + daoFacade.removeBsqStateListener(this); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoStateListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onParseBlockCompleteAfterBatchProcessing(Block block) { + updateWithBsqBlockChainData(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void createSupplyIncreasedInformation() { + addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.factsAndFigures.supply.issued")); + + Tuple3 genesisAmountTuple = addTopLabelReadOnlyTextField(root, gridRow, + Res.get("dao.factsAndFigures.supply.genesisIssueAmount"), Layout.FIRST_ROW_DISTANCE); + genesisIssueAmountTextField = genesisAmountTuple.second; + GridPane.setColumnSpan(genesisAmountTuple.third, 2); + + compRequestIssueAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow, + Res.get("dao.factsAndFigures.supply.compRequestIssueAmount")).second; + reimbursementAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1, + Res.get("dao.factsAndFigures.supply.reimbursementAmount")).second; + + + seriesBSQIssued = new XYChart.Series<>(); + createChart(seriesBSQIssued, Res.get("dao.factsAndFigures.supply.issued")); + } + + private void createSupplyReducedInformation() { + addTitledGroupBg(root, ++gridRow, 2, Res.get("dao.factsAndFigures.supply.burnt"), Layout.GROUP_DISTANCE); + + Tuple3 burntAmountTuple = addTopLabelReadOnlyTextField(root, gridRow, + Res.get("dao.factsAndFigures.supply.burntAmount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); + burntAmountTextField = burntAmountTuple.second; + + GridPane.setColumnSpan(burntAmountTuple.third, 2); + + seriesBSQBurnt = new XYChart.Series<>(); + createChart(seriesBSQBurnt, Res.get("dao.factsAndFigures.supply.burnt")); + } + + private void createSupplyLockedInformation() { + 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; + + } + + private void createChart(XYChart.Series series, String seriesLabel) { + NumberAxis xAxis = new NumberAxis(); + xAxis.setForceZeroInRange(false); + xAxis.setAutoRanging(true); + xAxis.setTickLabelGap(6); + xAxis.setTickMarkVisible(false); + xAxis.setMinorTickVisible(false); + xAxis.setTickLabelFormatter(new StringConverter<>() { + @Override + public String toString(Number timestamp) { + LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(timestamp.longValue(), + 0, OffsetDateTime.now(ZoneId.systemDefault()).getOffset()); + return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)); + } + + @Override + public Number fromString(String string) { + return 0; + } + }); + + NumberAxis yAxis = new NumberAxis(); + yAxis.setForceZeroInRange(false); + yAxis.setSide(Side.RIGHT); + yAxis.setAutoRanging(true); + yAxis.setTickMarkVisible(false); + yAxis.setMinorTickVisible(false); + yAxis.setTickLabelGap(5); + yAxis.setTickLabelFormatter(new StringConverter<>() { + @Override + public String toString(Number marketPrice) { + return bsqFormatter.formatBSQSatoshisWithCode(marketPrice.longValue()); + } + + @Override + public Number fromString(String string) { + return 0; + } + }); + + series.setName(seriesLabel); + + AreaChart chart = new AreaChart<>(xAxis, yAxis); + chart.setLegendVisible(true); + chart.setAnimated(false); + chart.setId("charts"); + chart.setMinHeight(250); + chart.setPrefHeight(250); + chart.setCreateSymbols(true); + chart.setPadding(new Insets(0)); + chart.getData().addAll(series); + + GridPane.setColumnSpan(chart, 2); + GridPane.setRowIndex(chart, ++gridRow); + + root.getChildren().add(chart); + } + + private void updateWithBsqBlockChainData() { + Coin issuedAmountFromGenesis = daoFacade.getGenesisTotalSupply(); + genesisIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromGenesis)); + + Coin issuedAmountFromCompRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.COMPENSATION)); + compRequestIssueAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromCompRequests)); + Coin issuedAmountFromReimbursementRequests = Coin.valueOf(daoFacade.getTotalIssuedAmount(IssuanceType.REIMBURSEMENT)); + reimbursementAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(issuedAmountFromReimbursementRequests)); + + Coin burntFee = Coin.valueOf(daoFacade.getTotalBurntFee()); + Coin totalLockedUpAmount = Coin.valueOf(daoFacade.getTotalLockupAmount()); + Coin totalUnlockingAmount = Coin.valueOf(daoFacade.getTotalAmountOfUnLockingTxOutputs()); + Coin totalUnlockedAmount = Coin.valueOf(daoFacade.getTotalAmountOfUnLockedTxOutputs()); + Coin totalConfiscatedAmount = Coin.valueOf(daoFacade.getTotalAmountOfConfiscatedTxOutputs()); + + burntAmountTextField.setText("-" + bsqFormatter.formatAmountWithGroupSeparatorAndCode(burntFee)); + totalLockedUpAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalLockedUpAmount)); + totalUnlockingAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockingAmount)); + totalUnlockedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalUnlockedAmount)); + totalConfiscatedAmountTextField.setText(bsqFormatter.formatAmountWithGroupSeparatorAndCode(totalConfiscatedAmount)); + } + + private void updateBSQTokenData() { + seriesBSQIssued.getData().clear(); + seriesBSQBurnt.getData().clear(); + + Map> feesBurntByMonth = daoStateService.getBurntFeeTxs().stream() + .sorted(Comparator.comparing(Tx::getTime)) + .collect(Collectors.groupingBy(item -> new Date(item.getTime()).toLocalDate() + .with(ADJUSTERS.get(MONTH)))); + + List> updatedBurntBSQ = feesBurntByMonth.keySet().stream() + .map(date -> { + ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneId.systemDefault()); + return new XYChart.Data(zonedDateTime.toInstant().getEpochSecond(), feesBurntByMonth.get(date) + .stream() + .mapToDouble(Tx::getBurntFee) + .sum() + ); + }) + .collect(Collectors.toList()); + + seriesBSQBurnt.getData().setAll(updatedBurntBSQ); + + Stream bsqByCompensation = daoStateService.getIssuanceSet(IssuanceType.COMPENSATION).stream() + .sorted(Comparator.comparing(Issuance::getChainHeight)); + + Stream bsqByReImbursement = daoStateService.getIssuanceSet(IssuanceType.REIMBURSEMENT).stream() + .sorted(Comparator.comparing(Issuance::getChainHeight)); + + Map> bsqAddedByVote = Stream.concat(bsqByCompensation, bsqByReImbursement) + .collect(Collectors.groupingBy(item -> new Date(daoFacade.getBlockTime(item.getChainHeight())).toLocalDate() + .with(ADJUSTERS.get(MONTH)))); + + List> updatedAddedBSQ = bsqAddedByVote.keySet().stream() + .map(date -> { + ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneId.systemDefault()); + return new XYChart.Data(zonedDateTime.toInstant().getEpochSecond(), bsqAddedByVote.get(date) + .stream() + .mapToDouble(Issuance::getAmount) + .sum()); + }) + .collect(Collectors.toList()); + + seriesBSQIssued.getData().setAll(updatedAddedBSQ); + + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.fxml new file mode 100644 index 0000000000..bb89c7c4b2 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java b/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java new file mode 100644 index 0000000000..3e4e6a49e7 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/transactions/BSQTransactionsView.java @@ -0,0 +1,149 @@ +/* + * 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 . + */ + +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 implements DaoStateListener { + + private final DaoFacade daoFacade; + private final Preferences preferences; + + private int gridRow = 0; + private TextField allTxTextField, burntTxTextField, + utxoTextField, compensationIssuanceTxTextField, + reimbursementIssuanceTxTextField; + + /////////////////////////////////////////////////////////////////////////////////////////// + // 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 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; + + int columnIndex = 1; + + gridRow = startRow; + + titledGroupBg = addTitledGroupBg(root, startRow, columnIndex, 3, "", Layout.GROUP_DISTANCE); + titledGroupBg.getStyleClass().add("last"); + + reimbursementIssuanceTxTextField = addTopLabelReadOnlyTextField(root, gridRow, columnIndex, + Res.get("dao.factsAndFigures.transactions.reimbursementIssuanceTx"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + burntTxTextField = addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, + Res.get("dao.factsAndFigures.transactions.burntTx")).second; + + } + + @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.getTxs().size())); + utxoTextField.setText(String.valueOf(daoFacade.getUnspentTxOutputs().size())); + compensationIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.COMPENSATION))); + reimbursementIssuanceTxTextField.setText(String.valueOf(daoFacade.getNumIssuanceTransactions(IssuanceType.REIMBURSEMENT))); + burntTxTextField.setText(String.valueOf(daoFacade.getFeeTxs().size())); + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java index 42fe39bf26..8bf1529a3a 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java +++ b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java @@ -336,7 +336,7 @@ public class OfferBookChartView extends ActivatableViewAndModel