fix(android): show backdrop behind WebView
This commit is contained in:
@@ -4,10 +4,15 @@ import android.annotation.SuppressLint
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebSettings
|
import android.webkit.WebSettings
|
||||||
|
import android.webkit.WebResourceError
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.safeDrawing
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledTonalIconButton
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -36,7 +42,10 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.Modifier
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
@@ -71,6 +80,7 @@ fun RootScreen(viewModel: MainViewModel) {
|
|||||||
PackageManager.PERMISSION_GRANTED
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
CanvasBackdrop(modifier = Modifier.fillMaxSize())
|
||||||
CanvasView(viewModel = viewModel, modifier = Modifier.fillMaxSize())
|
CanvasView(viewModel = viewModel, modifier = Modifier.fillMaxSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +153,7 @@ private fun OverlayIconButton(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val isDebuggable = (context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
factory = {
|
factory = {
|
||||||
@@ -151,12 +162,98 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
|
|||||||
// Some embedded web UIs (incl. the "background website") use localStorage/sessionStorage.
|
// Some embedded web UIs (incl. the "background website") use localStorage/sessionStorage.
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
|
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)
|
setBackgroundColor(Color.TRANSPARENT)
|
||||||
setBackgroundResource(0)
|
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)
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<network-security-config>
|
<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). -->
|
<!-- Allow HTTP for tailnet/local dev endpoints (e.g. canvas/background web). -->
|
||||||
<domain-config cleartextTrafficPermitted="true">
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
<domain includeSubdomains="true">clawdis.internal</domain>
|
<domain includeSubdomains="true">clawdis.internal</domain>
|
||||||
|
|||||||
Reference in New Issue
Block a user