Просмотр исходного кода

fix: WebView 日夜切换只生效一次,之后永久停在首次切换的那一侧

段评的 applyWebViewTheme 会给 documentElement 写 color-scheme(!important)。
页面一旦显式声明配色方案,Chromium 就认定它自带深浅色适配,从此不再对它做
算法反色;而第三方页面的白底是自己 CSS 写死的,仅靠 color-scheme: dark 改不动,
于是第一次切换之后就再也变不回去。方向无关,坏的是第二次及以后的每一次。

实测:系统深浅色来回切三轮,顶栏亮度 39 ↔ 241 每次都翻,
WebView 区域死在 247.2 不动(截图区域平均亮度)。

处理:
- 抽出 WebView.applyDayNight(),去掉 color-scheme 写入,只保留配置分发、
  算法反色开关和 legado-theme-change 事件;段评与订阅页共用同一份实现,
  避免两处各写一份再次跑偏。
- 订阅页接入该 helper,并在 onPageFinished 补一次,使主题切换后再加载或
  站内跳转的页面也带上正确的深浅色。
- 段评的 onPageFinished 在主题流尚未首次发射时回落到 AppConfig.isNightTheme,
  与订阅页初始化时的兜底对齐。
- setDarkeningAllowed 改用 WebViewFeature.ALGORITHMIC_DARKENING 特性检测:
  能否算法反色取决于 WebView 内核版本而非 SDK_INT,旧 forceDark 兜底路径不变。
5151561 3 месяцев назад
Родитель
Сommit
832e7799e8

+ 2 - 3
app/src/main/java/io/legado/app/ui/rss/read/RssReadRouteScreen.kt

@@ -71,9 +71,9 @@ import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.TopBarActionButton
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import io.legado.app.utils.NetworkUtils
+import io.legado.app.utils.applyDayNight
 import io.legado.app.utils.keepScreenOn
 import io.legado.app.utils.openUrl
