From 9fce9ba306a80b2bf5206f2be788f67b2e55da54 Mon Sep 17 00:00:00 2001 From: Ben Trengrove Date: Mon, 14 Nov 2022 13:34:43 +1100 Subject: [PATCH] Fix WebView always filling max available space --- sample/src/main/AndroidManifest.xml | 13 +++ .../webview/WrappedContentWebViewSample.kt | 96 +++++++++++++++++++ sample/src/main/res/values/strings.xml | 1 + .../com/google/accompanist/web/WebTest.kt | 38 +++++++- .../com/google/accompanist/web/WebView.kt | 83 +++++++++------- 5 files changed, 196 insertions(+), 35 deletions(-) create mode 100644 sample/src/main/java/com/google/accompanist/sample/webview/WrappedContentWebViewSample.kt diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 98877ba6a..2ced1f526 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ --> @@ -35,6 +36,9 @@ android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light.NoActionBar"> + + + + + + + + \n" + + "body {\n" + + " background-color: #f00;\n" + + "}\n" + + "\n" + + "

Hello

" + ) + WebView( + state = webViewState, + modifier = Modifier.fillMaxWidth() + .wrapContentHeight() + .heightIn(min = 1.dp) // A bottom sheet can't support content with 0 height. + ) +} diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index d5747166e..0cafcc18c 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -63,6 +63,7 @@ Placeholder: Shimmer WebView: Basic + WebView: Wrapped Content Adaptive: TwoPane Basic Adaptive: TwoPane Horizontal diff --git a/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt b/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt index c45b9f627..b518bc099 100644 --- a/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt +++ b/web/src/androidTest/kotlin/com/google/accompanist/web/WebTest.kt @@ -19,6 +19,12 @@ package com.google.accompanist.web import android.content.Context import android.graphics.Bitmap import android.webkit.WebView +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -27,11 +33,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.IdlingResource import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.assertHeightIsAtLeast import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.dp import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches import androidx.test.espresso.web.model.Atoms.getCurrentUrl import androidx.test.espresso.web.model.Atoms.getTitle @@ -681,6 +690,32 @@ class WebTest { assertThat(constructedWebView).isInstanceOf(CustomWebView::class.java) } + + @Test + fun testWebViewCanWrapHeight() { + rule.setContent { + Column(Modifier.fillMaxSize()) { + val webViewState = rememberWebViewStateWithHTMLData(data = TEST_DATA) + WebTestContent( + webViewState = webViewState, + idlingResource = idleResource, + modifier = Modifier.wrapContentHeight() + ) + Box( + Modifier + .weight(1f) + .fillMaxWidth() + .background(Color.Red) + .testTag("box") + ) + } + } + + rule.waitForIdle() + + // If the WebView is wrapping it's content successfully, the box will have some height. + rule.onNodeWithTag("box").assertHeightIsAtLeast(1.dp) + } } private const val LINK_ID = "link" @@ -699,6 +734,7 @@ private const val WebViewTag = "webview_tag" private fun WebTestContent( webViewState: WebViewState, idlingResource: WebViewIdlingResource, + modifier: Modifier = Modifier, navigator: WebViewNavigator = rememberWebViewNavigator(), onCreated: (WebView) -> Unit = { it.settings.javaScriptEnabled = true }, onDispose: (WebView) -> Unit = {}, @@ -711,7 +747,7 @@ private fun WebTestContent( MaterialTheme { WebView( state = webViewState, - modifier = Modifier.testTag(WebViewTag), + modifier = modifier.testTag(WebViewTag), navigator = navigator, onCreated = onCreated, onDispose = onDispose, diff --git a/web/src/main/java/com/google/accompanist/web/WebView.kt b/web/src/main/java/com/google/accompanist/web/WebView.kt index 0ce0881aa..562b55c0b 100644 --- a/web/src/main/java/com/google/accompanist/web/WebView.kt +++ b/web/src/main/java/com/google/accompanist/web/WebView.kt @@ -18,13 +18,14 @@ package com.google.accompanist.web import android.content.Context import android.graphics.Bitmap -import android.view.ViewGroup +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 androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable @@ -105,40 +106,51 @@ fun WebView( val runningInPreview = LocalInspectionMode.current - AndroidView( - factory = { context -> - (factory?.invoke(context) ?: WebView(context)).apply { - onCreated(this) - - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - - webChromeClient = chromeClient - webViewClient = client - }.also { webView = it } - }, - modifier = modifier - ) { view -> - // AndroidViews are not supported by preview, bail early - if (runningInPreview) return@AndroidView - - when (val content = state.content) { - is WebContent.Url -> { - val url = content.url - - if (url.isNotEmpty() && url != view.url) { - view.loadUrl(url, content.additionalHttpHeaders.toMutableMap()) - } + BoxWithConstraints(modifier) { + AndroidView( + factory = { context -> + (factory?.invoke(context) ?: WebView(context)).apply { + onCreated(this) + + // WebView changes it's layout strategy based on + // it's layoutParams. We convert from Compose Modifier to + // layout params here. + val height = + if (constraints.hasFixedHeight) + LayoutParams.MATCH_PARENT + else + LayoutParams.WRAP_CONTENT + + layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + height + ) + + webChromeClient = chromeClient + webViewClient = client + }.also { webView = it } } - is WebContent.Data -> { - view.loadDataWithBaseURL(content.baseUrl, content.data, null, "utf-8", null) + ) { view -> + // AndroidViews are not supported by preview, bail early + if (runningInPreview) return@AndroidView + + when (val content = state.content) { + is WebContent.Url -> { + val url = content.url + + if (url.isNotEmpty() && url != view.url) { + view.loadUrl(url, content.additionalHttpHeaders.toMutableMap()) + } + } + + is WebContent.Data -> { + view.loadDataWithBaseURL(content.baseUrl, content.data, null, "utf-8", null) + } } - } - navigator.canGoBack = view.canGoBack() - navigator.canGoForward = view.canGoForward() + navigator.canGoBack = view.canGoBack() + navigator.canGoForward = view.canGoForward() + } } } @@ -436,8 +448,11 @@ data class WebViewError( * Note that these headers are used for all subsequent requests of the WebView. */ @Composable -fun rememberWebViewState(url: String, additionalHttpHeaders: Map = emptyMap()): WebViewState = - // Rather than using .apply {} here we will recreate the state, this prevents +fun rememberWebViewState( + url: String, + additionalHttpHeaders: Map = 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(url, additionalHttpHeaders) { WebViewState(