Skip to content

Commit e9f751f

Browse files
committed
Add android and ios target
1 parent 1f5a15e commit e9f751f

87 files changed

Lines changed: 3130 additions & 549 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
5+
- `demo/`: Compose Desktop sample app (`demo/src/jvmMain/kotlin/...`).
6+
- `wrywebview/`: native WebView core.
7+
- Rust crate: `wrywebview/Cargo.toml` with sources in `wrywebview/src/main/rust/` (wry + UniFFI).
8+
- JVM glue: `wrywebview/src/main/kotlin/` + `wrywebview/src/main/java/` (JNA/Skiko interop).
9+
- `wrywebview-compose/`: Compose wrapper exposing `io.github.kdroidfilter.webview.*` (`WebView`, `WebViewState`, `WebViewNavigator`).
10+
- Shared API/types: `wrywebview-compose/src/commonMain/kotlin/...`.
11+
- Platform actuals: `.../src/jvmMain/` (Wry), `.../src/androidMain/` (Android WebView), `.../src/iosMain/` (WKWebView + cinterop in `.../src/nativeInterop/`).
12+
- Generated/build outputs live under `*/build/` and `wrywebview/target/` (don’t edit or commit).
13+
14+
## Build, Test, and Development Commands
15+
16+
- `./gradlew build`: builds all modules (Kotlin + Rust via Gobley/UniFFI); requires a working Rust toolchain.
17+
- `./gradlew :demo:run`: runs the desktop demo app.
18+
- `./gradlew :wrywebview:build`: rebuilds the native core and refreshes generated bindings.
19+
- `./gradlew :wrywebview-compose:compileDebugKotlinAndroid`: compiles the Android implementation (requires Android SDK).
20+
- `./gradlew clean`: removes Gradle build outputs (useful when native artifacts get out of sync).
21+
22+
## Coding Style & Naming Conventions
23+
24+
- Kotlin/Compose: 4-space indentation, idiomatic Kotlin style, `camelCase` for values/functions, `PascalCase` for types and `@Composable` functions (e.g., `WebView`).
25+
- Keep public API changes small and documented (README usage snippets should stay accurate).
26+
- Rust: format with `cargo fmt` in `wrywebview/`; keep the `#[uniffi::export]` surface stable and cross-platform.
27+
28+
## Testing Guidelines
29+
30+
- Kotlin tests (when added) should live in `*/src/jvmTest/kotlin` (or `commonTest`) and run with `./gradlew test`.
31+
- Rust tests (when added) can run via `cd wrywebview && cargo test`.
32+
33+
## Commit & Pull Request Guidelines
34+
35+
- Commit messages follow a simple imperative style (e.g., “Add …”, “Fix …”, “Refactor …”) and mention the affected module/API when helpful.
36+
- PRs should include: a short rationale, steps to verify (`./gradlew :demo:run`), OS tested (Linux/macOS/Windows), and screenshots/GIFs for UI changes.
37+
38+
## Security & Configuration Tips
39+
40+
- The demo/app JVM needs `--enable-native-access=ALL-UNNAMED` (JNA); keep this in sync with `README.md`.
41+
- Platform builds may require system deps (notably GTK/WebKit on Linux); call out any new requirements in the PR description.

README.md

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
# ComposeWebview
22

3-
Wry-powered **WebView for Compose Desktop (Kotlin/JVM)**, backed by a small Rust core (wry) exposed via **Gobley/UniFFI**.
3+
Compose Multiplatform **WebView** with a **KevinnZou/compose-webview-multiplatform-inspired API** under `io.github.kdroidfilter.webview.*`.
44

5-
This repository exposes a **KevinnZou/compose-webview-multiplatform-inspired API** under `io.github.kdroidfilter.webview.*` so you can keep the same mental model (`WebView`, `WebViewState`, `WebViewNavigator`, cookies, JS bridge) while using a desktop backend.
5+
- Desktop: **Wry** backend (Rust core via **Gobley/UniFFI**)
6+
- Android: `android.webkit.WebView`
7+
- iOS: `WKWebView`
68

79
## Demo
810

9-
Run the showcase app (recommended first):
11+
This repo ships a feature showcase app (recommended first):
1012

11-
```bash
12-
./gradlew :demo:run
13-
```
13+
- Desktop (Wry): `./gradlew :demo:run`
14+
- Android: `./gradlew :demo-android:installDebug` (or `:demo-android:assembleDebug`)
15+
- iOS: open `iosApp/iosApp.xcodeproj` in Xcode and Run (it calls `./gradlew :demo-shared:embedAndSignAppleFrameworkForXcode`)
1416

15-
The demo includes a “Tools” panel to exercise most APIs (navigation, headers + interceptor, cookies, JS, bridge, settings).
17+
The demo UI is responsive: on large screens it shows a side “Tools” panel, and on phones it uses a bottom sheet.
1618

1719
## Features
1820

