diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/update/FakeUpdateController.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/update/FakeUpdateController.kt new file mode 100644 index 00000000..3976489b --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/update/FakeUpdateController.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv.update + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Deterministic capture/test controller; no network, storage or Android hand-off. */ +class FakeUpdateController( + initialState: UpdateUiState = UpdateUiState.Idle, + initialAutoCheck: Boolean = true, + initialVersion: InstalledVersion = InstalledVersion("1.1.1", 100001), +) : UpdateController { + private val mutableState = MutableStateFlow(initialState) + private val mutableAutoCheck = MutableStateFlow(initialAutoCheck) + private val mutableVersion = MutableStateFlow(initialVersion) + override val state = mutableState.asStateFlow() + override val autoCheck = mutableAutoCheck.asStateFlow() + override val installedVersion = mutableVersion.asStateFlow() + val actions = mutableListOf() + + fun show(state: UpdateUiState) { mutableState.value = state } + fun showInstalled(version: InstalledVersion) { mutableVersion.value = version } + override fun check(manual: Boolean) { actions += "check:$manual" } + override fun download() { actions += "download" } + override fun cancel() { actions += "cancel" } + override fun install() { actions += "install" } + override fun retry() { actions += "retry" } + override fun onResume() { actions += "resume" } + override fun setAutoCheck(enabled: Boolean) { mutableAutoCheck.value = enabled } +} diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/update/UpdateController.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/update/UpdateController.kt new file mode 100644 index 00000000..c7faa29e --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/update/UpdateController.kt @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv.update + +import kotlinx.coroutines.flow.StateFlow + +/** Shared process-scoped updater for Wolfskin and classic settings. */ +interface UpdateController { + val state: StateFlow + val autoCheck: StateFlow + val installedVersion: StateFlow + + fun check(manual: Boolean) + fun download() + fun cancel() + fun install() + fun retry() + fun setAutoCheck(enabled: Boolean) + + /** Refresh installed identity and unknown-source permission after Android returns. */ + fun onResume() +} + +/** PackageManager identity, refreshed after returning from the system installer. */ +data class InstalledVersion(val versionName: String, val versionCode: Long) + +/** Only verified, app-private bytes may produce Ready. Checking never initiates download. */ +sealed interface UpdateUiState { + data object Idle : UpdateUiState + data object Checking : UpdateUiState + data object UpToDate : UpdateUiState + data object NoReleasePublished : UpdateUiState + data class Available(val versionName: String, val bytes: Long, val dateText: String, val notes: String) : UpdateUiState + data class Downloading(val bytesDone: Long, val bytesTotal: Long?) : UpdateUiState + data class Ready(val versionName: String) : UpdateUiState + data object PermissionNeeded : UpdateUiState + data object HandingOff : UpdateUiState + data object InstallCancelled : UpdateUiState + data class Failed(val kind: UpdateFailure, val message: String) : UpdateUiState + data object Offline : UpdateUiState + data object NoInstaller : UpdateUiState +} + +/** Failures are distinct from a successful version comparison or an empty channel. */ +enum class UpdateFailure { + Network, HttpRefused, InvalidMetadata, UnsupportedAbi, InsufficientStorage, + ChecksumMismatch, WrongPackage, WrongSigner, LowerVersionCode, +}