فهرست منبع

Added UI theme switcher (issue #930)

Sebastian Stenzel 6 سال پیش
والد
کامیت
c917fb6a57

+ 16 - 6
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<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
 	private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
 	private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
+	private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
 
 	private Consumer<Settings> saveCmd;
 
@@ -43,7 +49,7 @@ public class Settings {
 	 * Package-private constructor; use {@link SettingsProvider}.
 	 */
 	Settings() {
-		directories.addListener((ListChangeListener.Change<? extends VaultSettings> 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<Settings> 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<UiTheme> theme() {
+		return theme;
+	}
 }

+ 15 - 0
main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java

@@ -34,6 +34,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
 		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<Settings> {
 				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<Settings> {
 		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<Settings> {
 		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<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
 		List<VaultSettings> result = new ArrayList<>();
 		in.beginArray();

+ 18 - 0
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;
+	}
+
+}

+ 30 - 1
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<? extends UiTheme> 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;
+		}
+	}
 
 }

+ 0 - 2
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();
 		});
 	}

+ 19 - 0
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<UiTheme> themeChoiceBox;
 	public CheckBox checkForUpdatesCheckbox;
 	public CheckBox debugModeCheckbox;
 	public ChoiceBox<VolumeImpl> 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<UiTheme> {
+
+		@Override
+		public String toString(UiTheme impl) {
+			return impl.getDisplayName();
+		}
+
+		@Override
+		public UiTheme fromString(String string) {
+			throw new UnsupportedOperationException();
+		}
+	}
+
 	private static class WebDavUrlSchemeConverter extends StringConverter<WebDavUrlScheme> {
 
 		@Override

+ 5 - 0
main/ui/src/main/resources/fxml/preferences.fxml

@@ -17,6 +17,11 @@
 		<Insets bottom="12" left="12" right="12" top="12"/>
 	</padding>
 	<children>
+		<HBox spacing="6" alignment="BASELINE_LEFT">
+			<Label text="%preferences.theme"/>
+			<ChoiceBox fx:id="themeChoiceBox"/>
+		</HBox>
+
 		<CheckBox fx:id="checkForUpdatesCheckbox" text="%preferences.autoUpdateCheck"/>
 
 		<CheckBox fx:id="debugModeCheckbox" text="%preferences.debugLogging"/>

+ 1 - 0
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

+ 1 - 0
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