From a0b05c07418e6ad15198cf4106dadf199f724714 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 11 Jul 2018 00:08:42 +0200 Subject: [PATCH 01/20] Add mobile notification view --- build.gradle | 3 + .../notifications/NotificationsView.fxml | 34 ++ .../notifications/NotificationsView.java | 175 ++++++++++ .../content/notifications/QrCodeWindow.java | 110 ++++++ .../notifications/WebCamAppLauncher.java | 325 ++++++++++++++++++ .../account/settings/AccountSettingsView.java | 9 +- 6 files changed, 654 insertions(+), 2 deletions(-) create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java diff --git a/build.gradle b/build.gradle index 2fef01637d2..e03cb422426 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,9 @@ dependencies { compile 'de.jensd:fontawesomefx-commons:8.15' compile 'de.jensd:fontawesomefx-materialdesignfont:1.7.22-4' compile 'com.googlecode.jcsv:jcsv:1.4.0' + compile 'com.github.sarxos:webcam-capture:0.3.12' + + compileOnly 'org.projectlombok:lombok:1.16.16' annotationProcessor 'org.projectlombok:lombok:1.16.16' testCompile('org.mockito:mockito-core:2.8.9') { diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml new file mode 100644 index 00000000000..4e00072cf38 --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java new file mode 100644 index 00000000000..3faa04439bb --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -0,0 +1,175 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.notifications; + +import bisq.desktop.common.view.ActivatableView; +import bisq.desktop.common.view.FxmlView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.Layout; + +import bisq.core.locale.Res; +import bisq.core.notifications.MobileMessage; +import bisq.core.notifications.MobileMessageType; +import bisq.core.notifications.MobileNotificationService; +import bisq.core.notifications.MobileNotificationValidator; +import bisq.core.user.Preferences; + +import javax.inject.Inject; + +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; + +import javafx.beans.value.ChangeListener; + +import java.util.Random; + +@FxmlView +public class NotificationsView extends ActivatableView { + private final Preferences preferences; + private final MobileNotificationValidator mobileNotificationValidator; + private final MobileNotificationService mobileNotificationService; + + private int gridRow = 0; + private TextField tokenInputTextField; + private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; + private ChangeListener tokenInputTextFieldListener; + private Button webCamButton; + private Button wipeOutButton; + private Button devButton; + private ChangeListener useSoundCheckBoxListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private NotificationsView(Preferences preferences, + MobileNotificationValidator mobileNotificationValidator, + MobileNotificationService mobileNotificationService) { + super(); + this.preferences = preferences; + this.mobileNotificationValidator = mobileNotificationValidator; + this.mobileNotificationService = mobileNotificationService; + } + + @Override + public void initialize() { + mobileNotificationService.init(); + + FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title")); + webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"), Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second; + webCamButton.setDefaultButton(true); + + webCamButton.setOnAction((event) -> { + webCamButton.setDisable(true); + new QrCodeWindow(this::applyKeyAndToken); + }); + + tokenInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")).second; + tokenInputTextField.setPromptText(Res.get("account.notifications.email.prompt")); + if (preferences.getPhoneKeyAndToken() != null) + tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); + tokenInputTextFieldListener = (observable, oldValue, newValue) -> { + applyKeyAndToken(newValue); + }; + + Button resetButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.reset.label"), Res.get("account.notifications.reset.title")).second; + resetButton.setOnAction((event) -> { + mobileNotificationService.reset(); + tokenInputTextField.clear(); + setDisableAtControls(true); + }); + + wipeOutButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.wipeout.label"), Res.get("account.notifications.wipeout.title")).second; + wipeOutButton.setOnAction((event) -> { + try { + mobileNotificationService.sendWipeOutMessage(); + } catch (Exception e) { + new Popup<>().error(e.toString()).show(); + } + }); + + FormBuilder.addTitledGroupBg(root, ++gridRow, 5, Res.get("account.notifications.selection.title"), + Layout.GROUP_DISTANCE); + tradeCheckBox = FormBuilder.addLabelCheckBox(root, gridRow, Res.get("account.notifications.trade.label"), + Res.get("account.notifications.checkbox.title"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + + marketCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.market.label"), + Res.get("account.notifications.checkbox.title")).second; + + priceCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.price.label"), + Res.get("account.notifications.checkbox.title")).second; + + useSoundCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.useSound.label"), + Res.get("account.notifications.useSound.title")).second; + useSoundCheckBox.setSelected(preferences.isUseSoundForMobileNotifications()); + useSoundCheckBoxListener = (observable, oldValue, newValue) -> { + mobileNotificationService.getUseSoundProperty().set(newValue); + preferences.setUseSoundForMobileNotifications(newValue); + }; + + //TODO remove later + devButton = FormBuilder.addButton(root, ++gridRow, "Send test msg"); + devButton.setOnAction(event -> { + MobileMessage message = new MobileMessage("test title", + "test msg " + new Random().nextInt(1000), + "test txid", + MobileMessageType.TRADE); + try { + mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); + } catch (Exception e) { + new Popup<>().error(e.toString()).show(); + } + }); + + setDisableAtControls(!mobileNotificationService.isSetupConfirmationSent()); + } + + @Override + protected void activate() { + tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); + useSoundCheckBox.selectedProperty().addListener(useSoundCheckBoxListener); + } + + @Override + protected void deactivate() { + tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); + useSoundCheckBox.selectedProperty().removeListener(useSoundCheckBoxListener); + } + + private void applyKeyAndToken(String keyAndToken) { + mobileNotificationService.applyKeyAndToken(keyAndToken); + if (mobileNotificationValidator.isValid(keyAndToken)) + setDisableAtControls(false); + } + + private void setDisableAtControls(boolean disable) { + wipeOutButton.setDisable(disable); + tradeCheckBox.setDisable(disable); + useSoundCheckBox.setDisable(disable); + marketCheckBox.setDisable(disable); + priceCheckBox.setDisable(disable); + devButton.setDisable(disable); + } +} + diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java new file mode 100644 index 00000000000..7c4ccf308f5 --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java @@ -0,0 +1,110 @@ +package bisq.desktop.main.account.content.notifications; + +import bisq.common.UserThread; +import bisq.common.util.Utilities; + +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + + + +import com.github.sarxos.webcam.Webcam; +import com.github.sarxos.webcam.WebcamPanel; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.Result; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.HybridBinarizer; +import javax.swing.JFrame; + +public class QrCodeWindow extends JFrame { + + private Webcam webcam; + private Executor executor = Utilities.getListeningSingleThreadExecutor("readQR-runner"); + + private WebcamPanel panel; + private Consumer qrCodeResultHandler; + + public QrCodeWindow(Consumer qrCodeResultHandler) { + this.qrCodeResultHandler = qrCodeResultHandler; + + setLayout(new FlowLayout()); + setTitle("Bisq Notification token"); //TODO + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(WindowEvent windowEvent) { + webcam.close(); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + } + }); + + List webcams = Webcam.getWebcams(); + webcam = webcams.get(0); + Dimension[] sizes = webcam.getViewSizes(); + webcam.setViewSize(sizes[sizes.length - 1]); + + panel = new WebcamPanel(webcam); + add(panel); + pack(); + setLocationRelativeTo(null); + setVisible(true); + executor.execute(this::doRun); + } + + private void doRun() { + // check 10 times a second for the QR code + boolean run = true; + while (run) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + Result result = null; + BufferedImage image; + + if (webcam.isOpen()) { + if ((image = webcam.getImage()) == null) + continue; + + LuminanceSource source = new BufferedImageLuminanceSource(image); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + + try { + result = new MultiFormatReader().decode(bitmap); + } catch (NotFoundException ignore) { + // fall thru, it means there is no QR code in image + } + } + + if (result != null) { + String qrCode = result.getText(); + //boolean sendConfirmation = phone.validatePhoneId(qrCode); + // phone.save(); + dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); + run = false; + UserThread.execute(() -> { + qrCodeResultHandler.accept(qrCode); + }); + /* Platform.runLater( + () -> { + // app.updateGUI(); + // app.sendConfirmation(); + } + );*/ + } + } + } +} diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java new file mode 100644 index 00000000000..e29d14b916b --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java @@ -0,0 +1,325 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.notifications; + + +import javafx.application.Application; +import javafx.application.Platform; + +import javafx.stage.Stage; + +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.FlowPane; + +import javafx.geometry.Orientation; +import javafx.geometry.Pos; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.awt.image.BufferedImage; + + + +import com.github.sarxos.webcam.Webcam; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingFXUtils; + +public class WebCamAppLauncher extends Application { + + private FlowPane bottomCameraControlPane; + private FlowPane topPane; + private BorderPane root; + private String cameraListPromptText = "Choose Camera"; + private ImageView imgWebCamCapturedImage; + private Webcam webCam = null; + private boolean stopCamera = false; + private BufferedImage grabbedImage; + ObjectProperty imageProperty = new SimpleObjectProperty(); + private BorderPane webCamPane; + private Button btnCamreaStop; + private Button btnCamreaStart; + private Button btnCameraDispose; + + @Override + public void start(Stage primaryStage) { + + primaryStage.setTitle("Connecting WebCam Using Sarxos API"); + root = new BorderPane(); + topPane = new FlowPane(); + topPane.setAlignment(Pos.CENTER); + topPane.setHgap(20); + topPane.setOrientation(Orientation.HORIZONTAL); + topPane.setPrefHeight(40); + root.setTop(topPane); + webCamPane = new BorderPane(); + webCamPane.setStyle("-fx-background-color: #ccc;"); + imgWebCamCapturedImage = new ImageView(); + webCamPane.setCenter(imgWebCamCapturedImage); + root.setCenter(webCamPane); + createTopPanel(); + bottomCameraControlPane = new FlowPane(); + bottomCameraControlPane.setOrientation(Orientation.HORIZONTAL); + bottomCameraControlPane.setAlignment(Pos.CENTER); + bottomCameraControlPane.setHgap(20); + bottomCameraControlPane.setVgap(10); + bottomCameraControlPane.setPrefHeight(40); + bottomCameraControlPane.setDisable(true); + createCameraControls(); + root.setBottom(bottomCameraControlPane); + + primaryStage.setScene(new Scene(root)); + primaryStage.setHeight(700); + primaryStage.setWidth(600); + primaryStage.centerOnScreen(); + primaryStage.show(); + + Platform.runLater(new Runnable() { + + @Override + public void run() { + + setImageViewSize(); + } + }); + + } + + + protected void setImageViewSize() { + + double height = webCamPane.getHeight(); + double width = webCamPane.getWidth(); + imgWebCamCapturedImage.setFitHeight(height); + imgWebCamCapturedImage.setFitWidth(width); + imgWebCamCapturedImage.prefHeight(height); + imgWebCamCapturedImage.prefWidth(width); + imgWebCamCapturedImage.setPreserveRatio(true); + + } + + + private void createTopPanel() { + + int webCamCounter = 0; + Label lbInfoLabel = new Label("Select Your WebCam Camera"); + ObservableList options = FXCollections.observableArrayList( + ); + + topPane.getChildren().add(lbInfoLabel); + for (Webcam webcam : Webcam.getWebcams()) { + WebCamInfo webCamInfo = new WebCamInfo(); + webCamInfo.setWebCamIndex(webCamCounter); + webCamInfo.setWebCamName(webcam.getName()); + options.add(webCamInfo); + webCamCounter++; + } + ComboBox cameraOptions = new ComboBox(); + cameraOptions.setItems(options); + cameraOptions.setPromptText(cameraListPromptText); + cameraOptions.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { + + @Override + public void changed(ObservableValue arg0, WebCamInfo arg1, WebCamInfo arg2) { + if (arg2 != null) { + + System.out.println("WebCam Index: " + arg2.getWebCamIndex() + ": WebCam Name:" + arg2.getWebCamName()); + initializeWebCam(arg2.getWebCamIndex()); + } + } + }); + topPane.getChildren().add(cameraOptions); + } + + protected void initializeWebCam(final int webCamIndex) { + + Task webCamTask = new Task() { + + @Override + protected Void call() throws Exception { + + if (webCam != null) { + disposeWebCamCamera(); + webCam = Webcam.getWebcams().get(webCamIndex); + webCam.open(); + } else { + webCam = Webcam.getWebcams().get(webCamIndex); + webCam.open(); + } + + startWebCamStream(); + return null; + } + }; + + Thread webCamThread = new Thread(webCamTask); + webCamThread.setDaemon(true); + webCamThread.start(); + bottomCameraControlPane.setDisable(false); + btnCamreaStart.setDisable(true); + } + + protected void startWebCamStream() { + + stopCamera = false; + Task task = new Task() { + + + @Override + protected Void call() throws Exception { + + while (!stopCamera) { + try { + if ((grabbedImage = webCam.getImage()) != null) { + +// System.out.println("Captured Image height*width:"+grabbedImage.getWidth()+"*"+grabbedImage.getHeight()); + Platform.runLater(new Runnable() { + @Override + public void run() { + final Image mainiamge = SwingFXUtils + .toFXImage(grabbedImage, null); + imageProperty.set(mainiamge); + } + }); + + grabbedImage.flush(); + + } + } catch (Exception e) { + } finally { + + } + + } + + return null; + + } + + }; + Thread th = new Thread(task); + th.setDaemon(true); + th.start(); + imgWebCamCapturedImage.imageProperty().bind(imageProperty); + + } + + private void createCameraControls() { + + btnCamreaStop = new Button(); + btnCamreaStop.setOnAction(new EventHandler() { + + @Override + public void handle(ActionEvent arg0) { + + stopWebCamCamera(); + } + }); + btnCamreaStop.setText("Stop Camera"); + btnCamreaStart = new Button(); + btnCamreaStart.setOnAction(new EventHandler() { + + @Override + public void handle(ActionEvent arg0) { + startWebCamCamera(); + } + }); + btnCamreaStart.setText("Start Camera"); + btnCameraDispose = new Button(); + btnCameraDispose.setText("Dispose Camera"); + btnCameraDispose.setOnAction(new EventHandler() { + + @Override + public void handle(ActionEvent arg0) { + disposeWebCamCamera(); + } + }); + bottomCameraControlPane.getChildren().add(btnCamreaStart); + bottomCameraControlPane.getChildren().add(btnCamreaStop); + bottomCameraControlPane.getChildren().add(btnCameraDispose); + } + + protected void disposeWebCamCamera() { + + stopCamera = true; + webCam.close(); + // Webcam.shutdown(); + btnCamreaStart.setDisable(true); + btnCamreaStop.setDisable(true); + } + + protected void startWebCamCamera() { + + stopCamera = false; + startWebCamStream(); + btnCamreaStop.setDisable(false); + btnCamreaStart.setDisable(true); + } + + protected void stopWebCamCamera() { + + stopCamera = true; + btnCamreaStart.setDisable(false); + btnCamreaStop.setDisable(true); + } + + public static void main(String[] args) { + launch(args); + } + + class WebCamInfo { + private String webCamName; + private int webCamIndex; + + public String getWebCamName() { + return webCamName; + } + + public void setWebCamName(String webCamName) { + this.webCamName = webCamName; + } + + public int getWebCamIndex() { + return webCamIndex; + } + + public void setWebCamIndex(int webCamIndex) { + this.webCamIndex = webCamIndex; + } + + @Override + public String toString() { + return webCamName; + } + + } +} diff --git a/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java b/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java index 092e7708a1b..17777df06f6 100644 --- a/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java +++ b/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java @@ -31,6 +31,7 @@ import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView; import bisq.desktop.main.account.content.backup.BackupView; import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView; +import bisq.desktop.main.account.content.notifications.NotificationsView; import bisq.desktop.main.account.content.password.PasswordView; import bisq.desktop.main.account.content.seedwords.SeedWordsView; import bisq.desktop.util.Colors; @@ -63,7 +64,7 @@ public class AccountSettingsView extends ActivatableViewAndModel { private final Navigation navigation; - private MenuItem paymentAccount, altCoinsAccountView, arbitratorSelection, password, seedWords, backup; + private MenuItem paymentAccount, altCoinsAccountView, arbitratorSelection, notifications, password, seedWords, backup; private Navigation.Listener listener; @FXML @@ -96,11 +97,12 @@ public void initialize() { altCoinsAccountView = new MenuItem(navigation, toggleGroup, Res.get("account.menu.altCoinsAccountView"), AltCoinAccountsView.class, AwesomeIcon.LINK); arbitratorSelection = new MenuItem(navigation, toggleGroup, Res.get("account.menu.arbitratorSelection"), ArbitratorSelectionView.class, AwesomeIcon.USER_MD); + notifications = new MenuItem(navigation, toggleGroup, Res.get("account.menu.notifications"), NotificationsView.class, AwesomeIcon.BELL); password = new MenuItem(navigation, toggleGroup, Res.get("account.menu.password"), PasswordView.class, AwesomeIcon.UNLOCK_ALT); seedWords = new MenuItem(navigation, toggleGroup, Res.get("account.menu.seedWords"), SeedWordsView.class, AwesomeIcon.KEY); backup = new MenuItem(navigation, toggleGroup, Res.get("account.menu.backup"), BackupView.class, AwesomeIcon.CLOUD_DOWNLOAD); - leftVBox.getChildren().addAll(paymentAccount, altCoinsAccountView, arbitratorSelection, password, seedWords, backup); + leftVBox.getChildren().addAll(paymentAccount, altCoinsAccountView, arbitratorSelection, notifications, password, seedWords, backup); } @Override @@ -108,6 +110,7 @@ protected void activate() { paymentAccount.activate(); altCoinsAccountView.activate(); arbitratorSelection.activate(); + notifications.activate(); password.activate(); seedWords.activate(); backup.activate(); @@ -133,6 +136,7 @@ protected void deactivate() { paymentAccount.deactivate(); altCoinsAccountView.deactivate(); arbitratorSelection.deactivate(); + notifications.deactivate(); password.deactivate(); seedWords.deactivate(); backup.deactivate(); @@ -145,6 +149,7 @@ private void loadView(Class viewClass) { if (view instanceof FiatAccountsView) paymentAccount.setSelected(true); else if (view instanceof AltCoinAccountsView) altCoinsAccountView.setSelected(true); else if (view instanceof ArbitratorSelectionView) arbitratorSelection.setSelected(true); + else if (view instanceof NotificationsView) notifications.setSelected(true); else if (view instanceof PasswordView) password.setSelected(true); else if (view instanceof SeedWordsView) seedWords.setSelected(true); else if (view instanceof BackupView) backup.setSelected(true); From 253eb4ca4d97e6f73f9d0c2368ef6515e7a020bc Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 11 Jul 2018 00:20:20 +0200 Subject: [PATCH 02/20] Remove WebCamAppLauncher --- .../notifications/WebCamAppLauncher.java | 325 ------------------ 1 file changed, 325 deletions(-) delete mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java deleted file mode 100644 index e29d14b916b..00000000000 --- a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamAppLauncher.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq 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. - * - * Bisq 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 Bisq. If not, see . - */ - -package bisq.desktop.main.account.content.notifications; - - -import javafx.application.Application; -import javafx.application.Platform; - -import javafx.stage.Stage; - -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.FlowPane; - -import javafx.geometry.Orientation; -import javafx.geometry.Pos; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; - -import javafx.event.ActionEvent; -import javafx.event.EventHandler; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import java.awt.image.BufferedImage; - - - -import com.github.sarxos.webcam.Webcam; -import javafx.concurrent.Task; -import javafx.embed.swing.SwingFXUtils; - -public class WebCamAppLauncher extends Application { - - private FlowPane bottomCameraControlPane; - private FlowPane topPane; - private BorderPane root; - private String cameraListPromptText = "Choose Camera"; - private ImageView imgWebCamCapturedImage; - private Webcam webCam = null; - private boolean stopCamera = false; - private BufferedImage grabbedImage; - ObjectProperty imageProperty = new SimpleObjectProperty(); - private BorderPane webCamPane; - private Button btnCamreaStop; - private Button btnCamreaStart; - private Button btnCameraDispose; - - @Override - public void start(Stage primaryStage) { - - primaryStage.setTitle("Connecting WebCam Using Sarxos API"); - root = new BorderPane(); - topPane = new FlowPane(); - topPane.setAlignment(Pos.CENTER); - topPane.setHgap(20); - topPane.setOrientation(Orientation.HORIZONTAL); - topPane.setPrefHeight(40); - root.setTop(topPane); - webCamPane = new BorderPane(); - webCamPane.setStyle("-fx-background-color: #ccc;"); - imgWebCamCapturedImage = new ImageView(); - webCamPane.setCenter(imgWebCamCapturedImage); - root.setCenter(webCamPane); - createTopPanel(); - bottomCameraControlPane = new FlowPane(); - bottomCameraControlPane.setOrientation(Orientation.HORIZONTAL); - bottomCameraControlPane.setAlignment(Pos.CENTER); - bottomCameraControlPane.setHgap(20); - bottomCameraControlPane.setVgap(10); - bottomCameraControlPane.setPrefHeight(40); - bottomCameraControlPane.setDisable(true); - createCameraControls(); - root.setBottom(bottomCameraControlPane); - - primaryStage.setScene(new Scene(root)); - primaryStage.setHeight(700); - primaryStage.setWidth(600); - primaryStage.centerOnScreen(); - primaryStage.show(); - - Platform.runLater(new Runnable() { - - @Override - public void run() { - - setImageViewSize(); - } - }); - - } - - - protected void setImageViewSize() { - - double height = webCamPane.getHeight(); - double width = webCamPane.getWidth(); - imgWebCamCapturedImage.setFitHeight(height); - imgWebCamCapturedImage.setFitWidth(width); - imgWebCamCapturedImage.prefHeight(height); - imgWebCamCapturedImage.prefWidth(width); - imgWebCamCapturedImage.setPreserveRatio(true); - - } - - - private void createTopPanel() { - - int webCamCounter = 0; - Label lbInfoLabel = new Label("Select Your WebCam Camera"); - ObservableList options = FXCollections.observableArrayList( - ); - - topPane.getChildren().add(lbInfoLabel); - for (Webcam webcam : Webcam.getWebcams()) { - WebCamInfo webCamInfo = new WebCamInfo(); - webCamInfo.setWebCamIndex(webCamCounter); - webCamInfo.setWebCamName(webcam.getName()); - options.add(webCamInfo); - webCamCounter++; - } - ComboBox cameraOptions = new ComboBox(); - cameraOptions.setItems(options); - cameraOptions.setPromptText(cameraListPromptText); - cameraOptions.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { - - @Override - public void changed(ObservableValue arg0, WebCamInfo arg1, WebCamInfo arg2) { - if (arg2 != null) { - - System.out.println("WebCam Index: " + arg2.getWebCamIndex() + ": WebCam Name:" + arg2.getWebCamName()); - initializeWebCam(arg2.getWebCamIndex()); - } - } - }); - topPane.getChildren().add(cameraOptions); - } - - protected void initializeWebCam(final int webCamIndex) { - - Task webCamTask = new Task() { - - @Override - protected Void call() throws Exception { - - if (webCam != null) { - disposeWebCamCamera(); - webCam = Webcam.getWebcams().get(webCamIndex); - webCam.open(); - } else { - webCam = Webcam.getWebcams().get(webCamIndex); - webCam.open(); - } - - startWebCamStream(); - return null; - } - }; - - Thread webCamThread = new Thread(webCamTask); - webCamThread.setDaemon(true); - webCamThread.start(); - bottomCameraControlPane.setDisable(false); - btnCamreaStart.setDisable(true); - } - - protected void startWebCamStream() { - - stopCamera = false; - Task task = new Task() { - - - @Override - protected Void call() throws Exception { - - while (!stopCamera) { - try { - if ((grabbedImage = webCam.getImage()) != null) { - -// System.out.println("Captured Image height*width:"+grabbedImage.getWidth()+"*"+grabbedImage.getHeight()); - Platform.runLater(new Runnable() { - @Override - public void run() { - final Image mainiamge = SwingFXUtils - .toFXImage(grabbedImage, null); - imageProperty.set(mainiamge); - } - }); - - grabbedImage.flush(); - - } - } catch (Exception e) { - } finally { - - } - - } - - return null; - - } - - }; - Thread th = new Thread(task); - th.setDaemon(true); - th.start(); - imgWebCamCapturedImage.imageProperty().bind(imageProperty); - - } - - private void createCameraControls() { - - btnCamreaStop = new Button(); - btnCamreaStop.setOnAction(new EventHandler() { - - @Override - public void handle(ActionEvent arg0) { - - stopWebCamCamera(); - } - }); - btnCamreaStop.setText("Stop Camera"); - btnCamreaStart = new Button(); - btnCamreaStart.setOnAction(new EventHandler() { - - @Override - public void handle(ActionEvent arg0) { - startWebCamCamera(); - } - }); - btnCamreaStart.setText("Start Camera"); - btnCameraDispose = new Button(); - btnCameraDispose.setText("Dispose Camera"); - btnCameraDispose.setOnAction(new EventHandler() { - - @Override - public void handle(ActionEvent arg0) { - disposeWebCamCamera(); - } - }); - bottomCameraControlPane.getChildren().add(btnCamreaStart); - bottomCameraControlPane.getChildren().add(btnCamreaStop); - bottomCameraControlPane.getChildren().add(btnCameraDispose); - } - - protected void disposeWebCamCamera() { - - stopCamera = true; - webCam.close(); - // Webcam.shutdown(); - btnCamreaStart.setDisable(true); - btnCamreaStop.setDisable(true); - } - - protected void startWebCamCamera() { - - stopCamera = false; - startWebCamStream(); - btnCamreaStop.setDisable(false); - btnCamreaStart.setDisable(true); - } - - protected void stopWebCamCamera() { - - stopCamera = true; - btnCamreaStart.setDisable(false); - btnCamreaStop.setDisable(true); - } - - public static void main(String[] args) { - launch(args); - } - - class WebCamInfo { - private String webCamName; - private int webCamIndex; - - public String getWebCamName() { - return webCamName; - } - - public void setWebCamName(String webCamName) { - this.webCamName = webCamName; - } - - public int getWebCamIndex() { - return webCamIndex; - } - - public void setWebCamIndex(int webCamIndex) { - this.webCamIndex = webCamIndex; - } - - @Override - public String toString() { - return webCamName; - } - - } -} From b86c03ecc64455ae4c3ba6a4b88998fa73b4a8ba Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 11 Jul 2018 01:46:28 +0200 Subject: [PATCH 03/20] Add notifications for offers, trades and disputes --- .../notifications/NotificationsView.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 3faa04439bb..b9a344a7965 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -54,7 +54,7 @@ public class NotificationsView extends ActivatableView { private Button webCamButton; private Button wipeOutButton; private Button devButton; - private ChangeListener useSoundCheckBoxListener; + private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -73,8 +73,6 @@ private NotificationsView(Preferences preferences, @Override public void initialize() { - mobileNotificationService.init(); - FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title")); webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"), Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second; webCamButton.setDefaultButton(true); @@ -113,7 +111,11 @@ public void initialize() { tradeCheckBox = FormBuilder.addLabelCheckBox(root, gridRow, Res.get("account.notifications.trade.label"), Res.get("account.notifications.checkbox.title"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; - + tradeCheckBox.setSelected(preferences.isUseTradeNotifications()); + tradeCheckBoxListener = (observable, oldValue, newValue) -> { + mobileNotificationService.getUseTradeNotificationsProperty().set(newValue); + preferences.setUseTradeNotifications(newValue); + }; marketCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.market.label"), Res.get("account.notifications.checkbox.title")).second; @@ -148,12 +150,14 @@ public void initialize() { @Override protected void activate() { tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); + tradeCheckBox.selectedProperty().addListener(tradeCheckBoxListener); useSoundCheckBox.selectedProperty().addListener(useSoundCheckBoxListener); } @Override protected void deactivate() { tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); + tradeCheckBox.selectedProperty().removeListener(tradeCheckBoxListener); useSoundCheckBox.selectedProperty().removeListener(useSoundCheckBoxListener); } @@ -167,8 +171,9 @@ private void setDisableAtControls(boolean disable) { wipeOutButton.setDisable(disable); tradeCheckBox.setDisable(disable); useSoundCheckBox.setDisable(disable); - marketCheckBox.setDisable(disable); - priceCheckBox.setDisable(disable); + // not impl yet. so keep it inactive + marketCheckBox.setDisable(true); + priceCheckBox.setDisable(true); devButton.setDisable(disable); } } From fa208157cc4366051d84302b1d99552a5d890900 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 11 Jul 2018 13:34:53 +0200 Subject: [PATCH 04/20] Fix threading issue --- .../content/notifications/QrCodeWindow.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java index 7c4ccf308f5..2cbbb597b0c 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java @@ -8,8 +8,8 @@ import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; -import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -49,20 +49,23 @@ public void windowClosing(WindowEvent windowEvent) { } }); - List webcams = Webcam.getWebcams(); - webcam = webcams.get(0); - Dimension[] sizes = webcam.getViewSizes(); - webcam.setViewSize(sizes[sizes.length - 1]); - - panel = new WebcamPanel(webcam); - add(panel); - pack(); - setLocationRelativeTo(null); - setVisible(true); executor.execute(this::doRun); } private void doRun() { + try { + webcam = Webcam.getDefault(1000); // one second timeout - the default is too long + Dimension[] sizes = webcam.getViewSizes(); + webcam.setViewSize(sizes[sizes.length - 1]); + panel = new WebcamPanel(webcam); + add(panel); + pack(); + setLocationRelativeTo(null); + setVisible(true); + } catch (TimeoutException e) { + e.printStackTrace(); + } + // check 10 times a second for the QR code boolean run = true; while (run) { @@ -75,7 +78,7 @@ private void doRun() { Result result = null; BufferedImage image; - if (webcam.isOpen()) { + if (webcam != null && webcam.isOpen()) { if ((image = webcam.getImage()) == null) continue; From 5dec138b4fc94250491a53dc40ac32f63ad677aa Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Fri, 13 Jul 2018 04:09:40 +0200 Subject: [PATCH 05/20] Add JavaFXWebcam window --- .../desktop/main/account/AccountView.java | 6 +- .../notifications/NotificationsView.java | 21 +++- .../content/notifications/QrCodeReader.java | 97 +++++++++++++++ .../content/notifications/QrCodeWindow.java | 113 ------------------ .../content/notifications/WebCamLauncher.java | 56 +++++++++ .../account/settings/AccountSettingsView.fxml | 2 +- .../main/overlays/windows/WebCamWindow.java | 75 ++++++++++++ 7 files changed, 251 insertions(+), 119 deletions(-) create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java delete mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java create mode 100644 src/main/java/bisq/desktop/main/overlays/windows/WebCamWindow.java diff --git a/src/main/java/bisq/desktop/main/account/AccountView.java b/src/main/java/bisq/desktop/main/account/AccountView.java index 4b1480eea55..bda746d3fef 100644 --- a/src/main/java/bisq/desktop/main/account/AccountView.java +++ b/src/main/java/bisq/desktop/main/account/AccountView.java @@ -49,7 +49,7 @@ import javafx.event.EventHandler; @FxmlView -public class AccountView extends ActivatableView { +public class AccountView extends ActivatableView { @FXML Tab accountSettingsTab; @@ -67,8 +67,8 @@ public class AccountView extends ActivatableView { private EventHandler keyEventEventHandler; @Inject - private AccountView(AccountViewModel model, CachingViewLoader viewLoader, Navigation navigation) { - super(model); + private AccountView(CachingViewLoader viewLoader, Navigation navigation) { + super(); this.viewLoader = viewLoader; this.navigation = navigation; } diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index b9a344a7965..2536a4bf8ba 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -20,6 +20,7 @@ import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.WebCamWindow; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.Layout; @@ -55,6 +56,8 @@ public class NotificationsView extends ActivatableView { private Button wipeOutButton; private Button devButton; private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener; + private WebCamWindow webCamWindow; + private QrCodeReader qrCodeReader; /////////////////////////////////////////////////////////////////////////////////////////// @@ -74,12 +77,26 @@ private NotificationsView(Preferences preferences, @Override public void initialize() { FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title")); - webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"), Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second; + webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"), + Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second; webCamButton.setDefaultButton(true); webCamButton.setOnAction((event) -> { webCamButton.setDisable(true); - new QrCodeWindow(this::applyKeyAndToken); + new WebCamLauncher(webCam -> { + webCamWindow = new WebCamWindow(webCam.getViewSize().width, webCam.getViewSize().height) + .onClose(() -> { + webCamButton.setDisable(false); + qrCodeReader.close(); + }); + webCamWindow.show(); + + qrCodeReader = new QrCodeReader(webCam, webCamWindow.getImageView(), qrCode -> { + webCamWindow.hide(); + webCamButton.setDisable(false); + tokenInputTextField.setText(qrCode); + }); + }); }); tokenInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")).second; diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java new file mode 100644 index 00000000000..e777fe35ffc --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java @@ -0,0 +1,97 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.notifications; + +import bisq.common.UserThread; + +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; + +import java.awt.image.BufferedImage; + +import java.util.function.Consumer; + +import lombok.extern.slf4j.Slf4j; + + + +import com.github.sarxos.webcam.Webcam; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.NotFoundException; +import com.google.zxing.Result; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.HybridBinarizer; +import javafx.embed.swing.SwingFXUtils; + +@Slf4j +// Must not be UI thread +public class QrCodeReader extends Thread { + private Webcam webCam; + private ImageView imageView; + private Consumer resultHandler; + private boolean isRunning; + + public QrCodeReader(Webcam webCam, ImageView imageView, Consumer resultHandler) { + this.webCam = webCam; + this.imageView = imageView; + this.resultHandler = resultHandler; + + start(); + } + + @Override + public void run() { + try { + if (!webCam.isOpen()) + webCam.open(); + + isRunning = true; + Result result; + BufferedImage bufferedImage; + while (isRunning) { + bufferedImage = webCam.getImage(); + if (bufferedImage != null) { + WritableImage writableImage = SwingFXUtils.toFXImage(bufferedImage, null); + imageView.setImage(writableImage); + + LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + + try { + result = new MultiFormatReader().decode(bitmap); + isRunning = false; + String qrCode = result.getText(); + UserThread.execute(() -> resultHandler.accept(qrCode)); + } catch (NotFoundException ignore) { + // No qr code in image... + } + } + } + } catch (Throwable t) { + log.error(t.toString()); + } finally { + webCam.close(); + } + } + + public void close() { + isRunning = false; + } +} diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java deleted file mode 100644 index 2cbbb597b0c..00000000000 --- a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeWindow.java +++ /dev/null @@ -1,113 +0,0 @@ -package bisq.desktop.main.account.content.notifications; - -import bisq.common.UserThread; -import bisq.common.util.Utilities; - -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; - -import java.util.concurrent.Executor; -import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; - - - -import com.github.sarxos.webcam.Webcam; -import com.github.sarxos.webcam.WebcamPanel; -import com.google.zxing.BinaryBitmap; -import com.google.zxing.LuminanceSource; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.NotFoundException; -import com.google.zxing.Result; -import com.google.zxing.client.j2se.BufferedImageLuminanceSource; -import com.google.zxing.common.HybridBinarizer; -import javax.swing.JFrame; - -public class QrCodeWindow extends JFrame { - - private Webcam webcam; - private Executor executor = Utilities.getListeningSingleThreadExecutor("readQR-runner"); - - private WebcamPanel panel; - private Consumer qrCodeResultHandler; - - public QrCodeWindow(Consumer qrCodeResultHandler) { - this.qrCodeResultHandler = qrCodeResultHandler; - - setLayout(new FlowLayout()); - setTitle("Bisq Notification token"); //TODO - - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - - addWindowListener(new java.awt.event.WindowAdapter() { - @Override - public void windowClosing(WindowEvent windowEvent) { - webcam.close(); - setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - } - }); - - executor.execute(this::doRun); - } - - private void doRun() { - try { - webcam = Webcam.getDefault(1000); // one second timeout - the default is too long - Dimension[] sizes = webcam.getViewSizes(); - webcam.setViewSize(sizes[sizes.length - 1]); - panel = new WebcamPanel(webcam); - add(panel); - pack(); - setLocationRelativeTo(null); - setVisible(true); - } catch (TimeoutException e) { - e.printStackTrace(); - } - - // check 10 times a second for the QR code - boolean run = true; - while (run) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - Result result = null; - BufferedImage image; - - if (webcam != null && webcam.isOpen()) { - if ((image = webcam.getImage()) == null) - continue; - - LuminanceSource source = new BufferedImageLuminanceSource(image); - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - - try { - result = new MultiFormatReader().decode(bitmap); - } catch (NotFoundException ignore) { - // fall thru, it means there is no QR code in image - } - } - - if (result != null) { - String qrCode = result.getText(); - //boolean sendConfirmation = phone.validatePhoneId(qrCode); - // phone.save(); - dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); - run = false; - UserThread.execute(() -> { - qrCodeResultHandler.accept(qrCode); - }); - /* Platform.runLater( - () -> { - // app.updateGUI(); - // app.sendConfirmation(); - } - );*/ - } - } - } -} diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java new file mode 100644 index 00000000000..3c4e456ee45 --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java @@ -0,0 +1,56 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.notifications; + +import bisq.common.UserThread; + +import java.awt.Dimension; + +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +import lombok.extern.slf4j.Slf4j; + + + +import com.github.sarxos.webcam.Webcam; + +@Slf4j +// Must not be UI thread +public class WebCamLauncher extends Thread { + private Consumer resultHandler; + + public WebCamLauncher(Consumer resultHandler) { + this.resultHandler = resultHandler; + + start(); + } + + @Override + public void run() { + try { + Webcam webCam = Webcam.getDefault(1000); // one second timeout - the default is too long + Dimension[] sizes = webCam.getViewSizes(); + Dimension size = sizes[sizes.length - 1]; // the largest size + webCam.setViewSize(size); + UserThread.execute(() -> resultHandler.accept(webCam)); + } catch (TimeoutException e) { + log.error(e.toString()); + } + } +} diff --git a/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.fxml b/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.fxml index 882c23ba4ea..2e96c1f7c1f 100644 --- a/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.fxml +++ b/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.fxml @@ -21,7 +21,7 @@ . + */ + +package bisq.desktop.main.overlays.windows; + + +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.util.FormBuilder; + +import bisq.core.locale.Res; + +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; + +import javafx.geometry.HPos; +import javafx.geometry.Pos; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class WebCamWindow extends Overlay { + + @Getter + private ImageView imageView = new ImageView(); + + + public WebCamWindow(double width, double height) { + type = Type.Feedback; + + imageView.setFitWidth(width); + imageView.setFitHeight(height); + } + + public void show() { + headLine = Res.get("account.notifications.webCamWindow.headline"); + + createGridPane(); + addHeadLine(); + addSeparator(); + addContent(); + addCloseButton(); + applyStyles(); + display(); + + } + + private void addContent() { + GridPane.setHalignment(headLineLabel, HPos.CENTER); + + Label label = FormBuilder.addLabel(gridPane, ++rowIndex, Res.get("account.notifications.waitingForWebCam")); + label.setAlignment(Pos.CENTER); + GridPane.setColumnSpan(label, 2); + GridPane.setHalignment(label, HPos.CENTER); + + GridPane.setRowIndex(imageView, rowIndex); + GridPane.setColumnSpan(imageView, 2); + gridPane.getChildren().add(imageView); + } +} From 0832f3a3dc0c396da1d69a79580bdb07a48db7c3 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sun, 15 Jul 2018 14:01:18 +0200 Subject: [PATCH 06/20] Improve UI, fix productionMode --- src/main/java/bisq/desktop/bisq.css | 11 ++ .../notifications/NotificationsView.java | 144 +++++++++++------- .../main/overlays/windows/WebCamWindow.java | 22 ++- .../java/bisq/desktop/util/FormBuilder.java | 22 ++- 4 files changed, 146 insertions(+), 53 deletions(-) diff --git a/src/main/java/bisq/desktop/bisq.css b/src/main/java/bisq/desktop/bisq.css index ae7a42c387a..2220b443fab 100644 --- a/src/main/java/bisq/desktop/bisq.css +++ b/src/main/java/bisq/desktop/bisq.css @@ -64,6 +64,7 @@ bg color of non edit textFields: fafafa -bs-red: red; /* 5 usages */ -bs-error-red: #dd0000; /* 5 usages */ + -bs-soft-red: #ee6664; /* 1 usages */ -bs-pink: #ff8986; /* 2 usages */ -bs-orange: #ff8a2b; /* 2 usages */ -bs-orange2: #dd6900; /* 1 usages */ @@ -1333,3 +1334,13 @@ textfield */ -fx-background-insets: 0, 0 0 0 0 } +/******************************************************************************************************************** + * * + * Notifications * + * * + ********************************************************************************************************************/ + +#notification-erase-button { + -fx-background-color: -bs-soft-red; + -fx-text-fill: #ffffff; +} diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 2536a4bf8ba..e89e322e9e7 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -19,6 +19,7 @@ import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.WebCamWindow; import bisq.desktop.util.FormBuilder; @@ -31,10 +32,14 @@ import bisq.core.notifications.MobileNotificationValidator; import bisq.core.user.Preferences; +import bisq.common.util.Tuple2; +import bisq.common.util.Tuple3; + import javax.inject.Inject; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; @@ -53,11 +58,13 @@ public class NotificationsView extends ActivatableView { private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; private ChangeListener tokenInputTextFieldListener; private Button webCamButton; - private Button wipeOutButton; - private Button devButton; + private Button eraseButton; + private Button testMsgButton; private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener; private WebCamWindow webCamWindow; private QrCodeReader qrCodeReader; + private Label tokenInputLabel; + private Button noWebCamButton; /////////////////////////////////////////////////////////////////////////////////////////// @@ -77,12 +84,13 @@ private NotificationsView(Preferences preferences, @Override public void initialize() { FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title")); - webCamButton = FormBuilder.addLabelButton(root, gridRow, Res.get("account.notifications.webcam.label"), - Res.get("account.notifications.webcam.title"), Layout.FIRST_ROW_DISTANCE).second; + Tuple3 tuple = FormBuilder.addLabel2Buttons(root, gridRow, Res.get("account.notifications.webcam.label"), + Res.get("account.notifications.webcam.button"), Res.get("account.notifications.noWebcam.button"), Layout.FIRST_ROW_DISTANCE); + webCamButton = tuple.second; webCamButton.setDefaultButton(true); - webCamButton.setOnAction((event) -> { webCamButton.setDisable(true); + new WebCamLauncher(webCam -> { webCamWindow = new WebCamWindow(webCam.getViewSize().width, webCam.getViewSize().height) .onClose(() -> { @@ -94,104 +102,138 @@ public void initialize() { qrCodeReader = new QrCodeReader(webCam, webCamWindow.getImageView(), qrCode -> { webCamWindow.hide(); webCamButton.setDisable(false); + reset(); tokenInputTextField.setText(qrCode); }); }); }); - tokenInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")).second; + noWebCamButton = tuple.third; + noWebCamButton.setOnAction(e -> { + setPairingTokenFieldsVisible(); + + noWebCamButton.setManaged(false); + noWebCamButton.setVisible(false); + }); + + Tuple2 tuple2 = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")); + tokenInputLabel = tuple2.first; + tokenInputTextField = tuple2.second; tokenInputTextField.setPromptText(Res.get("account.notifications.email.prompt")); - if (preferences.getPhoneKeyAndToken() != null) - tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); tokenInputTextFieldListener = (observable, oldValue, newValue) -> { applyKeyAndToken(newValue); }; - - Button resetButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.reset.label"), Res.get("account.notifications.reset.title")).second; - resetButton.setOnAction((event) -> { - mobileNotificationService.reset(); - tokenInputTextField.clear(); - setDisableAtControls(true); + tokenInputLabel.setManaged(false); + tokenInputLabel.setVisible(false); + tokenInputTextField.setManaged(false); + tokenInputTextField.setVisible(false); + + testMsgButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.testMsg.label"), Res.get("account.notifications.testMsg.title")).second; + testMsgButton.setDefaultButton(false); + testMsgButton.setOnAction(event -> { + MobileMessage message = new MobileMessage("Test notification", + "Test message " + new Random().nextInt(1000), + "Test txId", + MobileMessageType.TRADE); + try { + mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); + } catch (Exception e) { + new Popup<>().error(e.toString()).show(); + } }); - wipeOutButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.wipeout.label"), Res.get("account.notifications.wipeout.title")).second; - wipeOutButton.setOnAction((event) -> { + eraseButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.erase.label"), Res.get("account.notifications.erase.title")).second; + eraseButton.setId("notification-erase-button"); + eraseButton.setOnAction((event) -> { try { mobileNotificationService.sendWipeOutMessage(); + reset(); } catch (Exception e) { new Popup<>().error(e.toString()).show(); } }); - FormBuilder.addTitledGroupBg(root, ++gridRow, 5, Res.get("account.notifications.selection.title"), + + FormBuilder.addTitledGroupBg(root, ++gridRow, 2, Res.get("account.notifications.selection.title"), Layout.GROUP_DISTANCE); - tradeCheckBox = FormBuilder.addLabelCheckBox(root, gridRow, Res.get("account.notifications.trade.label"), - Res.get("account.notifications.checkbox.title"), + + useSoundCheckBox = FormBuilder.addLabelCheckBox(root, gridRow, Res.get("account.notifications.useSound.label"), + "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + useSoundCheckBox.setSelected(preferences.isUseSoundForMobileNotifications()); + useSoundCheckBoxListener = (observable, oldValue, newValue) -> { + mobileNotificationService.getUseSoundProperty().set(newValue); + preferences.setUseSoundForMobileNotifications(newValue); + }; + + tradeCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.trade.label")).second; tradeCheckBox.setSelected(preferences.isUseTradeNotifications()); tradeCheckBoxListener = (observable, oldValue, newValue) -> { mobileNotificationService.getUseTradeNotificationsProperty().set(newValue); preferences.setUseTradeNotifications(newValue); }; - marketCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.market.label"), - Res.get("account.notifications.checkbox.title")).second; - - priceCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.price.label"), - Res.get("account.notifications.checkbox.title")).second; - useSoundCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.useSound.label"), - Res.get("account.notifications.useSound.title")).second; - useSoundCheckBox.setSelected(preferences.isUseSoundForMobileNotifications()); - useSoundCheckBoxListener = (observable, oldValue, newValue) -> { - mobileNotificationService.getUseSoundProperty().set(newValue); - preferences.setUseSoundForMobileNotifications(newValue); - }; + /* marketCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.market.label")).second; - //TODO remove later - devButton = FormBuilder.addButton(root, ++gridRow, "Send test msg"); - devButton.setOnAction(event -> { - MobileMessage message = new MobileMessage("test title", - "test msg " + new Random().nextInt(1000), - "test txid", - MobileMessageType.TRADE); - try { - mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); - } catch (Exception e) { - new Popup<>().error(e.toString()).show(); - } - }); + priceCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.price.label")).second;*/ setDisableAtControls(!mobileNotificationService.isSetupConfirmationSent()); + + if (preferences.getPhoneKeyAndToken() != null) { + tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); + setPairingTokenFieldsVisible(); + } else { + eraseButton.setDisable(true); + testMsgButton.setDisable(true); + } } @Override protected void activate() { tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); - tradeCheckBox.selectedProperty().addListener(tradeCheckBoxListener); useSoundCheckBox.selectedProperty().addListener(useSoundCheckBoxListener); + tradeCheckBox.selectedProperty().addListener(tradeCheckBoxListener); } @Override protected void deactivate() { tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); - tradeCheckBox.selectedProperty().removeListener(tradeCheckBoxListener); useSoundCheckBox.selectedProperty().removeListener(useSoundCheckBoxListener); + tradeCheckBox.selectedProperty().removeListener(tradeCheckBoxListener); } private void applyKeyAndToken(String keyAndToken) { mobileNotificationService.applyKeyAndToken(keyAndToken); - if (mobileNotificationValidator.isValid(keyAndToken)) + if (mobileNotificationValidator.isValid(keyAndToken)) { setDisableAtControls(false); + setPairingTokenFieldsVisible(); + } } private void setDisableAtControls(boolean disable) { - wipeOutButton.setDisable(disable); + testMsgButton.setDisable(disable); + eraseButton.setDisable(disable); tradeCheckBox.setDisable(disable); useSoundCheckBox.setDisable(disable); // not impl yet. so keep it inactive - marketCheckBox.setDisable(true); - priceCheckBox.setDisable(true); - devButton.setDisable(disable); + /* marketCheckBox.setDisable(true); + priceCheckBox.setDisable(true);*/ + + } + + private void setPairingTokenFieldsVisible() { + tokenInputLabel.setManaged(true); + tokenInputLabel.setVisible(true); + tokenInputTextField.setManaged(true); + tokenInputTextField.setVisible(true); + } + + private void reset() { + mobileNotificationService.reset(); + tokenInputTextField.clear(); + setDisableAtControls(true); + eraseButton.setDisable(true); + testMsgButton.setDisable(true); } } diff --git a/src/main/java/bisq/desktop/main/overlays/windows/WebCamWindow.java b/src/main/java/bisq/desktop/main/overlays/windows/WebCamWindow.java index 3559cdf5de6..18a22b91671 100644 --- a/src/main/java/bisq/desktop/main/overlays/windows/WebCamWindow.java +++ b/src/main/java/bisq/desktop/main/overlays/windows/WebCamWindow.java @@ -24,12 +24,15 @@ import bisq.core.locale.Res; import javafx.scene.control.Label; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; import javafx.geometry.HPos; import javafx.geometry.Pos; +import javafx.beans.value.ChangeListener; + import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -38,6 +41,7 @@ public class WebCamWindow extends Overlay { @Getter private ImageView imageView = new ImageView(); + private ChangeListener listener; public WebCamWindow(double width, double height) { @@ -57,7 +61,6 @@ public void show() { addCloseButton(); applyStyles(); display(); - } private void addContent() { @@ -72,4 +75,21 @@ private void addContent() { GridPane.setColumnSpan(imageView, 2); gridPane.getChildren().add(imageView); } + + @Override + protected void addCloseButton() { + super.addCloseButton(); + + closeButton.setVisible(false); + listener = (observable, oldValue, newValue) -> closeButton.setVisible(newValue != null); + imageView.imageProperty().addListener(listener); + } + + @Override + public void hide() { + super.hide(); + + if (listener != null) + imageView.imageProperty().removeListener(listener); + } } diff --git a/src/main/java/bisq/desktop/util/FormBuilder.java b/src/main/java/bisq/desktop/util/FormBuilder.java index 7b5d2049ae5..e74514246d6 100644 --- a/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/src/main/java/bisq/desktop/util/FormBuilder.java @@ -947,7 +947,7 @@ public static Tuple2 addLabelBalanceTextField(GridPane /////////////////////////////////////////////////////////////////////////////////////////// - // Label + Button + // Label + Button /////////////////////////////////////////////////////////////////////////////////////////// public static Tuple2 addLabelButton(GridPane gridPane, int rowIndex, String labelText, String buttonTitle) { @@ -966,6 +966,26 @@ public static Tuple2 addLabelButton(GridPane gridPane, int rowInd return new Tuple2<>(label, button); } + /////////////////////////////////////////////////////////////////////////////////////////// + // Label + Button + Button + /////////////////////////////////////////////////////////////////////////////////////////// + + public static Tuple3 addLabel2Buttons(GridPane gridPane, int rowIndex, String labelText, String title1, String title2, double top) { + Label label = addLabel(gridPane, rowIndex, labelText, top); + HBox hBox = new HBox(); + hBox.setSpacing(10); + Button button1 = new AutoTooltipButton(title1); + button1.setDefaultButton(true); + Button button2 = new AutoTooltipButton(title2); + hBox.getChildren().addAll(button1, button2); + GridPane.setRowIndex(hBox, rowIndex); + GridPane.setColumnIndex(hBox, 1); + GridPane.setMargin(hBox, new Insets(top, 10, 0, 0)); + gridPane.getChildren().add(hBox); + return new Tuple3<>(label, button1, button2); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Button /////////////////////////////////////////////////////////////////////////////////////////// From 751567d9990d4db61fcff34f7f0f94c9fc02467e Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sun, 15 Jul 2018 23:13:59 +0200 Subject: [PATCH 07/20] Add price alerts --- .../notifications/NotificationsView.java | 335 +++++++++++++++--- 1 file changed, 283 insertions(+), 52 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index e89e322e9e7..71adc512fe5 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -24,13 +24,23 @@ import bisq.desktop.main.overlays.windows.WebCamWindow; import bisq.desktop.util.FormBuilder; import bisq.desktop.util.Layout; +import bisq.desktop.util.validation.AltcoinValidator; +import bisq.desktop.util.validation.FiatPriceValidator; +import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; +import bisq.core.locale.TradeCurrency; +import bisq.core.monetary.Price; import bisq.core.notifications.MobileMessage; import bisq.core.notifications.MobileMessageType; import bisq.core.notifications.MobileNotificationService; import bisq.core.notifications.MobileNotificationValidator; +import bisq.core.notifications.alerts.price.PriceAlertFilter; +import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; +import bisq.core.user.User; +import bisq.core.util.BSFormatter; +import bisq.core.util.validation.InputValidator; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; @@ -39,32 +49,46 @@ import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.beans.value.ChangeListener; +import javafx.util.StringConverter; + +import java.util.Optional; import java.util.Random; @FxmlView public class NotificationsView extends ActivatableView { private final Preferences preferences; + private User user; + private PriceFeedService priceFeedService; private final MobileNotificationValidator mobileNotificationValidator; private final MobileNotificationService mobileNotificationService; + private BSFormatter formatter; - private int gridRow = 0; - private TextField tokenInputTextField; - private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; - private ChangeListener tokenInputTextFieldListener; - private Button webCamButton; - private Button eraseButton; - private Button testMsgButton; - private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener; private WebCamWindow webCamWindow; private QrCodeReader qrCodeReader; + + private TextField tokenInputTextField; private Label tokenInputLabel; - private Button noWebCamButton; + private InputTextField priceAlertHigh, priceAlertLow; + private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; + private ComboBox currencyComboBox; + private Button webCamButton, noWebCamButton, eraseButton, testMsgButton, setPriceAlertButton, + removePriceAlertButton; + + private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener, marketCheckBoxListener, + priceCheckBoxListener; + private ChangeListener tokenInputTextFieldListener, priceAlertHighListener, priceAlertLowListener; + private ChangeListener priceFeedServiceListener; + + private TradeCurrency selectedPriceAlertTradeCurrency; + private int gridRow = 0; + private int testMsgButtonClicked = 1; /////////////////////////////////////////////////////////////////////////////////////////// @@ -73,19 +97,95 @@ public class NotificationsView extends ActivatableView { @Inject private NotificationsView(Preferences preferences, + User user, + PriceFeedService priceFeedService, MobileNotificationValidator mobileNotificationValidator, - MobileNotificationService mobileNotificationService) { + MobileNotificationService mobileNotificationService, + BSFormatter formatter) { super(); this.preferences = preferences; + this.user = user; + this.priceFeedService = priceFeedService; this.mobileNotificationValidator = mobileNotificationValidator; this.mobileNotificationService = mobileNotificationService; + this.formatter = formatter; } @Override public void initialize() { + createSetupFields(); + createSettingsFields(); + createPriceAlertFields(); + } + + @Override + protected void activate() { + tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); + + useSoundCheckBox.selectedProperty().addListener(useSoundCheckBoxListener); + tradeCheckBox.selectedProperty().addListener(tradeCheckBoxListener); + marketCheckBox.selectedProperty().addListener(marketCheckBoxListener); + priceCheckBox.selectedProperty().addListener(priceCheckBoxListener); + + if (preferences.getPhoneKeyAndToken() != null) { + tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); + setPairingTokenFieldsVisible(); + } else { + eraseButton.setDisable(true); + testMsgButton.setDisable(true); + } + updateDisableState(!mobileNotificationService.isSetupConfirmationSent()); + + // priceAlert + priceAlertHigh.textProperty().addListener(priceAlertHighListener); + priceAlertLow.textProperty().addListener(priceAlertLowListener); + currencyComboBox.setOnAction(e -> setSelectedPriceAlertTradeCurrency()); + setPriceAlertButton.setOnAction(e -> { + if (arePriceAlertInputsValid()) { + String code = selectedPriceAlertTradeCurrency.getCode(); + long high = getPriceAlertHighTextFieldValue(); + long low = getPriceAlertLowTextFieldValue(); + user.setPriceAlertFilter(new PriceAlertFilter(code, high, low)); + updatePriceAlertInputs(); + } + }); + removePriceAlertButton.setOnAction(e -> removeAlert()); + + priceFeedService.updateCounterProperty().addListener(priceFeedServiceListener); + + currencyComboBox.setItems(preferences.getTradeCurrenciesAsObservable()); + fillPriceAlertFields(); + updatePriceAlertInputs(); + } + + @Override + protected void deactivate() { + tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); + + useSoundCheckBox.selectedProperty().removeListener(useSoundCheckBoxListener); + tradeCheckBox.selectedProperty().removeListener(tradeCheckBoxListener); + marketCheckBox.selectedProperty().removeListener(marketCheckBoxListener); + priceCheckBox.selectedProperty().removeListener(priceCheckBoxListener); + + priceAlertHigh.textProperty().removeListener(priceAlertHighListener); + priceAlertLow.textProperty().removeListener(priceAlertLowListener); + priceFeedService.updateCounterProperty().removeListener(priceFeedServiceListener); + currencyComboBox.setOnAction(null); + setPriceAlertButton.setOnAction(null); + removePriceAlertButton.setOnAction(null); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Create views + /////////////////////////////////////////////////////////////////////////////////////////// + + private void createSetupFields() { FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title")); - Tuple3 tuple = FormBuilder.addLabel2Buttons(root, gridRow, Res.get("account.notifications.webcam.label"), - Res.get("account.notifications.webcam.button"), Res.get("account.notifications.noWebcam.button"), Layout.FIRST_ROW_DISTANCE); + Tuple3 tuple = FormBuilder.addLabel2Buttons(root, gridRow, + Res.get("account.notifications.webcam.label"), + Res.get("account.notifications.webcam.button"), Res.get("account.notifications.noWebcam.button"), + Layout.FIRST_ROW_DISTANCE); webCamButton = tuple.second; webCamButton.setDefaultButton(true); webCamButton.setOnAction((event) -> { @@ -104,6 +204,7 @@ public void initialize() { webCamButton.setDisable(false); reset(); tokenInputTextField.setText(qrCode); + updatePriceAlertInputs(); }); }); }); @@ -111,12 +212,12 @@ public void initialize() { noWebCamButton = tuple.third; noWebCamButton.setOnAction(e -> { setPairingTokenFieldsVisible(); - noWebCamButton.setManaged(false); noWebCamButton.setVisible(false); }); - Tuple2 tuple2 = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")); + Tuple2 tuple2 = FormBuilder.addLabelInputTextField(root, ++gridRow, + Res.get("account.notifications.email.label")); tokenInputLabel = tuple2.first; tokenInputTextField = tuple2.second; tokenInputTextField.setPromptText(Res.get("account.notifications.email.prompt")); @@ -128,13 +229,19 @@ public void initialize() { tokenInputTextField.setManaged(false); tokenInputTextField.setVisible(false); - testMsgButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.testMsg.label"), Res.get("account.notifications.testMsg.title")).second; + testMsgButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.testMsg.label"), + Res.get("account.notifications.testMsg.title")).second; testMsgButton.setDefaultButton(false); testMsgButton.setOnAction(event -> { + MobileMessageType mobileMessageType = MobileMessageType.values()[testMsgButtonClicked]; MobileMessage message = new MobileMessage("Test notification", - "Test message " + new Random().nextInt(1000), + "Test " + mobileMessageType.name() + " message - " + new Random().nextInt(1000), "Test txId", - MobileMessageType.TRADE); + mobileMessageType); + testMsgButtonClicked++; + // we don't want to send the ERASE and SETUP_CONFIRMATION msg + if (testMsgButtonClicked >= MobileMessageType.values().length - 1) + testMsgButtonClicked = 1; try { mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); } catch (Exception e) { @@ -142,22 +249,27 @@ public void initialize() { } }); - eraseButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.erase.label"), Res.get("account.notifications.erase.title")).second; + eraseButton = FormBuilder.addLabelButton(root, ++gridRow, + Res.get("account.notifications.erase.label"), + Res.get("account.notifications.erase.title")).second; eraseButton.setId("notification-erase-button"); eraseButton.setOnAction((event) -> { try { - mobileNotificationService.sendWipeOutMessage(); + mobileNotificationService.sendEraseMessage(); reset(); } catch (Exception e) { new Popup<>().error(e.toString()).show(); } }); + } - - FormBuilder.addTitledGroupBg(root, ++gridRow, 2, Res.get("account.notifications.selection.title"), + private void createSettingsFields() { + FormBuilder.addTitledGroupBg(root, ++gridRow, 4, + Res.get("account.notifications.settings.title"), Layout.GROUP_DISTANCE); - useSoundCheckBox = FormBuilder.addLabelCheckBox(root, gridRow, Res.get("account.notifications.useSound.label"), + useSoundCheckBox = FormBuilder.addLabelCheckBox(root, gridRow, + Res.get("account.notifications.useSound.label"), "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; useSoundCheckBox.setSelected(preferences.isUseSoundForMobileNotifications()); @@ -166,58 +278,89 @@ public void initialize() { preferences.setUseSoundForMobileNotifications(newValue); }; - tradeCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.trade.label")).second; + tradeCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, + Res.get("account.notifications.trade.label")).second; tradeCheckBox.setSelected(preferences.isUseTradeNotifications()); tradeCheckBoxListener = (observable, oldValue, newValue) -> { mobileNotificationService.getUseTradeNotificationsProperty().set(newValue); preferences.setUseTradeNotifications(newValue); }; - /* marketCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.market.label")).second; + marketCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, + Res.get("account.notifications.market.label")).second; + marketCheckBox.setSelected(preferences.isUseMarketNotifications()); + marketCheckBoxListener = (observable, oldValue, newValue) -> { + mobileNotificationService.getUseMarketNotificationsProperty().set(newValue); + preferences.setUseMarketNotifications(newValue); + }; + priceCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, + Res.get("account.notifications.price.label")).second; + priceCheckBox.setSelected(preferences.isUsePriceNotifications()); + priceCheckBoxListener = (observable, oldValue, newValue) -> { + mobileNotificationService.getUsePriceNotificationsProperty().set(newValue); + preferences.setUsePriceNotifications(newValue); + updatePriceAlertInputs(); + }; + } + + private void createPriceAlertFields() { + FormBuilder.addTitledGroupBg(root, ++gridRow, 3, Res.get("account.notifications.priceAlert.title"), + Layout.GROUP_DISTANCE); + currencyComboBox = FormBuilder.addLabelComboBox(root, gridRow, Res.get("list.currency.select"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + currencyComboBox.setPromptText(Res.get("list.currency.select")); + currencyComboBox.setConverter(new StringConverter() { + @Override + public String toString(TradeCurrency currency) { + return currency.getNameAndCode(); + } + + @Override + public TradeCurrency fromString(String string) { + return null; + } + }); - priceCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.price.label")).second;*/ + priceAlertHigh = FormBuilder.addLabelInputTextField(root, ++gridRow, + Res.get("account.notifications.priceAlert.high.label")).second; + priceAlertHighListener = (observable, oldValue, newValue) -> updatePriceAlertInputs(); - setDisableAtControls(!mobileNotificationService.isSetupConfirmationSent()); + priceAlertLow = FormBuilder.addLabelInputTextField(root, ++gridRow, + Res.get("account.notifications.priceAlert.low.label")).second; + priceAlertLowListener = (observable, oldValue, newValue) -> updatePriceAlertInputs(); - if (preferences.getPhoneKeyAndToken() != null) { - tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); - setPairingTokenFieldsVisible(); - } else { - eraseButton.setDisable(true); - testMsgButton.setDisable(true); - } - } + Tuple2 tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow, + Res.get("account.notifications.priceAlert.setButton"), + Res.get("account.notifications.priceAlert.clearButton")); + setPriceAlertButton = tuple.first; + removePriceAlertButton = tuple.second; - @Override - protected void activate() { - tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); - useSoundCheckBox.selectedProperty().addListener(useSoundCheckBoxListener); - tradeCheckBox.selectedProperty().addListener(tradeCheckBoxListener); + priceFeedServiceListener = (observable, oldValue, newValue) -> { + fillPriceAlertFields(); + updatePriceAlertInputs(); + }; } - @Override - protected void deactivate() { - tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); - useSoundCheckBox.selectedProperty().removeListener(useSoundCheckBoxListener); - tradeCheckBox.selectedProperty().removeListener(tradeCheckBoxListener); - } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Setup/Settings + /////////////////////////////////////////////////////////////////////////////////////////// private void applyKeyAndToken(String keyAndToken) { mobileNotificationService.applyKeyAndToken(keyAndToken); if (mobileNotificationValidator.isValid(keyAndToken)) { - setDisableAtControls(false); + updateDisableState(false); setPairingTokenFieldsVisible(); } } - private void setDisableAtControls(boolean disable) { + private void updateDisableState(boolean disable) { testMsgButton.setDisable(disable); eraseButton.setDisable(disable); - tradeCheckBox.setDisable(disable); + useSoundCheckBox.setDisable(disable); - // not impl yet. so keep it inactive - /* marketCheckBox.setDisable(true); - priceCheckBox.setDisable(true);*/ + tradeCheckBox.setDisable(disable); + marketCheckBox.setDisable(disable); + priceCheckBox.setDisable(disable); } @@ -231,9 +374,97 @@ private void setPairingTokenFieldsVisible() { private void reset() { mobileNotificationService.reset(); tokenInputTextField.clear(); - setDisableAtControls(true); + updateDisableState(true); eraseButton.setDisable(true); testMsgButton.setDisable(true); + removeAlert(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PriceAlert + /////////////////////////////////////////////////////////////////////////////////////////// + + private void fillPriceAlertFields() { + PriceAlertFilter priceAlertFilter = user.getPriceAlertFilter(); + if (priceAlertFilter != null) { + String currencyCode = priceAlertFilter.getCurrencyCode(); + Optional optionalTradeCurrency = CurrencyUtil.getTradeCurrency(currencyCode); + if (optionalTradeCurrency.isPresent()) { + currencyComboBox.getSelectionModel().select(optionalTradeCurrency.get()); + setSelectedPriceAlertTradeCurrency(); + + priceAlertHigh.setText(formatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode)); + priceAlertLow.setText(formatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode)); + } else { + currencyComboBox.getSelectionModel().clearSelection(); + } + } else { + priceAlertHigh.clear(); + priceAlertLow.clear(); + currencyComboBox.getSelectionModel().clearSelection(); + } } + + private void updatePriceAlertInputs() { + boolean setupConfirmationSent = mobileNotificationService.isSetupConfirmationSent(); + boolean selected = priceCheckBox.isSelected(); + boolean disable = !setupConfirmationSent || + !selected; + priceAlertHigh.setDisable(selectedPriceAlertTradeCurrency == null || disable); + priceAlertLow.setDisable(selectedPriceAlertTradeCurrency == null || disable); + PriceAlertFilter priceAlertFilter = user.getPriceAlertFilter(); + boolean valueSameAsFilter = false; + if (priceAlertFilter != null && + selectedPriceAlertTradeCurrency != null) { + valueSameAsFilter = priceAlertFilter.getHigh() == getPriceAlertHighTextFieldValue() && + priceAlertFilter.getLow() == getPriceAlertLowTextFieldValue() && + priceAlertFilter.getCurrencyCode().equals(selectedPriceAlertTradeCurrency.getCode()); + } + setPriceAlertButton.setDisable(disable || !arePriceAlertInputsValid() || valueSameAsFilter); + removePriceAlertButton.setDisable(disable || priceAlertFilter == null); + currencyComboBox.setDisable(disable); + } + + private void removeAlert() { + user.removePriceAlertFilter(); + fillPriceAlertFields(); + updatePriceAlertInputs(); + } + + private void setSelectedPriceAlertTradeCurrency() { + selectedPriceAlertTradeCurrency = currencyComboBox.getSelectionModel().getSelectedItem(); + if (selectedPriceAlertTradeCurrency != null) { + boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(selectedPriceAlertTradeCurrency.getCode()); + priceAlertHigh.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); + priceAlertLow.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); + } + updatePriceAlertInputs(); + } + + private boolean arePriceAlertInputsValid() { + return selectedPriceAlertTradeCurrency != null && + isPriceInputValid(priceAlertHigh).isValid && + isPriceInputValid(priceAlertLow).isValid; + } + + private InputValidator.ValidationResult isPriceInputValid(InputTextField inputTextField) { + InputValidator validator = inputTextField.getValidator(); + if (validator != null) + return validator.validate(inputTextField.getText()); + else + return new InputValidator.ValidationResult(false); + } + + private long getPriceAlertHighTextFieldValue() { + String text = priceAlertHigh.getText(); + return text.isEmpty() ? 0 : Price.parse(selectedPriceAlertTradeCurrency.getCode(), text).getValue(); + } + + private long getPriceAlertLowTextFieldValue() { + String text = priceAlertLow.getText(); + return text.isEmpty() ? 0 : Price.parse(selectedPriceAlertTradeCurrency.getCode(), text).getValue(); + } + } From a957145e09c0166d7dee2b76fa8e9e737f248ecc Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 16 Jul 2018 00:17:48 +0200 Subject: [PATCH 08/20] Add testMsg for all types --- .../notifications/NotificationsView.java | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 71adc512fe5..9a864c18fc6 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -32,9 +32,13 @@ import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.notifications.MobileMessage; -import bisq.core.notifications.MobileMessageType; import bisq.core.notifications.MobileNotificationService; import bisq.core.notifications.MobileNotificationValidator; +import bisq.core.notifications.alerts.DisputeMsgEvents; +import bisq.core.notifications.alerts.MyOfferTakenEvents; +import bisq.core.notifications.alerts.TradeEvents; +import bisq.core.notifications.alerts.market.MarketAlerts; +import bisq.core.notifications.alerts.price.PriceAlert; import bisq.core.notifications.alerts.price.PriceAlertFilter; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; @@ -58,17 +62,17 @@ import javafx.util.StringConverter; +import java.util.List; import java.util.Optional; -import java.util.Random; @FxmlView public class NotificationsView extends ActivatableView { private final Preferences preferences; - private User user; - private PriceFeedService priceFeedService; + private final User user; + private final PriceFeedService priceFeedService; private final MobileNotificationValidator mobileNotificationValidator; private final MobileNotificationService mobileNotificationService; - private BSFormatter formatter; + private final BSFormatter formatter; private WebCamWindow webCamWindow; private QrCodeReader qrCodeReader; @@ -88,7 +92,7 @@ public class NotificationsView extends ActivatableView { private TradeCurrency selectedPriceAlertTradeCurrency; private int gridRow = 0; - private int testMsgButtonClicked = 1; + private int testMsgCounter = 0; /////////////////////////////////////////////////////////////////////////////////////////// @@ -233,17 +237,42 @@ private void createSetupFields() { Res.get("account.notifications.testMsg.title")).second; testMsgButton.setDefaultButton(false); testMsgButton.setOnAction(event -> { - MobileMessageType mobileMessageType = MobileMessageType.values()[testMsgButtonClicked]; - MobileMessage message = new MobileMessage("Test notification", - "Test " + mobileMessageType.name() + " message - " + new Random().nextInt(1000), - "Test txId", - mobileMessageType); - testMsgButtonClicked++; - // we don't want to send the ERASE and SETUP_CONFIRMATION msg - if (testMsgButtonClicked >= MobileMessageType.values().length - 1) - testMsgButtonClicked = 1; + MobileMessage message = null; + List messages = null; + switch (testMsgCounter) { + case 0: + message = MyOfferTakenEvents.getTestMsg(); + break; + case 1: + messages = TradeEvents.getTestMsgs(); + break; + case 2: + message = DisputeMsgEvents.getTestMsg(); + break; + case 3: + message = PriceAlert.getTestMsg(); + break; + case 4: + default: + message = MarketAlerts.getTestMsg(); + break; + } + testMsgCounter++; + if (testMsgCounter > 4) + testMsgCounter = 0; + try { - mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); + if (message != null) { + mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); + } else if (messages != null) { + messages.forEach(msg -> { + try { + mobileNotificationService.sendMessage(msg, useSoundCheckBox.isSelected()); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } } catch (Exception e) { new Popup<>().error(e.toString()).show(); } From 4c2366b598153a6f83a5221773bb8dcb471b4d59 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 16 Jul 2018 14:11:36 +0200 Subject: [PATCH 09/20] Add descriptor --- .../notifications/NotificationsView.java | 8 ++++++ .../java/bisq/desktop/AwesomeFontDemo.java | 25 ++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 9a864c18fc6..9655d6073ef 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -46,6 +46,7 @@ import bisq.core.util.BSFormatter; import bisq.core.util.validation.InputValidator; +import bisq.common.UserThread; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; @@ -64,6 +65,7 @@ import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; @FxmlView public class NotificationsView extends ActivatableView { @@ -209,6 +211,12 @@ private void createSetupFields() { reset(); tokenInputTextField.setText(qrCode); updatePriceAlertInputs(); + + if (!mobileNotificationService.getMobileModel().isContentAvailable()) + UserThread.runAfter(() -> new Popup<>() + .warning(Res.get("account.notifications.isContentAvailable.warning", + mobileNotificationService.getMobileModel().getDescriptor())) + .show(), 300, TimeUnit.MILLISECONDS); }); }); }); diff --git a/src/test/java/bisq/desktop/AwesomeFontDemo.java b/src/test/java/bisq/desktop/AwesomeFontDemo.java index 30cbf688e14..2f5ef64810b 100644 --- a/src/test/java/bisq/desktop/AwesomeFontDemo.java +++ b/src/test/java/bisq/desktop/AwesomeFontDemo.java @@ -17,9 +17,6 @@ package bisq.desktop; -import bisq.desktop.components.AutoTooltipButton; -import bisq.desktop.components.AutoTooltipLabel; - import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; @@ -31,35 +28,33 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; -import javafx.scene.layout.Pane; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class AwesomeFontDemo extends Application { - private static final Logger log = LoggerFactory.getLogger(AwesomeFontDemo.class); - public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { - Pane root = new FlowPane(); + FlowPane flowPane = new FlowPane(); + flowPane.setStyle("-fx-background-color: #ddd;"); + flowPane.setHgap(2); + flowPane.setVgap(2); List values = new ArrayList<>(Arrays.asList(AwesomeIcon.values())); values.sort((o1, o2) -> o1.name().compareTo(o2.name())); for (AwesomeIcon icon : values) { - Label label = new AutoTooltipLabel(); - Button button = new AutoTooltipButton(icon.name(), label); - AwesomeDude.setIcon(label, icon); - root.getChildren().add(button); + Label label = new Label(); + Button button = new Button(icon.name(), label); + button.setStyle("-fx-background-color: #fff;"); + AwesomeDude.setIcon(label, icon, "12"); + flowPane.getChildren().add(button); } - primaryStage.setScene(new Scene(root, 1200, 950)); + primaryStage.setScene(new Scene(flowPane, 1200, 950)); primaryStage.show(); } } From d2e8c0cf09baf507de7b260a7d93a73a34f62642 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 16 Jul 2018 23:20:12 +0200 Subject: [PATCH 10/20] Add tests --- .../content/notifications/NotificationsView.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 9655d6073ef..fcd918f866f 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -212,11 +212,13 @@ private void createSetupFields() { tokenInputTextField.setText(qrCode); updatePriceAlertInputs(); - if (!mobileNotificationService.getMobileModel().isContentAvailable()) - UserThread.runAfter(() -> new Popup<>() - .warning(Res.get("account.notifications.isContentAvailable.warning", - mobileNotificationService.getMobileModel().getDescriptor())) - .show(), 300, TimeUnit.MILLISECONDS); + UserThread.runAfter(() -> { + if (!mobileNotificationService.getMobileModel().isContentAvailable()) + new Popup<>() + .warning(Res.get("account.notifications.isContentAvailable.warning", + mobileNotificationService.getMobileModel().getDescriptor())) + .show(); + }, 600, TimeUnit.MILLISECONDS); }); }); }); From 24915aff599360c2cd1017e9b00b80976248c23a Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 16 Jul 2018 23:42:23 +0200 Subject: [PATCH 11/20] Add check for android --- .../account/content/notifications/NotificationsView.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index fcd918f866f..2ebd272dc71 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -32,6 +32,7 @@ import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.notifications.MobileMessage; +import bisq.core.notifications.MobileModel; import bisq.core.notifications.MobileNotificationService; import bisq.core.notifications.MobileNotificationValidator; import bisq.core.notifications.alerts.DisputeMsgEvents; @@ -212,13 +213,15 @@ private void createSetupFields() { tokenInputTextField.setText(qrCode); updatePriceAlertInputs(); - UserThread.runAfter(() -> { - if (!mobileNotificationService.getMobileModel().isContentAvailable()) + if (mobileNotificationService.getMobileModel().getOs() != MobileModel.OS.ANDROID && + !mobileNotificationService.getMobileModel().isContentAvailable()) { + UserThread.runAfter(() -> { new Popup<>() .warning(Res.get("account.notifications.isContentAvailable.warning", mobileNotificationService.getMobileModel().getDescriptor())) .show(); - }, 600, TimeUnit.MILLISECONDS); + }, 600, TimeUnit.MILLISECONDS); + } }); }); }); From 4ce22ece97ff2b2466df8ef9f638b01bf27d6dda Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 6 Aug 2018 01:14:28 +0200 Subject: [PATCH 12/20] Cleanups --- .../account/content/notifications/NotificationsView.java | 4 ++-- .../main/account/content/notifications/QrCodeReader.java | 8 ++++---- .../account/content/notifications/WebCamLauncher.java | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 2ebd272dc71..fa75c2591c9 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -257,7 +257,7 @@ private void createSetupFields() { message = MyOfferTakenEvents.getTestMsg(); break; case 1: - messages = TradeEvents.getTestMsgs(); + messages = TradeEvents.getTestMessages(); break; case 2: message = DisputeMsgEvents.getTestMsg(); @@ -348,7 +348,7 @@ private void createSettingsFields() { private void createPriceAlertFields() { FormBuilder.addTitledGroupBg(root, ++gridRow, 3, Res.get("account.notifications.priceAlert.title"), Layout.GROUP_DISTANCE); - currencyComboBox = FormBuilder.addLabelComboBox(root, gridRow, Res.get("list.currency.select"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + currencyComboBox = FormBuilder.addLabelComboBox(root, gridRow, Res.get("list.currency.select"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; currencyComboBox.setPromptText(Res.get("list.currency.select")); currencyComboBox.setConverter(new StringConverter() { @Override diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java index e777fe35ffc..20fc7bb1e3f 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java @@ -42,10 +42,10 @@ @Slf4j // Must not be UI thread -public class QrCodeReader extends Thread { - private Webcam webCam; - private ImageView imageView; - private Consumer resultHandler; +class QrCodeReader extends Thread { + private final Webcam webCam; + private final ImageView imageView; + private final Consumer resultHandler; private boolean isRunning; public QrCodeReader(Webcam webCam, ImageView imageView, Consumer resultHandler) { diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java index 3c4e456ee45..f742d34a9b1 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java @@ -32,8 +32,8 @@ @Slf4j // Must not be UI thread -public class WebCamLauncher extends Thread { - private Consumer resultHandler; +class WebCamLauncher extends Thread { + private final Consumer resultHandler; public WebCamLauncher(Consumer resultHandler) { this.resultHandler = resultHandler; From 4a0c8b6dc1442d6278408b0423e64d63e8c5eeb2 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 6 Aug 2018 02:11:58 +0200 Subject: [PATCH 13/20] Prepare for custom binary: - Set version 0.8.0 - Use Bisq_notifications as app name - Improve Relay - Fix build issues --- build.gradle | 14 ++++++++------ package/linux/32bitBuild.sh | 2 +- package/linux/64bitBuild.sh | 2 +- package/linux/Dockerfile | 2 +- package/linux/rpm.sh | 2 +- package/osx/Info.plist | 2 +- package/osx/create_app.sh | 10 +++++----- package/osx/finalize.sh | 2 +- package/windows/32bitBuild.bat | 2 +- package/windows/64bitBuild.bat | 2 +- package/windows/Bisq.iss | 2 +- 11 files changed, 22 insertions(+), 20 deletions(-) diff --git a/build.gradle b/build.gradle index 561f81ac432..a5d2218ab0c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ buildscript { repositories { + mavenLocal() jcenter() } dependencies { @@ -15,7 +16,7 @@ apply plugin: 'witness' apply plugin: 'com.github.johnrengelman.shadow' group = 'network.bisq' -version = '0.7.1-SNAPSHOT' +version = '0.8.0' sourceCompatibility = 1.8 @@ -33,6 +34,7 @@ tasks.withType(JavaCompile) { sourceSets.main.resources.srcDirs += ['src/main/java'] // to copy fxml and css files repositories { + mavenLocal() jcenter() maven { url 'https://jitpack.io' } maven { url 'https://raw.githubusercontent.com/JesusMcCloud/tor-binary/master/release/' } @@ -40,9 +42,9 @@ repositories { } dependencies { - compile 'network.bisq:bisq-p2p:-SNAPSHOT' - compile 'network.bisq:bisq-core:-SNAPSHOT' - compile 'network.bisq:bisq-common:-SNAPSHOT' + compile 'network.bisq:bisq-p2p:0.8.0' + compile 'network.bisq:bisq-core:0.8.0' + compile 'network.bisq:bisq-common:0.8.0' compile 'org.controlsfx:controlsfx:8.0.6_20' compile 'org.reactfx:reactfx:2.0-M3' compile 'net.glxn:qrgen:1.3' @@ -54,7 +56,7 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.16.16' - annotationProcessor 'org.projectlombok:lombok:1.16.16' + //annotationProcessor 'org.projectlombok:lombok:1.16.16' testCompile('org.mockito:mockito-core:2.8.9') { exclude(module: 'objenesis') } @@ -64,7 +66,7 @@ dependencies { testCompile 'org.springframework:spring-test:4.3.6.RELEASE' testCompile 'com.natpryce:make-it-easy:4.0.1' testCompileOnly 'org.projectlombok:lombok:1.16.16' - testAnnotationProcessor 'org.projectlombok:lombok:1.16.16' + //testAnnotationProcessor 'org.projectlombok:lombok:1.16.16' } build.dependsOn installDist diff --git a/package/linux/32bitBuild.sh b/package/linux/32bitBuild.sh index 5ccbd8b53f4..bb8a3dcf88c 100644 --- a/package/linux/32bitBuild.sh +++ b/package/linux/32bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e # Edit version -version=0.7.1 +version=0.8.0 dir="/media/sf_vm_shared_ubuntu14_32bit" diff --git a/package/linux/64bitBuild.sh b/package/linux/64bitBuild.sh index 43b260ddcd6..751115c24dd 100644 --- a/package/linux/64bitBuild.sh +++ b/package/linux/64bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e # Edit version -version=0.7.1 +version=0.8.0 dir="/media/sf_vm_shared_ubuntu" diff --git a/package/linux/Dockerfile b/package/linux/Dockerfile index cf80e3c12c9..0d79a422dbd 100644 --- a/package/linux/Dockerfile +++ b/package/linux/Dockerfile @@ -8,7 +8,7 @@ # pull base image FROM openjdk:8-jdk -ENV version 0.7.1 +ENV version 0.8.0 RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* && apt-get install -y vim fakeroot diff --git a/package/linux/rpm.sh b/package/linux/rpm.sh index 4260517a6e9..109375f5cdb 100644 --- a/package/linux/rpm.sh +++ b/package/linux/rpm.sh @@ -2,7 +2,7 @@ ## From https://github.com/bisq-network/bisq-desktop/issues/401#issuecomment-372091261 -version=0.7.1 +version=0.8.0 alien -r -g /home/$USER/Desktop/Bisq-64bit-$version.deb find bisq-$version -type f | while read LIB; do LDDOUT=$(ldd $LIB 2>&1); LDDRETVAL=$?;if [ \( -z "${LDDOUT%%*you do not have execution permission for*}" \) -a \( $LDDRETVAL -eq 0 \) ]; then chmod -v +x $LIB;fi;done diff --git a/package/osx/Info.plist b/package/osx/Info.plist index 45dfa4ca1b2..452e30fa2e6 100644 --- a/package/osx/Info.plist +++ b/package/osx/Info.plist @@ -40,7 +40,7 @@ JVMAppClasspath JVMMainJarName - Bisq-0.7.1.jar + Bisq-0.8.0.jar JVMPreferencesID bisq JVMOptions diff --git a/package/osx/create_app.sh b/package/osx/create_app.sh index 0d1c6c1bbb9..c4da4377939 100755 --- a/package/osx/create_app.sh +++ b/package/osx/create_app.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e -version="0.7.1" +version="0.8.0" ./gradlew --include-build ../common --include-build ../assets --include-build ../p2p --include-build ../core build -x test shadowJar @@ -82,18 +82,18 @@ $JAVA_HOME/bin/javapackager \ -Bicon=package/osx/Bisq.icns \ -Bruntime="$JAVA_HOME/jre" \ -native dmg \ - -name Bisq \ - -title Bisq \ + -name Bisq_notifications \ + -title Bisq_notifications \ -vendor Bisq \ -outdir deploy \ -srcfiles "deploy/Bisq-$version.jar" \ -appclass bisq.desktop.app.BisqAppMain \ - -outfile Bisq + -outfile Bisq_notifications rm "deploy/Bisq.html" rm "deploy/Bisq.jnlp" -mv "deploy/bundles/Bisq-$version.dmg" "deploy/Bisq-$version.dmg" +mv "deploy/bundles/Bisq_notifications-$version.dmg" "deploy/Bisq_notifications-$version.dmg" rm -r "deploy/bundles" open deploy diff --git a/package/osx/finalize.sh b/package/osx/finalize.sh index 60ff6ca268c..a02db45baf5 100755 --- a/package/osx/finalize.sh +++ b/package/osx/finalize.sh @@ -2,7 +2,7 @@ cd ../../ -version="0.7.1" +version="0.8.0" target_dir="releases/$version" diff --git a/package/windows/32bitBuild.bat b/package/windows/32bitBuild.bat index 87636dbc7c2..ab448adf96c 100644 --- a/package/windows/32bitBuild.bat +++ b/package/windows/32bitBuild.bat @@ -5,7 +5,7 @@ :: 32 bit build :: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php) -SET version=0.7.1 +SET version=0.8.0 :: Private setup SET outdir=\\VBOXSVR\vm_shared_windows_32bit diff --git a/package/windows/64bitBuild.bat b/package/windows/64bitBuild.bat index 3d6e2afa972..69116aae1cb 100644 --- a/package/windows/64bitBuild.bat +++ b/package/windows/64bitBuild.bat @@ -5,7 +5,7 @@ :: 64 bit build :: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php) -SET version=0.7.1 +SET version=0.8.0 :: Private setup SET outdir=\\VBOXSVR\vm_shared_windows diff --git a/package/windows/Bisq.iss b/package/windows/Bisq.iss index 6bf84746048..850226b08b7 100755 --- a/package/windows/Bisq.iss +++ b/package/windows/Bisq.iss @@ -3,7 +3,7 @@ [Setup] AppId={{bisq}} AppName=Bisq -AppVersion=0.7.1 +AppVersion=0.8.0 AppVerName=Bisq AppPublisher=Bisq AppComments=Bisq From 703bc08e79e3c698d94e3d199c80bc77f3109791 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Thu, 9 Aug 2018 11:18:32 +0200 Subject: [PATCH 14/20] Handle no-webcam case Fixed https://github.com/bisq-network/bisq-desktop/issues/1618 --- .../notifications/NoWebCamFoundException.java | 24 +++++++++++++++++++ .../notifications/NotificationsView.java | 11 +++++++++ .../content/notifications/WebCamLauncher.java | 18 ++++++++++---- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/NoWebCamFoundException.java diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NoWebCamFoundException.java b/src/main/java/bisq/desktop/main/account/content/notifications/NoWebCamFoundException.java new file mode 100644 index 00000000000..25428cde1b4 --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NoWebCamFoundException.java @@ -0,0 +1,24 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.notifications; + +public class NoWebCamFoundException extends Throwable { + public NoWebCamFoundException(String msg) { + super(msg); + } +} diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index fa75c2591c9..1e99e1d087d 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -223,6 +223,17 @@ private void createSetupFields() { }, 600, TimeUnit.MILLISECONDS); } }); + }, throwable -> { + if (throwable instanceof NoWebCamFoundException) { + new Popup<>().warning(Res.get("account.notifications.noWebCamFound.warning")).show(); + webCamButton.setDisable(false); + setPairingTokenFieldsVisible(); + noWebCamButton.setManaged(false); + noWebCamButton.setVisible(false); + } else { + log.error(throwable.toString()); + new Popup<>().error(throwable.toString()).show(); + } }); }); diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java index f742d34a9b1..569005508c8 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/WebCamLauncher.java @@ -18,6 +18,7 @@ package bisq.desktop.main.account.content.notifications; import bisq.common.UserThread; +import bisq.common.handlers.ExceptionHandler; import java.awt.Dimension; @@ -34,9 +35,11 @@ // Must not be UI thread class WebCamLauncher extends Thread { private final Consumer resultHandler; + private final ExceptionHandler exceptionHandler; - public WebCamLauncher(Consumer resultHandler) { + WebCamLauncher(Consumer resultHandler, ExceptionHandler exceptionHandler) { this.resultHandler = resultHandler; + this.exceptionHandler = exceptionHandler; start(); } @@ -45,12 +48,17 @@ public WebCamLauncher(Consumer resultHandler) { public void run() { try { Webcam webCam = Webcam.getDefault(1000); // one second timeout - the default is too long - Dimension[] sizes = webCam.getViewSizes(); - Dimension size = sizes[sizes.length - 1]; // the largest size - webCam.setViewSize(size); - UserThread.execute(() -> resultHandler.accept(webCam)); + if (webCam != null) { + Dimension[] sizes = webCam.getViewSizes(); + Dimension size = sizes[sizes.length - 1]; // the largest size + webCam.setViewSize(size); + UserThread.execute(() -> resultHandler.accept(webCam)); + } else { + UserThread.execute(() -> exceptionHandler.handleException(new NoWebCamFoundException("No webcam found."))); + } } catch (TimeoutException e) { log.error(e.toString()); + UserThread.execute(() -> exceptionHandler.handleException(e)); } } } From 671daacd180a2c083c00bfbd5599ed1a681f3446 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Fri, 10 Aug 2018 23:34:40 +0200 Subject: [PATCH 15/20] Add more logs --- .../content/notifications/NotificationsView.java | 14 +++++++++----- .../content/notifications/QrCodeReader.java | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index 1e99e1d087d..c5035718422 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -197,8 +197,9 @@ private void createSetupFields() { webCamButton.setDefaultButton(true); webCamButton.setOnAction((event) -> { webCamButton.setDisable(true); - + log.info("Start WebCamLauncher"); new WebCamLauncher(webCam -> { + log.info("webCam available"); webCamWindow = new WebCamWindow(webCam.getViewSize().width, webCam.getViewSize().height) .onClose(() -> { webCamButton.setDisable(false); @@ -207,6 +208,7 @@ private void createSetupFields() { webCamWindow.show(); qrCodeReader = new QrCodeReader(webCam, webCamWindow.getImageView(), qrCode -> { + log.info("Qr code available"); webCamWindow.hide(); webCamButton.setDisable(false); reset(); @@ -399,10 +401,12 @@ public TradeCurrency fromString(String string) { /////////////////////////////////////////////////////////////////////////////////////////// private void applyKeyAndToken(String keyAndToken) { - mobileNotificationService.applyKeyAndToken(keyAndToken); - if (mobileNotificationValidator.isValid(keyAndToken)) { - updateDisableState(false); - setPairingTokenFieldsVisible(); + if (keyAndToken != null && !keyAndToken.isEmpty()) { + mobileNotificationService.applyKeyAndToken(keyAndToken); + if (mobileNotificationValidator.isValid(keyAndToken)) { + updateDisableState(false); + setPairingTokenFieldsVisible(); + } } } diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java index 20fc7bb1e3f..9178cc1eea3 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/QrCodeReader.java @@ -48,7 +48,7 @@ class QrCodeReader extends Thread { private final Consumer resultHandler; private boolean isRunning; - public QrCodeReader(Webcam webCam, ImageView imageView, Consumer resultHandler) { + QrCodeReader(Webcam webCam, ImageView imageView, Consumer resultHandler) { this.webCam = webCam; this.imageView = imageView; this.resultHandler = resultHandler; From be5d1917e0cc689aebb1cb55c7a52899b7142f7f Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Sat, 11 Aug 2018 23:19:34 +0200 Subject: [PATCH 16/20] Remove iphone 6 warning Background App refresh need to be enabled (wlan+mobile data) and bisq enabled (settings/general/Background App refresh). If that is not enabled the app does not receive the messages even if it is open. Apple has some weird logic who the name such "features" - you need Background App refresh enabled to receive data if the app is not in background.... I think another reason to push Apple in background... --- .../content/notifications/NotificationsView.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java index c5035718422..832a5cead5d 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.java @@ -32,7 +32,6 @@ import bisq.core.locale.TradeCurrency; import bisq.core.monetary.Price; import bisq.core.notifications.MobileMessage; -import bisq.core.notifications.MobileModel; import bisq.core.notifications.MobileNotificationService; import bisq.core.notifications.MobileNotificationValidator; import bisq.core.notifications.alerts.DisputeMsgEvents; @@ -47,7 +46,6 @@ import bisq.core.util.BSFormatter; import bisq.core.util.validation.InputValidator; -import bisq.common.UserThread; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; @@ -66,7 +64,6 @@ import java.util.List; import java.util.Optional; -import java.util.concurrent.TimeUnit; @FxmlView public class NotificationsView extends ActivatableView { @@ -214,16 +211,6 @@ private void createSetupFields() { reset(); tokenInputTextField.setText(qrCode); updatePriceAlertInputs(); - - if (mobileNotificationService.getMobileModel().getOs() != MobileModel.OS.ANDROID && - !mobileNotificationService.getMobileModel().isContentAvailable()) { - UserThread.runAfter(() -> { - new Popup<>() - .warning(Res.get("account.notifications.isContentAvailable.warning", - mobileNotificationService.getMobileModel().getDescriptor())) - .show(); - }, 600, TimeUnit.MILLISECONDS); - } }); }, throwable -> { if (throwable instanceof NoWebCamFoundException) { From c3e7fe0e41a346e3e8d8eb7968ac1b5454f1c8bd Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 13 Aug 2018 17:44:35 +0200 Subject: [PATCH 17/20] Improve price alerts --- ...View.fxml => MobileNotificationsView.fxml} | 2 +- ...View.java => MobileNotificationsView.java} | 171 +++++++++++++----- .../account/settings/AccountSettingsView.java | 6 +- 3 files changed, 127 insertions(+), 52 deletions(-) rename src/main/java/bisq/desktop/main/account/content/notifications/{NotificationsView.fxml => MobileNotificationsView.fxml} (96%) rename src/main/java/bisq/desktop/main/account/content/notifications/{NotificationsView.java => MobileNotificationsView.java} (70%) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml b/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.fxml similarity index 96% rename from src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml rename to src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.fxml index 4e00072cf38..07d49b39d92 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/NotificationsView.fxml +++ b/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.fxml @@ -20,7 +20,7 @@ - { +public class MobileNotificationsView extends ActivatableView { private final Preferences preferences; private final User user; private final PriceFeedService priceFeedService; - private final MobileNotificationValidator mobileNotificationValidator; private final MobileNotificationService mobileNotificationService; private final BSFormatter formatter; @@ -79,14 +78,14 @@ public class NotificationsView extends ActivatableView { private TextField tokenInputTextField; private Label tokenInputLabel; - private InputTextField priceAlertHigh, priceAlertLow; + private InputTextField priceAlertHighInputTextField, priceAlertLowInputTextField; private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; private ComboBox currencyComboBox; private Button webCamButton, noWebCamButton, eraseButton, testMsgButton, setPriceAlertButton, removePriceAlertButton; private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener, marketCheckBoxListener, - priceCheckBoxListener; + priceCheckBoxListener, priceAlertHighFocusListener, priceAlertLowFocusListener; private ChangeListener tokenInputTextFieldListener, priceAlertHighListener, priceAlertLowListener; private ChangeListener priceFeedServiceListener; @@ -100,17 +99,15 @@ public class NotificationsView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private NotificationsView(Preferences preferences, - User user, - PriceFeedService priceFeedService, - MobileNotificationValidator mobileNotificationValidator, - MobileNotificationService mobileNotificationService, - BSFormatter formatter) { + private MobileNotificationsView(Preferences preferences, + User user, + PriceFeedService priceFeedService, + MobileNotificationService mobileNotificationService, + BSFormatter formatter) { super(); this.preferences = preferences; this.user = user; this.priceFeedService = priceFeedService; - this.mobileNotificationValidator = mobileNotificationValidator; this.mobileNotificationService = mobileNotificationService; this.formatter = formatter; } @@ -141,15 +138,18 @@ protected void activate() { updateDisableState(!mobileNotificationService.isSetupConfirmationSent()); // priceAlert - priceAlertHigh.textProperty().addListener(priceAlertHighListener); - priceAlertLow.textProperty().addListener(priceAlertLowListener); + priceAlertHighInputTextField.textProperty().addListener(priceAlertHighListener); + priceAlertLowInputTextField.textProperty().addListener(priceAlertLowListener); + priceAlertHighInputTextField.focusedProperty().addListener(priceAlertHighFocusListener); + priceAlertLowInputTextField.focusedProperty().addListener(priceAlertLowFocusListener); currencyComboBox.setOnAction(e -> setSelectedPriceAlertTradeCurrency()); setPriceAlertButton.setOnAction(e -> { if (arePriceAlertInputsValid()) { String code = selectedPriceAlertTradeCurrency.getCode(); - long high = getPriceAlertHighTextFieldValue(); - long low = getPriceAlertLowTextFieldValue(); - user.setPriceAlertFilter(new PriceAlertFilter(code, high, low)); + long high = getPriceAsLong(priceAlertHighInputTextField); + long low = getPriceAsLong(priceAlertLowInputTextField); + if (high > 0 && low > 0) + user.setPriceAlertFilter(new PriceAlertFilter(code, high, low)); updatePriceAlertInputs(); } }); @@ -171,8 +171,11 @@ protected void deactivate() { marketCheckBox.selectedProperty().removeListener(marketCheckBoxListener); priceCheckBox.selectedProperty().removeListener(priceCheckBoxListener); - priceAlertHigh.textProperty().removeListener(priceAlertHighListener); - priceAlertLow.textProperty().removeListener(priceAlertLowListener); + priceAlertHighInputTextField.textProperty().removeListener(priceAlertHighListener); + priceAlertLowInputTextField.textProperty().removeListener(priceAlertLowListener); + priceAlertHighInputTextField.focusedProperty().removeListener(priceAlertHighFocusListener); + priceAlertLowInputTextField.focusedProperty().removeListener(priceAlertLowFocusListener); + priceFeedService.updateCounterProperty().removeListener(priceFeedServiceListener); currencyComboBox.setOnAction(null); setPriceAlertButton.setOnAction(null); @@ -362,13 +365,50 @@ public TradeCurrency fromString(String string) { } }); - priceAlertHigh = FormBuilder.addLabelInputTextField(root, ++gridRow, + priceAlertHighInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.priceAlert.high.label")).second; - priceAlertHighListener = (observable, oldValue, newValue) -> updatePriceAlertInputs(); - - priceAlertLow = FormBuilder.addLabelInputTextField(root, ++gridRow, + priceAlertHighListener = (observable, oldValue, newValue) -> { + long priceAlertHighTextFieldValue = getPriceAsLong(priceAlertHighInputTextField); + long priceAlertLowTextFieldValue = getPriceAsLong(priceAlertLowInputTextField); + if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { + if (priceAlertHighTextFieldValue > priceAlertLowTextFieldValue) + updatePriceAlertInputs(); + } + }; + priceAlertHighFocusListener = (observable, oldValue, newValue) -> { + if (oldValue && !newValue) { + applyPriceFormatting(priceAlertHighInputTextField); + long priceAlertHighTextFieldValue = getPriceAsLong(priceAlertHighInputTextField); + long priceAlertLowTextFieldValue = getPriceAsLong(priceAlertLowInputTextField); + if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { + if (priceAlertHighTextFieldValue <= priceAlertLowTextFieldValue) { + new Popup<>().warning(Res.get("account.notifications.priceAlert.warning.highPriceTooLow")).show(); + UserThread.execute(() -> priceAlertHighInputTextField.clear()); + } + } + } + }; + priceAlertLowInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.priceAlert.low.label")).second; - priceAlertLowListener = (observable, oldValue, newValue) -> updatePriceAlertInputs(); + priceAlertLowListener = (observable, oldValue, newValue) -> { + long priceAlertHighTextFieldValue = getPriceAsLong(priceAlertHighInputTextField); + long priceAlertLowTextFieldValue = getPriceAsLong(priceAlertLowInputTextField); + if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { + if (priceAlertLowTextFieldValue < priceAlertHighTextFieldValue) + updatePriceAlertInputs(); + } + }; + priceAlertLowFocusListener = (observable, oldValue, newValue) -> { + applyPriceFormatting(priceAlertLowInputTextField); + long priceAlertHighTextFieldValue = getPriceAsLong(priceAlertHighInputTextField); + long priceAlertLowTextFieldValue = getPriceAsLong(priceAlertLowInputTextField); + if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { + if (priceAlertLowTextFieldValue >= priceAlertHighTextFieldValue) { + new Popup<>().warning(Res.get("account.notifications.priceAlert.warning.lowerPriceTooHigh")).show(); + UserThread.execute(() -> priceAlertLowInputTextField.clear()); + } + } + }; Tuple2 tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow, Res.get("account.notifications.priceAlert.setButton"), @@ -376,9 +416,16 @@ public TradeCurrency fromString(String string) { setPriceAlertButton = tuple.first; removePriceAlertButton = tuple.second; + // When we get a price update an existing price alert might get removed. + // We get updated the view at each price update so we get aware of the removed PriceAlertFilter in the + // fillPriceAlertFields method. To be sure that we called after the PriceAlertFilter has been removed we delay + // to the next frame. The priceFeedServiceListener in the mobileNotificationService might get called before + // our listener here. priceFeedServiceListener = (observable, oldValue, newValue) -> { - fillPriceAlertFields(); - updatePriceAlertInputs(); + UserThread.execute(() -> { + fillPriceAlertFields(); + updatePriceAlertInputs(); + }); }; } @@ -389,10 +436,11 @@ public TradeCurrency fromString(String string) { private void applyKeyAndToken(String keyAndToken) { if (keyAndToken != null && !keyAndToken.isEmpty()) { - mobileNotificationService.applyKeyAndToken(keyAndToken); - if (mobileNotificationValidator.isValid(keyAndToken)) { + boolean isValid = mobileNotificationService.applyKeyAndToken(keyAndToken); + if (isValid) { updateDisableState(false); setPairingTokenFieldsVisible(); + updatePriceAlertInputs(); } } } @@ -438,14 +486,16 @@ private void fillPriceAlertFields() { currencyComboBox.getSelectionModel().select(optionalTradeCurrency.get()); setSelectedPriceAlertTradeCurrency(); - priceAlertHigh.setText(formatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode)); - priceAlertLow.setText(formatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode)); + priceAlertHighInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode)); + priceAlertLowInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode)); } else { currencyComboBox.getSelectionModel().clearSelection(); } } else { - priceAlertHigh.clear(); - priceAlertLow.clear(); + priceAlertHighInputTextField.clear(); + priceAlertLowInputTextField.clear(); + priceAlertHighInputTextField.resetValidation(); + priceAlertLowInputTextField.resetValidation(); currencyComboBox.getSelectionModel().clearSelection(); } } @@ -455,14 +505,14 @@ private void updatePriceAlertInputs() { boolean selected = priceCheckBox.isSelected(); boolean disable = !setupConfirmationSent || !selected; - priceAlertHigh.setDisable(selectedPriceAlertTradeCurrency == null || disable); - priceAlertLow.setDisable(selectedPriceAlertTradeCurrency == null || disable); + priceAlertHighInputTextField.setDisable(selectedPriceAlertTradeCurrency == null || disable); + priceAlertLowInputTextField.setDisable(selectedPriceAlertTradeCurrency == null || disable); PriceAlertFilter priceAlertFilter = user.getPriceAlertFilter(); boolean valueSameAsFilter = false; if (priceAlertFilter != null && selectedPriceAlertTradeCurrency != null) { - valueSameAsFilter = priceAlertFilter.getHigh() == getPriceAlertHighTextFieldValue() && - priceAlertFilter.getLow() == getPriceAlertLowTextFieldValue() && + valueSameAsFilter = priceAlertFilter.getHigh() == getPriceAsLong(priceAlertHighInputTextField) && + priceAlertFilter.getLow() == getPriceAsLong(priceAlertLowInputTextField) && priceAlertFilter.getCurrencyCode().equals(selectedPriceAlertTradeCurrency.getCode()); } setPriceAlertButton.setDisable(disable || !arePriceAlertInputsValid() || valueSameAsFilter); @@ -480,16 +530,16 @@ private void setSelectedPriceAlertTradeCurrency() { selectedPriceAlertTradeCurrency = currencyComboBox.getSelectionModel().getSelectedItem(); if (selectedPriceAlertTradeCurrency != null) { boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(selectedPriceAlertTradeCurrency.getCode()); - priceAlertHigh.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); - priceAlertLow.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); + priceAlertHighInputTextField.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); + priceAlertLowInputTextField.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); } updatePriceAlertInputs(); } private boolean arePriceAlertInputsValid() { return selectedPriceAlertTradeCurrency != null && - isPriceInputValid(priceAlertHigh).isValid && - isPriceInputValid(priceAlertLow).isValid; + isPriceInputValid(priceAlertHighInputTextField).isValid && + isPriceInputValid(priceAlertLowInputTextField).isValid; } private InputValidator.ValidationResult isPriceInputValid(InputTextField inputTextField) { @@ -500,15 +550,40 @@ private InputValidator.ValidationResult isPriceInputValid(InputTextField inputTe return new InputValidator.ValidationResult(false); } - private long getPriceAlertHighTextFieldValue() { - String text = priceAlertHigh.getText(); - return text.isEmpty() ? 0 : Price.parse(selectedPriceAlertTradeCurrency.getCode(), text).getValue(); + private long getPriceAsLong(InputTextField inputTextField) { + try { + String inputValue = inputTextField.getText(); + if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) { + double priceAsDouble = formatter.parseNumberStringToDouble(inputValue); + String currencyCode = selectedPriceAlertTradeCurrency.getCode(); + int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ? + Altcoin.SMALLEST_UNIT_EXPONENT : 2; + // We want to use the converted value not the inout value as we apply the converted value at focus out. + // E.g. if input is 5555.5555 it will be rounded to 5555.55 and we use that as the value for comparing + // low and high price... + String stringValue = formatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision); + return formatter.parsePriceStringToLong(currencyCode, stringValue, precision); + } else { + return 0; + } + } catch (Throwable ignore) { + return 0; + } } - private long getPriceAlertLowTextFieldValue() { - String text = priceAlertLow.getText(); - return text.isEmpty() ? 0 : Price.parse(selectedPriceAlertTradeCurrency.getCode(), text).getValue(); + private void applyPriceFormatting(InputTextField inputTextField) { + try { + String inputValue = inputTextField.getText(); + if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) { + double priceAsDouble = formatter.parseNumberStringToDouble(inputValue); + String currencyCode = selectedPriceAlertTradeCurrency.getCode(); + int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ? + Altcoin.SMALLEST_UNIT_EXPONENT : 2; + String stringValue = formatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision); + inputTextField.setText(stringValue); + } + } catch (Throwable ignore) { + } } - } diff --git a/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java b/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java index 8fb56305d76..7db7447d66f 100644 --- a/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java +++ b/src/main/java/bisq/desktop/main/account/settings/AccountSettingsView.java @@ -31,7 +31,7 @@ import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView; import bisq.desktop.main.account.content.backup.BackupView; import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView; -import bisq.desktop.main.account.content.notifications.NotificationsView; +import bisq.desktop.main.account.content.notifications.MobileNotificationsView; import bisq.desktop.main.account.content.password.PasswordView; import bisq.desktop.main.account.content.seedwords.SeedWordsView; import bisq.desktop.util.Colors; @@ -97,7 +97,7 @@ public void initialize() { altCoinsAccountView = new MenuItem(navigation, toggleGroup, Res.get("account.menu.altCoinsAccountView"), AltCoinAccountsView.class, AwesomeIcon.LINK); arbitratorSelection = new MenuItem(navigation, toggleGroup, Res.get("account.menu.arbitratorSelection"), ArbitratorSelectionView.class, AwesomeIcon.USER_MD); - notifications = new MenuItem(navigation, toggleGroup, Res.get("account.menu.notifications"), NotificationsView.class, AwesomeIcon.BELL); + notifications = new MenuItem(navigation, toggleGroup, Res.get("account.menu.notifications"), MobileNotificationsView.class, AwesomeIcon.BELL); password = new MenuItem(navigation, toggleGroup, Res.get("account.menu.password"), PasswordView.class, AwesomeIcon.UNLOCK_ALT); seedWords = new MenuItem(navigation, toggleGroup, Res.get("account.menu.seedWords"), SeedWordsView.class, AwesomeIcon.KEY); backup = new MenuItem(navigation, toggleGroup, Res.get("account.menu.backup"), BackupView.class, AwesomeIcon.CLOUD_DOWNLOAD); @@ -149,7 +149,7 @@ private void loadView(Class viewClass) { if (view instanceof FiatAccountsView) paymentAccount.setSelected(true); else if (view instanceof AltCoinAccountsView) altCoinsAccountView.setSelected(true); else if (view instanceof ArbitratorSelectionView) arbitratorSelection.setSelected(true); - else if (view instanceof NotificationsView) notifications.setSelected(true); + else if (view instanceof MobileNotificationsView) notifications.setSelected(true); else if (view instanceof PasswordView) password.setSelected(true); else if (view instanceof SeedWordsView) seedWords.setSelected(true); else if (view instanceof BackupView) backup.setSelected(true); From bbc130614334efda38271466189c1fb09501f164 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 14 Aug 2018 14:49:35 +0200 Subject: [PATCH 18/20] Add market price alerts --- .../components/InfoInputTextField.java | 63 ++- .../ManageMarketAlertsWindow.java | 211 ++++++++ .../MobileNotificationsView.java | 511 ++++++++++++------ .../desktop/main/offer/MutableOfferView.java | 2 +- .../windows/ShowWalletDataWindow.java | 5 - .../java/bisq/desktop/util/FormBuilder.java | 24 +- .../validation/PercentageNumberValidator.java | 53 ++ 7 files changed, 679 insertions(+), 190 deletions(-) create mode 100644 src/main/java/bisq/desktop/main/account/content/notifications/ManageMarketAlertsWindow.java create mode 100644 src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java diff --git a/src/main/java/bisq/desktop/components/InfoInputTextField.java b/src/main/java/bisq/desktop/components/InfoInputTextField.java index edd15189d36..326867cb637 100644 --- a/src/main/java/bisq/desktop/components/InfoInputTextField.java +++ b/src/main/java/bisq/desktop/components/InfoInputTextField.java @@ -32,14 +32,19 @@ import java.util.concurrent.TimeUnit; +import lombok.Getter; + import static bisq.desktop.util.FormBuilder.getIcon; public class InfoInputTextField extends AnchorPane { private final StringProperty text = new SimpleStringProperty(); - private final InputTextField textField; + @Getter + private final InputTextField inputTextField; + @Getter private final Label infoIcon; + @Getter private final Label warningIcon; private Label currentIcon; private PopOver popover; @@ -48,7 +53,7 @@ public class InfoInputTextField extends AnchorPane { public InfoInputTextField() { super(); - textField = new InputTextField(); + inputTextField = new InputTextField(); infoIcon = getIcon(AwesomeIcon.INFO_SIGN); infoIcon.setLayoutY(3); @@ -60,12 +65,12 @@ public InfoInputTextField() { AnchorPane.setLeftAnchor(infoIcon, 7.0); AnchorPane.setLeftAnchor(warningIcon, 7.0); - AnchorPane.setRightAnchor(textField, 0.0); - AnchorPane.setLeftAnchor(textField, 0.0); + AnchorPane.setRightAnchor(inputTextField, 0.0); + AnchorPane.setLeftAnchor(inputTextField, 0.0); hideIcons(); - getChildren().addAll(textField, infoIcon, warningIcon); + getChildren().addAll(inputTextField, infoIcon, warningIcon); } private void hideIcons() { @@ -93,6 +98,35 @@ public void setContentForWarningPopOver(Node node) { setActionHandlers(node); } + public void setIconsRightAligned() { + AnchorPane.clearConstraints(infoIcon); + AnchorPane.clearConstraints(warningIcon); + AnchorPane.clearConstraints(inputTextField); + + AnchorPane.setRightAnchor(infoIcon, 7.0); + AnchorPane.setRightAnchor(warningIcon, 7.0); + AnchorPane.setLeftAnchor(inputTextField, 0.0); + AnchorPane.setRightAnchor(inputTextField, 0.0); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Getters/Setters + /////////////////////////////////////////////////////////////////////////////////////////// + + public void setText(String text) { + this.text.set(text); + } + + public String getText() { + return text.get(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + private void setActionHandlers(Node node) { currentIcon.setManaged(true); @@ -116,10 +150,6 @@ private void setActionHandlers(Node node) { }); } - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - private void showPopOver(Node node) { node.getStyleClass().add("default-text"); @@ -132,19 +162,4 @@ private void showPopOver(Node node) { popover.show(currentIcon, -17); } } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters/Setters - /////////////////////////////////////////////////////////////////////////////////////////// - - public InputTextField getTextField() { return textField; } - - public void setText(String text) { - this.text.set(text); - } - - public String getText() { - return text.get(); - } - } diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/ManageMarketAlertsWindow.java b/src/main/java/bisq/desktop/main/account/content/notifications/ManageMarketAlertsWindow.java new file mode 100644 index 00000000000..5189d522a88 --- /dev/null +++ b/src/main/java/bisq/desktop/main/account/content/notifications/ManageMarketAlertsWindow.java @@ -0,0 +1,211 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.account.content.notifications; + +import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.AutoTooltipTableColumn; +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.util.ImageUtil; + +import bisq.core.locale.Res; +import bisq.core.notifications.alerts.market.MarketAlertFilter; +import bisq.core.notifications.alerts.market.MarketAlerts; +import bisq.core.util.BSFormatter; + +import bisq.common.UserThread; + +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; + +import javafx.geometry.Insets; + +import javafx.beans.property.ReadOnlyObjectWrapper; + +import javafx.collections.FXCollections; + +import javafx.util.Callback; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ManageMarketAlertsWindow extends Overlay { + + private final MarketAlerts marketAlerts; + private final BSFormatter formatter; + + ManageMarketAlertsWindow(MarketAlerts marketAlerts, BSFormatter formatter) { + this.marketAlerts = marketAlerts; + this.formatter = formatter; + type = Type.Attention; + } + + @Override + public void show() { + if (headLine == null) + headLine = Res.get("account.notifications.marketAlert.manageAlerts.title"); + + width = 900; + createGridPane(); + addHeadLine(); + addContent(); + addCloseButton(); + applyStyles(); + display(); + } + + @Override + protected void applyStyles() { + super.applyStyles(); + gridPane.setId("popup-grid-pane-bg"); + } + + private void addContent() { + TableView tableView = new TableView<>(); + GridPane.setRowIndex(tableView, ++rowIndex); + GridPane.setColumnSpan(tableView, 2); + GridPane.setMargin(tableView, new Insets(10, 0, 0, 0)); + gridPane.getChildren().add(tableView); + Label placeholder = new AutoTooltipLabel(Res.get("table.placeholder.noData")); + placeholder.setWrapText(true); + tableView.setPlaceholder(placeholder); + tableView.setPrefHeight(300); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + setColumns(tableView); + tableView.setItems(FXCollections.observableArrayList(marketAlerts.getMarketAlertFilters())); + } + + private void removeMarketAlertFilter(MarketAlertFilter marketAlertFilter, TableView tableView) { + marketAlerts.removeMarketAlertFilter(marketAlertFilter); + UserThread.execute(() -> tableView.setItems(FXCollections.observableArrayList(marketAlerts.getMarketAlertFilters()))); + } + + private void setColumns(TableView tableView) { + TableColumn column; + + column = new AutoTooltipTableColumn<>(Res.get("account.notifications.marketAlert.manageAlerts.header.paymentAccount")); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final MarketAlertFilter item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.getPaymentAccount().getAccountName()); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + + + column = new AutoTooltipTableColumn<>(Res.get("account.notifications.marketAlert.manageAlerts.header.trigger")); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final MarketAlertFilter item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(formatter.formatPercentagePrice(item.getTriggerValue() / 10000d)); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + + + column = new AutoTooltipTableColumn<>(Res.get("account.notifications.marketAlert.manageAlerts.header.offerType")); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final MarketAlertFilter item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(item.isBuyOffer() ? Res.get("shared.buyBitcoin") : Res.get("shared.sellBitcoin")); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + + column = new TableColumn<>(); + column.setMinWidth(40); + column.setMaxWidth(column.getMinWidth()); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON); + final Button removeButton = new AutoTooltipButton("", icon); + + { + removeButton.setId("icon-button"); + removeButton.setTooltip(new Tooltip(Res.get("shared.remove"))); + } + + @Override + public void updateItem(final MarketAlertFilter item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + removeButton.setOnAction(e -> removeMarketAlertFilter(item, tableView)); + setGraphic(removeButton); + } else { + setGraphic(null); + removeButton.setOnAction(null); + } + } + }; + } + }); + + tableView.getColumns().add(column); + } +} diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java index 435c4b36c26..7f321dc55fb 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java @@ -19,6 +19,7 @@ import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.InfoInputTextField; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.WebCamWindow; @@ -26,6 +27,7 @@ import bisq.desktop.util.Layout; import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.FiatPriceValidator; +import bisq.desktop.util.validation.PercentageNumberValidator; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -36,9 +38,11 @@ import bisq.core.notifications.alerts.DisputeMsgEvents; import bisq.core.notifications.alerts.MyOfferTakenEvents; import bisq.core.notifications.alerts.TradeEvents; +import bisq.core.notifications.alerts.market.MarketAlertFilter; import bisq.core.notifications.alerts.market.MarketAlerts; import bisq.core.notifications.alerts.price.PriceAlert; import bisq.core.notifications.alerts.price.PriceAlertFilter; +import bisq.core.payment.PaymentAccount; import bisq.core.provider.price.PriceFeedService; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -55,13 +59,21 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.GridPane; +import javafx.geometry.Insets; + import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; + import javafx.util.StringConverter; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -70,6 +82,7 @@ public class MobileNotificationsView extends ActivatableView { private final Preferences preferences; private final User user; private final PriceFeedService priceFeedService; + private final MarketAlerts marketAlerts; private final MobileNotificationService mobileNotificationService; private final BSFormatter formatter; @@ -78,20 +91,24 @@ public class MobileNotificationsView extends ActivatableView { private TextField tokenInputTextField; private Label tokenInputLabel; - private InputTextField priceAlertHighInputTextField, priceAlertLowInputTextField; + private InputTextField priceAlertHighInputTextField, priceAlertLowInputTextField, marketAlertTriggerInputTextField; private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; private ComboBox currencyComboBox; + private ComboBox paymentAccountsComboBox; private Button webCamButton, noWebCamButton, eraseButton, testMsgButton, setPriceAlertButton, - removePriceAlertButton; + removePriceAlertButton, addMarketAlertButton, manageAlertsButton; private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener, marketCheckBoxListener, - priceCheckBoxListener, priceAlertHighFocusListener, priceAlertLowFocusListener; - private ChangeListener tokenInputTextFieldListener, priceAlertHighListener, priceAlertLowListener; + priceCheckBoxListener, priceAlertHighFocusListener, priceAlertLowFocusListener, marketAlertTriggerFocusListener; + private ChangeListener tokenInputTextFieldListener, priceAlertHighListener, priceAlertLowListener, marketAlertTriggerListener; private ChangeListener priceFeedServiceListener; - private TradeCurrency selectedPriceAlertTradeCurrency; private int gridRow = 0; private int testMsgCounter = 0; + private RadioButton buyOffersRadioButton, sellOffersRadioButton; + private ToggleGroup offerTypeRadioButtonsToggleGroup; + private ChangeListener offerTypeListener; + private String selectedPriceAlertTradeCurrency; /////////////////////////////////////////////////////////////////////////////////////////// @@ -102,12 +119,14 @@ public class MobileNotificationsView extends ActivatableView { private MobileNotificationsView(Preferences preferences, User user, PriceFeedService priceFeedService, + MarketAlerts marketAlerts, MobileNotificationService mobileNotificationService, BSFormatter formatter) { super(); this.preferences = preferences; this.user = user; this.priceFeedService = priceFeedService; + this.marketAlerts = marketAlerts; this.mobileNotificationService = mobileNotificationService; this.formatter = formatter; } @@ -116,66 +135,89 @@ private MobileNotificationsView(Preferences preferences, public void initialize() { createSetupFields(); createSettingsFields(); + createMarketAlertFields(); createPriceAlertFields(); } @Override protected void activate() { + // setup tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); + webCamButton.setOnAction(e -> onOpenWebCam()); + noWebCamButton.setOnAction(e -> onNoWebCam()); + testMsgButton.setOnAction(e -> onSendTestMsg()); + eraseButton.setOnAction(e -> onErase()); + // settings useSoundCheckBox.selectedProperty().addListener(useSoundCheckBoxListener); tradeCheckBox.selectedProperty().addListener(tradeCheckBoxListener); marketCheckBox.selectedProperty().addListener(marketCheckBoxListener); priceCheckBox.selectedProperty().addListener(priceCheckBoxListener); - if (preferences.getPhoneKeyAndToken() != null) { - tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); - setPairingTokenFieldsVisible(); - } else { - eraseButton.setDisable(true); - testMsgButton.setDisable(true); - } - updateDisableState(!mobileNotificationService.isSetupConfirmationSent()); + // market alert + marketAlertTriggerInputTextField.textProperty().addListener(marketAlertTriggerListener); + marketAlertTriggerInputTextField.focusedProperty().addListener(marketAlertTriggerFocusListener); + offerTypeRadioButtonsToggleGroup.selectedToggleProperty().addListener(offerTypeListener); + paymentAccountsComboBox.setOnAction(e -> onPaymentAccountSelected()); + addMarketAlertButton.setOnAction(e -> onAddMarketAlert()); + manageAlertsButton.setOnAction(e -> onManageMarketAlerts()); - // priceAlert + paymentAccountsComboBox.setItems(FXCollections.observableArrayList(user.getPaymentAccountsAsObservable())); + + // price alert priceAlertHighInputTextField.textProperty().addListener(priceAlertHighListener); priceAlertLowInputTextField.textProperty().addListener(priceAlertLowListener); priceAlertHighInputTextField.focusedProperty().addListener(priceAlertHighFocusListener); priceAlertLowInputTextField.focusedProperty().addListener(priceAlertLowFocusListener); - currencyComboBox.setOnAction(e -> setSelectedPriceAlertTradeCurrency()); - setPriceAlertButton.setOnAction(e -> { - if (arePriceAlertInputsValid()) { - String code = selectedPriceAlertTradeCurrency.getCode(); - long high = getPriceAsLong(priceAlertHighInputTextField); - long low = getPriceAsLong(priceAlertLowInputTextField); - if (high > 0 && low > 0) - user.setPriceAlertFilter(new PriceAlertFilter(code, high, low)); - updatePriceAlertInputs(); - } - }); - removePriceAlertButton.setOnAction(e -> removeAlert()); - priceFeedService.updateCounterProperty().addListener(priceFeedServiceListener); + currencyComboBox.setOnAction(e -> onSelectedTradeCurrency()); + setPriceAlertButton.setOnAction(e -> onSetPriceAlert()); + removePriceAlertButton.setOnAction(e -> onRemovePriceAlert()); currencyComboBox.setItems(preferences.getTradeCurrenciesAsObservable()); + + + if (preferences.getPhoneKeyAndToken() != null) { + tokenInputTextField.setText(preferences.getPhoneKeyAndToken()); + setPairingTokenFieldsVisible(); + } else { + eraseButton.setDisable(true); + testMsgButton.setDisable(true); + } + setDisableForSetupFields(!mobileNotificationService.isSetupConfirmationSent()); + updateMarketAlertFields(); fillPriceAlertFields(); - updatePriceAlertInputs(); + updatePriceAlertFields(); } @Override protected void deactivate() { + // setup tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); + webCamButton.setOnAction(null); + noWebCamButton.setOnAction(null); + testMsgButton.setOnAction(null); + eraseButton.setOnAction(null); + // settings useSoundCheckBox.selectedProperty().removeListener(useSoundCheckBoxListener); tradeCheckBox.selectedProperty().removeListener(tradeCheckBoxListener); marketCheckBox.selectedProperty().removeListener(marketCheckBoxListener); priceCheckBox.selectedProperty().removeListener(priceCheckBoxListener); + // market alert + marketAlertTriggerInputTextField.textProperty().removeListener(marketAlertTriggerListener); + marketAlertTriggerInputTextField.focusedProperty().removeListener(marketAlertTriggerFocusListener); + offerTypeRadioButtonsToggleGroup.selectedToggleProperty().removeListener(offerTypeListener); + paymentAccountsComboBox.setOnAction(null); + addMarketAlertButton.setOnAction(null); + manageAlertsButton.setOnAction(null); + + // price alert priceAlertHighInputTextField.textProperty().removeListener(priceAlertHighListener); priceAlertLowInputTextField.textProperty().removeListener(priceAlertLowListener); priceAlertHighInputTextField.focusedProperty().removeListener(priceAlertHighFocusListener); priceAlertLowInputTextField.focusedProperty().removeListener(priceAlertLowFocusListener); - priceFeedService.updateCounterProperty().removeListener(priceFeedServiceListener); currencyComboBox.setOnAction(null); setPriceAlertButton.setOnAction(null); @@ -183,6 +225,160 @@ protected void deactivate() { } + /////////////////////////////////////////////////////////////////////////////////////////// + // UI events + /////////////////////////////////////////////////////////////////////////////////////////// + + // Setup + private void onOpenWebCam() { + webCamButton.setDisable(true); + log.info("Start WebCamLauncher"); + new WebCamLauncher(webCam -> { + log.info("webCam available"); + webCamWindow = new WebCamWindow(webCam.getViewSize().width, webCam.getViewSize().height) + .onClose(() -> { + webCamButton.setDisable(false); + qrCodeReader.close(); + }); + webCamWindow.show(); + + qrCodeReader = new QrCodeReader(webCam, webCamWindow.getImageView(), qrCode -> { + log.info("Qr code available"); + webCamWindow.hide(); + webCamButton.setDisable(false); + reset(); + tokenInputTextField.setText(qrCode); + updateMarketAlertFields(); + updatePriceAlertFields(); + }); + }, throwable -> { + if (throwable instanceof NoWebCamFoundException) { + new Popup<>().warning(Res.get("account.notifications.noWebCamFound.warning")).show(); + webCamButton.setDisable(false); + onNoWebCam(); + } else { + log.error(throwable.toString()); + new Popup<>().error(throwable.toString()).show(); + } + }); + } + + private void onNoWebCam() { + setPairingTokenFieldsVisible(); + noWebCamButton.setManaged(false); + noWebCamButton.setVisible(false); + } + + private void onErase() { + try { + boolean success = mobileNotificationService.sendEraseMessage(); + if (!success) + log.warn("Erase message sending did not succeed"); + reset(); + } catch (Exception e) { + new Popup<>().error(e.toString()).show(); + } + } + + private void onSendTestMsg() { + MobileMessage message = null; + List messages = null; + switch (testMsgCounter) { + case 0: + message = MyOfferTakenEvents.getTestMsg(); + break; + case 1: + messages = TradeEvents.getTestMessages(); + break; + case 2: + message = DisputeMsgEvents.getTestMsg(); + break; + case 3: + message = PriceAlert.getTestMsg(); + break; + case 4: + default: + message = MarketAlerts.getTestMsg(); + break; + } + testMsgCounter++; + if (testMsgCounter > 4) + testMsgCounter = 0; + + try { + if (message != null) { + mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); + } else if (messages != null) { + messages.forEach(msg -> { + try { + mobileNotificationService.sendMessage(msg, useSoundCheckBox.isSelected()); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } catch (Exception e) { + new Popup<>().error(e.toString()).show(); + } + } + + + // Market alerts + private void onPaymentAccountSelected() { + marketAlertTriggerInputTextField.clear(); + marketAlertTriggerInputTextField.resetValidation(); + offerTypeRadioButtonsToggleGroup.selectToggle(null); + updateMarketAlertFields(); + } + + private void onAddMarketAlert() { + PaymentAccount paymentAccount = paymentAccountsComboBox.getSelectionModel().getSelectedItem(); + double percentAsDouble = formatter.parsePercentStringToDouble(marketAlertTriggerInputTextField.getText()); + int triggerValue = (int) Math.round(percentAsDouble * 10000); + boolean isBuyOffer = offerTypeRadioButtonsToggleGroup.getSelectedToggle() == buyOffersRadioButton; + MarketAlertFilter marketAlertFilter = new MarketAlertFilter(paymentAccount, triggerValue, isBuyOffer); + marketAlerts.addMarketAlertFilter(marketAlertFilter); + paymentAccountsComboBox.getSelectionModel().clearSelection(); + } + + private void onManageMarketAlerts() { + new ManageMarketAlertsWindow(marketAlerts, formatter) + .onClose(this::updateMarketAlertFields) + .show(); + } + + // Price alerts + private void onSelectedTradeCurrency() { + TradeCurrency selectedItem = currencyComboBox.getSelectionModel().getSelectedItem(); + if (selectedItem != null) { + selectedPriceAlertTradeCurrency = selectedItem.getCode(); + boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(selectedPriceAlertTradeCurrency); + priceAlertHighInputTextField.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); + priceAlertLowInputTextField.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); + } else { + selectedPriceAlertTradeCurrency = null; + } + updatePriceAlertFields(); + } + + private void onSetPriceAlert() { + if (arePriceAlertInputsValid()) { + String code = selectedPriceAlertTradeCurrency; + long high = getPriceAsLong(priceAlertHighInputTextField); + long low = getPriceAsLong(priceAlertLowInputTextField); + if (high > 0 && low > 0) + user.setPriceAlertFilter(new PriceAlertFilter(code, high, low)); + updatePriceAlertFields(); + } + } + + private void onRemovePriceAlert() { + user.removePriceAlertFilter(); + fillPriceAlertFields(); + updatePriceAlertFields(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Create views /////////////////////////////////////////////////////////////////////////////////////////// @@ -195,46 +391,8 @@ private void createSetupFields() { Layout.FIRST_ROW_DISTANCE); webCamButton = tuple.second; webCamButton.setDefaultButton(true); - webCamButton.setOnAction((event) -> { - webCamButton.setDisable(true); - log.info("Start WebCamLauncher"); - new WebCamLauncher(webCam -> { - log.info("webCam available"); - webCamWindow = new WebCamWindow(webCam.getViewSize().width, webCam.getViewSize().height) - .onClose(() -> { - webCamButton.setDisable(false); - qrCodeReader.close(); - }); - webCamWindow.show(); - - qrCodeReader = new QrCodeReader(webCam, webCamWindow.getImageView(), qrCode -> { - log.info("Qr code available"); - webCamWindow.hide(); - webCamButton.setDisable(false); - reset(); - tokenInputTextField.setText(qrCode); - updatePriceAlertInputs(); - }); - }, throwable -> { - if (throwable instanceof NoWebCamFoundException) { - new Popup<>().warning(Res.get("account.notifications.noWebCamFound.warning")).show(); - webCamButton.setDisable(false); - setPairingTokenFieldsVisible(); - noWebCamButton.setManaged(false); - noWebCamButton.setVisible(false); - } else { - log.error(throwable.toString()); - new Popup<>().error(throwable.toString()).show(); - } - }); - }); noWebCamButton = tuple.third; - noWebCamButton.setOnAction(e -> { - setPairingTokenFieldsVisible(); - noWebCamButton.setManaged(false); - noWebCamButton.setVisible(false); - }); Tuple2 tuple2 = FormBuilder.addLabelInputTextField(root, ++gridRow, Res.get("account.notifications.email.label")); @@ -252,60 +410,11 @@ private void createSetupFields() { testMsgButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.testMsg.label"), Res.get("account.notifications.testMsg.title")).second; testMsgButton.setDefaultButton(false); - testMsgButton.setOnAction(event -> { - MobileMessage message = null; - List messages = null; - switch (testMsgCounter) { - case 0: - message = MyOfferTakenEvents.getTestMsg(); - break; - case 1: - messages = TradeEvents.getTestMessages(); - break; - case 2: - message = DisputeMsgEvents.getTestMsg(); - break; - case 3: - message = PriceAlert.getTestMsg(); - break; - case 4: - default: - message = MarketAlerts.getTestMsg(); - break; - } - testMsgCounter++; - if (testMsgCounter > 4) - testMsgCounter = 0; - - try { - if (message != null) { - mobileNotificationService.sendMessage(message, useSoundCheckBox.isSelected()); - } else if (messages != null) { - messages.forEach(msg -> { - try { - mobileNotificationService.sendMessage(msg, useSoundCheckBox.isSelected()); - } catch (Exception e) { - e.printStackTrace(); - } - }); - } - } catch (Exception e) { - new Popup<>().error(e.toString()).show(); - } - }); eraseButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.erase.label"), Res.get("account.notifications.erase.title")).second; eraseButton.setId("notification-erase-button"); - eraseButton.setOnAction((event) -> { - try { - mobileNotificationService.sendEraseMessage(); - reset(); - } catch (Exception e) { - new Popup<>().error(e.toString()).show(); - } - }); } private void createSettingsFields() { @@ -337,6 +446,7 @@ private void createSettingsFields() { marketCheckBoxListener = (observable, oldValue, newValue) -> { mobileNotificationService.getUseMarketNotificationsProperty().set(newValue); preferences.setUseMarketNotifications(newValue); + updateMarketAlertFields(); }; priceCheckBox = FormBuilder.addLabelCheckBox(root, ++gridRow, Res.get("account.notifications.price.label")).second; @@ -344,14 +454,78 @@ private void createSettingsFields() { priceCheckBoxListener = (observable, oldValue, newValue) -> { mobileNotificationService.getUsePriceNotificationsProperty().set(newValue); preferences.setUsePriceNotifications(newValue); - updatePriceAlertInputs(); + updatePriceAlertFields(); }; } - private void createPriceAlertFields() { - FormBuilder.addTitledGroupBg(root, ++gridRow, 3, Res.get("account.notifications.priceAlert.title"), + private void createMarketAlertFields() { + FormBuilder.addTitledGroupBg(root, ++gridRow, 3, Res.get("account.notifications.marketAlert.title"), Layout.GROUP_DISTANCE); - currencyComboBox = FormBuilder.addLabelComboBox(root, gridRow, Res.get("list.currency.select"), Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + paymentAccountsComboBox = FormBuilder.addLabelComboBox(root, gridRow, + Res.getWithCol("account.notifications.marketAlert.selectPaymentAccount"), + Layout.FIRST_ROW_AND_GROUP_DISTANCE).second; + paymentAccountsComboBox.setPromptText(Res.get("shared.select")); + paymentAccountsComboBox.setConverter(new StringConverter() { + @Override + public String toString(PaymentAccount paymentAccount) { + return paymentAccount.getAccountName(); + } + + @Override + public PaymentAccount fromString(String string) { + return null; + } + }); + + offerTypeRadioButtonsToggleGroup = new ToggleGroup(); + Tuple3 tuple = FormBuilder.addLabelRadioButtonRadioButton(root, ++gridRow, + offerTypeRadioButtonsToggleGroup, Res.getWithCol("account.notifications.marketAlert.offerType.label"), + Res.get("account.notifications.marketAlert.offerType.buy"), + Res.get("account.notifications.marketAlert.offerType.sell")); + buyOffersRadioButton = tuple.second; + sellOffersRadioButton = tuple.third; + offerTypeListener = (observable, oldValue, newValue) -> { + marketAlertTriggerInputTextField.clear(); + marketAlertTriggerInputTextField.resetValidation(); + updateMarketAlertFields(); + }; + InfoInputTextField infoInputTextField = FormBuilder.addLabelInfoInputTextField(root, ++gridRow, + Res.getWithCol("account.notifications.marketAlert.trigger")).second; + marketAlertTriggerInputTextField = infoInputTextField.getInputTextField(); + marketAlertTriggerInputTextField.setPromptText(Res.get("account.notifications.marketAlert.trigger.prompt")); + PercentageNumberValidator validator = new PercentageNumberValidator(); + validator.setMaxValue(50D); + marketAlertTriggerInputTextField.setValidator(validator); + infoInputTextField.setContentForInfoPopOver(createMarketAlertPriceInfoPopupLabel(Res.get("account.notifications.marketAlert.trigger.info"))); + infoInputTextField.setIconsRightAligned(); + + marketAlertTriggerListener = (observable, oldValue, newValue) -> { + updateMarketAlertFields(); + }; + marketAlertTriggerFocusListener = (observable, oldValue, newValue) -> { + if (oldValue && !newValue) { + try { + double percentAsDouble = formatter.parsePercentStringToDouble(marketAlertTriggerInputTextField.getText()) * 100; + marketAlertTriggerInputTextField.setText(formatter.formatRoundedDoubleWithPrecision(percentAsDouble, 2) + "%"); + } catch (Throwable ignore) { + } + + updateMarketAlertFields(); + } + }; + + Tuple2 buttonTuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow, + Res.get("account.notifications.marketAlert.addButton"), + Res.get("account.notifications.marketAlert.manageAlertsButton")); + addMarketAlertButton = buttonTuple.first; + manageAlertsButton = buttonTuple.second; + } + + private void createPriceAlertFields() { + FormBuilder.addTitledGroupBg(root, ++gridRow, 3, + Res.get("account.notifications.priceAlert.title"), 20); + currencyComboBox = FormBuilder.addLabelComboBox(root, gridRow, + Res.getWithCol("list.currency.select"), 40).second; currencyComboBox.setPromptText(Res.get("list.currency.select")); currencyComboBox.setConverter(new StringConverter() { @Override @@ -366,13 +540,13 @@ public TradeCurrency fromString(String string) { }); priceAlertHighInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, - Res.get("account.notifications.priceAlert.high.label")).second; + Res.getWithCol("account.notifications.priceAlert.high.label")).second; priceAlertHighListener = (observable, oldValue, newValue) -> { long priceAlertHighTextFieldValue = getPriceAsLong(priceAlertHighInputTextField); long priceAlertLowTextFieldValue = getPriceAsLong(priceAlertLowInputTextField); if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { if (priceAlertHighTextFieldValue > priceAlertLowTextFieldValue) - updatePriceAlertInputs(); + updatePriceAlertFields(); } }; priceAlertHighFocusListener = (observable, oldValue, newValue) -> { @@ -383,19 +557,22 @@ public TradeCurrency fromString(String string) { if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { if (priceAlertHighTextFieldValue <= priceAlertLowTextFieldValue) { new Popup<>().warning(Res.get("account.notifications.priceAlert.warning.highPriceTooLow")).show(); - UserThread.execute(() -> priceAlertHighInputTextField.clear()); + UserThread.execute(() -> { + priceAlertHighInputTextField.clear(); + updatePriceAlertFields(); + }); } } } }; priceAlertLowInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow, - Res.get("account.notifications.priceAlert.low.label")).second; + Res.getWithCol("account.notifications.priceAlert.low.label")).second; priceAlertLowListener = (observable, oldValue, newValue) -> { long priceAlertHighTextFieldValue = getPriceAsLong(priceAlertHighInputTextField); long priceAlertLowTextFieldValue = getPriceAsLong(priceAlertLowInputTextField); if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { if (priceAlertLowTextFieldValue < priceAlertHighTextFieldValue) - updatePriceAlertInputs(); + updatePriceAlertFields(); } }; priceAlertLowFocusListener = (observable, oldValue, newValue) -> { @@ -405,14 +582,17 @@ public TradeCurrency fromString(String string) { if (priceAlertLowTextFieldValue != 0 && priceAlertHighTextFieldValue != 0) { if (priceAlertLowTextFieldValue >= priceAlertHighTextFieldValue) { new Popup<>().warning(Res.get("account.notifications.priceAlert.warning.lowerPriceTooHigh")).show(); - UserThread.execute(() -> priceAlertLowInputTextField.clear()); + UserThread.execute(() -> { + priceAlertLowInputTextField.clear(); + updatePriceAlertFields(); + }); } } }; Tuple2 tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow, Res.get("account.notifications.priceAlert.setButton"), - Res.get("account.notifications.priceAlert.clearButton")); + Res.get("account.notifications.priceAlert.removeButton")); setPriceAlertButton = tuple.first; removePriceAlertButton = tuple.second; @@ -424,28 +604,30 @@ public TradeCurrency fromString(String string) { priceFeedServiceListener = (observable, oldValue, newValue) -> { UserThread.execute(() -> { fillPriceAlertFields(); - updatePriceAlertInputs(); + updatePriceAlertFields(); }); }; } /////////////////////////////////////////////////////////////////////////////////////////// - // Setup/Settings + // Private /////////////////////////////////////////////////////////////////////////////////////////// + // Setup/Settings private void applyKeyAndToken(String keyAndToken) { if (keyAndToken != null && !keyAndToken.isEmpty()) { boolean isValid = mobileNotificationService.applyKeyAndToken(keyAndToken); if (isValid) { - updateDisableState(false); + setDisableForSetupFields(false); setPairingTokenFieldsVisible(); - updatePriceAlertInputs(); + updateMarketAlertFields(); + updatePriceAlertFields(); } } } - private void updateDisableState(boolean disable) { + private void setDisableForSetupFields(boolean disable) { testMsgButton.setDisable(disable); eraseButton.setDisable(disable); @@ -453,7 +635,6 @@ private void updateDisableState(boolean disable) { tradeCheckBox.setDisable(disable); marketCheckBox.setDisable(disable); priceCheckBox.setDisable(disable); - } private void setPairingTokenFieldsVisible() { @@ -466,17 +647,44 @@ private void setPairingTokenFieldsVisible() { private void reset() { mobileNotificationService.reset(); tokenInputTextField.clear(); - updateDisableState(true); + setDisableForSetupFields(true); eraseButton.setDisable(true); testMsgButton.setDisable(true); - removeAlert(); + onRemovePriceAlert(); + new ArrayList<>(marketAlerts.getMarketAlertFilters()).forEach(marketAlerts::removeMarketAlertFilter); + } - /////////////////////////////////////////////////////////////////////////////////////////// - // PriceAlert - /////////////////////////////////////////////////////////////////////////////////////////// + // Market alerts + private Label createMarketAlertPriceInfoPopupLabel(String text) { + final Label label = new Label(text); + label.setPrefWidth(300); + label.setWrapText(true); + label.setPadding(new Insets(10)); + return label; + } + + private void updateMarketAlertFields() { + boolean setupConfirmationSent = mobileNotificationService.isSetupConfirmationSent(); + boolean selected = marketCheckBox.isSelected(); + boolean disabled = !selected || !setupConfirmationSent; + boolean isPaymentAccountSelected = paymentAccountsComboBox.getSelectionModel().getSelectedItem() != null; + boolean isOfferTypeSelected = offerTypeRadioButtonsToggleGroup.getSelectedToggle() != null; + boolean isTriggerValueValid = marketAlertTriggerInputTextField.getValidator() != null && + marketAlertTriggerInputTextField.getValidator().validate(marketAlertTriggerInputTextField.getText()).isValid; + boolean allInputsValid = isPaymentAccountSelected && isOfferTypeSelected && isTriggerValueValid; + + paymentAccountsComboBox.setDisable(disabled); + buyOffersRadioButton.setDisable(disabled); + sellOffersRadioButton.setDisable(disabled); + marketAlertTriggerInputTextField.setDisable(disabled); + addMarketAlertButton.setDisable(disabled || !allInputsValid); + manageAlertsButton.setDisable(disabled || marketAlerts.getMarketAlertFilters().isEmpty()); + } + + // PriceAlert private void fillPriceAlertFields() { PriceAlertFilter priceAlertFilter = user.getPriceAlertFilter(); if (priceAlertFilter != null) { @@ -484,7 +692,7 @@ private void fillPriceAlertFields() { Optional optionalTradeCurrency = CurrencyUtil.getTradeCurrency(currencyCode); if (optionalTradeCurrency.isPresent()) { currencyComboBox.getSelectionModel().select(optionalTradeCurrency.get()); - setSelectedPriceAlertTradeCurrency(); + onSelectedTradeCurrency(); priceAlertHighInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode)); priceAlertLowInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode)); @@ -500,7 +708,7 @@ private void fillPriceAlertFields() { } } - private void updatePriceAlertInputs() { + private void updatePriceAlertFields() { boolean setupConfirmationSent = mobileNotificationService.isSetupConfirmationSent(); boolean selected = priceCheckBox.isSelected(); boolean disable = !setupConfirmationSent || @@ -513,29 +721,13 @@ private void updatePriceAlertInputs() { selectedPriceAlertTradeCurrency != null) { valueSameAsFilter = priceAlertFilter.getHigh() == getPriceAsLong(priceAlertHighInputTextField) && priceAlertFilter.getLow() == getPriceAsLong(priceAlertLowInputTextField) && - priceAlertFilter.getCurrencyCode().equals(selectedPriceAlertTradeCurrency.getCode()); + priceAlertFilter.getCurrencyCode().equals(selectedPriceAlertTradeCurrency); } setPriceAlertButton.setDisable(disable || !arePriceAlertInputsValid() || valueSameAsFilter); removePriceAlertButton.setDisable(disable || priceAlertFilter == null); currencyComboBox.setDisable(disable); } - private void removeAlert() { - user.removePriceAlertFilter(); - fillPriceAlertFields(); - updatePriceAlertInputs(); - } - - private void setSelectedPriceAlertTradeCurrency() { - selectedPriceAlertTradeCurrency = currencyComboBox.getSelectionModel().getSelectedItem(); - if (selectedPriceAlertTradeCurrency != null) { - boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(selectedPriceAlertTradeCurrency.getCode()); - priceAlertHighInputTextField.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); - priceAlertLowInputTextField.setValidator(isCryptoCurrency ? new AltcoinValidator() : new FiatPriceValidator()); - } - updatePriceAlertInputs(); - } - private boolean arePriceAlertInputsValid() { return selectedPriceAlertTradeCurrency != null && isPriceInputValid(priceAlertHighInputTextField).isValid && @@ -555,7 +747,7 @@ private long getPriceAsLong(InputTextField inputTextField) { String inputValue = inputTextField.getText(); if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) { double priceAsDouble = formatter.parseNumberStringToDouble(inputValue); - String currencyCode = selectedPriceAlertTradeCurrency.getCode(); + String currencyCode = selectedPriceAlertTradeCurrency; int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : 2; // We want to use the converted value not the inout value as we apply the converted value at focus out. @@ -576,13 +768,14 @@ private void applyPriceFormatting(InputTextField inputTextField) { String inputValue = inputTextField.getText(); if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) { double priceAsDouble = formatter.parseNumberStringToDouble(inputValue); - String currencyCode = selectedPriceAlertTradeCurrency.getCode(); + String currencyCode = selectedPriceAlertTradeCurrency; int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ? Altcoin.SMALLEST_UNIT_EXPONENT : 2; String stringValue = formatter.formatRoundedDoubleWithPrecision(priceAsDouble, precision); inputTextField.setText(stringValue); } } catch (Throwable ignore) { + updatePriceAlertFields(); } } } diff --git a/src/main/java/bisq/desktop/main/offer/MutableOfferView.java b/src/main/java/bisq/desktop/main/offer/MutableOfferView.java index 3f119e1c493..3d8aad47f65 100644 --- a/src/main/java/bisq/desktop/main/offer/MutableOfferView.java +++ b/src/main/java/bisq/desktop/main/offer/MutableOfferView.java @@ -1149,7 +1149,7 @@ private void addAmountPriceFields() { HBox priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first; marketBasedPriceInfoInputTextField = priceAsPercentageTuple.second; - marketBasedPriceTextField = marketBasedPriceInfoInputTextField.getTextField(); + marketBasedPriceTextField = marketBasedPriceInfoInputTextField.getInputTextField(); marketBasedPriceTextField.setPrefWidth(200); editOfferElements.add(marketBasedPriceTextField); marketBasedPriceLabel = priceAsPercentageTuple.third; diff --git a/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java b/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java index a8fc567f933..4d86d1654c8 100644 --- a/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java +++ b/src/main/java/bisq/desktop/main/overlays/windows/ShowWalletDataWindow.java @@ -31,15 +31,10 @@ import javafx.scene.control.TextArea; import javafx.scene.input.KeyCode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static bisq.desktop.util.FormBuilder.addLabelCheckBox; import static bisq.desktop.util.FormBuilder.addLabelTextArea; public class ShowWalletDataWindow extends Overlay { - private static final Logger log = LoggerFactory.getLogger(ShowWalletDataWindow.class); - private final WalletsManager walletsManager; diff --git a/src/main/java/bisq/desktop/util/FormBuilder.java b/src/main/java/bisq/desktop/util/FormBuilder.java index e844c70030a..942eb13082c 100644 --- a/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/src/main/java/bisq/desktop/util/FormBuilder.java @@ -362,6 +362,27 @@ public static Tuple2 addLabelInputTextField(GridPane grid } + /////////////////////////////////////////////////////////////////////////////////////////// + // Label + InfoInputTextField + /////////////////////////////////////////////////////////////////////////////////////////// + + public static Tuple2 addLabelInfoInputTextField(GridPane gridPane, int rowIndex, String title) { + return addLabelInfoInputTextField(gridPane, rowIndex, title, 0); + } + + public static Tuple2 addLabelInfoInputTextField(GridPane gridPane, int rowIndex, String title, double top) { + Label label = addLabel(gridPane, rowIndex, title, top); + + InfoInputTextField inputTextField = new InfoInputTextField(); + GridPane.setRowIndex(inputTextField, rowIndex); + GridPane.setColumnIndex(inputTextField, 1); + GridPane.setMargin(inputTextField, new Insets(top, 0, 0, 0)); + gridPane.getChildren().add(inputTextField); + + return new Tuple2<>(label, inputTextField); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Label + PasswordField /////////////////////////////////////////////////////////////////////////////////////////// @@ -612,6 +633,7 @@ public static Tuple3 addLabelRadioButtonRadioBu GridPane.setRowIndex(hBox, rowIndex); GridPane.setColumnIndex(hBox, 1); + GridPane.setMargin(hBox, new Insets(-5, 0, 0, 0)); gridPane.getChildren().add(hBox); return new Tuple3<>(label, radioButton1, radioButton2); @@ -1150,7 +1172,7 @@ public static Tuple3 getEditableValueCurrencyBox(St public static Tuple3 getEditableValueCurrencyBoxWithInfo(String promptText) { InfoInputTextField infoInputTextField = new InfoInputTextField(); - InputTextField input = infoInputTextField.getTextField(); + InputTextField input = infoInputTextField.getInputTextField(); input.setPrefWidth(170); input.setAlignment(Pos.CENTER_RIGHT); input.setId("text-input-with-currency-text-field"); diff --git a/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java b/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java new file mode 100644 index 00000000000..5b2263ecf91 --- /dev/null +++ b/src/main/java/bisq/desktop/util/validation/PercentageNumberValidator.java @@ -0,0 +1,53 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.util.validation; + +import bisq.core.locale.Res; + +import lombok.Setter; + +import javax.annotation.Nullable; + +public class PercentageNumberValidator extends NumberValidator { + @Nullable + @Setter + protected Double maxValue; // Keep it Double as we check for null + + @Override + public ValidationResult validate(String input) { + ValidationResult result = validateIfNotEmpty(input); + if (result.isValid) { + input = input.replace("%", ""); + input = cleanInput(input); + result = validateIfNumber(input); + } + return result.and(validateIfNotExceedsMaxValue(input)); + } + + private ValidationResult validateIfNotExceedsMaxValue(String input) { + try { + double value = Double.parseDouble(input); + if (maxValue != null && value > maxValue) + return new ValidationResult(false, Res.get("validation.inputTooLarge", maxValue)); + else + return new ValidationResult(true); + } catch (Throwable t) { + return new ValidationResult(false, Res.get("validation.invalidInput", t.getMessage())); + } + } +} From a8b5b1e1d8c8f13fbf20a0239a9648e810a27ed8 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 14 Aug 2018 18:27:50 +0200 Subject: [PATCH 19/20] Add download link. Filter own offers --- .../MobileNotificationsView.java | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java b/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java index 7f321dc55fb..0a56003e531 100644 --- a/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java +++ b/src/main/java/bisq/desktop/main/account/content/notifications/MobileNotificationsView.java @@ -24,6 +24,7 @@ import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.WebCamWindow; import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.desktop.util.validation.AltcoinValidator; import bisq.desktop.util.validation.FiatPriceValidator; @@ -95,8 +96,8 @@ public class MobileNotificationsView extends ActivatableView { private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox; private ComboBox currencyComboBox; private ComboBox paymentAccountsComboBox; - private Button webCamButton, noWebCamButton, eraseButton, testMsgButton, setPriceAlertButton, - removePriceAlertButton, addMarketAlertButton, manageAlertsButton; + private Button downloadButton, webCamButton, noWebCamButton, eraseButton, setPriceAlertButton, + removePriceAlertButton, addMarketAlertButton, manageAlertsButton /*,testMsgButton*/; private ChangeListener useSoundCheckBoxListener, tradeCheckBoxListener, marketCheckBoxListener, priceCheckBoxListener, priceAlertHighFocusListener, priceAlertLowFocusListener, marketAlertTriggerFocusListener; @@ -143,9 +144,10 @@ public void initialize() { protected void activate() { // setup tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener); + downloadButton.setOnAction(e -> onDownload()); webCamButton.setOnAction(e -> onOpenWebCam()); noWebCamButton.setOnAction(e -> onNoWebCam()); - testMsgButton.setOnAction(e -> onSendTestMsg()); + // testMsgButton.setOnAction(e -> onSendTestMsg()); eraseButton.setOnAction(e -> onErase()); // settings @@ -182,7 +184,7 @@ protected void activate() { setPairingTokenFieldsVisible(); } else { eraseButton.setDisable(true); - testMsgButton.setDisable(true); + //testMsgButton.setDisable(true); } setDisableForSetupFields(!mobileNotificationService.isSetupConfirmationSent()); updateMarketAlertFields(); @@ -194,9 +196,10 @@ protected void activate() { protected void deactivate() { // setup tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener); + downloadButton.setOnAction(null); webCamButton.setOnAction(null); noWebCamButton.setOnAction(null); - testMsgButton.setOnAction(null); + //testMsgButton.setOnAction(null); eraseButton.setOnAction(null); // settings @@ -230,6 +233,10 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// // Setup + private void onDownload() { + GUIUtil.openWebPage("https://bisq.network/downloads"); + } + private void onOpenWebCam() { webCamButton.setDisable(true); log.info("Start WebCamLauncher"); @@ -385,13 +392,14 @@ private void onRemovePriceAlert() { private void createSetupFields() { FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title")); - Tuple3 tuple = FormBuilder.addLabel2Buttons(root, gridRow, - Res.get("account.notifications.webcam.label"), - Res.get("account.notifications.webcam.button"), Res.get("account.notifications.noWebcam.button"), - Layout.FIRST_ROW_DISTANCE); - webCamButton = tuple.second; - webCamButton.setDefaultButton(true); + downloadButton = FormBuilder.addLabelButton(root, gridRow, + Res.getWithCol("account.notifications.download.label"), Res.get("account.notifications.download.button"), + Layout.FIRST_ROW_DISTANCE).second; + Tuple3 tuple = FormBuilder.addLabel2Buttons(root, ++gridRow, + Res.getWithCol("account.notifications.webcam.label"), + Res.get("account.notifications.webcam.button"), Res.get("account.notifications.noWebcam.button"), 0); + webCamButton = tuple.second; noWebCamButton = tuple.third; Tuple2 tuple2 = FormBuilder.addLabelInputTextField(root, ++gridRow, @@ -407,9 +415,9 @@ private void createSetupFields() { tokenInputTextField.setManaged(false); tokenInputTextField.setVisible(false); - testMsgButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.testMsg.label"), + /*testMsgButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.testMsg.label"), Res.get("account.notifications.testMsg.title")).second; - testMsgButton.setDefaultButton(false); + testMsgButton.setDefaultButton(false);*/ eraseButton = FormBuilder.addLabelButton(root, ++gridRow, Res.get("account.notifications.erase.label"), @@ -628,7 +636,7 @@ private void applyKeyAndToken(String keyAndToken) { } private void setDisableForSetupFields(boolean disable) { - testMsgButton.setDisable(disable); + // testMsgButton.setDisable(disable); eraseButton.setDisable(disable); useSoundCheckBox.setDisable(disable); @@ -649,7 +657,7 @@ private void reset() { tokenInputTextField.clear(); setDisableForSetupFields(true); eraseButton.setDisable(true); - testMsgButton.setDisable(true); + //testMsgButton.setDisable(true); onRemovePriceAlert(); new ArrayList<>(marketAlerts.getMarketAlertFilters()).forEach(marketAlerts::removeMarketAlertFilter); From 649977258046c2116d88e2b88ba53e1698e22811 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 15 Aug 2018 15:30:05 +0200 Subject: [PATCH 20/20] Fix version number --- build.gradle | 14 ++++++-------- package/linux/32bitBuild.sh | 2 +- package/linux/64bitBuild.sh | 2 +- package/linux/Dockerfile | 2 +- package/linux/rpm.sh | 2 +- package/osx/Info.plist | 2 +- package/osx/create_app.sh | 10 +++++----- package/osx/finalize.sh | 2 +- package/windows/32bitBuild.bat | 2 +- package/windows/64bitBuild.bat | 2 +- package/windows/Bisq.iss | 2 +- 11 files changed, 20 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index a5d2218ab0c..561f81ac432 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,5 @@ buildscript { repositories { - mavenLocal() jcenter() } dependencies { @@ -16,7 +15,7 @@ apply plugin: 'witness' apply plugin: 'com.github.johnrengelman.shadow' group = 'network.bisq' -version = '0.8.0' +version = '0.7.1-SNAPSHOT' sourceCompatibility = 1.8 @@ -34,7 +33,6 @@ tasks.withType(JavaCompile) { sourceSets.main.resources.srcDirs += ['src/main/java'] // to copy fxml and css files repositories { - mavenLocal() jcenter() maven { url 'https://jitpack.io' } maven { url 'https://raw.githubusercontent.com/JesusMcCloud/tor-binary/master/release/' } @@ -42,9 +40,9 @@ repositories { } dependencies { - compile 'network.bisq:bisq-p2p:0.8.0' - compile 'network.bisq:bisq-core:0.8.0' - compile 'network.bisq:bisq-common:0.8.0' + compile 'network.bisq:bisq-p2p:-SNAPSHOT' + compile 'network.bisq:bisq-core:-SNAPSHOT' + compile 'network.bisq:bisq-common:-SNAPSHOT' compile 'org.controlsfx:controlsfx:8.0.6_20' compile 'org.reactfx:reactfx:2.0-M3' compile 'net.glxn:qrgen:1.3' @@ -56,7 +54,7 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.16.16' - //annotationProcessor 'org.projectlombok:lombok:1.16.16' + annotationProcessor 'org.projectlombok:lombok:1.16.16' testCompile('org.mockito:mockito-core:2.8.9') { exclude(module: 'objenesis') } @@ -66,7 +64,7 @@ dependencies { testCompile 'org.springframework:spring-test:4.3.6.RELEASE' testCompile 'com.natpryce:make-it-easy:4.0.1' testCompileOnly 'org.projectlombok:lombok:1.16.16' - //testAnnotationProcessor 'org.projectlombok:lombok:1.16.16' + testAnnotationProcessor 'org.projectlombok:lombok:1.16.16' } build.dependsOn installDist diff --git a/package/linux/32bitBuild.sh b/package/linux/32bitBuild.sh index bb8a3dcf88c..5ccbd8b53f4 100644 --- a/package/linux/32bitBuild.sh +++ b/package/linux/32bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e # Edit version -version=0.8.0 +version=0.7.1 dir="/media/sf_vm_shared_ubuntu14_32bit" diff --git a/package/linux/64bitBuild.sh b/package/linux/64bitBuild.sh index 751115c24dd..43b260ddcd6 100644 --- a/package/linux/64bitBuild.sh +++ b/package/linux/64bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e # Edit version -version=0.8.0 +version=0.7.1 dir="/media/sf_vm_shared_ubuntu" diff --git a/package/linux/Dockerfile b/package/linux/Dockerfile index 0d79a422dbd..cf80e3c12c9 100644 --- a/package/linux/Dockerfile +++ b/package/linux/Dockerfile @@ -8,7 +8,7 @@ # pull base image FROM openjdk:8-jdk -ENV version 0.8.0 +ENV version 0.7.1 RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* && apt-get install -y vim fakeroot diff --git a/package/linux/rpm.sh b/package/linux/rpm.sh index 109375f5cdb..4260517a6e9 100644 --- a/package/linux/rpm.sh +++ b/package/linux/rpm.sh @@ -2,7 +2,7 @@ ## From https://github.com/bisq-network/bisq-desktop/issues/401#issuecomment-372091261 -version=0.8.0 +version=0.7.1 alien -r -g /home/$USER/Desktop/Bisq-64bit-$version.deb find bisq-$version -type f | while read LIB; do LDDOUT=$(ldd $LIB 2>&1); LDDRETVAL=$?;if [ \( -z "${LDDOUT%%*you do not have execution permission for*}" \) -a \( $LDDRETVAL -eq 0 \) ]; then chmod -v +x $LIB;fi;done diff --git a/package/osx/Info.plist b/package/osx/Info.plist index 452e30fa2e6..45dfa4ca1b2 100644 --- a/package/osx/Info.plist +++ b/package/osx/Info.plist @@ -40,7 +40,7 @@ JVMAppClasspath JVMMainJarName - Bisq-0.8.0.jar + Bisq-0.7.1.jar JVMPreferencesID bisq JVMOptions diff --git a/package/osx/create_app.sh b/package/osx/create_app.sh index c4da4377939..0d1c6c1bbb9 100755 --- a/package/osx/create_app.sh +++ b/package/osx/create_app.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e -version="0.8.0" +version="0.7.1" ./gradlew --include-build ../common --include-build ../assets --include-build ../p2p --include-build ../core build -x test shadowJar @@ -82,18 +82,18 @@ $JAVA_HOME/bin/javapackager \ -Bicon=package/osx/Bisq.icns \ -Bruntime="$JAVA_HOME/jre" \ -native dmg \ - -name Bisq_notifications \ - -title Bisq_notifications \ + -name Bisq \ + -title Bisq \ -vendor Bisq \ -outdir deploy \ -srcfiles "deploy/Bisq-$version.jar" \ -appclass bisq.desktop.app.BisqAppMain \ - -outfile Bisq_notifications + -outfile Bisq rm "deploy/Bisq.html" rm "deploy/Bisq.jnlp" -mv "deploy/bundles/Bisq_notifications-$version.dmg" "deploy/Bisq_notifications-$version.dmg" +mv "deploy/bundles/Bisq-$version.dmg" "deploy/Bisq-$version.dmg" rm -r "deploy/bundles" open deploy diff --git a/package/osx/finalize.sh b/package/osx/finalize.sh index a02db45baf5..60ff6ca268c 100755 --- a/package/osx/finalize.sh +++ b/package/osx/finalize.sh @@ -2,7 +2,7 @@ cd ../../ -version="0.8.0" +version="0.7.1" target_dir="releases/$version" diff --git a/package/windows/32bitBuild.bat b/package/windows/32bitBuild.bat index ab448adf96c..87636dbc7c2 100644 --- a/package/windows/32bitBuild.bat +++ b/package/windows/32bitBuild.bat @@ -5,7 +5,7 @@ :: 32 bit build :: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php) -SET version=0.8.0 +SET version=0.7.1 :: Private setup SET outdir=\\VBOXSVR\vm_shared_windows_32bit diff --git a/package/windows/64bitBuild.bat b/package/windows/64bitBuild.bat index 69116aae1cb..3d6e2afa972 100644 --- a/package/windows/64bitBuild.bat +++ b/package/windows/64bitBuild.bat @@ -5,7 +5,7 @@ :: 64 bit build :: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php) -SET version=0.8.0 +SET version=0.7.1 :: Private setup SET outdir=\\VBOXSVR\vm_shared_windows diff --git a/package/windows/Bisq.iss b/package/windows/Bisq.iss index 850226b08b7..6bf84746048 100755 --- a/package/windows/Bisq.iss +++ b/package/windows/Bisq.iss @@ -3,7 +3,7 @@ [Setup] AppId={{bisq}} AppName=Bisq -AppVersion=0.8.0 +AppVersion=0.7.1 AppVerName=Bisq AppPublisher=Bisq AppComments=Bisq