Add basic UI for bond lockup

This commit is contained in:
sqrrm 2018-06-10 15:22:17 +02:00
parent 61b8b5f28e
commit 56f4caf7bc
No known key found for this signature in database
GPG key ID: 45235F9EF87089EC
5 changed files with 427 additions and 2 deletions

View file

@ -25,6 +25,7 @@ import bisq.desktop.common.view.FxmlView;
import bisq.desktop.common.view.View;
import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.bonding.BondingView;
import bisq.desktop.main.dao.proposal.ProposalView;
import bisq.desktop.main.dao.voting.VotingView;
import bisq.desktop.main.dao.wallet.BsqWalletView;
@ -48,7 +49,7 @@ import javafx.beans.value.ChangeListener;
public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
@FXML
Tab bsqWalletTab, proposalsTab, votingTab;
Tab bsqWalletTab, proposalsTab, votingTab, bondingTab;
private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener;
@ -69,13 +70,15 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
public void initialize() {
proposalsTab = new Tab(Res.get("dao.tab.proposals"));
votingTab = new Tab(Res.get("dao.tab.voting"));
bondingTab = new Tab(Res.get("dao.tab.bonding"));
proposalsTab.setClosable(false);
votingTab.setClosable(false);
//TODO
root.getTabs().addAll(proposalsTab, votingTab);
root.getTabs().addAll(proposalsTab, votingTab, bondingTab);
if (!BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq() || !DevEnv.isDaoPhase2Activated()) {
votingTab.setDisable(true);
bondingTab.setDisable(true);
proposalsTab.setDisable(true);
}
@ -106,6 +109,9 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
} else if (newValue == votingTab) {
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class);
} else if (newValue == bondingTab) {
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
}
};
}
@ -126,6 +132,9 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
else if (selectedItem == votingTab)
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class);
else if (selectedItem == bondingTab)
//noinspection unchecked
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
}
}
@ -144,6 +153,8 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
selectedTab = proposalsTab;
} else if (view instanceof VotingView) {
selectedTab = votingTab;
} else if (view instanceof BondingView) {
selectedTab = bondingTab;
}
selectedTab.setContent(view.getRoot());

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bisq is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane fx:id="root" fx:controller="bisq.desktop.main.dao.bonding.BondingView"
prefHeight="660.0" prefWidth="1000.0"
xmlns:fx="http://javafx.com/fxml">
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
AnchorPane.topAnchor="20"/>
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
AnchorPane.topAnchor="30"/>
</AnchorPane>

View file

