Last active
March 26, 2025 09:30
-
-
Save holmeszyx/c32534db1fd4bd01f2ebcdb643c431d0 to your computer and use it in GitHub Desktop.
Compose Webview forked from https://github.com/google/accompanist/blob/compose-1.6/web/src/main/java/com/google/accompanist/web/WebView.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2021 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* https://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.google.accompanist.web | |
import android.content.Context | |
import android.graphics.Bitmap | |
import android.os.Bundle | |
import android.view.ViewGroup.LayoutParams | |
import android.webkit.WebChromeClient | |
import android.webkit.WebResourceError | |
import android.webkit.WebResourceRequest | |
import android.webkit.WebView | |
import android.webkit.WebViewClient | |
import android.widget.FrameLayout | |
import androidx.activity.compose.BackHandler | |
import androidx.compose.foundation.layout.BoxWithConstraints | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.Immutable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.Stable | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateListOf | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.rememberCoroutineScope | |
import androidx.compose.runtime.saveable.Saver | |
import androidx.compose.runtime.saveable.mapSaver | |
import androidx.compose.runtime.saveable.rememberSaveable | |
import androidx.compose.runtime.setValue | |
import androidx.compose.runtime.snapshotFlow | |
import androidx.compose.runtime.snapshots.SnapshotStateList | |
import androidx.compose.ui.ExperimentalComposeUiApi | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.viewinterop.AndroidView | |
import com.google.accompanist.web.LoadingState.Finished | |
import com.google.accompanist.web.LoadingState.Loading | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.flow.MutableSharedFlow | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.withContext | |
/** | |
* A wrapper around the Android View WebView to provide a basic WebView composable. | |
* | |
* If you require more customisation you are most likely better rolling your own and using this | |
* wrapper as an example. | |
* | |
* The WebView attempts to set the layoutParams based on the Compose modifier passed in. If it | |
* is incorrectly sizing, use the layoutParams composable function instead. | |
* | |
* @param state The webview state holder where the Uri to load is defined. | |
* @param modifier A compose modifier | |
* @param captureBackPresses Set to true to have this Composable capture back presses and navigate | |
* the WebView back. | |
* @param navigator An optional navigator object that can be used to control the WebView's | |
* navigation from outside the composable. | |
* @param onCreated Called when the WebView is first created, this can be used to set additional | |
* settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be | |
* subsequently overwritten after this lambda is called. | |
* @param onDispose Called when the WebView is destroyed. Provides a bundle which can be saved | |
* if you need to save and restore state in this WebView. | |
* @param client Provides access to WebViewClient via subclassing | |
* @param chromeClient Provides access to WebChromeClient via subclassing | |
* @param factory An optional WebView factory for using a custom subclass of WebView | |
* @sample com.google.accompanist.sample.webview.BasicWebViewSample | |
*/ | |
@Composable | |
public fun WebView( | |
state: WebViewState, | |
modifier: Modifier = Modifier, | |
captureBackPresses: Boolean = true, | |
navigator: WebViewNavigator = rememberWebViewNavigator(), | |
onCreated: (WebView) -> Unit = {}, | |
onDispose: (WebView) -> Unit = {}, | |
client: AccompanistWebViewClient = remember { AccompanistWebViewClient() }, | |
chromeClient: AccompanistWebChromeClient = remember { AccompanistWebChromeClient() }, | |
factory: ((Context) -> WebView)? = null, | |
) { | |
BoxWithConstraints(modifier) { | |
// WebView changes it's layout strategy based on | |
// it's layoutParams. We convert from Compose Modifier to | |
// layout params here. | |
val width = | |
if (constraints.hasFixedWidth) | |
LayoutParams.MATCH_PARENT | |
else | |
LayoutParams.WRAP_CONTENT | |
val height = | |
if (constraints.hasFixedHeight) | |
LayoutParams.MATCH_PARENT | |
else | |
LayoutParams.WRAP_CONTENT | |
val layoutParams = FrameLayout.LayoutParams( | |
width, | |
height | |
) | |
WebView( | |
state, | |
layoutParams, | |
Modifier, | |
captureBackPresses, | |
navigator, | |
onCreated, | |
onDispose, | |
client, | |
chromeClient, | |
factory | |
) | |
} | |
} | |
/** | |
* A wrapper around the Android View WebView to provide a basic WebView composable. | |
* | |
* If you require more customisation you are most likely better rolling your own and using this | |
* wrapper as an example. | |
* | |
* The WebView attempts to set the layoutParams based on the Compose modifier passed in. If it | |
* is incorrectly sizing, use the layoutParams composable function instead. | |
* | |
* @param state The webview state holder where the Uri to load is defined. | |
* @param layoutParams A FrameLayout.LayoutParams object to custom size the underlying WebView. | |
* @param modifier A compose modifier | |
* @param captureBackPresses Set to true to have this Composable capture back presses and navigate | |
* the WebView back. | |
* @param navigator An optional navigator object that can be used to control the WebView's | |
* navigation from outside the composable. | |
* @param onCreated Called when the WebView is first created, this can be used to set additional | |
* settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be | |
* subsequently overwritten after this lambda is called. | |
* @param onDispose Called when the WebView is destroyed. Provides a bundle which can be saved | |
* if you need to save and restore state in this WebView. | |
* @param client Provides access to WebViewClient via subclassing | |
* @param chromeClient Provides access to WebChromeClient via subclassing | |
* @param factory An optional WebView factory for using a custom subclass of WebView | |
*/ | |
@OptIn(ExperimentalComposeUiApi::class) | |
@Composable | |
public fun WebView( | |
state: WebViewState, | |
layoutParams: FrameLayout.LayoutParams, | |
modifier: Modifier = Modifier, | |
captureBackPresses: Boolean = true, | |
navigator: WebViewNavigator = rememberWebViewNavigator(), | |
onCreated: (WebView) -> Unit = {}, | |
onDispose: (WebView) -> Unit = {}, | |
client: AccompanistWebViewClient = remember { AccompanistWebViewClient() }, | |
chromeClient: AccompanistWebChromeClient = remember { AccompanistWebChromeClient() }, | |
factory: ((Context) -> WebView)? = null, | |
) { | |
val webView = state.webView | |
BackHandler(captureBackPresses && navigator.canGoBack) { | |
webView?.goBack() | |
} | |
webView?.let { wv -> | |
LaunchedEffect(wv, navigator) { | |
with(navigator) { | |
wv.handleNavigationEvents() | |
} | |
} | |
LaunchedEffect(wv, state) { | |
snapshotFlow { state.content }.collect { content -> | |
when (content) { | |
is WebContent.Url -> { | |
wv.loadUrl(content.url, content.additionalHttpHeaders) | |
} | |
is WebContent.Data -> { | |
wv.loadDataWithBaseURL( | |
content.baseUrl, | |
content.data, | |
content.mimeType, | |
content.encoding, | |
content.historyUrl | |
) | |
} | |
is WebContent.Post -> { | |
wv.postUrl( | |
content.url, | |
content.postData | |
) | |
} | |
is WebContent.NavigatorOnly -> { | |
// NO-OP | |
} | |
} | |
} | |
} | |
} | |
// Set the state of the client and chrome client | |
// This is done internally to ensure they always are the same instance as the | |
// parent Web composable | |
client.state = state | |
client.navigator = navigator | |
chromeClient.state = state | |
AndroidView( | |
factory = { context -> | |
(factory?.invoke(context) ?: WebView(context)).apply { | |
onCreated(this) | |
this.layoutParams = layoutParams | |
state.viewState?.let { | |
this.restoreState(it) | |
} | |
webChromeClient = chromeClient | |
webViewClient = client | |
}.also { state.webView = it } | |
}, | |
onReset = {}, | |
modifier = modifier, | |
onRelease = { | |
onDispose(it) | |
} | |
) | |
} | |
/** | |
* AccompanistWebViewClient | |
* | |
* A parent class implementation of WebViewClient that can be subclassed to add custom behaviour. | |
* | |
* As Accompanist Web needs to set its own web client to function, it provides this intermediary | |
* class that can be overriden if further custom behaviour is required. | |
*/ | |
public open class AccompanistWebViewClient : WebViewClient() { | |
public open lateinit var state: WebViewState | |
internal set | |
public open lateinit var navigator: WebViewNavigator | |
internal set | |
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { | |
super.onPageStarted(view, url, favicon) | |
state.loadingState = Loading(0.0f) | |
state.errorsForCurrentRequest.clear() | |
state.pageTitle = null | |
state.pageIcon = null | |
state.lastLoadedUrl = url | |
} | |
override fun onPageFinished(view: WebView, url: String?) { | |
super.onPageFinished(view, url) | |
state.loadingState = Finished | |
} | |
override fun doUpdateVisitedHistory(view: WebView, url: String?, isReload: Boolean) { | |
super.doUpdateVisitedHistory(view, url, isReload) | |
navigator.canGoBack = view.canGoBack() | |
navigator.canGoForward = view.canGoForward() | |
} | |
override fun onReceivedError( | |
view: WebView, | |
request: WebResourceRequest?, | |
error: WebResourceError? | |
) { | |
super.onReceivedError(view, request, error) | |
if (error != null) { | |
state.errorsForCurrentRequest.add(WebViewError(request, error)) | |
} | |
} | |
} | |
/** | |
* AccompanistWebChromeClient | |
* | |
* A parent class implementation of WebChromeClient that can be subclassed to add custom behaviour. | |
* | |
* As Accompanist Web needs to set its own web client to function, it provides this intermediary | |
* class that can be overriden if further custom behaviour is required. | |
*/ | |
public open class AccompanistWebChromeClient : WebChromeClient() { | |
public open lateinit var state: WebViewState | |
internal set | |
override fun onReceivedTitle(view: WebView, title: String?) { | |
super.onReceivedTitle(view, title) | |
state.pageTitle = title | |
} | |
override fun onReceivedIcon(view: WebView, icon: Bitmap?) { | |
super.onReceivedIcon(view, icon) | |
state.pageIcon = icon | |
} | |
override fun onProgressChanged(view: WebView, newProgress: Int) { | |
super.onProgressChanged(view, newProgress) | |
if (state.loadingState is Finished) return | |
state.loadingState = Loading(newProgress / 100.0f) | |
} | |
} | |
public sealed class WebContent { | |
public data class Url( | |
val url: String, | |
val additionalHttpHeaders: Map<String, String> = emptyMap(), | |
) : WebContent() | |
public data class Data( | |
val data: String, | |
val baseUrl: String? = null, | |
val encoding: String = "utf-8", | |
val mimeType: String? = null, | |
val historyUrl: String? = null | |
) : WebContent() | |
public data class Post( | |
val url: String, | |
val postData: ByteArray | |
) : WebContent() { | |
override fun equals(other: Any?): Boolean { | |
if (this === other) return true | |
if (javaClass != other?.javaClass) return false | |
other as Post | |
if (url != other.url) return false | |
if (!postData.contentEquals(other.postData)) return false | |
return true | |
} | |
override fun hashCode(): Int { | |
var result = url.hashCode() | |
result = 31 * result + postData.contentHashCode() | |
return result | |
} | |
} | |
@Deprecated("Use state.lastLoadedUrl instead") | |
public fun getCurrentUrl(): String? { | |
return when (this) { | |
is Url -> url | |
is Data -> baseUrl | |
is Post -> url | |
is NavigatorOnly -> throw IllegalStateException("Unsupported") | |
} | |
} | |
public object NavigatorOnly : WebContent() | |
} | |
internal fun WebContent.withUrl(url: String) = when (this) { | |
is WebContent.Url -> copy(url = url) | |
else -> WebContent.Url(url) | |
} | |
/** | |
* Sealed class for constraining possible loading states. | |
* See [Loading] and [Finished]. | |
*/ | |
public sealed class LoadingState { | |
/** | |
* Describes a WebView that has not yet loaded for the first time. | |
*/ | |
public object Initializing : LoadingState() | |
/** | |
* Describes a webview between `onPageStarted` and `onPageFinished` events, contains a | |
* [progress] property which is updated by the webview. | |
*/ | |
public data class Loading(val progress: Float) : LoadingState() | |
/** | |
* Describes a webview that has finished loading content. | |
*/ | |
public object Finished : LoadingState() | |
} | |
/** | |
* A state holder to hold the state for the WebView. In most cases this will be remembered | |
* using the rememberWebViewState(uri) function. | |
*/ | |
@Stable | |
public class WebViewState(webContent: WebContent) { | |
public var lastLoadedUrl: String? by mutableStateOf(null) | |
internal set | |
/** | |
* The content being loaded by the WebView | |
*/ | |
public var content: WebContent by mutableStateOf(webContent) | |
/** | |
* Whether the WebView is currently [LoadingState.Loading] data in its main frame (along with | |
* progress) or the data loading has [LoadingState.Finished]. See [LoadingState] | |
*/ | |
public var loadingState: LoadingState by mutableStateOf(LoadingState.Initializing) | |
internal set | |
/** | |
* Whether the webview is currently loading data in its main frame | |
*/ | |
public val isLoading: Boolean | |
get() = loadingState !is Finished | |
/** | |
* The title received from the loaded content of the current page | |
*/ | |
public var pageTitle: String? by mutableStateOf(null) | |
internal set | |
/** | |
* the favicon received from the loaded content of the current page | |
*/ | |
public var pageIcon: Bitmap? by mutableStateOf(null) | |
internal set | |
/** | |
* A list for errors captured in the last load. Reset when a new page is loaded. | |
* Errors could be from any resource (iframe, image, etc.), not just for the main page. | |
* For more fine grained control use the OnError callback of the WebView. | |
*/ | |
public val errorsForCurrentRequest: SnapshotStateList<WebViewError> = mutableStateListOf() | |
/** | |
* The saved view state from when the view was destroyed last. To restore state, | |
* use the navigator and only call loadUrl if the bundle is null. | |
* See WebViewSaveStateSample. | |
*/ | |
public var viewState: Bundle? = null | |
internal set | |
// We need access to this in the state saver. An internal DisposableEffect or AndroidView | |
// onDestroy is called after the state saver and so can't be used. | |
internal var webView by mutableStateOf<WebView?>(null) | |
} | |
/** | |
* Allows control over the navigation of a WebView from outside the composable. E.g. for performing | |
* a back navigation in response to the user clicking the "up" button in a TopAppBar. | |
* | |
* @see [rememberWebViewNavigator] | |
*/ | |
@Stable | |
public class WebViewNavigator(private val coroutineScope: CoroutineScope) { | |
private sealed interface NavigationEvent { | |
object Back : NavigationEvent | |
object Forward : NavigationEvent | |
object Reload : NavigationEvent | |
object StopLoading : NavigationEvent | |
data class LoadUrl( | |
val url: String, | |
val additionalHttpHeaders: Map<String, String> = emptyMap() | |
) : NavigationEvent | |
data class LoadHtml( | |
val html: String, | |
val baseUrl: String? = null, | |
val mimeType: String? = null, | |
val encoding: String? = "utf-8", | |
val historyUrl: String? = null | |
) : NavigationEvent | |
data class PostUrl( | |
val url: String, | |
val postData: ByteArray | |
) : NavigationEvent { | |
override fun equals(other: Any?): Boolean { | |
if (this === other) return true | |
if (javaClass != other?.javaClass) return false | |
other as PostUrl | |
if (url != other.url) return false | |
if (!postData.contentEquals(other.postData)) return false | |
return true | |
} | |
override fun hashCode(): Int { | |
var result = url.hashCode() | |
result = 31 * result + postData.contentHashCode() | |
return result | |
} | |
} | |
} | |
private val navigationEvents: MutableSharedFlow<NavigationEvent> = MutableSharedFlow(replay = 1) | |
// Use Dispatchers.Main to ensure that the webview methods are called on UI thread | |
internal suspend fun WebView.handleNavigationEvents(): Nothing = withContext(Dispatchers.Main) { | |
navigationEvents.collect { event -> | |
when (event) { | |
is NavigationEvent.Back -> goBack() | |
is NavigationEvent.Forward -> goForward() | |
is NavigationEvent.Reload -> reload() | |
is NavigationEvent.StopLoading -> stopLoading() | |
is NavigationEvent.LoadHtml -> loadDataWithBaseURL( | |
event.baseUrl, | |
event.html, | |
event.mimeType, | |
event.encoding, | |
event.historyUrl | |
) | |
is NavigationEvent.LoadUrl -> { | |
loadUrl(event.url, event.additionalHttpHeaders) | |
} | |
is NavigationEvent.PostUrl -> { | |
postUrl(event.url, event.postData) | |
} | |
} | |
} | |
} | |
/** | |
* True when the web view is able to navigate backwards, false otherwise. | |
*/ | |
public var canGoBack: Boolean by mutableStateOf(false) | |
internal set | |
/** | |
* True when the web view is able to navigate forwards, false otherwise. | |
*/ | |
public var canGoForward: Boolean by mutableStateOf(false) | |
internal set | |
public fun loadUrl(url: String, additionalHttpHeaders: Map<String, String> = emptyMap()) { | |
coroutineScope.launch { | |
navigationEvents.emit( | |
NavigationEvent.LoadUrl( | |
url, | |
additionalHttpHeaders | |
) | |
) | |
} | |
} | |
public fun loadHtml( | |
html: String, | |
baseUrl: String? = null, | |
mimeType: String? = null, | |
encoding: String? = "utf-8", | |
historyUrl: String? = null | |
) { | |
coroutineScope.launch { | |
navigationEvents.emit( | |
NavigationEvent.LoadHtml( | |
html, | |
baseUrl, | |
mimeType, | |
encoding, | |
historyUrl | |
) | |
) | |
} | |
} | |
public fun postUrl( | |
url: String, | |
postData: ByteArray | |
) { | |
coroutineScope.launch { | |
navigationEvents.emit( | |
NavigationEvent.PostUrl( | |
url, | |
postData | |
) | |
) | |
} | |
} | |
/** | |
* Navigates the webview back to the previous page. | |
*/ | |
public fun navigateBack() { | |
coroutineScope.launch { navigationEvents.emit(NavigationEvent.Back) } | |
} | |
/** | |
* Navigates the webview forward after going back from a page. | |
*/ | |
public fun navigateForward() { | |
coroutineScope.launch { navigationEvents.emit(NavigationEvent.Forward) } | |
} | |
/** | |
* Reloads the current page in the webview. | |
*/ | |
public fun reload() { | |
coroutineScope.launch { navigationEvents.emit(NavigationEvent.Reload) } | |
} | |
/** | |
* Stops the current page load (if one is loading). | |
*/ | |
public fun stopLoading() { | |
coroutineScope.launch { navigationEvents.emit(NavigationEvent.StopLoading) } | |
} | |
} | |
/** | |
* Creates and remembers a [WebViewNavigator] using the default [CoroutineScope] or a provided | |
* override. | |
*/ | |
@Composable | |
public fun rememberWebViewNavigator( | |
coroutineScope: CoroutineScope = rememberCoroutineScope() | |
): WebViewNavigator = remember(coroutineScope) { WebViewNavigator(coroutineScope) } | |
/** | |
* A wrapper class to hold errors from the WebView. | |
*/ | |
@Immutable | |
public data class WebViewError( | |
/** | |
* The request the error came from. | |
*/ | |
val request: WebResourceRequest?, | |
/** | |
* The error that was reported. | |
*/ | |
val error: WebResourceError | |
) | |
/** | |
* Creates a WebView state that is remembered across Compositions. | |
* | |
* @param url The url to load in the WebView | |
* @param additionalHttpHeaders Optional, additional HTTP headers that are passed to [WebView.loadUrl]. | |
* Note that these headers are used for all subsequent requests of the WebView. | |
*/ | |
@Composable | |
public fun rememberWebViewState( | |
url: String, | |
additionalHttpHeaders: Map<String, String> = emptyMap() | |
): WebViewState = | |
// Rather than using .apply {} here we will recreate the state, this prevents | |
// a recomposition loop when the webview updates the url itself. | |
remember { | |
WebViewState( | |
WebContent.Url( | |
url = url, | |
additionalHttpHeaders = additionalHttpHeaders | |
) | |
) | |
}.apply { | |
this.content = WebContent.Url( | |
url = url, | |
additionalHttpHeaders = additionalHttpHeaders | |
) | |
} | |
/** | |
* Creates a WebView state that is remembered across Compositions. | |
* | |
* @param data The uri to load in the WebView | |
*/ | |
@Composable | |
public fun rememberWebViewStateWithHTMLData( | |
data: String, | |
baseUrl: String? = null, | |
encoding: String = "utf-8", | |
mimeType: String? = null, | |
historyUrl: String? = null | |
): WebViewState = | |
remember { | |
WebViewState(WebContent.Data(data, baseUrl, encoding, mimeType, historyUrl)) | |
}.apply { | |
this.content = WebContent.Data( | |
data, baseUrl, encoding, mimeType, historyUrl | |
) | |
} | |
/** | |
* Creates a WebView state that is remembered across Compositions. | |
* | |
* @param url The url to load in the WebView | |
* @param postData The data to be posted to the WebView with the url | |
*/ | |
@Composable | |
public fun rememberWebViewState( | |
url: String, | |
postData: ByteArray | |
): WebViewState = | |
// Rather than using .apply {} here we will recreate the state, this prevents | |
// a recomposition loop when the webview updates the url itself. | |
remember { | |
WebViewState( | |
WebContent.Post( | |
url = url, | |
postData = postData | |
) | |
) | |
}.apply { | |
this.content = WebContent.Post( | |
url = url, | |
postData = postData | |
) | |
} | |
/** | |
* Creates a WebView state that is remembered across Compositions and saved | |
* across activity recreation. | |
* When using saved state, you cannot change the URL via recomposition. The only way to load | |
* a URL is via a WebViewNavigator. | |
* | |
* @param data The uri to load in the WebView | |
* @sample com.google.accompanist.sample.webview.WebViewSaveStateSample | |
*/ | |
@Composable | |
public fun rememberSaveableWebViewState(): WebViewState = | |
rememberSaveable(saver = WebStateSaver) { | |
WebViewState(WebContent.NavigatorOnly) | |
} | |
public val WebStateSaver: Saver<WebViewState, Any> = run { | |
val pageTitleKey = "pagetitle" | |
val lastLoadedUrlKey = "lastloaded" | |
val stateBundle = "bundle" | |
mapSaver( | |
save = { | |
val viewState = Bundle().apply { it.webView?.saveState(this) } | |
mapOf( | |
pageTitleKey to it.pageTitle, | |
lastLoadedUrlKey to it.lastLoadedUrl, | |
stateBundle to viewState | |
) | |
}, | |
restore = { | |
WebViewState(WebContent.NavigatorOnly).apply { | |
this.pageTitle = it[pageTitleKey] as String? | |
this.lastLoadedUrl = it[lastLoadedUrlKey] as String? | |
this.viewState = it[stateBundle] as Bundle? | |
} | |
} | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xyz.imzyx.nextdemo.ui.comp | |
import android.webkit.WebChromeClient | |
import android.webkit.WebView | |
import android.webkit.WebViewClient | |
import android.widget.FrameLayout | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.MutableState | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.viewinterop.AndroidView | |
/** | |
* | |
* created on 2024/1/28 | |
* @author holmes | |
*/ | |
@Composable | |
fun WebView(modifier: Modifier, url: String) { | |
// https://issuetracker.google.com/issues/242463987?pli=1 | |
val webviewRef: MutableState<WebView?> = remember { | |
mutableStateOf(null) | |
} | |
LaunchedEffect(key1 = url, block = { | |
if (url.isNotEmpty()) { | |
webviewRef.value?.loadUrl(url) | |
} | |
}) | |
Box(modifier = modifier) { | |
AndroidView( | |
modifier = modifier.fillMaxWidth(), | |
factory = { context -> | |
WebView(context).apply { | |
this.layoutParams = FrameLayout.LayoutParams( | |
FrameLayout.LayoutParams.MATCH_PARENT, | |
FrameLayout.LayoutParams.MATCH_PARENT | |
) | |
setBackgroundColor(0) | |
this.clipToOutline = true | |
this.webViewClient = WebViewClient() | |
this.webChromeClient = WebChromeClient() | |
}.also { | |
webviewRef.value = it | |
it.onResume() | |
} | |
}, | |
onRelease = { | |
webviewRef.value?.destroy() | |
webviewRef.value = null | |
} | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment