diff --git a/core/src/main/java/bisq/core/alert/Alert.java b/core/src/main/java/bisq/core/alert/Alert.java index 5bbb83c055..21f4e69cf3 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 final class Alert implements ProtectedStoragePayload, ExpirablePayload { @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 final class Alert implements ProtectedStoragePayload, ExpirablePayload { 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 final class Alert implements ProtectedStoragePayload, ExpirablePayload { return new Alert(proto.getMessage(), proto.getIsUpdateInfo(), + proto.getIsPreReleaseInfo(), proto.getVersion(), proto.getOwnerPubKeyBytes().toByteArray(), proto.getSignatureAsBase64(), @@ -143,7 +152,28 @@ public final class Alert implements ProtectedStoragePayload, ExpirablePayload { ownerPubKeyBytes = Sig.getPublicKeyBytes(ownerPubKey); } - public boolean isNewVersion() { - return Version.isNewVersion(version); + public boolean isSoftwareUpdateNotification() { + return (isUpdateInfo || isPreReleaseInfo); } + + 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 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 677b4e7678..7fb45917c5 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 class BisqSetup { /////////////////////////////////////////////////////////////////////////////////////////// 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 ee276d0ef0..3f501e2bd0 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 final class Preferences implements PersistedDataHost, BridgeAddressProvid requestPersistence(); } + public void setNotifyOnPreRelease(boolean value) { + prefPayload.setNotifyOnPreRelease(value); + requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1095,5 +1100,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid 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 b8991f8cba..3eb6ecfa7b 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 final class PreferencesPayload implements PersistableEnvelope { .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 final class PreferencesPayload implements PersistableEnvelope { .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 ce288a4658..d35c846b78 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 bc18d6e2c4..7241ad3a4b 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 class UserPayloadModelVOTest { 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 e6279f8918..041a55c000 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 class DisplayAlertMessageWindow extends Overlay { @@ -107,13 +110,26 @@ public class SendAlertMessageWindow extends Overlay { 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 @@ public class SendAlertMessageWindow extends Overlay { 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 @@ public class SendAlertMessageWindow extends Overlay { .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 19f4b73607..c5bafe2321 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 @@ public class PreferencesView extends ActivatableViewAndModel 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 @@ public class PreferencesView extends ActivatableViewAndModel 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 {