new: add loading overlay for WebView widget while pages load

This commit is contained in:
Antoine Jaury
2026-07-08 17:08:02 +02:00
parent bb10764178
commit b063dfaff6
9 changed files with 165 additions and 11 deletions
@@ -7,10 +7,14 @@ import androidx.compose.ui.Modifier
* Embeds a live web page. Platforms with a native web view (Android, iOS) render the page inline,
* honouring [darkTheme] so the page matches the app's light/dark appearance; the others fall back to
* [WebViewUnavailable], which offers to open the page in the system browser.
*
* [onLoadingStateChange] reports whether a page is currently loading, so the caller can show a
* loader. Fallback platforms report `false` immediately since there is nothing to load.
*/
@Composable
expect fun PlatformWebView(
url: String,
darkTheme: Boolean,
onLoadingStateChange: (Boolean) -> Unit = {},
modifier: Modifier = Modifier,
)
@@ -0,0 +1,35 @@
package bzh.ajaury.chombev.web.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import bzh.ajaury.chombev.theme.ChomBevTheme
/**
* Full-surface loading overlay shown on top of the embedded web view while a page is loading. The
* opaque [MaterialTheme.colorScheme.surface] background hides the blank web view until the page is
* ready, so the transition reads cleanly in both light and dark themes.
*/
@Composable
internal fun WebViewLoader(modifier: Modifier = Modifier) {
Box(
modifier = modifier.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}
@Preview
@Composable
private fun WebViewLoaderPreview() {
ChomBevTheme {
WebViewLoader(modifier = Modifier.fillMaxSize())
}
}
@@ -1,16 +1,22 @@
package bzh.ajaury.chombev.web.ui
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import bzh.ajaury.chombev.navigation.WebPage
/**
* Displays a [WebPage] inside the app: the embedded page, matching the app's [darkTheme]. The
* surrounding top and bottom bars are provided by the app-level scaffold.
* Displays a [WebPage] inside the app: the embedded page, matching the app's [darkTheme]. A
* [WebViewLoader] covers the page while it loads. The surrounding top and bottom bars are provided by
* the app-level scaffold.
*/
@Composable
fun WebViewScreen(
@@ -18,11 +24,22 @@ fun WebViewScreen(
contentPadding: PaddingValues,
darkTheme: Boolean = isSystemInDarkTheme(),
) {
PlatformWebView(
url = page.url,
darkTheme = darkTheme,
// Start in the loading state; each distinct page loads afresh.
var isLoading by remember(page) { mutableStateOf(true) }
Box(
modifier = Modifier
.padding(contentPadding)
.fillMaxSize(),
)
) {
PlatformWebView(
url = page.url,
darkTheme = darkTheme,
onLoadingStateChange = { isLoading = it },
modifier = Modifier.fillMaxSize(),
)
if (isLoading) {
WebViewLoader(modifier = Modifier.fillMaxSize())
}
}
}