mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 23:28:42 +03:00
Merge pull request #4922 from nextcloud/themeFollowOS
Dark mode: light, dark, follow system
This commit is contained in:
commit
fd48163e22
12 changed files with 122 additions and 61 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
27
src/main/java/com/nextcloud/client/preferences/DarkMode.java
Normal file
27
src/main/java/com/nextcloud/client/preferences/DarkMode.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.preferences;
|
||||
|
||||
public enum DarkMode {
|
||||
DARK, LIGHT, SYSTEM
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> 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<String> 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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<string name="prefs_imprint">Imprint</string>
|
||||
<string name="prefs_value_theme_light">Light</string>
|
||||
<string name="prefs_value_theme_dark">Dark</string>
|
||||
<string name="prefs_value_theme_system">Follow system</string>
|
||||
<string name="prefs_theme_title">Theme</string>
|
||||
|
||||
|
||||
|
|
|
@ -322,7 +322,4 @@
|
|||
<item name="android:scaleType">fitCenter</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
</style>
|
||||
<style name="SwitchPreference" parent="Widget.AppCompat.CompoundButton.Switch">
|
||||
<item name="android:colorForeground">@color/fg_default</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
@ -26,13 +26,10 @@
|
|||
<ListPreference
|
||||
android:title="@string/prefs_storage_path"
|
||||
android:key="storage_path"/>
|
||||
<com.owncloud.android.ui.ThemeableSwitchPreference
|
||||
android:id="@+id/dark_theme_preference"
|
||||
android:defaultValue="@string/prefs_value_theme_light"
|
||||
android:key="dark_theme_enabled"
|
||||
android:summary="%s"
|
||||
<ListPreference
|
||||
android:title="@string/prefs_theme_title"
|
||||
android:theme="@style/SwitchPreference"/>
|
||||
android:key="darkTheme"
|
||||
android:summary="%s" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/drawer_synced_folders"
|
||||
|
|
|
@ -11,10 +11,15 @@ import org.junit.runner.RunWith;
|
|||
import org.junit.runners.Suite;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
TestAppPreferences.Preferences.class,
|
||||
|
@ -45,7 +50,7 @@ public class TestAppPreferences {
|
|||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(appPreferences.isDarkThemeEnabled()).thenReturn(true);
|
||||
when(appPreferences.getDarkThemeMode()).thenReturn(DarkMode.DARK);
|
||||
registry = new AppPreferencesImpl.ListenerRegistry(appPreferences);
|
||||
}
|
||||
|
||||
|
@ -64,21 +69,21 @@ public class TestAppPreferences {
|
|||
registry.remove(listener2);
|
||||
registry.remove(listener3);
|
||||
return null;
|
||||
}).when(listener2).onDarkThemeEnabledChanged(anyBoolean());
|
||||
}).when(listener2).onDarkThemeModeChanged(DarkMode.DARK);
|
||||
|
||||
// WHEN
|
||||
// callback is called twice
|
||||
registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME_ENABLED);
|
||||
registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME_ENABLED);
|
||||
registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME);
|
||||
registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME);
|
||||
|
||||
// THEN
|
||||
// no ConcurrentModificationException
|
||||
// 1st time, all listeners (including removed) are called
|
||||
// 2nd time removed callbacks are not called
|
||||
verify(listener1, times(2)).onDarkThemeEnabledChanged(anyBoolean());
|
||||
verify(listener2).onDarkThemeEnabledChanged(anyBoolean());
|
||||
verify(listener3).onDarkThemeEnabledChanged(anyBoolean());
|
||||
verify(listener4, times(2)).onDarkThemeEnabledChanged(anyBoolean());
|
||||
verify(listener1, times(2)).onDarkThemeModeChanged(DarkMode.DARK);
|
||||
verify(listener2).onDarkThemeModeChanged(DarkMode.DARK);
|
||||
verify(listener3).onDarkThemeModeChanged(DarkMode.DARK);
|
||||
verify(listener4, times(2)).onDarkThemeModeChanged(DarkMode.DARK);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -90,7 +95,7 @@ public class TestAppPreferences {
|
|||
|
||||
// WHEN
|
||||
// callback is called
|
||||
registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME_ENABLED);
|
||||
registry.onSharedPreferenceChanged(NOT_USED_NULL, AppPreferencesImpl.PREF__DARK_THEME);
|
||||
|
||||
// THEN
|
||||
// nothing happens
|
||||
|
|
Loading…
Reference in a new issue