Skip to content

Commit 73dce2e

Browse files
committed
fix(browser): 夜间模式改用暗色蒙版并跟随 prefers-color-scheme
App 使用固定的 Light 主题(isLightTheme 恒为 true),而 WebView 依据所在主题的 isLightTheme 决定向网页报告的 prefers-color-scheme,导致支持深色的站点在夜间模式下不会切暗。现用带 isLightTheme 的专用主题创建 WebView 并按夜间模式切换。同时把亮底站点的夜间效果由整页反色改为顶层半透明暗色蒙版(保留原色、不改色相、不影响 fixed 布局),并关闭内核算法调暗。
1 parent 701daba commit 73dce2e

3 files changed

Lines changed: 40 additions & 22 deletions

File tree

‎app/src/main/java/com/aicode/feature/agent/domain/tool/browser/BrowserManager.kt‎

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.os.Handler
99
import android.os.Looper
1010
import android.os.SystemClock
1111
import android.util.Base64
12+
import android.view.ContextThemeWrapper
1213
import android.view.MotionEvent
1314
import android.view.View
1415
import android.view.ViewGroup
@@ -24,6 +25,7 @@ import android.webkit.WebViewClient
2425
import android.widget.FrameLayout
2526
import androidx.webkit.WebSettingsCompat
2627
import androidx.webkit.WebViewFeature
28+
import com.aicode.R
2729
import com.aicode.core.util.FileLogger
2830
import com.aicode.feature.agent.domain.model.AgentImage
2931
import com.aicode.feature.workspace.domain.FileAccessProvider
@@ -119,16 +121,16 @@ class BrowserManager @Inject constructor(
119121
private const val NIGHT_STYLE_ID = "__bicode_night_css__"
120122
private val NIGHT_BG_COLOR = 0xFF121212.toInt()
121123

122-
/** 反色夜间样式:整页 invert + hue-rotate,图片/视频二次反色还原原始观感。 */
124+
/** 夜间压暗样式:页面顶层盖一层半透明黑色蒙版,保留原色、只降低亮度(不改色相、不影响 fixed 布局)。 */
123125
private const val NIGHT_CSS =
124-
"html{filter:invert(1) hue-rotate(180deg);background:#fff}" +
125-
"img,video,picture,canvas,svg image,[style*=\"background-image\"]" +
126-
"{filter:invert(1) hue-rotate(180deg)}"
126+
"html::after{content:'';position:fixed;top:0;left:0;right:0;bottom:0;" +
127+
"pointer-events:none;z-index:2147483647;background:rgba(0,0,0,0.2)}"
127128
}
128129

