diff --git a/src/main/java/io/bitsquare/app/gui/Main.java b/src/main/java/io/bitsquare/app/gui/Main.java index e8754bf50e..2067587db8 100644 --- a/src/main/java/io/bitsquare/app/gui/Main.java +++ b/src/main/java/io/bitsquare/app/gui/Main.java @@ -139,10 +139,9 @@ public class Main extends Application { // For now we exit when closing/quit the app. // Later we will only hide the window (systemTray.hideStage()) and use the exit item in the system tray for // shut down. - if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent)) - systemTray.exit(); - if (new KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN).match(keyEvent)) - systemTray.exit(); + if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent) || + new KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN).match(keyEvent)) + stop(); }); diff --git a/src/main/java/io/bitsquare/gui/SystemTray.java b/src/main/java/io/bitsquare/gui/SystemTray.java index aadcd02d4f..e611998e06 100644 --- a/src/main/java/io/bitsquare/gui/SystemTray.java +++ b/src/main/java/io/bitsquare/gui/SystemTray.java @@ -94,12 +94,7 @@ public class SystemTray { } }); - exitItem.addActionListener(e -> exit()); - } - - public void exit() { - java.awt.SystemTray.getSystemTray().remove(trayIcon); - onExit.run(); + exitItem.addActionListener(e -> onExit.run()); } public void hideStage() { diff --git a/src/main/java/io/bitsquare/gui/images.css b/src/main/java/io/bitsquare/gui/images.css index 23d26e8a15..ff2d78763f 100644 --- a/src/main/java/io/bitsquare/gui/images.css +++ b/src/main/java/io/bitsquare/gui/images.css @@ -122,3 +122,20 @@ #image-arrow-grey { -fx-image: url("../../../images/arrow_grey.png"); } + +/* connection state*/ +#image-connection-direct { + -fx-image: url("../../../images/connection/direct.png"); +} + +#image-connection-nat { + -fx-image: url("../../../images/connection/nat.png"); +} + +#image-connection-relay { + -fx-image: url("../../../images/connection/relay.png"); +} + +#image-connection-synced { + -fx-image: url("../../../images/connection/synced.png"); +} \ No newline at end of file diff --git a/src/main/java/io/bitsquare/gui/main/MainPM.java b/src/main/java/io/bitsquare/gui/main/MainPM.java index 4a7451d879..539e9090ef 100644 --- a/src/main/java/io/bitsquare/gui/main/MainPM.java +++ b/src/main/java/io/bitsquare/gui/main/MainPM.java @@ -48,16 +48,19 @@ class MainPM extends PresentationModel { final BooleanProperty backendReady = new SimpleBooleanProperty(); final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty(); final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty(); - final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing"); 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 walletFacadeErrorMsg = new SimpleStringProperty(); + final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1); final BooleanProperty bootstrapFailed = new SimpleBooleanProperty(); - final BooleanProperty bootstrapIndicatorVisible = new SimpleBooleanProperty(true); final StringProperty bootstrapState = new SimpleStringProperty(); final StringProperty bootstrapErrorMsg = new SimpleStringProperty(); - final StringProperty walletFacadeErrorMsg = new SimpleStringProperty(); + final StringProperty bootstrapIconId = new SimpleStringProperty(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -88,8 +91,14 @@ class MainPM extends PresentationModel { newValue == BootstrapState.NAT_SUCCESS || newValue == BootstrapState.RELAY_SUCCESS) { bootstrapState.set("Successfully connected to P2P network: " + newValue.getMessage()); - bootstrapIndicatorVisible.set(false); bootstrapProgress.set(1); + + if (newValue == BootstrapState.DIRECT_SUCCESS) + bootstrapIconId.set("image-connection-direct"); + else if (newValue == BootstrapState.NAT_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 || @@ -98,7 +107,6 @@ class MainPM extends PresentationModel { bootstrapErrorMsg.set(newValue.getMessage()); bootstrapState.set("Connection to P2P network failed."); - bootstrapIndicatorVisible.set(false); bootstrapProgress.set(0); bootstrapFailed.set(true); } @@ -115,9 +123,15 @@ class MainPM extends PresentationModel { walletFacadeErrorMsg.set(((Throwable) newValue).getMessage()); }); - model.networkSyncProgress.addListener((ov, oldValue, newValue) -> setNetworkSyncProgress((double) newValue)); + 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" : ""); diff --git a/src/main/java/io/bitsquare/gui/main/MainViewCB.java b/src/main/java/io/bitsquare/gui/main/MainViewCB.java index 3cd6f5becb..88e7913a75 100644 --- a/src/main/java/io/bitsquare/gui/main/MainViewCB.java +++ b/src/main/java/io/bitsquare/gui/main/MainViewCB.java @@ -297,39 +297,64 @@ public class MainViewCB extends ViewCB { ProgressBar blockchainSyncIndicator = new ProgressBar(-1); blockchainSyncIndicator.setPrefWidth(120); blockchainSyncIndicator.progressProperty().bind(presentationModel.blockchainSyncProgress); - blockchainSyncIndicator.visibleProperty().bind(presentationModel.blockchainSyncIndicatorVisible); - blockchainSyncIndicator.managedProperty().bind(presentationModel.blockchainSyncIndicatorVisible); + + ImageView blockchainSyncIcon = new ImageView(); + blockchainSyncIcon.setVisible(false); + blockchainSyncIcon.setManaged(false); + + presentationModel.blockchainSyncIconId.addListener((ov, oldValue, newValue) -> { + blockchainSyncIcon.setId(newValue); + blockchainSyncIcon.setVisible(true); + blockchainSyncIcon.setManaged(true); + + blockchainSyncIndicator.setVisible(false); + blockchainSyncIndicator.setManaged(false); + }); HBox blockchainSyncBox = new HBox(); blockchainSyncBox.setSpacing(10); blockchainSyncBox.setAlignment(Pos.CENTER); blockchainSyncBox.setPadding(new Insets(60, 0, 0, 0)); - blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator); + blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator, blockchainSyncIcon); Label bootstrapStateLabel = new Label(); bootstrapStateLabel.setWrapText(true); bootstrapStateLabel.setMaxWidth(500); bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); bootstrapStateLabel.textProperty().bind(presentationModel.bootstrapState); + + ProgressIndicator bootstrapIndicator = new ProgressIndicator(); + bootstrapIndicator.setMaxSize(24, 24); + bootstrapIndicator.progressProperty().bind(presentationModel.bootstrapProgress); + presentationModel.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()); } }); - ProgressIndicator bootstrapIndicator = new ProgressIndicator(); - bootstrapIndicator.setMaxSize(24, 24); - bootstrapIndicator.progressProperty().bind(presentationModel.bootstrapProgress); - bootstrapIndicator.visibleProperty().bind(presentationModel.bootstrapIndicatorVisible); - bootstrapIndicator.managedProperty().bind(presentationModel.bootstrapIndicatorVisible); + ImageView bootstrapIcon = new ImageView(); + bootstrapIcon.setVisible(false); + bootstrapIcon.setManaged(false); + + presentationModel.bootstrapIconId.addListener((ov, oldValue, newValue) -> { + bootstrapIcon.setId(newValue); + bootstrapIcon.setVisible(true); + bootstrapIcon.setManaged(true); + + bootstrapIndicator.setVisible(false); + bootstrapIndicator.setManaged(false); + }); HBox bootstrapBox = new HBox(); bootstrapBox.setSpacing(10); bootstrapBox.setAlignment(Pos.CENTER); bootstrapBox.setPadding(new Insets(10, 0, 0, 0)); - bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator); + bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator, bootstrapIcon); vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox); return vBox; diff --git a/src/main/java/io/bitsquare/msg/tomp2p/BootstrappedPeerFactory.java b/src/main/java/io/bitsquare/msg/tomp2p/BootstrappedPeerFactory.java index a13a12856e..1642577f58 100644 --- a/src/main/java/io/bitsquare/msg/tomp2p/BootstrappedPeerFactory.java +++ b/src/main/java/io/bitsquare/msg/tomp2p/BootstrappedPeerFactory.java @@ -152,42 +152,44 @@ class BootstrappedPeerFactory { }); // We save last successful bootstrap method. - // Reset it to "default" after 5 start ups. + // Reset it to BootstrapState.DIRECT_SUCCESS after 5 start ups. Object bootstrapCounterObject = persistence.read(this, "bootstrapCounter"); int bootstrapCounter = 0; if (bootstrapCounterObject instanceof Integer) bootstrapCounter = (int) bootstrapCounterObject + 1; if (bootstrapCounter > 5) { - persistence.write(this, "lastSuccessfulBootstrap", "default"); + persistence.write(this, "lastSuccessfulBootstrap", BootstrapState.DIRECT_SUCCESS); bootstrapCounter = 0; } persistence.write(this, "bootstrapCounter", bootstrapCounter); - String lastSuccessfulBootstrap = (String) persistence.read(this, "lastSuccessfulBootstrap"); - if (lastSuccessfulBootstrap == null) - lastSuccessfulBootstrap = "default"; + BootstrapState lastSuccessfulBootstrap = BootstrapState.DIRECT_SUCCESS; + Object lastSuccessfulBootstrapObject = persistence.read(this, "lastSuccessfulBootstrap"); + if (lastSuccessfulBootstrapObject instanceof BootstrapState) + lastSuccessfulBootstrap = (BootstrapState) lastSuccessfulBootstrapObject; + else + persistence.write(this, "lastSuccessfulBootstrap", lastSuccessfulBootstrap); log.debug("lastSuccessfulBootstrap = " + lastSuccessfulBootstrap); - // just temporary while port forwarding is not working - lastSuccessfulBootstrap = "default"; + // just temporary always start with trying direct connection + lastSuccessfulBootstrap = BootstrapState.DIRECT_SUCCESS; switch (lastSuccessfulBootstrap) { - case "relay": + case RELAY_SUCCESS: bootstrapWithRelay(); break; - case "portForwarding": + case NAT_SUCCESS: tryPortForwarding(); break; - case "default": + case DIRECT_SUCCESS: default: discover(); break; } } catch (IOException e) { - setState(BootstrapState.PEER_CREATION, "Cannot create peer with port: " + port + ". Exeption: " + e, false); - settableFuture.setException(e); + handleError(BootstrapState.PEER_CREATION, "Cannot create peer with port: " + port + ". Exeption: " + e); } return settableFuture; @@ -202,7 +204,7 @@ class BootstrappedPeerFactory { public void operationComplete(BaseFuture future) throws Exception { if (future.isSuccess()) { setState(BootstrapState.DIRECT_SUCCESS, "We are directly connected and visible to other peers."); - bootstrap("default"); + bootstrap(BootstrapState.DIRECT_SUCCESS); } else { setState(BootstrapState.DIRECT_NOT_SUCCEEDED, "We are probably behind a NAT and not reachable to " + @@ -213,9 +215,7 @@ class BootstrappedPeerFactory { @Override public void exceptionCaught(Throwable t) throws Exception { - setState(BootstrapState.DIRECT_FAILED, "Exception at discover: " + t.getMessage(), false); - peerDHT.shutdown(); - settableFuture.setException(t); + handleError(BootstrapState.DIRECT_FAILED, "Exception at discover: " + t.getMessage()); } }); } @@ -244,9 +244,7 @@ class BootstrappedPeerFactory { @Override public void exceptionCaught(Throwable t) throws Exception { - setState(BootstrapState.NAT_FAILED, "Exception at port forwarding: " + t.getMessage(), false); - peerDHT.shutdown(); - settableFuture.setException(t); + handleError(BootstrapState.NAT_FAILED, "Exception at port forwarding: " + t.getMessage()); } }); } @@ -259,24 +257,17 @@ class BootstrappedPeerFactory { public void operationComplete(BaseFuture future) throws Exception { if (future.isSuccess()) { setState(BootstrapState.NAT_SUCCESS, "Discover with automatic port forwarding was successful."); - bootstrap("portForwarding"); + bootstrap(BootstrapState.NAT_SUCCESS); } else { - setState(BootstrapState.NAT_FAILED, "Discover with automatic port forwarding has failed " + - futureDiscover.failedReason(), false); - persistence.write(BootstrappedPeerFactory.this, "lastSuccessfulBootstrap", "default"); - peerDHT.shutdown(); - settableFuture.setException(new Exception("Discover with automatic port forwarding failed " + - futureDiscover.failedReason())); + handleError(BootstrapState.NAT_FAILED, "Discover with automatic port forwarding has failed " + + futureDiscover.failedReason()); } } @Override public void exceptionCaught(Throwable t) throws Exception { - setState(BootstrapState.NAT_FAILED, "Exception at discover: " + t, false); - persistence.write(BootstrappedPeerFactory.this, "lastSuccessfulBootstrap", "default"); - peerDHT.shutdown(); - settableFuture.setException(t); + handleError(BootstrapState.NAT_FAILED, "Exception at discover: " + t.getMessage()); } }); } @@ -293,50 +284,40 @@ class BootstrappedPeerFactory { public void operationComplete(BaseFuture future) throws Exception { if (future.isSuccess()) { setState(BootstrapState.RELAY_SUCCESS, "Bootstrap using relay was successful."); - bootstrap("relay"); + bootstrap(BootstrapState.RELAY_SUCCESS); } else { - setState(BootstrapState.RELAY_FAILED, "Bootstrap using relay has failed " + futureRelayNAT - .failedReason(), false); - persistence.write(BootstrappedPeerFactory.this, "lastSuccessfulBootstrap", "default"); - futureRelayNAT.shutdown(); - peerDHT.shutdown(); - settableFuture.setException(new Exception("Bootstrap using relay failed " + - futureRelayNAT.failedReason())); + handleError(BootstrapState.RELAY_FAILED, "Bootstrap using relay has failed " + + futureRelayNAT.failedReason()); } } @Override public void exceptionCaught(Throwable t) throws Exception { - setState(BootstrapState.RELAY_FAILED, "Exception at bootstrapWithRelay: " + t, false); - persistence.write(BootstrappedPeerFactory.this, "lastSuccessfulBootstrap", "default"); - futureRelayNAT.shutdown(); - peerDHT.shutdown(); - settableFuture.setException(t); + handleError(BootstrapState.RELAY_FAILED, "Exception at bootstrapWithRelay: " + t.getMessage()); } }); } - private void bootstrap(String state) { + private void bootstrap(BootstrapState state) { FutureBootstrap futureBootstrap = peer.bootstrap().peerAddress(getBootstrapAddress()).start(); futureBootstrap.addListener(new BaseFutureListener() { @Override public void operationComplete(BaseFuture future) throws Exception { if (futureBootstrap.isSuccess()) { - setState(BootstrapState.DIRECT_SUCCESS, "Bootstrap successful."); + setState(state, "Bootstrap successful."); persistence.write(BootstrappedPeerFactory.this, "lastSuccessfulBootstrap", state); settableFuture.set(peerDHT); } else { - setState(BootstrapState.DIRECT_NOT_SUCCEEDED, "Bootstrapping failed."); + handleError(BootstrapState.DIRECT_NOT_SUCCEEDED, "Bootstrapping failed. " + + futureBootstrap.failedReason()); } } @Override public void exceptionCaught(Throwable t) throws Exception { - setState(BootstrapState.DIRECT_FAILED, "Exception at bootstrap: " + t.getMessage(), false); - peerDHT.shutdown(); - settableFuture.setException(t); + handleError(BootstrapState.DIRECT_FAILED, "Exception at bootstrap: " + t.getMessage()); } }); } @@ -366,4 +347,11 @@ class BootstrappedPeerFactory { bootstrapState.setMessage(message); Platform.runLater(() -> this.bootstrapState.set(bootstrapState)); } + + private void handleError(BootstrapState state, String errorMessage) { + setState(state, errorMessage, false); + persistence.write(this, "lastSuccessfulBootstrap", BootstrapState.DIRECT_SUCCESS); + peerDHT.shutdown(); + settableFuture.setException(new Exception(errorMessage)); + } } diff --git a/src/main/resources/images/connection/direct.png b/src/main/resources/images/connection/direct.png new file mode 100644 index 0000000000..aa1636a876 Binary files /dev/null and b/src/main/resources/images/connection/direct.png differ diff --git a/src/main/resources/images/connection/direct@2x.png b/src/main/resources/images/connection/direct@2x.png new file mode 100644 index 0000000000..a0acbe3187 Binary files /dev/null and b/src/main/resources/images/connection/direct@2x.png differ diff --git a/src/main/resources/images/connection/nat.png b/src/main/resources/images/connection/nat.png new file mode 100644 index 0000000000..90ca54db25 Binary files /dev/null and b/src/main/resources/images/connection/nat.png differ diff --git a/src/main/resources/images/connection/nat@2x.png b/src/main/resources/images/connection/nat@2x.png new file mode 100644 index 0000000000..9f1f2dfdf4 Binary files /dev/null and b/src/main/resources/images/connection/nat@2x.png differ diff --git a/src/main/resources/images/connection/relay.png b/src/main/resources/images/connection/relay.png new file mode 100644 index 0000000000..50c3ae86c3 Binary files /dev/null and b/src/main/resources/images/connection/relay.png differ diff --git a/src/main/resources/images/connection/relay@2x.png b/src/main/resources/images/connection/relay@2x.png new file mode 100644 index 0000000000..18333580a4 Binary files /dev/null and b/src/main/resources/images/connection/relay@2x.png differ diff --git a/src/main/resources/images/connection/synced.png b/src/main/resources/images/connection/synced.png new file mode 100644 index 0000000000..0d13421744 Binary files /dev/null and b/src/main/resources/images/connection/synced.png differ diff --git a/src/main/resources/images/connection/synced@2x.png b/src/main/resources/images/connection/synced@2x.png new file mode 100644 index 0000000000..f4bce5e24f Binary files /dev/null and b/src/main/resources/images/connection/synced@2x.png differ