@@ -28,7 +28,6 @@ import androidx.compose.foundation.Text
28
28
import androidx.compose.foundation.clickable
29
29
import androidx.compose.foundation.contentColor
30
30
import androidx.compose.foundation.layout.Column
31
- import androidx.compose.foundation.layout.Stack
32
31
import androidx.compose.foundation.layout.fillMaxSize
33
32
import androidx.compose.foundation.layout.padding
34
33
import androidx.compose.foundation.layout.preferredSize
@@ -38,12 +37,14 @@ import androidx.compose.material.CircularProgressIndicator
38
37
import androidx.compose.material.Divider
39
38
import androidx.compose.material.DrawerValue
40
39
import androidx.compose.material.EmphasisAmbient
40
+ import androidx.compose.material.ExperimentalMaterialApi
41
41
import androidx.compose.material.IconButton
42
42
import androidx.compose.material.MaterialTheme
43
43
import androidx.compose.material.ProvideEmphasis
44
44
import androidx.compose.material.Scaffold
45
45
import androidx.compose.material.ScaffoldState
46
46
import androidx.compose.material.Snackbar
47
+ import androidx.compose.material.SnackbarResult
47
48
import androidx.compose.material.Surface
48
49
import androidx.compose.material.TextButton
49
50
import androidx.compose.material.TopAppBar
@@ -57,6 +58,7 @@ import androidx.compose.runtime.rememberCoroutineScope
57
58
import androidx.compose.ui.Alignment
58
59
import androidx.compose.ui.Modifier
59
60
import androidx.compose.ui.platform.ContextAmbient
61
+ import androidx.compose.ui.res.stringResource
60
62
import androidx.compose.ui.res.vectorResource
61
63
import androidx.compose.ui.text.style.TextAlign
62
64
import androidx.compose.ui.unit.dp
@@ -122,11 +124,13 @@ fun HomeScreen(
122
124
* Stateless composable is not coupled to any specific state management.
123
125
*
124
126
* @param posts (state) the data to show on the screen
127
+ * @param favorites (state) favorite posts
128
+ * @param onToggleFavorite (event) toggles favorite for a post
125
129
* @param onRefreshPosts (event) request a refresh of posts
126
130
* @param onErrorDismiss (event) request the current error be dismissed
127
131
* @param navigateTo (event) request navigation to [Screen]
128
- * @param scaffoldState (state) state for the [Scaffold] component on this screen
129
132
*/
133
+ @OptIn(ExperimentalMaterialApi ::class )
130
134
@Composable
131
135
fun HomeScreen (
132
136
posts : UiState <List <Post >>,
@@ -137,6 +141,24 @@ fun HomeScreen(
137
141
navigateTo : (Screen ) -> Unit ,
138
142
scaffoldState : ScaffoldState
139
143
) {
144
+ if (posts.hasError) {
145
+ val errorMessage = stringResource(id = R .string.load_error)
146
+ val retryMessage = stringResource(id = R .string.retry)
147
+ // Show snackbar using a coroutine, when the coroutine is cancelled the snackbar will
148
+ // automatically dismiss. This coroutine will cancel whenever posts.hasError changes, and
149
+ // only start when posts.hasError is true (due to the above if-check).
150
+ launchInComposition(posts.hasError) {
151
+ val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
152
+ message = errorMessage,
153
+ actionLabel = retryMessage
154
+ )
155
+ when (snackbarResult) {
156
+ SnackbarResult .ActionPerformed -> onRefreshPosts()
157
+ SnackbarResult .Dismissed -> onErrorDismiss()
158
+ }
159
+ }
160
+ }
161
+
140
162
Scaffold (
141
163
scaffoldState = scaffoldState,
142
164
drawerContent = {
@@ -147,8 +169,9 @@ fun HomeScreen(
147
169
)
148
170
},
149
171
topBar = {
172
+ val title = stringResource(id = R .string.app_name)
150
173
TopAppBar (
151
- title = { Text (text = " Jetnews " ) },
174
+ title = { Text (text = title ) },
152
175
navigationIcon = {
153
176
IconButton (onClick = { scaffoldState.drawerState.open() }) {
154
177
Icon (vectorResource(R .drawable.ic_jetnews_logo))
@@ -169,7 +192,6 @@ fun HomeScreen(
169
192
onRefresh = {
170
193
onRefreshPosts()
171
194
},
172
- onErrorDismiss = onErrorDismiss,
173
195
navigateTo = navigateTo,
174
196
favorites = favorites,
175
197
onToggleFavorite = onToggleFavorite,
@@ -223,35 +245,27 @@ private fun LoadingContent(
223
245
*
224
246
* @param posts (state) list of posts and error state to display
225
247
* @param onRefresh (event) request to refresh data
226
- * @param onErrorDismiss (event) request that the current error be dismissed
227
248
* @param navigateTo (event) request navigation to [Screen]
249
+ * @param favorites (state) all favorites
250
+ * @param onToggleFavorite (event) request a single favorite be toggled
228
251
* @param modifier modifier for root element
229
252
*/
230
253
@Composable
231
254
private fun HomeScreenErrorAndContent (
232
255
posts : UiState <List <Post >>,
233
256
onRefresh : () -> Unit ,
234
- onErrorDismiss : () -> Unit ,
235
257
navigateTo : (Screen ) -> Unit ,
236
258
favorites : Set <String >,
237
259
onToggleFavorite : (String ) -> Unit ,
238
260
modifier : Modifier = Modifier
239
261
) {
240
- Stack (modifier = modifier.fillMaxSize()) {
241
- if (posts.data != null ) {
242
- PostList (posts.data, navigateTo, favorites, onToggleFavorite)
243
- } else if (! posts.hasError) {
244
- // if there are no posts, and no error, let the user refresh manually
245
- TextButton (onClick = onRefresh, Modifier .fillMaxSize()) {
246
- Text (" Tap to load content" , textAlign = TextAlign .Center )
247
- }
262
+ if (posts.data != null ) {
263
+ PostList (posts.data, navigateTo, favorites, onToggleFavorite, modifier)
264
+ } else if (! posts.hasError) {
265
+ // if there are no posts, and no error, let the user refresh manually
266
+ TextButton (onClick = onRefresh, modifier.fillMaxSize()) {
267
+ Text (" Tap to load content" , textAlign = TextAlign .Center )
248
268
}
249
- ErrorSnackbar (
250
- showError = posts.hasError,
251
- onErrorAction = onRefresh,
252
- onDismiss = onErrorDismiss,
253
- modifier = Modifier .gravity(Alignment .BottomCenter )
254
- )
255
269
}
256
270
}
257
271
@@ -296,55 +310,6 @@ private fun FullScreenLoading() {
296
310
}
297
311
}
298
312
299
- /* *
300
- * Display an error snackbar when network requests fail.
301
- *
302
- * @param showError (state) if true, this snackbar will display
303
- * @param modifier modifier for root element
304
- * @param onErrorAction (event) user has requested error action by clicking on it
305
- * @param onDismiss (event) request that this snackbar be dismissed.
306
- */
307
- @OptIn(ExperimentalAnimationApi ::class )
308
- @Composable
309
- private fun ErrorSnackbar (
310
- showError : Boolean ,
311
- modifier : Modifier = Modifier ,
312
- onErrorAction : () -> Unit = { },
313
- onDismiss : () -> Unit = { }
314
- ) {
315
- // Make Snackbar disappear after 5 seconds if the user hasn't interacted with it
316
- launchInComposition(showError) {
317
- delay(timeMillis = 5000L )
318
- if (showError) { onDismiss() }
319
- }
320
-
321
- AnimatedVisibility (
322
- visible = showError,
323
- enter = slideInVertically(initialOffsetY = { it }),
324
- exit = slideOutVertically(targetOffsetY = { it }),
325
- modifier = modifier
326
- ) {
327
- Snackbar (
328
- modifier = Modifier .padding(16 .dp),
329
- text = { Text (" Can't update latest news" ) },
330
- action = {
331
- TextButton (
332
- onClick = {
333
- onErrorAction()
334
- onDismiss()
335
- },
336
- contentColor = contentColor()
337
- ) {
338
- Text (
339
- text = " RETRY" ,
340
- color = MaterialTheme .colors.snackbarAction
341
- )
342
- }
343
- }
344
- )
345
- }
346
- }
347
-
348
313
/* *
349
314
* Top section of [PostList]
350
315
*
0 commit comments