129130
private class TabHolder(
130131
val id: String,
131132
val webView: WebView,
133+
val themedContext: ContextThemeWrapper,
132134
var url: String = "",
133135
var title: String = "",
134136
var loading: Boolean = false,
@@ -399,9 +401,11 @@ class BrowserManager @Inject constructor(
399401
}
400402

401403
private fun createTabInternal(id: String, initialUrl: String?): TabHolder {
402-
val wv = WebView(appContext)
404+
// WebView 依据其上下文主题的 isLightTheme 向网页报告 prefers-color-scheme,必须用专用主题构建
405+
val themedContext = ContextThemeWrapper(appContext, webViewThemeRes(nightMode))
406+
val wv = WebView(themedContext)
403407
configureWebView(wv, id)
404-
val tab = TabHolder(id = id, webView = wv)
408+
val tab = TabHolder(id = id, webView = wv, themedContext = themedContext)
405409
tabs.add(tab)
406410
activeTabId = id
407411

@@ -449,37 +453,39 @@ class BrowserManager @Inject constructor(
449453
private fun applyNightMode() {
450454
val night = nightMode
451455
tabs.forEach { tab ->
456+
// 先更新主题,让随后的 settings 变更触发 WebView 重读 prefers-color-scheme
457+
tab.themedContext.setTheme(webViewThemeRes(night))
452458
tab.webView.setBackgroundColor(if (night) NIGHT_BG_COLOR else Color.WHITE)
453459
applyDarkThemeToSettings(tab.webView)
454460
applyNightModeTo(tab.webView)
455461
}
456462
publishState()
457463
}
458464

459-
/** 适配内核级暗色模式(开启 prefers-color-scheme: dark 支持)。 */
465+
/** WebView 主题:isLightTheme 决定网页看到的 prefers-color-scheme(见 values/styles.xml)。 */
466+
private fun webViewThemeRes(night: Boolean): Int =
467+
if (night) R.style.Theme_AICode_WebView_Dark else R.style.Theme_AICode_WebView
468+
469+
/**
470+
* 内核级暗色设置。prefers-color-scheme 实际由 WebView 主题的 isLightTheme 决定(见 [webViewThemeRes]),
471+
* 这里设置算法调暗并借此触发 WebView 重读偏好——夜间模式切换后需要一次 settings 变更才会下发新偏好。
472+
*/
460473
private fun applyDarkThemeToSettings(wv: WebView) {
461-
val night = nightMode
462474
try {
463475
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
464-
WebSettingsCompat.setAlgorithmicDarkeningAllowed(wv.settings, night)
476+
// 保持原色,不做内核算法调暗;此处借该开关的值变化触发 WebView 重读偏好——
477+
// 夜间模式切换后必须有一次 settings 变更,新的 prefers-color-scheme 才会下发到渲染进程。
478+
WebSettingsCompat.setAlgorithmicDarkeningAllowed(wv.settings, true)
479+
WebSettingsCompat.setAlgorithmicDarkeningAllowed(wv.settings, false)
465480
} else if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
466-
WebSettingsCompat.setForceDark(
467-
wv.settings,
468-
if (night) WebSettingsCompat.FORCE_DARK_ON else WebSettingsCompat.FORCE_DARK_OFF
469-
)
470-
if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {
471-
WebSettingsCompat.setForceDarkStrategy(
472-
wv.settings,
473-
WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING
474-
)
475-
}
481+
WebSettingsCompat.setForceDark(wv.settings, WebSettingsCompat.FORCE_DARK_OFF)
476482
}
477483
} catch (e: Throwable) {
478484
FileLogger.w(TAG, "Failed to apply dark theme to WebSettings", e)
479485
}
480486
}
481487

482-
/** 注入或移除夜间样式。带原生深色主题保护(对已支持深色的 GitHub 等网站避免反相破坏)。 */
488+
/** 注入或移除夜间压暗蒙版。对已支持原生深色的页面(背景为深色)不压暗。 */
483489
private fun applyNightModeTo(wv: WebView) {
484490
val js = if (nightMode) {
485491
"""

‎app/src/main/res/values/styles.xml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
<item name="android:colorPrimary">@color/primary</item>
55
</style>
66

7+
<!-- 内置浏览器 WebView 专用主题:WebView 依据所在主题的 android:isLightTheme 决定向网页
8+
报告 prefers-color-scheme,而 App 自身是固定 Light 主题 + Compose 控色,故为 WebView
9+
单独提供明/暗两套,由 BrowserManager 按浏览器夜间模式选用。 -->
10+
<style name="Theme.AICode.WebView" parent="Theme.AICode">
11+
<item name="android:isLightTheme">true</item>
12+
</style>
13+
14+
<style name="Theme.AICode.WebView.Dark" parent="android:Theme.Material.NoActionBar">
15+
<item name="android:isLightTheme">false</item>
16+
</style>
17+
718
<!-- 冷启动闪屏主题:以 App 图标与应用表面底色作为系统 Window 启动背景,杜绝冷启动刺眼纯白 -->
819
<style name="Theme.AICode.Splash" parent="Theme.AICode">
920
<item name="android:windowBackground">@drawable/splash_background</item>

‎docs-site/docs/guide/browser.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ AI 也可通过 `browser` 工具的 `navigate` 打开本地页面并截图分析
3636
浏览器内置夜间模式,默认跟随 App 外观:App 为深色时页面自动变暗,App 为浅色时恢复原样。底栏的月亮/太阳图标可手动切换;手动选择后不再跟随 App 外观,切回与 App 外观一致时会自动恢复跟随。
3737

3838
夜间模式采用双轨渲染架构:
39-
- **内核级暗色与原生深色优先**:优先向页面声明 `prefers-color-scheme: dark` 与 `color-scheme: dark`,现代网站(如 GitHub、Google 等)会自动启用官方精细调优的原生暗色主题,文字清晰且深色区域不会反白。
40-
- **智能调暗与防闪白**:在页面启动时立即设置暗色视口背景并提前注入深色偏好,消灭页面刷新瞬间的白屏闪烁;针对无暗色样式的亮底站点提供温和反色与媒体二次反色保护。该效果作用于页面渲染,AI 截图同样会呈现夜间配色。
39+
- **原生深色优先**:向页面声明 `prefers-color-scheme: dark` 与 `color-scheme: dark`,现代网站(如 GitHub、Google 等)会自动启用官方精细调优的原生暗色主题,文字清晰且深色区域不会反白。
40+
- **亮底站点压暗**:对无暗色样式的亮底站点,在页面顶层盖一层半透明暗色蒙版整体压暗(保留原色、只降低亮度,不做反色),观感柔和。该蒙版作用于页面渲染,AI 截图同样会呈现压暗后的配色。
41+
- **防闪白**:在页面启动时立即设置暗色视口背景并提前注入深色偏好,避免页面刷新瞬间的白屏闪烁。
4142

4243
## AI 自动化操作
4344

0 commit comments

Comments
 (0)