package wallettemplate;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.DownloadProgressTracker;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.utils.MonetaryFormat;
import org.coinjoin.client.MixStart;
import com.subgraph.orchid.TorClient;
import com.subgraph.orchid.TorInitializationListener;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.HBox;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.fxmisc.easybind.EasyBind;
import wallettemplate.controls.ClickableBitcoinAddress;
import wallettemplate.controls.NotificationBarPane;
import wallettemplate.utils.BitcoinUIModel;
import wallettemplate.utils.easing.EasingMode;
import wallettemplate.utils.easing.ElasticInterpolator;
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 MainController {
public HBox controlsBox;
public Label balance;
public Button sendMoneyOutBtn;
public ClickableBitcoinAddress addressControl;
public ListView<TransactionOutput> outputSelect;
public TextField destination;
public Button newDestination, newChange, mixStart;
public TextField change;
public TextArea debugInfo;
private MixStart mix;
private Address destAddr, changeAddr;
private BitcoinUIModel model = new BitcoinUIModel();
private NotificationBarPane.Item syncItem;
// Called by FXMLLoader.
public void initialize() {
addressControl.setOpacity(0.0);
}
public void onBitcoinSetup() {
model.setWallet(bitcoin.wallet());
addressControl.addressProperty().bind(model.addressProperty());
balance.textProperty().bind(EasyBind.map(model.balanceProperty(), coin -> MonetaryFormat.BTC.noCode().format(coin).toString()));
// Don't let the user click send money when the wallet is empty.
sendMoneyOutBtn.disableProperty().bind(model.balanceProperty().isEqualTo(Coin.ZERO));
TorClient torClient = Main.bitcoin.peerGroup().getTorClient();
if (torClient != null) {
SimpleDoubleProperty torProgress = new SimpleDoubleProperty(-1);
String torMsg = "Initialising Tor";
syncItem = Main.instance.notificationBar.pushItem(torMsg, torProgress);
torClient.addInitializationListener(new TorInitializationListener() {
@Override
public void initializationProgress(String message, int percent) {
Platform.runLater(() -> {
syncItem.label.set(torMsg + ": " + message);
torProgress.set(percent / 100.0);
});
}
@Override
public void initializationCompleted() {
Platform.runLater(() -> {
syncItem.cancel();
showBitcoinSyncMessage();
});
}
});
} else {
showBitcoinSyncMessage();
}
model.syncProgressProperty().addListener(x -> {
if (model.syncProgressProperty().get() >= 1.0) {
readyToGoAnimation();
if (syncItem != null) {
syncItem.cancel();
syncItem = null;
}
} else if (syncItem == null) {
showBitcoinSyncMessage();
}
});
Bindings.bindContent(outputSelect.getItems(), model.getOutputs());
outputSelect.setCellFactory(new Callback<ListView<TransactionOutput>, ListCell<TransactionOutput>>() {
@Override
public ListCell<TransactionOutput> call(
ListView<TransactionOutput> param) {
return new TextFieldListCell<TransactionOutput>(new StringConverter<TransactionOutput>() {
@Override
public String toString(TransactionOutput object) {
return (object.getValue().toPlainString() + " BTC | " + object.getAddressFromP2PKHScript(object.getParams()));
}
@Override
public TransactionOutput fromString(String string) {
// TODO Auto-generated method stub
return null;
}
});
}
});
destAddr = Main.bitcoin.wallet().currentReceiveAddress();
changeAddr = Main.bitcoin.wallet().getChangeAddress();
destination.setEditable(false);
destination.setText(destAddr.toString());
change.setEditable(false);
change.setText(changeAddr.toString());
debugInfo.setEditable(false);
debugInfo.clear();
}
public void mixStart(ActionEvent event) {
newDestination.disableProperty().set(true);
newChange.disableProperty().set(true);
mixStart.disableProperty().set(true);
debugInfo.clear();
TransactionOutput inputBuilder = outputSelect.getSelectionModel().getSelectedItem();
if (inputBuilder == null){
debugInfo.appendText("[ERROR] No Selected Output!\n");
this.finishMix();
} else {
StringProperty strProp = new SimpleStringProperty();
strProp.setValue(debugInfo.getText());
debugInfo.textProperty().bind(strProp);
mix = new MixStart(this, strProp, inputBuilder, destAddr, changeAddr);
Thread t = new Thread(mix);
t.setDaemon(true);
t.start();
}
}
public void finishMix() {
debugInfo.textProperty().unbind();
newDestination.disableProperty().set(false);
newChange.disableProperty().set(false);
mixStart.disableProperty().set(false);
}
public void updateChange(ActionEvent event) {
changeAddr = Main.bitcoin.wallet().freshReceiveAddress();
change.setText(changeAddr.toString());
}
public void updateDest(ActionEvent event) {
destAddr = Main.bitcoin.wallet().freshReceiveAddress();
destination.setText(destAddr.toString());
}
private void showBitcoinSyncMessage() {
syncItem = Main.instance.notificationBar.pushItem("Synchronising with the Bitcoin network", model.syncProgressProperty());
}
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 void settingsClicked(ActionEvent event) {
Main.OverlayUI<WalletSettingsController> screen = Main.instance.overlayUI("wallet_settings.fxml");
screen.controller.initialize(null);
}
public void restoreFromSeedAnimation() {
// Buttons slide out ...
TranslateTransition leave = new TranslateTransition(Duration.millis(1200), controlsBox);
leave.setByY(80.0);
leave.play();
}
public void readyToGoAnimation() {
// Buttons slide in and clickable address appears simultaneously.
TranslateTransition arrive = new TranslateTransition(Duration.millis(1200), controlsBox);
arrive.setInterpolator(new ElasticInterpolator(EasingMode.EASE_OUT, 1, 2));
arrive.setToY(0.0);
FadeTransition reveal = new FadeTransition(Duration.millis(1200), addressControl);
reveal.setToValue(1.0);
ParallelTransition group = new ParallelTransition(arrive, reveal);
group.setDelay(NotificationBarPane.ANIM_OUT_DURATION);
group.setCycleCount(1);
group.play();
}
public DownloadProgressTracker progressBarUpdater() {
return model.getDownloadProgressTracker();
}
}