mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Improve Splash screen info, Refactor WalletFacade (user executor, gui defines Platform.runlater as executor)
This commit is contained in:
parent
7dd61b35f6
commit
9e69822a40
@ -26,6 +26,7 @@ import io.bitsquare.persistence.Persistence;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.DownloadListener;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
@ -61,11 +62,13 @@ import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -74,7 +77,6 @@ import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.util.Pair;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -101,7 +103,6 @@ public class WalletFacade {
|
||||
private final CryptoFacade cryptoFacade;
|
||||
private final Persistence persistence;
|
||||
private final String appName;
|
||||
// private final List<DownloadListener> downloadListeners = new CopyOnWriteArrayList<>();
|
||||
private final List<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArrayList<>();
|
||||
private final List<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArrayList<>();
|
||||
private final List<BalanceListener> balanceListeners = new CopyOnWriteArrayList<>();
|
||||
@ -132,12 +133,13 @@ public class WalletFacade {
|
||||
// Public Methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void initialize(org.bitcoinj.core.DownloadListener downloadListener, StartupListener startupListener) {
|
||||
public void initialize(Executor executor, BlockchainDownloadListener blockchainDownloadListener,
|
||||
StartupListener startupListener) {
|
||||
// Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
|
||||
// we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
|
||||
// we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
|
||||
// a future version.
|
||||
Threading.USER_THREAD = Platform::runLater;
|
||||
Threading.USER_THREAD = executor;
|
||||
|
||||
// If seed is non-null it means we are restoring from backup.
|
||||
walletAppKit = new WalletAppKit(params, AppDirectory.dir(appName).toFile(), appName) {
|
||||
@ -150,7 +152,7 @@ public class WalletFacade {
|
||||
walletAppKit.peerGroup().setMaxConnections(11);
|
||||
walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001);
|
||||
initWallet();
|
||||
Platform.runLater(startupListener::completed);
|
||||
executor.execute(() -> startupListener.completed());
|
||||
}
|
||||
};
|
||||
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
|
||||
@ -176,9 +178,25 @@ public class WalletFacade {
|
||||
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
|
||||
//walletAppKit.useTor();
|
||||
}
|
||||
|
||||
// DownloadListener does not run yet in a user thread, so we map it our self
|
||||
DownloadListener downloadListener = new DownloadListener() {
|
||||
@Override
|
||||
protected void progress(double percentage, int blocksLeft, Date date) {
|
||||
super.progress(percentage, blocksLeft, date);
|
||||
executor.execute(() -> blockchainDownloadListener.progress(percentage));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doneDownload() {
|
||||
super.doneDownload();
|
||||
executor.execute(() -> blockchainDownloadListener.doneDownload());
|
||||
}
|
||||
};
|
||||
|
||||
walletAppKit.setDownloadListener(downloadListener)
|
||||
.setBlockingStartup(false)
|
||||
.setUserAgent("Bitsquare", "0.1");
|
||||
.setUserAgent(appName, "0.1");
|
||||
|
||||
/*
|
||||
// TODO restore from DeterministicSeed
|
||||
@ -190,24 +208,15 @@ public class WalletFacade {
|
||||
@Override
|
||||
public void failed(@NotNull Service.State from, @NotNull Throwable failure) {
|
||||
walletAppKit = null;
|
||||
// TODO show error popup
|
||||
//crashAlert(failure);
|
||||
startupListener.failed(failure);
|
||||
}
|
||||
}, Threading.SAME_THREAD);
|
||||
}, Threading.USER_THREAD);
|
||||
walletAppKit.startAsync();
|
||||
}
|
||||
|
||||
private void initWallet() {
|
||||
wallet = walletAppKit.wallet();
|
||||
|
||||
//walletAppKit.peerGroup().setMaxConnections(11);
|
||||
|
||||
/* if (params == RegTestParams.get())
|
||||
walletAppKit.peerGroup().setMinBroadcastConnections(1);
|
||||
else
|
||||
walletAppKit.peerGroup().setMinBroadcastConnections(3);*/
|
||||
|
||||
|
||||
walletEventListener = new WalletEventListener() {
|
||||
@Override
|
||||
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
|
||||
@ -282,15 +291,6 @@ public class WalletFacade {
|
||||
// Listener
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* public DownloadListener addDownloadListener(DownloadListener listener) {
|
||||
downloadListeners.add(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
public void removeDownloadListener(DownloadListener listener) {
|
||||
downloadListeners.remove(listener);
|
||||
}*/
|
||||
|
||||
public AddressConfidenceListener addAddressConfidenceListener(AddressConfidenceListener listener) {
|
||||
addressConfidenceListeners.add(listener);
|
||||
return listener;
|
||||
@ -1148,38 +1148,14 @@ public class WalletFacade {
|
||||
|
||||
public static interface StartupListener {
|
||||
void completed();
|
||||
|
||||
void failed(Throwable failure);
|
||||
}
|
||||
|
||||
public static interface DownloadListener {
|
||||
void progress(double percent);
|
||||
public static interface BlockchainDownloadListener {
|
||||
void progress(double percentage);
|
||||
|
||||
void downloadComplete();
|
||||
void doneDownload();
|
||||
}
|
||||
|
||||
/* private class BlockChainDownloadListener extends org.bitcoinj.core.DownloadListener {
|
||||
@Override
|
||||
protected void progress(double percent, int blocksSoFar, Date date) {
|
||||
super.progress(percent, blocksSoFar, date);
|
||||
Platform.runLater(() -> onProgressInUserThread(percent));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doneDownload() {
|
||||
super.doneDownload();
|
||||
Platform.runLater(this::onDoneDownloadInUserThread);
|
||||
}
|
||||
|
||||
private void onProgressInUserThread(double percent) {
|
||||
for (DownloadListener downloadListener : downloadListeners) {
|
||||
downloadListener.progress(percent);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDoneDownloadInUserThread() {
|
||||
for (DownloadListener downloadListener : downloadListeners) {
|
||||
downloadListener.downloadComplete();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ lower gradient color on tab: dddddd
|
||||
.root {
|
||||
-bs-grey: #666666;
|
||||
-bs-bg-grey: #dddddd;
|
||||
|
||||
-bs-error-red: #dd0000;
|
||||
|
||||
-fx-accent: #0f87c3;
|
||||
-fx-default-button: derive(-fx-accent,95%);
|
||||
-fx-focus-color: -fx-accent;
|
||||
@ -36,7 +37,9 @@ lower gradient color on tab: dddddd
|
||||
#splash {
|
||||
-fx-background-color: #ffffff;
|
||||
}
|
||||
|
||||
#splash-error-state-msg {
|
||||
-fx-text-fill: -bs-error-red;
|
||||
}
|
||||
|
||||
/* Main UI */
|
||||
#base-content-container {
|
||||
@ -52,18 +55,6 @@ lower gradient color on tab: dddddd
|
||||
-fx-font-size: 18;
|
||||
}
|
||||
|
||||
#info-label {
|
||||
-fx-font-size: 14;
|
||||
}
|
||||
|
||||
#online-label {
|
||||
-fx-fill: green;
|
||||
}
|
||||
|
||||
#offline-label {
|
||||
-fx-fill: red;
|
||||
}
|
||||
|
||||
|
||||
/* Main navigation */
|
||||
#nav-button {
|
||||
|
@ -29,12 +29,8 @@ import io.bitsquare.trade.Trade;
|
||||
import io.bitsquare.trade.TradeManager;
|
||||
import io.bitsquare.user.User;
|
||||
|
||||
import org.bitcoinj.core.DownloadListener;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.application.Platform;
|
||||
@ -45,8 +41,7 @@ import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
@ -70,7 +65,8 @@ class MainModel extends UIModel {
|
||||
final BooleanProperty backendReady = new SimpleBooleanProperty();
|
||||
final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(-1);
|
||||
final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0);
|
||||
final StringProperty bootstrapState = new SimpleStringProperty();
|
||||
final ObjectProperty<BootstrapState> bootstrapState = new SimpleObjectProperty<>();
|
||||
final ObjectProperty walletFacadeException = new SimpleObjectProperty<Throwable>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -130,41 +126,46 @@ class MainModel extends UIModel {
|
||||
|
||||
@Override
|
||||
public void onBootstrapStateChanged(BootstrapState bootstrapState) {
|
||||
MainModel.this.bootstrapState.set(bootstrapState.getMessage());
|
||||
MainModel.this.bootstrapState.set(bootstrapState);
|
||||
}
|
||||
});
|
||||
|
||||
Profiler.printMsgWithTime("MainModel.initFacades");
|
||||
|
||||
DownloadListener downloadListener = new DownloadListener() {
|
||||
WalletFacade.BlockchainDownloadListener blockchainDownloadListener = new WalletFacade
|
||||
.BlockchainDownloadListener() {
|
||||
@Override
|
||||
protected void progress(double percent, int blocksLeft, Date date) {
|
||||
super.progress(percent, blocksLeft, date);
|
||||
Platform.runLater(() -> {
|
||||
networkSyncProgress.set(percent / 100.0);
|
||||
public void progress(double percentage) {
|
||||
networkSyncProgress.set(percentage / 100.0);
|
||||
|
||||
if (facadesInitialised && percent >= 100.0)
|
||||
backendReady.set(true);
|
||||
});
|
||||
if (facadesInitialised && percentage >= 100.0)
|
||||
backendReady.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doneDownload() {
|
||||
super.doneDownload();
|
||||
Platform.runLater(() -> {
|
||||
networkSyncProgress.set(1.0);
|
||||
public void doneDownload() {
|
||||
networkSyncProgress.set(1.0);
|
||||
|
||||
if (facadesInitialised)
|
||||
backendReady.set(true);
|
||||
});
|
||||
if (facadesInitialised)
|
||||
backendReady.set(true);
|
||||
}
|
||||
};
|
||||
|
||||
walletFacade.initialize(downloadListener, () -> {
|
||||
walletFacadeInited = true;
|
||||
if (messageFacadeInited)
|
||||
onFacadesInitialised();
|
||||
});
|
||||
WalletFacade.StartupListener startupListener = new WalletFacade.StartupListener() {
|
||||
@Override
|
||||
public void completed() {
|
||||
walletFacadeInited = true;
|
||||
if (messageFacadeInited)
|
||||
onFacadesInitialised();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable failure) {
|
||||
walletFacadeException.set(failure);
|
||||
}
|
||||
};
|
||||
|
||||
walletFacade.initialize(Platform::runLater, blockchainDownloadListener, startupListener);
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ package io.bitsquare.gui.main;
|
||||
import io.bitsquare.bank.BankAccount;
|
||||
import io.bitsquare.gui.PresentationModel;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.network.BootstrapState;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@ -47,10 +48,16 @@ class MainPM extends PresentationModel<MainModel> {
|
||||
final BooleanProperty backendReady = new SimpleBooleanProperty();
|
||||
final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty();
|
||||
final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty();
|
||||
final StringProperty bootstrapState = new SimpleStringProperty();
|
||||
final StringProperty bitcoinSyncState = new SimpleStringProperty("Initializing");
|
||||
final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing");
|
||||
final IntegerProperty numPendingTrades = new SimpleIntegerProperty();
|
||||
final DoubleProperty networkSyncProgress = new SimpleDoubleProperty();
|
||||
final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty();
|
||||
final BooleanProperty blockchainSyncIndicatorVisible = new SimpleBooleanProperty(true);
|
||||
final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1);
|
||||
final BooleanProperty bootstrapFailed = new SimpleBooleanProperty();
|
||||
final BooleanProperty bootstrapIndicatorVisible = new SimpleBooleanProperty(true);
|
||||
final StringProperty bootstrapState = new SimpleStringProperty();
|
||||
final StringProperty bootstrapErrorMsg = new SimpleStringProperty();
|
||||
final StringProperty walletFacadeErrorMsg = new SimpleStringProperty();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -74,16 +81,42 @@ class MainPM extends PresentationModel<MainModel> {
|
||||
super.initialize();
|
||||
|
||||
backendReady.bind(model.backendReady);
|
||||
networkSyncProgress.bind(model.networkSyncProgress);
|
||||
numPendingTrades.bind(model.numPendingTrades);
|
||||
|
||||
model.bootstrapState.addListener((ov, oldValue, newValue) ->
|
||||
bootstrapState.set("Connection to P2P network: " + newValue));
|
||||
model.bootstrapState.addListener((ov, oldValue, newValue) -> {
|
||||
if (newValue == BootstrapState.DIRECT_SUCCESS ||
|
||||
newValue == BootstrapState.NAT_SUCCESS ||
|
||||
newValue == BootstrapState.RELAY_SUCCESS) {
|
||||
bootstrapState.set("Successfully connected to P2P network: " + newValue.getMessage());
|
||||
bootstrapIndicatorVisible.set(false);
|
||||
bootstrapProgress.set(1);
|
||||
}
|
||||
else if (newValue == BootstrapState.PEER_CREATION_FAILED ||
|
||||
newValue == BootstrapState.DIRECT_FAILED ||
|
||||
newValue == BootstrapState.NAT_FAILED ||
|
||||
newValue == BootstrapState.RELAY_FAILED) {
|
||||
|
||||
bootstrapState.set(model.bootstrapState.get());
|
||||
bootstrapErrorMsg.set(newValue.getMessage());
|
||||
bootstrapState.set("Connection to P2P network failed.");
|
||||
bootstrapIndicatorVisible.set(false);
|
||||
bootstrapProgress.set(0);
|
||||
bootstrapFailed.set(true);
|
||||
}
|
||||
else {
|
||||
bootstrapState.set("Connecting to P2P network: " + newValue.getMessage());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
model.networkSyncProgress.addListener((ov, oldValue, newValue) -> updateBitcoinSyncState((double) newValue));
|
||||
updateBitcoinSyncState(model.networkSyncProgress.get());
|
||||
model.walletFacadeException.addListener((ov, oldValue, newValue) -> {
|
||||
blockchainSyncProgress.set(0);
|
||||
blockchainSyncIndicatorVisible.set(false);
|
||||
blockchainSyncState.set("Startup failed.");
|
||||
walletFacadeErrorMsg.set(((Throwable) newValue).getMessage());
|
||||
});
|
||||
|
||||
model.networkSyncProgress.addListener((ov, oldValue, newValue) -> setNetworkSyncProgress((double) newValue));
|
||||
setNetworkSyncProgress(model.networkSyncProgress.get());
|
||||
|
||||
model.getBankAccounts().addListener((ListChangeListener<BankAccount>) change -> {
|
||||
bankAccountsComboBoxDisable.set(change.getList().isEmpty());
|
||||
@ -149,14 +182,16 @@ class MainPM extends PresentationModel<MainModel> {
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void updateBitcoinSyncState(double value) {
|
||||
if (value > 0.0)
|
||||
bitcoinSyncState.set("Synchronizing with bitcoin network: " +
|
||||
formatter.formatToPercent(value));
|
||||
else if (value == 1)
|
||||
bitcoinSyncState.set("Synchronizing with bitcoin network completed.");
|
||||
private void setNetworkSyncProgress(double value) {
|
||||
blockchainSyncProgress.set(value);
|
||||
if (value >= 1)
|
||||
blockchainSyncState.set("Synchronization completed.");
|
||||
else if (value > 0.0)
|
||||
blockchainSyncState.set("Synchronizing blockchain: " + formatter.formatToPercent(value));
|
||||
else
|
||||
bitcoinSyncState.set("Synchronizing with bitcoin network...");
|
||||
blockchainSyncState.set("Connecting to bitcoin network...");
|
||||
|
||||
blockchainSyncIndicatorVisible.set(value < 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -286,36 +286,52 @@ public class MainViewCB extends ViewCB<MainPM> {
|
||||
ImageView logo = new ImageView();
|
||||
logo.setId("image-splash-logo");
|
||||
|
||||
Label bitcoinSyncStateLabel = new Label();
|
||||
bitcoinSyncStateLabel.textProperty().bind(presentationModel.bitcoinSyncState);
|
||||
Label blockchainSyncLabel = new Label();
|
||||
blockchainSyncLabel.textProperty().bind(presentationModel.blockchainSyncState);
|
||||
presentationModel.walletFacadeErrorMsg.addListener((ov, oldValue, newValue) -> {
|
||||
blockchainSyncLabel.setId("splash-error-state-msg");
|
||||
Popups.openErrorPopup("Error", "An error occurred at startup. \n\nError message:\n" +
|
||||
newValue);
|
||||
});
|
||||
|
||||
ProgressBar btcProgressIndicator = new ProgressBar(-1);
|
||||
btcProgressIndicator.setPrefWidth(120);
|
||||
btcProgressIndicator.progressProperty().bind(presentationModel.networkSyncProgress);
|
||||
ProgressBar blockchainSyncIndicator = new ProgressBar(-1);
|
||||
blockchainSyncIndicator.setPrefWidth(120);
|
||||
blockchainSyncIndicator.progressProperty().bind(presentationModel.blockchainSyncProgress);
|
||||
blockchainSyncIndicator.visibleProperty().bind(presentationModel.blockchainSyncIndicatorVisible);
|
||||
blockchainSyncIndicator.managedProperty().bind(presentationModel.blockchainSyncIndicatorVisible);
|
||||
|
||||
HBox btcBox = new HBox();
|
||||
btcBox.setSpacing(10);
|
||||
btcBox.setAlignment(Pos.CENTER);
|
||||
btcBox.setPadding(new Insets(60, 0, 0, 0));
|
||||
btcBox.getChildren().addAll(bitcoinSyncStateLabel, btcProgressIndicator);
|
||||
HBox blockchainSyncBox = new HBox();
|
||||
blockchainSyncBox.setSpacing(10);
|
||||
blockchainSyncBox.setAlignment(Pos.CENTER);
|
||||
blockchainSyncBox.setPadding(new Insets(60, 0, 0, 0));
|
||||
blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator);
|
||||
|
||||
Label bootstrapStateLabel = new Label();
|
||||
bootstrapStateLabel.setWrapText(true);
|
||||
bootstrapStateLabel.setMaxWidth(500);
|
||||
bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER);
|
||||
bootstrapStateLabel.textProperty().bind(presentationModel.bootstrapState);
|
||||
presentationModel.bootstrapFailed.addListener((ov, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
bootstrapStateLabel.setId("splash-error-state-msg");
|
||||
Popups.openErrorPopup("Error", "Cannot connect to P2P network. \n\nError message:\n" +
|
||||
presentationModel.bootstrapErrorMsg.get());
|
||||
}
|
||||
});
|
||||
|
||||
ProgressIndicator p2pProgressIndicator = new ProgressIndicator(-1);
|
||||
p2pProgressIndicator.setMaxSize(24, 24);
|
||||
ProgressIndicator bootstrapIndicator = new ProgressIndicator();
|
||||
bootstrapIndicator.setMaxSize(24, 24);
|
||||
bootstrapIndicator.progressProperty().bind(presentationModel.bootstrapProgress);
|
||||
bootstrapIndicator.visibleProperty().bind(presentationModel.bootstrapIndicatorVisible);
|
||||
bootstrapIndicator.managedProperty().bind(presentationModel.bootstrapIndicatorVisible);
|
||||
|
||||
HBox p2pBox = new HBox();
|
||||
p2pBox.setSpacing(10);
|
||||
p2pBox.setAlignment(Pos.CENTER);
|
||||
p2pBox.setPadding(new Insets(10, 0, 0, 0));
|
||||
p2pBox.getChildren().addAll(bootstrapStateLabel, p2pProgressIndicator);
|
||||
|
||||
vBox.getChildren().addAll(logo, btcBox, p2pBox);
|
||||
HBox bootstrapBox = new HBox();
|
||||
bootstrapBox.setSpacing(10);
|
||||
bootstrapBox.setAlignment(Pos.CENTER);
|
||||
bootstrapBox.setPadding(new Insets(10, 0, 0, 0));
|
||||
bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator);
|
||||
|
||||
vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox);
|
||||
return vBox;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user