Add UI for myVotes

This commit is contained in:
Manfred Karrer 2018-03-21 20:53:44 -05:00
parent 11049d1e5e
commit a34bf50650
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
17 changed files with 1039 additions and 217 deletions

View file

@ -37,6 +37,7 @@ import bisq.core.locale.Res;
import javax.inject.Inject;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
@ -56,12 +57,13 @@ import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@FxmlView
@ -69,12 +71,14 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
protected final ProposalCollectionsService proposalCollectionsService;
protected final BsqBlockChain bsqBlockChain;
protected final ObservableList<ProposalListItem> proposalListItems = FXCollections.observableArrayList();
protected TableView<ProposalListItem> tableView;
protected final BsqWalletService bsqWalletService;
protected final BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher;
protected final BsqFormatter bsqFormatter;
protected SortedList<ProposalListItem> sortedList = new SortedList<>(proposalListItems);
protected final ObservableList<ProposalListItem> proposalListItems = FXCollections.observableArrayList();
protected final SortedList<ProposalListItem> sortedList = new SortedList<>(proposalListItems);
protected final List<Node> proposalViewItems = new ArrayList<>();
protected TableView<ProposalListItem> proposalTableView;
protected Subscription selectedProposalSubscription;
protected ProposalDisplay proposalDisplay;
protected int gridRow = 0;
@ -85,6 +89,7 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
protected final DaoPeriodService daoPeriodService;
protected DaoPeriodService.Phase currentPhase;
protected Subscription phaseSubscription;
private ScrollPane proposalDisplayView;
///////////////////////////////////////////////////////////////////////////////////////////
@ -111,80 +116,41 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
super.initialize();
root.getStyleClass().add("vote-root");
proposalListChangeListener = c -> updateList();
detailsGridPane = new GridPane();
proposalListChangeListener = c -> updateProposalList();
phaseChangeListener = (observable, oldValue, newValue) -> onPhaseChanged(newValue);
}
protected void createTableView() {
TableGroupHeadline proposalsHeadline = new TableGroupHeadline(Res.get("dao.proposal.active.header"));
GridPane.setRowIndex(proposalsHeadline, ++gridRow);
GridPane.setMargin(proposalsHeadline, new Insets(-10, -10, -10, -10));
GridPane.setColumnSpan(proposalsHeadline, 2);
root.getChildren().add(proposalsHeadline);
tableView = new TableView<>();
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
createColumns(tableView);
GridPane.setRowIndex(tableView, gridRow);
GridPane.setMargin(tableView, new Insets(10, -10, 5, -10));
GridPane.setColumnSpan(tableView, 2);
GridPane.setHgrow(tableView, Priority.ALWAYS);
root.getChildren().add(tableView);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
}
protected void createProposalDisplay() {
detailsGridPane = new GridPane();
proposalDisplay = new ProposalDisplay(detailsGridPane, bsqFormatter, bsqWalletService, null);
final ScrollPane proposalDisplayView = proposalDisplay.getView();
GridPane.setMargin(proposalDisplayView, new Insets(10, -10, 0, -10));
GridPane.setRowIndex(proposalDisplayView, ++gridRow);
GridPane.setColumnSpan(proposalDisplayView, 2);
root.getChildren().add(proposalDisplayView);
}
@Override
protected void activate() {
phaseSubscription = EasyBind.subscribe(daoPeriodService.getPhaseProperty(), phase -> {
if (!phase.equals(this.currentPhase)) {
this.currentPhase = phase;
onSelectProposal(selectedProposalListItem);
}
});
phaseSubscription = EasyBind.subscribe(daoPeriodService.getPhaseProperty(), this::onPhaseChanged);
selectedProposalSubscription = EasyBind.subscribe(proposalTableView.getSelectionModel().selectedItemProperty(), this::onSelectProposal);
daoPeriodService.getPhaseProperty().addListener(phaseChangeListener);
onPhaseChanged(daoPeriodService.getPhaseProperty().get());
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
selectedProposalSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectProposal);
bsqBlockChainChangeDispatcher.addBsqBlockChainListener(this);
proposalCollectionsService.getAllProposals().addListener(proposalListChangeListener);
updateList();
onPhaseChanged(daoPeriodService.getPhaseProperty().get());
sortedList.comparatorProperty().bind(proposalTableView.comparatorProperty());
updateProposalList();
}
@Override
protected void deactivate() {
phaseSubscription.unsubscribe();
daoPeriodService.getPhaseProperty().removeListener(phaseChangeListener);
sortedList.comparatorProperty().unbind();
selectedProposalSubscription.unsubscribe();
daoPeriodService.getPhaseProperty().removeListener(phaseChangeListener);
bsqBlockChainChangeDispatcher.removeBsqBlockChainListener(this);
proposalCollectionsService.getAllProposals().removeListener(proposalListChangeListener);
proposalListItems.forEach(ProposalListItem::cleanup);
sortedList.comparatorProperty().unbind();
tableView.getSelectionModel().clearSelection();
removeProposalDisplay();
proposalListItems.forEach(ProposalListItem::cleanup);
proposalTableView.getSelectionModel().clearSelection();
selectedProposalListItem = null;
}
@ -199,16 +165,93 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
//UserThread.execute(this::updateList);
}
abstract protected void updateList();
///////////////////////////////////////////////////////////////////////////////////////////
// Create views
///////////////////////////////////////////////////////////////////////////////////////////
protected void createProposalsTableView() {
createProposalsTableView(Res.get("dao.proposal.active.header"), -10);
}
protected void createProposalsTableView(String header, double top) {
TableGroupHeadline proposalsHeadline = new TableGroupHeadline(header);
GridPane.setRowIndex(proposalsHeadline, ++gridRow);
GridPane.setMargin(proposalsHeadline, new Insets(top, -10, -10, -10));
GridPane.setColumnSpan(proposalsHeadline, 2);
root.getChildren().add(proposalsHeadline);
proposalTableView = new TableView<>();
proposalTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
proposalTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
createProposalColumns(proposalTableView);
GridPane.setRowIndex(proposalTableView, gridRow);
GridPane.setMargin(proposalTableView, new Insets(top + 20, -10, 5, -10));
GridPane.setColumnSpan(proposalTableView, 2);
GridPane.setHgrow(proposalTableView, Priority.ALWAYS);
root.getChildren().add(proposalTableView);
proposalTableView.setItems(sortedList);
proposalViewItems.add(proposalsHeadline);
proposalViewItems.add(proposalTableView);
}
protected void createProposalDisplay() {
proposalDisplay = new ProposalDisplay(detailsGridPane, bsqFormatter, bsqWalletService, null);
proposalDisplayView = proposalDisplay.getView();
GridPane.setMargin(proposalDisplayView, new Insets(10, -10, 0, -10));
GridPane.setRowIndex(proposalDisplayView, ++gridRow);
GridPane.setColumnSpan(proposalDisplayView, 2);
root.getChildren().add(proposalDisplayView);
}
protected void hideProposalDisplay() {
proposalDisplay.removeAllFields();
proposalDisplayView.setVisible(false);
proposalDisplayView.setManaged(false);
}
protected void showProposalDisplay(Proposal proposal) {
proposalDisplayView.setVisible(true);
proposalDisplayView.setManaged(true);
proposalDisplay.createAllFields(Res.get("dao.proposal.selectedProposal"), 0, 0, proposal.getType(),
false, false);
proposalDisplay.setEditable(false);
proposalDisplay.applyProposalPayload(proposal.getProposalPayload());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handlers
///////////////////////////////////////////////////////////////////////////////////////////
protected void onSelectProposal(ProposalListItem item) {
selectedProposalListItem = item;
if (item != null)
showProposalDisplay(item.getProposal());
else
hideProposalDisplay();
}
protected void onPhaseChanged(DaoPeriodService.Phase phase) {
if (!phase.equals(this.currentPhase)) {
this.currentPhase = phase;
onSelectProposal(selectedProposalListItem);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
protected void doUpdateList(FilteredList<Proposal> list) {
abstract protected void updateProposalList();
protected void doUpdateProposalList(List<Proposal> list) {
proposalListItems.forEach(ProposalListItem::cleanup);
proposalListItems.setAll(list.stream()
@ -221,34 +264,23 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
bsqFormatter))
.collect(Collectors.toSet()));
if (list.isEmpty() && proposalDisplay != null)
proposalDisplay.removeAllFields();
if (list.isEmpty())
hideProposalDisplay();
}
protected void onSelectProposal(ProposalListItem item) {
selectedProposalListItem = item;
if (item != null) {
final Proposal proposal = item.getProposal();
removeProposalDisplay();
//TODO
proposalDisplay = new ProposalDisplay(detailsGridPane, bsqFormatter, bsqWalletService, null);
proposalDisplay.createAllFields(Res.get("dao.proposal.selectedProposal"), 0, 0, proposal.getType(),
false, false);
proposalDisplay.setAllFieldsEditable(false);
proposalDisplay.fillWithData(proposal.getProposalPayload());
}
protected void changeProposalViewItemsVisibility(boolean value) {
proposalViewItems.forEach(node -> {
node.setVisible(value);
node.setManaged(value);
});
}
protected void removeProposalDisplay() {
if (proposalDisplay != null) {
proposalDisplay.removeAllFields();
proposalDisplay = null;
}
}
protected void createColumns(TableView<ProposalListItem> tableView) {
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns
///////////////////////////////////////////////////////////////////////////////////////////
protected void createProposalColumns(TableView<ProposalListItem> tableView) {
TableColumn<ProposalListItem, ProposalListItem> dateColumn = new AutoTooltipTableColumn<ProposalListItem, ProposalListItem>(Res.get("shared.dateTime")) {
{
setMinWidth(190);
@ -302,8 +334,31 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
nameColumn.setComparator(Comparator.comparing(o2 -> o2.getProposal().getProposalPayload().getName()));
tableView.getColumns().add(nameColumn);
TableColumn<ProposalListItem, ProposalListItem> uidColumn = new AutoTooltipTableColumn<>(Res.get("shared.id"));
TableColumn<ProposalListItem, ProposalListItem> titleColumn = new AutoTooltipTableColumn<>(Res.get("dao.proposal.title"));
titleColumn.setPrefWidth(100);
titleColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
titleColumn.setCellFactory(
new Callback<TableColumn<ProposalListItem, ProposalListItem>, TableCell<ProposalListItem,
ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(
TableColumn<ProposalListItem, ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(item.getProposal().getProposalPayload().getTitle());
else
setText("");
}
};
}
});
titleColumn.setComparator(Comparator.comparing(o2 -> o2.getProposal().getProposalPayload().getTitle()));
tableView.getColumns().add(titleColumn);
TableColumn<ProposalListItem, ProposalListItem> uidColumn = new AutoTooltipTableColumn<>(Res.get("shared.id"));
uidColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
uidColumn.setCellFactory(
new Callback<TableColumn<ProposalListItem, ProposalListItem>, TableCell<ProposalListItem,
@ -338,7 +393,9 @@ public abstract class BaseProposalView extends ActivatableView<GridPane, Void> i
});
uidColumn.setComparator(Comparator.comparing(o -> o.getProposal().getProposalPayload().getUid()));
tableView.getColumns().add(uidColumn);
}
protected void createConfidenceColumn(TableView<ProposalListItem> tableView) {
TableColumn<ProposalListItem, ProposalListItem> confidenceColumn = new TableColumn<>(Res.get("shared.confirmations"));
confidenceColumn.setMinWidth(130);
confidenceColumn.setMaxWidth(confidenceColumn.getMinWidth());

View file

@ -60,8 +60,8 @@ public class ProposalDetailsWindow extends Overlay<ProposalDetailsWindow> {
proposalDisplay.createAllFields(Res.get("dao.proposal.details"), 1, Layout.GROUP_DISTANCE,
proposalPayload.getType(), false, true);
proposalDisplay.setAllFieldsEditable(false);
proposalDisplay.fillWithData(proposalPayload);
proposalDisplay.setEditable(false);
proposalDisplay.applyProposalPayload(proposalPayload);
closeButton = addButtonAfterGroup(gridPane, proposalDisplay.incrementAndGetGridRow(), Res.get("shared.close"));
closeButton.setOnAction(e -> doClose());

View file

@ -92,6 +92,7 @@ public class ProposalDisplay {
public void createAllFields(String title, int gridRowStartIndex, double top, ProposalType proposalType,
boolean isMakeProposalScreen, boolean showDetails) {
removeAllFields();
this.gridRowStartIndex = gridRowStartIndex;
this.gridRow = gridRowStartIndex;
int rowSpan;
@ -114,7 +115,7 @@ public class ProposalDisplay {
top == Layout.GROUP_DISTANCE ? Layout.FIRST_ROW_AND_GROUP_DISTANCE : Layout.FIRST_ROW_DISTANCE).second;
}
titleTextField = addLabelInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.title")).second;
titleTextField = addLabelInputTextField(gridPane, ++gridRow, Res.getWithCol("dao.proposal.title")).second;
descriptionTextArea = addLabelTextArea(gridPane, ++gridRow, Res.get("dao.proposal.display.description"), Res.get("dao.proposal.display.description.prompt", maxLengthDescriptionText)).second;
descriptionTextArea.setMaxHeight(42); // for 2 lines
@ -152,7 +153,7 @@ public class ProposalDisplay {
Res.get("dao.proposal.display.txId"), "").second;
}
public void fillWithData(ProposalPayload proposalPayload) {
public void applyProposalPayload(ProposalPayload proposalPayload) {
if (uidTextField != null)
uidTextField.setText(proposalPayload.getUid());
nameTextField.setText(proposalPayload.getName());
@ -204,7 +205,7 @@ public class ProposalDisplay {
bsqAddressTextField.setText("B" + bsqWalletService.getUnusedAddress().toBase58());
}
public void setAllFieldsEditable(boolean isEditable) {
public void setEditable(boolean isEditable) {
nameTextField.setEditable(isEditable);
titleTextField.setEditable(isEditable);
descriptionTextArea.setEditable(isEditable);
@ -224,9 +225,7 @@ public class ProposalDisplay {
public void removeAllFields() {
if (gridRow > 0) {
clearForm();
GUIUtil.removeChildrenFromGridPaneRows(gridPane, gridRowStartIndex, gridRow + 1);
gridRow = gridRowStartIndex;
}
}

View file

@ -123,10 +123,9 @@ public class ProposalListItem implements BsqBlockChainListener {
daoPeriodService.getPhaseProperty().addListener(phaseChangeListener);
proposal.getVoteResultProperty().addListener(voteResultChangeListener);
applyState(daoPeriodService.getPhaseProperty().get(), proposal.getVoteResult());
}
private void applyState(DaoPeriodService.Phase newValue, VoteResult voteResult) {
public void applyState(DaoPeriodService.Phase newValue, VoteResult voteResult) {
actionButton.setText("");
actionButton.setVisible(false);
actionButton.setOnAction(null);
@ -141,7 +140,10 @@ public class ProposalListItem implements BsqBlockChainListener {
actionButton.setText(Res.get("shared.remove"));
actionButton.setGraphic(actionButtonIconView);
actionButtonIconView.setId("image-remove");
actionButton.setOnAction(e -> onRemoveHandler.run());
actionButton.setOnAction(e -> {
if (onRemoveHandler != null)
onRemoveHandler.run();
});
actionNode = actionButton;
}
break;

View file

@ -30,6 +30,8 @@ import bisq.desktop.main.dao.proposal.active.ActiveProposalsView;
import bisq.desktop.main.dao.proposal.closed.ClosedProposalsView;
import bisq.desktop.main.dao.proposal.dashboard.ProposalDashboardView;
import bisq.desktop.main.dao.proposal.make.MakeProposalView;
import bisq.desktop.main.dao.proposal.myvotes.MyVotesView;
import bisq.desktop.main.dao.proposal.votes.VotesView;
import bisq.core.locale.Res;
@ -49,7 +51,7 @@ public class ProposalView extends ActivatableViewAndModel {
private final ViewLoader viewLoader;
private final Navigation navigation;
private MenuItem dashboard, create, proposed, past;
private MenuItem dashboard, make, active, myVotes, votes, closed;
private Navigation.Listener listener;
@FXML
@ -77,18 +79,24 @@ public class ProposalView extends ActivatableViewAndModel {
ToggleGroup toggleGroup = new ToggleGroup();
dashboard = new MenuItem(navigation, toggleGroup, Res.get("shared.dashboard"), ProposalDashboardView.class, AwesomeIcon.DASHBOARD);
create = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.make"), MakeProposalView.class, AwesomeIcon.EDIT);
proposed = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.active"), ActiveProposalsView.class, AwesomeIcon.STACKEXCHANGE);
past = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.closed"), ClosedProposalsView.class, AwesomeIcon.LIST);
leftVBox.getChildren().addAll(dashboard, create, proposed, past);
make = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.make"), MakeProposalView.class, AwesomeIcon.EDIT);
active = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.active"), ActiveProposalsView.class, AwesomeIcon.STACKEXCHANGE);
myVotes = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.myVotes"), MyVotesView.class,
AwesomeIcon.LIST);
votes = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.votes"), VotesView.class, AwesomeIcon
.LIST);
closed = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.closed"), ClosedProposalsView.class, AwesomeIcon.LIST);
leftVBox.getChildren().addAll(dashboard, make, active, myVotes, votes, closed);
}
@Override
protected void activate() {
dashboard.activate();
create.activate();
proposed.activate();
past.activate();
make.activate();
active.activate();
myVotes.activate();
votes.activate();
closed.activate();
navigation.addListener(listener);
ViewPath viewPath = navigation.getCurrentPath();
@ -110,9 +118,11 @@ public class ProposalView extends ActivatableViewAndModel {
navigation.removeListener(listener);
dashboard.deactivate();
create.deactivate();
proposed.deactivate();
past.deactivate();
make.deactivate();
active.deactivate();
myVotes.deactivate();
votes.deactivate();
closed.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
@ -120,9 +130,11 @@ public class ProposalView extends ActivatableViewAndModel {
content.getChildren().setAll(view.getRoot());
if (view instanceof ProposalDashboardView) dashboard.setSelected(true);
else if (view instanceof MakeProposalView) create.setSelected(true);
else if (view instanceof ActiveProposalsView) proposed.setSelected(true);
else if (view instanceof ClosedProposalsView) past.setSelected(true);
else if (view instanceof MakeProposalView) make.setSelected(true);
else if (view instanceof ActiveProposalsView) active.setSelected(true);
else if (view instanceof MyVotesView) myVotes.setSelected(true);
else if (view instanceof VotesView) votes.setSelected(true);
else if (view instanceof ClosedProposalsView) closed.setSelected(true);
}
public Class<? extends View> getSelectedViewClass() {

View file

@ -19,6 +19,7 @@ package bisq.desktop.main.dao.proposal.active;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.dao.proposal.BaseProposalView;
import bisq.desktop.main.dao.proposal.ProposalListItem;
import bisq.desktop.main.overlays.popups.Popup;
@ -41,6 +42,7 @@ import bisq.core.dao.vote.VoteService;
import bisq.core.locale.Res;
import bisq.common.crypto.CryptoException;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import org.bitcoinj.core.Coin;
@ -53,6 +55,7 @@ import com.google.common.util.concurrent.FutureCallback;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
@ -61,7 +64,9 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.annotation.Nullable;
@ -71,13 +76,14 @@ import static bisq.desktop.util.FormBuilder.addLabelInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class ActiveProposalsView extends BaseProposalView {
public class ActiveProposalsView extends BaseProposalView implements BsqBalanceListener {
private final VoteService voteService;
private Button removeButton, acceptButton, rejectButton, cancelVoteButton, voteButton;
private InputTextField stakeInputTextField;
private BsqBalanceListener bsqBalanceListener;
private List<Node> voteViewItems = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
@ -100,70 +106,100 @@ public class ActiveProposalsView extends BaseProposalView {
public void initialize() {
super.initialize();
createTableView();
addTitledGroupBg(root, ++gridRow, 1, Res.get("dao.proposal.vote.header"), Layout.GROUP_DISTANCE - 20);
stakeInputTextField = addLabelInputTextField(root, gridRow, Res.getWithCol("dao.proposal.vote.stake"), Layout
.FIRST_ROW_AND_GROUP_DISTANCE - 20).second;
voteButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.proposal.vote.button"));
createProposalsTableView();
createVoteView();
createProposalDisplay();
bsqBalanceListener = (availableBalance, unverifiedBalance) -> stakeInputTextField.setPromptText(Res.get("dao.proposal.vote.stake.prompt", bsqFormatter.formatCoinWithCode(availableBalance)));
}
@Override
protected void activate() {
super.activate();
bsqWalletService.addBsqBalanceListener(bsqBalanceListener);
stakeInputTextField.setPromptText(Res.get("dao.proposal.vote.stake.prompt", bsqFormatter.formatCoinWithCode
(bsqWalletService.getAvailableBalance())));
bsqWalletService.addBsqBalanceListener(this);
voteButton.setOnAction(e -> {
Coin stake = bsqFormatter.parseToCoin(stakeInputTextField.getText());
// TODO verify stake
//TODO show popup
try {
voteService.publishBlindVote(stake, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction result) {
//TODO
}
onUpdateBalances(bsqWalletService.getAvailableBalance(),
bsqWalletService.getPendingBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockedInBondsBalance());
@Override
public void onFailure(Throwable t) {
//TODO
}
});
} catch (CryptoException e1) {
//TODO show error popup
e1.printStackTrace();
} catch (InsufficientBsqException e1) {
e1.printStackTrace();
} catch (WalletException e1) {
e1.printStackTrace();
} catch (TransactionVerificationException e1) {
e1.printStackTrace();
} catch (InsufficientMoneyException e1) {
e1.printStackTrace();
} catch (ChangeBelowDustException e1) {
e1.printStackTrace();
}
});
if (voteButton != null) {
voteButton.setOnAction(e -> {
Coin stake = bsqFormatter.parseToCoin(stakeInputTextField.getText());
// TODO verify stake
//TODO show popup
try {
voteService.publishBlindVote(stake, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction result) {
//TODO
}
@Override
public void onFailure(Throwable t) {
//TODO
}
});
} catch (CryptoException e1) {
//TODO show error popup
e1.printStackTrace();
} catch (InsufficientBsqException e1) {
e1.printStackTrace();
} catch (WalletException e1) {
e1.printStackTrace();
} catch (TransactionVerificationException e1) {
e1.printStackTrace();
} catch (InsufficientMoneyException e1) {
e1.printStackTrace();
} catch (ChangeBelowDustException e1) {
e1.printStackTrace();
}
});
}
}
@Override
protected void deactivate() {
super.deactivate();
bsqWalletService.removeBsqBalanceListener(bsqBalanceListener);
voteButton.setOnAction(null);
bsqWalletService.removeBsqBalanceListener(this);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Create views
///////////////////////////////////////////////////////////////////////////////////////////
private void createVoteView() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 1, Res.get("dao.proposal.vote.header"),
Layout.GROUP_DISTANCE - 20);
final Tuple2<Label, InputTextField> tuple2 = addLabelInputTextField(root, gridRow,
Res.getWithCol("dao.proposal.vote.stake"), Layout
.FIRST_ROW_AND_GROUP_DISTANCE - 20);
stakeInputTextField = tuple2.second;
voteButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.proposal.vote.button"));
voteViewItems.add(titledGroupBg);
voteViewItems.add(tuple2.first);
voteViewItems.add(stakeInputTextField);
voteViewItems.add(voteButton);
changeVoteViewItemsVisibility(false);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handlers
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void updateList() {
doUpdateList(proposalCollectionsService.getActiveProposals());
public void onUpdateBalances(Coin confirmedBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockedInBondsBalance) {
stakeInputTextField.setPromptText(Res.get("dao.proposal.vote.stake.prompt",
bsqFormatter.formatCoinWithCode(confirmedBalance)));
}
protected void onSelectProposal(ProposalListItem item) {
@ -209,23 +245,12 @@ public class ActiveProposalsView extends BaseProposalView {
updateStateAfterVote();
}
private void updateStateAfterVote() {
removeProposalDisplay();
proposalCollectionsService.persist();
tableView.getSelectionModel().clearSelection();
}
private void onRemove() {
if (proposalCollectionsService.removeProposal(selectedProposalListItem.getProposal()))
removeProposalDisplay();
else
new Popup<>().warning(Res.get("dao.proposal.active.remove.failed")).show();
tableView.getSelectionModel().clearSelection();
}
@Override
protected void onPhaseChanged(DaoPeriodService.Phase phase) {
super.onPhaseChanged(phase);
changeVoteViewItemsVisibility(phase == DaoPeriodService.Phase.OPEN_FOR_VOTING);
if (removeButton != null) {
removeButton.setManaged(false);
removeButton.setVisible(false);
@ -286,9 +311,46 @@ public class ActiveProposalsView extends BaseProposalView {
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createColumns(TableView<ProposalListItem> tableView) {
super.createColumns(tableView);
protected void updateProposalList() {
doUpdateProposalList(proposalCollectionsService.getActiveProposals());
}
private void updateStateAfterVote() {
hideProposalDisplay();
proposalCollectionsService.persist();
proposalTableView.getSelectionModel().clearSelection();
}
private void changeVoteViewItemsVisibility(boolean value) {
voteViewItems.forEach(node -> {
node.setVisible(value);
node.setManaged(value);
});
}
private void onRemove() {
if (proposalCollectionsService.removeProposal(selectedProposalListItem.getProposal()))
hideProposalDisplay();
else
new Popup<>().warning(Res.get("dao.proposal.active.remove.failed")).show();
proposalTableView.getSelectionModel().clearSelection();
}
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void createProposalColumns(TableView<ProposalListItem> tableView) {
super.createProposalColumns(tableView);
createConfidenceColumn(tableView);
TableColumn<ProposalListItem, ProposalListItem> actionColumn = new TableColumn<>();
actionColumn.setMinWidth(130);
@ -313,7 +375,11 @@ public class ActiveProposalsView extends BaseProposalView {
if (node == null) {
node = item.getActionNode();
setGraphic(node);
item.setOnRemoveHandler(ActiveProposalsView.this::onRemove);
item.setOnRemoveHandler(() -> {
ActiveProposalsView.this.selectedProposalListItem = item;
ActiveProposalsView.this.onRemove();
});
item.applyState(currentPhase, item.getProposal().getVoteResultProperty().get());
}
} else {
setGraphic(null);

View file

@ -51,8 +51,7 @@ public class ClosedProposalsView extends BaseProposalView {
public void initialize() {
super.initialize();
createTableView();
createProposalsTableView();
createProposalDisplay();
}
@ -67,8 +66,8 @@ public class ClosedProposalsView extends BaseProposalView {
}
@Override
protected void updateList() {
doUpdateList(proposalCollectionsService.getClosedProposals());
protected void updateProposalList() {
doUpdateProposalList(proposalCollectionsService.getClosedProposals());
}
}

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.proposal.myvotes.MyVotesView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,377 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.myvotes;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.TableGroupHeadline;
import bisq.desktop.main.dao.proposal.BaseProposalView;
import bisq.desktop.main.dao.proposal.ProposalListItem;
import bisq.desktop.util.BsqFormatter;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.DaoPeriodService;
import bisq.core.dao.blockchain.BsqBlockChain;
import bisq.core.dao.blockchain.BsqBlockChainChangeDispatcher;
import bisq.core.dao.blockchain.ReadableBsqBlockChain;
import bisq.core.dao.proposal.ProposalCollectionsService;
import bisq.core.dao.proposal.ProposalList;
import bisq.core.dao.vote.BooleanVoteResult;
import bisq.core.dao.vote.VoteResult;
import bisq.core.dao.vote.VoteService;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import javax.inject.Inject;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.geometry.Insets;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.util.Callback;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@FxmlView
public class MyVotesView extends BaseProposalView {
private final VoteService voteService;
private final ReadableBsqBlockChain readableBsqBlockChain;
private final Preferences preferences;
private final ObservableList<VoteListItem> voteListItems = FXCollections.observableArrayList();
private SortedList<VoteListItem> sortedList = new SortedList<>(voteListItems);
private TableView<VoteListItem> votesTableView;
private VoteListItem selectedVoteListItem;
private Subscription selectedVoteSubscription;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private MyVotesView(ProposalCollectionsService voteRequestManger,
DaoPeriodService daoPeriodService,
VoteService voteService,
BsqWalletService bsqWalletService,
BsqBlockChain bsqBlockChain,
BsqBlockChainChangeDispatcher bsqBlockChainChangeDispatcher,
ReadableBsqBlockChain readableBsqBlockChain,
Preferences preferences,
BsqFormatter bsqFormatter) {
super(voteRequestManger, bsqWalletService, bsqBlockChain, bsqBlockChainChangeDispatcher, daoPeriodService,
bsqFormatter);
this.voteService = voteService;
this.readableBsqBlockChain = readableBsqBlockChain;
this.preferences = preferences;
}
@Override
public void initialize() {
super.initialize();
createVoteTableView();
createProposalsTableView(Res.get("dao.proposal.myVotes.proposals.header"), Layout.GROUP_DISTANCE - 20);
createProposalDisplay();
changeProposalViewItemsVisibility(false);
}
@Override
protected void activate() {
super.activate();
selectedVoteSubscription = EasyBind.subscribe(votesTableView.getSelectionModel().selectedItemProperty(),
this::onSelectVote);
sortedList.comparatorProperty().bind(votesTableView.comparatorProperty());
voteListItems.clear();
List<VoteListItem> items = voteService.getVoteList().stream()
.map(vote -> new VoteListItem(vote, bsqWalletService, readableBsqBlockChain))
.collect(Collectors.toList());
voteListItems.addAll(items);
}
private void onSelectVote(VoteListItem voteListItem) {
selectedVoteListItem = voteListItem;
changeProposalViewItemsVisibility(selectedVoteListItem != null);
updateProposalList();
}
@Override
protected void deactivate() {
super.deactivate();
selectedVoteSubscription.unsubscribe();
changeProposalViewItemsVisibility(false);
votesTableView.getSelectionModel().clearSelection();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Create views
///////////////////////////////////////////////////////////////////////////////////////////
private void createVoteTableView() {
TableGroupHeadline proposalsHeadline = new TableGroupHeadline(Res.get("dao.proposal.myVotes.header"));
GridPane.setRowIndex(proposalsHeadline, ++gridRow);
GridPane.setMargin(proposalsHeadline, new Insets(-10, -10, -10, -10));
GridPane.setColumnSpan(proposalsHeadline, 2);
root.getChildren().add(proposalsHeadline);
votesTableView = new TableView<>();
votesTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
votesTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
createVoteColumns(votesTableView);
GridPane.setRowIndex(votesTableView, gridRow);
GridPane.setMargin(votesTableView, new Insets(10, -10, 5, -10));
GridPane.setColumnSpan(votesTableView, 2);
GridPane.setHgrow(votesTableView, Priority.ALWAYS);
root.getChildren().add(votesTableView);
votesTableView.setItems(sortedList);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Handler
///////////////////////////////////////////////////////////////////////////////////////////
private void onShowProposalList(ProposalList proposalList) {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void updateProposalList() {
if (selectedVoteListItem != null)
doUpdateProposalList(selectedVoteListItem.getVote().getProposalList().getList());
}
///////////////////////////////////////////////////////////////////////////////////////////
// TableColumns
///////////////////////////////////////////////////////////////////////////////////////////
private void createVoteColumns(TableView<VoteListItem> tableView) {
TableColumn<VoteListItem, VoteListItem> dateColumn = new AutoTooltipTableColumn<VoteListItem, VoteListItem>(Res.get("shared.dateTime")) {
{
setMinWidth(190);
setMaxWidth(190);
}
};
dateColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<VoteListItem, VoteListItem>, TableCell<VoteListItem,
VoteListItem>>() {
@Override
public TableCell<VoteListItem, VoteListItem> call(
TableColumn<VoteListItem, VoteListItem> column) {
return new TableCell<VoteListItem, VoteListItem>() {
@Override
public void updateItem(final VoteListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(bsqFormatter.formatDateTime(new Date(item.getVote()
.getDate())));
else
setText("");
}
};
}
});
dateColumn.setComparator(Comparator.comparing(o3 -> o3.getVote().getDate()));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getColumns().add(dateColumn);
tableView.getSortOrder().add(dateColumn);
TableColumn<VoteListItem, VoteListItem> proposalListColumn = new AutoTooltipTableColumn<>(Res.get("dao.proposal.myVotes.proposalList"));
proposalListColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
proposalListColumn.setCellFactory(
new Callback<TableColumn<VoteListItem, VoteListItem>, TableCell<VoteListItem,
VoteListItem>>() {
@Override
public TableCell<VoteListItem, VoteListItem> call(
TableColumn<VoteListItem, VoteListItem> column) {
return new TableCell<VoteListItem, VoteListItem>() {
@Override
public void updateItem(final VoteListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
ProposalList proposalList = item.getVote().getProposalList();
HyperlinkWithIcon field = new HyperlinkWithIcon(Res.get("dao.proposal.myVotes.showProposalList"), AwesomeIcon.INFO_SIGN);
field.setOnAction(event -> onShowProposalList(proposalList));
field.setTooltip(new Tooltip(Res.get("dao.proposal.myVotes.tooltip.showProposalList")));
setGraphic(field);
} else {
setGraphic(null);
}
}
};
}
});
proposalListColumn.setSortable(false);
tableView.getColumns().add(proposalListColumn);
TableColumn<VoteListItem, VoteListItem> txColumn = new AutoTooltipTableColumn<>(Res.get("dao.proposal.myVotes.tx"));
txColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
txColumn.setCellFactory(
new Callback<TableColumn<VoteListItem, VoteListItem>, TableCell<VoteListItem,
VoteListItem>>() {
@Override
public TableCell<VoteListItem, VoteListItem> call(
TableColumn<VoteListItem, VoteListItem> column) {
return new TableCell<VoteListItem, VoteListItem>() {
@Override
public void updateItem(final VoteListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
String txId = item.getVote().getBlindVote().getTxId();
HyperlinkWithIcon hyperlinkWithIcon = new HyperlinkWithIcon(txId, AwesomeIcon.EXTERNAL_LINK);
hyperlinkWithIcon.setOnAction(event -> {
if (txId != null)
GUIUtil.openWebPage(preferences.getBlockChainExplorer().txUrl + txId);
});
hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", txId)));
setGraphic(hyperlinkWithIcon);
} else {
setGraphic(null);
}
}
};
}
});
txColumn.setComparator(Comparator.comparing(o2 -> o2.getVote().getBlindVote().getTxId()));
tableView.getColumns().add(txColumn);
TableColumn<VoteListItem, VoteListItem> confidenceColumn = new TableColumn<>(Res.get("shared.confirmations"));
confidenceColumn.setMinWidth(130);
confidenceColumn.setMaxWidth(confidenceColumn.getMinWidth());
confidenceColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
confidenceColumn.setCellFactory(new Callback<TableColumn<VoteListItem, VoteListItem>,
TableCell<VoteListItem, VoteListItem>>() {
@Override
public TableCell<VoteListItem, VoteListItem> call(TableColumn<VoteListItem,
VoteListItem> column) {
return new TableCell<VoteListItem, VoteListItem>() {
@Override
public void updateItem(final VoteListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
setGraphic(item.getTxConfidenceIndicator());
} else {
setGraphic(null);
}
}
};
}
});
confidenceColumn.setComparator(Comparator.comparing(VoteListItem::getConfirmations));
tableView.getColumns().add(confidenceColumn);
}
@Override
protected void createProposalColumns(TableView<ProposalListItem> tableView) {
super.createProposalColumns(tableView);
TableColumn<ProposalListItem, ProposalListItem> actionColumn = new TableColumn<>(Res.get("dao.proposal.myVotes.vote"));
actionColumn.setMinWidth(50);
actionColumn.setMaxWidth(actionColumn.getMinWidth());
actionColumn.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
actionColumn.setCellFactory(new Callback<TableColumn<ProposalListItem, ProposalListItem>,
TableCell<ProposalListItem, ProposalListItem>>() {
@Override
public TableCell<ProposalListItem, ProposalListItem> call(TableColumn<ProposalListItem,
ProposalListItem> column) {
return new TableCell<ProposalListItem, ProposalListItem>() {
ImageView actionButtonIconView;
@Override
public void updateItem(final ProposalListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
actionButtonIconView = new ImageView();
VoteResult voteResult = item.getProposal().getVoteResult();
if (voteResult instanceof BooleanVoteResult) {
if (((BooleanVoteResult) voteResult).isAccepted()) {
actionButtonIconView.setId("accepted");
} else {
actionButtonIconView.setId("rejected");
}
} else {
//TODO
}
setGraphic(actionButtonIconView);
} else {
setGraphic(null);
}
}
};
}
});
actionColumn.setComparator(Comparator.comparing(ProposalListItem::getConfirmations));
tableView.getColumns().add(actionColumn);
}
}

View file

@ -0,0 +1,162 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.myvotes;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.core.btc.listeners.TxConfidenceListener;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.dao.blockchain.BsqBlockChainListener;
import bisq.core.dao.blockchain.ReadableBsqBlockChain;
import bisq.core.dao.blockchain.vo.Tx;
import bisq.core.dao.vote.Vote;
import bisq.core.locale.Res;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import javafx.scene.control.Tooltip;
import javafx.beans.value.ChangeListener;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@ToString
@Slf4j
@EqualsAndHashCode
public class VoteListItem implements BsqBlockChainListener {
@Getter
private final Vote vote;
private final BsqWalletService bsqWalletService;
private final ReadableBsqBlockChain readableBsqBlockChain;
private final ChangeListener<Number> chainHeightListener;
@Getter
private TxConfidenceIndicator txConfidenceIndicator;
@Getter
private Integer confirmations = 0;
private TxConfidenceListener txConfidenceListener;
private Tooltip tooltip = new Tooltip(Res.get("confidence.unknown"));
private Transaction walletTransaction;
@Setter
private Runnable onRemoveHandler;
VoteListItem(Vote vote, BsqWalletService bsqWalletService, ReadableBsqBlockChain readableBsqBlockChain) {
this.vote = vote;
this.bsqWalletService = bsqWalletService;
this.readableBsqBlockChain = readableBsqBlockChain;
txConfidenceIndicator = new TxConfidenceIndicator();
txConfidenceIndicator.setId("funds-confidence");
txConfidenceIndicator.setProgress(-1);
txConfidenceIndicator.setPrefSize(24, 24);
txConfidenceIndicator.setTooltip(tooltip);
chainHeightListener = (observable, oldValue, newValue) -> setupConfidence();
bsqWalletService.getChainHeightProperty().addListener(chainHeightListener);
setupConfidence();
}
@Override
public void onBsqBlockChainChanged() {
setupConfidence();
}
private void setupConfidence() {
final Tx tx = readableBsqBlockChain.getTxMap().get(vote.getBlindVote().getTxId());
if (tx != null) {
final String txId = tx.getId();
// We cache the walletTransaction once found
if (walletTransaction == null) {
final Optional<Transaction> transactionOptional = bsqWalletService.isWalletTransaction(txId);
transactionOptional.ifPresent(transaction -> walletTransaction = transaction);
}
if (walletTransaction != null) {
// It is our tx so we get confidence updates
if (txConfidenceListener == null) {
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence.getConfidenceType(), confidence.getDepthInBlocks(), confidence.numBroadcastPeers());
}
};
bsqWalletService.addTxConfidenceListener(txConfidenceListener);
}
} else {
// tx from other users, we dont get confidence updates but as we have the bsq tx we can calculate it
// we get setupConfidence called at each new block from above listener so no need to register a new listener
int depth = bsqWalletService.getChainHeightProperty().get() - tx.getBlockHeight() + 1;
if (depth > 0)
updateConfidence(TransactionConfidence.ConfidenceType.BUILDING, depth, -1);
//log.error("name={}, id ={}, depth={}", compensationRequest.getPayload().getName(), compensationRequest.getPayload().getUid(), depth);
}
final TransactionConfidence confidence = bsqWalletService.getConfidenceForTxId(txId);
if (confidence != null)
updateConfidence(confidence, confidence.getDepthInBlocks());
}
}
private void updateConfidence(TransactionConfidence confidence, int depthInBlocks) {
if (confidence != null) {
updateConfidence(confidence.getConfidenceType(), confidence.getDepthInBlocks(), confidence.numBroadcastPeers());
confirmations = depthInBlocks;
}
}
public void cleanup() {
bsqWalletService.getChainHeightProperty().removeListener(chainHeightListener);
if (txConfidenceListener != null)
bsqWalletService.removeTxConfidenceListener(txConfidenceListener);
}
private void updateConfidence(TransactionConfidence.ConfidenceType confidenceType, int depthInBlocks, int numBroadcastPeers) {
switch (confidenceType) {
case UNKNOWN:
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
break;
case PENDING:
tooltip.setText(Res.get("confidence.seen", numBroadcastPeers > -1 ? numBroadcastPeers : Res.get("shared.na")));
txConfidenceIndicator.setProgress(-1.0);
break;
case BUILDING:
tooltip.setText(Res.get("confidence.confirmed", depthInBlocks));
txConfidenceIndicator.setProgress(Math.min(1, (double) depthInBlocks / 6.0));
break;
case DEAD:
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
break;
}
txConfidenceIndicator.setPrefSize(24, 24);
}
}

View file

@ -0,0 +1,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.proposal.votes.VotesView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -0,0 +1,50 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.dao.proposal.votes;
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import javax.inject.Inject;
import javafx.scene.layout.GridPane;
@FxmlView
public class VotesView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private VotesView() {
}
@Override
public void initialize() {
}
@Override
protected void activate() {
}
@Override
protected void deactivate() {
}
}

View file

@ -42,7 +42,8 @@ import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
public class BsqBalanceUtil implements BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BsqFormatter bsqFormatter;
private TextField availableBalanceTextField, unverifiedBalanceTextField, totalBalanceTextField;
private TextField confirmedBalanceTextField, pendingBalanceTextField, lockedForVoteBalanceTextField,
totalBalanceTextField;
@Inject
private BsqBalanceUtil(BsqWalletService bsqWalletService,
@ -52,27 +53,37 @@ public class BsqBalanceUtil implements BsqBalanceListener {
}
public int addGroup(GridPane gridPane, int gridRow) {
addTitledGroupBg(gridPane, gridRow, 3, Res.get("shared.balance"));
availableBalanceTextField = addLabelTextField(gridPane, gridRow, Res.getWithCol("shared.availableBsqBalance"),
addTitledGroupBg(gridPane, gridRow, 4, Res.get("shared.balance"));
confirmedBalanceTextField = addLabelTextField(gridPane, gridRow, Res.getWithCol("shared.availableBsqBalance"),
Layout.FIRST_ROW_DISTANCE).second;
availableBalanceTextField.setMouseTransparent(false);
availableBalanceTextField.setMaxWidth(150);
availableBalanceTextField.setAlignment(Pos.CENTER_RIGHT);
confirmedBalanceTextField.setMouseTransparent(false);
confirmedBalanceTextField.setMaxWidth(150);
confirmedBalanceTextField.setAlignment(Pos.CENTER_RIGHT);
unverifiedBalanceTextField = addLabelTextField(gridPane, ++gridRow, Res.getWithCol("shared.unverifiedBsqBalance")).second;
unverifiedBalanceTextField.setMouseTransparent(false);
unverifiedBalanceTextField.setMaxWidth(availableBalanceTextField.getMaxWidth());
unverifiedBalanceTextField.setAlignment(Pos.CENTER_RIGHT);
pendingBalanceTextField = addLabelTextField(gridPane, ++gridRow, Res.getWithCol("shared.unverifiedBsqBalance")).second;
pendingBalanceTextField.setMouseTransparent(false);
pendingBalanceTextField.setMaxWidth(confirmedBalanceTextField.getMaxWidth());
pendingBalanceTextField.setAlignment(Pos.CENTER_RIGHT);
lockedForVoteBalanceTextField = addLabelTextField(gridPane, ++gridRow, Res.getWithCol("shared" +
".lockedForVoteBalance")).second;
lockedForVoteBalanceTextField.setMouseTransparent(false);
lockedForVoteBalanceTextField.setMaxWidth(confirmedBalanceTextField.getMaxWidth());
lockedForVoteBalanceTextField.setAlignment(Pos.CENTER_RIGHT);
totalBalanceTextField = addLabelTextField(gridPane, ++gridRow, Res.getWithCol("shared.totalBsqBalance")).second;
totalBalanceTextField.setMouseTransparent(false);
totalBalanceTextField.setMaxWidth(availableBalanceTextField.getMaxWidth());
totalBalanceTextField.setMaxWidth(confirmedBalanceTextField.getMaxWidth());
totalBalanceTextField.setAlignment(Pos.CENTER_RIGHT);
return gridRow;
}
public void activate() {
updateAvailableBalance(bsqWalletService.getAvailableBalance(), bsqWalletService.getUnverifiedBalance());
onUpdateBalances(bsqWalletService.getAvailableBalance(),
bsqWalletService.getPendingBalance(),
bsqWalletService.getLockedForVotingBalance(),
bsqWalletService.getLockedInBondsBalance());
bsqWalletService.addBsqBalanceListener(this);
}
@ -80,10 +91,16 @@ public class BsqBalanceUtil implements BsqBalanceListener {
bsqWalletService.removeBsqBalanceListener(this);
}
@Override
public void updateAvailableBalance(Coin availableBalance, Coin unverifiedBalance) {
availableBalanceTextField.setText(bsqFormatter.formatCoinWithCode(availableBalance));
unverifiedBalanceTextField.setText(bsqFormatter.formatCoinWithCode(unverifiedBalance));
totalBalanceTextField.setText(bsqFormatter.formatCoinWithCode(availableBalance.add(unverifiedBalance)));
public void onUpdateBalances(Coin confirmedBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockedInBondsBalance) {
confirmedBalanceTextField.setText(bsqFormatter.formatCoinWithCode(confirmedBalance));
pendingBalanceTextField.setText(bsqFormatter.formatCoinWithCode(pendingBalance));
lockedForVoteBalanceTextField.setText(bsqFormatter.formatCoinWithCode(lockedForVotingBalance));
final Coin total = confirmedBalance.add(pendingBalance).add(lockedForVotingBalance).add(lockedInBondsBalance);
totalBalanceTextField.setText(bsqFormatter.formatCoinWithCode(total));
}
}

View file

@ -65,7 +65,7 @@ import static bisq.desktop.util.FormBuilder.addLabelInputTextField;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class BsqSendView extends ActivatableView<GridPane, Void> {
public class BsqSendView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
private final BsqWalletService bsqWalletService;
private final BtcWalletService btcWalletService;
private final WalletsSetup walletsSetup;
@ -82,7 +82,7 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
private Button sendButton;
private InputTextField receiversAddressInputTextField;
private ChangeListener<Boolean> focusOutListener;
private BsqBalanceListener balanceListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
@ -128,7 +128,8 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
focusOutListener = (observable, oldValue, newValue) -> {
if (!newValue)
verifyInputs();
onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getPendingBalance(),
bsqWalletService.getLockedForVotingBalance(), bsqWalletService.getLockedInBondsBalance());
};
sendButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.send"));
@ -198,8 +199,6 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
}
});
balanceListener = (availableBalance, unverifiedBalance) -> verifyInputs();
}
@Override
@ -207,8 +206,9 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
bsqBalanceUtil.activate();
receiversAddressInputTextField.focusedProperty().addListener(focusOutListener);
amountInputTextField.focusedProperty().addListener(focusOutListener);
bsqWalletService.addBsqBalanceListener(balanceListener);
verifyInputs();
bsqWalletService.addBsqBalanceListener(this);
onUpdateBalances(bsqWalletService.getAvailableBalance(), bsqWalletService.getPendingBalance(),
bsqWalletService.getLockedForVotingBalance(), bsqWalletService.getLockedInBondsBalance());
}
@Override
@ -216,11 +216,15 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
bsqBalanceUtil.deactivate();
receiversAddressInputTextField.focusedProperty().removeListener(focusOutListener);
amountInputTextField.focusedProperty().removeListener(focusOutListener);
bsqWalletService.removeBsqBalanceListener(balanceListener);
bsqWalletService.removeBsqBalanceListener(this);
}
private void verifyInputs() {
bsqValidator.setAvailableBalance(bsqWalletService.getAvailableBalance());
@Override
public void onUpdateBalances(Coin confirmedBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockedInBondsBalance) {
bsqValidator.setAvailableBalance(confirmedBalance);
boolean isValid = bsqAddressValidator.validate(receiversAddressInputTextField.getText()).isValid &&
bsqValidator.validate(amountInputTextField.getText()).isValid;
sendButton.setDisable(!isValid);

View file

@ -40,6 +40,7 @@ import bisq.core.dao.node.BsqNodeProvider;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
@ -78,7 +79,7 @@ import java.util.Set;
import java.util.stream.Collectors;
@FxmlView
public class BsqTxView extends ActivatableView<GridPane, Void> {
public class BsqTxView extends ActivatableView<GridPane, Void> implements BsqBalanceListener {
TableView<BsqTxListItem> tableView;
private Pane rootParent;
@ -99,7 +100,6 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
private int gridRow = 0;
private Label chainHeightLabel;
private BsqBlockChainListener bsqBlockChainListener;
private BsqBalanceListener bsqBalanceListener;
private ProgressBar chainSyncIndicator;
private ChangeListener<Number> chainHeightChangedListener;
@ -162,7 +162,6 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
root.getChildren().add(vBox);
walletBsqTransactionsListener = change -> updateList();
bsqBalanceListener = (availableBalance, unverifiedBalance) -> updateList();
parentHeightListener = (observable, oldValue, newValue) -> layout();
bsqBlockChainListener = this::onChainHeightChanged;
chainHeightChangedListener = (observable, oldValue, newValue) -> onChainHeightChanged();
@ -172,7 +171,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
protected void activate() {
bsqBalanceUtil.activate();
bsqWalletService.getWalletTransactions().addListener(walletBsqTransactionsListener);
bsqWalletService.addBsqBalanceListener(bsqBalanceListener);
bsqWalletService.addBsqBalanceListener(this);
btcWalletService.getChainHeightProperty().addListener(chainHeightChangedListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
@ -195,7 +194,7 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
bsqBalanceUtil.deactivate();
sortedList.comparatorProperty().unbind();
bsqWalletService.getWalletTransactions().removeListener(walletBsqTransactionsListener);
bsqWalletService.removeBsqBalanceListener(bsqBalanceListener);
bsqWalletService.removeBsqBalanceListener(this);
btcWalletService.getChainHeightProperty().removeListener(chainHeightChangedListener);
bsqNode.removeBsqBlockChainListener(bsqBlockChainListener);
@ -205,6 +204,14 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
rootParent.heightProperty().removeListener(parentHeightListener);
}
@Override
public void onUpdateBalances(Coin confirmedBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockedInBondsBalance) {
updateList();
}
private void onChainHeightChanged() {
final int bsqWalletChainHeight = bsqWalletService.getChainHeightProperty().get();
final int bsqBlockChainHeight = bsqBlockChain.getChainHeadHeight();
@ -558,6 +565,5 @@ public class BsqTxView extends ActivatableView<GridPane, Void> {
GUIUtil.openWebPage(preferences.getBsqBlockChainExplorer().addressUrl + item.getAddress());
}
}
}

View file

@ -98,7 +98,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
* Note that the create offer domain has a deeper scope in the application domain (TradeManager).
* That model is just responsible for the domain specific parts displayed needed in that UI element.
*/
class CreateOfferDataModel extends OfferDataModel {
class CreateOfferDataModel extends OfferDataModel implements BsqBalanceListener {
private final OpenOfferManager openOfferManager;
private final BsqWalletService bsqWalletService;
private final Preferences preferences;
@ -114,7 +114,6 @@ class CreateOfferDataModel extends OfferDataModel {
private final BSFormatter formatter;
private final String offerId;
private final BalanceListener btcBalanceListener;
private final BsqBalanceListener bsqBalanceListener;
private final SetChangeListener<PaymentAccount> paymentAccountsChangeListener;
private OfferPayload.Direction direction;
@ -206,8 +205,6 @@ class CreateOfferDataModel extends OfferDataModel {
}
};
bsqBalanceListener = (availableBalance, unverifiedBalance) -> updateBalance();
paymentAccountsChangeListener = change -> fillPaymentAccounts();
}
@ -229,7 +226,7 @@ class CreateOfferDataModel extends OfferDataModel {
private void addListeners() {
btcWalletService.addBalanceListener(btcBalanceListener);
if (BisqEnvironment.isBaseCurrencySupportingBsq())
bsqWalletService.addBsqBalanceListener(bsqBalanceListener);
bsqWalletService.addBsqBalanceListener(this);
user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener);
}
@ -237,7 +234,7 @@ class CreateOfferDataModel extends OfferDataModel {
private void removeListeners() {
btcWalletService.removeBalanceListener(btcBalanceListener);
if (BisqEnvironment.isBaseCurrencySupportingBsq())
bsqWalletService.removeBsqBalanceListener(bsqBalanceListener);
bsqWalletService.removeBsqBalanceListener(this);
user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener);
}
@ -544,6 +541,14 @@ class CreateOfferDataModel extends OfferDataModel {
}
}
@Override
public void onUpdateBalances(Coin confirmedBalance,
Coin pendingBalance,
Coin lockedForVotingBalance,
Coin lockedInBondsBalance) {
updateBalance();
}
void fundFromSavingsWallet() {
this.useSavingsWallet = true;
updateBalance();

View file

@ -539,7 +539,7 @@ public class GUIUtil {
childByRowMap.get(rowIndex).add(child);
});
for (int i = start; i < end; i++) {
for (int i = Math.min(start, childByRowMap.size()); i < Math.min(end, childByRowMap.size()); i++) {
childByRowMap.get(i).forEach(child -> gridPane.getChildren().remove(child));
}
}