-
Notifications
You must be signed in to change notification settings - Fork 0
Add tabbed UI with Realtime and Agent tabs with WebView enhancements and debugging tools #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bc39055
1f163cd
d5f720a
7e54ab2
9bad6eb
f20a5ee
287d7e0
4cbbb38
f434e5a
bcdf1eb
266d9f2
16b354d
fff9534
6a7a623
a7fc0da
30c48aa
b637a88
3732aab
c8c3db7
393cec2
4ba9644
bc507cb
451188c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,251 @@ | ||||||||||
| package llc.lookatwhataicando.codeoba.android | ||||||||||
|
|
||||||||||
| import android.annotation.SuppressLint | ||||||||||
| import android.os.Bundle | ||||||||||
| import android.util.Log | ||||||||||
| import android.webkit.CookieManager | ||||||||||
| import android.webkit.WebChromeClient | ||||||||||
| import android.webkit.WebView | ||||||||||
| import android.webkit.WebViewClient | ||||||||||
| import androidx.activity.ComponentActivity | ||||||||||
| import androidx.activity.compose.setContent | ||||||||||
| import androidx.compose.foundation.background | ||||||||||
| import androidx.compose.foundation.layout.Column | ||||||||||
| import androidx.compose.foundation.layout.Row | ||||||||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||||||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||||||||
| import androidx.compose.foundation.layout.padding | ||||||||||
| import androidx.compose.foundation.text.KeyboardActions | ||||||||||
| import androidx.compose.foundation.text.KeyboardOptions | ||||||||||
| import androidx.compose.material.icons.Icons | ||||||||||
| import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||||||||||
| import androidx.compose.material.icons.filled.Refresh | ||||||||||
| import androidx.compose.material3.ExperimentalMaterial3Api | ||||||||||
| import androidx.compose.material3.Icon | ||||||||||
| import androidx.compose.material3.IconButton | ||||||||||
| import androidx.compose.material3.MaterialTheme | ||||||||||
| import androidx.compose.material3.OutlinedTextField | ||||||||||
| import androidx.compose.material3.Scaffold | ||||||||||
| import androidx.compose.material3.Surface | ||||||||||
| import androidx.compose.material3.Text | ||||||||||
| import androidx.compose.material3.TopAppBar | ||||||||||
| 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 androidx.compose.ui.graphics.Color | ||||||||||
| import androidx.compose.ui.platform.LocalFocusManager | ||||||||||
| import androidx.compose.ui.text.input.ImeAction | ||||||||||
| import androidx.compose.ui.unit.dp | ||||||||||
| import androidx.compose.ui.viewinterop.AndroidView | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Test activity with WebView and editable address bar. | ||||||||||
| * Used to isolate and diagnose WebView rendering issues. | ||||||||||
| */ | ||||||||||
| class TestWebViewActivity : ComponentActivity() { | ||||||||||
|
|
||||||||||
| companion object { | ||||||||||
| private const val TAG = "TestWebViewActivity" | ||||||||||
| private const val DEFAULT_URL = "https://github.com/copilot/agents" | ||||||||||
| } | ||||||||||
|
|
||||||||||
| override fun onCreate(savedInstanceState: Bundle?) { | ||||||||||
| super.onCreate(savedInstanceState) | ||||||||||
|
|
||||||||||
| setContent { | ||||||||||
| MaterialTheme { | ||||||||||
| TestWebViewScreen(defaultUrl = DEFAULT_URL) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| @OptIn(ExperimentalMaterial3Api::class) | ||||||||||
| @Composable | ||||||||||
| fun TestWebViewScreen(defaultUrl: String) { | ||||||||||
| var currentUrl by remember { mutableStateOf(defaultUrl) } | ||||||||||
| var addressBarText by remember { mutableStateOf(defaultUrl) } | ||||||||||
| var webView by remember { mutableStateOf<WebView?>(null) } | ||||||||||
| val focusManager = LocalFocusManager.current | ||||||||||
|
|
||||||||||
| Scaffold( | ||||||||||
| topBar = { | ||||||||||
| TopAppBar( | ||||||||||
| title = { Text("Test WebView") }, | ||||||||||
| navigationIcon = { | ||||||||||
| IconButton(onClick = { /* Activity will handle back */ }) { | ||||||||||
| Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back") | ||||||||||
| } | ||||||||||
| }, | ||||||||||
| actions = { | ||||||||||
| IconButton(onClick = { webView?.reload() }) { | ||||||||||
| Icon(Icons.Default.Refresh, "Reload") | ||||||||||
| } | ||||||||||
| } | ||||||||||
| ) | ||||||||||
| } | ||||||||||
| ) { paddingValues -> | ||||||||||
| Column( | ||||||||||
| modifier = Modifier | ||||||||||
| .fillMaxSize() | ||||||||||
| .padding(paddingValues) | ||||||||||
| .background(Color.White) // Ensure white background for entire column | ||||||||||
| ) { | ||||||||||
| // Address bar - wrapped in Surface to ensure visibility | ||||||||||
| Surface( | ||||||||||
| modifier = Modifier | ||||||||||
| .fillMaxWidth() | ||||||||||
| .padding(horizontal = 8.dp, vertical = 4.dp), | ||||||||||
| color = MaterialTheme.colorScheme.surface, | ||||||||||
| tonalElevation = 1.dp | ||||||||||
| ) { | ||||||||||
| OutlinedTextField( | ||||||||||
| value = addressBarText, | ||||||||||
| onValueChange = { addressBarText = it }, | ||||||||||
| modifier = Modifier.fillMaxWidth(), | ||||||||||
| label = { Text("URL") }, | ||||||||||
| singleLine = true, | ||||||||||
| keyboardOptions = KeyboardOptions(imeAction = ImeAction.Go), | ||||||||||
| keyboardActions = KeyboardActions( | ||||||||||
| onGo = { | ||||||||||
| var url = addressBarText.trim() | ||||||||||
| // Add https:// if no protocol specified | ||||||||||
| if (!url.startsWith("http://") && !url.startsWith("https://")) { | ||||||||||
| url = "https://$url" | ||||||||||
| addressBarText = url | ||||||||||
| } | ||||||||||
| currentUrl = url | ||||||||||
| focusManager.clearFocus() | ||||||||||
| } | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // WebView - ensure it's in its own layer | ||||||||||
| TestWebView( | ||||||||||
| url = currentUrl, | ||||||||||
| modifier = Modifier | ||||||||||
| .fillMaxWidth() | ||||||||||
| .weight(1f) | ||||||||||
| .background(Color.White), // Explicit white background | ||||||||||
| onWebViewCreated = { webView = it }, | ||||||||||
| onUrlChanged = { newUrl -> | ||||||||||
| addressBarText = newUrl | ||||||||||
| } | ||||||||||
| ) | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| @SuppressLint("SetJavaScriptEnabled") | ||||||||||
| @Composable | ||||||||||
| fun TestWebView( | ||||||||||
| url: String, | ||||||||||
| modifier: Modifier = Modifier, | ||||||||||
| onWebViewCreated: (WebView) -> Unit = {}, | ||||||||||
| onUrlChanged: (String) -> Unit = {} | ||||||||||
| ) { | ||||||||||
| AndroidView( | ||||||||||
| modifier = modifier, | ||||||||||
| factory = { context -> | ||||||||||
| WebView(context).apply { | ||||||||||
| Log.d("TestWebViewActivity", "Creating WebView") | ||||||||||
|
|
||||||||||
| // CRITICAL: Set layout params to ensure WebView has proper dimensions | ||||||||||
| layoutParams = android.view.ViewGroup.LayoutParams( | ||||||||||
| android.view.ViewGroup.LayoutParams.MATCH_PARENT, | ||||||||||
| android.view.ViewGroup.LayoutParams.MATCH_PARENT | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| // CRITICAL: Enable hardware acceleration explicitly on the view | ||||||||||
| setLayerType(android.view.View.LAYER_TYPE_HARDWARE, null) | ||||||||||
|
|
||||||||||
| // Basic settings | ||||||||||
| settings.javaScriptEnabled = true | ||||||||||
| settings.domStorageEnabled = true | ||||||||||
|
|
||||||||||
| // Cookie support | ||||||||||
| CookieManager.getInstance().setAcceptCookie(true) | ||||||||||
| CookieManager.getInstance().setAcceptThirdPartyCookies(this, true) | ||||||||||
|
|
||||||||||
| // Cache configuration | ||||||||||
| settings.cacheMode = android.webkit.WebSettings.LOAD_DEFAULT | ||||||||||
| settings.databaseEnabled = true | ||||||||||
|
|
||||||||||
| // Modern web features | ||||||||||
| settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW | ||||||||||
|
||||||||||
| settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW | |
| settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW | |
| // NOTE: File and content access are intentionally enabled in this isolated test activity | |
| // to allow loading local test assets (e.g., file:// and content:// URLs) during debugging. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -56,12 +56,37 @@ kotlin { | |||||
| implementation(libs.ktor.client.okhttp) | ||||||
| implementation(libs.webrtc.android) | ||||||
| implementation(libs.audioswitch) | ||||||
| implementation(libs.androidx.activity.compose) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| val desktopMain by getting { | ||||||
| dependencies { | ||||||
| implementation(libs.ktor.client.cio) | ||||||
| implementation(libs.kotlinx.coroutines.swing) | ||||||
|
|
||||||
| // Platform-specific JavaFX native libraries for WebView support | ||||||
| // Note: Using string interpolation because Gradle catalog doesn't support | ||||||
| // classifier-based dependencies. Platform classifier must be determined at runtime. | ||||||
|
||||||
| // classifier-based dependencies. Platform classifier must be determined at runtime. | |
| // classifier-based dependencies. Platform classifier is determined at build (configuration) time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enabling MIXED_CONTENT_ALWAYS_ALLOW poses a security risk by allowing HTTPS pages to load insecure HTTP resources, potentially exposing users to man-in-the-middle attacks. Since this is a test/debugging activity, the security risk is more acceptable, but it should still be documented in a comment explaining why this is necessary for testing purposes.