diff --git a/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java b/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java index 6792247eeb..672a2e28d3 100644 --- a/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java +++ b/src/android/jar/src/org/qtproject/qt/android/ExtractStyle.java @@ -34,6 +34,7 @@ import android.graphics.drawable.ScaleDrawable; import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.VectorDrawable; import android.os.Build; +import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; @@ -135,6 +136,42 @@ public class ExtractStyle { Context m_context; private final HashMap 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) { m_minimal = minimal; m_extractPath = extractPath + "/"; diff --git a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java index 63298c0730..ec890d1d61 100644 --- a/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt/android/QtActivityDelegate.java @@ -84,8 +84,6 @@ public class QtActivityDelegate private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; private static final String APPLICATION_PARAMETERS_KEY = "application.parameters"; 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_FULLSCREEN = 1; @@ -707,11 +705,8 @@ public class QtActivityDelegate libraries.remove(libraries.size() - 1); } - if (loaderParams.containsKey(EXTRACT_STYLE_KEY)) { - String path = loaderParams.getString(EXTRACT_STYLE_KEY); - new ExtractStyle(m_activity, path, loaderParams.containsKey(EXTRACT_STYLE_MINIMAL_KEY) && - loaderParams.getBoolean(EXTRACT_STYLE_MINIMAL_KEY)); - } + ExtractStyle.setup(loaderParams); + ExtractStyle.runIfNeeded(m_activity, isUiModeDark(m_activity.getResources().getConfiguration())); QtNative.setEnvironmentVariables(loaderParams.getString(ENVIRONMENT_VARIABLES_KEY)); QtNative.setEnvironmentVariable("QT_ANDROID_FONTS_MONOSPACE", @@ -971,13 +966,20 @@ public class QtActivityDelegate updateFullScreen(); } + boolean isUiModeDark(Configuration config) + { + return (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + } + private void handleUiModeChange(int uiMode) { switch (uiMode) { case Configuration.UI_MODE_NIGHT_NO: + ExtractStyle.runIfNeeded(m_activity, false); QtNative.handleUiDarkModeChanged(0); break; case Configuration.UI_MODE_NIGHT_YES: + ExtractStyle.runIfNeeded(m_activity, true); QtNative.handleUiDarkModeChanged(1); break; } diff --git a/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java index 9cef8146fd..94d3c2dee1 100644 --- a/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java +++ b/src/android/java/src/org/qtproject/qt/android/bindings/QtActivity.java @@ -65,8 +65,14 @@ public class QtActivity extends Activity public QtActivity() { 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"; + } } diff --git a/src/android/java/src/org/qtproject/qt/android/bindings/QtLoader.java b/src/android/java/src/org/qtproject/qt/android/bindings/QtLoader.java index 883c0d6478..fea193a4db 100644 --- a/src/android/java/src/org/qtproject/qt/android/bindings/QtLoader.java +++ b/src/android/java/src/org/qtproject/qt/android/bindings/QtLoader.java @@ -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.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal")); } diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp index 57546bdbca..0d5b473477 100644 --- a/src/plugins/platforms/android/qandroidplatformintegration.cpp +++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp @@ -460,7 +460,7 @@ QStringList QAndroidPlatformIntegration::themeNames() const QPlatformTheme *QAndroidPlatformIntegration::createPlatformTheme(const QString &name) const { if (androidThemeName == name) - return new QAndroidPlatformTheme(m_androidPlatformNativeInterface); + return QAndroidPlatformTheme::instance(m_androidPlatformNativeInterface); return 0; } @@ -523,6 +523,9 @@ void QAndroidPlatformIntegration::setAppearance(Qt::Appearance newAppearance) if (m_appearance == newAppearance) return; m_appearance = newAppearance; + + QMetaObject::invokeMethod(qGuiApp, + [] () { QAndroidPlatformTheme::instance()->updateAppearance();}); } void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize, diff --git a/src/plugins/platforms/android/qandroidplatformtheme.cpp b/src/plugins/platforms/android/qandroidplatformtheme.cpp index 6f950dc4fb..f11de02d44 100644 --- a/src/plugins/platforms/android/qandroidplatformtheme.cpp +++ b/src/plugins/platforms/android/qandroidplatformtheme.cpp @@ -158,6 +158,9 @@ QJsonObject AndroidStyle::loadStyleData() if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar)) stylePath += slashChar; + if (QAndroidPlatformIntegration::appearance() == Qt::Appearance::Dark) + stylePath += "darkUiMode/"_L1; + Q_ASSERT(!stylePath.isEmpty()); QString androidTheme = QLatin1StringView(qgetenv("QT_ANDROID_THEME")); @@ -185,13 +188,22 @@ QJsonObject AndroidStyle::loadStyleData() return document.object(); } -static std::shared_ptr loadAndroidStyle(QPalette *defaultPalette) +static void loadAndroidStyle(QPalette *defaultPalette, std::shared_ptr &style) { double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0; - std::shared_ptr style = std::make_shared(); + if (style) { + style->m_standardPalette = QPalette(); + style->m_palettes.clear(); + style->m_fonts.clear(); + style->m_QWidgetsFonts.clear(); + } else { + style = std::make_shared(); + } + style->m_styleData = AndroidStyle::loadStyleData(); + if (style->m_styleData.isEmpty()) - return std::shared_ptr(); + return; { QFont font("Droid Sans Mono"_L1, 14.0 * 100 / 72); @@ -293,11 +305,43 @@ static std::shared_ptr loadAndroidStyle(QPalette *defaultPalette) // 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) { + 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 light = background.lighter(150); QColor mid(background.darker(130)); @@ -314,7 +358,27 @@ QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *an QColor highlight(148, 210, 231); QColor disabledShadow = shadow.lighter(150); - m_defaultPalette = QPalette(Qt::black,background,light,dark,mid,text,base); + if (appearance() == Qt::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::Button, button); 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::Inactive, QPalette::Highlight, highlight); 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 - m_systemFont = QFont("Roboto"_L1, 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi + loadAndroidStyle(&m_defaultPalette, m_androidStyleData); + QGuiApplication::setPalette(m_defaultPalette); } QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const diff --git a/src/plugins/platforms/android/qandroidplatformtheme.h b/src/plugins/platforms/android/qandroidplatformtheme.h index 0fc3c152c5..38e653ab71 100644 --- a/src/plugins/platforms/android/qandroidplatformtheme.h +++ b/src/plugins/platforms/android/qandroidplatformtheme.h @@ -30,7 +30,9 @@ class QAndroidPlatformNativeInterface; class QAndroidPlatformTheme: public QPlatformTheme { public: - QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface); + ~QAndroidPlatformTheme(); + void updateAppearance(); + void updateStyle(); QPlatformMenuBar *createPlatformMenuBar() const override; QPlatformMenu *createPlatformMenu() const override; QPlatformMenuItem *createPlatformMenuItem() const override; @@ -43,8 +45,12 @@ public: bool usePlatformNativeDialog(DialogType type) const override; QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; + static QAndroidPlatformTheme *instance( + QAndroidPlatformNativeInterface * androidPlatformNativeInterface = nullptr); private: + QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface); + static QAndroidPlatformTheme * m_instance; std::shared_ptr m_androidStyleData; QPalette m_defaultPalette; QFont m_systemFont;