From 5e687f2c17108ce504cbbc05db495815cb0dda75 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Tue, 5 Nov 2019 20:43:56 +0100 Subject: [PATCH] Dark mode: light, dark, follow system Signed-off-by: tobiasKaminsky --- .../client/preferences/AppPreferences.java | 12 ++++---- .../preferences/AppPreferencesImpl.java | 22 ++++++++------ .../client/preferences/DarkMode.java | 27 +++++++++++++++++ .../java/com/owncloud/android/MainApp.java | 19 ++++++++---- .../android/ui/activity/BaseActivity.java | 13 +++++---- .../android/ui/activity/DrawerActivity.java | 12 ++++---- .../android/ui/activity/SettingsActivity.java | 29 +++++++++++++++---- .../ui/activity/ThemedPreferenceActivity.java | 9 ++++-- src/main/res/values/strings.xml | 1 + src/main/res/values/styles.xml | 3 -- src/main/res/xml/preferences.xml | 9 ++---- .../preferences/TestAppPreferences.java | 27 ++++++++++------- 12 files changed, 122 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/nextcloud/client/preferences/DarkMode.java diff --git a/src/main/java/com/nextcloud/client/preferences/AppPreferences.java b/src/main/java/com/nextcloud/client/preferences/AppPreferences.java index 5c6d9107a6..241a7ec032 100644 --- a/src/main/java/com/nextcloud/client/preferences/AppPreferences.java +++ b/src/main/java/com/nextcloud/client/preferences/AppPreferences.java @@ -39,7 +39,7 @@ public interface AppPreferences { * events. */ interface Listener { - default void onDarkThemeEnabledChanged(boolean enabled) { + default void onDarkThemeModeChanged(DarkMode mode) { /* default empty implementation */ }; } @@ -274,18 +274,18 @@ public interface AppPreferences { int getUploaderBehaviour(); /** - * Enable dark theme. + * Changes dark theme mode * * This is reactive property. Listeners will be invoked if registered. * - * @param enabled true to turn dark theme on, false to turn it off + * @param mode dark mode setting: on, off, system */ - void setDarkThemeEnabled(boolean enabled); + void setDarkThemeMode(DarkMode mode); /** - * @return true if application uses dark UI theme, false otherwise + * @return dark mode setting: on, off, system */ - boolean isDarkThemeEnabled(); + DarkMode getDarkThemeMode(); /** * Saves the uploader behavior which the user has set last. diff --git a/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java b/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java index 60867fabf0..a231116b18 100644 --- a/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java +++ b/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java @@ -21,7 +21,6 @@ package com.nextcloud.client.preferences; -import android.accounts.Account; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; @@ -57,6 +56,7 @@ public final class AppPreferencesImpl implements AppPreferences { */ public static final String AUTO_PREF__LAST_SEEN_VERSION_CODE = "lastSeenVersionCode"; public static final String STORAGE_PATH = "storage_path"; + public static final String PREF__DARK_THEME = "dark_theme_mode"; public static final float DEFAULT_GRID_COLUMN = 4.0f; private static final String AUTO_PREF__LAST_UPLOAD_PATH = "last_upload_path"; @@ -79,7 +79,6 @@ public final class AppPreferencesImpl implements AppPreferences { private static final String PREF__AUTO_UPLOAD_INIT = "autoUploadInit"; private static final String PREF__FOLDER_SORT_ORDER = "folder_sort_order"; private static final String PREF__FOLDER_LAYOUT = "folder_layout"; - static final String PREF__DARK_THEME_ENABLED = "dark_theme_enabled"; private static final String PREF__LOCK_TIMESTAMP = "lock_timestamp"; private static final String PREF__SHOW_MEDIA_SCAN_NOTIFICATIONS = "show_media_scan_notifications"; @@ -121,10 +120,10 @@ public final class AppPreferencesImpl implements AppPreferences { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(PREF__DARK_THEME_ENABLED.equals(key)) { - boolean enabled = preferences.isDarkThemeEnabled(); + if (PREF__DARK_THEME.equals(key)) { + DarkMode mode = preferences.getDarkThemeMode(); for(Listener l : listeners) { - l.onDarkThemeEnabledChanged(enabled); + l.onDarkThemeModeChanged(mode); } } } @@ -408,13 +407,18 @@ public final class AppPreferencesImpl implements AppPreferences { } @Override - public void setDarkThemeEnabled(boolean enabled) { - preferences.edit().putBoolean(PREF__DARK_THEME_ENABLED, enabled).apply(); + public void setDarkThemeMode(DarkMode mode) { + preferences.edit().putString(PREF__DARK_THEME, mode.name()).apply(); } @Override - public boolean isDarkThemeEnabled() { - return preferences.getBoolean(PREF__DARK_THEME_ENABLED, false); + public DarkMode getDarkThemeMode() { + try { + return DarkMode.valueOf(preferences.getString(PREF__DARK_THEME, DarkMode.LIGHT.name())); + } catch (ClassCastException e) { + preferences.edit().putString(PREF__DARK_THEME, DarkMode.LIGHT.name()).apply(); + return DarkMode.DARK; + } } @Override diff --git a/src/main/java/com/nextcloud/client/preferences/DarkMode.java b/src/main/java/com/nextcloud/client/preferences/DarkMode.java new file mode 100644 index 0000000000..3437e0ac7d --- /dev/null +++ b/src/main/java/com/nextcloud/client/preferences/DarkMode.java @@ -0,0 +1,27 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2019 Tobias Kaminsky + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.client.preferences; + +public enum DarkMode { + DARK, LIGHT, SYSTEM +} diff --git a/src/main/java/com/owncloud/android/MainApp.java b/src/main/java/com/owncloud/android/MainApp.java index 3b7f088efe..b78d6ef628 100644 --- a/src/main/java/com/owncloud/android/MainApp.java +++ b/src/main/java/com/owncloud/android/MainApp.java @@ -57,6 +57,7 @@ import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.client.onboarding.OnboardingService; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferencesImpl; +import com.nextcloud.client.preferences.DarkMode; import com.owncloud.android.authentication.PassCodeManager; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.MediaFolder; @@ -247,7 +248,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector { @SuppressFBWarnings("ST") @Override public void onCreate() { - setAppTheme(preferences.isDarkThemeEnabled()); + setAppTheme(preferences.getDarkThemeMode()); super.onCreate(); insertConscrypt(); @@ -821,11 +822,17 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector { } - public static void setAppTheme(Boolean darkTheme) { - if (darkTheme) { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - } else { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + public static void setAppTheme(DarkMode mode) { + switch (mode) { + case LIGHT: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + break; + case DARK: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + break; + case SYSTEM: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + break; } } } diff --git a/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java b/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java index 327bb10387..29c544b89c 100644 --- a/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java @@ -15,6 +15,7 @@ import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.java.util.Optional; +import com.nextcloud.client.preferences.DarkMode; import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -59,8 +60,8 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab private AppPreferences.Listener onPreferencesChanged = new AppPreferences.Listener() { @Override - public void onDarkThemeEnabledChanged(boolean enabled) { - BaseActivity.this.onThemeSettingsChanged(); + public void onDarkThemeModeChanged(DarkMode mode) { + onThemeSettingsModeChanged(); } }; @@ -91,7 +92,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab super.onResume(); paused = false; - if(themeChangePending) { + if (themeChangePending) { recreate(); } } @@ -129,8 +130,8 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab Log_OC.v(TAG, "onRestart() end"); } - private void onThemeSettingsChanged() { - if(paused) { + private void onThemeSettingsModeChanged() { + if (paused) { themeChangePending = true; } else { recreate(); @@ -226,7 +227,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab return Optional.empty(); } } - + public FileDataStorageManager getStorageManager() { return storageManager; } diff --git a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 5984380b1f..b955c66755 100644 --- a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -57,11 +57,11 @@ import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.google.android.material.navigation.NavigationView; import com.nextcloud.client.account.User; -import com.nextcloud.client.account.UserAccountManager; import com.nextcloud.client.di.Injectable; import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.onboarding.FirstRunActivity; import com.nextcloud.client.preferences.AppPreferences; +import com.nextcloud.client.preferences.DarkMode; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.PassCodeManager; @@ -71,7 +71,6 @@ import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.ExternalLink; import com.owncloud.android.lib.common.ExternalLinkType; -import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.Quota; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.accounts.ExternalLinksOperation; @@ -1275,9 +1274,12 @@ public abstract class DrawerActivity extends ToolbarActivity @Override protected void onResume() { super.onResume(); - getDelegate().setLocalNightMode(preferences.isDarkThemeEnabled() ? - AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); - getDelegate().applyDayNight(); + if (AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) { + + getDelegate().setLocalNightMode(DarkMode.DARK == preferences.getDarkThemeMode() ? + AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + getDelegate().applyDayNight(); + } setDrawerMenuItemChecked(mCheckedMenuItem); } diff --git a/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index 03563a7304..aa49df51c6 100644 --- a/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -59,6 +59,7 @@ import com.nextcloud.client.logger.ui.LogsActivity; import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.AppPreferencesImpl; +import com.nextcloud.client.preferences.DarkMode; import com.owncloud.android.BuildConfig; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -79,6 +80,7 @@ import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.ThemeUtils; import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; @@ -106,6 +108,7 @@ public class SettingsActivity extends ThemedPreferenceActivity public static final String LOCK_PASSCODE = "passcode"; public static final String LOCK_DEVICE_CREDENTIALS = "device_credentials"; + public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint"; public static final String PREFERENCE_SHOW_MEDIA_SCAN_NOTIFICATIONS = "show_media_scan_notifications"; @@ -692,13 +695,27 @@ public class SettingsActivity extends ThemedPreferenceActivity loadStoragePath(); - SwitchPreference themePref = (SwitchPreference) findPreference("dark_theme_enabled"); - boolean darkThemeEnabled = preferences.isDarkThemeEnabled(); - int summaryResId = darkThemeEnabled ? R.string.prefs_value_theme_dark : R.string.prefs_value_theme_light; - themePref.setSummary(summaryResId); + ListPreference themePref = (ListPreference) findPreference("darkTheme"); + + List themeEntries = new ArrayList<>(3); + themeEntries.add(getString(R.string.prefs_value_theme_light)); + themeEntries.add(getString(R.string.prefs_value_theme_dark)); + themeEntries.add(getString(R.string.prefs_value_theme_system)); + + List themeValues = new ArrayList<>(3); + themeValues.add(DarkMode.LIGHT.name()); + themeValues.add(DarkMode.DARK.name()); + themeValues.add(DarkMode.SYSTEM.name()); + + themePref.setEntries(themeEntries.toArray(new String[0])); + themePref.setEntryValues(themeValues.toArray(new String[0])); + themePref.setSummary(themePref.getEntry().length() == 0 ? DarkMode.LIGHT.name() : themePref.getEntry()); + themePref.setOnPreferenceChangeListener((preference, newValue) -> { - boolean enabled = (Boolean)newValue; - MainApp.setAppTheme(enabled); + DarkMode mode = DarkMode.valueOf((String) newValue); + preferences.setDarkThemeMode(mode); + MainApp.setAppTheme(mode); + return true; }); } diff --git a/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java b/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java index 058a1bed8a..2cf02cd145 100644 --- a/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/ThemedPreferenceActivity.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.preference.PreferenceActivity; import com.nextcloud.client.preferences.AppPreferences; +import com.nextcloud.client.preferences.DarkMode; import javax.inject.Inject; @@ -41,8 +42,10 @@ public class ThemedPreferenceActivity extends PreferenceActivity { private AppPreferences.Listener onThemeChangedListener = new AppPreferences.Listener() { @Override - public void onDarkThemeEnabledChanged(boolean enabled) { - if(paused) { + public void onDarkThemeModeChanged(DarkMode mode) { + preferences.setDarkThemeMode(mode); + + if (paused) { themeChangePending = true; return; } @@ -73,7 +76,7 @@ public class ThemedPreferenceActivity extends PreferenceActivity { super.onResume(); paused = false; - if(themeChangePending) { + if (themeChangePending) { recreate(); } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a1b8a6e770..c52d623394 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -58,6 +58,7 @@ Imprint Light Dark + Follow system Theme diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index bac5c803c7..518e301cfe 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -322,7 +322,4 @@ fitCenter center_vertical - diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 67e479c50c..c95fc9ac82 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -26,13 +26,10 @@ - + android:key="darkTheme" + android:summary="%s" />