feat: autoupdater

This commit is contained in:
2026-09-06 12:38:00 +03:00
parent ec6a7174e8
commit 889c57bbba
13 changed files with 506 additions and 1 deletions
@@ -0,0 +1,150 @@
package agzam.vyatsu.ui.settings
import agzam.vyatsu.BuildConfig
import agzam.vyatsu.update.UpdateState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SettingsScreen(viewModel: UpdateViewModel) {
val autoEnabled by viewModel.autoEnabled.collectAsState()
val updateState by viewModel.updateState.collectAsState()
LazyColumn(
Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets.statusBars)
) {
item { SectionHeader("Обновления") }
item {
SettingsItem(
title = "Автоматические обновления",
subtitle = "Проверять и устанавливать новые версии при запуске",
trailing = {
Switch(
checked = autoEnabled,
onCheckedChange = { viewModel.setAutoEnabled(it) }
)
}
)
SettingsDivider()
}
item {
SettingsItem(
title = "Проверить наличие обновлений",
subtitle = updateStatusText(updateState),
onClick = { viewModel.checkNow() }
)
SettingsDivider()
}
if (updateState is UpdateState.Available || updateState is UpdateState.ReadyToInstall) {
item {
SettingsItem(
title = "Установить обновление",
subtitle = installHint(updateState, viewModel),
onClick = {
when (updateState) {
is UpdateState.Available -> viewModel.downloadAndInstall()
is UpdateState.ReadyToInstall -> viewModel.installDownloadedApk()
else -> {}
}
}
)
SettingsDivider()
}
}
item { SectionHeader("О приложении") }
item {
SettingsItem(
title = "Версия",
trailing = {
Text(
BuildConfig.VERSION_NAME,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
)
SettingsDivider()
}
}
}
private fun updateStatusText(state: UpdateState): String = when (state) {
is UpdateState.Idle -> "Проверить сейчас"
is UpdateState.Checking -> "Проверка…"
is UpdateState.UpToDate -> "Установлена последняя версия"
is UpdateState.Available -> "Доступна версия ${state.version}"
is UpdateState.Downloading -> "Скачивание: ${state.progress?.let { "$it%" } ?: "…"}"
is UpdateState.ReadyToInstall -> "Обновление скачано, нажмите для установки"
is UpdateState.Failed -> "Ошибка: ${state.message}"
}
private fun installHint(state: UpdateState, viewModel: UpdateViewModel): String = when {
state is UpdateState.ReadyToInstall && !viewModel.canRequestInstalls() ->
"Разрешите установку приложений из этого источника"
else -> "Нажмите, чтобы продолжить"
}
@Composable
private fun SectionHeader(title: String) {
Text(
title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
@Composable
private fun SettingsItem(
title: String,
subtitle: String? = null,
trailing: (@Composable () -> Unit)? = null,
onClick: (() -> Unit)? = null
) {
Row(
Modifier
.fillMaxWidth()
.heightIn(min = 48.dp)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(Modifier.weight(1f)) {
Text(title, style = MaterialTheme.typography.bodyLarge)
if (subtitle != null) {
Text(
subtitle,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
trailing?.invoke()
}
}
@Composable
private fun SettingsDivider() {
HorizontalDivider(Modifier.padding(horizontal = 16.dp))
}