mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 07:12:17 +00:00
Add a demo wallet app that uses JavaFX and Java 8.
The app is not meant to be usable by end users. It is intended to act as a template for people to build custom apps that use contracts. To that end you can get money in, and empty your wallet, but there's no other controls beyond that. Apps based on this template look professional and have nice animations and visual effects. You can also use this as a way to learn JavaFX.
This commit is contained in:
parent
8daec2363b
commit
6a84f55727
1
pom.xml
1
pom.xml
@ -11,6 +11,7 @@
|
|||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>examples</module>
|
<module>examples</module>
|
||||||
<module>tools</module>
|
<module>tools</module>
|
||||||
|
<module>wallettemplate</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<name>bitcoinj Parent</name>
|
<name>bitcoinj Parent</name>
|
||||||
|
43
wallettemplate/pom.xml
Normal file
43
wallettemplate/pom.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.bitcoinj</groupId>
|
||||||
|
<artifactId>wallettemplate</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google</groupId>
|
||||||
|
<artifactId>bitcoinj</artifactId>
|
||||||
|
<version>0.11-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-jdk14</artifactId>
|
||||||
|
<version>1.6.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>13.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,32 @@
|
|||||||
|
package wallettemplate;
|
||||||
|
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
public class AlertWindowController {
|
||||||
|
public Label messageLabel;
|
||||||
|
public Label detailsLabel;
|
||||||
|
public Button okButton;
|
||||||
|
public Button cancelButton;
|
||||||
|
public Button actionButton;
|
||||||
|
|
||||||
|
/** Initialize this alert dialog for information about a crash. */
|
||||||
|
public void crashAlert(Stage stage, String crashMessage) {
|
||||||
|
messageLabel.setText("Unfortunately, we screwed up and the app crashed. Sorry about that!");
|
||||||
|
detailsLabel.setText(crashMessage);
|
||||||
|
|
||||||
|
cancelButton.setVisible(false);
|
||||||
|
actionButton.setVisible(false);
|
||||||
|
okButton.setOnAction(actionEvent -> stage.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize this alert for general information: OK button only, nothing happens on dismissal. */
|
||||||
|
public void informational(Stage stage, String message, String details) {
|
||||||
|
messageLabel.setText(message);
|
||||||
|
detailsLabel.setText(details);
|
||||||
|
cancelButton.setVisible(false);
|
||||||
|
actionButton.setVisible(false);
|
||||||
|
okButton.setOnAction(actionEvent -> stage.close());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package wallettemplate;
|
||||||
|
|
||||||
|
import com.google.bitcoin.core.Address;
|
||||||
|
import com.google.bitcoin.core.AddressFormatException;
|
||||||
|
import com.google.bitcoin.core.NetworkParameters;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import wallettemplate.utils.TextFieldValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a text field, some network params and optionally some nodes, will make the text field an angry red colour
|
||||||
|
* if the address is invalid for those params, and enable/disable the nodes.
|
||||||
|
*/
|
||||||
|
public class BitcoinAddressValidator {
|
||||||
|
private NetworkParameters params;
|
||||||
|
private Node[] nodes;
|
||||||
|
|
||||||
|
public BitcoinAddressValidator(NetworkParameters params, TextField field, Node... nodes) {
|
||||||
|
this.params = params;
|
||||||
|
this.nodes = nodes;
|
||||||
|
|
||||||
|
// Handle the red highlighting, but don't highlight in red just when the field is empty because that makes
|
||||||
|
// the example/prompt address hard to read.
|
||||||
|
new TextFieldValidator(field, text -> text.isEmpty() || testAddr(text));
|
||||||
|
// However we do want the buttons to be disabled when empty so we apply a different test there.
|
||||||
|
field.textProperty().addListener((observableValue, prev, current) -> {
|
||||||
|
toggleButtons(current);
|
||||||
|
});
|
||||||
|
toggleButtons(field.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleButtons(String current) {
|
||||||
|
boolean valid = testAddr(current);
|
||||||
|
for (Node n : nodes) n.setDisable(!valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean testAddr(String text) {
|
||||||
|
try {
|
||||||
|
new Address(params, text);
|
||||||
|
return true;
|
||||||
|
} catch (AddressFormatException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
152
wallettemplate/src/main/java/wallettemplate/Controller.java
Normal file
152
wallettemplate/src/main/java/wallettemplate/Controller.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package wallettemplate;
|
||||||
|
|
||||||
|
import com.google.bitcoin.core.*;
|
||||||
|
import com.google.bitcoin.uri.BitcoinURI;
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import wallettemplate.utils.GuiUtils;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static javafx.application.Platform.isFxApplicationThread;
|
||||||
|
import static wallettemplate.Main.bitcoin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets created auto-magically by FXMLLoader via reflection. The widget fields are set to the GUI controls they're named
|
||||||
|
* after. This class handles all the updates and event handling for the main UI.
|
||||||
|
*/
|
||||||
|
public class Controller {
|
||||||
|
public ProgressBar syncProgress;
|
||||||
|
public VBox syncBox;
|
||||||
|
public HBox controlsBox;
|
||||||
|
public Label requestMoneyLink;
|
||||||
|
public Label balance;
|
||||||
|
public ContextMenu addressMenu;
|
||||||
|
public HBox addressLabelBox;
|
||||||
|
public Button sendMoneyOutBtn;
|
||||||
|
public ImageView copyWidget;
|
||||||
|
|
||||||
|
private Address primaryAddress;
|
||||||
|
|
||||||
|
// Called by FXMLLoader.
|
||||||
|
public void initialize() {
|
||||||
|
syncProgress.setProgress(-1);
|
||||||
|
addressLabelBox.setOpacity(0.0);
|
||||||
|
Tooltip tooltip = new Tooltip("Copy address to clipboard");
|
||||||
|
Tooltip.install(copyWidget, tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBitcoinSetup() {
|
||||||
|
bitcoin.wallet().addEventListener(new BalanceUpdater());
|
||||||
|
primaryAddress = bitcoin.wallet().getKeys().get(0).toAddress(Main.params);
|
||||||
|
refreshBalanceLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestMoney(MouseEvent event) {
|
||||||
|
// User clicked on the address.
|
||||||
|
if (event.getButton() == MouseButton.SECONDARY || (event.getButton() == MouseButton.PRIMARY && event.isMetaDown())) {
|
||||||
|
addressMenu.show(requestMoneyLink, event.getScreenX(), event.getScreenY());
|
||||||
|
} else {
|
||||||
|
String uri = getURI();
|
||||||
|
System.out.println("Opening " + uri);
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().browse(URI.create(uri));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Couldn't open wallet app.
|
||||||
|
GuiUtils.crashAlert(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getURI() {
|
||||||
|
return BitcoinURI.convertToBitcoinURI(getAddress(), Utils.COIN, Main.APP_NAME, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAddress() {
|
||||||
|
return primaryAddress.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyWidgetClicked(MouseEvent event) {
|
||||||
|
copyAddress(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyAddress(ActionEvent event) {
|
||||||
|
// User clicked icon or menu item.
|
||||||
|
Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||||
|
ClipboardContent content = new ClipboardContent();
|
||||||
|
content.putString(getAddress());
|
||||||
|
content.putHtml(String.format("<a href='%s'>%s</a>", getURI(), getAddress()));
|
||||||
|
clipboard.setContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMoneyOut(ActionEvent event) {
|
||||||
|
// Hide this UI and show the send money UI. This UI won't be clickable until the user dismisses send_money.
|
||||||
|
Main.instance.overlayUI("send_money.fxml");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProgressBarUpdater extends DownloadListener {
|
||||||
|
@Override
|
||||||
|
protected void progress(double pct, int blocksSoFar, Date date) {
|
||||||
|
super.progress(pct, blocksSoFar, date);
|
||||||
|
Platform.runLater(() -> syncProgress.setProgress(pct / 100.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doneDownload() {
|
||||||
|
super.doneDownload();
|
||||||
|
Platform.runLater(Controller.this::readyToGoAnimation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readyToGoAnimation() {
|
||||||
|
// Sync progress bar slides out ...
|
||||||
|
TranslateTransition leave = new TranslateTransition(Duration.millis(600), syncBox);
|
||||||
|
leave.setByY(80.0);
|
||||||
|
// Buttons slide in and clickable address appears simultaneously.
|
||||||
|
TranslateTransition arrive = new TranslateTransition(Duration.millis(600), controlsBox);
|
||||||
|
arrive.setToY(0.0);
|
||||||
|
requestMoneyLink.setText(primaryAddress.toString());
|
||||||
|
FadeTransition reveal = new FadeTransition(Duration.millis(500), addressLabelBox);
|
||||||
|
reveal.setToValue(1.0);
|
||||||
|
ParallelTransition group = new ParallelTransition(arrive, reveal);
|
||||||
|
// Slide out happens then slide in/fade happens.
|
||||||
|
SequentialTransition both = new SequentialTransition(leave, group);
|
||||||
|
both.setCycleCount(1);
|
||||||
|
both.setInterpolator(Interpolator.EASE_BOTH);
|
||||||
|
both.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressBarUpdater progressBarUpdater() {
|
||||||
|
return new ProgressBarUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BalanceUpdater extends AbstractWalletEventListener {
|
||||||
|
@Override
|
||||||
|
public void onWalletChanged(Wallet wallet) {
|
||||||
|
checkState(isFxApplicationThread());
|
||||||
|
refreshBalanceLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshBalanceLabel() {
|
||||||
|
final BigInteger amount = bitcoin.wallet().getBalance(Wallet.BalanceType.ESTIMATED);
|
||||||
|
balance.setText(Utils.bitcoinValueToFriendlyString(amount));
|
||||||
|
}
|
||||||
|
}
|
138
wallettemplate/src/main/java/wallettemplate/Main.java
Normal file
138
wallettemplate/src/main/java/wallettemplate/Main.java
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package wallettemplate;
|
||||||
|
|
||||||
|
import com.google.bitcoin.core.NetworkParameters;
|
||||||
|
import com.google.bitcoin.kits.WalletAppKit;
|
||||||
|
import com.google.bitcoin.params.MainNetParams;
|
||||||
|
import com.google.bitcoin.params.RegTestParams;
|
||||||
|
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||||
|
import com.google.bitcoin.utils.Threading;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import wallettemplate.utils.GuiUtils;
|
||||||
|
import wallettemplate.utils.TextFieldValidator;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class Main extends Application {
|
||||||
|
public static String APP_NAME = "WalletTemplate";
|
||||||
|
|
||||||
|
public static NetworkParameters params = MainNetParams.get();
|
||||||
|
public static WalletAppKit bitcoin;
|
||||||
|
public static Main instance;
|
||||||
|
|
||||||
|
private StackPane uiStack;
|
||||||
|
private Pane mainUI;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage mainWindow) throws Exception {
|
||||||
|
instance = this;
|
||||||
|
try {
|
||||||
|
init(mainWindow);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
GuiUtils.crashAlert(t.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Stage mainWindow) throws IOException {
|
||||||
|
// Load the GUI. The Controller class will be automagically created and wired up.
|
||||||
|
URL location = getClass().getResource("main.fxml");
|
||||||
|
FXMLLoader loader = new FXMLLoader(location);
|
||||||
|
mainUI = (Pane) loader.load();
|
||||||
|
Controller controller = loader.getController();
|
||||||
|
// Configure the window with a StackPane so we can overlay things on top of the main UI.
|
||||||
|
uiStack = new StackPane(mainUI);
|
||||||
|
mainWindow.setTitle(APP_NAME);
|
||||||
|
final Scene scene = new Scene(uiStack);
|
||||||
|
TextFieldValidator.configureScene(scene); // Add CSS that we need.
|
||||||
|
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.
|
||||||
|
bitcoin = new WalletAppKit(params, new File("."), APP_NAME);
|
||||||
|
if (params == RegTestParams.get()) {
|
||||||
|
bitcoin.connectToLocalHost(); // You should run a regtest mode bitcoind locally.
|
||||||
|
} else if (params == MainNetParams.get()) {
|
||||||
|
// Checkpoints are block headers that ship inside our app: for a new user, we pick the last header
|
||||||
|
// in the checkpoints file and then download the rest from the network. It makes things much faster.
|
||||||
|
// Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
|
||||||
|
// last months worth or more (takes a few seconds).
|
||||||
|
bitcoin.setCheckpoints(getClass().getResourceAsStream("checkpoints"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
|
||||||
|
// or progress widget to keep the user engaged whilst we initialise, but we don't.
|
||||||
|
bitcoin.setAutoSave(true)
|
||||||
|
.setDownloadListener(controller.progressBarUpdater())
|
||||||
|
.setBlockingStartup(false)
|
||||||
|
.startAndWait();
|
||||||
|
// Don't make the user wait for confirmations for now, as the intention is they're sending it their own money!
|
||||||
|
bitcoin.wallet().allowSpendingUnconfirmedTransactions();
|
||||||
|
System.out.println(bitcoin.wallet());
|
||||||
|
controller.onBitcoinSetup();
|
||||||
|
mainWindow.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OverlayUI {
|
||||||
|
Node ui;
|
||||||
|
Object controller;
|
||||||
|
|
||||||
|
public OverlayUI(Node ui, Object controller) {
|
||||||
|
this.ui = ui;
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void done() {
|
||||||
|
GuiUtils.fadeOutAndRemove(ui, uiStack);
|
||||||
|
GuiUtils.blurIn(mainUI);
|
||||||
|
this.ui = null;
|
||||||
|
this.controller = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loads the FXML file with the given name, blurs out the main UI and puts this one on top. */
|
||||||
|
public OverlayUI overlayUI(String name) {
|
||||||
|
try {
|
||||||
|
// Load the UI from disk.
|
||||||
|
URL location = getClass().getResource(name);
|
||||||
|
FXMLLoader loader = new FXMLLoader(location);
|
||||||
|
Pane ui = (Pane) loader.load();
|
||||||
|
Object controller = loader.getController();
|
||||||
|
OverlayUI pair = new OverlayUI(ui, controller);
|
||||||
|
// Auto-magically set the overlayUi member, if it's there.
|
||||||
|
try {
|
||||||
|
controller.getClass().getDeclaredField("overlayUi").set(controller, pair);
|
||||||
|
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
GuiUtils.blurOut(mainUI);
|
||||||
|
uiStack.getChildren().add(ui);
|
||||||
|
GuiUtils.fadeIn(ui);
|
||||||
|
return pair;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e); // Can't happen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() throws Exception {
|
||||||
|
bitcoin.stopAndWait();
|
||||||
|
super.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch(args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package wallettemplate;
|
||||||
|
|
||||||
|
import com.google.bitcoin.core.Address;
|
||||||
|
import com.google.bitcoin.core.AddressFormatException;
|
||||||
|
import com.google.bitcoin.core.Transaction;
|
||||||
|
import com.google.bitcoin.core.Wallet;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import wallettemplate.utils.GuiUtils;
|
||||||
|
|
||||||
|
public class SendMoneyController {
|
||||||
|
public Button sendBtn;
|
||||||
|
public Button cancelBtn;
|
||||||
|
public TextField address;
|
||||||
|
public Label titleLabel;
|
||||||
|
|
||||||
|
public Main.OverlayUI overlayUi;
|
||||||
|
|
||||||
|
// Called by FXMLLoader
|
||||||
|
public void initialize() {
|
||||||
|
new BitcoinAddressValidator(Main.params, address, sendBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel(ActionEvent event) {
|
||||||
|
overlayUi.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(ActionEvent event) {
|
||||||
|
try {
|
||||||
|
Address destination = new Address(Main.params, address.getText());
|
||||||
|
Wallet.SendRequest req = Wallet.SendRequest.emptyWallet(destination);
|
||||||
|
final Wallet.SendResult sendResult = Main.bitcoin.wallet().sendCoins(req);
|
||||||
|
if (sendResult == null) {
|
||||||
|
// We couldn't empty the wallet for some reason. TODO: When bitcoinj issue 425 is fixed, be more helpful
|
||||||
|
GuiUtils.informationalAlert("Could not empty the wallet",
|
||||||
|
"You may have too little money left in the wallet to make a transaction.");
|
||||||
|
overlayUi.done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Transaction result) {
|
||||||
|
// TODO: Fix bitcoinj so these callbacks run on the user thread.
|
||||||
|
Platform.runLater(overlayUi::done);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
// We died trying to empty the wallet.
|
||||||
|
Platform.runLater(() -> GuiUtils.crashAlert(t.getMessage()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sendBtn.setDisable(true);
|
||||||
|
address.setDisable(true);
|
||||||
|
titleLabel.setText("Broadcasting ...");
|
||||||
|
} catch (AddressFormatException e) {
|
||||||
|
// Cannot happen because we already validated it when the text field changed.
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package wallettemplate.utils;
|
||||||
|
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.effect.GaussianBlur;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import wallettemplate.AlertWindowController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class GuiUtils {
|
||||||
|
private static void runAlert(BiConsumer<Stage, AlertWindowController> setup) {
|
||||||
|
try {
|
||||||
|
// JavaFX2 doesn't actually have a standard alert template. Instead the Scene Builder app will create FXML
|
||||||
|
// files for an alert window for you, and then you customise it as you see fit. I guess it makes sense in
|
||||||
|
// an odd sort of way.
|
||||||
|
Stage dialogStage = new Stage();
|
||||||
|
dialogStage.initModality(Modality.WINDOW_MODAL);
|
||||||
|
FXMLLoader loader = new FXMLLoader(GuiUtils.class.getResource("alert.fxml"));
|
||||||
|
Pane pane = (Pane) loader.load();
|
||||||
|
AlertWindowController controller = (AlertWindowController) loader.getController();
|
||||||
|
setup.accept(dialogStage, controller);
|
||||||
|
dialogStage.setScene(new Scene(pane));
|
||||||
|
dialogStage.showAndWait();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// We crashed whilst trying to show the alert dialog (this should never happen). Give up!
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void crashAlert(String alert) {
|
||||||
|
runAlert((stage, controller) -> controller.crashAlert(stage, alert));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void informationalAlert(String message, String details) {
|
||||||
|
runAlert((stage, controller) -> controller.informational(stage, message, details));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int UI_ANIMATION_TIME_MSEC = 350;
|
||||||
|
|
||||||
|
public static void fadeIn(Node ui) {
|
||||||
|
FadeTransition ft = new FadeTransition(Duration.millis(UI_ANIMATION_TIME_MSEC), ui);
|
||||||
|
ft.setFromValue(0.0);
|
||||||
|
ft.setToValue(1.0);
|
||||||
|
ft.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Animation fadeOut(Node ui) {
|
||||||
|
FadeTransition ft = new FadeTransition(Duration.millis(UI_ANIMATION_TIME_MSEC), ui);
|
||||||
|
ft.setFromValue(ui.getOpacity());
|
||||||
|
ft.setToValue(0.0);
|
||||||
|
ft.play();
|
||||||
|
return ft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Animation fadeOutAndRemove(Node ui, Pane parentPane) {
|
||||||
|
Animation animation = fadeOut(ui);
|
||||||
|
animation.setOnFinished(actionEvent -> parentPane.getChildren().remove(ui));
|
||||||
|
return animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void blurOut(Node node) {
|
||||||
|
GaussianBlur blur = new GaussianBlur(0.0);
|
||||||
|
node.setEffect(blur);
|
||||||
|
Timeline timeline = new Timeline();
|
||||||
|
KeyValue kv = new KeyValue(blur.radiusProperty(), 10.0);
|
||||||
|
KeyFrame kf = new KeyFrame(Duration.millis(UI_ANIMATION_TIME_MSEC), kv);
|
||||||
|
timeline.getKeyFrames().add(kf);
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void blurIn(Node node) {
|
||||||
|
GaussianBlur blur = (GaussianBlur) node.getEffect();
|
||||||
|
Timeline timeline = new Timeline();
|
||||||
|
KeyValue kv = new KeyValue(blur.radiusProperty(), 0.0);
|
||||||
|
KeyFrame kf = new KeyFrame(Duration.millis(UI_ANIMATION_TIME_MSEC), kv);
|
||||||
|
timeline.getKeyFrames().add(kf);
|
||||||
|
timeline.setOnFinished(actionEvent -> node.setEffect(null));
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package wallettemplate.utils;
|
||||||
|
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class TextFieldValidator {
|
||||||
|
private boolean valid;
|
||||||
|
|
||||||
|
public TextFieldValidator(TextField textField, Predicate<String> validator) {
|
||||||
|
this.valid = validator.test(textField.getText());
|
||||||
|
apply(textField, valid);
|
||||||
|
textField.textProperty().addListener((observableValue, prev, current) -> {
|
||||||
|
boolean nowValid = validator.test(current);
|
||||||
|
if (nowValid == valid) return;
|
||||||
|
apply(textField, nowValid);
|
||||||
|
valid = nowValid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void apply(TextField textField, boolean nowValid) {
|
||||||
|
if (nowValid) {
|
||||||
|
textField.getStyleClass().remove("validation_error");
|
||||||
|
} else {
|
||||||
|
textField.getStyleClass().add("validation_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void configureScene(Scene scene) {
|
||||||
|
final String file = TextFieldValidator.class.getResource("text-validation.css").toString();
|
||||||
|
scene.getStylesheets().add(file);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
wallettemplate/src/main/resources/wallettemplate/checkpoints
Normal file
BIN
wallettemplate/src/main/resources/wallettemplate/checkpoints
Normal file
Binary file not shown.
BIN
wallettemplate/src/main/resources/wallettemplate/copy-icon.png
Normal file
BIN
wallettemplate/src/main/resources/wallettemplate/copy-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1010 B |
94
wallettemplate/src/main/resources/wallettemplate/main.fxml
Normal file
94
wallettemplate/src/main/resources/wallettemplate/main.fxml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import java.util.*?>
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.effect.*?>
|
||||||
|
<?import javafx.scene.image.*?>
|
||||||
|
<?import javafx.scene.input.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.paint.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
|
||||||
|
<AnchorPane id="AnchorPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="200.0" minWidth="300.0" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wallettemplate.Controller">
|
||||||
|
<children>
|
||||||
|
<Label layoutX="14.0" layoutY="14.0" text="Balance">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="25.0" fx:id="x1" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<HBox alignment="CENTER_LEFT" prefHeight="30.0" prefWidth="305.0" AnchorPane.leftAnchor="143.0" AnchorPane.rightAnchor="152.0" AnchorPane.topAnchor="14.0">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="balance" text="0.00" underline="false">
|
||||||
|
<font>
|
||||||
|
<Font size="25.0" />
|
||||||
|
</font>
|
||||||
|
<textFill>
|
||||||
|
<Color blue="0.750" green="0.750" red="0.750" fx:id="x2" />
|
||||||
|
</textFill>
|
||||||
|
</Label>
|
||||||
|
<Label font="$x1" text="BTC" textFill="$x2" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<VBox fx:id="syncBox" maxHeight="-1.0" prefHeight="46.0" prefWidth="243.0" spacing="10.0" translateY="0.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Synchronizing with network ..." />
|
||||||
|
<ProgressBar fx:id="syncProgress" maxWidth="1.7976931348623157E308" prefWidth="200.0" progress="0.0" />
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
<HBox fx:id="controlsBox" alignment="TOP_LEFT" fillHeight="true" layoutX="14.0" minHeight="16.0" prefHeight="16.0" prefWidth="205.0" spacing="10.0" translateY="60.0" visible="true" AnchorPane.bottomAnchor="29.0">
|
||||||
|
<children>
|
||||||
|
<Button id="connectBtn" defaultButton="true" mnemonicParsing="false" text="Primary">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<Button mnemonicParsing="false" text="Secondary" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<ImageView fitHeight="243.0" fitWidth="243.0" opacity="0.28" pickOnBounds="true" preserveRatio="true" AnchorPane.bottomAnchor="-36.0" AnchorPane.rightAnchor="-34.0">
|
||||||
|
<effect>
|
||||||
|
<ColorAdjust>
|
||||||
|
<input>
|
||||||
|
<BoxBlur height="20.0" width="20.0" />
|
||||||
|
</input>
|
||||||
|
</ColorAdjust>
|
||||||
|
</effect>
|
||||||
|
<image>
|
||||||
|
<Image url="@bitcoin_logo_plain.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
<HBox fx:id="addressLabelBox" alignment="CENTER_LEFT" layoutY="45.0" prefHeight="21.0" prefWidth="391.0" spacing="10.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="195.0">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="requestMoneyLink" onMouseClicked="#requestMoney" style="-fx-cursor: hand" text="<address goes here>" textFill="BLUE" underline="true">
|
||||||
|
<contextMenu>
|
||||||
|
<ContextMenu fx:id="addressMenu">
|
||||||
|
<items>
|
||||||
|
<MenuItem mnemonicParsing="false" onAction="#copyAddress" text="Copy to clipboard">
|
||||||
|
<accelerator>
|
||||||
|
<KeyCodeCombination alt="UP" code="C" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
|
||||||
|
</accelerator>
|
||||||
|
</MenuItem>
|
||||||
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</contextMenu>
|
||||||
|
</Label>
|
||||||
|
<ImageView fx:id="copyWidget" fitHeight="16.0" fitWidth="16.0" focusTraversable="true" onMouseClicked="#copyWidgetClicked" pickOnBounds="true" preserveRatio="true" smooth="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@copy-icon.png" />
|
||||||
|
</image>
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets />
|
||||||
|
</HBox.margin>
|
||||||
|
</ImageView>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<StackPane id="connectionsListView" layoutX="14.0" layoutY="81.0" prefHeight="249.0" prefWidth="572.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Your content goes here" />
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
|
<Button id="sendMoneyOut" fx:id="sendMoneyOutBtn" alignment="CENTER" mnemonicParsing="false" onAction="#sendMoneyOut" text="Send money out" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="17.0" />
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import java.util.*?>
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.effect.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.paint.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
|
||||||
|
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wallettemplate.SendMoneyController">
|
||||||
|
<children>
|
||||||
|
<VBox alignment="CENTER" layoutY="100.0" prefHeight="200.0" prefWidth="600.0" spacing="20.0" style="-fx-background-color: white;" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="titleLabel" text="Send all money to ...">
|
||||||
|
<font>
|
||||||
|
<Font size="25.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<TextField fx:id="address" prefWidth="354.0" promptText="1EZEqFBd8yuc9ir2761987q7k3VcALC8YQ">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets left="40.0" right="40.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</TextField>
|
||||||
|
<HBox alignment="CENTER" fillHeight="true" prefHeight="30.0" prefWidth="600.0" spacing="50.0" VBox.vgrow="NEVER">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="cancelBtn" cancelButton="true" mnemonicParsing="false" onAction="#cancel" prefWidth="79.0" text="Cancel" />
|
||||||
|
<Button fx:id="sendBtn" defaultButton="true" mnemonicParsing="false" onAction="#send" prefWidth="79.0" text="Send" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
<effect>
|
||||||
|
<DropShadow />
|
||||||
|
</effect>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import java.util.*?>
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.image.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.paint.*?>
|
||||||
|
<?import javafx.scene.text.*?>
|
||||||
|
|
||||||
|
<GridPane hgap="14.0" maxHeight="+Infinity" maxWidth="+Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="20.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wallettemplate.AlertWindowController">
|
||||||
|
<children>
|
||||||
|
<ImageView fitHeight="60.0" fitWidth="60.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="0" GridPane.halignment="CENTER" GridPane.rowIndex="0" GridPane.valignment="TOP">
|
||||||
|
<image>
|
||||||
|
<Image url="@../bitcoin_logo_plain.png" />
|
||||||
|
<!-- place holder -->
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
<VBox maxHeight="+Infinity" maxWidth="+Infinity" minHeight="-Infinity" prefWidth="400.0" spacing="7.0" GridPane.columnIndex="1" GridPane.rowIndex="0">
|
||||||
|
<children>
|
||||||
|
<Label fx:id="messageLabel" text="message" textAlignment="LEFT" wrapText="true">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="13.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="detailsLabel" text="details" textAlignment="LEFT" wrapText="true">
|
||||||
|
<font>
|
||||||
|
<Font size="12.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
<HBox maxHeight="-Infinity" maxWidth="+Infinity" minHeight="-Infinity" minWidth="-Infinity" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
|
<children>
|
||||||
|
<HBox id="HBox" fx:id="actionParent" alignment="CENTER">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="actionButton" mnemonicParsing="false" text="Action" HBox.hgrow="NEVER">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets right="14.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets />
|
||||||
|
</HBox.margin>
|
||||||
|
</HBox>
|
||||||
|
<Pane maxWidth="+Infinity" HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="cancelButton" cancelButton="true" minWidth="80.0" mnemonicParsing="false" text="Cancel" HBox.hgrow="NEVER">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<HBox id="HBox" fx:id="okParent" alignment="CENTER">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="okButton" minWidth="80.0" mnemonicParsing="false" text="Ok" HBox.hgrow="NEVER">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets left="14.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="NEVER" maxWidth="-Infinity" minWidth="-Infinity" />
|
||||||
|
<ColumnConstraints halignment="CENTER" hgrow="ALWAYS" maxWidth="+Infinity" minWidth="-Infinity" />
|
||||||
|
</columnConstraints>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="14.0" left="14.0" right="14.0" top="14.0" />
|
||||||
|
</padding>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints maxHeight="+Infinity" minHeight="-Infinity" valignment="CENTER" vgrow="ALWAYS" />
|
||||||
|
<RowConstraints maxHeight="-Infinity" minHeight="-Infinity" vgrow="NEVER" />
|
||||||
|
</rowConstraints>
|
||||||
|
</GridPane>
|
@ -0,0 +1,17 @@
|
|||||||
|
.text-field.validation_error {
|
||||||
|
-fx-background-color: red,
|
||||||
|
linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
derive(red,70%) 5%,
|
||||||
|
derive(red,90%) 40%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-field.validation_warning {
|
||||||
|
-fx-background-color: orange,
|
||||||
|
linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
derive(orange,70%) 5%,
|
||||||
|
derive(orange,90%) 40%
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user