@@ -30,27 +32,32 @@ The demo includes a “Tools” panel to exercise most APIs (navigation, headers
3032
- **Cookies**
3133
- `state.cookieManager.setCookie/getCookies/removeCookies/removeAllCookies`
3234
- **JavaScript**
33-
- `navigator.evaluateJavaScript(script)` *(fire-and-forget on desktop right now)*
35+
- `navigator.evaluateJavaScript(script)` *(fire-and-forget; no return value)*
3436
- JS ↔ Kotlin **bridge**: `window.kmpJsBridge.callNative(...)` with callbacks
3537
- **Settings**
36-
- `customUserAgentString` (**implemented**; requires WebView recreation)
38+
- `customUserAgentString` (**implemented**; desktop recreates, Android/iOS update in-place)
3739
- `logSeverity` (internal logging)
3840

3941
## Limitations (current)
4042

41-
- **Desktop/JVM only** (no Android/iOS in this repo).
43+
- Desktop + Android demos build from Gradle; iOS requires **macOS + Xcode toolchain** (Kotlin/Native + cinterop).
4244
- `RequestInterceptor` only applies to **navigations triggered via `WebViewNavigator`** (not to sub-resources like images/XHR loaded by the page).
43-
- Changing `customUserAgentString` **recreates** the WebView (debounced ~400ms): expect JS context/history loss.
45+
- On desktop, changing `customUserAgentString` **recreates** the WebView (debounced ~400ms): expect JS context/history loss.
4446

4547
## Project layout
4648

4749
- `wrywebview/`: Rust core (wry) + UniFFI exports + JVM glue (`WryWebViewPanel`).
4850
- `wrywebview-compose/`: Compose API layer (`io.github.kdroidfilter.webview.*`).
49-
- `demo/`: feature showcase app.
51+
- `demo-shared/`: shared demo UI (`App()`), used by all demo targets.
52+
- `demo/`: Compose Desktop demo wrapper.
53+
- `demo-android/`: Android demo app wrapper.
54+
- `iosApp/`: iOS SwiftUI demo app wrapper (imports the `demoShared` framework).
5055

5156
## Requirements
5257

5358
- Rust toolchain (`rustup` installed) for building the native core.
59+
- Android SDK for building the Android target.
60+
- macOS + Xcode for building iOS targets.
5461
- Platform deps for the underlying webview (notably on Linux: GTK/WebKitGTK packages depending on your distro).
5562
- JVM flag (required for JNA native access):
5663
- `--enable-native-access=ALL-UNNAMED`
@@ -184,9 +191,9 @@ navigator.loadHtml("<html><body><h1>Hello</h1></body></html>")
184191

185192
#### HTML file from resources (assets)
186193

187-
On desktop, `WebViewFileReadType.ASSET_RESOURCES` loads from classpath resources under `assets/`.
194+
Recommended (cross-platform): put your HTML under Compose Multiplatform resources and load it with `WebViewFileReadType.ASSET_RESOURCES`.
188195

189-
- Put your file here: `src/jvmMain/resources/assets/my_page.html`
196+
- Put your file here: `src/commonMain/composeResources/files/my_page.html`
190197
- Load it:
191198

192199
```kotlin
@@ -195,7 +202,7 @@ import io.github.kdroidfilter.webview.web.WebViewFileReadType
195202
navigator.loadHtmlFile("my_page.html", WebViewFileReadType.ASSET_RESOURCES)
196203
```
197204

198-
The demo includes `demo/src/jvmMain/resources/assets/bridge_playground.html` (JS bridge playground).
205+
The demo includes `demo-shared/src/commonMain/composeResources/files/bridge_playground.html` (JS bridge playground).
199206

200207
### RequestInterceptor (pre-navigation)
201208

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
plugins {
22
// this is necessary to avoid the plugins to be loaded multiple times
33
// in each subproject's classloader
4+
alias(libs.plugins.androidApplication) apply false
5+
alias(libs.plugins.androidLibrary) apply false
46
alias(libs.plugins.composeHotReload) apply false
57
alias(libs.plugins.composeMultiplatform) apply false
68
alias(libs.plugins.composeCompiler) apply false

demo-android/build.gradle.kts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
plugins {
2+
alias(libs.plugins.kotlinMultiplatform)
3+
alias(libs.plugins.composeMultiplatform)
4+
alias(libs.plugins.composeCompiler)
5+
alias(libs.plugins.androidApplication)
6+
}
7+
8+
kotlin {
9+
androidTarget()
10+
11+
sourceSets {
12+
androidMain.dependencies {
13+
implementation(libs.androidx.activity.compose)
14+
implementation(libs.google.material)
15+
implementation(compose.material3)
16+
implementation(project(":demo-shared"))
17+
}
18+
}
19+
}
20+
21+
android {
22+
namespace = "io.github.kdroidfilter.webview.demo.android"
23+
compileSdk = 35
24+
25+
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
26+
27+
defaultConfig {
28+
applicationId = "io.github.kdroidfilter.webview.demo"
29+
minSdk = 23
30+
targetSdk = 35
31+
versionCode = 1
32+
versionName = "1.0"
33+
}
34+
35+
compileOptions {
36+
sourceCompatibility = JavaVersion.VERSION_17
37+
targetCompatibility = JavaVersion.VERSION_17
38+
}
39+
40+
kotlin {
41+
jvmToolchain(17)
42+
}
43+
44+
// Lint is unstable with this KMP + AGP setup in CI.
45+
lint {
46+
abortOnError = false
47+
checkReleaseBuilds = false
48+
}
49+
}
50+
51+
tasks.matching { it.name.startsWith("lint") }.configureEach {
52+
enabled = false
53+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<uses-permission android:name="android.permission.INTERNET" />
3+
4+
<application
5+
android:label="ComposeWebView"
6+
android:theme="@style/Theme.ComposeWebViewDemo">
7+
<activity
8+
android:name="io.github.kdroidfilter.webview.demo.MainActivity"
9+
android:exported="true">
10+
<intent-filter>
11+
<action android:name="android.intent.action.MAIN" />
12+
<category android:name="android.intent.category.LAUNCHER" />
13+
</intent-filter>
14+
</activity>
15+
</application>
16+
</manifest>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.github.kdroidfilter.webview.demo
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
7+
class MainActivity : ComponentActivity() {
8+
override fun onCreate(savedInstanceState: Bundle?) {
9+
super.onCreate(savedInstanceState)
10+
setContent {
11+
App()
12+
}
13+
}
14+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<resources>
2+
<style name="Theme.ComposeWebViewDemo" parent="Theme.Material3.DayNight.NoActionBar" />
3+
</resources>

demo-shared/build.gradle.kts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
plugins {
2+
alias(libs.plugins.androidLibrary)
3+
alias(libs.plugins.kotlinMultiplatform)
4+
alias(libs.plugins.composeMultiplatform)
5+
alias(libs.plugins.composeCompiler)
6+
}
7+
8+
kotlin {
9+
applyDefaultHierarchyTemplate()
10+
11+
androidTarget()
12+
jvm()
13+
14+
val isMacHost = System.getProperty("os.name")?.contains("Mac", ignoreCase = true) == true
15+
if (isMacHost) {
16+
listOf(
17+
iosX64(),
18+
iosArm64(),
19+
iosSimulatorArm64(),
20+
).forEach { iosTarget ->
21+
iosTarget.binaries.framework {
22+
baseName = "demoShared"
23+
isStatic = true
24+
}
25+
}
26+
}
27+
28+
sourceSets {
29+
commonMain.dependencies {
30+
implementation(compose.runtime)
31+
implementation(compose.foundation)
32+
implementation(compose.material3)
33+
implementation(compose.materialIconsExtended)
34+
implementation(compose.ui)
35+
implementation(compose.components.resources)
36+
implementation(compose.components.uiToolingPreview)
37+
implementation(libs.androidx.lifecycle.viewmodelCompose)
38+
implementation(libs.androidx.lifecycle.runtimeCompose)
39+
implementation(libs.kotlinx.coroutinesCore)
40+
implementation(libs.kotlinx.serializationJson)
41+
42+
implementation(project(":wrywebview-compose"))
43+
}
44+
45+
jvmMain.dependencies {
46+
implementation(compose.desktop.common)
47+
}
48+
49+
if (isMacHost) {
50+
iosMain.dependencies { }
51+
}
52+
}
53+
}
54+
55+
android {
56+
namespace = "io.github.kdroidfilter.webview.demo.shared"
57+
compileSdk = 35
58+
59+
defaultConfig {
60+
minSdk = 23
61+
}
62+
63+
compileOptions {
64+
sourceCompatibility = JavaVersion.VERSION_17
65+
targetCompatibility = JavaVersion.VERSION_17
66+
}
67+
68+
kotlin {
69+
jvmToolchain(17)
70+
}
71+
72+
// Lint is unstable with this KMP + AGP setup in CI.
73+
lint {
74+
abortOnError = false
75+
checkReleaseBuilds = false
76+
}
77+
}
78+
79+
tasks.matching { it.name.startsWith("lint") }.configureEach {
80+
enabled = false
81+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.github.kdroidfilter.webview.demo
2+
3+
import java.time.LocalTime
4+
5+
internal actual fun nowTimestamp(): String {
6+
val time = LocalTime.now()
7+
return buildString {
8+
append(time.hour.twoDigits())
9+
append(':')
10+
append(time.minute.twoDigits())
11+
append(':')
12+
append(time.second.twoDigits())
13+
append('.')
14+
append((time.nano / 1_000_000).threeDigits())
15+
}
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.kdroidfilter.webview.demo
2+
3+
import android.os.Build
4+
5+
internal actual fun platformInfoJson(): String {
6+
val device = Build.MODEL.orEmpty()
7+
val brand = Build.BRAND.orEmpty()
8+
val sdk = Build.VERSION.SDK_INT.toString()
9+
val release = Build.VERSION.RELEASE.orEmpty()
10+
return """{"platform":"android","brand":"$brand","device":"$device","sdk":$sdk,"osVersion":"$release"}"""
11+
}

0 commit comments

Comments
 (0)