package wallettemplate;
import com.google.common.util.concurrent.Service;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import network.thunder.core.ThunderContext;
import network.thunder.core.communication.objects.messages.impl.results.NullResultCommand;
import network.thunder.core.database.DBHandler;
import network.thunder.core.etc.Constants;
import network.thunder.core.etc.InMemoryDBHandler;
import network.thunder.core.etc.MockWallet;
import network.thunder.core.mesh.NodeServer;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Wallet;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.utils.BriefLogFormatter;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.DeterministicSeed;
import wallettemplate.controls.NotificationBarPane;
import wallettemplate.utils.GuiUtils;
import wallettemplate.utils.TextFieldValidator;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import static wallettemplate.utils.GuiUtils.*;
public class Main extends Application {
public static String APP_NAME = "ThunderWallet";
public static NetworkParameters params = TestNet3Params.get();
public static WalletAppKit bitcoin;
public static Main instance;
public static Wallet wallet;
public static ThunderContext thunderContext;
public static DBHandler dbHandler = new InMemoryDBHandler();
public static NodeServer node = new NodeServer();
private StackPane uiStack;
private Pane mainUI;
public MainController controller;
public NotificationBarPane notificationBar;
public Stage mainWindow;
public static int CLIENTID = 2;
public static String REQUEST;
@Override
public void start (Stage mainWindow) throws Exception {
try {
realStart(mainWindow);
} catch (Throwable e) {
GuiUtils.crashAlert(e);
throw e;
}
}
private void realStart (Stage mainWindow) throws IOException {
this.mainWindow = mainWindow;
instance = this;
// Show the crash dialog for any exceptions that we don't handle and that hit the main loop.
GuiUtils.handleCrashesOnThisThread();
// Load the GUI. The MainController class will be automagically created and wired up.
File file = new File("main.fxml");
URL location = getClass().getResource("main.fxml");
FXMLLoader loader = new FXMLLoader(location);
mainUI = loader.load();
controller = loader.getController();
// Configure the window with a StackPane so we can overlay things on top of the main UI, and a
// NotificationBarPane so we can slide messages and progress bars in from the bottom. Note that
// ordering of the construction and connection matters here, otherwise we get (harmless) CSS error
// spew to the logs.
notificationBar = new NotificationBarPane(mainUI);
mainWindow.setTitle(APP_NAME);
uiStack = new StackPane();
Scene scene = new Scene(uiStack);
TextFieldValidator.configureScene(scene); // Add CSS that we need.
scene.getStylesheets().add(getClass().getResource("wallet.css").toString());
uiStack.getChildren().add(notificationBar);
mainWindow.setScene(scene);
// Make log output concise.
BriefLogFormatter.init();
// Tell bitcoinj to run 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;
// Create the app kit. It won't do any heavyweight initialization until after we start it.
setupWalletKit(null);
if (bitcoin.isChainFileLocked()) {
if (REQUEST != null) {
PaymentProtocolClientSocket.sendPaymentRequest(REQUEST);
Platform.exit();
return;
}
informationalAlert("Already running", "This application is already running and cannot be started twice.");
Platform.exit();
return;
}
PaymentProtocolServerSocket.init();
mainWindow.show();
WalletSetPasswordController.estimateKeyDerivationTimeMsec();
bitcoin.addListener(new Service.Listener() {
@Override
public void failed (Service.State from, Throwable failure) {
GuiUtils.crashAlert(failure);
}
}, Platform::runLater);
bitcoin.startAsync();
System.out.println("init");
node.init();
wallet = new MockWallet(Constants.getNetwork());
thunderContext = new ThunderContext(wallet, dbHandler, node);
thunderContext.startUp(new NullResultCommand());
scene.getAccelerators().put(KeyCombination.valueOf("Shortcut+F"), () -> bitcoin.peerGroup().getDownloadPeer().close());
}
public void setupWalletKit (@Nullable DeterministicSeed seed) {
// If seed is non-null it means we are restoring from backup.
bitcoin = new WalletAppKit(params, new File("."), APP_NAME + "-" + params.getPaymentProtocolId() + CLIENTID) {
@Override
protected void onSetupCompleted () {
// Don't make the user wait for confirmations for now, as the intention is they're sending it
// their own money!
bitcoin.wallet().allowSpendingUnconfirmedTransactions();
Platform.runLater(controller::onBitcoinSetup);
}
};
bitcoin.setDownloadListener(controller.progressBarUpdater())
.setBlockingStartup(false)
.setUserAgent(APP_NAME, "1.0");
if (seed != null) {
bitcoin.restoreWalletFromSeed(seed);
}
}
private Node stopClickPane = new Pane();
public class OverlayUI <T> {
public Node ui;
public T controller;
public OverlayUI (Node ui, T controller) {
this.ui = ui;
this.controller = controller;
}
public void show () {
checkGuiThread();
if (currentOverlay == null) {
uiStack.getChildren().add(stopClickPane);
uiStack.getChildren().add(ui);
blurOut(mainUI);
//darken(mainUI);
fadeIn(ui);
zoomIn(ui);
} else {
// Do a quick transition between the current overlay and the next.
// Bug here: we don't pay attention to changes in outsideClickDismisses.
explodeOut(currentOverlay.ui);
fadeOutAndRemove(uiStack, currentOverlay.ui);
uiStack.getChildren().add(ui);
ui.setOpacity(0.0);
fadeIn(ui, 100);
zoomIn(ui, 100);
}
currentOverlay = this;
}
public void outsideClickDismisses () {
stopClickPane.setOnMouseClicked((ev) -> done());
}
public void done () {
checkGuiThread();
if (ui == null) {
return; // In the middle of being dismissed and got an extra click.
}
explodeOut(ui);
fadeOutAndRemove(uiStack, ui, stopClickPane);
blurIn(mainUI);
this.ui = null;
this.controller = null;
currentOverlay = null;
}
}
@Nullable
private OverlayUI currentOverlay;
public <T> OverlayUI<T> overlayUI (Node node, T controller) {
checkGuiThread();
OverlayUI<T> pair = new OverlayUI<T>(node, controller);
// Auto-magically set the overlayUI member, if it's there.
try {
controller.getClass().getField("overlayUI").set(controller, pair);
} catch (IllegalAccessException | NoSuchFieldException ignored) {
}
pair.show();
return pair;
}
/**
* Loads the FXML file with the given name, blurs out the main UI and puts this one on top.
*/
public <T> OverlayUI<T> overlayUI (String name) {
try {
checkGuiThread();
// Load the UI from disk.
URL location = GuiUtils.getResource(name);
FXMLLoader loader = new FXMLLoader(location);
Pane ui = loader.load();
T controller = loader.getController();
OverlayUI<T> pair = new OverlayUI<T>(ui, controller);
// Auto-magically set the overlayUI member, if it's there.
try {
if (controller != null) {
controller.getClass().getField("overlayUI").set(controller, pair);
}
} catch (IllegalAccessException | NoSuchFieldException ignored) {
ignored.printStackTrace();
}
pair.show();
return pair;
} catch (IOException e) {
throw new RuntimeException(e); // Can't happen.
}
}
/**
* Loads the FXML file with the given name, blurs out the main UI and puts this one on top.
*/
public <T> OverlayUI<T> overlayUI (Pane ui, T controller) {
checkGuiThread();
OverlayUI<T> pair = new OverlayUI<T>(ui, controller);
// Auto-magically set the overlayUI member, if it's there.
try {
if (controller != null) {
controller.getClass().getField("overlayUI").set(controller, pair);
}
} catch (IllegalAccessException | NoSuchFieldException ignored) {
ignored.printStackTrace();
}
pair.show();
return pair;
}
@Override
public void stop () throws Exception {
bitcoin.stopAsync();
bitcoin.awaitTerminated();
// Forcibly terminate the JVM because Orchid likes to spew non-daemon threads everywhere.
Runtime.getRuntime().exit(0);
}
public static void main (String[] args) {
try {
int id = Integer.parseInt(args[0]);
CLIENTID = id;
} catch (Exception e) {
try {
REQUEST = args[0];
} catch (Exception f) {
}
}
launch(args);
}
}