mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 10:15:52 +00:00
Wallet template: add a UI to show the users seed words and demonstrate how to do a restore from seed with WalletAppKit.
This commit is contained in:
parent
78383f98f4
commit
05877abff7
@ -39,11 +39,13 @@
|
|||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>16.0.1</version>
|
<version>16.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Native Mac skin (there's also AeroFX, MetroFX, etc)
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aquafx-project</groupId>
|
<groupId>com.aquafx-project</groupId>
|
||||||
<artifactId>aquafx</artifactId>
|
<artifactId>aquafx</artifactId>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>de.jensd</groupId>
|
<groupId>de.jensd</groupId>
|
||||||
<artifactId>fontawesomefx</artifactId>
|
<artifactId>fontawesomefx</artifactId>
|
||||||
|
@ -46,6 +46,10 @@ public class Controller {
|
|||||||
Main.instance.overlayUI("send_money.fxml");
|
Main.instance.overlayUI("send_money.fxml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void settingsClicked(ActionEvent event) {
|
||||||
|
Main.instance.overlayUI("wallet_settings.fxml");
|
||||||
|
}
|
||||||
|
|
||||||
public class ProgressBarUpdater extends DownloadListener {
|
public class ProgressBarUpdater extends DownloadListener {
|
||||||
@Override
|
@Override
|
||||||
protected void progress(double pct, int blocksSoFar, Date date) {
|
protected void progress(double pct, int blocksSoFar, Date date) {
|
||||||
@ -60,6 +64,20 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void restoreFromSeedAnimation() {
|
||||||
|
// Buttons slide out ...
|
||||||
|
TranslateTransition leave = new TranslateTransition(Duration.millis(600), controlsBox);
|
||||||
|
leave.setByY(80.0);
|
||||||
|
// Sync bar slides in ...
|
||||||
|
TranslateTransition arrive = new TranslateTransition(Duration.millis(600), syncBox);
|
||||||
|
arrive.setToY(0.0);
|
||||||
|
// Slide out happens then slide in/fade happens.
|
||||||
|
SequentialTransition both = new SequentialTransition(leave, arrive);
|
||||||
|
both.setCycleCount(1);
|
||||||
|
both.setInterpolator(Interpolator.EASE_BOTH);
|
||||||
|
both.play();
|
||||||
|
}
|
||||||
|
|
||||||
public void readyToGoAnimation() {
|
public void readyToGoAnimation() {
|
||||||
// Sync progress bar slides out ...
|
// Sync progress bar slides out ...
|
||||||
TranslateTransition leave = new TranslateTransition(Duration.millis(600), syncBox);
|
TranslateTransition leave = new TranslateTransition(Duration.millis(600), syncBox);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package wallettemplate;
|
package wallettemplate;
|
||||||
|
|
||||||
import com.aquafx_project.AquaFx;
|
|
||||||
import com.google.bitcoin.core.NetworkParameters;
|
import com.google.bitcoin.core.NetworkParameters;
|
||||||
import com.google.bitcoin.kits.WalletAppKit;
|
import com.google.bitcoin.kits.WalletAppKit;
|
||||||
import com.google.bitcoin.params.MainNetParams;
|
import com.google.bitcoin.params.MainNetParams;
|
||||||
import com.google.bitcoin.params.RegTestParams;
|
import com.google.bitcoin.params.RegTestParams;
|
||||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||||
import com.google.bitcoin.utils.Threading;
|
import com.google.bitcoin.utils.Threading;
|
||||||
|
import com.google.bitcoin.wallet.DeterministicSeed;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
@ -18,6 +18,7 @@ import javafx.stage.Stage;
|
|||||||
import wallettemplate.utils.GuiUtils;
|
import wallettemplate.utils.GuiUtils;
|
||||||
import wallettemplate.utils.TextFieldValidator;
|
import wallettemplate.utils.TextFieldValidator;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -33,6 +34,7 @@ public class Main extends Application {
|
|||||||
|
|
||||||
private StackPane uiStack;
|
private StackPane uiStack;
|
||||||
private Pane mainUI;
|
private Pane mainUI;
|
||||||
|
public Controller controller;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage mainWindow) throws Exception {
|
public void start(Stage mainWindow) throws Exception {
|
||||||
@ -40,21 +42,23 @@ public class Main extends Application {
|
|||||||
// Show the crash dialog for any exceptions that we don't handle and that hit the main loop.
|
// Show the crash dialog for any exceptions that we don't handle and that hit the main loop.
|
||||||
GuiUtils.handleCrashesOnThisThread();
|
GuiUtils.handleCrashesOnThisThread();
|
||||||
|
|
||||||
// Match Aqua UI style.
|
|
||||||
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
|
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
|
||||||
AquaFx.style();
|
// We could match the Mac Aqua style here, except that (a) Modena doesn't look that bad, and (b)
|
||||||
|
// the date picker widget is kinda broken in AquaFx and I can't be bothered fixing it.
|
||||||
|
// AquaFx.style();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the GUI. The Controller class will be automagically created and wired up.
|
// Load the GUI. The Controller class will be automagically created and wired up.
|
||||||
URL location = getClass().getResource("main.fxml");
|
URL location = getClass().getResource("main.fxml");
|
||||||
FXMLLoader loader = new FXMLLoader(location);
|
FXMLLoader loader = new FXMLLoader(location);
|
||||||
mainUI = loader.load();
|
mainUI = loader.load();
|
||||||
Controller controller = loader.getController();
|
controller = loader.getController();
|
||||||
// Configure the window with a StackPane so we can overlay things on top of the main UI.
|
// Configure the window with a StackPane so we can overlay things on top of the main UI.
|
||||||
uiStack = new StackPane(mainUI);
|
uiStack = new StackPane(mainUI);
|
||||||
mainWindow.setTitle(APP_NAME);
|
mainWindow.setTitle(APP_NAME);
|
||||||
final Scene scene = new Scene(uiStack);
|
final Scene scene = new Scene(uiStack);
|
||||||
TextFieldValidator.configureScene(scene); // Add CSS that we need.
|
TextFieldValidator.configureScene(scene); // Add CSS that we need.
|
||||||
|
scene.getStylesheets().add(getClass().getResource("wallet.css").toString());
|
||||||
mainWindow.setScene(scene);
|
mainWindow.setScene(scene);
|
||||||
|
|
||||||
// Make log output concise.
|
// Make log output concise.
|
||||||
@ -65,7 +69,8 @@ public class Main extends Application {
|
|||||||
// a future version.
|
// a future version.
|
||||||
Threading.USER_THREAD = Platform::runLater;
|
Threading.USER_THREAD = Platform::runLater;
|
||||||
// Create the app kit. It won't do any heavyweight initialization until after we start it.
|
// Create the app kit. It won't do any heavyweight initialization until after we start it.
|
||||||
bitcoin = new WalletAppKit(params, new File("."), APP_NAME);
|
setupWalletKit(null);
|
||||||
|
|
||||||
if (bitcoin.isChainFileLocked()) {
|
if (bitcoin.isChainFileLocked()) {
|
||||||
informationalAlert("Already running", "This application is already running and cannot be started twice.");
|
informationalAlert("Already running", "This application is already running and cannot be started twice.");
|
||||||
Platform.exit();
|
Platform.exit();
|
||||||
@ -74,6 +79,22 @@ public class Main extends Application {
|
|||||||
|
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
|
|
||||||
|
bitcoin.startAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@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();
|
||||||
|
bitcoin.peerGroup().setMaxConnections(11);
|
||||||
|
bitcoin.peerGroup().setBloomFilterFalsePositiveRate(0.00001);
|
||||||
|
Platform.runLater(controller::onBitcoinSetup);
|
||||||
|
}
|
||||||
|
};
|
||||||
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
|
// 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.
|
// or progress widget to keep the user engaged whilst we initialise, but we don't.
|
||||||
if (params == RegTestParams.get()) {
|
if (params == RegTestParams.get()) {
|
||||||
@ -90,13 +111,8 @@ public class Main extends Application {
|
|||||||
bitcoin.setDownloadListener(controller.progressBarUpdater())
|
bitcoin.setDownloadListener(controller.progressBarUpdater())
|
||||||
.setBlockingStartup(false)
|
.setBlockingStartup(false)
|
||||||
.setUserAgent(APP_NAME, "1.0");
|
.setUserAgent(APP_NAME, "1.0");
|
||||||
bitcoin.startAsync();
|
if (seed != null)
|
||||||
bitcoin.awaitRunning();
|
bitcoin.restoreWalletFromSeed(seed);
|
||||||
// Don't make the user wait for confirmations for now, as the intention is they're sending it their own money!
|
|
||||||
bitcoin.wallet().allowSpendingUnconfirmedTransactions();
|
|
||||||
bitcoin.peerGroup().setMaxConnections(11);
|
|
||||||
System.out.println(bitcoin.wallet());
|
|
||||||
controller.onBitcoinSetup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OverlayUI<T> {
|
public class OverlayUI<T> {
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
package wallettemplate;
|
||||||
|
|
||||||
|
import com.google.bitcoin.crypto.MnemonicCode;
|
||||||
|
import com.google.bitcoin.wallet.DeterministicSeed;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.util.concurrent.Service;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.DatePicker;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import wallettemplate.utils.TextFieldValidator;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
import static javafx.beans.binding.Bindings.*;
|
||||||
|
import static wallettemplate.utils.GuiUtils.informationalAlert;
|
||||||
|
import static wallettemplate.utils.WTUtils.didThrow;
|
||||||
|
import static wallettemplate.utils.WTUtils.unchecked;
|
||||||
|
|
||||||
|
public class WalletSettingsController {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WalletSettingsController.class);
|
||||||
|
|
||||||
|
@FXML DatePicker datePicker;
|
||||||
|
@FXML TextArea wordsArea;
|
||||||
|
@FXML Button restoreButton;
|
||||||
|
|
||||||
|
public Main.OverlayUI overlayUi;
|
||||||
|
|
||||||
|
// Called by FXMLLoader
|
||||||
|
public void initialize() {
|
||||||
|
final DeterministicSeed seed = Main.bitcoin.wallet().getKeyChainSeed();
|
||||||
|
|
||||||
|
// Set the date picker to show the birthday of this wallet.
|
||||||
|
Instant creationTime = Instant.ofEpochSecond(seed.getCreationTimeSeconds());
|
||||||
|
final LocalDate origDate = creationTime.atZone(ZoneId.systemDefault()).toLocalDate();
|
||||||
|
datePicker.setValue(origDate);
|
||||||
|
|
||||||
|
// Set the mnemonic seed words.
|
||||||
|
final String origWords = Joiner.on(" ").join(seed.getMnemonicCode());
|
||||||
|
wordsArea.setText(origWords);
|
||||||
|
|
||||||
|
// Validate words as they are being typed.
|
||||||
|
MnemonicCode codec = unchecked(MnemonicCode::new);
|
||||||
|
TextFieldValidator validator = new TextFieldValidator(wordsArea, text ->
|
||||||
|
!didThrow(() -> codec.check(Splitter.on(' ').splitToList(text)))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear the date picker if the user starts editing the words, if it contained the current wallets date.
|
||||||
|
// This forces them to set the birthday field when restoring.
|
||||||
|
wordsArea.textProperty().addListener(o -> {
|
||||||
|
if (origDate.equals(datePicker.getValue()))
|
||||||
|
datePicker.setValue(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
BooleanBinding datePickerIsInvalid = or(
|
||||||
|
datePicker.valueProperty().isNull(),
|
||||||
|
|
||||||
|
createBooleanBinding(() ->
|
||||||
|
datePicker.getValue().isAfter(LocalDate.now())
|
||||||
|
, /* depends on */ datePicker.valueProperty())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't let the user click restore if the words area contains the current wallet words, or are an invalid set,
|
||||||
|
// or if the date field isn't set, or if it's in the future.
|
||||||
|
restoreButton.disableProperty().bind(
|
||||||
|
or(
|
||||||
|
or(
|
||||||
|
not(validator.valid),
|
||||||
|
equal(origWords, wordsArea.textProperty())
|
||||||
|
),
|
||||||
|
|
||||||
|
datePickerIsInvalid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Highlight the date picker in red if it's empty or in the future, so the user knows why restore is disabled.
|
||||||
|
datePickerIsInvalid.addListener((dp, old, cur) -> {
|
||||||
|
if (cur) {
|
||||||
|
datePicker.getStyleClass().add("validation_error");
|
||||||
|
} else {
|
||||||
|
datePicker.getStyleClass().remove("validation_error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeClicked(ActionEvent event) {
|
||||||
|
overlayUi.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreClicked(ActionEvent event) {
|
||||||
|
// Don't allow a restore unless this wallet is presently empty. We don't want to end up with two wallets, too
|
||||||
|
// much complexity, even though WalletAppKit will keep the current one as a backup file in case of disaster.
|
||||||
|
if (Main.bitcoin.wallet().getBalance().value > 0) {
|
||||||
|
informationalAlert("Wallet is not empty",
|
||||||
|
"You must empty this wallet out before attempting to restore an older one, as mixing wallets " +
|
||||||
|
"together can lead to invalidated backups.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Attempting wallet restore using seed '{}' from date {}", wordsArea.getText(), datePicker.getValue());
|
||||||
|
informationalAlert("Wallet restore in progress",
|
||||||
|
"Your wallet will now be resynced from the Bitcoin network. This can take a long time for old wallets.");
|
||||||
|
overlayUi.done();
|
||||||
|
Main.instance.controller.restoreFromSeedAnimation();
|
||||||
|
|
||||||
|
long birthday = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||||
|
DeterministicSeed seed = new DeterministicSeed(Splitter.on(' ').splitToList(wordsArea.getText()), "", birthday);
|
||||||
|
// Shut down bitcoinj and restart it with the new seed.
|
||||||
|
Main.bitcoin.addListener(new Service.Listener() {
|
||||||
|
@Override
|
||||||
|
public void terminated(Service.State from) {
|
||||||
|
super.terminated(from);
|
||||||
|
Main.instance.setupWalletKit(seed);
|
||||||
|
Main.bitcoin.startAsync();
|
||||||
|
}
|
||||||
|
}, Platform::runLater);
|
||||||
|
Main.bitcoin.stopAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,27 @@
|
|||||||
package wallettemplate.utils;
|
package wallettemplate.utils;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextInputControl;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class TextFieldValidator {
|
public class TextFieldValidator {
|
||||||
private boolean valid;
|
public final BooleanProperty valid = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
public TextFieldValidator(TextField textField, Predicate<String> validator) {
|
public TextFieldValidator(TextInputControl control, Predicate<String> validator) {
|
||||||
this.valid = validator.test(textField.getText());
|
this.valid.set(validator.test(control.getText()));
|
||||||
apply(textField, valid);
|
apply(control, valid.get());
|
||||||
textField.textProperty().addListener((observableValue, prev, current) -> {
|
control.textProperty().addListener((observableValue, prev, current) -> {
|
||||||
boolean nowValid = validator.test(current);
|
boolean nowValid = validator.test(current);
|
||||||
if (nowValid == valid) return;
|
if (nowValid == valid.get()) return;
|
||||||
apply(textField, nowValid);
|
valid.set(nowValid);
|
||||||
valid = nowValid;
|
|
||||||
});
|
});
|
||||||
|
valid.addListener(o -> apply(control, valid.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void apply(TextField textField, boolean nowValid) {
|
private static void apply(TextInputControl textField, boolean nowValid) {
|
||||||
if (nowValid) {
|
if (nowValid) {
|
||||||
textField.getStyleClass().remove("validation_error");
|
textField.getStyleClass().remove("validation_error");
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package wallettemplate.utils;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some generic utilities to make Java a bit less annoying.
|
||||||
|
*/
|
||||||
|
public class WTUtils {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WTUtils.class);
|
||||||
|
|
||||||
|
public interface UncheckedRun<T> {
|
||||||
|
public T run() throws Throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UncheckedRunnable {
|
||||||
|
public void run() throws Throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T unchecked(UncheckedRun<T> run) {
|
||||||
|
try {
|
||||||
|
return run.run();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throw new RuntimeException(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uncheck(UncheckedRunnable run) {
|
||||||
|
try {
|
||||||
|
run.run();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throw new RuntimeException(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ignoreAndLog(UncheckedRunnable runnable) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error("Ignoring error", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T ignoredAndLogged(UncheckedRun<T> runnable) {
|
||||||
|
try {
|
||||||
|
return runnable.run();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error("Ignoring error", t);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean didThrow(UncheckedRun run) {
|
||||||
|
try {
|
||||||
|
run.run();
|
||||||
|
return false;
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean didThrow(UncheckedRunnable run) {
|
||||||
|
try {
|
||||||
|
run.run();
|
||||||
|
return false;
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
<?import wallettemplate.controls.*?>
|
<?import wallettemplate.controls.*?>
|
||||||
<?import wallettemplate.controls.ClickableBitcoinAddress ?>
|
<?import wallettemplate.controls.ClickableBitcoinAddress ?>
|
||||||
|
|
||||||
<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">
|
<AnchorPane styleClass="root-pane" maxHeight="Infinity" maxWidth="Infinity" minHeight="200.0" minWidth="300.0" prefHeight="400.0" prefWidth="800.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wallettemplate.Controller">
|
||||||
<children>
|
<children>
|
||||||
<Label layoutX="14.0" layoutY="14.0" text="Balance">
|
<Label layoutX="14.0" layoutY="14.0" text="Balance">
|
||||||
<font>
|
<font>
|
||||||
@ -64,11 +64,14 @@
|
|||||||
</image>
|
</image>
|
||||||
</ImageView>
|
</ImageView>
|
||||||
<ClickableBitcoinAddress fx:id="addressControl" layoutY="45.0" prefHeight="21.0" prefWidth="391.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="195.0" />
|
<ClickableBitcoinAddress fx:id="addressControl" layoutY="45.0" prefHeight="21.0" prefWidth="391.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="195.0" />
|
||||||
<StackPane id="connectionsListView" layoutX="14.0" layoutY="81.0" prefHeight="249.0" prefWidth="572.0">
|
<StackPane style="-fx-border-color: lightgrey; -fx-border-style: dashed" layoutX="14.0" layoutY="81.0" prefHeight="249.0" prefWidth="572.0" AnchorPane.leftAnchor="15.0" AnchorPane.rightAnchor="15.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Your content goes here" />
|
<Label text="Your content goes here" />
|
||||||
</children>
|
</children>
|
||||||
</StackPane>
|
</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" />
|
<HBox AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="17.0" spacing="20">
|
||||||
|
<Button onAction="#settingsClicked" text="Settings"/>
|
||||||
|
<Button id="sendMoneyOut" fx:id="sendMoneyOutBtn" alignment="CENTER" mnemonicParsing="false" onAction="#sendMoneyOut" text="Send money out" />
|
||||||
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<HBox id="HBox" fx:id="okParent" alignment="CENTER">
|
<HBox id="HBox" fx:id="okParent" alignment="CENTER">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="okButton" minWidth="80.0" mnemonicParsing="false" text="Ok" HBox.hgrow="NEVER">
|
<Button fx:id="okButton" minWidth="80.0" mnemonicParsing="false" text="OK" HBox.hgrow="NEVER">
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets left="14.0" />
|
<Insets left="14.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.text-field.validation_error {
|
.validation_error {
|
||||||
-fx-background-color: red,
|
-fx-background-color: red,
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
@ -7,7 +7,25 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-field.validation_warning {
|
.text-area.validation_error .content {
|
||||||
|
-fx-background-color: red,
|
||||||
|
linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
derive(red,70%) 5%,
|
||||||
|
derive(red,90%) 40%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker.validation_error > .text-field {
|
||||||
|
-fx-background-color: red,
|
||||||
|
linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
derive(red,70%) 5%,
|
||||||
|
derive(red,90%) 40%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation_warning {
|
||||||
-fx-background-color: orange,
|
-fx-background-color: orange,
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
|
16
wallettemplate/src/main/resources/wallettemplate/wallet.css
Normal file
16
wallettemplate/src/main/resources/wallettemplate/wallet.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.mnemonic-area *.text {
|
||||||
|
-fx-text-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-banner {
|
||||||
|
-fx-background-color: #b3fbc4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-label {
|
||||||
|
-fx-background-color: darkseagreen;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-pane {
|
||||||
|
-fx-background-color: white;
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?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.*?>
|
||||||
|
|
||||||
|
<StackPane maxHeight="Infinity" maxWidth="Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="wallettemplate.WalletSettingsController">
|
||||||
|
<children>
|
||||||
|
<AnchorPane maxHeight="400.0" maxWidth="600.0" styleClass="root-pane">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" layoutX="26.0" prefHeight="68.0" prefWidth="600.0" styleClass="title-banner" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
|
||||||
|
<children>
|
||||||
|
<Label maxHeight="1.7976931348623157E308" styleClass="title-label" text="settings">
|
||||||
|
<font>
|
||||||
|
<Font size="30.0" />
|
||||||
|
</font>
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets left="20.0" right="20.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<TextArea fx:id="wordsArea" layoutX="22.0" layoutY="155.0" prefHeight="127.0" prefWidth="561.0" styleClass="mnemonic-area" text="absorb tornado scrap blush purpose ethics destroy vicious abandon chunk labor inquiry" wrapText="true" AnchorPane.leftAnchor="22.0" AnchorPane.rightAnchor="17.0">
|
||||||
|
<font>
|
||||||
|
<Font size="30.0" />
|
||||||
|
</font>
|
||||||
|
</TextArea>
|
||||||
|
<Label layoutX="35.0" layoutY="82.0" prefHeight="52.0" prefWidth="530.0" text="These are your wallet words. Write them down along with the creation date, and you can get your money back even if you lose all your wallet backup files. Just type the details back in below to restore!" textAlignment="CENTER" wrapText="true" />
|
||||||
|
<HBox alignment="CENTER_RIGHT" layoutX="22.0" layoutY="292.0" prefHeight="26.0" prefWidth="561.0" spacing="10.0" AnchorPane.rightAnchor="17.0">
|
||||||
|
<children>
|
||||||
|
<Label layoutX="64.0" layoutY="283.0" text="Created on:" />
|
||||||
|
<DatePicker fx:id="datePicker" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER_RIGHT" layoutX="272.0" layoutY="360.0" prefHeight="26.0" prefWidth="311.0" spacing="20.0" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="17.0">
|
||||||
|
<children>
|
||||||
|
<Button maxWidth="80.0" mnemonicParsing="false" text="Backup" HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="restoreButton" maxWidth="80.0" mnemonicParsing="false" onAction="#restoreClicked" text="Restore" HBox.hgrow="ALWAYS" />
|
||||||
|
<Button defaultButton="true" layoutX="523.0" layoutY="360.0" maxWidth="80.0" mnemonicParsing="false" onAction="#closeClicked" text="Close" HBox.hgrow="ALWAYS" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
<effect>
|
||||||
|
<DropShadow />
|
||||||
|
</effect>
|
||||||
|
</AnchorPane>
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
Loading…
x
Reference in New Issue
Block a user