mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 20:21:27 +00:00
Added UI theme switcher (issue #930)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user