Browse Source

Merge pull request #2998 from cryptomator/feature/update-reminder

Update Reminder
mindmonk 1 year ago
parent
commit
91ece74f57

+ 5 - 0
src/main/java/org/cryptomator/common/settings/Settings.java

@@ -44,6 +44,7 @@ public class Settings {
 	static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
 	static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
 	static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
+	static final String DEFAULT_LAST_UPDATE_CHECK = "2000-01-01";
 
 	public final ObservableList<VaultSettings> directories;
 	public final BooleanProperty askedForUpdateCheck;
@@ -67,6 +68,7 @@ public class Settings {
 	public final StringProperty displayConfiguration;
 	public final StringProperty language;
 	public final StringProperty mountService;
+	public final StringProperty lastUpdateCheck;
 
 	private Consumer<Settings> saveCmd;
 
@@ -104,6 +106,7 @@ public class Settings {
 		this.displayConfiguration = new SimpleStringProperty(this, "displayConfiguration", json.displayConfiguration);
 		this.language = new SimpleStringProperty(this, "language", json.language);
 		this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
+		this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck);
 
 		this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
 
@@ -131,6 +134,7 @@ public class Settings {
 		displayConfiguration.addListener(this::somethingChanged);
 		language.addListener(this::somethingChanged);
 		mountService.addListener(this::somethingChanged);
+		lastUpdateCheck.addListener(this::somethingChanged);
 	}
 
 	@SuppressWarnings("deprecation")
@@ -185,6 +189,7 @@ public class Settings {
 		json.displayConfiguration = displayConfiguration.get();
 		json.language = language.get();
 		json.mountService = mountService.get();
+		json.lastUpdateCheck = lastUpdateCheck.get();
 		return json;
 	}
 

+ 3 - 0
src/main/java/org/cryptomator/common/settings/SettingsJson.java

@@ -83,4 +83,7 @@ class SettingsJson {
 	@JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
 	String preferredVolumeImpl;
 
+	@JsonProperty("lastUpdateCheck")
+	String lastUpdateCheck = Settings.DEFAULT_LAST_UPDATE_CHECK;
+
 }

+ 1 - 0
src/main/java/org/cryptomator/ui/common/FxmlFile.java

@@ -42,6 +42,7 @@ public enum FxmlFile {
 	RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
 	RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
 	REMOVE_VAULT("/fxml/remove_vault.fxml"), //
+	UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
 	UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
 	UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
 	UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //

+ 2 - 0
src/main/java/org/cryptomator/ui/fxapp/FxApplication.java

@@ -68,6 +68,8 @@ public class FxApplication {
 			return null;
 		});
 
+		appWindows.checkAndShowUpdateReminderWindow();
+
 		launchEventHandler.startHandlingLaunchEvents();
 		autoUnlocker.tryUnlockForTimespan(2, TimeUnit.MINUTES);
 	}

+ 3 - 1
src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java

@@ -15,13 +15,14 @@ import org.cryptomator.ui.preferences.PreferencesComponent;
 import org.cryptomator.ui.quit.QuitComponent;
 import org.cryptomator.ui.traymenu.TrayMenuComponent;
 import org.cryptomator.ui.unlock.UnlockComponent;
+import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
 import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
 
 import javafx.scene.image.Image;
 import java.io.IOException;
 import java.io.InputStream;
 
-@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class})
+@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class, UpdateReminderComponent.class})
 abstract class FxApplicationModule {
 
 	private static Image createImageFromResource(String resourceName) throws IOException {
@@ -53,4 +54,5 @@ abstract class FxApplicationModule {
 	static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
 		return builder.build();
 	}
+
 }

+ 18 - 1
src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java

@@ -13,6 +13,7 @@ import org.cryptomator.ui.preferences.SelectedPreferencesTab;
 import org.cryptomator.ui.quit.QuitComponent;
 import org.cryptomator.ui.unlock.UnlockComponent;
 import org.cryptomator.ui.unlock.UnlockWorkflow;
+import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
 import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
 import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
 import org.jetbrains.annotations.Nullable;
@@ -45,6 +46,7 @@ public class FxApplicationWindows {
 	private final Lazy<PreferencesComponent> preferencesWindow;
 	private final QuitComponent.Builder quitWindowBuilder;
 	private final UnlockComponent.Factory unlockWorkflowFactory;
+	private final UpdateReminderComponent.Factory updateReminderWindowBuilder;
 	private final LockComponent.Factory lockWorkflowFactory;
 	private final ErrorComponent.Factory errorWindowFactory;
 	private final ExecutorService executor;
@@ -52,13 +54,24 @@ public class FxApplicationWindows {
 	private final FilteredList<Window> visibleWindows;
 
 	@Inject
-	public FxApplicationWindows(@PrimaryStage Stage primaryStage, Optional<TrayIntegrationProvider> trayIntegration, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, QuitComponent.Builder quitWindowBuilder, UnlockComponent.Factory unlockWorkflowFactory, LockComponent.Factory lockWorkflowFactory, ErrorComponent.Factory errorWindowFactory, ExecutorService executor, VaultOptionsComponent.Factory vaultOptionsWindow) {
+	public FxApplicationWindows(@PrimaryStage Stage primaryStage,
+								Optional<TrayIntegrationProvider> trayIntegration, //
+								Lazy<MainWindowComponent> mainWindow, //
+								Lazy<PreferencesComponent> preferencesWindow, //
+								QuitComponent.Builder quitWindowBuilder, //
+								UnlockComponent.Factory unlockWorkflowFactory, //
+								UpdateReminderComponent.Factory updateReminderWindowBuilder, //
+								LockComponent.Factory lockWorkflowFactory, //
+								ErrorComponent.Factory errorWindowFactory, //
+								VaultOptionsComponent.Factory vaultOptionsWindow, //
+								ExecutorService executor) {
 		this.primaryStage = primaryStage;
 		this.trayIntegration = trayIntegration;
 		this.mainWindow = mainWindow;
 		this.preferencesWindow = preferencesWindow;
 		this.quitWindowBuilder = quitWindowBuilder;
 		this.unlockWorkflowFactory = unlockWorkflowFactory;
+		this.updateReminderWindowBuilder = updateReminderWindowBuilder;
 		this.lockWorkflowFactory = lockWorkflowFactory;
 		this.errorWindowFactory = errorWindowFactory;
 		this.executor = executor;
@@ -117,6 +130,10 @@ public class FxApplicationWindows {
 			CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
 	}
 
+	public void checkAndShowUpdateReminderWindow() {
+		CompletableFuture.runAsync(() -> updateReminderWindowBuilder.create().checkAndShowUpdateReminderWindow(), Platform::runLater);
+	}
+
 	public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
 		return CompletableFuture.supplyAsync(() -> {
 					Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked.");

+ 38 - 0
src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderComponent.java

@@ -0,0 +1,38 @@
+package org.cryptomator.ui.updatereminder;
+
+import dagger.Lazy;
+import dagger.Subcomponent;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import java.time.LocalDate;
+
+@UpdateReminderScoped
+@Subcomponent(modules = {UpdateReminderModule.class})
+public interface UpdateReminderComponent {
+
+	@UpdateReminderWindow
+	Stage window();
+
+	@FxmlScene(FxmlFile.UPDATE_REMINDER)
+	Lazy<Scene> updateReminderScene();
+
+	Settings settings();
+
+	default void checkAndShowUpdateReminderWindow() {
+		if (LocalDate.parse(settings().lastUpdateCheck.get()).isBefore(LocalDate.now().minusDays(14)) && !settings().checkForUpdates.getValue()) {
+			Stage stage = window();
+			stage.setScene(updateReminderScene().get());
+			stage.sizeToScene();
+			stage.show();
+		}
+	}
+
+	@Subcomponent.Factory
+	interface Factory {
+		UpdateReminderComponent create();
+	}
+}

+ 49 - 0
src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderController.java

@@ -0,0 +1,49 @@
+package org.cryptomator.ui.updatereminder;
+
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.fxapp.UpdateChecker;
+
+import javax.inject.Inject;
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+@UpdateReminderScoped
+public class UpdateReminderController implements FxController {
+
+	private final Stage window;
+	private final Settings settings;
+	private final UpdateChecker updateChecker;
+
+
+	@Inject
+	UpdateReminderController(@UpdateReminderWindow Stage window, Settings settings, UpdateChecker updateChecker) {
+		this.window = window;
+		this.settings = settings;
+		this.updateChecker = updateChecker;
+	}
+
+	@FXML
+	public void cancel() {
+		settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
+		window.close();
+	}
+
+	@FXML
+	public void once() {
+		settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
+		updateChecker.checkForUpdatesNow();
+		window.close();
+	}
+
+	@FXML
+	public void automatically() {
+		settings.lastUpdateCheck.set(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
+		updateChecker.checkForUpdatesNow();
+		settings.checkForUpdates.set(true);
+		window.close();
+	}
+
+}

+ 59 - 0
src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderModule.java

@@ -0,0 +1,59 @@
+package org.cryptomator.ui.updatereminder;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoMap;
+import org.cryptomator.ui.common.DefaultSceneFactory;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.FxControllerKey;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlLoaderFactory;
+import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.common.StageFactory;
+
+import javax.inject.Provider;
+import javafx.scene.Scene;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+@Module
+abstract class UpdateReminderModule {
+
+	@Provides
+	@UpdateReminderWindow
+	@UpdateReminderScoped
+	static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
+		return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
+	}
+
+	@Provides
+	@UpdateReminderWindow
+	@UpdateReminderScoped
+	static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
+		Stage stage = factory.create();
+		stage.setTitle(resourceBundle.getString("updateReminder.title"));
+		stage.setMinWidth(500);
+		stage.setMinHeight(100);
+		stage.initModality(Modality.APPLICATION_MODAL);
+		return stage;
+	}
+
+	@Provides
+	@FxmlScene(FxmlFile.UPDATE_REMINDER)
+	@UpdateReminderScoped
+	static Scene provideUpdateReminderScene(@UpdateReminderWindow FxmlLoaderFactory fxmlLoaders) {
+		return fxmlLoaders.createScene(FxmlFile.UPDATE_REMINDER);
+	}
+
+
+	// ------------------
+
+	@Binds
+	@IntoMap
+	@FxControllerKey(UpdateReminderController.class)
+	abstract FxController bindUpdateReminderController(UpdateReminderController controller);
+
+}

+ 13 - 0
src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderScoped.java

@@ -0,0 +1,13 @@
+package org.cryptomator.ui.updatereminder;
+
+import javax.inject.Scope;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Scope
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@interface UpdateReminderScoped {
+
+}

+ 14 - 0
src/main/java/org/cryptomator/ui/updatereminder/UpdateReminderWindow.java

@@ -0,0 +1,14 @@
+package org.cryptomator.ui.updatereminder;
+
+import javax.inject.Qualifier;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+@interface UpdateReminderWindow {
+
+}

+ 54 - 0
src/main/resources/fxml/update_reminder.fxml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.Group?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.Region?>
+<?import javafx.scene.layout.StackPane?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.shape.Circle?>
+<HBox xmlns:fx="http://javafx.com/fxml"
+	  xmlns="http://javafx.com/javafx"
+	  fx:controller="org.cryptomator.ui.updatereminder.UpdateReminderController"
+	  minWidth="500"
+	  prefWidth="500"
+	  minHeight="145"
+	  spacing="12"
+	  alignment="TOP_LEFT">
+	<padding>
+		<Insets topRightBottomLeft="12"/>
+	</padding>
+	<children>
+		<Group>
+			<StackPane>
+				<padding>
+					<Insets topRightBottomLeft="6"/>
+				</padding>
+				<Circle styleClass="glyph-icon-primary" radius="24"/>
+				<FontAwesome5IconView styleClass="glyph-icon-white" glyph="QUESTION" glyphSize="24"/>
+			</StackPane>
+		</Group>
+
+		<VBox HBox.hgrow="ALWAYS">
+			<Label styleClass="label-large" text="%updateReminder.message" wrapText="true">
+				<padding>
+					<Insets bottom="6" top="6"/>
+				</padding>
+			</Label>
+			<Label text="%updateReminder.description" wrapText="true"/>
+			<Region VBox.vgrow="ALWAYS" minHeight="18"/>
+			<ButtonBar buttonMinWidth="120" buttonOrder="+CY" >
+				<buttons>
+					<Button text="%updateReminder.notNow" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
+					<Button text="%updateReminder.yesOnce" ButtonBar.buttonData="YES" onAction="#once"/>
+					<Button text="%updateReminder.yesAutomatically" ButtonBar.buttonData="YES" defaultButton="true" onAction="#automatically"/>
+				</buttons>
+			</ButtonBar>
+
+		</VBox>
+	</children>
+</HBox>

+ 9 - 1
src/main/resources/i18n/strings.properties

@@ -498,4 +498,12 @@ quit.lockAndQuitBtn=Lock and Quit
 # Forced Quit
 quit.forced.message=Some vaults could not be locked
 quit.forced.description=Locking vaults was blocked by pending operations or open files. You can force lock remaining vaults, however interrupting I/O may result in the loss of unsaved data.
-quit.forced.forceAndQuitBtn=Force and Quit
+quit.forced.forceAndQuitBtn=Force and Quit
+
+# Update Reminder
+updateReminder.title=Update Check
+updateReminder.message=Check for Updates?
+updateReminder.description=Stay updated with new features, bug fixes, and security improvements. We recommend to automatically check for updates.
+updateReminder.notNow=Not Now
+updateReminder.yesOnce=Yes, Once
+updateReminder.yesAutomatically=Yes, Automatically