mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
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:
@@ -5,16 +5,16 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import androidx.navigation3.runtime.NavBackStack
|
import androidx.navigation3.runtime.NavBackStack
|
||||||
@@ -24,17 +24,18 @@ import androidx.navigation3.runtime.rememberNavBackStack
|
|||||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
import hu.bbara.purefin.feature.browse.home.AppViewModel
|
import hu.bbara.purefin.feature.browse.home.AppViewModel
|
||||||
|
import hu.bbara.purefin.feature.update.AppUpdateInfo
|
||||||
|
import hu.bbara.purefin.feature.update.AppUpdateViewModel
|
||||||
import hu.bbara.purefin.navigation.LocalNavigationManager
|
import hu.bbara.purefin.navigation.LocalNavigationManager
|
||||||
import hu.bbara.purefin.update.AppUpdateInstaller
|
|
||||||
import hu.bbara.purefin.ui.screen.download.DownloadsScreen
|
import hu.bbara.purefin.ui.screen.download.DownloadsScreen
|
||||||
import hu.bbara.purefin.ui.screen.home.HomeScreen
|
import hu.bbara.purefin.ui.screen.home.HomeScreen
|
||||||
import hu.bbara.purefin.ui.screen.libraries.LibrariesScreen
|
import hu.bbara.purefin.ui.screen.libraries.LibrariesScreen
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppScreen(
|
fun AppScreen(
|
||||||
viewModel: AppViewModel = hiltViewModel(),
|
viewModel: AppViewModel = hiltViewModel(),
|
||||||
|
updateViewModel: AppUpdateViewModel = hiltViewModel(),
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val libraries by viewModel.libraries.collectAsState()
|
val libraries by viewModel.libraries.collectAsState()
|
||||||
@@ -43,12 +44,10 @@ fun AppScreen(
|
|||||||
val continueWatching by viewModel.continueWatching.collectAsState()
|
val continueWatching by viewModel.continueWatching.collectAsState()
|
||||||
val nextUp by viewModel.nextUp.collectAsState()
|
val nextUp by viewModel.nextUp.collectAsState()
|
||||||
val isRefreshing by viewModel.isRefreshing.collectAsState()
|
val isRefreshing by viewModel.isRefreshing.collectAsState()
|
||||||
|
val isCheckingForUpdates by updateViewModel.isCheckingForUpdates.collectAsState()
|
||||||
|
val availableUpdate by updateViewModel.availableUpdate.collectAsState()
|
||||||
val navigationManager = LocalNavigationManager.current
|
val navigationManager = LocalNavigationManager.current
|
||||||
val context = LocalContext.current
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val appUpdateInstaller = remember(context) { AppUpdateInstaller(context) }
|
|
||||||
var isCheckingForUpdates by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val backStack = rememberNavBackStack(AppTabRoute.Home) as NavBackStack<AppTabRoute>
|
val backStack = rememberNavBackStack(AppTabRoute.Home) as NavBackStack<AppTabRoute>
|
||||||
@@ -60,6 +59,12 @@ fun AppScreen(
|
|||||||
onPauseOrDispose { }
|
onPauseOrDispose { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(updateViewModel) {
|
||||||
|
updateViewModel.snackbarMessages.collect { message ->
|
||||||
|
snackbarHostState.showSnackbar(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val onTabSelected = remember(backStack) {
|
val onTabSelected = remember(backStack) {
|
||||||
{ selectedIndex: Int ->
|
{ selectedIndex: Int ->
|
||||||
val route = selectedIndex.toAppTabRoute()
|
val route = selectedIndex.toAppTabRoute()
|
||||||
@@ -68,21 +73,6 @@ fun AppScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val onCheckForUpdates = {
|
|
||||||
if (!isCheckingForUpdates) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
isCheckingForUpdates = true
|
|
||||||
val message = try {
|
|
||||||
appUpdateInstaller.checkForUpdateAndInstall()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.message ?: "Update check failed"
|
|
||||||
} finally {
|
|
||||||
isCheckingForUpdates = false
|
|
||||||
}
|
|
||||||
snackbarHostState.showSnackbar(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val tabEntryProvider = entryProvider {
|
val tabEntryProvider = entryProvider {
|
||||||
entry<AppTabRoute.Home>(metadata = appTabMetadata(AppTabRoute.Home)) {
|
entry<AppTabRoute.Home>(metadata = appTabMetadata(AppTabRoute.Home)) {
|
||||||
@@ -102,7 +92,7 @@ fun AppScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onProfileClick = {},
|
onProfileClick = {},
|
||||||
onCheckForUpdates = onCheckForUpdates,
|
onCheckForUpdates = updateViewModel::checkForUpdates,
|
||||||
isCheckingForUpdates = isCheckingForUpdates,
|
isCheckingForUpdates = isCheckingForUpdates,
|
||||||
onSettingsClick = {},
|
onSettingsClick = {},
|
||||||
onLogoutClick = viewModel::logout,
|
onLogoutClick = viewModel::logout,
|
||||||
@@ -150,6 +140,14 @@ fun AppScreen(
|
|||||||
entryProvider = tabEntryProvider,
|
entryProvider = tabEntryProvider,
|
||||||
modifier = modifier.fillMaxSize()
|
modifier = modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
availableUpdate?.let { update ->
|
||||||
|
UpdateAvailableDialog(
|
||||||
|
update = update,
|
||||||
|
onAccept = updateViewModel::acceptUpdate,
|
||||||
|
onDecline = updateViewModel::declineUpdate
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -183,4 +181,35 @@ private fun appTabMetadata(route: AppTabRoute): Map<String, Any> =
|
|||||||
private fun Map<String, Any>.appTabIndex(): Int =
|
private fun Map<String, Any>.appTabIndex(): Int =
|
||||||
this[APP_TAB_INDEX_METADATA] as? Int ?: 0
|
this[APP_TAB_INDEX_METADATA] as? Int ?: 0
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun UpdateAvailableDialog(
|
||||||
|
update: AppUpdateInfo,
|
||||||
|
onAccept: () -> Unit,
|
||||||
|
onDecline: () -> Unit
|
||||||
|
) {
|
||||||
|
val versionLabel = update.versionName?.takeIf { it.isNotBlank() } ?: update.versionCode.toString()
|
||||||
|
val releaseNotes = update.releaseNotes?.takeIf { it.isNotBlank() }
|
||||||
|
val updateText = if (releaseNotes == null) {
|
||||||
|
"Purefin $versionLabel is available.\n\nVersion code: ${update.versionCode}"
|
||||||
|
} else {
|
||||||
|
"Purefin $versionLabel is available.\n\nVersion code: ${update.versionCode}\n\n$releaseNotes"
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDecline,
|
||||||
|
title = { Text("Update available") },
|
||||||
|
text = { Text(updateText) },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onAccept) {
|
||||||
|
Text("Install")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDecline) {
|
||||||
|
Text("Not now")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private const val APP_TAB_INDEX_METADATA = "app_tab_index"
|
private const val APP_TAB_INDEX_METADATA = "app_tab_index"
|
||||||
|
|||||||
@@ -10,67 +10,44 @@ import android.content.pm.PackageManager
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import hu.bbara.purefin.BuildConfig
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import hu.bbara.purefin.feature.update.AppUpdateInfo
|
||||||
|
import hu.bbara.purefin.feature.update.AppUpdateInstaller
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AppUpdateInstaller(
|
class AndroidAppUpdateInstaller @Inject constructor(
|
||||||
context: Context,
|
@ApplicationContext context: Context
|
||||||
private val client: OkHttpClient = OkHttpClient()
|
) : AppUpdateInstaller {
|
||||||
) {
|
|
||||||
private val appContext = context.applicationContext
|
private val appContext = context.applicationContext
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
suspend fun checkForUpdateAndInstall(): String {
|
|
||||||
val manifestUrl = "http://purefin.t.bbara.hu/app/update.json"
|
|
||||||
if (manifestUrl.isBlank()) {
|
|
||||||
return "Update manifest URL not configured"
|
|
||||||
}
|
|
||||||
|
|
||||||
val manifest = withContext(Dispatchers.IO) { fetchManifest(manifestUrl) }
|
|
||||||
if (manifest.versionCode <= BuildConfig.VERSION_CODE.toLong()) {
|
|
||||||
return "Purefin is up to date"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
override suspend fun installUpdate(update: AppUpdateInfo): String {
|
||||||
if (!appContext.packageManager.canRequestPackageInstalls()) {
|
if (!appContext.packageManager.canRequestPackageInstalls()) {
|
||||||
openInstallPermissionSettings()
|
openInstallPermissionSettings()
|
||||||
return "Allow app installs, then check again"
|
return "Allow app installs, then check again"
|
||||||
}
|
}
|
||||||
|
|
||||||
val apkFile = withContext(Dispatchers.IO) {
|
val apkFile = withContext(Dispatchers.IO) {
|
||||||
downloadApk(manifestUrl, manifest)
|
downloadApk(update)
|
||||||
.also { validateApk(it, manifest.versionCode) }
|
.also { validateApk(it, update.versionCode) }
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
commitInstallSession(apkFile, manifest.versionCode)
|
commitInstallSession(apkFile, update.versionCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
val versionLabel = manifest.versionName?.takeIf { it.isNotBlank() } ?: manifest.versionCode.toString()
|
val versionLabel = update.versionName?.takeIf { it.isNotBlank() } ?: update.versionCode.toString()
|
||||||
return "Downloaded Purefin $versionLabel"
|
return "Downloaded Purefin $versionLabel"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchManifest(manifestUrl: String): AppUpdateManifest {
|
private fun downloadApk(update: AppUpdateInfo): File {
|
||||||
val request = Request.Builder()
|
val apkUrl = update.manifestUrl.toHttpUrl().resolve(update.apkUrl)
|
||||||
.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun downloadApk(manifestUrl: String, manifest: AppUpdateManifest): File {
|
|
||||||
val apkUrl = manifestUrl.toHttpUrl().resolve(manifest.apkUrl)
|
|
||||||
?: throw IllegalStateException("APK URL invalid")
|
?: throw IllegalStateException("APK URL invalid")
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(apkUrl)
|
.url(apkUrl)
|
||||||
@@ -78,7 +55,7 @@ class AppUpdateInstaller(
|
|||||||
val updateDir = appContext.cacheDir.resolve("updates")
|
val updateDir = appContext.cacheDir.resolve("updates")
|
||||||
updateDir.mkdirs()
|
updateDir.mkdirs()
|
||||||
updateDir.listFiles()?.forEach { it.delete() }
|
updateDir.listFiles()?.forEach { it.delete() }
|
||||||
val apkFile = File(updateDir, "purefin-${manifest.versionCode}.apk")
|
val apkFile = File(updateDir, "purefin-${update.versionCode}.apk")
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
client.newCall(request).execute().use { response ->
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
@@ -165,10 +142,3 @@ class AppUpdateInstaller(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
|
||||||
private data class AppUpdateManifest(
|
|
||||||
val versionCode: Long,
|
|
||||||
val versionName: String? = null,
|
|
||||||
val apkUrl: String
|
|
||||||
)
|
|
||||||
|
|||||||
19
app/src/main/java/hu/bbara/purefin/update/AppUpdateModule.kt
Normal file
19
app/src/main/java/hu/bbara/purefin/update/AppUpdateModule.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package hu.bbara.purefin.update
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import hu.bbara.purefin.feature.update.AppUpdateInstaller
|
||||||
|
import hu.bbara.purefin.feature.update.AppVersionProvider
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
abstract class AppUpdateModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindAppUpdateInstaller(impl: AndroidAppUpdateInstaller): AppUpdateInstaller
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindAppVersionProvider(impl: BuildConfigAppVersionProvider): AppVersionProvider
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package hu.bbara.purefin.update
|
||||||
|
|
||||||
|
import hu.bbara.purefin.BuildConfig
|
||||||
|
import hu.bbara.purefin.feature.update.AppVersionProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BuildConfigAppVersionProvider @Inject constructor() : AppVersionProvider {
|
||||||
|
override val versionCode: Long = BuildConfig.VERSION_CODE.toLong()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package hu.bbara.purefin.feature.update
|
||||||
|
|
||||||
|
interface AppUpdateInstaller {
|
||||||
|
suspend fun installUpdate(update: AppUpdateInfo): String
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package hu.bbara.purefin.feature.update
|
||||||
|
|
||||||
|
interface AppVersionProvider {
|
||||||
|
val versionCode: Long
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user