fix(android): show backdrop behind WebView

This commit is contained in:
Peter Steinberger
2025-12-18 09:46:03 +01:00
parent a74c4db948
commit 4a68b4add4
2 changed files with 101 additions and 2 deletions

View File

@@ -4,10 +4,15 @@ import android.annotation.SuppressLint
import android.Manifest
import android.content.pm.PackageManager
import android.graphics.Color
import android.util.Log
import android.view.View
import android.webkit.WebView
import android.webkit.WebSettings
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebViewClient
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -19,6 +24,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.background
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
@@ -36,7 +42,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color as ComposeColor
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
@@ -71,6 +80,7 @@ fun RootScreen(viewModel: MainViewModel) {
PackageManager.PERMISSION_GRANTED
Box(modifier = Modifier.fillMaxSize()) {
CanvasBackdrop(modifier = Modifier.fillMaxSize())
CanvasView(viewModel = viewModel, modifier = Modifier.fillMaxSize())
}
@@ -143,6 +153,7 @@ private fun OverlayIconButton(
@Composable
private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier) {
val context = LocalContext.current
val isDebuggable = (context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
AndroidView(
modifier = modifier,
factory = {
@@ -151,12 +162,98 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
// Some embedded web UIs (incl. the "background website") use localStorage/sessionStorage.
settings.domStorageEnabled = true
settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
webViewClient = WebViewClient()
webViewClient =
object : WebViewClient() {
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError,
) {
if (!isDebuggable) return
if (!request.isForMainFrame) return
Log.e("ClawdisWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}")
}
override fun onReceivedHttpError(
view: WebView,
request: WebResourceRequest,
errorResponse: WebResourceResponse,
) {
if (!isDebuggable) return
if (!request.isForMainFrame) return
Log.e(
"ClawdisWebView",
"onReceivedHttpError: ${errorResponse.statusCode} ${errorResponse.reasonPhrase} ${request.url}",
)
}
}
setBackgroundColor(Color.TRANSPARENT)
setBackgroundResource(0)
setLayerType(View.LAYER_TYPE_HARDWARE, null)
// WebView transparency + HW acceleration can render as solid black on some Android/WebView builds.
// Prefer correct alpha blending since we render the idle backdrop in Compose underneath.
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
viewModel.canvas.attach(this)
}
},
)
}
@Composable
private fun CanvasBackdrop(modifier: Modifier = Modifier) {
val base = MaterialTheme.colorScheme.background
Canvas(modifier = modifier.background(base)) {
// Subtle idle backdrop; also acts as fallback when WebView content is transparent or fails to load.
drawRect(
brush =
Brush.linearGradient(
colors =
listOf(
ComposeColor(0xFF0A2034),
ComposeColor(0xFF070A10),
ComposeColor(0xFF250726),
),
start = Offset(0f, 0f),
end = Offset(size.width, size.height),
),
)
val step = 48f * density
val lineColor = ComposeColor.White.copy(alpha = 0.028f)
var x = -step
while (x < size.width + step) {
drawLine(color = lineColor, start = Offset(x, 0f), end = Offset(x, size.height), strokeWidth = 1f)
x += step
}
var y = -step
while (y < size.height + step) {
drawLine(color = lineColor, start = Offset(0f, y), end = Offset(size.width, y), strokeWidth = 1f)
y += step
}
drawRect(
brush =
Brush.radialGradient(
colors = listOf(ComposeColor(0xFF2A71FF).copy(alpha = 0.22f), ComposeColor.Transparent),
center = Offset(size.width * 0.15f, size.height * 0.20f),
radius = size.minDimension * 0.9f,
),
)
drawRect(
brush =
Brush.radialGradient(
colors = listOf(ComposeColor(0xFFFF008A).copy(alpha = 0.18f), ComposeColor.Transparent),
center = Offset(size.width * 0.85f, size.height * 0.30f),
radius = size.minDimension * 0.75f,
),
)
drawRect(
brush =
Brush.radialGradient(
colors = listOf(ComposeColor(0xFF00D1FF).copy(alpha = 0.14f), ComposeColor.Transparent),
center = Offset(size.width * 0.60f, size.height * 0.90f),
radius = size.minDimension * 0.85f,
),
)
}
}

View File

@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- This app is primarily used on a trusted tailnet; allow cleartext for IP-based endpoints too. -->
<base-config cleartextTrafficPermitted="true" />
<!-- Allow HTTP for tailnet/local dev endpoints (e.g. canvas/background web). -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">clawdis.internal</domain>