diff --git a/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt b/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt index fd7808ab..2cc9b247 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/screens/WebViewScreen.kt @@ -123,6 +123,23 @@ private fun isSameHost(url: String, base: String): Boolean { } } +/** Kiosk WebView retained across navigation (remote ⇄ dashboard) so leaving + * the kiosk and coming back reattaches the LIVE page — no reload, no + * re-login, no reconnect. Dropped on retry/disconnect/server change. */ +private object KioskWebView { + var instance: WebView? = null + var url: String? = null + + fun drop() { + instance?.let { + (it.parent as? ViewGroup)?.removeView(it) + it.destroy() + } + instance = null + url = null + } +} + /** True when a TCP listener answers at [base]'s host:port within [timeoutMs]. */ private fun tcpAnswers(base: String, timeoutMs: Int): Boolean = try { val u = android.net.Uri.parse(base) @@ -213,6 +230,13 @@ fun WebViewScreen( var startUrl by remember(serverUrl) { mutableStateOf(null) } var raceNonce by remember { mutableIntStateOf(0) } LaunchedEffect(serverUrl, meshFallbackUrl, raceNonce) { + // A retained live session exists — reattach instantly: no race, no + // reload, no re-login (remote ⇄ dashboard round trip). + if (KioskWebView.instance != null && KioskWebView.url == serverUrl) { + isLoading = false + startUrl = serverUrl + return@LaunchedEffect + } val picked = pickStartUrl(serverUrl, meshFallbackUrl) // Starting on the mesh: don't bounce back to it on error (it IS it). if (picked != serverUrl) triedMeshFallback = true @@ -328,7 +352,10 @@ fun WebViewScreen( text = stringResource(R.string.retry), onClick = { // Re-race LAN vs mesh — the network we're on may have - // changed since the last pick. + // changed since the last pick. Drop the retained view: + // an errored session must genuinely reload. + KioskWebView.drop() + webView = null hasError = false isLoading = true triedMeshFallback = false @@ -342,7 +369,10 @@ fun WebViewScreen( GlassButton( text = stringResource(R.string.disconnect), - onClick = onDisconnect, + onClick = { + KioskWebView.drop() + onDisconnect() + }, modifier = Modifier.fillMaxWidth().height(48.dp), ) } @@ -359,7 +389,15 @@ fun WebViewScreen( AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> - WebView(context).apply { + // Reattach the retained kiosk WebView (remote ⇄ dashboard + // must not reload the node UI). Everything configured + // below is idempotent, and re-running it rebinds clients, + // bridges and listeners to THIS composition's state — + // stale closures from the previous visit are replaced. + if (KioskWebView.url != serverUrl) KioskWebView.drop() + val reused = KioskWebView.instance + (reused ?: WebView(context)).apply { + (parent as? ViewGroup)?.removeView(this) layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, @@ -665,7 +703,11 @@ fun WebViewScreen( } webView = this - loadUrl(initialUrl) + if (reused == null) { + KioskWebView.instance = this + KioskWebView.url = serverUrl + loadUrl(initialUrl) + } } }, ) diff --git a/neode-ui/public/packages/archipelago-companion.apk b/neode-ui/public/packages/archipelago-companion.apk index 6f33528e..a343162a 100644 Binary files a/neode-ui/public/packages/archipelago-companion.apk and b/neode-ui/public/packages/archipelago-companion.apk differ