Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bc39055
Initial plan
Copilot Dec 22, 2025
1f163cd
Add tabbed UI with Realtime and Agent tabs, implement WebView for bro…
Copilot Dec 22, 2025
d5f720a
Fix code review issues: remove unused imports, fix race condition, re…
Copilot Dec 22, 2025
7e54ab2
Add URL check to prevent unnecessary reloads and improve code clarity
Copilot Dec 22, 2025
9bad6eb
Add WebView improvements: cookie persistence, pull-to-refresh, and ba…
Copilot Dec 22, 2025
f20a5ee
Remove redundant user agent setting and unnecessary ExperimentalMater…
Copilot Dec 22, 2025
287d7e0
Fix pull-to-refresh gesture conflict with navigation drawer by implem…
Copilot Dec 22, 2025
4cbbb38
Fix WebView black screen and improve pull-to-refresh reliability with…
Copilot Dec 22, 2025
f434e5a
Fix WebView black screen by switching to software rendering and forci…
Copilot Dec 22, 2025
bcdf1eb
Add standalone TestWebViewActivity for debugging WebView rendering is…
Copilot Dec 22, 2025
266d9f2
Enhance TestWebViewActivity with Scaffold, TopAppBar, and editable ad…
Copilot Dec 22, 2025
16b354d
Fix build error by adding missing materialIconsExtended dependency fo…
Copilot Dec 23, 2025
fff9534
Remove hardware acceleration workarounds to test default rendering be…
Copilot Dec 23, 2025
6a7a623
Fix address bar visibility and WebView rendering with proper layout s…
Copilot Dec 23, 2025
a7fc0da
Fix WebView zero height issue by adding explicit MATCH_PARENT layout …
Copilot Dec 23, 2025
30c48aa
Fix Desktop WebView crash by removing Platform.startup() and adding e…
Copilot Dec 23, 2025
b637a88
Add Android WebView.setWebContentsDebuggingEnabled(true)
paulpv Dec 23, 2025
3732aab
Add kotlinx-coroutines-swing dependency and fix JavaFX ARM64 support …
Copilot Dec 23, 2025
c8c3db7
Merge remote-tracking branch 'origin/copilot/add-agent-tab-ui' into c…
paulpv Dec 23, 2025
393cec2
Add javafx-media dependency to fix NoClassDefFoundError for WebView m…
Copilot Dec 23, 2025
4ba9644
Enable JavaScript and set proper user agent for Desktop WebView to fi…
Copilot Dec 23, 2025
bc507cb
Address security review: Remove file access, unused dependencies, and…
Copilot Dec 23, 2025
451188c
Update documentation for tabbed UI with Agent browser implementation
Copilot Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Future support planned for:
- **Multi-platform**: Single Kotlin codebase targeting all major platforms
- **Audio routing**: Smart Bluetooth headset and device audio management
- **Event logging**: Clear visibility into voice → transcript → actions → results
- **Tabbed interface**: Monitor GitHub Copilot Agents progress alongside voice interaction
- **WebView integration**: Cross-platform browser components for GitHub Copilot Agents (fully functional on Android, limited on Desktop)

---

Expand Down Expand Up @@ -113,10 +115,10 @@ Before running, you need to configure your OpenAI API key. See [docs/DEVELOPMENT

| Component | Status |
|-----------|--------|
| Desktop App | ✅ Functional |
| Android App | ✅ Ready (95%) |
| Shared UI | 🟡 Basic (60%) |
| Realtime API | 🔴 Stub (10%) |
| Desktop App | ✅ Functional (with WebView) |
| Android App | ✅ Ready (enhanced with WebView) |
| Shared UI | 🟡 Tabbed Interface (85%) |
| Realtime API | 🔴 Stub (Desktop), ✅ Complete (Android) |
| MCP Client | 🔴 Stub (10%) |
| iOS App | 🔴 Stub (5%) |

Expand Down
1 change: 1 addition & 0 deletions app-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ android {
dependencies {
implementation(project(":core"))
implementation(compose.material3)
implementation(compose.materialIconsExtended)
implementation(compose.ui)
implementation(compose.uiTooling)
implementation(compose.preview)
Expand Down
6 changes: 6 additions & 0 deletions app-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".TestWebViewActivity"
android:exported="false"
android:theme="@style/Theme.Codeoba"
android:label="Test WebView" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,19 @@ class MainActivity : ComponentActivity() {
?: "https://api.openai.com/v1/realtime"
)

CodeobaUI(app = codeobaApp, config = config)
CodeobaUI(
app = codeobaApp,
config = config,
onTestWebViewClick = {
// Launch test WebView activity
startActivity(
android.content.Intent(
this@MainActivity,
TestWebViewActivity::class.java
)
)
}
)
}
}
}
Expand Down
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
Copy link

