From 3c3d3a507c460a1228e7d73dcd1dc9a2d0f110d1 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Mon, 17 Nov 2014 12:59:10 +0100 Subject: [PATCH 1/2] Redesign controller/model types and apply to gui.main.Main* Major changes: - Introduce Controller base class and FxmlController subclass. The latter has awareness of an @FXML "root" Node. Together, these classes functionally replace the ViewCB class, however ViewCB has been left in place so as to avoid the need to refactor all controllers at once. In this commit, the new Controller hierarchy has been applied only to the gui.main.MainViewCB controller. - Eliminate MainPM in favor of placing all logic in MainModel. This is potentially temporary, i.e. the distinction between data model and presentation model may be reintroduced in later commits, but for the purposes of this change, the goal was to simplify and remove as many layers as possible. The precise arrangement of controller and model classes is a topic to be discussed when reviewing this change. Minor changes: - Inject model objects into MainModel instead of MainViewCB. Previously, model objects such as WalletService were injected into both MainModel and MainViewCB. Now this intended separation is more strictly observed. - Remove comment section markers and empty methods from MainModel and MainViewCB - Use public constructors in MainModel and elsewhere. This avoids unnecessary IDE warnings, allows the possibility of unit testing, and generally avoids surprise for the reader. - Eliminate Profiler statements in MainModel and elsewhere. These statements are fine during debugging or optimization sessions, but should otherwise be removed so as not to fill the logs with unimportant information. - Change signature of User#getCurrentBankAccount to return ObjectProperty. Previously, this method returned the underlying BankAccount; now returning ObjectProperty allows other components to add listeners (such as for user persistence when changing accounts in the UI). - Handle user persistence on account change elsewhere; namely add a listener for it in the MainModel constructor. Previously this was done in MainModel#setCurrentBankAccount, which amounts to a side effect in a setter method--something to avoid if possible. - Expose MainModel#getUser, and eliminate delegate methods previously in place that mediated access to the User object. This is mainly for consistency and concision. --- .../java/io/bitsquare/gui/Controller.java | 52 +++++ .../java/io/bitsquare/gui/FxmlController.java | 44 ++++ src/main/java/io/bitsquare/gui/Model.java | 22 ++ src/main/java/io/bitsquare/gui/ViewCB.java | 4 +- .../java/io/bitsquare/gui/main/MainModel.java | 188 ++++++++++------ .../java/io/bitsquare/gui/main/MainPM.java | 211 ------------------ .../io/bitsquare/gui/main/MainViewCB.java | 70 +++--- .../trade/createoffer/CreateOfferModel.java | 2 +- .../gui/main/trade/offerbook/OfferBook.java | 2 +- .../main/trade/offerbook/OfferBookModel.java | 10 +- .../java/io/bitsquare/trade/TradeManager.java | 11 +- .../trade/taker/SellerTakesOfferProtocol.java | 2 +- src/main/java/io/bitsquare/user/User.java | 4 +- 13 files changed, 291 insertions(+), 331 deletions(-) create mode 100644 src/main/java/io/bitsquare/gui/Controller.java create mode 100644 src/main/java/io/bitsquare/gui/FxmlController.java create mode 100644 src/main/java/io/bitsquare/gui/Model.java delete mode 100644 src/main/java/io/bitsquare/gui/main/MainPM.java diff --git a/src/main/java/io/bitsquare/gui/Controller.java b/src/main/java/io/bitsquare/gui/Controller.java new file mode 100644 index 0000000000..028a83c144 --- /dev/null +++ b/src/main/java/io/bitsquare/gui/Controller.java @@ -0,0 +1,52 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.gui; + +import java.net.URL; + +import java.util.ResourceBundle; + +import javafx.fxml.Initializable; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class Controller implements Initializable { + + public static final String TITLE_KEY = "view.title"; + + protected M model; + + protected Controller(M model) { + this.model = checkNotNull(model); + } + + @Override + public void initialize(URL url, ResourceBundle rb) { + model.initialize(); + doInitialize(); + } + + public final void terminate() { + //model.terminate(); + //doTerminate(); + } + + protected abstract void doInitialize(); + + protected void doTerminate() { }; +} diff --git a/src/main/java/io/bitsquare/gui/FxmlController.java b/src/main/java/io/bitsquare/gui/FxmlController.java new file mode 100644 index 0000000000..0da5966484 --- /dev/null +++ b/src/main/java/io/bitsquare/gui/FxmlController.java @@ -0,0 +1,44 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.gui; + +import java.net.URL; + +import java.util.ResourceBundle; + +import javafx.fxml.FXML; +import javafx.scene.*; + +public abstract class FxmlController extends Controller { + + protected @FXML R root; + + protected FxmlController(M model) { + super(model); + } + + @Override + public void initialize(URL url, ResourceBundle rb) { + root.sceneProperty().addListener((ov, oldValue, newValue) -> { + // root node has been removed the scene + if (oldValue != null && newValue == null) + terminate(); + }); + super.initialize(url, rb); + } +} diff --git a/src/main/java/io/bitsquare/gui/Model.java b/src/main/java/io/bitsquare/gui/Model.java new file mode 100644 index 0000000000..2300711dfd --- /dev/null +++ b/src/main/java/io/bitsquare/gui/Model.java @@ -0,0 +1,22 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.gui; + +public interface Model { + void initialize(); +} diff --git a/src/main/java/io/bitsquare/gui/ViewCB.java b/src/main/java/io/bitsquare/gui/ViewCB.java index ce39237b44..efbb46dfe7 100644 --- a/src/main/java/io/bitsquare/gui/ViewCB.java +++ b/src/main/java/io/bitsquare/gui/ViewCB.java @@ -40,7 +40,7 @@ public class ViewCB implements Initializable { protected T presentationModel; protected Initializable childController; - protected ViewCB parent; + protected Initializable parent; @FXML protected Parent root; @@ -88,7 +88,7 @@ public class ViewCB implements Initializable { * @param parent Controller who has created this.getClass().getSimpleName() instance (via * navigateToView/FXMLLoader). */ - public void setParent(ViewCB parent) { + public void setParent(Initializable parent) { log.trace("Lifecycle: setParentController " + this.getClass().getSimpleName() + " / parent = " + parent); this.parent = parent; diff --git a/src/main/java/io/bitsquare/gui/main/MainModel.java b/src/main/java/io/bitsquare/gui/main/MainModel.java index e1c60c1fe8..39fdacc77a 100644 --- a/src/main/java/io/bitsquare/gui/main/MainModel.java +++ b/src/main/java/io/bitsquare/gui/main/MainModel.java @@ -18,9 +18,10 @@ package io.bitsquare.gui.main; import io.bitsquare.bank.BankAccount; +import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.btc.WalletService; -import io.bitsquare.gui.UIModel; -import io.bitsquare.gui.util.Profiler; +import io.bitsquare.gui.Model; +import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.msg.MessageService; import io.bitsquare.msg.listeners.BootstrapListener; import io.bitsquare.network.BootstrapState; @@ -40,73 +41,121 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.ListChangeListener; import javafx.collections.MapChangeListener; -import javafx.collections.ObservableList; +import javafx.util.StringConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class MainModel extends UIModel { +class MainModel implements Model { private static final Logger log = LoggerFactory.getLogger(MainModel.class); - private final User user; - private final WalletService walletService; - private final MessageService messageService; - private final TradeManager tradeManager; - private final Persistence persistence; - - private boolean messageServiceInited; - private boolean walletServiceInited; - private boolean servicesInitialised; - final BooleanProperty backendReady = new SimpleBooleanProperty(); final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(-1); final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0); final ObjectProperty bootstrapState = new SimpleObjectProperty<>(); + final StringProperty bootstrapStateText = new SimpleStringProperty(); final ObjectProperty walletServiceException = new SimpleObjectProperty(); + final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty(); + final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty(); + + final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing"); + final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty(); + final BooleanProperty blockchainSyncIndicatorVisible = new SimpleBooleanProperty(true); + final StringProperty blockchainSyncIconId = new SimpleStringProperty(); + final StringProperty walletServiceErrorMsg = new SimpleStringProperty(); + + final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1); + final BooleanProperty bootstrapFailed = new SimpleBooleanProperty(); + final StringProperty bootstrapErrorMsg = new SimpleStringProperty(); + final StringProperty bootstrapIconId = new SimpleStringProperty(); + + private final User user; + private final WalletService walletService; + private final MessageService messageService; + private final TradeManager tradeManager; + private final BitcoinNetwork bitcoinNetwork; + private final BSFormatter formatter; + + private boolean messageServiceInited; + private boolean walletServiceInited; + private boolean servicesInitialised; - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private MainModel(User user, WalletService walletService, MessageService messageService, - TradeManager tradeManager, Persistence persistence) { + public MainModel(User user, WalletService walletService, MessageService messageService, TradeManager tradeManager, + BitcoinNetwork bitcoinNetwork, BSFormatter formatter, Persistence persistence) { this.user = user; this.walletService = walletService; this.messageService = messageService; this.tradeManager = tradeManager; - this.persistence = persistence; + this.formatter = formatter; + this.bitcoinNetwork = bitcoinNetwork; + + user.getCurrentBankAccount().addListener((observable, oldValue, newValue) -> persistence.write(user)); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Lifecycle - /////////////////////////////////////////////////////////////////////////////////////////// - - @SuppressWarnings("EmptyMethod") @Override public void initialize() { - super.initialize(); + bootstrapState.addListener((ov, oldValue, newValue) -> { + if (newValue == BootstrapState.DIRECT_SUCCESS || + newValue == BootstrapState.AUTO_PORT_FORWARDING_SUCCESS || + newValue == BootstrapState.RELAY_SUCCESS) { + bootstrapStateText.set("Successfully connected to P2P network: " + newValue.getMessage()); + bootstrapProgress.set(1); + + if (newValue == BootstrapState.DIRECT_SUCCESS) + bootstrapIconId.set("image-connection-direct"); + else if (newValue == BootstrapState.AUTO_PORT_FORWARDING_SUCCESS) + bootstrapIconId.set("image-connection-nat"); + else if (newValue == BootstrapState.RELAY_SUCCESS) + bootstrapIconId.set("image-connection-relay"); + } + else if (newValue == BootstrapState.PEER_CREATION_FAILED || + newValue == BootstrapState.DIRECT_FAILED || + newValue == BootstrapState.AUTO_PORT_FORWARDING_FAILED || + newValue == BootstrapState.RELAY_FAILED) { + + bootstrapErrorMsg.set(newValue.getMessage()); + bootstrapStateText.set("Connection to P2P network failed."); + bootstrapProgress.set(0); + bootstrapFailed.set(true); + } + else { + bootstrapStateText.set("Connecting to P2P network: " + newValue.getMessage()); + } + } + ); + + walletServiceException.addListener((ov, oldValue, newValue) -> { + blockchainSyncProgress.set(0); + blockchainSyncIndicatorVisible.set(false); + blockchainSyncState.set("Startup failed."); + walletServiceErrorMsg.set(((Throwable) newValue).getMessage()); + }); + + networkSyncProgress.addListener((ov, oldValue, newValue) -> { + setNetworkSyncProgress((double) newValue); + + if ((double) newValue >= 1) + blockchainSyncIconId.set("image-connection-synced"); + }); + setNetworkSyncProgress(networkSyncProgress.get()); + + + user.getBankAccounts().addListener((ListChangeListener) change -> { + bankAccountsComboBoxDisable.set(change.getList().isEmpty()); + bankAccountsComboBoxPrompt.set(change.getList().isEmpty() ? "No accounts" : ""); + }); + bankAccountsComboBoxDisable.set(user.getBankAccounts().isEmpty()); + bankAccountsComboBoxPrompt.set(user.getBankAccounts().isEmpty() ? "No accounts" : ""); } - @SuppressWarnings("EmptyMethod") - @Override - public void terminate() { - super.terminate(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public - /////////////////////////////////////////////////////////////////////////////////////////// - - void initBackend() { - - // For testing with the bootstrap node we need the BootstrappedPeerFactory which gets started from - // messageService.init - + public void initBackend() { messageService.init(new BootstrapListener() { @Override public void onCompleted() { @@ -125,8 +174,6 @@ class MainModel extends UIModel { } }); - Profiler.printMsgWithTime("MainModel.initServices"); - WalletService.BlockchainDownloadListener blockchainDownloadListener = new WalletService .BlockchainDownloadListener() { @Override @@ -164,33 +211,29 @@ class MainModel extends UIModel { } - /////////////////////////////////////////////////////////////////////////////////////////// - // Setters - /////////////////////////////////////////////////////////////////////////////////////////// - - void setCurrentBankAccount(BankAccount bankAccount) { - user.setCurrentBankAccount(bankAccount); - persistence.write(user); + public User getUser() { + return user; } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters - /////////////////////////////////////////////////////////////////////////////////////////// - - ObservableList getBankAccounts() { - return user.getBankAccounts(); + public TradeManager getTradeManager() { + return tradeManager; } - ObjectProperty currentBankAccountProperty() { - return user.currentBankAccountProperty(); + public StringConverter getBankAccountsConverter() { + return new StringConverter() { + @Override + public String toString(BankAccount bankAccount) { + return bankAccount.getNameOfBank(); + } + + @Override + public BankAccount fromString(String s) { + return null; + } + }; } - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - private void onServicesInitialised() { tradeManager.getPendingTrades().addListener((MapChangeListener) change -> updateNumPendingTrades()); @@ -206,4 +249,19 @@ class MainModel extends UIModel { numPendingTrades.set(tradeManager.getPendingTrades().size()); } + 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 + blockchainSyncState.set("Connecting to bitcoin network..."); + + blockchainSyncIndicatorVisible.set(value < 1); + } + + public BitcoinNetwork getBitcoinNetwork() { + return bitcoinNetwork; + } } diff --git a/src/main/java/io/bitsquare/gui/main/MainPM.java b/src/main/java/io/bitsquare/gui/main/MainPM.java deleted file mode 100644 index b09a1f6a8d..0000000000 --- a/src/main/java/io/bitsquare/gui/main/MainPM.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * This file is part of Bitsquare. - * - * Bitsquare is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bitsquare is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bitsquare. If not, see . - */ - -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; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.IntegerProperty; -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.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.util.StringConverter; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class MainPM extends PresentationModel { - private static final Logger log = LoggerFactory.getLogger(MainPM.class); - - private final BSFormatter formatter; - - final BooleanProperty backendReady = new SimpleBooleanProperty(); - final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty(); - final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty(); - final IntegerProperty numPendingTrades = new SimpleIntegerProperty(); - - final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing"); - final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty(); - final BooleanProperty blockchainSyncIndicatorVisible = new SimpleBooleanProperty(true); - final StringProperty blockchainSyncIconId = new SimpleStringProperty(); - final StringProperty walletServiceErrorMsg = new SimpleStringProperty(); - - final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1); - final BooleanProperty bootstrapFailed = new SimpleBooleanProperty(); - final StringProperty bootstrapState = new SimpleStringProperty(); - final StringProperty bootstrapErrorMsg = new SimpleStringProperty(); - final StringProperty bootstrapIconId = new SimpleStringProperty(); - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - @Inject - private MainPM(MainModel model, BSFormatter formatter) { - super(model); - this.formatter = formatter; - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Lifecycle - /////////////////////////////////////////////////////////////////////////////////////////// - - - @SuppressWarnings("EmptyMethod") - @Override - public void initialize() { - super.initialize(); - - backendReady.bind(model.backendReady); - numPendingTrades.bind(model.numPendingTrades); - - model.bootstrapState.addListener((ov, oldValue, newValue) -> { - if (newValue == BootstrapState.DIRECT_SUCCESS || - newValue == BootstrapState.AUTO_PORT_FORWARDING_SUCCESS || - newValue == BootstrapState.RELAY_SUCCESS) { - bootstrapState.set("Successfully connected to P2P network: " + newValue.getMessage()); - bootstrapProgress.set(1); - - if (newValue == BootstrapState.DIRECT_SUCCESS) - bootstrapIconId.set("image-connection-direct"); - else if (newValue == BootstrapState.AUTO_PORT_FORWARDING_SUCCESS) - bootstrapIconId.set("image-connection-nat"); - else if (newValue == BootstrapState.RELAY_SUCCESS) - bootstrapIconId.set("image-connection-relay"); - } - else if (newValue == BootstrapState.PEER_CREATION_FAILED || - newValue == BootstrapState.DIRECT_FAILED || - newValue == BootstrapState.AUTO_PORT_FORWARDING_FAILED || - newValue == BootstrapState.RELAY_FAILED) { - - bootstrapErrorMsg.set(newValue.getMessage()); - bootstrapState.set("Connection to P2P network failed."); - bootstrapProgress.set(0); - bootstrapFailed.set(true); - } - else { - bootstrapState.set("Connecting to P2P network: " + newValue.getMessage()); - } - } - ); - - model.walletServiceException.addListener((ov, oldValue, newValue) -> { - blockchainSyncProgress.set(0); - blockchainSyncIndicatorVisible.set(false); - blockchainSyncState.set("Startup failed."); - walletServiceErrorMsg.set(((Throwable) newValue).getMessage()); - }); - - model.networkSyncProgress.addListener((ov, oldValue, newValue) -> { - setNetworkSyncProgress((double) newValue); - - if ((double) newValue >= 1) - blockchainSyncIconId.set("image-connection-synced"); - }); - setNetworkSyncProgress(model.networkSyncProgress.get()); - - - model.getBankAccounts().addListener((ListChangeListener) change -> { - bankAccountsComboBoxDisable.set(change.getList().isEmpty()); - bankAccountsComboBoxPrompt.set(change.getList().isEmpty() ? "No accounts" : ""); - }); - bankAccountsComboBoxDisable.set(model.getBankAccounts().isEmpty()); - bankAccountsComboBoxPrompt.set(model.getBankAccounts().isEmpty() ? "No accounts" : ""); - } - - @SuppressWarnings("EmptyMethod") - @Override - public void terminate() { - super.terminate(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public - /////////////////////////////////////////////////////////////////////////////////////////// - - void initBackend() { - model.initBackend(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Setters - /////////////////////////////////////////////////////////////////////////////////////////// - - void setCurrentBankAccount(BankAccount bankAccount) { - model.setCurrentBankAccount(bankAccount); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters - /////////////////////////////////////////////////////////////////////////////////////////// - - ObservableList getBankAccounts() { - return model.getBankAccounts(); - } - - ObjectProperty currentBankAccountProperty() { - return model.currentBankAccountProperty(); - } - - StringConverter getBankAccountsConverter() { - return new StringConverter() { - @Override - public String toString(BankAccount bankAccount) { - return bankAccount.getNameOfBank(); - } - - @Override - public BankAccount fromString(String s) { - return null; - } - }; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - 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 - blockchainSyncState.set("Connecting to bitcoin network..."); - - blockchainSyncIndicatorVisible.set(value < 1); - } - -} diff --git a/src/main/java/io/bitsquare/gui/main/MainViewCB.java b/src/main/java/io/bitsquare/gui/main/MainViewCB.java index ea8dd0d44d..9f24c06c15 100644 --- a/src/main/java/io/bitsquare/gui/main/MainViewCB.java +++ b/src/main/java/io/bitsquare/gui/main/MainViewCB.java @@ -19,7 +19,7 @@ package io.bitsquare.gui.main; import io.bitsquare.BitsquareException; import io.bitsquare.bank.BankAccount; -import io.bitsquare.btc.BitcoinNetwork; +import io.bitsquare.gui.FxmlController; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.OverlayManager; import io.bitsquare.gui.ViewCB; @@ -27,11 +27,6 @@ import io.bitsquare.gui.ViewLoader; import io.bitsquare.gui.components.Popups; import io.bitsquare.gui.components.SystemNotification; import io.bitsquare.gui.util.Transitions; -import io.bitsquare.trade.TradeManager; - -import java.net.URL; - -import java.util.ResourceBundle; import javax.inject.Inject; import javax.inject.Named; @@ -50,7 +45,7 @@ import javafx.scene.text.*; import static io.bitsquare.gui.Navigation.Item.*; import static javafx.scene.layout.AnchorPane.*; -public class MainViewCB extends ViewCB { +public class MainViewCB extends FxmlController { private final ToggleGroup navButtons = new ToggleGroup(); @@ -58,33 +53,28 @@ public class MainViewCB extends ViewCB { private final Navigation navigation; private final OverlayManager overlayManager; private final Transitions transitions; - private final BitcoinNetwork bitcoinNetwork; private final String title; @Inject - public MainViewCB(MainPM presentationModel, ViewLoader viewLoader, Navigation navigation, - OverlayManager overlayManager, TradeManager tradeManager, Transitions transitions, - BitcoinNetwork bitcoinNetwork, @Named(TITLE_KEY) String title) { - super(presentationModel); + public MainViewCB(MainModel model, ViewLoader viewLoader, Navigation navigation, OverlayManager overlayManager, + Transitions transitions, @Named(TITLE_KEY) String title) { + super(model); this.viewLoader = viewLoader; this.navigation = navigation; this.overlayManager = overlayManager; this.transitions = transitions; - this.bitcoinNetwork = bitcoinNetwork; this.title = title; - tradeManager.featureNotImplementedWarningProperty().addListener((ov, oldValue, newValue) -> { + model.getTradeManager().featureNotImplementedWarningProperty().addListener((ov, oldValue, newValue) -> { if (oldValue == null && newValue != null) { Popups.openWarningPopup(newValue); - tradeManager.setFeatureNotImplementedWarning(null); + model.getTradeManager().setFeatureNotImplementedWarning(null); } }); } @Override - public void initialize(URL url, ResourceBundle rb) { - super.initialize(url, rb); - + public void doInitialize() { ToggleButton homeButton = new NavButton(HOME) {{ setDisable(true); // during irc demo }}; @@ -151,16 +141,16 @@ public class MainViewCB extends ViewCB { VBox splashScreen = createSplashScreen(); - ((Pane) root).getChildren().addAll(baseApplicationContainer, splashScreen); + root.getChildren().addAll(baseApplicationContainer, splashScreen); - presentationModel.backendReady.addListener((ov1, prev1, ready) -> { + model.backendReady.addListener((ov1, prev1, ready) -> { if (!ready) return; bankAccountComboBoxHolder.getChildren().setAll(createBankAccountComboBox()); - applyPendingTradesInfoIcon(presentationModel.numPendingTrades.get(), portfolioButtonHolder); - presentationModel.numPendingTrades.addListener((ov2, prev2, numPendingTrades) -> + applyPendingTradesInfoIcon(model.numPendingTrades.get(), portfolioButtonHolder); + model.numPendingTrades.addListener((ov2, prev2, numPendingTrades) -> applyPendingTradesInfoIcon((int) numPendingTrades, portfolioButtonHolder)); navigation.navigateToLastStoredItem(); @@ -168,7 +158,7 @@ public class MainViewCB extends ViewCB { transitions.fadeOutAndRemove(splashScreen, 1500); }); - Platform.runLater(presentationModel::initBackend); + Platform.runLater(model::initBackend); } private VBox createSplashScreen() { @@ -181,8 +171,8 @@ public class MainViewCB extends ViewCB { logo.setId("image-splash-logo"); Label blockchainSyncLabel = new Label(); - blockchainSyncLabel.textProperty().bind(presentationModel.blockchainSyncState); - presentationModel.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> { + blockchainSyncLabel.textProperty().bind(model.blockchainSyncState); + model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> { blockchainSyncLabel.setId("splash-error-state-msg"); Popups.openErrorPopup("Error", "An error occurred at startup. \n\nError message:\n" + newValue); @@ -190,13 +180,13 @@ public class MainViewCB extends ViewCB { ProgressBar blockchainSyncIndicator = new ProgressBar(-1); blockchainSyncIndicator.setPrefWidth(120); - blockchainSyncIndicator.progressProperty().bind(presentationModel.blockchainSyncProgress); + blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress); ImageView blockchainSyncIcon = new ImageView(); blockchainSyncIcon.setVisible(false); blockchainSyncIcon.setManaged(false); - presentationModel.blockchainSyncIconId.addListener((ov, oldValue, newValue) -> { + model.blockchainSyncIconId.addListener((ov, oldValue, newValue) -> { blockchainSyncIcon.setId(newValue); blockchainSyncIcon.setVisible(true); blockchainSyncIcon.setManaged(true); @@ -206,7 +196,7 @@ public class MainViewCB extends ViewCB { }); Label bitcoinNetworkLabel = new Label(); - bitcoinNetworkLabel.setText(bitcoinNetwork.toString()); + bitcoinNetworkLabel.setText(model.getBitcoinNetwork().toString()); bitcoinNetworkLabel.setId("splash-bitcoin-network-label"); HBox blockchainSyncBox = new HBox(); @@ -221,19 +211,19 @@ public class MainViewCB extends ViewCB { bootstrapStateLabel.setWrapText(true); bootstrapStateLabel.setMaxWidth(500); bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); - bootstrapStateLabel.textProperty().bind(presentationModel.bootstrapState); + bootstrapStateLabel.textProperty().bind(model.bootstrapStateText); ProgressIndicator bootstrapIndicator = new ProgressIndicator(); bootstrapIndicator.setMaxSize(24, 24); - bootstrapIndicator.progressProperty().bind(presentationModel.bootstrapProgress); + bootstrapIndicator.progressProperty().bind(model.bootstrapProgress); - presentationModel.bootstrapFailed.addListener((ov, oldValue, newValue) -> { + model.bootstrapFailed.addListener((ov, oldValue, newValue) -> { if (newValue) { bootstrapStateLabel.setId("splash-error-state-msg"); bootstrapIndicator.setVisible(false); Popups.openErrorPopup("Error", "Cannot connect to P2P network. \n\nError message:\n" + - presentationModel.bootstrapErrorMsg.get()); + model.bootstrapErrorMsg.get()); } }); @@ -241,7 +231,7 @@ public class MainViewCB extends ViewCB { bootstrapIcon.setVisible(false); bootstrapIcon.setManaged(false); - presentationModel.bootstrapIconId.addListener((ov, oldValue, newValue) -> { + model.bootstrapIconId.addListener((ov, oldValue, newValue) -> { bootstrapIcon.setId(newValue); bootstrapIcon.setVisible(true); bootstrapIcon.setManaged(true); @@ -262,21 +252,21 @@ public class MainViewCB extends ViewCB { } private VBox createBankAccountComboBox() { - final ComboBox comboBox = new ComboBox<>(presentationModel.getBankAccounts()); + final ComboBox comboBox = new ComboBox<>(model.getUser().getBankAccounts()); comboBox.setLayoutY(12); comboBox.setVisibleRowCount(5); - comboBox.setConverter(presentationModel.getBankAccountsConverter()); + comboBox.setConverter(model.getBankAccountsConverter()); comboBox.valueProperty().addListener((ov, oldValue, newValue) -> - presentationModel.setCurrentBankAccount(newValue)); + model.getUser().setCurrentBankAccount(newValue)); - comboBox.disableProperty().bind(presentationModel.bankAccountsComboBoxDisable); - comboBox.promptTextProperty().bind(presentationModel.bankAccountsComboBoxPrompt); + comboBox.disableProperty().bind(model.bankAccountsComboBoxDisable); + comboBox.promptTextProperty().bind(model.bankAccountsComboBoxPrompt); - presentationModel.currentBankAccountProperty().addListener((ov, oldValue, newValue) -> + model.getUser().currentBankAccountProperty().addListener((ov, oldValue, newValue) -> comboBox.getSelectionModel().select(newValue)); - comboBox.getSelectionModel().select(presentationModel.currentBankAccountProperty().get()); + comboBox.getSelectionModel().select(model.getUser().currentBankAccountProperty().get()); final Label titleLabel = new Label("Bank account"); titleLabel.setMouseTransparent(true); diff --git a/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferModel.java b/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferModel.java index 3d43606ddb..bc87472349 100644 --- a/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferModel.java +++ b/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferModel.java @@ -152,7 +152,7 @@ class CreateOfferModel extends UIModel { if (user != null) { user.currentBankAccountProperty().addListener((ov, oldValue, newValue) -> applyBankAccount(newValue)); - applyBankAccount(user.getCurrentBankAccount()); + applyBankAccount(user.getCurrentBankAccount().get()); } if (accountSettings != null) diff --git a/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBook.java b/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBook.java index 0bbc0986c8..086881ec6f 100644 --- a/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBook.java +++ b/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBook.java @@ -171,7 +171,7 @@ public class OfferBook { // TODO Just temporary, will be removed later when we have a push solution private void startPolling() { addListeners(); - setBankAccount(user.getCurrentBankAccount()); + setBankAccount(user.getCurrentBankAccount().get()); pollingTimer = Utilities.setInterval(3000, (animationTimer) -> { offerRepository.requestInvalidationTimeStampFromDHT(fiatCode); return null; diff --git a/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBookModel.java b/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBookModel.java index 0e1dff1f72..28c3e50dc1 100644 --- a/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBookModel.java +++ b/src/main/java/io/bitsquare/gui/main/trade/offerbook/OfferBookModel.java @@ -121,7 +121,7 @@ class OfferBookModel extends UIModel { user.currentBankAccountProperty().addListener(bankAccountChangeListener); btcCode.bind(preferences.btcDenominationProperty()); - setBankAccount(user.getCurrentBankAccount()); + setBankAccount(user.getCurrentBankAccount().get()); applyFilter(); } @@ -181,16 +181,18 @@ class OfferBookModel extends UIModel { boolean isTradable(Offer offer) { // if user has not registered yet we display all - if (user.getCurrentBankAccount() == null) + BankAccount currentBankAccount = user.getCurrentBankAccount().get(); + if (currentBankAccount == null) return true; - boolean countryResult = offer.getAcceptedCountries().contains(user.getCurrentBankAccount().getCountry()); + boolean countryResult = offer.getAcceptedCountries().contains(currentBankAccount.getCountry()); // for IRC test version deactivate the check countryResult = true; if (!countryResult) restrictionsInfo.set("This offer requires that the payments account resides in one of those countries:\n" + formatter.countryLocalesToString(offer.getAcceptedCountries()) + - "\n\nThe country of your payments account (" + user.getCurrentBankAccount().getCountry().getName() + + "\n\nThe country of your payments account (" + user.getCurrentBankAccount().get().getCountry() + .getName() + ") is not included in that list." + "\n\n Do you want to edit your preferences now?"); diff --git a/src/main/java/io/bitsquare/trade/TradeManager.java b/src/main/java/io/bitsquare/trade/TradeManager.java index 6e99c8bead..a504d26411 100644 --- a/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/src/main/java/io/bitsquare/trade/TradeManager.java @@ -18,6 +18,7 @@ package io.bitsquare.trade; import io.bitsquare.account.AccountSettings; +import io.bitsquare.bank.BankAccount; import io.bitsquare.btc.BlockChainService; import io.bitsquare.btc.WalletService; import io.bitsquare.crypto.SignatureService; @@ -55,6 +56,7 @@ import java.util.Map; import javax.inject.Inject; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; @@ -152,16 +154,17 @@ public class TradeManager { TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + BankAccount currentBankAccount = user.getCurrentBankAccount().get(); Offer offer = new Offer(id, user.getMessagePublicKey(), direction, price.getValue(), amount, minAmount, - user.getCurrentBankAccount().getBankAccountType(), - user.getCurrentBankAccount().getCurrency(), - user.getCurrentBankAccount().getCountry(), - user.getCurrentBankAccount().getUid(), + currentBankAccount.getBankAccountType(), + currentBankAccount.getCurrency(), + currentBankAccount.getCountry(), + currentBankAccount.getUid(), accountSettings.getAcceptedArbitrators(), accountSettings.getSecurityDeposit(), accountSettings.getAcceptedCountries(), diff --git a/src/main/java/io/bitsquare/trade/protocol/trade/taker/SellerTakesOfferProtocol.java b/src/main/java/io/bitsquare/trade/protocol/trade/taker/SellerTakesOfferProtocol.java index c291b72634..1614c6ac7a 100644 --- a/src/main/java/io/bitsquare/trade/protocol/trade/taker/SellerTakesOfferProtocol.java +++ b/src/main/java/io/bitsquare/trade/protocol/trade/taker/SellerTakesOfferProtocol.java @@ -154,7 +154,7 @@ public class SellerTakesOfferProtocol { peersMessagePublicKey = offer.getMessagePublicKey(); - bankAccount = user.getCurrentBankAccount(); + bankAccount = user.getCurrentBankAccount().get(); accountId = user.getAccountId(); messagePublicKey = user.getMessagePublicKey(); diff --git a/src/main/java/io/bitsquare/user/User.java b/src/main/java/io/bitsquare/user/User.java index 096c6763dc..461d93fe16 100644 --- a/src/main/java/io/bitsquare/user/User.java +++ b/src/main/java/io/bitsquare/user/User.java @@ -148,8 +148,8 @@ public class User implements Serializable { return bankAccounts; } - public BankAccount getCurrentBankAccount() { - return currentBankAccount.get(); + public ObjectProperty getCurrentBankAccount() { + return currentBankAccount; } public BankAccount getBankAccount(String bankAccountId) { From 67295aea55f92664c13bb373789ffdab1d80c10e Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Tue, 18 Nov 2014 11:57:50 +0100 Subject: [PATCH 2/2] Improve service initialization coordination using rx.Observable This change introduces the use of RxJava's Observable [1] to redesign how we work with non-deterministic and/or event-based information, such as: connecting to peer-to-peer infrastructure, synchronizing the bitcoin blockchain, and so on. Prior to this commit, these activities were initiated in methods like WalletService#initialize and TomP2PMessageService#init. These methods accepted 'listener' interfaces, and these listeners' callback methods would be invoked whenever work progressed, completed, or failed. This approach required significant coordination logic, which, prior to this commit, was found primarily in MainModel#initBackend. A primary goal of the logic found here was to determine when the backend was "ready". This state was represented in MainModel's `backendReady` field, which would be set to true once the following three conditions were satisfied: 1. the message service had finished initialization 2. the wallet service had finished initialization, and 3. the blockchain synchronization had reached 100% Monitoring these three states was complex, and required hard-to-follow conditional logic spread across a number of locations in the code. In any case, however, once these three conditions were satisfied and backendReady's value was set to true, a listener on the backendReady field (in MainViewCB#doInitialize) would then populate combo boxes and pending trade counts in the main view and cause the splash screen to fade out, rendering the application ready for user interaction. The introduction of rx.Observable is designed to achieve the same show-the-splash-screen-until-everything-is-ready functionality described above, without the complex monitoring, conditional logic and nested callbacks. This is achieved by modeling each process as an Observable stream of events. Observables in RxJava can emit any number of events, and can complete either normally or with an error. These observables may be 'subscribed' to by any number of subscribers, and events emitted can be acted upon by instructing the subscriber what to do `onNext`, `onCompleted`, and `onError`. So for example WalletService now exposes an Observable called bootstrapState. This Observable is subscribed to in MainModel#initBackend in such a way that every time it emits a new double value (i.e. a new percentage), the various bootstrap state text labels and progress indicators are updated accordingly. Where it gets really interesting, however, is when Observables are combined. The primary complexity described above is coordinating the fading out of the splash screen with the completed initialization of all backend services. As can now be seen in MainModel#initBackend, the wallet service and message service Observables are simply "merged" into a single observable and returned. From the MainViewCB side, this "single backend observable" is subscribed to and, when it completes (i.e. when all the underlying Observables complete), then combo boxes and pending trade counts are populated and the splash screen is faded out. Understanding RxJava, Observables, and the principles of "Functional Reactive Programming" takes time. It is a paradigm shift in dealing with concurrency and non-determinism, but one that ultimately rewards those who take the time. In the end, I believe it's use will result in a significantly more concise and robust internal architecture for Bitsquare, and using RxJava's lightweight, well-adopted and infrastructure-agnostic API leaves us open to using Akka or other more sophisticated infrastructure later without tying ourselves to those specific APIs (because virtually anything can be modeled as an Observable). Achieve these benifits means that core committers will need to understand how RxJava works, how to think about it, and how to design using it. I have spent the better part of the last week getting to know it, and I am certainly still learning. I can recommend many resources to aid in this process, but having gone through it myself, I recommend that everyone read at least [1] and [2] first. [1]: https://github.com/ReactiveX/RxJava/wiki/Observable [2]: [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) --- build.gradle | 1 + .../java/io/bitsquare/btc/WalletService.java | 164 ++++++++---------- .../java/io/bitsquare/gui/main/MainModel.java | 91 +++------- .../io/bitsquare/gui/main/MainViewCB.java | 28 +-- .../java/io/bitsquare/msg/MessageService.java | 6 +- .../msg/listeners/BootstrapListener.java | 29 ---- .../msg/tomp2p/TomP2PMessageService.java | 10 +- .../io/bitsquare/msg/tomp2p/TomP2PNode.java | 36 ++-- 8 files changed, 145 insertions(+), 220 deletions(-) delete mode 100644 src/main/java/io/bitsquare/msg/listeners/BootstrapListener.java diff --git a/build.gradle b/build.gradle index e3c45ce1b2..345720ff82 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ repositories { dependencies { compile 'org.bitcoinj:bitcoinj-core:0.12' compile 'net.tomp2p:tomp2p-all:5.0-Alpha.60850a9-SNAPSHOT' + compile 'io.reactivex:rxjava:1.0.0-rc.12' compile 'org.springframework:spring-core:4.1.1.RELEASE' compile 'net.sf.jopt-simple:jopt-simple:4.8' compile 'org.slf4j:slf4j-api:1.7.7' diff --git a/src/main/java/io/bitsquare/btc/WalletService.java b/src/main/java/io/bitsquare/btc/WalletService.java index fcfb24a8db..5eba838f18 100644 --- a/src/main/java/io/bitsquare/btc/WalletService.java +++ b/src/main/java/io/bitsquare/btc/WalletService.java @@ -23,6 +23,7 @@ import io.bitsquare.btc.listeners.TxConfidenceListener; import io.bitsquare.crypto.SignatureService; import io.bitsquare.persistence.Persistence; +import org.bitcoinj.core.AbstractWalletEventListener; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; @@ -85,6 +86,10 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import rx.Observable; +import rx.subjects.BehaviorSubject; +import rx.subjects.Subject; + import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; /** @@ -103,6 +108,10 @@ public class WalletService { private final List balanceListeners = new CopyOnWriteArrayList<>(); private final ReentrantLock lock = Threading.lock(LOCK_NAME); + private final ObservableDownloadListener downloadListener = new ObservableDownloadListener(); + private final Observable downloadProgress = downloadListener.getObservable(); + private final WalletEventListener walletEventListener = new BitsquareWalletEventListener(); + private final NetworkParameters params; private final FeePolicy feePolicy; private final SignatureService signatureService; @@ -113,7 +122,6 @@ public class WalletService { private WalletAppKit walletAppKit; private Wallet wallet; - private WalletEventListener walletEventListener; private AddressEntry registrationAddressEntry; private AddressEntry arbitratorDepositAddressEntry; private @GuardedBy(LOCK_NAME) List addressEntryList = new ArrayList<>(); @@ -140,8 +148,9 @@ public class WalletService { // Public Methods /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(Executor executor, BlockchainDownloadListener blockchainDownloadListener, - StartupListener startupListener) { + public Observable initialize(Executor executor) { + Subject status = BehaviorSubject.create(); + // 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 @@ -159,7 +168,7 @@ public class WalletService { walletAppKit.peerGroup().setMaxConnections(11); walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001); initWallet(); - executor.execute(() -> startupListener.completed()); + status.onCompleted(); } }; // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen @@ -186,21 +195,6 @@ public class WalletService { //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(userAgent.getName(), userAgent.getVersion()); @@ -215,51 +209,16 @@ public class WalletService { @Override public void failed(@NotNull Service.State from, @NotNull Throwable failure) { walletAppKit = null; - startupListener.failed(failure); + status.onError(failure); } }, Threading.USER_THREAD); walletAppKit.startAsync(); + + return status.mergeWith(downloadProgress); } private void initWallet() { wallet = walletAppKit.wallet(); - - walletEventListener = new WalletEventListener() { - @Override - public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { - notifyBalanceListeners(); - } - - @Override - public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { - notifyBalanceListeners(); - } - - @Override - public void onReorganize(Wallet wallet) { - - } - - @Override - public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) { - notifyConfidenceListeners(tx); - } - - @Override - public void onWalletChanged(Wallet wallet) { - - } - - @Override - public void onScriptsAdded(Wallet wallet, List