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