# Clean-room APK-inspired Android TV feature implementation

Use this reference when a user asks to study competitor APKs and implement practical Android TV streaming-app improvements in their own app.

## Boundary

- Treat decompiled APKs as feature/UX references only.
- Do not copy source code, assets, extractor logic, API keys, endpoints bundled as secrets, or implementation structure from APKs.
- Write new code against the target app's existing architecture and public API docs.
- Keep provider/resolver changes conservative: do not introduce scraper fleets or hardcoded extractor farms when the app is Stremio/addon-first.

## Batch sequence that worked well

1. **Audit and document first**
   - Produce reference docs under a project docs directory before implementation.
   - Separate: what to implement, what not to copy, UI/UX notes, player/playback notes, settings/profile/addon recommendations.

2. **Small implementation batches, build after each**
   - Batch 1: low-risk polish and navigation plumbing.
     - Cloud/debrid manager entry from Settings.
     - Persistent recent search history via DataStore/PreferencesManager.
     - External player `Intent.ACTION_VIEW` launch with package-specific options and generic chooser fallback.
   - Batch 2: player parity polish.
     - Media3 aspect ratio toggle: fit/fill/zoom via `AspectRatioFrameLayout.RESIZE_MODE_*`.
     - Subtitle delay controls and subtitle encoding preference scaffold.
   - Batch 3: provider-neutral debrid cloud groundwork.
     - Add a neutral `DebridCloudItem` model.
     - Add default `DebridApi.listCloudItems(): List<DebridCloudItem> = emptyList()`.
     - Implement only for providers with documented account/cloud list APIs.
     - Surface cloud items in UI without persisting/logging tokenized unrestricted URLs.

3. **Verification after every batch**
   - Run `./gradlew testDebugUnitTest compileDebugKotlin --console=plain` after each batch.
   - Finish with `./gradlew assembleDebug testDebugUnitTest --console=plain`.

## Implementation notes

### External player launch

Use Android's public intent contract, not competitor code:

```kotlin
val intent = Intent(Intent.ACTION_VIEW).apply {
    setDataAndType(Uri.parse(url), "video/*")
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    if (packageName != null) setPackage(packageName)
}
```

- Show a toast if the stream URL is not ready.
- Catch `ActivityNotFoundException`; retry without `setPackage()` for a generic chooser, or show an install-player message.
- Keep tokenized URLs in memory only; do not write them to logs/history.

### Media3 aspect ratio toggle

Keep the state as a small string/enum in player UI state and update `PlayerView.resizeMode` both in `factory` and `update`:

```kotlin
private fun resizeModeFor(mode: String): Int = when (mode) {
    "fill" -> AspectRatioFrameLayout.RESIZE_MODE_FILL
    "zoom" -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
    else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
}
```

### Search history persistence

For Android TV, recent search persistence is a high-impact accessibility/UX improvement because D-pad typing is slow.

- Store a capped, de-duped list in DataStore.
- Respect the existing `searchHistoryEnabled` privacy toggle.
- Settings clear action should call the same PreferencesManager method used by SearchViewModel.

### Debrid cloud model

Provider APIs expose different fields and capabilities. Use a provider-neutral UI model and provider-specific mapping:

```kotlin
data class DebridCloudItem(
    val provider: DebridProvider,
    val id: String,
    val name: String,
    val status: String,
    val progress: Float? = null,
    val sizeBytes: Long? = null,
    val fileCount: Int = 0,
    val playableUrl: String? = null,
)
```

Provider mappings used:

- Real-Debrid: REST `torrents` list; fields map from torrent response (`filename`, `status`, `progress`, `bytes`, `files`, `links`).
- Premiumize: `transfer/list`; transfer status/progress/name/id, no playable URL until item detail/directdl work.
- TorBox: `api/torrents/mylist`; torrent status/progress/size/files, optional file `download_url`.
- AllDebrid: keep default empty until a documented safe cloud-list endpoint is added.

## Pitfalls

- Do not make every debrid provider implement cloud browsing at once if the documented API coverage is uneven. A default empty method preserves compile stability and lets UI show "No cloud transfers".
- Never log full stream URLs from debrid/cloud APIs; they can be tokenized or time-limited secrets.
- Do not call unresolved cloud/playable links a finished feature. Label initial UI as groundwork unless drill-in/play/delete/refresh flows exist.
- If `SettingsUiState` has a massive `combine()`, adding one DataStore preference requires updating: the flow list, indexed casts, data class field, `copy` construction, setter method, and settings screen UI. Build immediately after.
