diff --git a/docs/api/document.md b/docs/api/document.md index b0ab92f8..2e0ab01a 100644 --- a/docs/api/document.md +++ b/docs/api/document.md @@ -28,6 +28,8 @@ [filename](public/ModuleAppCompatActivity.md ':include') +[filename](public/ModuleContextThemeWrapper.md ':include') + [filename](public/YukiModuleResources.md ':include') [filename](public/YukiResources.md ':include') diff --git a/docs/api/public/ModuleContextThemeWrapper.md b/docs/api/public/ModuleContextThemeWrapper.md new file mode 100644 index 00000000..fb1b83c3 --- /dev/null +++ b/docs/api/public/ModuleContextThemeWrapper.md @@ -0,0 +1,31 @@ +## ModuleContextThemeWrapper *- class* + +```kotlin +class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: Int, configuration: Configuration?) : ContextThemeWrapper +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 代理 `ContextThemeWrapper`。 + +通过包装,你可以轻松在 (Xposed) 宿主环境使用来自模块的主题资源。 + +### applyConfiguration *- method* + +```kotlin +fun applyConfiguration(initiate: Configuration.() -> Unit): ModuleContextThemeWrapper +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 设置当前 `ModuleContextThemeWrapper` 的 `Configuration`。 + +设置后会自动调用 `Resources.updateConfiguration`。 \ No newline at end of file diff --git a/docs/api/public/YukiHookFactory.md b/docs/api/public/YukiHookFactory.md index f2ee7006..eb114716 100644 --- a/docs/api/public/YukiHookFactory.md +++ b/docs/api/public/YukiHookFactory.md @@ -342,7 +342,7 @@ context.startActivity(context, HostTestActivity::class.java) ### Context.applyTheme *- ext-method* ```kotlin -fun Context.applyTheme(theme: Int, isUseNewConfig: Boolean): ModuleContextThemeWrapper +fun Context.applyTheme(theme: Int, configuration: Configuration?): ModuleContextThemeWrapper ``` **变更记录** @@ -355,7 +355,7 @@ fun Context.applyTheme(theme: Int, isUseNewConfig: Boolean): ModuleContextThemeW 在 Hook APP (宿主) 中使用此方法会自动调用 `injectModuleAppResources` 注入当前 Xposed 模块的资源。 -如果在 Hook APP (宿主) 中使用此方法发生 `ClassCastException`,请设置 `isUseNewConfig` 为 `true`。 +如果在 Hook APP (宿主) 中使用此方法发生 `ClassCastException`,请手动设置 `configuration`。 为防止资源 ID 互相冲突,你需要在当前 Xposed 模块项目的 `build.gradle` 中修改资源 ID。 @@ -406,6 +406,33 @@ injectMember { } ``` +你还可以对当前 `Context` 通过 `uiMode` 设置原生的夜间模式和日间模式,至少需要 Android 10 及以上系统版本支持且当前主题包含夜间模式相关元素。 + +> 示例如下 + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + // 定义当前模块中的主题资源 + var appCompatContext: ModuleContextThemeWrapper + // <方案1> 直接得到 Configuration 对象设置 + appCompatContext = instance().applyTheme(R.style.Theme_AppCompat).applyConfiguration { uiMode = Configuration.UI_MODE_NIGHT_YES } + // <方案2> 创建一个新的 Configuration 对象,但会破坏当前宿主中原有的字体缩放大小等设置,你需要手动重新传递 densityDpi 等参数 + appCompatContext = instance().applyTheme(R.style.Theme_AppCompat, Configuration().apply { uiMode = Configuration.UI_MODE_NIGHT_YES }) + // 直接使用这个包装了模块主题后的 Context 创建对话框 + MaterialAlertDialogBuilder(appCompatContext) + .setTitle("AppCompat 主题对话框") + .setMessage("我是一个在宿主中显示的 AppCompat 主题对话框。") + .setPositiveButton("确定", null) + .show() + } +} +``` + 这样,我们就可以在宿主中非常简单地使用 `MaterialAlertDialogBuilder` 创建对话框了。 ### ~~isSupportResourcesHook *- field*~~ diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt index b73a0eee..b076edd6 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt @@ -156,15 +156,15 @@ fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.regi * * 在 Hook APP (宿主) 中使用此方法会自动调用 [injectModuleAppResources] 注入当前 Xposed 模块的资源 * - * - 如果在 Hook APP (宿主) 中使用此方法发生 [ClassCastException] - 请设置 [isUseNewConfig] 为 true + * - 如果在 Hook APP (宿主) 中使用此方法发生 [ClassCastException] - 请手动设置新的 [configuration] * * 详情请参考 [API 文档 - Context.applyTheme](https://fankes.github.io/YukiHookAPI/#/api/document?id=contextapplytheme-ext-method) * @param theme 主题资源 ID - * @param isUseNewConfig 是否使用新的 [Configuration] - 默认否 + * @param configuration 使用的 [Configuration] - 默认空 * @return [ModuleContextThemeWrapper] */ -fun Context.applyTheme(@StyleRes theme: Int, isUseNewConfig: Boolean = false) = - ModuleContextThemeWrapper.wrapper(baseContext = this, theme, isUseNewConfig) +fun Context.applyTheme(@StyleRes theme: Int, configuration: Configuration? = null) = + ModuleContextThemeWrapper.wrapper(baseContext = this, theme, configuration) /** * 仅判断模块是否在太极、无极中激活 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/context/wrapper/ModuleContextThemeWrapper.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/context/wrapper/ModuleContextThemeWrapper.kt index 906c2d6b..0df8078d 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/context/wrapper/ModuleContextThemeWrapper.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/context/wrapper/ModuleContextThemeWrapper.kt @@ -26,6 +26,8 @@ * This file is Created by fankes on 2022/8/15. * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/ui/CommonContextWrapper.java */ +@file:Suppress("unused", "DEPRECATION") + package com.highcapable.yukihookapi.hook.xposed.parasitic.context.wrapper import android.content.Context @@ -42,9 +44,9 @@ import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLo * 通过包装 - 你可以轻松在 (Xposed) 宿主环境使用来自模块的主题资源 * @param baseContext 原始 [Context] * @param theme 使用的主题 - * @param isUseNewConfig 是否使用新的 [Configuration] + * @param configuration 使用的 [Configuration] */ -class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: Int, isUseNewConfig: Boolean) : +class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: Int, configuration: Configuration?) : ContextThemeWrapper(baseContext, theme) { internal companion object { @@ -53,13 +55,13 @@ class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: * 从 [Context] 创建 [ModuleContextThemeWrapper] * @param baseContext 对接的 [Context] * @param theme 需要使用的主题 - * @param isUseNewConfig 是否使用新的 [Configuration] + * @param configuration 使用的 [Configuration] * @return [ModuleContextThemeWrapper] * @throws IllegalStateException 如果重复装载 */ - internal fun wrapper(baseContext: Context, theme: Int, isUseNewConfig: Boolean) = + internal fun wrapper(baseContext: Context, theme: Int, configuration: Configuration?) = if (baseContext !is ModuleContextThemeWrapper) - ModuleContextThemeWrapper(baseContext, theme, isUseNewConfig) + ModuleContextThemeWrapper(baseContext, theme, configuration) else error("ModuleContextThemeWrapper already loaded") } @@ -67,11 +69,26 @@ class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: private var baseResources: Resources? = null init { - if (isUseNewConfig && baseContext.resources?.configuration != null) - baseResources = baseContext.createConfigurationContext(baseContext.resources.configuration)?.resources + configuration?.also { + baseResources = baseContext.createConfigurationContext(it)?.resources + baseResources?.updateConfiguration(it, baseContext.resources.displayMetrics) + } if (YukiHookBridge.hasXposedBridge) resources?.injectModuleAppResources() } + /** + * 设置当前 [ModuleContextThemeWrapper] 的 [Configuration] + * + * 设置后会自动调用 [Resources.updateConfiguration] + * @param initiate [Configuration] 方法体 + * @return [ModuleContextThemeWrapper] + */ + fun applyConfiguration(initiate: Configuration.() -> Unit): ModuleContextThemeWrapper { + resources?.configuration?.apply(initiate) + resources?.updateConfiguration(resources?.configuration, resources?.displayMetrics) + return this + } + override fun getClassLoader(): ClassLoader = ModuleClassLoader.instance() override fun getResources(): Resources? = baseResources ?: super.getResources()