KevinnZou / compose-webview-multiplatform

WebView for JetBrains Compose Multiplatform
https://kevinnzou.github.io/compose-webview-multiplatform/
Apache License 2.0
305 stars 39 forks source link

Outer composable steals scrolls #123

Closed ShmuelCammebys closed 1 week ago

ShmuelCammebys commented 2 months ago

When the WebView is too small to display its contents, it displays scrollbars. However, in my use case, the WebView is embedded in a horizontally scrolling tab pager, and the width has a max value. Horizontal scrolls are stolen by the pager.

KevinnZou commented 2 months ago

@ShmuelCammebys Thanks for your feedback! It appears to be expected behavior, and we need to handle it ourselves. Have you tried using Modifier.nestedscroll?

ShmuelCammebys commented 2 months ago

@KevinnZou Yes, I did. It did not appear to have any affect.

KevinnZou commented 2 months ago

@ShmuelCammebys Could you show me the code?

ShmuelCammebys commented 1 month ago

@KevinnZou Here is a minimal reproduction (the image in the HTML is from wikipedia and is very large, so it may take a bit to load):

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

@Composable
@OptIn(ExperimentalFoundationApi::class)
fun FullScreenErrorReproduction() {
    val scrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
        Orientation.Horizontal
    )
    val titleToScreen = mapOf<String, @Composable () -> Unit>(
        "Page 1" to {
            CardListRepro(scrollConnection)
        },
        "Page 2" to {
            Column {
                Text("a")
                Text("a")
                Text("a")
            }
        }
    )
    Tabs(titleToScreen, scrollConnection)
}

@Composable
private fun CardListRepro(scrollConnection: NestedScrollConnection) {
    val html = """<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="Generator" content="Microsoft Word 15 (filtered medium)">
</head>
<body lang="EN-US" link="#467886" vlink="#96607D" style="word-wrap:break-word">
<div class="WordSection1"><p class="MsoNormal">Image:</p>
    <p class="MsoNormal"><img width="2112" height="3802" id="Picture_x0020_1"
                              src="https://upload.wikimedia.org/wikipedia/commons/3/3d/LARGE_elevation.jpg"
                              alt="A map" style="width:22.0in; height:39.6041in" data="image001.png@01DA7B76.35A2CCB0">
    </p>
</div>
</body>
</html>"""
    val messages = listOf(
        html,
        html,
        html,
    )
    val listState = rememberLazyListState()
    Column(Modifier.fillMaxSize()) {
        LazyColumn(
            Modifier.fillMaxWidth().padding(horizontal = 16.dp).weight(1F),
            state = listState,
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(messages.size) { index ->
                messages
                    .elementAtOrNull(index)
                    ?.let { HtmlCardRepro(it, scrollConnection) }
            }
        }
    }
}

@Composable
private fun HtmlCardRepro(html: String, parentScrollConnection: NestedScrollConnection? = null) {
    Column(
        Modifier.widthIn(max = 300.dp).padding(bottom = 4.dp),
        horizontalAlignment = Alignment.Start
    ) {
        WebView(
            html,
            (if (parentScrollConnection != null) Modifier.nestedScroll(parentScrollConnection)
            else Modifier)
                .heightIn(max = 500.dp)
        )
    }
}
@Composable
private fun WebView(html: String, modifier: Modifier) {
    val state = rememberWebViewStateWithHTMLData(data = html)
    com.multiplatform.webview.web.WebView(state = state, modifier = modifier)
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun Tabs(
    titleToScreen: Map<String, @Composable () -> Unit>,
    scrollConnection: NestedScrollConnection
) {
    val state = rememberPagerState(0) { titleToScreen.size }
    val scope = rememberCoroutineScope()
    TabRow(state.currentPage) {
        for ((index, i) in titleToScreen.keys.withIndex()) Tab(
            state.currentPage == index,
            onClick = {
                scope.launch {
                    state.animateScrollToPage(index)
                }
            },
            text = { Text(i) }
        )
    }
    HorizontalPager(
        state,
        pageNestedScrollConnection = scrollConnection
    ) {
        titleToScreen.values.elementAt(it)()
    }
}
KevinnZou commented 1 month ago

@ShmuelCammebys Thank you for the information! However, I am currently focused on resolving an issue at my company and may not have the time to look it in depth. I did take a quick look and believe that the PagerDefaults.pageNestedScrollConnection may not be sufficient to handle the nested scroll issue. I think we will need to create a custom nestedScrollConnection that intercepts the scroll of the pager and allows the WebView to scroll first. However, we currently do not have a scroll method available for the WebView. Therefore, you may need to wait until I have the time to add this feature. Alternatively, you are welcome to submit a pull request if you cannot wait.

BTW, I recommend checking out this library link to library for guidance on implementing nested scrolling with a webview inside an outer container.

Sternbach-Software commented 5 days ago

Was this fixed?