Android: Handle light/dark mode changes
Update Theme's style according to current UiMode. New style.json file for dark mode was added (stored in separate subdirectory 'darkUiMode/'). Theme_DeviceDefault_DayNight[0] is used for extraction for API 29 or higher. Style is updated each time when UiMode is changed. [0]https://developer.android.com/reference/android/R.style#Theme_DeviceDefault_DayNight Task-number: QTBUG-83185 Change-Id: Id26059231f41761d822d494ac6c641bf3cba3322 Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io> (cherry picked from commit b4a9bb1f6a40e6d504c1f48f0d9ea2b70ab1a9f0)
This commit is contained in:
parent
96b3382ec7
commit
3884635189
@ -34,6 +34,7 @@ import android.graphics.drawable.ScaleDrawable;
|
|||||||
import android.graphics.drawable.StateListDrawable;
|
import android.graphics.drawable.StateListDrawable;
|
||||||
import android.graphics.drawable.VectorDrawable;
|
import android.graphics.drawable.VectorDrawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
@ -135,6 +136,42 @@ public class ExtractStyle {
|
|||||||
Context m_context;
|
Context m_context;
|
||||||
private final HashMap<String, DrawableCache> m_drawableCache = new HashMap<>();
|
private final HashMap<String, DrawableCache> m_drawableCache = new HashMap<>();
|
||||||
|
|
||||||
|
private static final String EXTRACT_STYLE_KEY = "extract.android.style";
|
||||||
|
private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
|
||||||
|
|
||||||
|
private static boolean m_missingNormalStyle = false;
|
||||||
|
private static boolean m_missingDarkStyle = false;
|
||||||
|
private static String m_stylePath = null;
|
||||||
|
private static boolean m_extractMinimal = false;
|
||||||
|
|
||||||
|
public static void setup(Bundle loaderParams) {
|
||||||
|
if (loaderParams.containsKey(EXTRACT_STYLE_KEY)) {
|
||||||
|
m_stylePath = loaderParams.getString(EXTRACT_STYLE_KEY);
|
||||||
|
|
||||||
|
boolean darkModeFileMissing = !(new File(m_stylePath + "darkUiMode/style.json").exists());
|
||||||
|
m_missingDarkStyle = Build.VERSION.SDK_INT > 28 && darkModeFileMissing;
|
||||||
|
|
||||||
|
m_missingNormalStyle = !(new File(m_stylePath + "style.json").exists());
|
||||||
|
|
||||||
|
m_extractMinimal = loaderParams.containsKey(EXTRACT_STYLE_MINIMAL_KEY) &&
|
||||||
|
loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runIfNeeded(Context context, boolean extractDarkMode) {
|
||||||
|
if (m_stylePath == null)
|
||||||
|
return;
|
||||||
|
if (extractDarkMode) {
|
||||||
|
if (m_missingDarkStyle) {
|
||||||
|
new ExtractStyle(context, m_stylePath + "darkUiMode/", m_extractMinimal);
|
||||||
|
m_missingDarkStyle = false;
|
||||||
|
}
|
||||||
|
} else if (m_missingNormalStyle) {
|
||||||
|
new ExtractStyle(context, m_stylePath, m_extractMinimal);
|
||||||
|
m_missingNormalStyle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ExtractStyle(Context context, String extractPath, boolean minimal) {
|
public ExtractStyle(Context context, String extractPath, boolean minimal) {
|
||||||
m_minimal = minimal;
|
m_minimal = minimal;
|
||||||
m_extractPath = extractPath + "/";
|
m_extractPath = extractPath + "/";
|
||||||
|
@ -84,8 +84,6 @@ public class QtActivityDelegate
|
|||||||
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||||
private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
||||||
private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
||||||
private static final String EXTRACT_STYLE_KEY = "extract.android.style";
|
|
||||||
private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
|
|
||||||
|
|
||||||
public static final int SYSTEM_UI_VISIBILITY_NORMAL = 0;
|
public static final int SYSTEM_UI_VISIBILITY_NORMAL = 0;
|
||||||
public static final int SYSTEM_UI_VISIBILITY_FULLSCREEN = 1;
|
public static final int SYSTEM_UI_VISIBILITY_FULLSCREEN = 1;
|
||||||
@ -707,11 +705,8 @@ public class QtActivityDelegate
|
|||||||
libraries.remove(libraries.size() - 1);
|
libraries.remove(libraries.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loaderParams.containsKey(EXTRACT_STYLE_KEY)) {
|
ExtractStyle.setup(loaderParams);
|
||||||
String path = loaderParams.getString(EXTRACT_STYLE_KEY);
|
ExtractStyle.runIfNeeded(m_activity, isUiModeDark(m_activity.getResources().getConfiguration()));
|
||||||
new ExtractStyle(m_activity, path, loaderParams.containsKey(EXTRACT_STYLE_MINIMAL_KEY) &&
|
|
||||||
loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
QtNative.setEnvironmentVariables(loaderParams.getString(ENVIRONMENT_VARIABLES_KEY));
|
QtNative.setEnvironmentVariables(loaderParams.getString(ENVIRONMENT_VARIABLES_KEY));
|
||||||
QtNative.setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE",
|
QtNative.setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE",
|
||||||
@ -971,13 +966,20 @@ public class QtActivityDelegate
|
|||||||
updateFullScreen();
|
updateFullScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isUiModeDark(Configuration config)
|
||||||
|
{
|
||||||
|
return (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleUiModeChange(int uiMode)
|
private void handleUiModeChange(int uiMode)
|
||||||
{
|
{
|
||||||
switch (uiMode) {
|
switch (uiMode) {
|
||||||
case Configuration.UI_MODE_NIGHT_NO:
|
case Configuration.UI_MODE_NIGHT_NO:
|
||||||
|
ExtractStyle.runIfNeeded(m_activity, false);
|
||||||
QtNative.handleUiDarkModeChanged(0);
|
QtNative.handleUiDarkModeChanged(0);
|
||||||
break;
|
break;
|
||||||
case Configuration.UI_MODE_NIGHT_YES:
|
case Configuration.UI_MODE_NIGHT_YES:
|
||||||
|
ExtractStyle.runIfNeeded(m_activity, true);
|
||||||
QtNative.handleUiDarkModeChanged(1);
|
QtNative.handleUiDarkModeChanged(1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -65,8 +65,14 @@ public class QtActivity extends Activity
|
|||||||
public QtActivity()
|
public QtActivity()
|
||||||
{
|
{
|
||||||
m_loader = new QtActivityLoader(this);
|
m_loader = new QtActivityLoader(this);
|
||||||
QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"};
|
|
||||||
QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light";
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
|
QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"};
|
||||||
|
QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light";
|
||||||
|
} else {
|
||||||
|
QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_DayNight"};
|
||||||
|
QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_DayNight";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,7 +331,7 @@ public abstract class QtLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(new File(stylePath)).exists() && !extractOption.equals("none")) {
|
if (!extractOption.equals("none")) {
|
||||||
loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
|
loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
|
||||||
loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal"));
|
loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal"));
|
||||||
}
|
}
|
||||||
|
@ -432,7 +432,7 @@ QStringList QAndroidPlatformIntegration::themeNames() const
|
|||||||
QPlatformTheme *QAndroidPlatformIntegration::createPlatformTheme(const QString &name) const
|
QPlatformTheme *QAndroidPlatformIntegration::createPlatformTheme(const QString &name) const
|
||||||
{
|
{
|
||||||
if (androidThemeName == name)
|
if (androidThemeName == name)
|
||||||
return new QAndroidPlatformTheme(m_androidPlatformNativeInterface);
|
return QAndroidPlatformTheme::instance(m_androidPlatformNativeInterface);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -495,6 +495,9 @@ void QAndroidPlatformIntegration::setAppearance(QPlatformTheme::Appearance newAp
|
|||||||
if (m_appearance == newAppearance)
|
if (m_appearance == newAppearance)
|
||||||
return;
|
return;
|
||||||
m_appearance = newAppearance;
|
m_appearance = newAppearance;
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(qGuiApp,
|
||||||
|
[] () { QAndroidPlatformTheme::instance()->updateAppearance();});
|
||||||
}
|
}
|
||||||
|
|
||||||
void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize,
|
void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize,
|
||||||
|
@ -158,6 +158,9 @@ QJsonObject AndroidStyle::loadStyleData()
|
|||||||
if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
|
if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
|
||||||
stylePath += slashChar;
|
stylePath += slashChar;
|
||||||
|
|
||||||
|
if (QAndroidPlatformIntegration::appearance() == QPlatformTheme::Appearance::Dark)
|
||||||
|
stylePath += "darkUiMode/"_L1;
|
||||||
|
|
||||||
Q_ASSERT(!stylePath.isEmpty());
|
Q_ASSERT(!stylePath.isEmpty());
|
||||||
|
|
||||||
QString androidTheme = QLatin1StringView(qgetenv("QT_ANDROID_THEME"));
|
QString androidTheme = QLatin1StringView(qgetenv("QT_ANDROID_THEME"));
|
||||||
@ -185,13 +188,22 @@ QJsonObject AndroidStyle::loadStyleData()
|
|||||||
return document.object();
|
return document.object();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<AndroidStyle> loadAndroidStyle(QPalette *defaultPalette)
|
static void loadAndroidStyle(QPalette *defaultPalette, std::shared_ptr<AndroidStyle> &style)
|
||||||
{
|
{
|
||||||
double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0;
|
double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0;
|
||||||
std::shared_ptr<AndroidStyle> style = std::make_shared<AndroidStyle>();
|
if (style) {
|
||||||
|
style->m_standardPalette = QPalette();
|
||||||
|
style->m_palettes.clear();
|
||||||
|
style->m_fonts.clear();
|
||||||
|
style->m_QWidgetsFonts.clear();
|
||||||
|
} else {
|
||||||
|
style = std::make_shared<AndroidStyle>();
|
||||||
|
}
|
||||||
|
|
||||||
style->m_styleData = AndroidStyle::loadStyleData();
|
style->m_styleData = AndroidStyle::loadStyleData();
|
||||||
|
|
||||||
if (style->m_styleData.isEmpty())
|
if (style->m_styleData.isEmpty())
|
||||||
return std::shared_ptr<AndroidStyle>();
|
return;
|
||||||
|
|
||||||
{
|
{
|
||||||
QFont font("Droid Sans Mono"_L1, 14.0 * 100 / 72);
|
QFont font("Droid Sans Mono"_L1, 14.0 * 100 / 72);
|
||||||
@ -293,11 +305,43 @@ static std::shared_ptr<AndroidStyle> loadAndroidStyle(QPalette *defaultPalette)
|
|||||||
// Extract palette information
|
// Extract palette information
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return style;
|
}
|
||||||
|
|
||||||
|
QAndroidPlatformTheme *QAndroidPlatformTheme::m_instance = nullptr;
|
||||||
|
|
||||||
|
QAndroidPlatformTheme *QAndroidPlatformTheme::instance(
|
||||||
|
QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
|
||||||
|
{
|
||||||
|
if (androidPlatformNativeInterface && !m_instance) {
|
||||||
|
m_instance = new QAndroidPlatformTheme(androidPlatformNativeInterface);
|
||||||
|
}
|
||||||
|
return m_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
|
QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
|
||||||
{
|
{
|
||||||
|
updateStyle();
|
||||||
|
|
||||||
|
androidPlatformNativeInterface->m_androidStyle = m_androidStyleData;
|
||||||
|
|
||||||
|
// default in case the style has not set a font
|
||||||
|
m_systemFont = QFont("Roboto"_L1, 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi
|
||||||
|
}
|
||||||
|
|
||||||
|
QAndroidPlatformTheme::~QAndroidPlatformTheme()
|
||||||
|
{
|
||||||
|
m_instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QAndroidPlatformTheme::updateAppearance()
|
||||||
|
{
|
||||||
|
updateStyle();
|
||||||
|
QWindowSystemInterface::handleThemeChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QAndroidPlatformTheme::updateStyle()
|
||||||
|
{
|
||||||
|
QColor windowText = Qt::black;
|
||||||
QColor background(229, 229, 229);
|
QColor background(229, 229, 229);
|
||||||
QColor light = background.lighter(150);
|
QColor light = background.lighter(150);
|
||||||
QColor mid(background.darker(130));
|
QColor mid(background.darker(130));
|
||||||
@ -314,7 +358,27 @@ QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *an
|
|||||||
QColor highlight(148, 210, 231);
|
QColor highlight(148, 210, 231);
|
||||||
QColor disabledShadow = shadow.lighter(150);
|
QColor disabledShadow = shadow.lighter(150);
|
||||||
|
|
||||||
m_defaultPalette = QPalette(Qt::black,background,light,dark,mid,text,base);
|
if (appearance() == QPlatformTheme::Appearance::Dark) {
|
||||||
|
// Colors were prepared based on Theme.DeviceDefault.DayNight
|
||||||
|
windowText = QColor(250, 250, 250);
|
||||||
|
background = QColor(48, 48, 48);
|
||||||
|
light = background.darker(150);
|
||||||
|
mid = background.lighter(130);
|
||||||
|
midLight = mid.darker(110);
|
||||||
|
base = background;
|
||||||
|
disabledBase = background;
|
||||||
|
dark = background.darker(150);
|
||||||
|
darkDisabled = dark.darker(110);
|
||||||
|
text = QColor(250, 250, 250);
|
||||||
|
highlightedText = QColor(250, 250, 250);
|
||||||
|
disabledText = QColor(96, 96, 96);
|
||||||
|
button = QColor(48, 48, 48);
|
||||||
|
shadow = QColor(32, 32, 32);
|
||||||
|
highlight = QColor(102, 178, 204);
|
||||||
|
disabledShadow = shadow.darker(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_defaultPalette = QPalette(windowText,background,light,dark,mid,text,base);
|
||||||
m_defaultPalette.setBrush(QPalette::Midlight, midLight);
|
m_defaultPalette.setBrush(QPalette::Midlight, midLight);
|
||||||
m_defaultPalette.setBrush(QPalette::Button, button);
|
m_defaultPalette.setBrush(QPalette::Button, button);
|
||||||
m_defaultPalette.setBrush(QPalette::Shadow, shadow);
|
m_defaultPalette.setBrush(QPalette::Shadow, shadow);
|
||||||
@ -330,12 +394,9 @@ QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *an
|
|||||||
m_defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight);
|
m_defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight);
|
||||||
m_defaultPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
|
m_defaultPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
|
||||||
m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlight.lighter(150));
|
m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlight.lighter(150));
|
||||||
m_androidStyleData = loadAndroidStyle(&m_defaultPalette);
|
|
||||||
QGuiApplication::setPalette(m_defaultPalette);
|
|
||||||
androidPlatformNativeInterface->m_androidStyle = m_androidStyleData;
|
|
||||||
|
|
||||||
// default in case the style has not set a font
|
loadAndroidStyle(&m_defaultPalette, m_androidStyleData);
|
||||||
m_systemFont = QFont("Roboto"_L1, 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi
|
QGuiApplication::setPalette(m_defaultPalette);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const
|
QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const
|
||||||
|
@ -30,7 +30,9 @@ class QAndroidPlatformNativeInterface;
|
|||||||
class QAndroidPlatformTheme: public QPlatformTheme
|
class QAndroidPlatformTheme: public QPlatformTheme
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface);
|
~QAndroidPlatformTheme();
|
||||||
|
void updateAppearance();
|
||||||
|
void updateStyle();
|
||||||
QPlatformMenuBar *createPlatformMenuBar() const override;
|
QPlatformMenuBar *createPlatformMenuBar() const override;
|
||||||
QPlatformMenu *createPlatformMenu() const override;
|
QPlatformMenu *createPlatformMenu() const override;
|
||||||
QPlatformMenuItem *createPlatformMenuItem() const override;
|
QPlatformMenuItem *createPlatformMenuItem() const override;
|
||||||
@ -43,8 +45,12 @@ public:
|
|||||||
bool usePlatformNativeDialog(DialogType type) const override;
|
bool usePlatformNativeDialog(DialogType type) const override;
|
||||||
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
|
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
|
||||||
|
|
||||||
|
static QAndroidPlatformTheme *instance(
|
||||||
|
QAndroidPlatformNativeInterface * androidPlatformNativeInterface = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface);
|
||||||
|
static QAndroidPlatformTheme * m_instance;
|
||||||
std::shared_ptr<AndroidStyle> m_androidStyleData;
|
std::shared_ptr<AndroidStyle> m_androidStyleData;
|
||||||
QPalette m_defaultPalette;
|
QPalette m_defaultPalette;
|
||||||
QFont m_systemFont;
|
QFont m_systemFont;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user