Migrated from deprecated MacFunctions to UiAppearanceProvider for UI theme switching

This commit is contained in:
Sebastian Stenzel
2020-11-05 17:49:39 +01:00
parent 18b408a83a
commit c0e2d01297
5 changed files with 78 additions and 44 deletions

View File

@@ -25,9 +25,9 @@
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>1.9.12</cryptomator.cryptofs.version>
<cryptomator.integrations.version>0.1.4</cryptomator.integrations.version>
<cryptomator.integrations.version>0.1.5</cryptomator.integrations.version>
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>0.1.0-beta1</cryptomator.integrations.mac.version>
<cryptomator.integrations.mac.version>0.1.0-beta2</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>0.1.0-beta1</cryptomator.integrations.linux.version>
<cryptomator.jni.version>2.2.3</cryptomator.jni.version>
<cryptomator.fuse.version>1.2.5</cryptomator.fuse.version>

View File

@@ -6,8 +6,10 @@ import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiAppearance;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.common.VaultService;
@@ -42,20 +44,21 @@ public class FxApplication extends Application {
private final Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
private final Provider<QuitComponent.Builder> quitWindowBuilderProvider;
private final Optional<MacFunctions> macFunctions;
private final Optional<UiAppearanceProvider> appearanceProvider;
private final VaultService vaultService;
private final LicenseHolder licenseHolder;
private final BooleanBinding hasVisibleStages;
private Optional<String> macApperanceObserverIdentifier = Optional.empty();
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
@Inject
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<QuitComponent.Builder> quitWindowBuilderProvider, Optional<MacFunctions> macFunctions, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet<Stage> visibleStages) {
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<QuitComponent.Builder> quitWindowBuilderProvider, Optional<MacFunctions> macFunctions, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet<Stage> visibleStages) {
this.settings = settings;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
this.quitWindowBuilderProvider = quitWindowBuilderProvider;
this.macFunctions = macFunctions;
this.appearanceProvider = appearanceProvider;
this.vaultService = vaultService;
this.licenseHolder = licenseHolder;
this.hasVisibleStages = Bindings.isNotEmpty(visibleStages);
@@ -67,7 +70,7 @@ public class FxApplication extends Application {
EasyBind.subscribe(hasVisibleStages, this::hasVisibleStagesChanged);
settings.theme().addListener(this::themeChanged);
settings.theme().addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme().get());
}
@@ -116,45 +119,60 @@ public class FxApplication extends Application {
return vaultService;
}
private void themeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
if (macApperanceObserverIdentifier.isPresent()) {
macFunctions.map(MacFunctions::uiAppearance).ifPresent(uiAppearance -> uiAppearance.removeListener(macApperanceObserverIdentifier.get()));
macApperanceObserverIdentifier = Optional.empty();
}
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
appearanceProvider.ifPresent(appearanceProvider -> {
try {
appearanceProvider.removeListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to disable automatic theme switching.");
}
});
loadSelectedStyleSheet(newValue);
}
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
switch (theme) {
case LIGHT -> setToLightTheme();
case DARK -> setToDarkTheme();
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
case AUTOMATIC -> {
macFunctions.map(MacFunctions::uiAppearance).ifPresent(uiAppearance -> {
macApperanceObserverIdentifier = Optional.of(uiAppearance.addListener(this::macInterfaceThemeChanged));
appearanceProvider.ifPresent(appearanceProvider -> {
try {
appearanceProvider.addListener(systemInterfaceThemeListener);
} catch (UiAppearanceException e) {
LOG.error("Failed to enable automatic theme switching.");
}
});
macInterfaceThemeChanged();
applySystemTheme();
}
}
}
private void macInterfaceThemeChanged() {
macFunctions.map(MacFunctions::uiAppearance).ifPresent(uiAppearance -> {
switch (uiAppearance.getCurrentInterfaceStyle()) {
case LIGHT -> setToLightTheme();
case DARK -> setToDarkTheme();
}
private void systemInterfaceThemeChanged(Theme theme) {
switch (theme) {
case LIGHT -> applyLightTheme();
case DARK -> applyDarkTheme();
}
}
private void applySystemTheme() {
appearanceProvider.ifPresent(appearanceProvider -> {
systemInterfaceThemeChanged(appearanceProvider.getSystemTheme());
});
}
private void setToLightTheme() {
private void applyLightTheme() {
Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString());
macFunctions.map(MacFunctions::uiAppearance).ifPresent(JniException.ignore(MacApplicationUiAppearance::setToAqua));
appearanceProvider.ifPresent(appearanceProvider -> {
appearanceProvider.adjustToTheme(Theme.LIGHT);
});
}
private void setToDarkTheme() {
private void applyDarkTheme() {
Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString());
macFunctions.map(MacFunctions::uiAppearance).ifPresent(JniException.ignore(MacApplicationUiAppearance::setToDarkAqua));
appearanceProvider.ifPresent(appearanceProvider -> {
appearanceProvider.adjustToTheme(Theme.DARK);
});
}
}

View File

@@ -3,18 +3,28 @@ package org.cryptomator.ui.launcher;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.JniModule;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.fxapp.FxApplicationComponent;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@Module(includes = {JniModule.class}, subcomponents = {TrayMenuComponent.class, FxApplicationComponent.class})
public abstract class UiLauncherModule {
@Provides
@Singleton
static Optional<UiAppearanceProvider> provideAppearanceProvider() {
return ServiceLoader.load(UiAppearanceProvider.class).findFirst();
}
@Provides
@Singleton
static ResourceBundle provideLocalization() {

View File

@@ -1,6 +1,9 @@
package org.cryptomator.ui.traymenu;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.jni.MacFunctions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,20 +20,26 @@ public class TrayIconController {
private static final Logger LOG = LoggerFactory.getLogger(TrayIconController.class);
private final TrayImageFactory imageFactory;
private final Optional<UiAppearanceProvider> appearanceProvider;
private final TrayMenuController trayMenuController;
private final TrayIcon trayIcon;
private final Optional<MacFunctions> macFunctions;
@Inject
TrayIconController(TrayImageFactory imageFactory, TrayMenuController trayMenuController, Optional<MacFunctions> macFunctions) {
TrayIconController(TrayImageFactory imageFactory, TrayMenuController trayMenuController, Optional<UiAppearanceProvider> appearanceProvider) {
this.trayMenuController = trayMenuController;
this.imageFactory = imageFactory;
this.appearanceProvider = appearanceProvider;
this.trayIcon = new TrayIcon(imageFactory.loadImage(), "Cryptomator", trayMenuController.getMenu());
this.macFunctions = macFunctions;
}
public void initializeTrayIcon() {
macFunctions.map(MacFunctions::uiAppearance).ifPresent(uiAppearance -> uiAppearance.addListener(this::macInterfaceThemeChanged));
appearanceProvider.ifPresent(appearanceProvider -> {
try {
appearanceProvider.addListener(this::systemInterfaceThemeChanged);
} catch (UiAppearanceException e) {
LOG.error("Failed to enable automatic tray icon theme switching.");
}
});
trayIcon.setImageAutoSize(true);
if (SystemUtils.IS_OS_WINDOWS) {
@@ -47,8 +56,8 @@ public class TrayIconController {
trayMenuController.initTrayMenu();
}
private void macInterfaceThemeChanged() {
trayIcon.setImage(imageFactory.loadImage());
private void systemInterfaceThemeChanged(Theme theme) {
trayIcon.setImage(imageFactory.loadImage()); // TODO refactor "theme" is re-queried in loadImage()
}
}

View File

@@ -1,9 +1,8 @@
package org.cryptomator.ui.traymenu;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.jni.MacApplicationUiAppearance;
import org.cryptomator.jni.MacApplicationUiInterfaceStyle;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.integrations.uiappearance.Theme;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import javax.inject.Inject;
import java.awt.Image;
@@ -13,11 +12,11 @@ import java.util.Optional;
@TrayMenuScoped
class TrayImageFactory {
private final Optional<MacFunctions> macFunctions;
private final Optional<UiAppearanceProvider> appearanceProvider;
@Inject
TrayImageFactory(Optional<MacFunctions> macFunctions) {
this.macFunctions = macFunctions;
TrayImageFactory(Optional<UiAppearanceProvider> appearanceProvider) {
this.appearanceProvider = appearanceProvider;
}
public Image loadImage() {
@@ -26,10 +25,8 @@ class TrayImageFactory {
}
private String getMacResourceName() {
MacApplicationUiInterfaceStyle interfaceStyle = macFunctions.map(MacFunctions::uiAppearance) //
.map(MacApplicationUiAppearance::getCurrentInterfaceStyle) //
.orElse(MacApplicationUiInterfaceStyle.LIGHT);
return switch (interfaceStyle) {
var theme = appearanceProvider.map(UiAppearanceProvider::getSystemTheme).orElse(Theme.LIGHT);
return switch (theme) {
case DARK -> "/img/tray_icon_mac_white.png";
case LIGHT -> "/img/tray_icon_mac_black.png";
};