# NuvioTV Screen Port Pattern (May 2026)

## Process: porting NuvioTV screens into UNSPOOLED (com.nexstream.app)

### Three-layer approach

1. **Infrastructure (self-contained)** — Copy files that have zero NuvioTV-internal deps
2. **Bridge ViewModel** — Write a ViewModel using UNSPOOLED's existing data stores that exposes the same API the NuvioTV screen expects
3. **Screen adaptation** — Mechanical sed replacements + Python string-resource mapping + shim components

### Step 1: Mechanical package rename

```bash
SRC="NuvioTV-0.6.21-beta/app/src/main/java/com/nuvio/tv"
DST="nexstream/app/src/main/java/com/nexstream/app"

cat "$SRC/ui/screens/settings/DebridSettingsScreen.kt" | sed \
  -e 's/^package com\.nuvio\.tv\./package com.nexstream.app./' \
  -e 's/^import com\.nuvio\.tv\.R$/import com.nexstream.app.R/' \
  -e 's/import com\.nuvio\.tv\./import com.nexstream.app./g' \
  > "$DST/ui/screens/settings/DebridSettingsScreen.kt"
```

### Step 2: Name clash resolution

UNSPOOLED already has types that collide with NuvioTV types. Rename at import level:

| NuvioTV Type | UNSPOOLED Collision | Renamed To |
|---|---|---|
| `DebridProvider` (data class) | `DebridProvider` (enum in Models.kt) | `DebridServiceProvider` |
| `DebridProviders` (object) | — | `DebridServiceProviders` |
| `DebridProviderAuthMethod` | — | `DebridServiceAuthMethod` |
| `DebridProviderCapability` | — | `DebridServiceCapability` |

### Step 3: String resource replacement

NuvioTV has `R.string.*` resources. UNSPOOLED has different resources. Replace all with inline English strings:

```python
string_map = {
    "debrid_title": 'Debrid',
    "debrid_cloud_library": 'Cloud Library',
    # ... 80+ entries
}
for key, value in string_map.items():
    text = text.replace(f'stringResource(R.string.{key})', f'"{value}"')
```

### Step 4: Color system mapping

`NuvioColors` → `NexColors` with property renames:

| NuvioColors | NexColors |
|---|---|
| `Primary` | `Accent` |
| `BackgroundElevated` | `SurfaceHigh` |
| `Border` | `Divider` |
| `FocusRing` | `AccentSoft` |
| `FocusBackground` | `AccentSoft` |
| `BackgroundCard` | `Surface` |
| `Secondary` | `TextSecondary` |

### Step 5: Create shim components

For NuvioTV components that don't exist in UNSPOOLED:

- `NuvioDialog` → `SettingsDialogWrapper` (simple Material3 Dialog wrapper)
- `QrCodeOverlay` → stub composable (accepts bitmap + URL, displays them)
- `EmptyScreenState` → adapt existing UNSPOOLED equivalent

### Step 6: Bridge ViewModel pattern

The ViewModel must expose the exact API the screen expects, but delegate to UNSPOOLED's data stores:

```kotlin
@HiltViewModel
class DebridSettingsViewModel @Inject constructor(
    private val dataStore: DebridSettingsDataStore  // UNSPOOLED's data store
) : ViewModel() {
    // Match NuvioTV ViewModel API exactly
    val uiState: StateFlow<DebridSettingsUiState>
    val validating: StateFlow<Boolean>
    val validationError: SharedFlow<String>
    fun onEvent(event: DebridSettingsEvent)
    fun setStreamPreferences(preferences: DebridStreamPreferences)
    fun saveProviderCredential(providerId: String, value: String)
    fun validateAndSaveProviderApiKey(providerId: String, value: String, onSuccess: () -> Unit)
    // Stub methods requiring NuvioTV backend infrastructure
    suspend fun startDeviceAuthorization(providerId: String): DebridDeviceAuthorization? = null
    suspend fun redeemDeviceAuthorization(...): DebridDeviceAuthorizationTokenResult = Unsupported
}
```

### Pitfalls

1. **Sed line-number corruption**: Using `read_file` in execute_code embeds line numbers like `"     1|code"`. Use `cat | sed` in terminal instead.
2. **Multi-line stringResource**: `stringResource(\n  R.string.foo,\n  arg\n)` — regex won't match. Fix manually with `patch()`.
3. **Private composable references**: UNSPOOLED's `SettingsDialogWrapper` is `private` in SettingsDesignSystem.kt. Change it to `internal` — don't create a shim:
   ```bash
   sed -i 's/private fun SettingsDialogWrapper(/internal fun SettingsDialogWrapper(/' \
     app/src/main/java/com/nexstream/app/ui/screens/settings/SettingsDesignSystem.kt
   ```
4. **`@file:OptIn` annotations**: Need correct full classpaths for non-standard Compose annotations.

### Verified working example

DebridSettingsScreen (1,471 lines) ported successfully:
- All 20+ stream filter dialogs (resolutions, qualities, visual tags, audio, encodes, languages, release groups)
- API key entry + device auth dialogs
- QR formatter stub
- Builds green with 131 tests passing
