11package io.github.kdroidfilter.webview.web
22
3- import androidx.compose.runtime.Composable
4- import androidx.compose.runtime.DisposableEffect
5- import androidx.compose.runtime.LaunchedEffect
6- import androidx.compose.runtime.getValue
7- import androidx.compose.runtime.key
8- import androidx.compose.runtime.mutableStateOf
9- import androidx.compose.runtime.remember
10- import androidx.compose.runtime.rememberCoroutineScope
11- import androidx.compose.runtime.rememberUpdatedState
12- import androidx.compose.runtime.setValue
3+ import androidx.compose.runtime.*
134import androidx.compose.ui.Modifier
145import androidx.compose.ui.awt.SwingPanel
6+ import androidx.compose.ui.graphics.Color
7+ import androidx.compose.ui.graphics.toArgb
158import io.github.kdroidfilter.webview.cookie.WryCookieManager
169import io.github.kdroidfilter.webview.jsbridge.WebViewJsBridge
1710import io.github.kdroidfilter.webview.jsbridge.parseJsMessage
1811import io.github.kdroidfilter.webview.request.WebRequest
1912import io.github.kdroidfilter.webview.request.WebRequestInterceptResult
13+ import io.github.kdroidfilter.webview.wry.Rgba
2014import kotlinx.coroutines.delay
2115
2216actual class WebViewFactoryParam (
2317 val state : WebViewState ,
2418 val fileContent : String = " " ,
25- val userAgent : String? = null ,
2619)
2720
2821actual fun defaultWebViewFactory (param : WebViewFactoryParam ): NativeWebView =
2922 when (val content = param.state.content) {
30- is WebContent .Url -> NativeWebView (content.url, param.userAgent ? : param.state.webSettings.customUserAgentString)
31- else -> NativeWebView (" about:blank" , param.userAgent ? : param.state.webSettings.customUserAgentString)
23+ is WebContent .Url -> NativeWebView (
24+ initialUrl = content.url,
25+ customUserAgent = param.state.webSettings.customUserAgentString,
26+ dataDirectory = param.state.webSettings.desktopWebSettings.dataDirectory,
27+ supportZoom = param.state.webSettings.supportZoom,
28+ backgroundColor = param.state.webSettings.backgroundColor.toRgba(),
29+ transparent = param.state.webSettings.desktopWebSettings.transparent,
30+ initScript = param.state.webSettings.desktopWebSettings.initScript,
31+ enableClipboard = param.state.webSettings.desktopWebSettings.enableClipboard,
32+ enableDevtools = param.state.webSettings.desktopWebSettings.enableDevtools,
33+ enableNavigationGestures = param.state.webSettings.desktopWebSettings.enableNavigationGestures,
34+ incognito = param.state.webSettings.desktopWebSettings.incognito,
35+ autoplayWithoutUserInteraction = param.state.webSettings.desktopWebSettings.autoplayWithoutUserInteraction,
36+ focused = param.state.webSettings.desktopWebSettings.focused
37+ )
38+
39+ else -> NativeWebView (
40+ initialUrl = " about:blank" ,
41+ customUserAgent = param.state.webSettings.customUserAgentString,
42+ dataDirectory = param.state.webSettings.desktopWebSettings.dataDirectory,
43+ supportZoom = param.state.webSettings.supportZoom,
44+ backgroundColor = param.state.webSettings.backgroundColor.toRgba(),
45+ transparent = param.state.webSettings.desktopWebSettings.transparent,
46+ initScript = param.state.webSettings.desktopWebSettings.initScript,
47+ enableClipboard = param.state.webSettings.desktopWebSettings.enableClipboard,
48+ enableDevtools = param.state.webSettings.desktopWebSettings.enableDevtools,
49+ enableNavigationGestures = param.state.webSettings.desktopWebSettings.enableNavigationGestures,
50+ incognito = param.state.webSettings.desktopWebSettings.incognito,
51+ autoplayWithoutUserInteraction = param.state.webSettings.desktopWebSettings.autoplayWithoutUserInteraction,
52+ focused = param.state.webSettings.desktopWebSettings.focused
53+ )
3254 }
3355
3456@Composable
@@ -44,18 +66,25 @@ actual fun ActualWebView(
4466 val currentOnDispose by rememberUpdatedState(onDispose)
4567 val scope = rememberCoroutineScope()
4668
47- val desiredUserAgent = state.webSettings.customUserAgentString?.trim()?.takeIf { it.isNotEmpty() }
48- var effectiveUserAgent by remember { mutableStateOf(desiredUserAgent) }
69+ val desiredSettingsKey = state.webSettings.let {
70+ listOf (
71+ it.customUserAgentString?.trim()?.takeIf (String ::isNotEmpty),
72+ it.supportZoom,
73+ it.backgroundColor,
74+ )
75+ }
76+
77+ var effectiveSettingsKey by remember { mutableStateOf(desiredSettingsKey) }
4978
50- LaunchedEffect (desiredUserAgent ) {
51- if (desiredUserAgent == effectiveUserAgent) return @LaunchedEffect
52- // Wry applies user-agent at creation time, so recreate the webview after a small debounce.
53- delay( 400 )
54- effectiveUserAgent = desiredUserAgent
79+ LaunchedEffect (desiredSettingsKey ) {
80+ if (desiredSettingsKey != effectiveSettingsKey) {
81+ delay( 400 )
82+ effectiveSettingsKey = desiredSettingsKey
83+ }
5584 }
5685
57- key(effectiveUserAgent ) {
58- val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam (state, userAgent = effectiveUserAgent )) }
86+ key(effectiveSettingsKey ) {
87+ val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam (state)) }
5988
6089 val desktopWebView =
6190 remember(nativeWebView, scope, webViewJsBridge) {
@@ -73,52 +102,58 @@ actual fun ActualWebView(
73102 }
74103
75104 // Poll native state (URL/loading/title/nav) and drain IPC messages for JS bridge.
76- LaunchedEffect (nativeWebView, state, navigator, webViewJsBridge) {
77- while (true ) {
78- if (! nativeWebView.isReady()) {
79- if (state.loadingState !is LoadingState .Initializing ) {
80- state.loadingState = LoadingState .Initializing
105+ listOf (nativeWebView, state, navigator, webViewJsBridge).let {
106+ LaunchedEffect (it) {
107+ while (true ) {
108+ if (! nativeWebView.isReady()) {
109+ if (state.loadingState !is LoadingState .Initializing ) {
110+ state.loadingState = LoadingState .Initializing
111+ }
112+ delay(50 )
113+ continue
81114 }
82- delay(50 )
83- continue
84- }
85115
86- val isLoading = nativeWebView.isLoading()
87- state.loadingState =
88- if (isLoading) {
89- val current = state.loadingState
90- val next =
91- when (current) {
92- is LoadingState .Loading -> (current.progress + 0.02f ).coerceAtMost(0.9f )
93- else -> 0.1f
94- }
95- LoadingState .Loading (next)
96- } else {
97- LoadingState .Finished
116+ val isLoading = nativeWebView.isLoading()
117+ state.loadingState =
118+ if (isLoading) {
119+ val next =
120+ when (val current = state.loadingState) {
121+ is LoadingState .Loading -> (current.progress + 0.02f ).coerceAtMost(0.9f )
122+ else -> 0.1f
123+ }
124+ LoadingState .Loading (next)
125+ } else {
126+ LoadingState .Finished
127+ }
128+
129+ val url = nativeWebView.getCurrentUrl()
130+ if (! url.isNullOrBlank()) {
131+ if (! isLoading || state.lastLoadedUrl.isNullOrBlank()) {
132+ state.lastLoadedUrl = url
133+ }
98134 }
99135
100- val url = nativeWebView.getCurrentUrl()
101- if (! url.isNullOrBlank()) {
102- if (! isLoading || state.lastLoadedUrl.isNullOrBlank()) {
103- state.lastLoadedUrl = url
136+ val title = nativeWebView.getTitle()
137+ if (! title.isNullOrBlank()) {
138+ state.pageTitle = title
104139 }
105- }
106140
107- val title = nativeWebView.getTitle()
108- if (! title.isNullOrBlank()) {
109- state.pageTitle = title
110- }
141+ navigator.canGoBack = nativeWebView.canGoBack()
142+ navigator.canGoForward = nativeWebView.canGoForward()
111143
112- navigator.canGoBack = nativeWebView.canGoBack()
113- navigator.canGoForward = nativeWebView.canGoForward()
144+ delay(250 )
145+ }
146+ }
114147
115- if (webViewJsBridge != null ) {
116- for (raw in nativeWebView.drainIpcMessages()) {
117- parseJsMessage(raw)?.let { webViewJsBridge.dispatch(it) }
148+ LaunchedEffect (it) {
149+ while (true ) {
150+ if (webViewJsBridge != null ) {
151+ for (raw in nativeWebView.drainIpcMessages()) {
152+ parseJsMessage(raw)?.let { webViewJsBridge.dispatch(it) }
153+ }
118154 }
155+ delay(50 )
119156 }
120-
121- delay(250 )
122157 }
123158 }
124159
@@ -137,7 +172,8 @@ actual fun ActualWebView(
137172 method = " GET" ,
138173 )
139174
140- return @a when (val interceptResult = navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) {
175+ return @a when (val interceptResult =
176+ navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) {
141177 WebRequestInterceptResult .Allow -> true
142178
143179 WebRequestInterceptResult .Reject -> false
@@ -174,3 +210,12 @@ actual fun ActualWebView(
174210 }
175211 }
176212}
213+
214+ private fun Color.toRgba (): Rgba {
215+ val argb: Int = this .toArgb() // 0xAARRGGBB (sRGB)
216+ val a: UByte = ((argb ushr 24 ) and 0xFF ).toUByte()
217+ val r: UByte = ((argb ushr 16 ) and 0xFF ).toUByte()
218+ val g: UByte = ((argb ushr 8 ) and 0xFF ).toUByte()
219+ val b: UByte = (argb and 0xFF ).toUByte()
220+ return Rgba (r = r, g = g, b = b, a = a)
221+ }
0 commit comments