diff --git a/core/src/main/java/bisq/core/util/BSFormatter.java b/core/src/main/java/bisq/core/util/BSFormatter.java index bf0db4618ea..8d2501a0203 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 String formatPrice(Price price, MonetaryFormat fiatPriceFormat, boolean a } } + 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 3d3958638b4..cba0117212e 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 String formatBSQSatoshis(long satoshi) { 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 6f50beca256..aee1ff1083d 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 @@ -1268,6 +1269,7 @@ account.notifications.priceAlert.warning.lowerPriceTooHigh=The lower price must # DAO #################################################################### +dao.tab.factsAndFigures=Facts & Figures dao.tab.bsqWallet=BSQ wallet dao.tab.proposals=Governance dao.tab.bonding=Bonding @@ -1755,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) @@ -1939,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=BSQ Supply +dao.factsAndFigures.menuItem.transactions=BSQ 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/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 491dd694491..8f4f570725c 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -1682,17 +1682,21 @@ textfield */ * * ********************************************************************************************************************/ -#charts .chart-legend { +.chart-pane { + -fx-background-color: -bs-rd-white; +} + +#charts .chart-legend, #charts-dao .chart-legend { -fx-font-size: 1.077em; -fx-alignment: center; } -#charts .axis, #price-chart .axis, #volume-chart .axis { +#charts .axis, #price-chart .axis, #volume-chart .axis, #charts-dao .axis { -fx-tick-label-fill: -bs-rd-font-lighter; -fx-tick-label-font-size: 0.769em; } -#charts .chart-plot-background { +#charts .chart-plot-background, #charts-dao .chart-plot-background { -fx-background-color: -bs-rd-white; } @@ -1700,7 +1704,7 @@ textfield */ -fx-background-color: -bs-sell, -bs-rd-white; } -#charts .default-color1.chart-area-symbol { +#charts .default-color1.chart-area-symbol, #charts-dao .default-color0.chart-area-symbol { -fx-background-color: -bs-buy, -bs-rd-white; } @@ -1708,7 +1712,7 @@ textfield */ -fx-stroke: -bs-sell; } -#charts .default-color1.chart-series-area-line { +#charts .default-color1.chart-series-area-line, #charts-dao .default-color0.chart-series-area-line { -fx-stroke: -bs-buy; } @@ -1716,7 +1720,7 @@ textfield */ -fx-fill: -bs-sell-transparent; } -#charts .default-color1.chart-series-area-fill { +#charts .default-color1.chart-series-area-fill, #charts-dao .default-color0.chart-series-area-fill { -fx-fill: -bs-buy-transparent; } @@ -2071,6 +2075,17 @@ textfield */ -fx-text-fill: -bs-rd-error-red; } +.dao-kpi-big { + -fx-font-size: 1.923em; + -fx-text-fill: -bs-rd-black; + -fx-font-family: "IBM Plex Sans Light"; +} + +.dao-kpi-subtext { + -fx-text-fill: -bs-rd-font-light; + -fx-font-size: 0.923em; +} + /******************************************************************************************************************** * * * Notifications * diff --git a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java index 98239372f88..9a2d1603941 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java @@ -27,11 +27,12 @@ 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.dashboard.BsqDashboardView; +import bisq.desktop.main.dao.wallet.send.BsqSendView; import bisq.desktop.main.overlays.popups.Popup; import bisq.core.dao.governance.votereveal.VoteRevealService; @@ -53,7 +54,7 @@ public class DaoView extends ActivatableViewAndModel { @FXML - private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab, monitor; + private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab, monitorTab, factsAndFiguresTab; private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; @@ -77,36 +78,39 @@ private DaoView(CachingViewLoader viewLoader, VoteRevealService voteRevealServic @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()); - monitor = new Tab(Res.get("dao.tab.monitor").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); - monitor.setClosable(false); + monitorTab.setClosable(false); if (!DevEnv.isDaoActivated()) { + factsAndFiguresTab.setDisable(true); bsqWalletTab.setDisable(true); proposalsTab.setDisable(true); bondingTab.setDisable(true); burnBsqTab.setDisable(true); - monitor.setDisable(true); + monitorTab.setDisable(true); daoNewsTab = new Tab(Res.get("dao.tab.news").toUpperCase()); root.getTabs().add(daoNewsTab); } else { - root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, monitor); + root.getTabs().addAll(factsAndFiguresTab, bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, monitorTab); } navigationListener = viewPath -> { if (viewPath.size() == 3 && viewPath.indexOf(DaoView.class) == 1) { - if (proposalsTab == null && viewPath.get(2).equals(BsqWalletView.class)) - navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class); + if (proposalsTab == null && viewPath.get(2).equals(EconomyView.class)) + navigation.navigateTo(MainView.class, DaoView.class, EconomyView.class); else loadView(viewPath.tip()); } @@ -114,9 +118,9 @@ public void initialize() { tabChangeListener = (ov, oldValue, newValue) -> { if (newValue == bsqWalletTab) { - Class selectedViewClass = bsqWalletView.getSelectedViewClass(); + Class selectedViewClass = bsqWalletView != null ? bsqWalletView.getSelectedViewClass() : null; if (selectedViewClass == null) - navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, BsqDashboardView.class); + navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, BsqSendView.class); else navigation.navigateTo(MainView.class, DaoView.class, BsqWalletView.class, selectedViewClass); } else if (newValue == proposalsTab) { @@ -125,7 +129,9 @@ public void initialize() { navigation.navigateTo(MainView.class, DaoView.class, BondingView.class); } else if (newValue == burnBsqTab) { navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class); - } else if (newValue == monitor) { + } else if (newValue == factsAndFiguresTab) { + navigation.navigateTo(MainView.class, DaoView.class, EconomyView.class); + } else if (newValue == monitorTab) { navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class); } }; @@ -147,7 +153,9 @@ 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 == monitor) + 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 { @@ -182,9 +190,11 @@ private void loadView(Class viewClass) { } else if (view instanceof BurnBsqView) { selectedTab = burnBsqTab; } else if (view instanceof MonitorView) { - selectedTab = monitor; + selectedTab = monitorTab; } else if (view instanceof NewsView) { selectedTab = daoNewsTab; + } else if (view instanceof EconomyView) { + selectedTab = factsAndFiguresTab; } selectedTab.setContent(view.getRoot()); diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.fxml new file mode 100644 index 00000000000..1f1c5c527b1 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..ae5ce9478ce --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/EconomyView.java @@ -0,0 +1,136 @@ +/* + * 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 . + */ + +package bisq.desktop.main.dao.economy; + +import bisq.desktop.Navigation; +import bisq.desktop.common.view.ActivatableViewAndModel; +import bisq.desktop.common.view.CachingViewLoader; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.common.view.View; +import bisq.desktop.common.view.ViewLoader; +import bisq.desktop.common.view.ViewPath; +import bisq.desktop.components.MenuItem; +import bisq.desktop.main.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 ActivatableViewAndModel { + + 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 selectedViewClass; + private ToggleGroup toggleGroup; + + @Inject + private EconomyView(CachingViewLoader viewLoader, Navigation navigation) { + this.viewLoader = viewLoader; + this.navigation = navigation; + } + + @Override + public void initialize() { + listener = viewPath -> { + if (viewPath.size() != 4 || viewPath.indexOf(EconomyView.class) != 2) + return; + + selectedViewClass = viewPath.tip(); + loadView(selectedViewClass); + }; + + 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); + 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); + 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 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); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/BsqDashboardView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml similarity index 89% rename from desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/BsqDashboardView.fxml rename to desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml index 35a2a682fca..3a541a621a4 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/BsqDashboardView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.fxml @@ -20,14 +20,13 @@ - - 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 new file mode 100644 index 00000000000..65986ee0291 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/dashboard/BsqDashboardView.java @@ -0,0 +1,324 @@ +/* + * 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.dashboard; + +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +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.AnchorPane; +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.addLabelWithSubText; +import static bisq.desktop.util.FormBuilder.addTopLabelReadOnlyTextField; + + + +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, availableAmountTextField; + private Label marketPriceLabel; + + private Coin availableAmount; + + private int gridRow = 0; + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private BsqDashboardView(DaoFacade daoFacade, + TradeStatisticsManager tradeStatisticsManager, + PriceFeedService priceFeedService, + DaoStateService daoStateService, + Preferences preferences, + 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() { + + Tuple3 marketPriceBox = addLabelWithSubText(root, gridRow++, "0.004000 BSQ/BTC", "Latest BSQ/BTC trade price (in Bisq)"); + marketPriceLabel = marketPriceBox.first; + marketPriceLabel.getStyleClass().add("dao-kpi-big"); + + marketPriceBox.second.getStyleClass().add("dao-kpi-subtext"); + + 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); + priceFeedService.updateCounterProperty().addListener(priceChangeListener); + + updateWithBsqBlockChainData(); + updatePrice(); + updateChartData(); + } + + + @Override + protected void deactivate() { + daoFacade.removeBsqStateListener(this); + priceFeedService.updateCounterProperty().removeListener(priceChangeListener); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoStateListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onParseBlockCompleteAfterBatchProcessing(Block block) { + updateWithBsqBlockChainData(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // 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(false); + bsqPriceChart.setAnimated(false); + bsqPriceChart.setId("charts-dao"); + bsqPriceChart.setMinHeight(385); + bsqPriceChart.setPrefHeight(385); + bsqPriceChart.setCreateSymbols(true); + bsqPriceChart.setPadding(new Insets(0)); + bsqPriceChart.getData().addAll(seriesBSQPrice); + + AnchorPane chartPane = new AnchorPane(); + chartPane.getStyleClass().add("chart-pane"); + + AnchorPane.setTopAnchor(bsqPriceChart, 15d); + AnchorPane.setBottomAnchor(bsqPriceChart, 10d); + AnchorPane.setLeftAnchor(bsqPriceChart, 25d); + AnchorPane.setRightAnchor(bsqPriceChart, 10d); + + chartPane.getChildren().add(bsqPriceChart); + + GridPane.setRowIndex(chartPane, ++gridRow); + GridPane.setColumnSpan(chartPane, 2); + GridPane.setMargin(chartPane, new Insets(10, 0, 0, 0)); + + root.getChildren().addAll(chartPane); + } + + 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(); + marketPriceLabel.setText(bsqFormatter.formatPrice(bsqPrice) + " BSQ/BTC"); + + marketCapTextField.setText(bsqFormatter.formatMarketCap(priceFeedService.getMarketPrice("BSQ"), + priceFeedService.getMarketPrice(preferences.getPreferredTradeCurrency().getCode()), + availableAmount)); + + updateChartData(); + + } else { + marketPriceLabel.setText(Res.get("shared.na")); + marketCapTextField.setText(Res.get("shared.na")); + } + } +} + diff --git a/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.fxml new file mode 100644 index 00000000000..561c3c2121c --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + 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 00000000000..1d28e15ab0a --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/dao/economy/supply/SupplyView.java @@ -0,0 +1,335 @@ +/* + * 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.GlobalSettings; +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.AnchorPane; +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.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(); + } + + @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.ofPattern("MMM uu", GlobalSettings.getLocale())); + } + + @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(false); + chart.setAnimated(false); + chart.setId("charts-dao"); + chart.setMinHeight(250); + chart.setPrefHeight(250); + chart.setCreateSymbols(true); + chart.setPadding(new Insets(0)); + chart.getData().addAll(series); + + AnchorPane chartPane = new AnchorPane(); + chartPane.getStyleClass().add("chart-pane"); + + AnchorPane.setTopAnchor(chart, 15d); + AnchorPane.setBottomAnchor(chart, 10d); + AnchorPane.setLeftAnchor(chart, 25d); + AnchorPane.setRightAnchor(chart, 10d); + + chartPane.getChildren().add(chart); + + GridPane.setColumnSpan(chartPane, 2); + GridPane.setRowIndex(chartPane, ++gridRow); + GridPane.setMargin(chartPane, new Insets(10, 0, 0, 0)); + + root.getChildren().add(chartPane); + } + + 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)); + + updateCharts(); + } + + private void updateCharts() { + 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 00000000000..bb89c7c4b20 --- /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 00000000000..3e4e6a49e78 --- /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/dao/wallet/BsqWalletView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqWalletView.java index c72cc510d4d..92558b5ee5a 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqWalletView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/BsqWalletView.java @@ -27,7 +27,6 @@ import bisq.desktop.components.MenuItem; import bisq.desktop.main.MainView; import bisq.desktop.main.dao.DaoView; -import bisq.desktop.main.dao.wallet.dashboard.BsqDashboardView; import bisq.desktop.main.dao.wallet.receive.BsqReceiveView; import bisq.desktop.main.dao.wallet.send.BsqSendView; import bisq.desktop.main.dao.wallet.tx.BsqTxView; @@ -53,7 +52,7 @@ public class BsqWalletView extends ActivatableViewAndModel { private final ViewLoader viewLoader; private final Navigation navigation; - private MenuItem dashboard, send, receive, transactions; + private MenuItem send, receive, transactions; private Navigation.Listener listener; @FXML @@ -82,15 +81,13 @@ public void initialize() { toggleGroup = new ToggleGroup(); List> baseNavPath = Arrays.asList(MainView.class, DaoView.class, BsqWalletView.class); - dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), BsqDashboardView.class, baseNavPath); 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(dashboard, send, receive, transactions); + leftVBox.getChildren().addAll(send, receive, transactions); // TODO just until DAO is enabled if (!DevEnv.isDaoActivated()) { - dashboard.setDisable(true); send.setDisable(true); transactions.setDisable(true); } @@ -98,7 +95,6 @@ public void initialize() { @Override protected void activate() { - dashboard.activate(); send.activate(); receive.activate(); transactions.activate(); @@ -108,7 +104,7 @@ protected void activate() { if (viewPath.size() == 3 && viewPath.indexOf(BsqWalletView.class) == 2 || viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) { if (selectedViewClass == null) - selectedViewClass = BsqDashboardView.class; + selectedViewClass = BsqSendView.class; // TODO just until DAO is enabled if (!DevEnv.isDaoActivated()) @@ -126,7 +122,6 @@ protected void activate() { protected void deactivate() { navigation.removeListener(listener); - dashboard.deactivate(); send.deactivate(); receive.deactivate(); transactions.deactivate(); @@ -136,8 +131,7 @@ private void loadView(Class viewClass) { View view = viewLoader.load(viewClass); content.getChildren().setAll(view.getRoot()); - if (view instanceof BsqDashboardView) toggleGroup.selectToggle(dashboard); - else if (view instanceof BsqSendView) toggleGroup.selectToggle(send); + if (view instanceof BsqSendView) toggleGroup.selectToggle(send); else if (view instanceof BsqReceiveView) toggleGroup.selectToggle(receive); else if (view instanceof BsqTxView) toggleGroup.selectToggle(transactions); } diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/BsqDashboardView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/BsqDashboardView.java deleted file mode 100644 index f7454e65762..00000000000 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/dashboard/BsqDashboardView.java +++ /dev/null @@ -1,236 +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.wallet.dashboard; - -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.main.dao.wallet.BsqBalanceUtil; -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 BsqDashboardView extends ActivatableView implements DaoStateListener { - - private final BsqBalanceUtil bsqBalanceUtil; - 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 BsqDashboardView(BsqBalanceUtil bsqBalanceUtil, - DaoFacade daoFacade, - PriceFeedService priceFeedService, - Preferences preferences, - BsqFormatter bsqFormatter) { - this.bsqBalanceUtil = bsqBalanceUtil; - this.daoFacade = daoFacade; - this.priceFeedService = priceFeedService; - this.preferences = preferences; - this.bsqFormatter = bsqFormatter; - } - - @Override - public void initialize() { - gridRow = bsqBalanceUtil.addGroup(root, gridRow); - int columnIndex = 2; - - int startRow = gridRow; - addTitledGroupBg(root, ++gridRow, 5, Res.get("dao.wallet.dashboard.distribution"), Layout.GROUP_DISTANCE); - genesisIssueAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.wallet.dashboard.genesisIssueAmount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - compRequestIssueAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.compRequestIssueAmount")).second; - reimbursementAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.reimbursementAmount")).second; - burntAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.burntAmount")).second; - availableAmountTextField = 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 = addTopLabelReadOnlyTextField(root, gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalLockedUpAmount"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - totalUnlockingAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalUnlockingAmount")).second; - totalUnlockedAmountTextField = addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, Res.get("dao.wallet.dashboard.totalUnlockedAmount")).second; - totalConfiscatedAmountTextField = 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 = addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.wallet.dashboard.price"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - marketCapTextField = 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; - TitledGroupBg titledGroupBgTxDetails = addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.wallet.dashboard.txDetails"), Layout.GROUP_DISTANCE); - titledGroupBgTxDetails.getStyleClass().add("last"); - allTxTextField = addTopLabelReadOnlyTextField(root, gridRow, Res.get("dao.wallet.dashboard.allTx"), - genTxHeight, Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - utxoTextField = addTopLabelReadOnlyTextField(root, ++gridRow, Res.get("dao.wallet.dashboard.utxo")).second; - compensationIssuanceTxTextField = addTopLabelReadOnlyTextField(root, ++gridRow, - Res.get("dao.wallet.dashboard.compensationIssuanceTx")).second; - - gridRow = startRow; - TitledGroupBg titledGroupBgReImbursement = addTitledGroupBg(root, ++gridRow, columnIndex, 3, "", Layout.GROUP_DISTANCE); - titledGroupBgReImbursement.getStyleClass().add("last"); - reimbursementIssuanceTxTextField = addTopLabelReadOnlyTextField(root, gridRow, columnIndex, - Res.get("dao.wallet.dashboard.reimbursementIssuanceTx"), - Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - burntTxTextField = addTopLabelReadOnlyTextField(root, ++gridRow, columnIndex, - Res.get("dao.wallet.dashboard.burntTx")).second; - ++gridRow; - - priceChangeListener = (observable, oldValue, newValue) -> updatePrice(); - } - - @Override - protected void activate() { - bsqBalanceUtil.activate(); - - daoFacade.addBsqStateListener(this); - priceFeedService.updateCounterProperty().addListener(priceChangeListener); - - updateWithBsqBlockChainData(); - updatePrice(); - } - - @Override - protected void deactivate() { - bsqBalanceUtil.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/market/offerbook/OfferBookChartView.java b/desktop/src/main/java/bisq/desktop/main/market/offerbook/OfferBookChartView.java index 42fe39bf26f..680bac80fbe 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 @@ -61,6 +61,7 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; @@ -106,6 +107,7 @@ public class OfferBookChartView extends ActivatableViewAndModel buyOfferTableView; private TableView sellOfferTableView; private AreaChart areaChart; + private AnchorPane chartPane; private ComboBox currencyComboBox; private Subscription tradeCurrencySubscriber; private final StringProperty volumeColumnLabel = new SimpleStringProperty(); @@ -154,6 +156,8 @@ public void initialize() { createChart(); + VBox.setMargin(chartPane, new Insets(0, 0, 5, 0)); + Tuple4, VBox, Button, Label> tupleBuy = getOfferTable(OfferPayload.Direction.BUY); Tuple4, VBox, Button, Label> tupleSell = getOfferTable(OfferPayload.Direction.SELL); buyOfferTableView = tupleBuy.first; @@ -175,7 +179,8 @@ public void initialize() { tupleSell.second.setUserData(OfferPayload.Direction.SELL.name()); bottomHBox.getChildren().addAll(tupleBuy.second, tupleSell.second); - root.getChildren().addAll(currencyComboBoxTuple.first, areaChart, bottomHBox); + + root.getChildren().addAll(currencyComboBoxTuple.first, chartPane, bottomHBox); } @Override @@ -334,11 +339,21 @@ private void createChart() { areaChart.setLegendVisible(false); areaChart.setAnimated(false); areaChart.setId("charts"); - areaChart.setMinHeight(300); - areaChart.setPrefHeight(300); - areaChart.setCreateSymbols(false); + areaChart.setMinHeight(270); + areaChart.setPrefHeight(270); + areaChart.setCreateSymbols(true); areaChart.setPadding(new Insets(0, 10, 0, 10)); areaChart.getData().addAll(seriesBuy, seriesSell); + + chartPane = new AnchorPane(); + chartPane.getStyleClass().add("chart-pane"); + + AnchorPane.setTopAnchor(areaChart, 15d); + AnchorPane.setBottomAnchor(areaChart, 10d); + AnchorPane.setLeftAnchor(areaChart, 10d); + AnchorPane.setRightAnchor(areaChart, 0d); + + chartPane.getChildren().add(areaChart); } private void updateChartData() { diff --git a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java index 6b60582d252..ba7d8051d13 100644 --- a/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java +++ b/desktop/src/main/java/bisq/desktop/main/market/trades/TradesChartsView.java @@ -59,6 +59,7 @@ import javafx.scene.control.Toggle; import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; @@ -126,6 +127,7 @@ public class TradesChartsView extends ActivatableViewAndModel parentHeightListener; private Pane rootParent; private ChangeListener priceColumnLabelListener; + private AnchorPane priceChartPane, volumeChartPane; /////////////////////////////////////////////////////////////////////////////////////////// @@ -152,7 +154,7 @@ public void initialize() { nrOfTradeStatisticsLabel = new AutoTooltipLabel(" "); // set empty string for layout nrOfTradeStatisticsLabel.setId("num-offers"); nrOfTradeStatisticsLabel.setPadding(new Insets(-5, 0, -10, 5)); - root.getChildren().addAll(toolBox, priceChart, volumeChart, tableView, nrOfTradeStatisticsLabel); + root.getChildren().addAll(toolBox, priceChartPane, volumeChartPane, tableView, nrOfTradeStatisticsLabel); timeUnitChangeListener = (observable, oldValue, newValue) -> { if (newValue != null) { @@ -348,6 +350,15 @@ public Number fromString(String string) { //noinspection unchecked priceChart.setData(FXCollections.observableArrayList(priceSeries)); + priceChartPane = new AnchorPane(); + priceChartPane.getStyleClass().add("chart-pane"); + + AnchorPane.setTopAnchor(priceChart, 15d); + AnchorPane.setBottomAnchor(priceChart, 10d); + AnchorPane.setLeftAnchor(priceChart, 0d); + AnchorPane.setRightAnchor(priceChart, 10d); + + priceChartPane.getChildren().add(priceChart); volumeSeries = new XYChart.Series<>(); @@ -392,6 +403,16 @@ public Number fromString(String string) { volumeChart.setMaxHeight(200); volumeChart.setLegendVisible(false); volumeChart.setPadding(new Insets(0)); + + volumeChartPane = new AnchorPane(); + volumeChartPane.getStyleClass().add("chart-pane"); + + AnchorPane.setTopAnchor(volumeChart, 15d); + AnchorPane.setBottomAnchor(volumeChart, 10d); + AnchorPane.setLeftAnchor(volumeChart, 0d); + AnchorPane.setRightAnchor(volumeChart, 10d); + + volumeChartPane.getChildren().add(volumeChart); } private void updateChartData() { diff --git a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java index d5f9eb11d63..30528116440 100644 --- a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java @@ -141,6 +141,28 @@ public static Label addLabel(GridPane gridPane, int rowIndex, String title, doub return label; } + /////////////////////////////////////////////////////////////////////////////////////////// + // Label + Subtext + /////////////////////////////////////////////////////////////////////////////////////////// + + public static Tuple3 addLabelWithSubText(GridPane gridPane, int rowIndex, String title, String description) { + return addLabelWithSubText(gridPane, rowIndex, title, description, 0); + } + + public static Tuple3 addLabelWithSubText(GridPane gridPane, int rowIndex, String title, String description, double top) { + Label label = new AutoTooltipLabel(title); + Label subText = new AutoTooltipLabel(description); + + VBox vBox = new VBox(); + vBox.getChildren().setAll(label, subText); + + GridPane.setRowIndex(vBox, rowIndex); + GridPane.setMargin(vBox, new Insets(top, 0, 0, 0)); + gridPane.getChildren().add(vBox); + + return new Tuple3<>(label, subText, vBox); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Multiline Label