Android: Add theme mode switcher

Similar to app themes, theme modes have to be loaded before directory initialization is ready. So we save the proper key the same way.
This commit is contained in:
Charles Lombardo 2022-11-11 20:44:30 -05:00
parent 385dfb60a0
commit b9fffa2e66
16 changed files with 170 additions and 55 deletions

View File

@ -96,7 +96,7 @@ android {
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'

View File

@ -34,6 +34,8 @@ public enum IntSetting implements AbstractIntSetting
MAIN_EMULATION_ORIENTATION(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
"EmulationOrientation", ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE),
MAIN_INTERFACE_THEME(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "InterfaceTheme", 0),
MAIN_INTERFACE_THEME_MODE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID,
"InterfaceThemeMode", -1),
MAIN_LAST_PLATFORM_TAB(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "LastPlatformTab", 0),
MAIN_MOTION_CONTROLS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "MotionControls", 1),
MAIN_IR_MODE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID, "IRMode",

View File

@ -8,6 +8,8 @@ import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.appcompat.app.AppCompatActivity;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.UserDataActivity;
@ -328,33 +330,37 @@ public final class SettingsFragmentPresenter
AbstractIntSetting appTheme = new AbstractIntSetting()
{
@Override public boolean isOverridden(Settings settings)
@Override
public boolean isOverridden(Settings settings)
{
return IntSetting.MAIN_INTERFACE_THEME.isOverridden(settings);
}
@Override public boolean isRuntimeEditable()
@Override
public boolean isRuntimeEditable()
{
// This only affects app UI
return true;
}
@Override public boolean delete(Settings settings)
@Override
public boolean delete(Settings settings)
{
ThemeHelper.deleteThemeKey(mView.getActivity());
ThemeHelper.deleteThemeKey((AppCompatActivity) mView.getActivity());
return IntSetting.MAIN_INTERFACE_THEME.delete(settings);
}
@Override public int getInt(Settings settings)
@Override
public int getInt(Settings settings)
{
return IntSetting.MAIN_INTERFACE_THEME.getInt(settings);
}
@Override public void setInt(Settings settings, int newValue)
@Override
public void setInt(Settings settings, int newValue)
{
ThemeHelper.saveTheme(mView.getActivity(), newValue);
IntSetting.MAIN_INTERFACE_THEME.setInt(settings, newValue);
mView.getActivity().recreate();
ThemeHelper.saveTheme((AppCompatActivity) mView.getActivity(), newValue);
}
};
@ -369,6 +375,45 @@ public final class SettingsFragmentPresenter
sl.add(new SingleChoiceSetting(mContext, appTheme, R.string.change_theme, 0,
R.array.themeEntries, R.array.themeValues));
}
AbstractIntSetting themeMode = new AbstractIntSetting()
{
@Override
public boolean isOverridden(Settings settings)
{
return IntSetting.MAIN_INTERFACE_THEME_MODE.isOverridden(settings);
}
@Override
public boolean isRuntimeEditable()
{
// This only affects app UI
return true;
}
@Override
public boolean delete(Settings settings)
{
ThemeHelper.deleteThemeModeKey((AppCompatActivity) mView.getActivity());
return IntSetting.MAIN_INTERFACE_THEME_MODE.delete(settings);
}
@Override
public int getInt(Settings settings)
{
return IntSetting.MAIN_INTERFACE_THEME_MODE.getInt(settings);
}
@Override
public void setInt(Settings settings, int newValue)
{
IntSetting.MAIN_INTERFACE_THEME_MODE.setInt(settings, newValue);
ThemeHelper.saveThemeMode((AppCompatActivity) mView.getActivity(), newValue);
}
};
sl.add(new SingleChoiceSetting(mContext, themeMode, R.string.change_theme_mode, 0,
R.array.themeModeEntries, R.array.themeModeValues));
}
private void addAudioSettings(ArrayList<SettingsItem> sl)

View File