-import io.legado.app.utils.setDarkeningAllowed
 import io.legado.app.utils.share
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.toastOnUi
@@ -203,8 +203,7 @@ fun RssReadRouteScreen(
 
     LaunchedEffect(isNight, webView) {
         val currentWebView = webView ?: return@LaunchedEffect
-        currentWebView.settings.setDarkeningAllowed(isNight)
-        currentWebView.postInvalidate()
+        currentWebView.applyDayNight(isNight)
     }
 
     Box(modifier = Modifier.fillMaxSize()) {

+ 2 - 0
app/src/main/java/io/legado/app/ui/rss/read/RssReadWebController.kt

@@ -35,6 +35,7 @@ import io.legado.app.lib.dialogs.SelectItem
 import io.legado.app.lib.dialogs.selector
 import io.legado.app.model.Download
 import io.legado.app.ui.association.OnLineImportActivity
+import io.legado.app.utils.applyDayNight
 import io.legado.app.utils.isTrue
 import io.legado.app.utils.longSnackbar
 import io.legado.app.utils.openUrl
@@ -246,6 +247,7 @@ internal fun configureRssReadWebView(
 
         override fun onPageFinished(view: WebView, url: String?) {
             super.onPageFinished(view, url)
+            view.applyDayNight(AppConfig.isNightTheme)
             view.title?.let { webTitle ->
                 if (
                     webTitle != url &&

+ 3 - 24
app/src/main/java/io/legado/app/ui/widget/dialog/BottomWebViewDialog.kt

@@ -3,7 +3,6 @@ package io.legado.app.ui.widget.dialog
 import android.annotation.SuppressLint
 import android.content.Context
 import android.content.pm.ActivityInfo
-import android.content.res.Configuration
 import android.graphics.Bitmap
 import android.net.Uri
 import android.net.http.SslError
@@ -72,7 +71,7 @@ import io.legado.app.utils.invisible
 import io.legado.app.utils.longSnackbar
 import io.legado.app.utils.openUrl
 import io.legado.app.utils.setLayout
-import io.legado.app.utils.setDarkeningAllowed
+import io.legado.app.utils.applyDayNight
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -527,27 +526,7 @@ class BottomWebViewDialog() : BottomSheetDialogFragment(R.layout.dialog_web_view
     private fun applyWebViewTheme(isDark: Boolean, force: Boolean = false) {
         if (!force && appliedDarkTheme == isDark) return
         appliedDarkTheme = isDark
-        val configuration = Configuration(currentWebView.resources.configuration).apply {
-            uiMode = (uiMode and Configuration.UI_MODE_NIGHT_MASK.inv()) or if (isDark) {
-                Configuration.UI_MODE_NIGHT_YES
-            } else {
-                Configuration.UI_MODE_NIGHT_NO
-            }
-        }
-        currentWebView.dispatchConfigurationChanged(configuration)
-        currentWebView.settings.setDarkeningAllowed(isDark)
-        currentWebView.evaluateJavascript(
-            """
-            (() => {
-              const root = document.documentElement;
-              if (!root) return;
-              root.style.setProperty('color-scheme', '${if (isDark) "dark" else "light"}', 'important');
-              window.dispatchEvent(new CustomEvent('legado-theme-change', { detail: { dark: $isDark } }));
-            })();
-            """.trimIndent(),
-            null,
-        )
-        currentWebView.postInvalidate()
+        currentWebView.applyDayNight(isDark)
     }
 
     private fun buildSpliceHtml(originalHtml: String): String {
@@ -786,7 +765,7 @@ class BottomWebViewDialog() : BottomSheetDialogFragment(R.layout.dialog_web_view
 
         override fun onPageFinished(view: WebView?, url: String?) {
             super.onPageFinished(view, url)
-            appliedDarkTheme?.let { applyWebViewTheme(it, force = true) }
+            applyWebViewTheme(appliedDarkTheme ?: AppConfig.isNightTheme, force = true)
         }
 
         private fun shouldOverrideUrlLoading(url: Uri): Boolean {

+ 36 - 11
app/src/main/java/io/legado/app/utils/WebSettingsExtensions.kt

@@ -1,23 +1,20 @@
 package io.legado.app.utils
 
-import android.annotation.SuppressLint
-import android.os.Build
+import android.content.res.Configuration
 import android.webkit.WebSettings
+import android.webkit.WebView
 import androidx.webkit.WebSettingsCompat
 import androidx.webkit.WebViewFeature
 
 /**
- * 设置是否夜间模式
+ * 设置是否夜间模式。
+ * 能否算法反色取决于 WebView 内核版本而非系统版本,用特性检测而不是 SDK_INT 判断;
+ * 内核不支持时回落到旧的 forceDark 组合。
  */
-@SuppressLint("RequiresFeature")
 fun WebSettings.setDarkeningAllowed(allow: Boolean) {
-    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-        kotlin.runCatching {
-            WebSettingsCompat.setAlgorithmicDarkeningAllowed(this, allow)
-            return
-        }.onFailure {
-            it.printOnDebug()
-        }
+    if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
+        WebSettingsCompat.setAlgorithmicDarkeningAllowed(this, allow)
+        return
     }
     if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
         @Suppress("DEPRECATION")
@@ -34,3 +31,31 @@ fun WebSettings.setDarkeningAllowed(allow: Boolean) {
         )
     }
 }
+
+/**
+ * 把当前深浅色下发给 WebView。
+ *
+ * 页面侧的 prefers-color-scheme 取自 WebView 上下文主题的 android:isLightTheme,
+ * 也就是 Activity 资源的 uiMode,由 AppCompat 的夜间模式决定,不归这里管;
+ * 这里只负责重绘 WebView 自身、开关兜底的算法反色,并通知页面脚本。
+ *
+ * 不要在这里写 documentElement 的 color-scheme:页面一旦显式声明配色方案,
+ * Chromium 就认为它自带深浅色适配,从此不再对它做算法反色,切回深色时页面会一直停在浅色。
+ */
+fun WebView.applyDayNight(isDark: Boolean) {
+    val configuration = Configuration(resources.configuration).apply {
+        uiMode = (uiMode and Configuration.UI_MODE_NIGHT_MASK.inv()) or if (isDark) {
+            Configuration.UI_MODE_NIGHT_YES
+        } else {
+            Configuration.UI_MODE_NIGHT_NO
+        }
+    }
+    dispatchConfigurationChanged(configuration)
+    settings.setDarkeningAllowed(isDark)
+    evaluateJavascript(
+        "window.dispatchEvent(new CustomEvent('legado-theme-change'," +
+            " { detail: { dark: $isDark } }));",
+        null,
+    )
+    postInvalidate()
+}