diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..b2ab0d0 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/android/app/src/main/java/com/guidecity/app/ui/guide/GuideScreen.kt b/android/app/src/main/java/com/guidecity/app/ui/guide/GuideScreen.kt index b0c40e3..830fe10 100644 --- a/android/app/src/main/java/com/guidecity/app/ui/guide/GuideScreen.kt +++ b/android/app/src/main/java/com/guidecity/app/ui/guide/GuideScreen.kt @@ -1,13 +1,18 @@ package com.guidecity.app.ui.guide +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.VolumeOff import androidx.compose.material.icons.filled.VolumeUp +import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -20,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.guidecity.app.R @@ -61,6 +67,22 @@ fun GuideScreen( .padding(padding), ) { when { + state.errorMessage != null -> { + Column( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(text = state.errorMessage.orEmpty()) + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { viewModel.retry() }) { + Text(text = stringResource(R.string.guide_retry)) + } + } + } + state.isLoading -> { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() diff --git a/android/app/src/main/java/com/guidecity/app/ui/guide/GuideViewModel.kt b/android/app/src/main/java/com/guidecity/app/ui/guide/GuideViewModel.kt index 425037b..f43ecbf 100644 --- a/android/app/src/main/java/com/guidecity/app/ui/guide/GuideViewModel.kt +++ b/android/app/src/main/java/com/guidecity/app/ui/guide/GuideViewModel.kt @@ -26,6 +26,7 @@ data class GuideUiState( val activeIndex: Int = 0, val doneIndices: Set = emptySet(), val isMuted: Boolean = false, + val errorMessage: String? = null, ) /** @@ -73,11 +74,11 @@ class GuideViewModel @Inject constructor( private suspend fun loadNearby(startNarration: Boolean) { val location = selectedLocationHolder.location.value ?: locationProvider.getCurrentLocation() if (location == null) { - _uiState.value = _uiState.value.copy(isLoading = false) + _uiState.value = _uiState.value.copy(isLoading = false, errorMessage = "Location unavailable") return } - val response = runCatching { + runCatching { placesRepository.getNearby( lat = location.lat, lon = location.lon, @@ -85,17 +86,30 @@ class GuideViewModel @Inject constructor( lang = "ru", length = contentLength.toApiValue(), ) - }.getOrNull() ?: return + }.onSuccess { response -> + _uiState.value = _uiState.value.copy( + isLoading = false, + places = response.places, + activeIndex = 0, + doneIndices = emptySet(), + errorMessage = null, + ) + if (startNarration) { + narrateActive() + } + }.onFailure { error -> + _uiState.value = _uiState.value.copy( + isLoading = false, + errorMessage = error.message ?: "Network error", + ) + } + } - _uiState.value = _uiState.value.copy( - isLoading = false, - places = response.places, - activeIndex = 0, - doneIndices = emptySet(), - ) - - if (startNarration) { - narrateActive() + /** Retries after a failed load (e.g. tap a "retry" button shown on error). */ + fun retry() { + viewModelScope.launch { + _uiState.value = _uiState.value.copy(isLoading = true, errorMessage = null) + loadNearby(startNarration = true) } } diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml index e2f6c8b..d3d07e8 100644 --- a/android/app/src/main/res/values-ru/strings.xml +++ b/android/app/src/main/res/values-ru/strings.xml @@ -17,6 +17,7 @@ Введите место для поиска… Поблизости пока не найдено интересных мест. + Повторить Добавить в избранное Убрать из избранного Выключить озвучку diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f52e75d..bdae1cf 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ Search for a place instead… No places found nearby yet. + Retry Add to favorites Remove from favorites Mute narration