Improve proposal list view with splitpane and scroller

This commit is contained in:
Manfred Karrer 2016-12-13 12:31:58 +01:00
parent 313ad63f17
commit 1126c50a7c
9 changed files with 326 additions and 59 deletions

View File

@ -302,6 +302,16 @@ public class BtcWalletService extends WalletService {
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Send funds to a proposal
///////////////////////////////////////////////////////////////////////////////////////////
public void fundProposal(Coin amount, String btcAddress, Address squAddressForProposalFunding, FutureCallback<Transaction> callback) {
//TODO
}
///////////////////////////////////////////////////////////////////////////////////////////
// AddressEntry
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -250,4 +250,13 @@ public class SquWalletService extends WalletService {
Futures.addCallback(walletsSetup.getPeerGroup().broadcastTransaction(tx).future(), callback);
printTx("signAndBroadcastProposalFeeTx", tx);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Create funds to a proposal
///////////////////////////////////////////////////////////////////////////////////////////
public Address getSquAddressForProposalFunding() {
return wallet.freshReceiveAddress();
}
}

View File

@ -32,6 +32,18 @@ import java.util.concurrent.TimeUnit;
public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePayload {
private static final Logger log = LoggerFactory.getLogger(Proposal.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Enums
///////////////////////////////////////////////////////////////////////////////////////////
public enum Phase {
NEW,
OPEN_FOR_VOTING,
OPEN_FOR_FUNDING,
CLOSED
}
public static final long TTL = TimeUnit.DAYS.toMillis(30);
public final String uid;
@ -56,6 +68,9 @@ public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePa
// Set after we signed and set the hash. The hash is used in the OP_RETURN of the fee tx
public String feeTxId;
private boolean accepted;
private Coin fundsReceived;
private int phaseAsOrdinal;
public Proposal(String uid,
String name,
@ -85,6 +100,7 @@ public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePa
this.nodeAddress = nodeAddress;
this.p2pStorageSignaturePubKey = p2pStorageSignaturePubKey;
this.squPubKey = squPubKey;
setPhase(Phase.NEW);
}
@Override
@ -97,6 +113,7 @@ public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePa
return p2pStorageSignaturePubKey;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
@ -110,6 +127,34 @@ public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePa
this.feeTxId = feeTxId;
}
public Phase getPhase() {
return Phase.values()[phaseAsOrdinal];
}
public void setPhase(Phase phase) {
phaseAsOrdinal = phase.ordinal();
}
public boolean isAccepted() {
return accepted;
}
public void setAccepted(boolean accepted) {
this.accepted = accepted;
}
public Coin getFundsReceived() {
return fundsReceived;
}
public void setFundsReceived(Coin fundsReceived) {
this.fundsReceived = fundsReceived;
}
public String shortId() {
return uid.substring(0, 8);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -17,7 +17,10 @@
package io.bitsquare.dao.proposals;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject;
import io.bitsquare.btc.wallet.BtcWalletService;
import io.bitsquare.btc.wallet.SquWalletService;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.payload.StoragePayload;
@ -25,6 +28,8 @@ import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import io.bitsquare.storage.Storage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,6 +40,8 @@ public class ProposalManager {
private P2PService p2PService;
private Storage<ArrayList<Proposal>> proposalsStorage;
private BtcWalletService btcWalletService;
private SquWalletService squWalletService;
private ObservableList<Proposal> observableProposalsList = FXCollections.observableArrayList();
@ -43,9 +50,12 @@ public class ProposalManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public ProposalManager(P2PService p2PService, Storage<ArrayList<Proposal>> proposalsStorage) {
public ProposalManager(P2PService p2PService, Storage<ArrayList<Proposal>> proposalsStorage,
BtcWalletService btcWalletService, SquWalletService squWalletService) {
this.p2PService = p2PService;
this.proposalsStorage = proposalsStorage;
this.btcWalletService = btcWalletService;
this.squWalletService = squWalletService;
init(proposalsStorage);
}
@ -94,4 +104,8 @@ public class ProposalManager {
public ObservableList<Proposal> getObservableProposalsList() {
return observableProposalsList;
}
public void fundProposal(Proposal proposal, Coin amount, FutureCallback<Transaction> callback) {
btcWalletService.fundProposal(amount, proposal.btcAddress, squWalletService.getSquAddressForProposalFunding(), callback);
}
}

View File

@ -20,19 +20,22 @@ package io.bitsquare.gui.main.dao.proposals;
import io.bitsquare.dao.proposals.Proposal;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.util.Layout;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField;
import static io.bitsquare.gui.util.FormBuilder.addTitledGroupBg;
import java.util.Random;
import static io.bitsquare.gui.util.FormBuilder.*;
public class ProposalDisplay {
private static final Logger log = LoggerFactory.getLogger(ProposalDisplay.class);
private InputTextField nameTextField, titleTextField, categoryTextField, descriptionTextField, linkTextField,
startDateTextField, endDateTextField, requestedBTCTextField, btcAddressTextField;
private GridPane gridPane;
public InputTextField nameTextField, titleTextField, categoryTextField, descriptionTextField, linkTextField,
startDateTextField, endDateTextField, requestedBTCTextField, btcAddressTextField;
private TextField phaseTextField;
private int gridRow = 0;
public ProposalDisplay(GridPane gridPane) {
@ -40,7 +43,7 @@ public class ProposalDisplay {
}
public void createAllFields() {
addTitledGroupBg(gridPane, ++gridRow, 9, "Selected proposal", Layout.GROUP_DISTANCE);
addTitledGroupBg(gridPane, ++gridRow, 10, "Selected proposal", Layout.GROUP_DISTANCE);
nameTextField = addLabelInputTextField(gridPane, gridRow, "Name/nickname:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
titleTextField = addLabelInputTextField(gridPane, ++gridRow, "Title:").second;
categoryTextField = addLabelInputTextField(gridPane, ++gridRow, "Category:").second;
@ -50,6 +53,7 @@ public class ProposalDisplay {
endDateTextField = addLabelInputTextField(gridPane, ++gridRow, "Delivery date:").second;
requestedBTCTextField = addLabelInputTextField(gridPane, ++gridRow, "Requested funds in BTC:").second;
btcAddressTextField = addLabelInputTextField(gridPane, ++gridRow, "Bitcoin address:").second;
phaseTextField = addLabelTextField(gridPane, ++gridRow, "Phase:").second;
}
public void fillWithProposalData(Proposal proposal) {
@ -62,10 +66,53 @@ public class ProposalDisplay {
endDateTextField.setText(proposal.endDate.toString());
requestedBTCTextField.setText(proposal.requestedBtc.toPlainString());
btcAddressTextField.setText(proposal.btcAddress.toString());
phaseTextField.setText(proposal.getPhase().name());
}
public void clearForm() {
nameTextField.setText("");
titleTextField.setText("");
categoryTextField.setText("");
descriptionTextField.setText("");
linkTextField.setText("");
startDateTextField.setText("");
endDateTextField.setText("");
requestedBTCTextField.setText("");
btcAddressTextField.setText("");
phaseTextField.setText("");
}
public void fillWithMock() {
int random = new Random().nextInt(100);
nameTextField.setText("Mock name" + random);
titleTextField.setText("Mock Title " + random);
categoryTextField.setText("Mock Category " + random);
descriptionTextField.setText("Mock Description " + random);
linkTextField.setText("Mock Link " + random);
startDateTextField.setText("Mock Start date " + random);
endDateTextField.setText("Mock Delivery date " + random);
requestedBTCTextField.setText("Mock Requested funds " + random);
btcAddressTextField.setText("Mock Bitcoin address " + random);
}
public void setAllFieldsEditable(boolean isEditable) {
nameTextField.setEditable(isEditable);
titleTextField.setEditable(isEditable);
categoryTextField.setEditable(isEditable);
descriptionTextField.setEditable(isEditable);
linkTextField.setEditable(isEditable);
startDateTextField.setEditable(isEditable);
endDateTextField.setEditable(isEditable);
requestedBTCTextField.setEditable(isEditable);
btcAddressTextField.setEditable(isEditable);
}
public void removeAllFields() {
gridPane.getChildren().clear();
gridRow = 0;
}
public int incrementAndGetGridRow() {
return ++gridRow;
}
}

View File

@ -17,27 +17,44 @@
package io.bitsquare.gui.main.dao.proposals.active;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.common.UserThread;
import io.bitsquare.dao.proposals.Proposal;
import io.bitsquare.dao.proposals.ProposalManager;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.TableGroupHeadline;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.dao.DaoView;
import io.bitsquare.gui.main.dao.proposals.ProposalDisplay;
import io.bitsquare.gui.main.dao.voting.VotingView;
import io.bitsquare.gui.main.dao.voting.dashboard.VotingDashboardView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.util.Callback;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import static com.google.common.base.Preconditions.checkArgument;
import static io.bitsquare.gui.util.FormBuilder.addButtonAfterGroup;
import static io.bitsquare.gui.util.FormBuilder.addLabel;
@FxmlView
public class ActiveProposalsView extends ActivatableView<SplitPane, Void> {
@ -47,9 +64,15 @@ public class ActiveProposalsView extends ActivatableView<SplitPane, Void> {
private final ProposalManager proposalManager;
private final BSFormatter formatter;
private Navigation navigation;
private FundProposalWindow fundProposalWindow;
private BSFormatter btcFormatter;
private SortedList<Proposal> sortedList;
private Subscription selectedProposalSubscription;
private ProposalDisplay proposalDisplay;
private GridPane gridPane;
private Button fundButton;
private Button voteButton;
///////////////////////////////////////////////////////////////////////////////////////////
@ -57,9 +80,13 @@ public class ActiveProposalsView extends ActivatableView<SplitPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ActiveProposalsView(ProposalManager proposalManager, BSFormatter formatter) {
private ActiveProposalsView(ProposalManager proposalManager, BSFormatter formatter, Navigation navigation,
FundProposalWindow fundProposalWindow, BSFormatter btcFormatter) {
this.proposalManager = proposalManager;
this.formatter = formatter;
this.navigation = navigation;
this.fundProposalWindow = fundProposalWindow;
this.btcFormatter = btcFormatter;
}
@Override
@ -126,9 +153,17 @@ public class ActiveProposalsView extends ActivatableView<SplitPane, Void> {
AnchorPane bottomAnchorPane = new AnchorPane();
scrollPane.setContent(bottomAnchorPane);
GridPane gridPane = new GridPane();
gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(5);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setHalignment(HPos.RIGHT);
columnConstraints1.setHgrow(Priority.SOMETIMES);
columnConstraints1.setMinWidth(140);
ColumnConstraints columnConstraints2 = new ColumnConstraints();
columnConstraints2.setHgrow(Priority.ALWAYS);
columnConstraints2.setMinWidth(300);
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
AnchorPane.setBottomAnchor(gridPane, 20d);
AnchorPane.setRightAnchor(gridPane, -10d);
AnchorPane.setLeftAnchor(gridPane, 10d);
@ -139,6 +174,41 @@ public class ActiveProposalsView extends ActivatableView<SplitPane, Void> {
}
proposalDisplay.removeAllFields();
proposalDisplay.createAllFields();
proposal.setPhase(Proposal.Phase.OPEN_FOR_FUNDING);
proposal.setAccepted(true);
if (proposal.getPhase() == Proposal.Phase.NEW) {
} else if (proposal.getPhase() == Proposal.Phase.OPEN_FOR_VOTING) {
voteButton = addButtonAfterGroup(gridPane, proposalDisplay.incrementAndGetGridRow(), "Vote on proposal");
voteButton.setOnAction(event -> {
navigation.navigateTo(MainView.class, DaoView.class, VotingView.class, VotingDashboardView.class);
});
} else if (proposal.getPhase() == Proposal.Phase.OPEN_FOR_FUNDING) {
checkArgument(proposal.isAccepted(), "A proposal with state OPEN_FOR_FUNDING must be accepted.");
fundButton = addButtonAfterGroup(gridPane, proposalDisplay.incrementAndGetGridRow(), "Fund proposal");
fundButton.setOnAction(event -> {
fundProposalWindow.applyProposal(proposal).
onAction(() -> {
Coin amount = btcFormatter.parseToCoin(fundProposalWindow.getAmount().getText());
proposalManager.fundProposal(proposal, amount,
new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
UserThread.runAfter(() -> new Popup<>().feedback("Proposal successfully funded.").show(), 1);
}
@Override
public void onFailure(@NotNull Throwable t) {
UserThread.runAfter(() -> new Popup<>().error(t.toString()).show(), 1);
}
});
}).show();
});
} else if (proposal.getPhase() == Proposal.Phase.CLOSED) {
addLabel(gridPane, proposalDisplay.incrementAndGetGridRow(), "This proposal is not open anymore for funding. Please wait until the next funding period starts.");
}
proposalDisplay.setAllFieldsEditable(false);
proposalDisplay.fillWithProposalData(proposal);
}

View File

@ -0,0 +1,104 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.dao.proposals.active;
import io.bitsquare.dao.proposals.Proposal;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.Overlay;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField;
import static io.bitsquare.gui.util.FormBuilder.addLabelTextField;
public class FundProposalWindow extends Overlay<FundProposalWindow> {
private static final Logger log = LoggerFactory.getLogger(FundProposalWindow.class);
private Proposal proposal;
private InputTextField amount;
private TextField info;
///////////////////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public FundProposalWindow() {
type = Type.Instruction;
}
public void show() {
if (headLine == null)
headLine = "Fund proposal";
createGridPane();
addHeadLine();
addSeparator();
addContent();
addCloseButton();
applyStyles();
display();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void setupKeyHandler(Scene scene) {
if (!hideCloseButton) {
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
e.consume();
doClose();
}
});
}
}
public FundProposalWindow applyProposal(Proposal proposal) {
this.proposal = proposal;
return this;
}
public InputTextField getAmount() {
return amount;
}
private void addContent() {
info = addLabelTextField(gridPane, ++rowIndex, "Proposal ID:").second;
amount = addLabelInputTextField(gridPane, ++rowIndex, "Amount in BTC:").second;
info.setText(proposal.shortId());
}
@Override
protected void addCloseButton() {
super.addCloseButton();
if (actionButton != null) {
actionButton.setOnAction(event -> {
actionHandlerOptional.ifPresent(Runnable::run);
});
}
}
}

View File

@ -31,10 +31,9 @@ import io.bitsquare.dao.proposals.Proposal;
import io.bitsquare.dao.proposals.ProposalManager;
import io.bitsquare.gui.common.view.ActivatableView;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.dao.proposals.ProposalDisplay;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import javafx.scene.control.Button;
@ -54,23 +53,21 @@ import java.util.Date;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.bitsquare.gui.util.FormBuilder.*;
import static io.bitsquare.gui.util.FormBuilder.addButtonAfterGroup;
@FxmlView
public class CreateProposalView extends ActivatableView<GridPane, Void> {
private final NodeAddress nodeAddress;
private final PublicKey p2pStorageSignaturePubKey;
private InputTextField nameTextField, titleTextField, categoryTextField, descriptionTextField, linkTextField,
startDateTextField, endDateTextField, requestedBTCTextField, btcAddressTextField;
private ProposalDisplay proposalDisplay;
private Button createButton;
private final NodeAddress nodeAddress;
private final PublicKey p2pStorageSignaturePubKey;
private final SquWalletService squWalletService;
private final BtcWalletService btcWalletService;
private final FeeService feeService;
private final ProposalManager proposalManager;
private final BSFormatter btcFormatter;
private int gridRow = 0;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
@ -92,32 +89,15 @@ public class CreateProposalView extends ActivatableView<GridPane, Void> {
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 9, "Create new funding proposal");
nameTextField = addLabelInputTextField(root, gridRow, "Name/nickname:", Layout.FIRST_ROW_DISTANCE).second;
titleTextField = addLabelInputTextField(root, ++gridRow, "Title:").second;
categoryTextField = addLabelInputTextField(root, ++gridRow, "Category:").second;
descriptionTextField = addLabelInputTextField(root, ++gridRow, "Description:").second;
linkTextField = addLabelInputTextField(root, ++gridRow, "Link to detail info:").second;
startDateTextField = addLabelInputTextField(root, ++gridRow, "Start date:").second;
endDateTextField = addLabelInputTextField(root, ++gridRow, "Delivery date:").second;
requestedBTCTextField = addLabelInputTextField(root, ++gridRow, "Requested funds in BTC:").second;
btcAddressTextField = addLabelInputTextField(root, ++gridRow, "Bitcoin address:").second;
createButton = addButtonAfterGroup(root, ++gridRow, "Create proposal");
//TODO
nameTextField.setText("Mock name");
titleTextField.setText("Mock Title");
categoryTextField.setText("Mock Category");
descriptionTextField.setText("Mock Description");
linkTextField.setText("Mock Link");
startDateTextField.setText("Mock Start date");
endDateTextField.setText("Mock Delivery date");
requestedBTCTextField.setText("Mock Requested funds");
btcAddressTextField.setText("Mock Bitcoin address");
proposalDisplay = new ProposalDisplay(root);
proposalDisplay.removeAllFields();
proposalDisplay.createAllFields();
createButton = addButtonAfterGroup(root, proposalDisplay.incrementAndGetGridRow(), "Create proposal");
}
@Override
protected void activate() {
proposalDisplay.fillWithMock();
createButton.setOnAction(event -> {
DeterministicKey squKeyPair = squWalletService.getWallet().freshKey(KeyChain.KeyPurpose.AUTHENTICATION);
checkNotNull(squKeyPair, "squKeyPair must not be null");
@ -127,15 +107,15 @@ public class CreateProposalView extends ActivatableView<GridPane, Void> {
Date endDate = new Date();
Proposal proposal = new Proposal(UUID.randomUUID().toString(),
nameTextField.getText(),
titleTextField.getText(),
categoryTextField.getText(),
descriptionTextField.getText(),
linkTextField.getText(),
proposalDisplay.nameTextField.getText(),
proposalDisplay.titleTextField.getText(),
proposalDisplay.categoryTextField.getText(),
proposalDisplay.descriptionTextField.getText(),
proposalDisplay.linkTextField.getText(),
startDate,
endDate,
btcFormatter.parseToCoin(requestedBTCTextField.getText()),
btcAddressTextField.getText(),
btcFormatter.parseToCoin(proposalDisplay.requestedBTCTextField.getText()),
proposalDisplay.btcAddressTextField.getText(),
nodeAddress,
p2pStorageSignaturePubKey,
squKeyPair.getPubKey()
@ -155,7 +135,7 @@ public class CreateProposalView extends ActivatableView<GridPane, Void> {
checkNotNull(transaction, "Transaction must not be null at signAndBroadcastProposalFeeTx callback.");
proposal.setFeeTxId(transaction.getHashAsString());
publishToP2PNetwork(proposal);
clearForm();
proposalDisplay.clearForm();
new Popup<>().confirmation("Your proposal has been successfully published.").show();
}
@ -174,18 +154,6 @@ public class CreateProposalView extends ActivatableView<GridPane, Void> {
});
}
private void clearForm() {
nameTextField.setText("");
titleTextField.setText("");
categoryTextField.setText("");
descriptionTextField.setText("");
linkTextField.setText("");
startDateTextField.setText("");
endDateTextField.setText("");
requestedBTCTextField.setText("");
btcAddressTextField.setText("");
}
private void publishToP2PNetwork(Proposal proposal) {
proposalManager.addToP2PNetwork(proposal);
}

View File

@ -120,7 +120,7 @@ public abstract class Overlay<T extends Overlay> {
protected Optional<Runnable> closeHandlerOptional = Optional.empty();
protected Optional<Runnable> actionHandlerOptional = Optional.empty();
protected Stage stage;
private boolean showReportErrorButtons;
protected boolean showReportErrorButtons;
protected Label messageLabel;
protected String truncatedMessage;
private BusyAnimation busyAnimation;