@ -13,6 +13,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.preference.PreferenceManager;
@ -87,6 +88,16 @@ public final class DirectoryInitialization
.apply();
}
if (IntSetting.MAIN_INTERFACE_THEME_MODE.getIntGlobal() !=
preferences.getInt(ThemeHelper.CURRENT_THEME_MODE,
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM))
{
preferences.edit()
.putInt(ThemeHelper.CURRENT_THEME_MODE,
IntSetting.MAIN_INTERFACE_THEME_MODE.getIntGlobal())
.apply();
}
if (wiimoteIniWritten)
{
// This has to be done after calling NativeLibrary.Initialize(),

View File

@ -10,7 +10,10 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.preference.PreferenceManager;
import com.google.android.material.appbar.AppBarLayout;
@ -23,6 +26,7 @@ import org.dolphinemu.dolphinemu.ui.main.ThemeProvider;
public class ThemeHelper
{
public static final String CURRENT_THEME = "current_theme";
public static final String CURRENT_THEME_MODE = "current_theme_mode";
public static final int DEFAULT = 0;
public static final int MONET = 1;
@ -38,6 +42,7 @@ public class ThemeHelper
// requested theme id is ready before the onCreate method of any given Activity.
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
setThemeMode(activity);
switch (preferences.getInt(CURRENT_THEME, DEFAULT))
{
case DEFAULT:
@ -70,22 +75,88 @@ public class ThemeHelper
}
}
public static void saveTheme(@NonNull Activity activity, int themeValue)
private static void setThemeMode(@NonNull AppCompatActivity activity)
{
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
preferences.edit().putInt(CURRENT_THEME, themeValue).apply();
int themeMode = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.getInt(CURRENT_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
activity.getDelegate().setLocalNightMode(themeMode);
WindowInsetsControllerCompat windowController =
WindowCompat.getInsetsController(activity.getWindow(),
activity.getWindow().getDecorView());
int systemReportedThemeMode =
activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (themeMode)
{
case AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM:
switch (systemReportedThemeMode)
{
case Configuration.UI_MODE_NIGHT_NO:
setLightModeSystemBars(windowController);
break;
case Configuration.UI_MODE_NIGHT_YES:
setDarkModeSystemBars(windowController);
break;
}
break;
case AppCompatDelegate.MODE_NIGHT_NO:
setLightModeSystemBars(windowController);
break;
case AppCompatDelegate.MODE_NIGHT_YES:
setDarkModeSystemBars(windowController);
break;
}
}
public static void deleteThemeKey(@NonNull Activity activity)
private static void setLightModeSystemBars(@NonNull WindowInsetsControllerCompat windowController)
{
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
preferences.edit().remove(CURRENT_THEME).apply();
activity.setTheme(R.style.Theme_Dolphin_Main);
windowController.setAppearanceLightStatusBars(true);
windowController.setAppearanceLightNavigationBars(true);
}
private static void setDarkModeSystemBars(@NonNull WindowInsetsControllerCompat windowController)
{
windowController.setAppearanceLightStatusBars(false);
windowController.setAppearanceLightNavigationBars(false);
}
public static void saveTheme(@NonNull AppCompatActivity activity, int themeValue)
{
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.edit()
.putInt(CURRENT_THEME, themeValue)
.apply();
activity.recreate();
}
public static void deleteThemeKey(@NonNull AppCompatActivity activity)
{
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.edit()
.remove(CURRENT_THEME)
.apply();
activity.recreate();
}
public static void saveThemeMode(@NonNull AppCompatActivity activity, int themeModeValue)
{
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.edit()
.putInt(CURRENT_THEME_MODE, themeModeValue)
.apply();
setThemeMode(activity);
}
public static void deleteThemeModeKey(@NonNull AppCompatActivity activity)
{
PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext())
.edit()
.remove(CURRENT_THEME_MODE)
.apply();
setThemeMode(activity);
}
public static void setCorrectTheme(AppCompatActivity activity)
{
int currentTheme = ((ThemeProvider) activity).getThemeId();
@ -110,7 +181,7 @@ public class ThemeHelper
}
}
public static void setNavigationBarColor(Activity activity, @ColorInt int color)
public static void setNavigationBarColor(@NonNull Activity activity, @ColorInt int color)
{
int gestureType = InsetsHelper.getSystemGestureType(activity.getApplicationContext());
int orientation = activity.getResources().getConfiguration().orientation;

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorOnSurface"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -2,7 +2,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_selected="true"
android:drawable="@color/dolphin_primary"/>
android:drawable="@color/dolphin_blue"/>
<item
android:drawable="@color/tv_card_unselected"/>
</selector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="lightSystemBars">false</bool>
</resources>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.V27.Dolphin" parent="Theme.Dolphin">
<item name="android:windowLightNavigationBar">@bool/lightSystemBars</item>
</style>
<style name="Theme.Dolphin.Main" parent="Theme.V27.Dolphin" />
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.V29.Dolphin" parent="Theme.V27.Dolphin">
<style name="Theme.V29.Dolphin" parent="Theme.Dolphin">
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:enforceNavigationBarContrast">false</item>
</style>

View File

@ -507,6 +507,17 @@
<item>4</item>
</integer-array>
<string-array name="themeModeEntries">
<item>Follow System</item>
<item>Light</item>
<item>Dark</item>
</string-array>
<integer-array name="themeModeValues">
<item>-1</item>
<item>1</item>
<item>2</item>
</integer-array>
<string-array name="synchronizeGpuThreadEntries">
<item>Never</item>
<item>On Idle Skipping</item>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="lightSystemBars">true</bool>
</resources>

View File

@ -196,6 +196,7 @@
<string name="show_titles_in_game_list">Show Titles in Game List</string>
<string name="show_titles_in_game_list_description">Show the title and creator below each game cover.</string>
<string name="change_theme">Change App Theme</string>
<string name="change_theme_mode">Change Theme Mode</string>
<!-- Online Update Region Select Fragment -->
<string name="region_select_title">Please select a region</string>

View File

@ -42,7 +42,7 @@
<style name="DolphinTVDialog" parent="Theme.Material3.DayNight.Dialog.Alert">
<item name="colorSurface">@color/dolphin_inverseOnSurface</item>
<item name="colorPrimary">@color/dolphin_primary</item>
<item name="colorPrimary">@color/dolphin_blue</item>
</style>
<style name="DolphinButton" parent="Widget.Material3.Button.TextButton.Dialog">

View File

@ -47,9 +47,6 @@
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="homeAsUpIndicator">@drawable/ic_back</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">@bool/lightSystemBars</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">default</item>
<item name="materialAlertDialogTheme">@style/DolphinMaterialDialog</item>
@ -58,7 +55,7 @@
<item name="materialDividerStyle">@style/DolphinDivider</item>
</style>
<!-- Trick for API >= 27 specific changes -->
<!-- Trick for API >= 29 specific changes -->
<style name="Theme.Dolphin.Main" parent="Theme.Dolphin" />
<style name="Theme.Dolphin.Main.Material" parent="Theme.Dolphin.Main">

View File

@ -39,10 +39,12 @@ bool IsSettingSaveable(const Config::Location& config_location)
// TODO: Kill the current Android controller mappings system
if (config_location.section == "Android")
{
static constexpr std::array<const char*, 12> android_setting_saveable = {
"ControlScale", "ControlOpacity", "EmulationOrientation", "JoystickRelCenter",
"LastPlatformTab", "MotionControls", "PhoneRumble", "ShowInputOverlay",
"IRMode", "IRAlwaysRecenter", "ShowGameTitles", "InterfaceTheme"};
static constexpr std::array<const char*, 13> android_setting_saveable = {
"ControlScale", "ControlOpacity", "EmulationOrientation",
"JoystickRelCenter", "LastPlatformTab", "MotionControls",
"PhoneRumble", "ShowInputOverlay", "IRMode",
"IRAlwaysRecenter", "ShowGameTitles", "InterfaceTheme",
"InterfaceThemeMode"};
return std::any_of(
android_setting_saveable.cbegin(), android_setting_saveable.cend(),