From c917fb6a5702293326b53284db0b391ce9e4e590 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 22 Jul 2019 15:05:23 +0200 Subject: [PATCH] Added UI theme switcher (issue #930) --- .../cryptomator/common/settings/Settings.java | 22 +++++++++---- .../common/settings/SettingsJsonAdapter.java | 15 +++++++++ .../cryptomator/common/settings/UiTheme.java | 18 +++++++++++ .../org/cryptomator/ui/FxApplication.java | 31 ++++++++++++++++++- .../ui/FxApplicationComponent.java | 2 -- .../ui/preferences/PreferencesController.java | 19 ++++++++++++ .../src/main/resources/fxml/preferences.fxml | 5 +++ .../main/resources/i18n/strings.properties | 1 + .../main/resources/i18n/strings_en.properties | 1 + 9 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java index e345f1779..9c5a3c96b 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java @@ -8,10 +8,14 @@ ******************************************************************************/ package org.cryptomator.common.settings; -import javafx.beans.property.*; -import javafx.beans.value.ObservableValue; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import java.util.function.Consumer; @@ -25,6 +29,7 @@ public class Settings { public static final int DEFAULT_PORT = 42427; public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV; + public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT; public static final boolean DEFAULT_DEBUG_MODE = false; public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE; @@ -36,6 +41,7 @@ public class Settings { private final ObjectProperty preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME); private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE); private final ObjectProperty preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL); + private final ObjectProperty theme = new SimpleObjectProperty<>(DEFAULT_THEME); private Consumer saveCmd; @@ -43,7 +49,7 @@ public class Settings { * Package-private constructor; use {@link SettingsProvider}. */ Settings() { - directories.addListener((ListChangeListener.Change change) -> this.save()); + directories.addListener(this::somethingChanged); askedForUpdateCheck.addListener(this::somethingChanged); checkForUpdates.addListener(this::somethingChanged); port.addListener(this::somethingChanged); @@ -51,13 +57,14 @@ public class Settings { preferredGvfsScheme.addListener(this::somethingChanged); debugMode.addListener(this::somethingChanged); preferredVolumeImpl.addListener(this::somethingChanged); + theme.addListener(this::somethingChanged); } void setSaveCmd(Consumer saveCmd) { this.saveCmd = saveCmd; } - - private void somethingChanged(ObservableValue observable, Object oldValue, Object newValue) { + + private void somethingChanged(@SuppressWarnings("unused") Observable observable) { this.save(); } @@ -101,4 +108,7 @@ public class Settings { return preferredVolumeImpl; } + public ObjectProperty theme() { + return theme; + } } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java index 609096834..42ae08f38 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java @@ -34,6 +34,7 @@ public class SettingsJsonAdapter extends TypeAdapter { out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name()); out.name("debugMode").value(value.debugMode().get()); out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name()); + out.name("theme").value(value.theme().get().name()); out.endObject(); } @@ -77,6 +78,9 @@ public class SettingsJsonAdapter extends TypeAdapter { case "preferredVolumeImpl": settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString())); break; + case "theme": + settings.theme().set(parseUiTheme(in.nextString())); + break; default: LOG.warn("Unsupported vault setting found in JSON: " + name); in.skipValue(); @@ -92,6 +96,7 @@ public class SettingsJsonAdapter extends TypeAdapter { try { return VolumeImpl.valueOf(nioAdapterName.toUpperCase()); } catch (IllegalArgumentException e) { + LOG.warn("Invalid volume type {}. Defaulting to {}.", nioAdapterName, Settings.DEFAULT_PREFERRED_VOLUME_IMPL); return Settings.DEFAULT_PREFERRED_VOLUME_IMPL; } } @@ -100,10 +105,20 @@ public class SettingsJsonAdapter extends TypeAdapter { try { return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase()); } catch (IllegalArgumentException e) { + LOG.warn("Invalid volume type {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME); return Settings.DEFAULT_GVFS_SCHEME; } } + private UiTheme parseUiTheme(String uiThemeName) { + try { + return UiTheme.valueOf(uiThemeName.toUpperCase()); + } catch (IllegalArgumentException e) { + LOG.warn("Invalid volume type {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME); + return Settings.DEFAULT_THEME; + } + } + private List readVaultSettingsArray(JsonReader in) throws IOException { List result = new ArrayList<>(); in.beginArray(); diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java b/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java new file mode 100644 index 000000000..8d7d79067 --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/settings/UiTheme.java @@ -0,0 +1,18 @@ +package org.cryptomator.common.settings; + +public enum UiTheme { + LIGHT("Light"), + DARK("Dark"), + CUSTOM("Custom (%s)"); + + private String displayName; + + UiTheme(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/FxApplication.java b/main/ui/src/main/java/org/cryptomator/ui/FxApplication.java index 6a6b315d2..7bf3c24bd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/FxApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/FxApplication.java @@ -2,7 +2,11 @@ package org.cryptomator.ui; import javafx.application.Application; import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.value.ObservableValue; import javafx.stage.Stage; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.UiTheme; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; import org.slf4j.Logger; @@ -17,17 +21,22 @@ public class FxApplication extends Application { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); + private final Settings settings; private final MainWindowComponent.Builder mainWindow; private final PreferencesComponent.Builder preferencesWindow; @Inject - FxApplication(MainWindowComponent.Builder mainWindow, PreferencesComponent.Builder preferencesWindow) { + FxApplication(Settings settings, MainWindowComponent.Builder mainWindow, PreferencesComponent.Builder preferencesWindow) { + this.settings = settings; this.mainWindow = mainWindow; this.preferencesWindow = preferencesWindow; } public void start() { LOG.trace("FxApplication.start()"); + + settings.theme().addListener(this::themeChanged); + loadSelectedStyleSheet(settings.theme().get()); if (Desktop.getDesktop().isSupported(Desktop.Action.APP_PREFERENCES)) { Desktop.getDesktop().setPreferencesHandler(this::handlePreferences); @@ -49,5 +58,25 @@ public class FxApplication extends Application { preferencesWindow.build().showPreferencesWindow(); } + private void themeChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) { + loadSelectedStyleSheet(newValue); + } + + private void loadSelectedStyleSheet(UiTheme theme) { + switch (theme) { + case CUSTOM: + // TODO + Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString()); + break; + case DARK: + // TODO + Application.setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString()); + break; + case LIGHT: + default: + Application.setUserAgentStylesheet(getClass().getResource("/css/theme.css").toString()); + break; + } + } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/FxApplicationComponent.java b/main/ui/src/main/java/org/cryptomator/ui/FxApplicationComponent.java index 165b143ea..3a8eb9d2b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/FxApplicationComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/FxApplicationComponent.java @@ -6,7 +6,6 @@ package org.cryptomator.ui; import dagger.Subcomponent; -import javafx.application.Application; import javafx.application.Platform; @FxApplicationScoped @@ -18,7 +17,6 @@ public interface FxApplicationComponent { default void start() { Platform.startup(() -> { assert Platform.isFxApplicationThread(); - Application.setUserAgentStylesheet(getClass().getResource("/css/theme.css").toString()); application().start(); }); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java index a48e7d584..e229ab2a4 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java @@ -8,6 +8,7 @@ import javafx.scene.control.ChoiceBox; import javafx.scene.control.TextField; import javafx.util.StringConverter; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.UiTheme; import org.cryptomator.common.settings.VolumeImpl; import org.cryptomator.common.settings.WebDavUrlScheme; import org.cryptomator.ui.common.FxController; @@ -20,6 +21,7 @@ public class PreferencesController implements FxController { private final Settings settings; private final BooleanBinding showWebDavSettings; + public ChoiceBox themeChoiceBox; public CheckBox checkForUpdatesCheckbox; public CheckBox debugModeCheckbox; public ChoiceBox volumeTypeChoicBox; @@ -34,6 +36,10 @@ public class PreferencesController implements FxController { } public void initialize() { + themeChoiceBox.getItems().addAll(UiTheme.values()); + themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); + themeChoiceBox.setConverter(new UiThemeConverter()); + checkForUpdatesCheckbox.selectedProperty().bindBidirectional(settings.checkForUpdates()); debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); @@ -77,6 +83,19 @@ public class PreferencesController implements FxController { /* Helper classes */ + private static class UiThemeConverter extends StringConverter { + + @Override + public String toString(UiTheme impl) { + return impl.getDisplayName(); + } + + @Override + public UiTheme fromString(String string) { + throw new UnsupportedOperationException(); + } + } + private static class WebDavUrlSchemeConverter extends StringConverter { @Override diff --git a/main/ui/src/main/resources/fxml/preferences.fxml b/main/ui/src/main/resources/fxml/preferences.fxml index fe3ebff87..de53ba511 100644 --- a/main/ui/src/main/resources/fxml/preferences.fxml +++ b/main/ui/src/main/resources/fxml/preferences.fxml @@ -17,6 +17,11 @@ + + + diff --git a/main/ui/src/main/resources/i18n/strings.properties b/main/ui/src/main/resources/i18n/strings.properties index 48e737ed5..d7be6fbaf 100644 --- a/main/ui/src/main/resources/i18n/strings.properties +++ b/main/ui/src/main/resources/i18n/strings.properties @@ -2,5 +2,6 @@ main.closeBtn.tooltip=Close main.settingsBtn.tooltip=Settings preferences.autoUpdateCheck=Check for updates automatically preferences.debugLogging=Enable debug logging +preferences.theme=Look & Feel preferences.volumeType=Volume type vaultlist.emptyList.onboardingInstruction=Click here to add a vault \ No newline at end of file diff --git a/main/ui/src/main/resources/i18n/strings_en.properties b/main/ui/src/main/resources/i18n/strings_en.properties index 48e737ed5..d7be6fbaf 100644 --- a/main/ui/src/main/resources/i18n/strings_en.properties +++ b/main/ui/src/main/resources/i18n/strings_en.properties @@ -2,5 +2,6 @@ main.closeBtn.tooltip=Close main.settingsBtn.tooltip=Settings preferences.autoUpdateCheck=Check for updates automatically preferences.debugLogging=Enable debug logging +preferences.theme=Look & Feel preferences.volumeType=Volume type vaultlist.emptyList.onboardingInstruction=Click here to add a vault \ No newline at end of file