mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 10:22:18 +01:00
Merge pull request #1624 from ManfredKarrer/notifications
Add mobile notifications
This commit is contained in:
commit
276d526e18
@ -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') {
|
||||
|
@ -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 */
|
||||
@ -1337,3 +1338,13 @@ textfield */
|
||||
-fx-background-insets: 0, 0 0 0 0
|
||||
}
|
||||
|
||||
/********************************************************************************************************************
|
||||
* *
|
||||
* Notifications *
|
||||
* *
|
||||
********************************************************************************************************************/
|
||||
|
||||
#notification-erase-button {
|
||||
-fx-background-color: -bs-soft-red;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
@ -32,14 +32,19 @@ import javafx.beans.property.StringProperty;
|
||||
|
||||
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 class InfoInputTextField extends AnchorPane {
|
||||
|
||||
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 class InfoInputTextField extends AnchorPane {
|
||||
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 @@ public class InfoInputTextField extends AnchorPane {
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void showPopOver(Node node) {
|
||||
node.getStyleClass().add("default-text");
|
||||
|
||||
@ -132,19 +162,4 @@ public class InfoInputTextField extends AnchorPane {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ import javafx.beans.value.ChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
@FxmlView
|
||||
public class AccountView extends ActivatableView<TabPane, AccountViewModel> {
|
||||
public class AccountView extends ActivatableView<TabPane, Void> {
|
||||
|
||||
@FXML
|
||||
Tab accountSettingsTab;
|
||||
@ -67,8 +67,8 @@ public class AccountView extends ActivatableView<TabPane, AccountViewModel> {
|
||||
private EventHandler<KeyEvent> 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;
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<ManageMarketAlertsWindow> {
|
||||
|
||||
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<MarketAlertFilter> 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<MarketAlertFilter> tableView) {
|
||||
marketAlerts.removeMarketAlertFilter(marketAlertFilter);
|
||||
UserThread.execute(() -> tableView.setItems(FXCollections.observableArrayList(marketAlerts.getMarketAlertFilters())));
|
||||
}
|
||||
|
||||
private void setColumns(TableView<MarketAlertFilter> tableView) {
|
||||
TableColumn<MarketAlertFilter, MarketAlertFilter> column;
|
||||
|
||||
column = new AutoTooltipTableColumn<>(Res.get("account.notifications.marketAlert.manageAlerts.header.paymentAccount"));
|
||||
column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<TableColumn<MarketAlertFilter, MarketAlertFilter>, TableCell<MarketAlertFilter, MarketAlertFilter>>() {
|
||||
@Override
|
||||
public TableCell<MarketAlertFilter, MarketAlertFilter> call(TableColumn<MarketAlertFilter, MarketAlertFilter> column) {
|
||||
return new TableCell<MarketAlertFilter, MarketAlertFilter>() {
|
||||
|
||||
@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<TableColumn<MarketAlertFilter, MarketAlertFilter>, TableCell<MarketAlertFilter, MarketAlertFilter>>() {
|
||||
@Override
|
||||
public TableCell<MarketAlertFilter, MarketAlertFilter> call(TableColumn<MarketAlertFilter, MarketAlertFilter> column) {
|
||||
return new TableCell<MarketAlertFilter, MarketAlertFilter>() {
|
||||
|
||||
@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<TableColumn<MarketAlertFilter, MarketAlertFilter>, TableCell<MarketAlertFilter, MarketAlertFilter>>() {
|
||||
@Override
|
||||
public TableCell<MarketAlertFilter, MarketAlertFilter> call(TableColumn<MarketAlertFilter, MarketAlertFilter> column) {
|
||||
return new TableCell<MarketAlertFilter, MarketAlertFilter>() {
|
||||
|
||||
@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<TableColumn<MarketAlertFilter, MarketAlertFilter>, TableCell<MarketAlertFilter, MarketAlertFilter>>() {
|
||||
@Override
|
||||
public TableCell<MarketAlertFilter, MarketAlertFilter> call(TableColumn<MarketAlertFilter, MarketAlertFilter> column) {
|
||||
return new TableCell<MarketAlertFilter, MarketAlertFilter>() {
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<GridPane fx:id="root" fx:controller="bisq.desktop.main.account.content.notifications.MobileNotificationsView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="20.0"
|
||||
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
|
||||
</GridPane>
|
@ -0,0 +1,790 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.account.content.notifications;
|
||||
|
||||
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;
|
||||
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;
|
||||
import bisq.desktop.util.validation.PercentageNumberValidator;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.notifications.MobileMessage;
|
||||
import bisq.core.notifications.MobileNotificationService;
|
||||
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;
|
||||
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;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
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;
|
||||
|
||||
@FxmlView
|
||||
public class MobileNotificationsView extends ActivatableView<GridPane, Void> {
|
||||
private final Preferences preferences;
|
||||
private final User user;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final MarketAlerts marketAlerts;
|
||||
private final MobileNotificationService mobileNotificationService;
|
||||
private final BSFormatter formatter;
|
||||
|
||||
private WebCamWindow webCamWindow;
|
||||
private QrCodeReader qrCodeReader;
|
||||
|
||||
private TextField tokenInputTextField;
|
||||
private Label tokenInputLabel;
|
||||
private InputTextField priceAlertHighInputTextField, priceAlertLowInputTextField, marketAlertTriggerInputTextField;
|
||||
private CheckBox useSoundCheckBox, tradeCheckBox, marketCheckBox, priceCheckBox;
|
||||
private ComboBox<TradeCurrency> currencyComboBox;
|
||||
private ComboBox<PaymentAccount> paymentAccountsComboBox;
|
||||
private Button downloadButton, webCamButton, noWebCamButton, eraseButton, setPriceAlertButton,
|
||||
removePriceAlertButton, addMarketAlertButton, manageAlertsButton /*,testMsgButton*/;
|
||||
|
||||
private ChangeListener<Boolean> useSoundCheckBoxListener, tradeCheckBoxListener, marketCheckBoxListener,
|
||||
priceCheckBoxListener, priceAlertHighFocusListener, priceAlertLowFocusListener, marketAlertTriggerFocusListener;
|
||||
private ChangeListener<String> tokenInputTextFieldListener, priceAlertHighListener, priceAlertLowListener, marketAlertTriggerListener;
|
||||
private ChangeListener<Number> priceFeedServiceListener;
|
||||
|
||||
private int gridRow = 0;
|
||||
private int testMsgCounter = 0;
|
||||
private RadioButton buyOffersRadioButton, sellOffersRadioButton;
|
||||
private ToggleGroup offerTypeRadioButtonsToggleGroup;
|
||||
private ChangeListener<Toggle> offerTypeListener;
|
||||
private String selectedPriceAlertTradeCurrency;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
createSetupFields();
|
||||
createSettingsFields();
|
||||
createMarketAlertFields();
|
||||
createPriceAlertFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
// setup
|
||||
tokenInputTextField.textProperty().addListener(tokenInputTextFieldListener);
|
||||
downloadButton.setOnAction(e -> onDownload());
|
||||
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);
|
||||
|
||||
// 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());
|
||||
|
||||
paymentAccountsComboBox.setItems(FXCollections.observableArrayList(user.getPaymentAccountsAsObservable()));
|
||||
|
||||
// price alert
|
||||
priceAlertHighInputTextField.textProperty().addListener(priceAlertHighListener);
|
||||
priceAlertLowInputTextField.textProperty().addListener(priceAlertLowListener);
|
||||
priceAlertHighInputTextField.focusedProperty().addListener(priceAlertHighFocusListener);
|
||||
priceAlertLowInputTextField.focusedProperty().addListener(priceAlertLowFocusListener);
|
||||
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();
|
||||
updatePriceAlertFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
// setup
|
||||
tokenInputTextField.textProperty().removeListener(tokenInputTextFieldListener);
|
||||
downloadButton.setOnAction(null);
|
||||
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);
|
||||
removePriceAlertButton.setOnAction(null);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// UI events
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Setup
|
||||
private void onDownload() {
|
||||
GUIUtil.openWebPage("https://bisq.network/downloads");
|
||||
}
|
||||
|
||||
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<MobileMessage> 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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void createSetupFields() {
|
||||
FormBuilder.addTitledGroupBg(root, gridRow, 4, Res.get("account.notifications.setup.title"));
|
||||
downloadButton = FormBuilder.addLabelButton(root, gridRow,
|
||||
Res.getWithCol("account.notifications.download.label"), Res.get("account.notifications.download.button"),
|
||||
Layout.FIRST_ROW_DISTANCE).second;
|
||||
|
||||
Tuple3<Label, Button, Button> 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<Label, InputTextField> 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"));
|
||||
tokenInputTextFieldListener = (observable, oldValue, newValue) -> {
|
||||
applyKeyAndToken(newValue);
|
||||
};
|
||||
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);*/
|
||||
|
||||
eraseButton = FormBuilder.addLabelButton(root, ++gridRow,
|
||||
Res.get("account.notifications.erase.label"),
|
||||
Res.get("account.notifications.erase.title")).second;
|
||||
eraseButton.setId("notification-erase-button");
|
||||
}
|
||||
|
||||
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"),
|
||||
"",
|
||||
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")).second;
|
||||
marketCheckBox.setSelected(preferences.isUseMarketNotifications());
|
||||
marketCheckBoxListener = (observable, oldValue, newValue) -> {
|
||||
mobileNotificationService.getUseMarketNotificationsProperty().set(newValue);
|
||||
preferences.setUseMarketNotifications(newValue);
|
||||
updateMarketAlertFields();
|
||||
};
|
||||
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);
|
||||
updatePriceAlertFields();
|
||||
};
|
||||
}
|
||||
|
||||
private void createMarketAlertFields() {
|
||||
FormBuilder.addTitledGroupBg(root, ++gridRow, 3, Res.get("account.notifications.marketAlert.title"),
|
||||
Layout.GROUP_DISTANCE);
|
||||
paymentAccountsComboBox = FormBuilder.<PaymentAccount>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<PaymentAccount>() {
|
||||
@Override
|
||||
public String toString(PaymentAccount paymentAccount) {
|
||||
return paymentAccount.getAccountName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentAccount fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
offerTypeRadioButtonsToggleGroup = new ToggleGroup();
|
||||
Tuple3<Label, RadioButton, RadioButton> 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<Button, Button> 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.<TradeCurrency>addLabelComboBox(root, gridRow,
|
||||
Res.getWithCol("list.currency.select"), 40).second;
|
||||
currencyComboBox.setPromptText(Res.get("list.currency.select"));
|
||||
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
|
||||
@Override
|
||||
public String toString(TradeCurrency currency) {
|
||||
return currency.getNameAndCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradeCurrency fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
priceAlertHighInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow,
|
||||
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)
|
||||
updatePriceAlertFields();
|
||||
}
|
||||
};
|
||||
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();
|
||||
updatePriceAlertFields();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
priceAlertLowInputTextField = FormBuilder.addLabelInputTextField(root, ++gridRow,
|
||||
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)
|
||||
updatePriceAlertFields();
|
||||
}
|
||||
};
|
||||
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();
|
||||
updatePriceAlertFields();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Tuple2<Button, Button> tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow,
|
||||
Res.get("account.notifications.priceAlert.setButton"),
|
||||
Res.get("account.notifications.priceAlert.removeButton"));
|
||||
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) -> {
|
||||
UserThread.execute(() -> {
|
||||
fillPriceAlertFields();
|
||||
updatePriceAlertFields();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Setup/Settings
|
||||
private void applyKeyAndToken(String keyAndToken) {
|
||||
if (keyAndToken != null && !keyAndToken.isEmpty()) {
|
||||
boolean isValid = mobileNotificationService.applyKeyAndToken(keyAndToken);
|
||||
if (isValid) {
|
||||
setDisableForSetupFields(false);
|
||||
setPairingTokenFieldsVisible();
|
||||
updateMarketAlertFields();
|
||||
updatePriceAlertFields();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDisableForSetupFields(boolean disable) {
|
||||
// testMsgButton.setDisable(disable);
|
||||
eraseButton.setDisable(disable);
|
||||
|
||||
useSoundCheckBox.setDisable(disable);
|
||||
tradeCheckBox.setDisable(disable);
|
||||
marketCheckBox.setDisable(disable);
|
||||
priceCheckBox.setDisable(disable);
|
||||
}
|
||||
|
||||
private void setPairingTokenFieldsVisible() {
|
||||
tokenInputLabel.setManaged(true);
|
||||
tokenInputLabel.setVisible(true);
|
||||
tokenInputTextField.setManaged(true);
|
||||
tokenInputTextField.setVisible(true);
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
mobileNotificationService.reset();
|
||||
tokenInputTextField.clear();
|
||||
setDisableForSetupFields(true);
|
||||
eraseButton.setDisable(true);
|
||||
//testMsgButton.setDisable(true);
|
||||
onRemovePriceAlert();
|
||||
new ArrayList<>(marketAlerts.getMarketAlertFilters()).forEach(marketAlerts::removeMarketAlertFilter);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
String currencyCode = priceAlertFilter.getCurrencyCode();
|
||||
Optional<TradeCurrency> optionalTradeCurrency = CurrencyUtil.getTradeCurrency(currencyCode);
|
||||
if (optionalTradeCurrency.isPresent()) {
|
||||
currencyComboBox.getSelectionModel().select(optionalTradeCurrency.get());
|
||||
onSelectedTradeCurrency();
|
||||
|
||||
priceAlertHighInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getHigh() / 10000d, currencyCode));
|
||||
priceAlertLowInputTextField.setText(formatter.formatMarketPrice(priceAlertFilter.getLow() / 10000d, currencyCode));
|
||||
} else {
|
||||
currencyComboBox.getSelectionModel().clearSelection();
|
||||
}
|
||||
} else {
|
||||
priceAlertHighInputTextField.clear();
|
||||
priceAlertLowInputTextField.clear();
|
||||
priceAlertHighInputTextField.resetValidation();
|
||||
priceAlertLowInputTextField.resetValidation();
|
||||
currencyComboBox.getSelectionModel().clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePriceAlertFields() {
|
||||
boolean setupConfirmationSent = mobileNotificationService.isSetupConfirmationSent();
|
||||
boolean selected = priceCheckBox.isSelected();
|
||||
boolean disable = !setupConfirmationSent ||
|
||||
!selected;
|
||||
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() == getPriceAsLong(priceAlertHighInputTextField) &&
|
||||
priceAlertFilter.getLow() == getPriceAsLong(priceAlertLowInputTextField) &&
|
||||
priceAlertFilter.getCurrencyCode().equals(selectedPriceAlertTradeCurrency);
|
||||
}
|
||||
setPriceAlertButton.setDisable(disable || !arePriceAlertInputsValid() || valueSameAsFilter);
|
||||
removePriceAlertButton.setDisable(disable || priceAlertFilter == null);
|
||||
currencyComboBox.setDisable(disable);
|
||||
}
|
||||
|
||||
private boolean arePriceAlertInputsValid() {
|
||||
return selectedPriceAlertTradeCurrency != null &&
|
||||
isPriceInputValid(priceAlertHighInputTextField).isValid &&
|
||||
isPriceInputValid(priceAlertLowInputTextField).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 getPriceAsLong(InputTextField inputTextField) {
|
||||
try {
|
||||
String inputValue = inputTextField.getText();
|
||||
if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) {
|
||||
double priceAsDouble = formatter.parseNumberStringToDouble(inputValue);
|
||||
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.
|
||||
// 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 void applyPriceFormatting(InputTextField inputTextField) {
|
||||
try {
|
||||
String inputValue = inputTextField.getText();
|
||||
if (inputValue != null && !inputValue.isEmpty() && selectedPriceAlertTradeCurrency != null) {
|
||||
double priceAsDouble = formatter.parseNumberStringToDouble(inputValue);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.account.content.notifications;
|
||||
|
||||
public class NoWebCamFoundException extends Throwable {
|
||||
public NoWebCamFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
class QrCodeReader extends Thread {
|
||||
private final Webcam webCam;
|
||||
private final ImageView imageView;
|
||||
private final Consumer<String> resultHandler;
|
||||
private boolean isRunning;
|
||||
|
||||
QrCodeReader(Webcam webCam, ImageView imageView, Consumer<String> 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.account.content.notifications;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.handlers.ExceptionHandler;
|
||||
|
||||
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
|
||||
class WebCamLauncher extends Thread {
|
||||
private final Consumer<Webcam> resultHandler;
|
||||
private final ExceptionHandler exceptionHandler;
|
||||
|
||||
WebCamLauncher(Consumer<Webcam> resultHandler, ExceptionHandler exceptionHandler) {
|
||||
this.resultHandler = resultHandler;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Webcam webCam = Webcam.getDefault(1000); // one second timeout - the default is too long
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<AnchorPane fx:id="root" prefHeight="660.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="bisq.desktop.main.account.settings.AccountSettingsView">
|
||||
|
||||
<VBox fx:id="leftVBox" prefWidth="240" spacing="5" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
|
||||
|
@ -31,6 +31,7 @@ import bisq.desktop.main.account.content.altcoinaccounts.AltCoinAccountsView;
|
||||
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.MobileNotificationsView;
|
||||
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 class AccountSettingsView extends ActivatableViewAndModel {
|
||||
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"), 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);
|
||||
|
||||
leftVBox.getChildren().addAll(paymentAccount, altCoinsAccountView, arbitratorSelection, password, seedWords, backup);
|
||||
leftVBox.getChildren().addAll(paymentAccount, altCoinsAccountView, arbitratorSelection, notifications, password, seedWords, backup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,6 +110,7 @@ public class AccountSettingsView extends ActivatableViewAndModel {
|
||||
paymentAccount.activate();
|
||||
altCoinsAccountView.activate();
|
||||
arbitratorSelection.activate();
|
||||
notifications.activate();
|
||||
password.activate();
|
||||
seedWords.activate();
|
||||
backup.activate();
|
||||
@ -133,6 +136,7 @@ public class AccountSettingsView extends ActivatableViewAndModel {
|
||||
paymentAccount.deactivate();
|
||||
altCoinsAccountView.deactivate();
|
||||
arbitratorSelection.deactivate();
|
||||
notifications.deactivate();
|
||||
password.deactivate();
|
||||
seedWords.deactivate();
|
||||
backup.deactivate();
|
||||
@ -145,6 +149,7 @@ public class AccountSettingsView extends ActivatableViewAndModel {
|
||||
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 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);
|
||||
|
@ -1152,7 +1152,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
|
||||
|
||||
HBox priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first;
|
||||
marketBasedPriceInfoInputTextField = priceAsPercentageTuple.second;
|
||||
marketBasedPriceTextField = marketBasedPriceInfoInputTextField.getTextField();
|
||||
marketBasedPriceTextField = marketBasedPriceInfoInputTextField.getInputTextField();
|
||||
marketBasedPriceTextField.setPrefWidth(200);
|
||||
editOfferElements.add(marketBasedPriceTextField);
|
||||
marketBasedPriceLabel = priceAsPercentageTuple.third;
|
||||
|
@ -31,15 +31,10 @@ import javafx.scene.control.Label;
|
||||
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<ShowWalletDataWindow> {
|
||||
private static final Logger log = LoggerFactory.getLogger(ShowWalletDataWindow.class);
|
||||
|
||||
private final WalletsManager walletsManager;
|
||||
|
||||
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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.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;
|
||||
|
||||
@Slf4j
|
||||
public class WebCamWindow extends Overlay<WebCamWindow> {
|
||||
|
||||
@Getter
|
||||
private ImageView imageView = new ImageView();
|
||||
private ChangeListener<Image> listener;
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
@ -362,6 +362,27 @@ public class FormBuilder {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Label + InfoInputTextField
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static Tuple2<Label, InfoInputTextField> addLabelInfoInputTextField(GridPane gridPane, int rowIndex, String title) {
|
||||
return addLabelInfoInputTextField(gridPane, rowIndex, title, 0);
|
||||
}
|
||||
|
||||
public static Tuple2<Label, InfoInputTextField> 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 class FormBuilder {
|
||||
|
||||
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);
|
||||
@ -955,7 +977,7 @@ public class FormBuilder {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Label + Button
|
||||
// Label + Button
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static Tuple2<Label, Button> addLabelButton(GridPane gridPane, int rowIndex, String labelText, String buttonTitle) {
|
||||
@ -974,6 +996,26 @@ public class FormBuilder {
|
||||
return new Tuple2<>(label, button);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Label + Button + Button
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public static Tuple3<Label, Button, Button> 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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -1130,7 +1172,7 @@ public class FormBuilder {
|
||||
|
||||
public static Tuple3<HBox, InfoInputTextField, Label> 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");
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -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.Scene;
|
||||
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<AwesomeIcon> 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();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user