refactor(app): refactor update logic to make core logic more reusable by moving it into the core.feature.update package

This commit is contained in:
2026-05-03 13:59:14 +02:00
parent 3a748397f8
commit 5bec125f56
9 changed files with 251 additions and 72 deletions

View File

@@ -0,0 +1,9 @@
package hu.bbara.purefin.feature.update
data class AppUpdateInfo(
val versionCode: Long,
val versionName: String?,
val releaseNotes: String?,
val apkUrl: String,
val manifestUrl: String
)

View File

@@ -0,0 +1,5 @@
package hu.bbara.purefin.feature.update
interface AppUpdateInstaller {
suspend fun installUpdate(update: AppUpdateInfo): String
}

View File

@@ -0,0 +1,57 @@
package hu.bbara.purefin.feature.update
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import javax.inject.Inject
class AppUpdateRepository @Inject constructor(
private val appVersionProvider: AppVersionProvider
) {
private val client = OkHttpClient()
private val json = Json { ignoreUnknownKeys = true }
suspend fun checkForUpdate(): AppUpdateInfo? {
val manifestUrl = "http://purefin.t.bbara.hu/app/update.json"
if (manifestUrl.isBlank()) {
throw IllegalStateException("Update manifest URL not configured")
}
val manifest = withContext(Dispatchers.IO) { fetchManifest(manifestUrl) }
if (manifest.versionCode <= appVersionProvider.versionCode) {
return null
}
return AppUpdateInfo(
versionCode = manifest.versionCode,
versionName = manifest.versionName,
releaseNotes = manifest.releaseNotes,
apkUrl = manifest.apkUrl,
manifestUrl = manifestUrl
)
}
private fun fetchManifest(manifestUrl: String): AppUpdateManifest {
val request = Request.Builder()
.url(manifestUrl)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IllegalStateException("Update check failed: HTTP ${response.code}")
}
val body = response.body?.string() ?: throw IllegalStateException("Update manifest empty")
return json.decodeFromString<AppUpdateManifest>(body)
}
}
}
@Serializable
private data class AppUpdateManifest(
val versionCode: Long,
val versionName: String? = null,
val apkUrl: String,
val releaseNotes: String? = null
)

View File

@@ -0,0 +1,76 @@
package hu.bbara.purefin.feature.update
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class AppUpdateViewModel @Inject constructor(
private val appUpdateRepository: AppUpdateRepository,
private val appUpdateInstaller: AppUpdateInstaller
) : ViewModel() {
private val _isCheckingForUpdates = MutableStateFlow(false)
val isCheckingForUpdates: StateFlow<Boolean> = _isCheckingForUpdates.asStateFlow()
private val _availableUpdate = MutableStateFlow<AppUpdateInfo?>(null)
val availableUpdate: StateFlow<AppUpdateInfo?> = _availableUpdate.asStateFlow()
private val _snackbarMessages = MutableSharedFlow<String>()
val snackbarMessages: SharedFlow<String> = _snackbarMessages.asSharedFlow()
private var isInstallingUpdate = false
fun checkForUpdates() {
if (_isCheckingForUpdates.value) {
return
}
viewModelScope.launch {
_isCheckingForUpdates.value = true
try {
val update = appUpdateRepository.checkForUpdate()
if (update == null) {
_snackbarMessages.emit("Purefin is up to date")
} else {
_availableUpdate.value = update
}
} catch (e: Exception) {
_snackbarMessages.emit(e.message ?: "Update check failed")
} finally {
_isCheckingForUpdates.value = false
}
}
}
fun acceptUpdate() {
val update = _availableUpdate.value ?: return
if (isInstallingUpdate) {
return
}
viewModelScope.launch {
isInstallingUpdate = true
_availableUpdate.value = null
try {
_snackbarMessages.emit(appUpdateInstaller.installUpdate(update))
} catch (e: Exception) {
_snackbarMessages.emit(e.message ?: "Update install failed")
} finally {
isInstallingUpdate = false
}
}
}
fun declineUpdate() {
_availableUpdate.value = null
}
}

View File

@@ -0,0 +1,5 @@
package hu.bbara.purefin.feature.update
interface AppVersionProvider {
val versionCode: Long
}