# 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 navigation:
- Normal Navi app URLs on the configured server host → handled by WebView.
- API, iframe, and artifact preview loads without a user gesture → handled by WebView.
- External hosts → opened via `Intent.ACTION_VIEW` in the system browser.
- User-clicked same-host artifact/file URLs → opened via `Intent.ACTION_VIEW` in the system browser:
  - `/content-viewers/...`
  - `/sessions/{session_id}/files/{filename}`
  - URLs with `download=1`

This means inline preview cards still render inside the app, but when the user explicitly opens a preview, raw file, download link, or external URL, it leaves the app and opens in the user's browser.

## 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.).

## OAuth / login flow

Android WebView **does not share cookies** with the system browser. When the user clicks **Login**, the app opens gnexus-auth in the user's default browser (Chrome, DuckDuckGo, etc.) so SSO works. After successful authentication, the backend redirects the browser to `/auth/mobile-done?sid=<session_id>` instead of setting a cookie.

`/auth/mobile-done` is a bridge page styled with the gnexus UI kit. It:
1. Immediately attempts `window.location.href = "intent://auth/callback?sid=xxx#Intent;scheme=navi;package=com.navi.client;end"` — Chrome understands this and opens the app automatically.
2. If the browser blocks the redirect (DuckDuckGo, Firefox), a styled fallback button **"Open Navi App"** appears after 1.5s. Tapping it triggers the same Intent URL with a user gesture, which the browser cannot block.

### Deep-link intent filter

`AndroidManifest.xml` declares an intent filter for `navi://auth/callback`:

```xml
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="navi" android:host="auth" android:pathPrefix="/callback" />
</intent-filter>
```

The activity uses `launchMode="singleTask"` so `onNewIntent` is called even when the app is already running.

### Cookie injection

`MainActivity` handles the incoming intent in both `onCreate` (cold start) and `onNewIntent` (app already running):

```kotlin
override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    intent.data?.let { uri ->
        if (uri.scheme == "navi" && uri.host == "auth" && uri.path == "/callback") {
            val sid = uri.getQueryParameter("sid")
            val prefs = getSharedPreferences("navi", Context.MODE_PRIVATE)
            val serverUrl = prefs.getString("server_url", null)
            if (sid != null && serverUrl != null) {
                CookieManager.getInstance().setCookie(
                    serverUrl,
                    "navi_auth_session=$sid; Path=/"
                )
                webView.loadUrl(serverUrl)
            }
        }
    }
}
```

After the cookie is set, the WebView reloads the server URL and the user is authenticated.

---

## 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`