Copilot AI Dec 23, 2025

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.

Suggested change
// Modern web features
// Modern web features
// WARNING: MIXED_CONTENT_ALWAYS_ALLOW is insecure and allows HTTPS pages to load HTTP content.
// This is intentionally enabled here *only* for test/debug WebView rendering to reproduce
// mixed-content issues. Do not copy this setting into production code.

Copilot uses AI. Check for mistakes.
settings.mixedContentMode = android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting allowFileAccess to true enables the WebView to access the device's file system. While this may be acceptable for a test/debugging activity, it should be documented with a comment explaining why file access is needed for testing purposes, or disabled if not required.

Suggested change
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.

Copilot uses AI. Check for mistakes.
settings.allowFileAccess = true
settings.allowContentAccess = true

// Zoom
settings.setSupportZoom(true)
settings.builtInZoomControls = true
settings.displayZoomControls = false

// CRITICAL: Use wide viewport for proper rendering
settings.useWideViewPort = true
settings.loadWithOverviewMode = true

// CRITICAL: Ensure layout algorithm is correct
settings.layoutAlgorithm = android.webkit.WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING

// Set white background
setBackgroundColor(android.graphics.Color.WHITE)

// CRITICAL: Force the WebView to be visible
visibility = android.view.View.VISIBLE

// WebView client for logging and URL updates
webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
super.onPageStarted(view, url, favicon)
Log.d("TestWebViewActivity", "Page started: $url")
url?.let { onUrlChanged(it) }
}

override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
Log.d("TestWebViewActivity", "Page finished: $url")
// Force a layout pass after page loads
view?.post {
view.requestLayout()
view.invalidate()
}
}

override fun onReceivedError(
view: WebView?,
errorCode: Int,
description: String?,
failingUrl: String?
) {
super.onReceivedError(view, errorCode, description, failingUrl)
Log.e("TestWebViewActivity", "Error: $description (code: $errorCode) URL: $failingUrl")
}
}

webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
Log.d("TestWebViewActivity", "Progress: $newProgress%")
}
}

Log.d("TestWebViewActivity", "Loading URL: $url")
loadUrl(url)
onWebViewCreated(this)
}
},
update = { view ->
// Ensure WebView stays visible
view.visibility = android.view.View.VISIBLE
if (view.url != url) {
Log.d("TestWebViewActivity", "Updating URL to: $url")
view.loadUrl(url)
}
}
)
}
25 changes: 25 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The platform detection logic happens at build configuration time (not runtime as the comment on line 70 suggests). This means the build will create artifacts for only the platform where the build runs, not for all target platforms. For a proper multiplatform distribution, you would typically need separate builds for each platform or use Gradle's dependency resolution capabilities with proper classifiers. Consider clarifying the comment to say "build time" instead of "runtime" to accurately reflect when platform detection occurs.

Suggested change
// classifier-based dependencies. Platform classifier must be determined at runtime.
// classifier-based dependencies. Platform classifier is determined at build (configuration) time.

Copilot uses AI. Check for mistakes.
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
val platform = when {
osName.contains("mac") || osName.contains("darwin") -> {
// macOS: detect ARM64 (Apple Silicon) vs x86_64 (Intel)
if (osArch.contains("aarch64") || osArch.contains("arm")) "mac-aarch64" else "mac"
}
osName.contains("win") -> "win"
osName.contains("linux") -> "linux"
else -> "linux"
}

val javafxVersion = libs.versions.javafx.get()
implementation("org.openjfx:javafx-base:$javafxVersion:$platform")
implementation("org.openjfx:javafx-graphics:$javafxVersion:$platform")
implementation("org.openjfx:javafx-controls:$javafxVersion:$platform")
implementation("org.openjfx:javafx-media:$javafxVersion:$platform")
implementation("org.openjfx:javafx-web:$javafxVersion:$platform")
implementation("org.openjfx:javafx-swing:$javafxVersion:$platform")
}
}
}
Expand Down
Loading
Loading