@ -0,0 +1,120 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.bonding;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.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.bonding.Lock.LockupBSQView;
import bisq.core.locale.Res;
import javax.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.fxml.FXML;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import java.util.Arrays;
import java.util.List;
@FxmlView
public class BondingView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem lockupBSQ;
private Navigation.Listener listener;
@FXML
private VBox leftVBox;
@FXML
private AnchorPane content;
private Class<? extends View> selectedViewClass;
@Inject
private BondingView(CachingViewLoader viewLoader, Navigation navigation) {
this.viewLoader = viewLoader;
this.navigation = navigation;
}
@Override
public void initialize() {
listener = viewPath -> {
if (viewPath.size() != 4 || viewPath.indexOf(bisq.desktop.main.dao.bonding.BondingView.class) != 2)
return;
selectedViewClass = viewPath.tip();
loadView(selectedViewClass);
};
ToggleGroup toggleGroup = new ToggleGroup();
final List<Class<? extends View>> baseNavPath = Arrays.asList(MainView.class, DaoView.class, bisq.desktop.main.dao.bonding.BondingView.class);
lockupBSQ = new MenuItem(navigation, toggleGroup, Res.get("dao.bonding.menuItem.lockupBSQ"),
LockupBSQView.class, AwesomeIcon.LIST_UL, baseNavPath);
leftVBox.getChildren().addAll(lockupBSQ);
}
@Override
protected void activate() {
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
if (viewPath.size() == 3 && viewPath.indexOf(BondingView.class) == 2 ||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
if (selectedViewClass == null)
selectedViewClass = LockupBSQView.class;
loadView(selectedViewClass);
} else if (viewPath.size() == 4 && viewPath.indexOf(BondingView.class) == 2) {
selectedViewClass = viewPath.get(3);
loadView(selectedViewClass);
}
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
lockupBSQ.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
View view = viewLoader.load(viewClass);
content.getChildren().setAll(view.getRoot());
if (view instanceof LockupBSQView) lockupBSQ.setSelected(true);
}
public Class<? extends View> getSelectedViewClass() {
return selectedViewClass;
}
}

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bisq is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.dao.bonding.Lock.LockupBSQView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,230 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.bonding.Lock;
import bisq.desktop.Navigation;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.InputTextField;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.wallet.BsqBalanceUtil;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.funds.deposit.DepositView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.BSFormatter;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqAddressValidator;
import bisq.desktop.util.validation.BsqValidator;
import bisq.core.btc.Restrictions;
import bisq.core.btc.wallet.BsqBalanceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TxBroadcastException;
import bisq.core.btc.wallet.TxBroadcastTimeoutException;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.TxMalleabilityException;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.btc.wallet.WalletsSetup;
import bisq.core.locale.Res;
import bisq.core.util.CoinUtil;
import bisq.network.p2p.P2PService;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.beans.value.ChangeListener;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addLabelInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class LockupBSQView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final BsqFormatter bsqFormatter;
private final BSFormatter btcFormatter;
private final Navigation navigation;
private final BsqBalanceUtil bsqBalanceUtil;
private final BsqValidator bsqValidator;
private int gridRow = 0;
private InputTextField amountInputTextField;
private Button lockupButton;
private ChangeListener<Boolean> focusOutListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private LockupBSQView(BsqWalletService bsqWalletService,
BtcWalletService btcWalletService,
WalletsManager walletsManager,
WalletsSetup walletsSetup,
P2PService p2PService,
BsqFormatter bsqFormatter,
BSFormatter btcFormatter,
Navigation navigation,
BsqBalanceUtil bsqBalanceUtil,
BsqValidator bsqValidator,
BsqAddressValidator bsqAddressValidator) {
this.bsqWalletService = bsqWalletService;
this.btcWalletService = btcWalletService;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.bsqFormatter = bsqFormatter;
this.btcFormatter = btcFormatter;
this.navigation = navigation;
this.bsqBalanceUtil = bsqBalanceUtil;
this.bsqValidator = bsqValidator;
}
@Override
public void initialize() {
// TODO: Show balance locked up in bonds
gridRow = bsqBalanceUtil.addGroup(root, gridRow);
addTitledGroupBg(root, ++gridRow, 3, Res.get("dao.bonding.lock.lockBSQ"), Layout.GROUP_DISTANCE);
amountInputTextField = addLabelInputTextField(root, gridRow, Res.get("dao.bonding.lock.amount"),
Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
amountInputTextField.setPromptText(Res.get("dao.bonding.lock.setAmount", Restrictions.getMinNonDustOutput().value));
amountInputTextField.setValidator(bsqValidator);
focusOutListener = (observable, oldValue, newValue) -> {
if (!newValue)
onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getPendingBalance(),
bsqWalletService.getLockedForVotingBalance(), bsqWalletService.getLockedInBondsBalance());
};
lockupButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.bonding.lock.lockupButton"));
lockupButton.setOnAction((event) -> {
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
Address lockupAddress = bsqWalletService.getUnusedAddress();
Coin lockupAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
try {
// TODO: Implement lockup bond function (this is just sending BSQ)
Transaction preparedSendTx = bsqWalletService.getPreparedSendTx(lockupAddress.toString(), lockupAmount);
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
Coin miningFee = signedTx.getFee();
int txSize = signedTx.bitcoinSerialize().length;
new Popup<>().headLine(Res.get("dao.bonding.lock.sendFunds.headline"))
.confirmation(Res.get("dao.bonding.lock.sendFunds.details",
bsqFormatter.formatCoinWithCode(lockupAmount),
bsqFormatter.getBsqAddressStringFromAddress(lockupAddress),
btcFormatter.formatCoinWithCode(miningFee),
CoinUtil.getFeePerByte(miningFee, txSize),
txSize / 1000d,
bsqFormatter.formatCoinWithCode(lockupAmount)))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> {
walletsManager.publishAndCommitBsqTx(txWithBtcFee, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
log.debug("Successfully sent tx with id " + txWithBtcFee.getHashAsString());
}
@Override
public void onTimeout(TxBroadcastTimeoutException exception) {
//TODO handle
new Popup<>().warning(exception.toString());
}
@Override
public void onTxMalleability(TxMalleabilityException exception) {
//TODO handle
new Popup<>().warning(exception.toString());
}
@Override
public void onFailure(TxBroadcastException exception) {
//TODO handle
new Popup<>().warning(exception.toString());
}
});
amountInputTextField.setText("");
})
.closeButtonText(Res.get("shared.cancel"))
.show();
} catch (Throwable t) {
if (t instanceof InsufficientMoneyException) {
final Coin missingCoin = ((InsufficientMoneyException) t).missing;
final String missing = missingCoin != null ? missingCoin.toFriendlyString() : "null";
//noinspection unchecked
new Popup<>().warning(Res.get("popup.warning.insufficientBtcFundsForBsqTx", missing))
.actionButtonTextWithGoTo("navigation.funds.depositFunds")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
.show();
} else {
log.error(t.toString());
t.printStackTrace();
new Popup<>().warning(t.getMessage()).show();
}
}
} else {
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
}
});
}
@Override
protected void activate() {
bsqBalanceUtil.activate();
amountInputTextField.focusedProperty().addListener(focusOutListener);
bsqWalletService.addBsqBalanceListener(this);
onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getPendingBalance(),
bsqWalletService.getLockedForVotingBalance(), bsqWalletService.getLockedInBondsBalance());
}
@Override
protected void deactivate() {
bsqBalanceUtil.deactivate();
amountInputTextField.focusedProperty().removeListener(focusOutListener);
bsqWalletService.removeBsqBalanceListener(this);
}
@Override
public void onUpdateBalances(Coin confirmedBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockedInBondsBalance) {
bsqValidator.setAvailableBalance(confirmedBalance);
boolean isValid = bsqValidator.validate(amountInputTextField.getText()).isValid;
lockupButton.setDisable(!isValid);
}
}