Skip to content

Commit 61c675a

Browse files
committed
Merge branch 'error-snackbars' into main
2 parents 0b4091f + 49dbb87 commit 61c675a

File tree

5 files changed

+60
-24
lines changed

5 files changed

+60
-24
lines changed

app/src/main/java/dev/shorthouse/coinwatch/ui/previewdata/MarketUiStatePreviewProvider.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.shorthouse.coinwatch.ui.previewdata
22

33
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
4+
import dev.shorthouse.coinwatch.R
45
import dev.shorthouse.coinwatch.data.source.local.model.CachedCoin
56
import dev.shorthouse.coinwatch.model.Percentage
67
import dev.shorthouse.coinwatch.model.Price
@@ -26,7 +27,7 @@ class MarketUiStatePreviewProvider : PreviewParameterProvider<MarketUiState> {
2627
showCurrencyBottomSheet = true
2728
),
2829
MarketUiState(
29-
errorMessage = "Error message"
30+
errorMessageIds = persistentListOf(R.string.error_network_coins)
3031
),
3132
MarketUiState(
3233
isLoading = true

app/src/main/java/dev/shorthouse/coinwatch/ui/screen/market/MarketScreen.kt

+24-11
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ import androidx.compose.material3.Icon
2323
import androidx.compose.material3.MaterialTheme
2424
import androidx.compose.material3.Scaffold
2525
import androidx.compose.material3.SmallFloatingActionButton
26+
import androidx.compose.material3.SnackbarHost
27+
import androidx.compose.material3.SnackbarHostState
2628
import androidx.compose.material3.Text
2729
import androidx.compose.material3.TopAppBar
2830
import androidx.compose.material3.TopAppBarDefaults
2931
import androidx.compose.material3.TopAppBarScrollBehavior
3032
import androidx.compose.material3.rememberModalBottomSheetState
3133
import androidx.compose.runtime.Composable
34+
import androidx.compose.runtime.LaunchedEffect
3235
import androidx.compose.runtime.derivedStateOf
3336
import androidx.compose.runtime.getValue
3437
import androidx.compose.runtime.remember
@@ -46,7 +49,6 @@ import dev.shorthouse.coinwatch.R
4649
import dev.shorthouse.coinwatch.data.datastore.CoinSort
4750
import dev.shorthouse.coinwatch.data.datastore.Currency
4851
import dev.shorthouse.coinwatch.data.source.local.model.CachedCoin
49-
import dev.shorthouse.coinwatch.ui.component.ErrorState
5052
import dev.shorthouse.coinwatch.ui.component.pullrefresh.PullRefreshIndicator
5153
import dev.shorthouse.coinwatch.ui.component.pullrefresh.pullRefresh
5254
import dev.shorthouse.coinwatch.ui.component.pullrefresh.rememberPullRefreshState
@@ -89,6 +91,9 @@ fun MarketScreen(
8991
},
9092
onRefresh = {
9193
viewModel.pullRefreshCachedCoins()
94+
},
95+
onErrorDismiss = { errorMessageId ->
96+
viewModel.onErrorDismiss(errorMessageId)
9297
}
9398
)
9499
}
@@ -103,13 +108,15 @@ fun MarketScreen(
103108
onUpdateCurrency: (Currency) -> Unit,
104109
onUpdateShowCurrencyBottomSheet: (Boolean) -> Unit,
105110
onRefresh: () -> Unit,
111+
onErrorDismiss: (Int) -> Unit,
106112
modifier: Modifier = Modifier
107113
) {
108114
val scope = rememberCoroutineScope()
109115
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
110116
val lazyListState = rememberLazyListState()
111117
val coinSortSheetState = rememberModalBottomSheetState()
112118
val currencySheetState = rememberModalBottomSheetState()
119+
val snackbarHostState = remember { SnackbarHostState() }
113120
val pullRefreshState = rememberPullRefreshState(
114121
refreshing = uiState.isRefreshing,
115122
onRefresh = onRefresh
@@ -127,6 +134,7 @@ fun MarketScreen(
127134
scrollBehavior = scrollBehavior
128135
)
129136
},
137+
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
130138
content = { scaffoldPadding ->
131139
Box(
132140
contentAlignment = Alignment.TopCenter,
@@ -137,14 +145,6 @@ fun MarketScreen(
137145
MarketSkeletonLoader(modifier = Modifier.padding(scaffoldPadding))
138146
}
139147

140-
uiState.errorMessage != null -> {
141-
ErrorState(
142-
message = uiState.errorMessage,
143-
onRetry = onRefresh,
144-
modifier = Modifier.padding(scaffoldPadding)
145-
)
146-
}
147-
148148
else -> {
149149
MarketContent(
150150
coins = uiState.coins,
@@ -203,6 +203,18 @@ fun MarketScreen(
203203
backgroundColor = MaterialTheme.colorScheme.primaryContainer,
204204
modifier = Modifier.padding(scaffoldPadding)
205205
)
206+
207+
if (uiState.errorMessageIds.isNotEmpty()) {
208+
val errorMessage = stringResource(uiState.errorMessageIds.first())
209+
210+
LaunchedEffect(errorMessage, snackbarHostState) {
211+
snackbarHostState.showSnackbar(
212+
message = errorMessage
213+
)
214+
215+
onErrorDismiss(uiState.errorMessageIds.first())
216+
}
217+
}
206218
}
207219
},
208220
floatingActionButton = {
@@ -352,9 +364,10 @@ private fun MarketScreenPreview(
352364
onCoinClick = {},
353365
onUpdateCoinSort = {},
354366
onUpdateShowCoinSortBottomSheet = {},
355-
onRefresh = {},
356367
onUpdateCurrency = {},
357-
onUpdateShowCurrencyBottomSheet = {}
368+
onUpdateShowCurrencyBottomSheet = {},
369+
onRefresh = {},
370+
onErrorDismiss = {}
358371
)
359372
}
360373
}

app/src/main/java/dev/shorthouse/coinwatch/ui/screen/market/MarketUiState.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ data class MarketUiState(
1313
val showCoinSortBottomSheet: Boolean = false,
1414
val currency: Currency = Currency.USD,
1515
val showCurrencyBottomSheet: Boolean = false,
16-
val isRefreshing: Boolean = false,
1716
val timeOfDay: TimeOfDay = TimeOfDay.Morning,
1817
val isLoading: Boolean = false,
19-
val errorMessage: String? = null
18+
val isRefreshing: Boolean = false,
19+
val errorMessageIds: List<Int> = persistentListOf()
2020
)

app/src/main/java/dev/shorthouse/coinwatch/ui/screen/market/MarketViewModel.kt

+30-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package dev.shorthouse.coinwatch.ui.screen.market
22

3+
import androidx.annotation.StringRes
34
import androidx.lifecycle.ViewModel
45
import androidx.lifecycle.viewModelScope
56
import dagger.hilt.android.lifecycle.HiltViewModel
7+
import dev.shorthouse.coinwatch.R
68
import dev.shorthouse.coinwatch.common.Result
79
import dev.shorthouse.coinwatch.data.datastore.CoinSort
810
import dev.shorthouse.coinwatch.data.datastore.Currency
@@ -51,7 +53,7 @@ class MarketViewModel @Inject constructor(
5153
)
5254
}
5355

54-
refreshCachedCoinsUseCase(
56+
refreshCachedCoins(
5557
coinSort = userPreferences.coinSort,
5658
currency = userPreferences.currency
5759
)
@@ -65,17 +67,17 @@ class MarketViewModel @Inject constructor(
6567
_uiState.update {
6668
it.copy(
6769
coins = coins,
68-
isLoading = false,
69-
errorMessage = null
70+
isLoading = false
7071
)
7172
}
7273
}
7374

7475
is Result.Error -> {
7576
_uiState.update {
77+
val errorMessages = it.errorMessageIds + R.string.error_local_coins
7678
it.copy(
77-
isLoading = false,
78-
errorMessage = coinsResult.message
79+
errorMessageIds = errorMessages,
80+
isLoading = false
7981
)
8082
}
8183
}
@@ -99,21 +101,19 @@ class MarketViewModel @Inject constructor(
99101
viewModelScope.launch {
100102
_uiState.update {
101103
it.copy(
102-
isRefreshing = true,
103-
isLoading = true
104+
isRefreshing = true
104105
)
105106
}
106107

107108
val userPreferences = getUserPreferencesUseCase().first()
108-
refreshCachedCoinsUseCase(
109+
refreshCachedCoins(
109110
coinSort = userPreferences.coinSort,
110111
currency = userPreferences.currency
111112
)
112113

113114
_uiState.update {
114115
it.copy(
115-
isRefreshing = false,
116-
isLoading = false
116+
isRefreshing = false
117117
)
118118
}
119119
}
@@ -143,6 +143,15 @@ class MarketViewModel @Inject constructor(
143143
_uiState.update { it.copy(showCurrencyBottomSheet = showSheet) }
144144
}
145145

146+
fun onErrorDismiss(@StringRes dismissedErrorMessageId: Int) {
147+
_uiState.update {
148+
val errorMessageIds = it.errorMessageIds.filter { errorMessageId ->
149+
errorMessageId != dismissedErrorMessageId
150+
}
151+
it.copy(errorMessageIds = errorMessageIds)
152+
}
153+
}
154+
146155
private fun isAnyBottomSheetOpen(): Boolean {
147156
return _uiState.value.showCoinSortBottomSheet || _uiState.value.showCurrencyBottomSheet
148157
}
@@ -154,4 +163,15 @@ class MarketViewModel @Inject constructor(
154163
delay(5.minutes.inWholeMilliseconds)
155164
}
156165
}
166+
167+
private suspend fun refreshCachedCoins(coinSort: CoinSort, currency: Currency) {
168+
val result = refreshCachedCoinsUseCase(coinSort = coinSort, currency = currency)
169+
170+
if (result is Result.Error) {
171+
_uiState.update {
172+
val errorMessages = it.errorMessageIds + R.string.error_network_coins
173+
it.copy(errorMessageIds = errorMessages)
174+
}
175+
}
176+
}
157177
}

app/src/main/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,6 @@
6161
<string name="top_bar_action_sort_coins">Sort coins</string>
6262
<string name="top_bar_action_change_currency">Change currency</string>
6363
<string name="time_of_day_prefix_good">Good</string>
64+
<string name="error_network_coins">Latest coin data unavailable</string>
65+
<string name="error_local_coins">Saved coin data unavailable</string>
6466
</resources>

0 commit comments

Comments
 (0)