# Cross-App Screen Porting Between Android Architectures

## When to Use

When porting Compose screens from one Android app to another with different architectures (different DI graphs, component libraries, data layers, theme systems). NOT for porting within the same app or between apps that share a common architecture.

## Core Pattern: The Partial Bridge

Copy the screen composable, create adapter ViewModels, stub deep dependencies. Do NOT copy backend infrastructure.

```
Screen composable  →  copy + mechanical rename
ViewModel          →  rewrite with adapter that matches screen's API surface
Deep deps          →  stub (empty class, no-op methods)
R.string           →  replace with inline English strings
Theme colors       →  map source color names to target color names
Custom components  →  create shim (wraps target's equivalent)
```

## Step 1: Dependency Audit (BEFORE copying)

Before copying any screen file, grep its imports to understand dependency depth:

```bash
grep '^import com.sourceapp' ScreenName.kt | grep -v 'domain.model\|ui.components' | head -20
```

Count the depth levels. If a screen has >5 levels of backend dependencies (sync services, Retrofit APIs, profile managers, config servers), the port cost exceeds the rewrite cost. Consider rewriting from scratch using the target app's infrastructure.

**Red flags** (indicate deep coupling):
- `CollectionSyncService`, `StartupSyncService`, `ProfileSyncService`
- `*Resolver` classes (tmdb, trakt, etc.)
- `*ConfigServer` classes (NanoHTTPD-backed local servers)
- `ProfileManager` with multi-profile DI
- `SavedStateHandle` constructor parameters (navigation arg coupling)

## Step 2: Mechanical Replacements

Use sed for bulk package renames:

```bash
sed -i \
  -e 's/^package com\.source\.app\./package com.target.app./' \
  -e 's/import com\.source\.app\./import com.target.app./g' \
  -e 's/com\.source\.app\./com.target.app./g' \
  screen.kt
```

Use Python for R.string mapping (many entries, complex patterns):

```python
# Replace stringResource(R.string.XXX) with inline strings
string_map = {"debrid_title": "Debrid", ...}
for key, value in string_map.items():
    text = text.replace(f'stringResource(R.string.{key})', f'"{value}"')
```

## Step 3: Component Shims

Create minimal wrappers that match the source app's component API:

```kotlin
// Shim for NuvioDialog → wraps Material3 Dialog
@Composable
fun SettingsDialogWrapper(
    onDismiss: () -> Unit,
    title: String,
    subtitle: String? = null,
    width: Dp = 560.dp,
    titleTextAlign: TextAlign = TextAlign.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    Dialog(onDismissRequest = onDismiss, ...) {
        Card(...) {
            Column(...) {
                Text(title, ...)
                subtitle?.let { Text(it, ...) }
                content()
            }
        }
    }
}
```

## Step 4: Adapter ViewModel

The ViewModel must match the screen's expected API surface exactly:

- Same method names, same parameters
- Same UiState data class with same field names and computed properties
- Same StateFlow/SharedFlow exposure pattern
- Same event sealed class names

Internally, the adapter ViewModel calls the target app's data layer.

## Step 5: Build-After-Each

Build after every screen, not after all screens. One screen at a time:

```bash
./gradlew :app:compileDebugKotlin 2>&1 | grep "^e: " | head -40
```

Fix errors iteratively. If a single screen produces >30 errors, delete it and consider rewriting from scratch.

## Known Traps

### Trap: Line numbers in file content

When using `read_file()` from `hermes_tools`, the output includes line number prefixes like `     1|`. If this content is written back to a file via `write_file()`, the file gets corrupted. Always use `terminal(cp ...)` or Python `open()` to read source files, never the `read_file()` tool for source file content that will be written back.

### Trap: Name clashes with existing target types

Check for existing types with the same name before copying. Example: NuvioTV's `DebridProvider` (data class) clashed with UNSPOOLED's `DebridProvider` (enum). Fix: rename the copied type (`DebridServiceProvider`).

### Trap: Global sed destroys Kotlin syntax

A single `sed` that matches across all files can break:
- `@file:OptIn` annotations at line 1
- Multi-line string resources
- `when` expressions with string resource branches
- Lambdas with trailing commas

Prefer targeted `patch()` calls for complex replacements.

### Trap: Hilt DI graph explosion

Copied files with `@HiltViewModel @Inject constructor(...)` inject into the target app's DI graph. If the constructor parameters reference types that don't exist in the target's DI modules, KSP fails with `error.NonExistentClass`. Fix: strip `@HiltViewModel` and `@Inject`, create parameterless stub ViewModels.

## Decision Matrix

| Screen complexity | Approach |
|---|---|
| Simple (≤3 deps, pure Compose) | Copy + mechanical rename + build |
| Medium (3-7 deps, needs ViewModel) | Partial bridge: copy screen, adapter VM, stub deps |
| Complex (7-15 deps, needs sync/resolver) | Rewrite from scratch using target's infra |
| Deep (>15 deps, needs servers/APIs) | Delete and document what was learned |
