impl VBoxUpdateFetcher

This commit is contained in:
2026-07-25 21:09:26 +03:00
parent aa6c62d283
commit c49a5ed087
7 changed files with 47 additions and 18 deletions
@@ -1,7 +0,0 @@
package ru.shadowsparky.vbox.shared.domain
import kotlinx.coroutines.flow.StateFlow
interface ActivityInfo {
val isActiveFlow: StateFlow<Boolean>
}
@@ -0,0 +1,27 @@
package ru.shadowsparky.vbox.shared.data
import io.ktor.client.HttpClient
import org.koin.core.annotation.Factory
import ru.shadowsparky.domain.AppConfig
import ru.shadowsparky.updater.domain.UpdateFetcher
import ru.shadowsparky.updater.domain.UpdateInfo
import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository
import ru.shadowsparky.vbox.shared.domain.getServerConfiguration
@Factory
class VBoxUpdateFetcher(
private val httpClient: HttpClient,
private val serverConfigurationProvider: ServerConfigurationRepository,
private val appConfig: AppConfig
) : UpdateFetcher {
override suspend fun fetch(): UpdateInfo {
return httpClient.get<UpdateInfo>(
"$DYNAMIC_PREFIX/checkUpdates",
mapOf(
"versionCode" to appConfig.versionCode.toString()
),
serverConfigurationProvider.getServerConfiguration()
)
}
}
@@ -39,7 +39,7 @@ class SettingsComponent(
val showOffline: () -> Unit, val showOffline: () -> Unit,
val showTags: () -> Unit val showTags: () -> Unit
) : ComponentContext by context { ) : ComponentContext by context {
private val updateComponent = updateComponentFactory.create(childContext("UpdateComponent")) val updateComponent = updateComponentFactory.create(childContext("UpdateComponent"))
val hasUpdateSupport = updateComponent.hasSupport val hasUpdateSupport = updateComponent.hasSupport
@@ -27,6 +27,7 @@ import ru.shadowsparky.ui.applyBlurSource
import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.ExpressiveLazyColumn
import ru.shadowsparky.ui.components.SwitchPreference import ru.shadowsparky.ui.components.SwitchPreference
import ru.shadowsparky.ui.components.TextPreference import ru.shadowsparky.ui.components.TextPreference
import ru.shadowsparky.updater.presentation.UpdateBottomSheet
@Composable @Composable
fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) { fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
@@ -73,7 +74,7 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
modifier = Modifier.clickable { comp.showTags() } modifier = Modifier.clickable { comp.showTags() }
) )
} }
if (comp.hasUpdateSupport) { if (comp.updateComponent.hasSupport) {
item { item {
TextPreference( TextPreference(
text = stringResource(Res.string.check_for_updates), text = stringResource(Res.string.check_for_updates),
@@ -115,6 +116,7 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
} }
} }
) )
UpdateBottomSheet(comp.updateComponent)
LaunchedEffect(null) { LaunchedEffect(null) {
withFrameNanos {} withFrameNanos {}
requester.requestFocus() requester.requestFocus()
@@ -5,6 +5,7 @@ import com.russhwolf.settings.Settings
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import ru.shadowsparky.domain.DispatcherProvider import ru.shadowsparky.domain.DispatcherProvider
@@ -27,7 +28,7 @@ class SettingsKeyValueStorage(
} }
override fun get(key: String): Flow<String?> { override fun get(key: String): Flow<String?> {
return impl.get(key) return impl.get(key).flowOn(dispatcherProvider.io)
} }
override suspend fun remove(key: String) = withContext(dispatcherProvider.io) { override suspend fun remove(key: String) = withContext(dispatcherProvider.io) {
@@ -18,11 +18,11 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -38,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.extensions.compose.subscribeAsState
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.updater.domain.UpdateState import ru.shadowsparky.updater.domain.UpdateState
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -54,11 +55,15 @@ fun UpdateBottomSheet(
val isVisible = remember(state) { state !is UpdateState.Idle && state !is UpdateState.UpToDate } val isVisible = remember(state) { state !is UpdateState.Idle && state !is UpdateState.UpToDate }
if (isVisible) { if (isVisible) {
ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState, modifier = modifier) { ModalBottomSheet(
onDismissRequest = { component.reset() },
sheetState = sheetState,
modifier = modifier
) {
AnimatedContent( AnimatedContent(
targetState = state, targetState = state,
label = "UpdateStateAnimation", label = "UpdateStateAnimation",
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 32.dp) modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
) { currentState -> ) { currentState ->
when (currentState) { when (currentState) {
is UpdateState.Checking -> UpdateCheckingContent() is UpdateState.Checking -> UpdateCheckingContent()
@@ -90,11 +95,8 @@ fun UpdateBottomSheet(
} }
@Composable @Composable
private fun UpdateCheckingContent(modifier: Modifier = Modifier) { private fun UpdateCheckingContent() {
Column( Loading(Modifier.fillMaxSize())
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) { CircularProgressIndicator() }
} }
@Composable @Composable
@@ -65,6 +65,10 @@ class UpdateComponent(
fun installApk(path: String) { fun installApk(path: String) {
scope.launch { interactor.installApk(path) } scope.launch { interactor.installApk(path) }
} }
fun reset() {
_state.value = UpdateState.Idle
}
} }
@Factory @Factory