Added Support for System Wide Dark Theme (#3460)

* Added Support for System Wide Dark Theme

* changed methods to private

* Moved Strings to strings.xml

* Used Dagger to reduce code repetition

* Changes made as per review suggestions

* Minor Changes

* Fixes as per suggestions

* Minor Fixes as per suggestion

* made the variables static

* removed irrelevant code
This commit is contained in:
Seán Mac Gillicuddy 2020-03-05 14:33:57 +00:00 committed by GitHub
parent 65ec071493
commit 1584ffe0e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 42 deletions

View file

@ -55,6 +55,7 @@ import fr.free.nrw.commons.explore.categories.ExploreActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.theme.NavigationBaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.SystemThemeUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import retrofit2.Call; import retrofit2.Call;
@ -83,6 +84,9 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Inject @Inject
LoginClient loginClient; LoginClient loginClient;
@Inject
SystemThemeUtils systemThemeUtils;
@BindView(R.id.login_button) @BindView(R.id.login_button)
Button loginButton; Button loginButton;
@ -121,7 +125,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
.getCommonsApplicationComponent() .getCommonsApplicationComponent()
.inject(this); .inject(this);
boolean isDarkTheme = applicationKvStore.getBoolean("theme", false); boolean isDarkTheme = systemThemeUtils.isDeviceInNightMode();
setTheme(isDarkTheme ? R.style.DarkAppTheme : R.style.LightAppTheme); setTheme(isDarkTheme ? R.style.DarkAppTheme : R.style.LightAppTheme);
getDelegate().installViewFactory(); getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState); getDelegate().onCreate(savedInstanceState);
@ -133,7 +137,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
usernameEdit.addTextChangedListener(textWatcher); usernameEdit.addTextChangedListener(textWatcher);
passwordEdit.addTextChangedListener(textWatcher); passwordEdit.addTextChangedListener(textWatcher);
twoFactorEdit.addTextChangedListener(textWatcher); twoFactorEdit.addTextChangedListener(textWatcher);
if (ConfigUtils.isBetaFlavour()) { if (ConfigUtils.isBetaFlavour()) {
loginCredentials.setText(getString(R.string.login_credential)); loginCredentials.setText(getString(R.string.login_credential));
} else { } else {
@ -264,7 +268,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
new Callback<MwQueryResponse>() { new Callback<MwQueryResponse>() {
@Override @Override
public void onResponse(Call<MwQueryResponse> call, public void onResponse(Call<MwQueryResponse> call,
Response<MwQueryResponse> response) { Response<MwQueryResponse> response) {
loginClient.login(commonsWikiSite, username, password, null, twoFactorCode, loginClient.login(commonsWikiSite, username, password, null, twoFactorCode,
response.body().query().loginToken(), new LoginCallback() { response.body().query().loginToken(), new LoginCallback() {
@Override @Override
@ -275,7 +279,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Override @Override
public void twoFactorPrompt(@NonNull Throwable caught, public void twoFactorPrompt(@NonNull Throwable caught,
@Nullable String token) { @Nullable String token) {
Timber.d("Requesting 2FA prompt"); Timber.d("Requesting 2FA prompt");
hideProgress(); hideProgress();
askUserForTwoFactorAuth(); askUserForTwoFactorAuth();

View file

@ -9,7 +9,6 @@ import android.content.IntentFilter;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -93,6 +92,7 @@ import fr.free.nrw.commons.utils.LocationUtils;
import fr.free.nrw.commons.utils.NearbyFABUtils; import fr.free.nrw.commons.utils.NearbyFABUtils;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.SystemThemeUtils;
import fr.free.nrw.commons.utils.UiUtils; import fr.free.nrw.commons.utils.UiUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener; import fr.free.nrw.commons.wikidata.WikidataEditListener;
@ -156,6 +156,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Inject ContributionController controller; @Inject ContributionController controller;
@Inject WikidataEditListener wikidataEditListener; @Inject WikidataEditListener wikidataEditListener;
@Inject
SystemThemeUtils systemThemeUtils;
private NearbyFilterSearchRecyclerViewAdapter nearbyFilterSearchRecyclerViewAdapter; private NearbyFilterSearchRecyclerViewAdapter nearbyFilterSearchRecyclerViewAdapter;
private BottomSheetBehavior bottomSheetListBehavior; private BottomSheetBehavior bottomSheetListBehavior;
@ -210,7 +213,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
isDarkTheme = applicationKvStore.getBoolean("theme", false); isDarkTheme = systemThemeUtils.isDeviceInNightMode();
cameraMoveListener= () -> presenter.onCameraMove(mapBox.getCameraPosition().target); cameraMoveListener= () -> presenter.onCameraMove(mapBox.getCameraPosition().target);
addCheckBoxCallback(); addCheckBoxCallback();
presenter.attachView(this); presenter.attachView(this);
@ -821,10 +824,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
private void showFABs() { private void showFABs() {
NearbyFABUtils.addAnchorToBigFABs(fabPlus, bottomSheetDetails.getId()); NearbyFABUtils.addAnchorToBigFABs(fabPlus, bottomSheetDetails.getId());
fabPlus.show(); fabPlus.show();
NearbyFABUtils.addAnchorToSmallFABs(fabGallery, getView().findViewById(R.id.empty_view).getId()); NearbyFABUtils.addAnchorToSmallFABs(fabGallery, getView().findViewById(R.id.empty_view).getId());
NearbyFABUtils.addAnchorToSmallFABs(fabCamera, getView().findViewById(R.id.empty_view1).getId()); NearbyFABUtils.addAnchorToSmallFABs(fabCamera, getView().findViewById(R.id.empty_view1).getId());
} }
/** /**

View file

@ -9,6 +9,7 @@ public class Prefs {
public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged"; public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged";
public static final String MANAGED_EXIF_TAGS = "managed_exif_tags"; public static final String MANAGED_EXIF_TAGS = "managed_exif_tags";
public static final String KEY_LANGUAGE_VALUE = "languageDescription"; public static final String KEY_LANGUAGE_VALUE = "languageDescription";
public static final String KEY_THEME_VALUE = "appThemePref";
public static class Licenses { public static class Licenses {
public static final String CC_BY_SA_3 = "CC BY-SA 3.0"; public static final String CC_BY_SA_3 = "CC BY-SA 3.0";

View file

@ -12,18 +12,15 @@ import android.preference.SwitchPreference;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import com.google.gson.reflect.TypeToken;
import com.karumi.dexter.Dexter; import com.karumi.dexter.Dexter;
import com.karumi.dexter.listener.PermissionGrantedResponse; import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.single.BasePermissionListener; import com.karumi.dexter.listener.single.BasePermissionListener;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -37,14 +34,19 @@ import fr.free.nrw.commons.upload.Language;
import fr.free.nrw.commons.utils.PermissionUtils; import fr.free.nrw.commons.utils.PermissionUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import static fr.free.nrw.commons.utils.SystemThemeUtils.THEME_MODE_DEFAULT;
public class SettingsFragment extends PreferenceFragment { public class SettingsFragment extends PreferenceFragment {
@Inject @Inject
@Named("default_preferences") @Named("default_preferences")
JsonKvStore defaultKvStore; JsonKvStore defaultKvStore;
@Inject @Inject
CommonsLogSender commonsLogSender; CommonsLogSender commonsLogSender;
private ListPreference listPreference;
private ListPreference themeListPreference;
private ListPreference langListPreference;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -57,11 +59,8 @@ public class SettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource // Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
SwitchPreference themePreference = (SwitchPreference) findPreference("theme"); themeListPreference = (ListPreference) findPreference(Prefs.KEY_THEME_VALUE);
themePreference.setOnPreferenceChangeListener((preference, newValue) -> { prepareTheme();
getActivity().recreate();
return true;
});
//Check if the Author Name switch is enabled and appropriately handle the author name usage //Check if the Author Name switch is enabled and appropriately handle the author name usage
SwitchPreference useAuthorName = (SwitchPreference) findPreference("useAuthorName"); SwitchPreference useAuthorName = (SwitchPreference) findPreference("useAuthorName");
@ -117,7 +116,7 @@ public class SettingsFragment extends PreferenceFragment {
} }
}); });
listPreference = (ListPreference) findPreference("descriptionDefaultLanguagePref"); langListPreference = (ListPreference) findPreference("descriptionDefaultLanguagePref");
prepareLanguages(); prepareLanguages();
Preference betaTesterPreference = findPreference("becomeBetaTester"); Preference betaTesterPreference = findPreference("becomeBetaTester");
betaTesterPreference.setOnPreferenceClickListener(preference -> { betaTesterPreference.setOnPreferenceClickListener(preference -> {
@ -144,10 +143,32 @@ public class SettingsFragment extends PreferenceFragment {
} }
} }
/**
* Uses previously saved theme if there is any, if not then uses default.
*/
private void prepareTheme() {
themeListPreference.setSummary(getThemeSummary(getCurrentTheme()));
themeListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
getActivity().recreate();
return true;
});
}
private CharSequence getThemeSummary(String value) {
int prefIndex = themeListPreference.findIndexOfValue(value);
return themeListPreference.getEntries()[prefIndex];
}
private String getCurrentTheme() {
return defaultKvStore.getString(Prefs.KEY_THEME_VALUE, THEME_MODE_DEFAULT);
}
/** /**
* Prepares language summary and language codes list and adds them to list preference as pairs. * Prepares language summary and language codes list and adds them to list preference as pairs.
* Uses previously saved language if there is any, if not uses phone local as initial language. * Uses previously saved language if there is any, if not uses phone local as initial language.
* Adds preference changed listener and saves value choosen by user to shared preferences * Adds preference changed listener and saves value chosen by user to shared preferences
* to remember later * to remember later
*/ */
private void prepareLanguages() { private void prepareLanguages() {
@ -167,26 +188,26 @@ public class SettingsFragment extends PreferenceFragment {
CharSequence[] languageNames = languageNamesList.toArray(new CharSequence[0]); CharSequence[] languageNames = languageNamesList.toArray(new CharSequence[0]);
CharSequence[] languageCodes = languageCodesList.toArray(new CharSequence[0]); CharSequence[] languageCodes = languageCodesList.toArray(new CharSequence[0]);
// Add all languages and languages codes to lists preference as pair // Add all languages and languages codes to lists preference as pair
listPreference.setEntries(languageNames); langListPreference.setEntries(languageNames);
listPreference.setEntryValues(languageCodes); langListPreference.setEntryValues(languageCodes);
// Gets current language code from shared preferences // Gets current language code from shared preferences
String languageCode = getCurrentLanguageCode(); String languageCode = getCurrentLanguageCode();
if(languageCode.equals("")){ if (languageCode.equals("")){
// If current language code is empty, means none selected by user yet so use phone local // If current language code is empty, means none selected by user yet so use phone local
listPreference.setSummary(Locale.getDefault().getDisplayLanguage()); langListPreference.setSummary(Locale.getDefault().getDisplayLanguage());
listPreference.setValue(Locale.getDefault().getLanguage()); langListPreference.setValue(Locale.getDefault().getLanguage());
} else { } else {
// If any language is selected by user previously, use it // If any language is selected by user previously, use it
int prefIndex = listPreference.findIndexOfValue(languageCode); int prefIndex = langListPreference.findIndexOfValue(languageCode);
listPreference.setSummary(listPreference.getEntries()[prefIndex]); langListPreference.setSummary(langListPreference.getEntries()[prefIndex]);
listPreference.setValue(languageCode); langListPreference.setValue(languageCode);
} }
listPreference.setOnPreferenceChangeListener((preference, newValue) -> { langListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
String userSelectedValue = (String) newValue; String userSelectedValue = (String) newValue;
int prefIndex = listPreference.findIndexOfValue(userSelectedValue); int prefIndex = langListPreference.findIndexOfValue(userSelectedValue);
listPreference.setSummary(listPreference.getEntries()[prefIndex]); langListPreference.setSummary(langListPreference.getEntries()[prefIndex]);
saveLanguageValue(userSelectedValue); saveLanguageValue(userSelectedValue);
return true; return true;
}); });

View file

@ -8,6 +8,7 @@ import javax.inject.Named;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity; import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.utils.SystemThemeUtils;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
public abstract class BaseActivity extends CommonsDaggerAppCompatActivity { public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
@ -15,20 +16,23 @@ public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
@Named("default_preferences") @Named("default_preferences")
public JsonKvStore defaultKvStore; public JsonKvStore defaultKvStore;
@Inject
SystemThemeUtils systemThemeUtils;
protected CompositeDisposable compositeDisposable = new CompositeDisposable(); protected CompositeDisposable compositeDisposable = new CompositeDisposable();
protected boolean wasPreviouslyDarkTheme; protected boolean wasPreviouslyDarkTheme;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
wasPreviouslyDarkTheme = defaultKvStore.getBoolean("theme", false); wasPreviouslyDarkTheme = systemThemeUtils.isDeviceInNightMode();
setTheme(wasPreviouslyDarkTheme ? R.style.DarkAppTheme : R.style.LightAppTheme); setTheme(wasPreviouslyDarkTheme ? R.style.DarkAppTheme : R.style.LightAppTheme);
} }
@Override @Override
protected void onResume() { protected void onResume() {
// Restart activity if theme is changed // Restart activity if theme is changed
if (wasPreviouslyDarkTheme != defaultKvStore.getBoolean("theme", false)) { if (wasPreviouslyDarkTheme != systemThemeUtils.isDeviceInNightMode()) {
recreate(); recreate();
} }

View file

@ -0,0 +1,49 @@
package fr.free.nrw.commons.utils;
import android.content.Context;
import android.content.res.Configuration;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.settings.Prefs;
public class SystemThemeUtils {
private Context context;
private JsonKvStore applicationKvStore;
public static final String THEME_MODE_DEFAULT = "0";
public static final String THEME_MODE_DARK = "1";
public static final String THEME_MODE_LIGHT = "2";
@Inject
public SystemThemeUtils(Context context, @Named("default_preferences") JsonKvStore applicationKvStore) {
this.context = context;
this.applicationKvStore = applicationKvStore;
}
// Return true is system wide dark theme is enabled else false
public boolean getSystemDefaultThemeBool(String theme) {
if (theme.equals(THEME_MODE_DARK)) {
return true;
} else if (theme.equals(THEME_MODE_DEFAULT)) {
return getSystemDefaultThemeBool(getSystemDefaultTheme());
}
return false;
}
// Returns the default system wide theme
public String getSystemDefaultTheme() {
return (context.getResources().getConfiguration().uiMode &
Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES ? THEME_MODE_DARK : THEME_MODE_LIGHT;
}
// Returns true if the device is in night mode or false otherwise
public boolean isDeviceInNightMode() {
return getSystemDefaultThemeBool(
applicationKvStore.getString(Prefs.KEY_THEME_VALUE, getSystemDefaultTheme()));
}
}

View file

@ -35,4 +35,15 @@
<item>@string/exif_tag_software</item> <item>@string/exif_tag_software</item>
</array> </array>
<array name="pref_theme_entries">
<item>@string/theme_default_name</item>
<item>@string/theme_dark_name</item>
<item>@string/theme_light_name</item>
</array>
<string-array name="pref_theme_entries_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources> </resources>

View file

@ -113,8 +113,7 @@
<string name="use_previous">Use previous title and description</string> <string name="use_previous">Use previous title and description</string>
<string name="allow_gps">Automatically get current location</string> <string name="allow_gps">Automatically get current location</string>
<string name="allow_gps_summary">Retrieves current location if image is not geotagged, and geotags image with it. Warning: This will reveal your current location.</string> <string name="allow_gps_summary">Retrieves current location if image is not geotagged, and geotags image with it. Warning: This will reveal your current location.</string>
<string name="preference_theme">Night mode</string> <string name="preference_theme">Theme</string>
<string name="preference_theme_summary">Use dark theme</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>
<string name="license_name_cc_by_four"> Attribution 4.0</string> <string name="license_name_cc_by_four"> Attribution 4.0</string>
<string name="license_name_cc_by_sa"> Attribution-ShareAlike 3.0</string> <string name="license_name_cc_by_sa"> Attribution-ShareAlike 3.0</string>
@ -599,4 +598,7 @@ Upload your first media by tapping on the add button.</string>
<string name="wallpaper_set_unsuccessfully">Something went wrong. Could not set the wallpaper</string> <string name="wallpaper_set_unsuccessfully">Something went wrong. Could not set the wallpaper</string>
<string name="setting_wallpaper_dialog_title">Set as Wallpaper</string> <string name="setting_wallpaper_dialog_title">Set as Wallpaper</string>
<string name="setting_wallpaper_dialog_message">Setting Wallpaper. Please wait…</string> <string name="setting_wallpaper_dialog_message">Setting Wallpaper. Please wait…</string>
<string name="theme_default_name">Default</string>
<string name="theme_dark_name">Dark</string>
<string name="theme_light_name">Light</string>
</resources> </resources>

View file

@ -5,11 +5,13 @@
<fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory <fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory
android:title="@string/preference_category_appearance"> android:title="@string/preference_category_appearance">
<fr.free.nrw.commons.ui.LongTitlePreferences.LongTitleSwitchPreference <fr.free.nrw.commons.ui.LongTitlePreferences.LongTitleListPreference
android:title="@string/preference_theme" android:key="appThemePref"
android:defaultValue="false" android:title= "@string/preference_theme"
android:summary="@string/preference_theme_summary" android:entries="@array/pref_theme_entries"
android:key="theme" /> android:entryValues="@array/pref_theme_entries_values"
android:defaultValue="0"
android:summary="@string/theme_default_name" />
</fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory> </fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory>