diff --git a/core/src/main/java/bisq/core/alert/Alert.java b/core/src/main/java/bisq/core/alert/Alert.java index 5bbb83c055e..d9830b38ee0 100644 --- a/core/src/main/java/bisq/core/alert/Alert.java +++ b/core/src/main/java/bisq/core/alert/Alert.java @@ -17,6 +17,8 @@ package bisq.core.alert; +import bisq.core.user.Preferences; + import bisq.network.p2p.storage.payload.ExpirablePayload; import bisq.network.p2p.storage.payload.ProtectedStoragePayload; @@ -51,6 +53,7 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload { private final String message; private final boolean isUpdateInfo; + private final boolean isPreReleaseInfo; private final String version; @Nullable @@ -68,9 +71,11 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload { public Alert(String message, boolean isUpdateInfo, + boolean isPreReleaseInfo, String version) { this.message = message; this.isUpdateInfo = isUpdateInfo; + this.isPreReleaseInfo = isPreReleaseInfo; this.version = version; } @@ -82,12 +87,14 @@ public Alert(String message, @SuppressWarnings("NullableProblems") public Alert(String message, boolean isUpdateInfo, + boolean isPreReleaseInfo, String version, byte[] ownerPubKeyBytes, String signatureAsBase64, Map extraDataMap) { this.message = message; this.isUpdateInfo = isUpdateInfo; + this.isPreReleaseInfo = isPreReleaseInfo; this.version = version; this.ownerPubKeyBytes = ownerPubKeyBytes; this.signatureAsBase64 = signatureAsBase64; @@ -103,6 +110,7 @@ public protobuf.StoragePayload toProtoMessage() { protobuf.Alert.Builder builder = protobuf.Alert.newBuilder() .setMessage(message) .setIsUpdateInfo(isUpdateInfo) + .setIsPreReleaseInfo(isPreReleaseInfo) .setVersion(version) .setOwnerPubKeyBytes(ByteString.copyFrom(ownerPubKeyBytes)) .setSignatureAsBase64(signatureAsBase64); @@ -119,6 +127,7 @@ public static Alert fromProto(protobuf.Alert proto) { return new Alert(proto.getMessage(), proto.getIsUpdateInfo(), + proto.getIsPreReleaseInfo(), proto.getVersion(), proto.getOwnerPubKeyBytes().toByteArray(), proto.getSignatureAsBase64(), @@ -143,7 +152,28 @@ public void setSigAndPubKey(String signatureAsBase64, PublicKey ownerPubKey) { ownerPubKeyBytes = Sig.getPublicKeyBytes(ownerPubKey); } - public boolean isNewVersion() { - return Version.isNewVersion(version); + public boolean isNewVersion(Preferences preferences) { + // regular release: always notify user + // pre-release: if user has set preference to receive pre-release notification + if (isUpdateInfo || + (isPreReleaseInfo && preferences.isNotifyOnPreRelease())) { + return Version.isNewVersion(version); + } + return false; + } + + public boolean isSoftwareUpdateNotification() { + return (isUpdateInfo || isPreReleaseInfo); + } + + public boolean canShowPopup(Preferences preferences) { + // only show popup if its version is newer than current + // and only if user has not checked "don't show again" + return isNewVersion(preferences) && preferences.showAgain(showAgainKey()); + } + + public String showAgainKey() { + return "Update_" + version; } + } diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 677b4e76784..7fb45917c5f 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -250,20 +250,23 @@ public BisqSetup(DomainInitialisation domainInitialisation, /////////////////////////////////////////////////////////////////////////////////////////// public void displayAlertIfPresent(Alert alert, boolean openNewVersionPopup) { - if (alert != null) { - if (alert.isUpdateInfo()) { - user.setDisplayedAlert(alert); - final boolean isNewVersion = alert.isNewVersion(); - newVersionAvailableProperty.set(isNewVersion); - String key = "Update_" + alert.getVersion(); - if (isNewVersion && (preferences.showAgain(key) || openNewVersionPopup) && displayUpdateHandler != null) { - displayUpdateHandler.accept(alert, key); + if (alert == null) + return; + + if (alert.isSoftwareUpdateNotification()) { + // only process if the alert version is "newer" than ours + if (alert.isNewVersion(preferences)) { + user.setDisplayedAlert(alert); // save context to compare later + newVersionAvailableProperty.set(true); // shows link in footer bar + if ((alert.canShowPopup(preferences) || openNewVersionPopup) && displayUpdateHandler != null) { + displayUpdateHandler.accept(alert, alert.showAgainKey()); } - } else { - final Alert displayedAlert = user.getDisplayedAlert(); - if ((displayedAlert == null || !displayedAlert.equals(alert)) && displayAlertHandler != null) - displayAlertHandler.accept(alert); } + } else { + // it is a normal message alert + final Alert displayedAlert = user.getDisplayedAlert(); + if ((displayedAlert == null || !displayedAlert.equals(alert)) && displayAlertHandler != null) + displayAlertHandler.accept(alert); } } diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index ee276d0ef03..3f501e2bd05 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -782,6 +782,11 @@ public void setDenyApiTaker(boolean value) { requestPersistence(); } + public void setNotifyOnPreRelease(boolean value) { + prefPayload.setNotifyOnPreRelease(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1095,5 +1100,7 @@ private interface ExcludesDelegateMethods { void setShowOffersMatchingMyAccounts(boolean value); void setDenyApiTaker(boolean value); + + void setNotifyOnPreRelease(boolean value); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index b8991f8cbae..3eb6ecfa7ba 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -133,6 +133,7 @@ public final class PreferencesPayload implements PersistableEnvelope { private boolean hideNonAccountPaymentMethods; private boolean showOffersMatchingMyAccounts; private boolean denyApiTaker; + private boolean notifyOnPreRelease; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -199,7 +200,8 @@ public Message toProtoMessage() { .collect(Collectors.toList())) .setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods) .setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts) - .setDenyApiTaker(denyApiTaker); + .setDenyApiTaker(denyApiTaker) + .setNotifyOnPreRelease(notifyOnPreRelease); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); @@ -296,7 +298,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co .collect(Collectors.toList())), proto.getHideNonAccountPaymentMethods(), proto.getShowOffersMatchingMyAccounts(), - proto.getDenyApiTaker() + proto.getDenyApiTaker(), + proto.getNotifyOnPreRelease() ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index ce288a46588..d35c846b78c 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1223,6 +1223,7 @@ setting.preferences.useDarkMode=Use dark mode setting.preferences.sortWithNumOffers=Sort market lists with no. of offers/trades setting.preferences.onlyShowPaymentMethodsFromAccount=Hide non-supported payment methods setting.preferences.denyApiTaker=Deny takers using the API +setting.preferences.notifyOnPreRelease=Receive pre-release notifications setting.preferences.resetAllFlags=Reset all \"Don't show again\" flags settings.preferences.languageChange=To apply the language change to all screens requires a restart. settings.preferences.supportLanguageWarning=In case of a dispute, please note that mediation is handled in {0} and arbitration in {1}. @@ -2658,7 +2659,9 @@ selectDepositTxWindow.select=Select deposit transaction sendAlertMessageWindow.headline=Send global notification sendAlertMessageWindow.alertMsg=Alert message sendAlertMessageWindow.enterMsg=Enter message -sendAlertMessageWindow.isUpdate=Is update notification +sendAlertMessageWindow.isSoftwareUpdate=Software download notification +sendAlertMessageWindow.isUpdate=Is full release +sendAlertMessageWindow.isPreRelease=Is pre-release sendAlertMessageWindow.version=New version no. sendAlertMessageWindow.send=Send notification sendAlertMessageWindow.remove=Remove notification diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java index bc18d6e2c4b..7241ad3a4b2 100644 --- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java +++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java @@ -41,7 +41,7 @@ public void testRoundtrip() { public void testRoundtripFull() { UserPayload vo = new UserPayload(); vo.setAccountId("accountId"); - vo.setDisplayedAlert(new Alert("message", true, "version", new byte[]{12, -64, 12}, "string", null)); + vo.setDisplayedAlert(new Alert("message", true, false, "version", new byte[]{12, -64, 12}, "string", null)); vo.setDevelopersFilter(new Filter(Lists.newArrayList(), Lists.newArrayList(), Lists.newArrayList(), diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisplayAlertMessageWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisplayAlertMessageWindow.java index e6279f89181..7537e713475 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisplayAlertMessageWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisplayAlertMessageWindow.java @@ -50,7 +50,7 @@ public void show() { checkNotNull(alert, "alertMessage must not be null"); - if (alert.isUpdateInfo()) { + if (alert.isSoftwareUpdateNotification()) { information(""); headLine = Res.get("displayAlertMessageWindow.update.headline"); } else { @@ -78,7 +78,7 @@ public DisplayAlertMessageWindow alertMessage(Alert alert) { private void addContent() { checkNotNull(alert, "alertMessage must not be null"); addMultilineLabel(gridPane, ++rowIndex, alert.getMessage(), 10); - if (alert.isUpdateInfo()) { + if (alert.isSoftwareUpdateNotification()) { String url = "https://bisq.network/downloads"; HyperlinkWithIcon hyperlinkWithIcon = FormBuilder.addLabelHyperlinkWithIcon(gridPane, ++rowIndex, Res.get("displayAlertMessageWindow.update.download"), url, url).second; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SendAlertMessageWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SendAlertMessageWindow.java index f0cbc29d17c..b7bf081c205 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SendAlertMessageWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SendAlertMessageWindow.java @@ -39,7 +39,9 @@ import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; import javafx.scene.control.TextArea; +import javafx.scene.control.ToggleGroup; import javafx.scene.input.KeyCode; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; @@ -49,6 +51,7 @@ import static bisq.desktop.util.FormBuilder.addInputTextField; import static bisq.desktop.util.FormBuilder.addLabelCheckBox; +import static bisq.desktop.util.FormBuilder.addRadioButton; import static bisq.desktop.util.FormBuilder.addTopLabelTextArea; public class SendAlertMessageWindow extends Overlay { @@ -107,13 +110,26 @@ private void addContent() { TextArea alertMessageTextArea = labelTextAreaTuple2.second; Label first = labelTextAreaTuple2.first; first.setMinWidth(150); - CheckBox isUpdateCheckBox = addLabelCheckBox(gridPane, ++rowIndex, - Res.get("sendAlertMessageWindow.isUpdate")); + CheckBox isSoftwareUpdateCheckBox = addLabelCheckBox(gridPane, ++rowIndex, + Res.get("sendAlertMessageWindow.isSoftwareUpdate")); + HBox hBoxRelease = new HBox(); + hBoxRelease.setSpacing(10); + GridPane.setRowIndex(hBoxRelease, ++rowIndex); + + ToggleGroup toggleGroup = new ToggleGroup(); + RadioButton isUpdateCheckBox = addRadioButton(gridPane, rowIndex, toggleGroup, Res.get("sendAlertMessageWindow.isUpdate")); + RadioButton isPreReleaseCheckBox = addRadioButton(gridPane, rowIndex, toggleGroup, Res.get("sendAlertMessageWindow.isPreRelease")); + hBoxRelease.getChildren().addAll(new Label(""), isUpdateCheckBox, isPreReleaseCheckBox); + gridPane.getChildren().add(hBoxRelease); + + isSoftwareUpdateCheckBox.setSelected(true); isUpdateCheckBox.setSelected(true); InputTextField versionInputTextField = FormBuilder.addInputTextField(gridPane, ++rowIndex, Res.get("sendAlertMessageWindow.version")); - versionInputTextField.disableProperty().bind(isUpdateCheckBox.selectedProperty().not()); + versionInputTextField.disableProperty().bind(isSoftwareUpdateCheckBox.selectedProperty().not()); + isUpdateCheckBox.disableProperty().bind(isSoftwareUpdateCheckBox.selectedProperty().not()); + isPreReleaseCheckBox.disableProperty().bind(isSoftwareUpdateCheckBox.selectedProperty().not()); Button sendButton = new AutoTooltipButton(Res.get("sendAlertMessageWindow.send")); sendButton.getStyleClass().add("action-button"); @@ -121,8 +137,9 @@ private void addContent() { sendButton.setOnAction(e -> { final String version = versionInputTextField.getText(); boolean versionOK = false; - final boolean isUpdate = isUpdateCheckBox.isSelected(); - if (isUpdate) { + final boolean isUpdate = (isSoftwareUpdateCheckBox.isSelected() && isUpdateCheckBox.isSelected()); + final boolean isPreRelease = (isSoftwareUpdateCheckBox.isSelected() && isPreReleaseCheckBox.isSelected()); + if (isUpdate || isPreRelease) { final String[] split = version.split("\\."); versionOK = split.length == 3; if (!versionOK) // Do not translate as only used by devs @@ -130,10 +147,10 @@ private void addContent() { .onClose(this::blurAgain) .show(); } - if (!isUpdate || versionOK) { + if (!isSoftwareUpdateCheckBox.isSelected() || versionOK) { if (alertMessageTextArea.getText().length() > 0 && keyInputTextField.getText().length() > 0) { if (alertManager.addAlertMessageIfKeyIsValid( - new Alert(alertMessageTextArea.getText(), isUpdate, version), + new Alert(alertMessageTextArea.getText(), isUpdate, isPreRelease, version), keyInputTextField.getText()) ) hide(); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 19f4b736071..c5bafe23217 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -120,7 +120,8 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, - avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle; + avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle, + notifyOnPreReleaseToggle; private int gridRow = 0; private int displayCurrenciesGridRowIndex = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, @@ -612,6 +613,7 @@ private void initializeDisplayOptions() { sortMarketCurrenciesNumerically = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.sortWithNumOffers")); hideNonAccountPaymentMethodsToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.onlyShowPaymentMethodsFromAccount")); denyApiTakerToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.denyApiTaker")); + notifyOnPreReleaseToggle = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.notifyOnPreRelease")); resetDontShowAgainButton = addButton(root, ++gridRow, Res.get("setting.preferences.resetAllFlags"), 0); resetDontShowAgainButton.getStyleClass().add("compact-button"); resetDontShowAgainButton.setMaxWidth(Double.MAX_VALUE); @@ -954,6 +956,9 @@ private void activateDisplayPreferences() { denyApiTakerToggle.setSelected(preferences.isDenyApiTaker()); denyApiTakerToggle.setOnAction(e -> preferences.setDenyApiTaker(denyApiTakerToggle.isSelected())); + notifyOnPreReleaseToggle.setSelected(preferences.isNotifyOnPreRelease()); + notifyOnPreReleaseToggle.setOnAction(e -> preferences.setNotifyOnPreRelease(notifyOnPreReleaseToggle.isSelected())); + resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgain()); editCustomBtcExplorer.setOnAction(e -> { @@ -1133,6 +1138,7 @@ private void deactivateDisplayPreferences() { sortMarketCurrenciesNumerically.setOnAction(null); hideNonAccountPaymentMethodsToggle.setOnAction(null); denyApiTakerToggle.setOnAction(null); + notifyOnPreReleaseToggle.setOnAction(null); showOwnOffersInOfferBook.setOnAction(null); resetDontShowAgainButton.setOnAction(null); if (displayStandbyModeFeature) { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index e9463018e1e..70c9397b2a5 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -620,6 +620,7 @@ message Alert { string signature_as_base64 = 4; bytes owner_pub_key_bytes = 5; map extra_data = 6; + bool is_pre_release_info = 7; } message Arbitrator { @@ -1638,6 +1639,7 @@ message PreferencesPayload { bool hide_non_account_payment_methods = 58; bool show_offers_matching_my_accounts = 59; bool deny_api_taker = 60; + bool notify_on_pre_release = 61; } message AutoConfirmSettings {