From e8d448f27412a7101b77f0ca93a6d5aa74f1586f Mon Sep 17 00:00:00 2001 From: Muhammad Saqib Khan Date: Mon, 29 Jul 2024 23:11:46 +0500 Subject: [PATCH] Merge pull request #1 * issue solved --- app/build.gradle.kts | 2 + app/src/main/AndroidManifest.xml | 12 +- .../webview/sample/BasicWebViewSample.kt | 1 - .../sample/PullToRefreshWebViewSample.kt | 340 +++++++++++++++--- .../main/res/xml/network_security_config.xml | 4 + .../main/java/com/kevinnzou/web/WebView.kt | 23 +- 6 files changed, 315 insertions(+), 67 deletions(-) create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3c1c1c54..6aa3cc23 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,6 +62,7 @@ dependencies { implementation("androidx.compose.material3:material3") implementation("androidx.navigation:navigation-compose:2.7.6") implementation("com.github.fengdai.compose:pulltorefresh:0.2.0") + implementation ("androidx.compose.material:material:1.6.8") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") @@ -69,4 +70,5 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9c61fa55..d1057b00 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,10 +10,11 @@ android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" - android:usesCleartextTraffic="true" android:supportsRtl="true" android:theme="@style/Theme.Composewebview" + android:usesCleartextTraffic="true" tools:targetApi="31"> - - - diff --git a/app/src/main/java/com/kevinnzou/webview/sample/BasicWebViewSample.kt b/app/src/main/java/com/kevinnzou/webview/sample/BasicWebViewSample.kt index 6d07bc22..f67e710b 100644 --- a/app/src/main/java/com/kevinnzou/webview/sample/BasicWebViewSample.kt +++ b/app/src/main/java/com/kevinnzou/webview/sample/BasicWebViewSample.kt @@ -144,7 +144,6 @@ class BasicWebViewSample : ComponentActivity() { } } } - WebView( state = state, modifier = Modifier diff --git a/app/src/main/java/com/kevinnzou/webview/sample/PullToRefreshWebViewSample.kt b/app/src/main/java/com/kevinnzou/webview/sample/PullToRefreshWebViewSample.kt index 48971124..fed47785 100644 --- a/app/src/main/java/com/kevinnzou/webview/sample/PullToRefreshWebViewSample.kt +++ b/app/src/main/java/com/kevinnzou/webview/sample/PullToRefreshWebViewSample.kt @@ -1,90 +1,330 @@ package com.kevinnzou.webview.sample import android.annotation.SuppressLint -import android.graphics.Bitmap import android.os.Bundle import android.util.Log +import android.webkit.WebChromeClient import android.webkit.WebView +import android.webkit.WebViewClient import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +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.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Surface +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.github.fengdai.compose.pulltorefresh.PullToRefresh -import com.github.fengdai.compose.pulltorefresh.rememberPullToRefreshState -import com.kevinnzou.web.AccompanistWebViewClient -import com.kevinnzou.web.WebView -import com.kevinnzou.web.rememberWebViewNavigator -import com.kevinnzou.web.rememberWebViewState +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import com.kevinnzou.webview.ui.theme.ComposewebviewTheme -import kotlinx.coroutines.delay class PullToRefreshWebViewSample : ComponentActivity() { - val initialUrl = "https://github.com/KevinnZou/compose-webview" +// val initialUrl = "https://stackoverflow.com/questions/69199334/trying-to-add-a-refresh-method-to-a-webview-and-having-issues" + val initialUrl = "https://www.thelinehotel.com/wp-admin" + - @OptIn(ExperimentalMaterial3Api::class) @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ComposewebviewTheme { - val state = rememberWebViewState(url = initialUrl) - val navigator = rememberWebViewNavigator() - var textFieldValue by remember(state.lastLoadedUrl) { - mutableStateOf(state.lastLoadedUrl) - } - var refreshing by remember { mutableStateOf(false) } - LaunchedEffect(refreshing) { - if (refreshing) { - navigator.reload() - delay(1200) - refreshing = false + /* val state = rememberWebViewState(url = initialUrl) + val navigator = rememberWebViewNavigator() + var textFieldValue by remember(state.lastLoadedUrl) { + mutableStateOf(state.lastLoadedUrl) + } + val refreshScope = rememberCoroutineScope() + var refreshing by remember { mutableStateOf(false) } + + val webClient = remember { + object : AccompanistWebViewClient() { + override fun onPageStarted( + view: WebView, + url: String?, + favicon: Bitmap? + ) { + super.onPageStarted(view, url, favicon) + Log.d("Accompanist WebView", "Page started loading for $url") + } + } + } + + fun refresh() = refreshScope.launch { + refreshing = true + delay(1500) + refreshing = false + } + + val pullRefreshState = rememberPullRefreshState(refreshing, ::refresh) + + LaunchedEffect(key1 = refreshing) { + Log.d("saqiii", "onCreate: $refreshing") + if (refreshing) { + navigator.reload() + delay(1000) + refreshing = false + } + } + + Box( + Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState), + contentAlignment = Alignment.Center + ) { + WebView( + state = state, + modifier = Modifier + .background(Color.White) + .fillMaxSize(), + navigator = navigator, + onCreated = { webView -> + webView.settings.javaScriptEnabled = true + }, + client = webClient + ) + PullRefreshIndicator( + refreshing, pullRefreshState, + Modifier + .wrapContentSize() + .align(Alignment.TopCenter) + ) + }*/ + WebViewPage(initialUrl) + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun WebViewPage2(url: String) { + val context = LocalContext.current + + var isRefreshing by remember { mutableStateOf(false) } + + //WEB-VIEW + var contentHeight by remember { mutableIntStateOf(0) } + val webView = remember(context) { + WebView(context).apply { + settings.javaScriptEnabled = true + settings.useWideViewPort = true + setBackgroundColor(0x000000) + webChromeClient = object : WebChromeClient() + { + override fun onProgressChanged(view: WebView?, newProgress: Int) { + LinearProgressIndicatorProgress = newProgress / 100f + } + } + webViewClient = object : WebViewClient() { + override fun onPageFinished( + view: WebView?, + url: String? + ) { + isRefreshing = false + view?.evaluateJavascript( + "(function() { return document.body.scrollHeight; })();" + ) { height -> + contentHeight = height?.toInt() ?: 0 + Log.d("saqiii", "onPageFinished:$contentHeight") } } + } + loadUrl(url) + } + } - PullToRefresh( - state = rememberPullToRefreshState(isRefreshing = refreshing), - onRefresh = { refreshing = true } + val pullRefreshState = rememberPullRefreshState( + refreshing = isRefreshing, + onRefresh = { + isRefreshing = true + webView.reload() + } + ) + + //PROGRESS INDICATOR FOR WEB-VIEW + Surface( + modifier = Modifier + .fillMaxSize() + ) { + CompositionLocalProvider(LocalContext provides context) { + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) ) { - Column( - modifier = Modifier.verticalScroll(state = rememberScrollState()) - ) { - // A custom WebViewClient and WebChromeClient can be provided via subclassing - val webClient = remember { - object : AccompanistWebViewClient() { - override fun onPageStarted( - view: WebView, - url: String?, - favicon: Bitmap? - ) { - super.onPageStarted(view, url, favicon) - Log.d("Accompanist WebView", "Page started loading for $url") - } - } + if (LinearProgressIndicatorProgress < 1f) { + LinearProgressIndicator( + progress = LinearProgressIndicatorProgress, + color = Color(0xffae52de), + modifier = Modifier.fillMaxWidth() + ) + } + AndroidView( + factory = { webView }, + modifier = if (contentHeight < 700) { + Modifier + .fillMaxSize() + .weight(1f) + } else { + Modifier + .fillMaxSize() + } + ) + } + //PULL-TO-REFRESH + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + contentColor = Color(0xffae52de), + modifier = Modifier.align(Alignment.TopCenter) + ) - WebView( - state = state, - modifier = Modifier - .fillMaxSize(), - navigator = navigator, - onCreated = { webView -> - webView.settings.javaScriptEnabled = true - }, - client = webClient + if (isRefreshing) { + Text( + text = "Pull to refresh", + color = Color(0xffae52de), + modifier = Modifier + .align(Alignment.TopCenter) + .padding(top = 60.dp) + ) + } + } + } + LaunchedEffect(webView) { + webView.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + isRefreshing = false + } + } + } + } +} +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun WebViewPage(url: String) { + val context = LocalContext.current + + var isRefreshing by remember { mutableStateOf(false) } + var LinearProgressIndicatorProgress by remember { mutableFloatStateOf(0f) } + var contentHeight by remember { mutableIntStateOf(0) } + + // Remember the WebView instance + val webView = remember { + WebView(context).apply { + settings.javaScriptEnabled = true + settings.useWideViewPort = true + setBackgroundColor(0x000000) + webChromeClient = object : WebChromeClient() { + override fun onProgressChanged(view: WebView?, newProgress: Int) { + LinearProgressIndicatorProgress = newProgress / 100f + } + } + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + isRefreshing = false + view?.evaluateJavascript( + "(function() { return document.body.scrollHeight; })();" + ) { height -> + height?.toIntOrNull()?.let { + contentHeight = it + Log.d("WebViewPage", "Content height: $contentHeight") + } + } + } + } + loadUrl(url) + } + } + + val pullRefreshState = rememberPullRefreshState( + refreshing = isRefreshing, + onRefresh = { + isRefreshing = true + webView.reload() + } + ) + + // Progress indicator for WebView + Surface(modifier = Modifier.fillMaxSize()) { + CompositionLocalProvider(LocalContext provides context) { + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + if (LinearProgressIndicatorProgress < 1f) { + LinearProgressIndicator( + progress = LinearProgressIndicatorProgress, + color = Color(0xffae52de), + modifier = Modifier.fillMaxWidth() ) } + AndroidView( + factory = { webView }, + modifier = if (contentHeight < 700) { + Modifier + .fillMaxSize() + .weight(1f) + } else { + Modifier.fillMaxSize() + } + ) + } + // Pull-to-refresh indicator + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + contentColor = Color(0xffae52de), + modifier = Modifier.align(Alignment.TopCenter) + ) + + if (isRefreshing) { + Text( + text = "Pull to refresh", + color = Color(0xffae52de), + modifier = Modifier + .align(Alignment.TopCenter) + .padding(top = 60.dp) + ) } } } } -} \ No newline at end of file +} + + +private var LinearProgressIndicatorProgress by mutableFloatStateOf(0f) \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..dca93c07 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/web/src/main/java/com/kevinnzou/web/WebView.kt b/web/src/main/java/com/kevinnzou/web/WebView.kt index cb60d0f3..812345eb 100644 --- a/web/src/main/java/com/kevinnzou/web/WebView.kt +++ b/web/src/main/java/com/kevinnzou/web/WebView.kt @@ -19,7 +19,7 @@ package com.kevinnzou.web import android.content.Context -import android.view.ViewGroup.LayoutParams +import android.view.ViewGroup import android.webkit.WebView import android.widget.FrameLayout import androidx.activity.compose.BackHandler @@ -57,7 +57,7 @@ import androidx.compose.ui.viewinterop.AndroidView * @sample com.google.accompanist.sample.webview.BasicWebViewSample */ @Composable -public fun WebView( +fun WebView( state: WebViewState, modifier: Modifier = Modifier, captureBackPresses: Boolean = true, @@ -74,20 +74,19 @@ public fun WebView( // layout params here. val width = if (constraints.hasFixedWidth) - LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams.MATCH_PARENT else - LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT val height = if (constraints.hasFixedHeight) - LayoutParams.MATCH_PARENT + ViewGroup.LayoutParams.MATCH_PARENT else - LayoutParams.WRAP_CONTENT + ViewGroup.LayoutParams.WRAP_CONTENT val layoutParams = FrameLayout.LayoutParams( width, height ) - WebView( state, layoutParams, @@ -129,7 +128,7 @@ public fun WebView( * @param factory An optional WebView factory for using a custom subclass of WebView */ @Composable -public fun WebView( +fun WebView( state: WebViewState, layoutParams: FrameLayout.LayoutParams, modifier: Modifier = Modifier, @@ -146,7 +145,6 @@ public fun WebView( BackHandler(captureBackPresses && navigator.canGoBack) { webView?.goBack() } - webView?.let { wv -> LaunchedEffect(wv, navigator) { with(navigator) { @@ -155,6 +153,7 @@ public fun WebView( } LaunchedEffect(wv, state) { + snapshotFlow { state.content }.collect { content -> when (content) { is WebContent.Url -> { @@ -201,13 +200,10 @@ public fun WebView( 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 } @@ -218,3 +214,6 @@ public fun WebView( } ) } + + +