Compare commits

..
2 Commits
Author SHA1 Message Date
Agzamus bafebfd253 fix: no auto download if updated disabled 2026-09-07 00:31:34 +03:00
Agzamus f19d65abb5 feat: update request when need 2026-09-06 17:23:55 +03:00
7 changed files with 71 additions and 7 deletions
+27
View File
@@ -6,6 +6,9 @@ import agzam.vyatsu.ui.groups.GroupPickerViewModel
import agzam.vyatsu.ui.home.HomeScreen import agzam.vyatsu.ui.home.HomeScreen
import agzam.vyatsu.ui.schedule.ScheduleViewModel import agzam.vyatsu.ui.schedule.ScheduleViewModel
import agzam.vyatsu.ui.settings.UpdateViewModel import agzam.vyatsu.ui.settings.UpdateViewModel
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -24,6 +27,30 @@ fun AppRoot() {
val updateViewModel: UpdateViewModel = viewModel(factory = updateFactory(container)) val updateViewModel: UpdateViewModel = viewModel(factory = updateFactory(container))
val state by scheduleViewModel.uiState.collectAsState() val state by scheduleViewModel.uiState.collectAsState()
val showFirstRunDialog by updateViewModel.showFirstRunDialog.collectAsState()
if (showFirstRunDialog) {
AlertDialog(
onDismissRequest = { updateViewModel.onFirstRunDecision(false) },
title = { Text("Автоматические обновления") },
text = {
Text(
"Разрешить автоматически проверять и устанавливать " +
"новые версии приложения?"
)
},
confirmButton = {
TextButton(onClick = { updateViewModel.onFirstRunDecision(true) }) {
Text("Разрешить")
}
},
dismissButton = {
TextButton(onClick = { updateViewModel.onFirstRunDecision(false) }) {
Text("Не надо")
}
}
)
}
if (state.noGroup) { if (state.noGroup) {
GroupPickerScreen( GroupPickerScreen(
@@ -33,4 +33,10 @@ interface PreferencesDao {
@Query("INSERT OR REPLACE INTO preferences(key, value) VALUES('autoUpdateEnabled', :enabled)") @Query("INSERT OR REPLACE INTO preferences(key, value) VALUES('autoUpdateEnabled', :enabled)")
suspend fun setAutoUpdateEnabled(enabled: String) suspend fun setAutoUpdateEnabled(enabled: String)
@Query("SELECT value FROM preferences WHERE key = 'autoUpdateAsked'")
suspend fun autoUpdateAsked(): String?
@Query("INSERT OR REPLACE INTO preferences(key, value) VALUES('autoUpdateAsked', :asked)")
suspend fun setAutoUpdateAsked(asked: String)
} }
@@ -14,6 +14,9 @@ class PreferencesRepositoryImpl(
override suspend fun selectedDayDate(): String? = dao.selectedDayDate() override suspend fun selectedDayDate(): String? = dao.selectedDayDate()
override suspend fun setSelectedDayDate(date: String) = dao.setSelectedDayDate(date) override suspend fun setSelectedDayDate(date: String) = dao.setSelectedDayDate(date)
override suspend fun autoUpdateEnabled(): Boolean = dao.autoUpdateEnabled() != "false" override suspend fun autoUpdateEnabled(): Boolean = dao.autoUpdateEnabled() == "true"
override suspend fun setAutoUpdateEnabled(enabled: Boolean) = dao.setAutoUpdateEnabled(enabled.toString()) override suspend fun setAutoUpdateEnabled(enabled: Boolean) = dao.setAutoUpdateEnabled(enabled.toString())
override suspend fun autoUpdateAsked(): Boolean = dao.autoUpdateAsked() == "true"
override suspend fun setAutoUpdateAsked(asked: Boolean) = dao.setAutoUpdateAsked(asked.toString())
} }
@@ -11,4 +11,7 @@ interface PreferencesRepository {
suspend fun autoUpdateEnabled(): Boolean suspend fun autoUpdateEnabled(): Boolean
suspend fun setAutoUpdateEnabled(enabled: Boolean) suspend fun setAutoUpdateEnabled(enabled: Boolean)
suspend fun autoUpdateAsked(): Boolean
suspend fun setAutoUpdateAsked(asked: Boolean)
} }
@@ -50,12 +50,12 @@ fun SettingsScreen(viewModel: UpdateViewModel) {
item { item {
SettingsItem( SettingsItem(
title = "Проверить наличие обновлений", title = "Проверить наличие обновлений",
subtitle = updateStatusText(updateState), subtitle = updateStatusText(updateState, autoEnabled),
onClick = { viewModel.checkNow() } onClick = { viewModel.checkNow() }
) )
SettingsDivider() SettingsDivider()
} }
if (updateState is UpdateState.Available || updateState is UpdateState.ReadyToInstall) { if (autoEnabled && (updateState is UpdateState.Available || updateState is UpdateState.ReadyToInstall)) {
item { item {
SettingsItem( SettingsItem(
title = "Установить обновление", title = "Установить обновление",
@@ -89,11 +89,13 @@ fun SettingsScreen(viewModel: UpdateViewModel) {
} }
} }
private fun updateStatusText(state: UpdateState): String = when (state) { private fun updateStatusText(state: UpdateState, autoEnabled: Boolean): String = when (state) {
is UpdateState.Idle -> "Проверить сейчас" is UpdateState.Idle -> "Проверить сейчас"
is UpdateState.Checking -> "Проверка…" is UpdateState.Checking -> "Проверка…"
is UpdateState.UpToDate -> "Установлена последняя версия" is UpdateState.UpToDate -> "Установлена последняя версия"
is UpdateState.Available -> "Доступна версия ${state.version}" is UpdateState.Available ->
if (autoEnabled) "Доступна версия ${state.version}"
else "Доступна версия ${state.version} — включите автообновления для установки"
is UpdateState.Downloading -> "Скачивание: ${state.progress?.let { "$it%" } ?: "…"}" is UpdateState.Downloading -> "Скачивание: ${state.progress?.let { "$it%" } ?: "…"}"
is UpdateState.ReadyToInstall -> "Обновление скачано, нажмите для установки" is UpdateState.ReadyToInstall -> "Обновление скачано, нажмите для установки"
is UpdateState.Failed -> "Ошибка: ${state.message}" is UpdateState.Failed -> "Ошибка: ${state.message}"
@@ -17,18 +17,33 @@ class UpdateViewModel(
val updateState: StateFlow<UpdateState> = updater.state val updateState: StateFlow<UpdateState> = updater.state
private val _autoEnabled = MutableStateFlow(true) private val _autoEnabled = MutableStateFlow(false)
val autoEnabled: StateFlow<Boolean> = _autoEnabled.asStateFlow() val autoEnabled: StateFlow<Boolean> = _autoEnabled.asStateFlow()
private val _showFirstRunDialog = MutableStateFlow(false)
val showFirstRunDialog: StateFlow<Boolean> = _showFirstRunDialog.asStateFlow()
init { init {
viewModelScope.launch { viewModelScope.launch {
_autoEnabled.value = preferencesRepository.autoUpdateEnabled() _autoEnabled.value = preferencesRepository.autoUpdateEnabled()
_showFirstRunDialog.value = !preferencesRepository.autoUpdateAsked()
} }
} }
fun setAutoEnabled(enabled: Boolean) { fun setAutoEnabled(enabled: Boolean) {
_autoEnabled.value = enabled _autoEnabled.value = enabled
updater.setAutoEnabled(enabled) updater.setAutoEnabled(enabled)
if (enabled && !updater.canRequestInstalls()) {
updater.openUnknownSourcesSettings()
}
}
fun onFirstRunDecision(enable: Boolean) {
_showFirstRunDialog.value = false
viewModelScope.launch {
preferencesRepository.setAutoUpdateAsked(true)
}
setAutoEnabled(enable)
} }
fun checkNow() = updater.checkForUpdate() fun checkNow() = updater.checkForUpdate()
@@ -9,6 +9,7 @@ import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -75,6 +76,7 @@ class AppUpdater(
if (job?.isActive == true) return if (job?.isActive == true) return
val update = pendingUpdate ?: return val update = pendingUpdate ?: return
job = scope.launch { job = scope.launch {
if (!preferencesRepository.autoUpdateEnabled()) return@launch
runCatching { install(download(update)) }.onFailure(::onUpdateFailed) runCatching { install(download(update)) }.onFailure(::onUpdateFailed)
} }
} }
@@ -85,6 +87,12 @@ class AppUpdater(
} }
fun setAutoEnabled(enabled: Boolean) { fun setAutoEnabled(enabled: Boolean) {
if (!enabled) {
job?.cancel()
if (_state.value is UpdateState.Downloading || _state.value is UpdateState.Checking) {
_state.value = UpdateState.Idle
}
}
scope.launch { scope.launch {
preferencesRepository.setAutoUpdateEnabled(enabled) preferencesRepository.setAutoUpdateEnabled(enabled)
} }
@@ -166,7 +174,6 @@ class AppUpdater(
private fun install(apk: File) { private fun install(apk: File) {
if (!canRequestInstalls()) { if (!canRequestInstalls()) {
openUnknownSourcesSettings()
return return
} }
val uri = FileProvider.getUriForFile( val uri = FileProvider.getUriForFile(
@@ -184,6 +191,7 @@ class AppUpdater(
} }
private fun onUpdateFailed(error: Throwable) { private fun onUpdateFailed(error: Throwable) {
if (error is CancellationException) throw error
Log.e(TAG, "Ошибка обновления", error) Log.e(TAG, "Ошибка обновления", error)
_state.value = UpdateState.Failed(error.message ?: "Неизвестная ошибка") _state.value = UpdateState.Failed(error.message ?: "Неизвестная ошибка")
} }