diff --git a/CLAUDE.md b/CLAUDE.md index 9e85019..dd58e3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -149,6 +149,7 @@ | `docs/api.md` | All REST + WS endpoints with request/response schemas | | `docs/config.md` | All `.env` variables with types and defaults | | `docs/context_providers.md` | Context providers — dynamic system message injection | +| `docs/android-client.md` | Android app — architecture, build/deploy, WebView config, platform detection | | `docs/architecture.md` | Component diagram, data flow, registry wiring | `NAVI.md` (project root) is a lightweight hub with the server command, key paths table, and a `filesystem(action="query")` pattern for querying docs at runtime. diff --git a/docs/android-client.md b/docs/android-client.md new file mode 100644 index 0000000..241d2a4 --- /dev/null +++ b/docs/android-client.md @@ -0,0 +1,81 @@ +# Android Client + +Native Android app that wraps the Navi web client in a WebView. + +## Location + +`android-client/` — standalone Android project (Kotlin, Gradle). + +## Architecture + +Single-activity app with two screens: + +| Activity | Purpose | +|---|---| +| `SetupActivity` | First-launch screen — prompts for server URL, saves to SharedPreferences | +| `MainActivity` | Main screen — full-screen WebView loading the Navi web client | + +Server URL is stored in SharedPreferences under key `server_url`. If absent on launch, `SetupActivity` is shown. If the WebView fails to load the main frame, the saved URL is cleared so the next launch prompts again. + +## Build & Deploy + +```bash +cd android-client + +# Debug APK +./gradlew assembleDebug + +# Install via adb (device connected via USB) +adb install -r app/build/outputs/apk/debug/app-debug.apk + +# One-liner: build + install +./gradlew assembleDebug && adb install -r app/build/outputs/apk/debug/app-debug.apk +``` + +## WebView configuration + +| Setting | Value | Reason | +|---|---|---| +| `javaScriptEnabled` | true | Required for the Vue app | +| `domStorageEnabled` | true | Session state, composables | +| `cacheMode` | `LOAD_NO_CACHE` | Always fetch fresh from server | +| `mixedContentMode` | `ALWAYS_ALLOW` | Local server may serve mixed content | +| `userAgentString` | `…original… NaviAndroid/1.0` | Platform detection in web client | + +## External link handling + +`shouldOverrideUrlLoading` intercepts all navigation: +- URL host matches the Navi server → handled by WebView (returns `false`) +- Any other host (external link) → opened via `Intent.ACTION_VIEW` in the system browser (returns `true`) + +This means links Navi produces in chat (URLs to external sites, generated HTML pages served by nginx, etc.) open in the user's browser, not inside the app. + +## Platform detection in web client + +The app appends `NaviAndroid/1.0` to the WebView's User-Agent. The web client can detect this: + +```js +import { isAndroid } from '@/composables/usePlatform.js' + +if (isAndroid) { + // Android-specific behaviour +} +``` + +`usePlatform.js` is a single-line composable: +```js +export const isAndroid = navigator.userAgent.includes('NaviAndroid') +``` + +Use this for any future UI differences between the web client and the Android app (hiding elements, adjusting layout, etc.). + +## Back navigation + +Hardware back button navigates WebView history if available (`webView.canGoBack()`), otherwise falls through to the default Android behaviour (closes the activity). + +## File / image picker + +`WebChromeClient.onShowFileChooser` is implemented to support image attachment in chat: +- Requests `CAMERA` and `READ_MEDIA_IMAGES` / `READ_EXTERNAL_STORAGE` permissions at runtime +- Presents a chooser combining gallery picker and camera capture +- Camera photos are saved to `getExternalFilesDir(DIRECTORY_PICTURES)` via `FileProvider`