@@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid.feature.bookmarks
1919import androidx.annotation.VisibleForTesting
2020import androidx.compose.foundation.Image
2121import androidx.compose.foundation.layout.Arrangement
22+ import androidx.compose.foundation.layout.Box
2223import androidx.compose.foundation.layout.Column
2324import androidx.compose.foundation.layout.PaddingValues
2425import androidx.compose.foundation.layout.Spacer
@@ -34,13 +35,23 @@ import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
3435import androidx.compose.foundation.lazy.grid.GridItemSpan
3536import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
3637import androidx.compose.foundation.lazy.grid.rememberLazyGridState
38+ import androidx.compose.material3.ExperimentalMaterial3Api
3739import androidx.compose.material3.MaterialTheme
40+ import androidx.compose.material3.Scaffold
41+ import androidx.compose.material3.SnackbarDuration.Short
42+ import androidx.compose.material3.SnackbarHost
43+ import androidx.compose.material3.SnackbarHostState
44+ import androidx.compose.material3.SnackbarResult.ActionPerformed
3845import androidx.compose.material3.Text
3946import androidx.compose.runtime.Composable
47+ import androidx.compose.runtime.DisposableEffect
48+ import androidx.compose.runtime.LaunchedEffect
4049import androidx.compose.runtime.getValue
50+ import androidx.compose.runtime.remember
4151import androidx.compose.ui.Alignment
4252import androidx.compose.ui.Modifier
4353import androidx.compose.ui.graphics.ColorFilter
54+ import androidx.compose.ui.platform.LocalLifecycleOwner
4455import androidx.compose.ui.platform.testTag
4556import androidx.compose.ui.res.painterResource
4657import androidx.compose.ui.res.stringResource
@@ -50,6 +61,8 @@ import androidx.compose.ui.tooling.preview.Preview
5061import androidx.compose.ui.tooling.preview.PreviewParameter
5162import androidx.compose.ui.unit.dp
5263import androidx.hilt.navigation.compose.hiltViewModel
64+ import androidx.lifecycle.Lifecycle
65+ import androidx.lifecycle.LifecycleEventObserver
5366import androidx.lifecycle.compose.collectAsStateWithLifecycle
5467import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
5568import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalTintTheme
@@ -76,12 +89,16 @@ internal fun BookmarksRoute(
7689 onNewsResourceViewed = { viewModel.setNewsResourceViewed(it, true ) },
7790 onTopicClick = onTopicClick,
7891 modifier = modifier,
92+ shouldDisplayUndoBookmark = viewModel.shouldDisplayUndoBookmark,
93+ undoBookmarkRemoval = viewModel::undoBookmarkRemoval,
94+ clearUndoState = viewModel::clearUndoState,
7995 )
8096}
8197
8298/* *
8399 * Displays the user's bookmarked articles. Includes support for loading and empty states.
84100 */
101+ @OptIn(ExperimentalMaterial3Api ::class )
85102@VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
86103@Composable
87104internal fun BookmarksScreen (
@@ -90,13 +107,51 @@ internal fun BookmarksScreen(
90107 onNewsResourceViewed : (String ) -> Unit ,
91108 onTopicClick : (String ) -> Unit ,
92109 modifier : Modifier = Modifier ,
110+ shouldDisplayUndoBookmark : Boolean = false,
111+ undoBookmarkRemoval : () -> Unit = {},
112+ clearUndoState : () -> Unit = {},
93113) {
94- when (feedState) {
95- Loading -> LoadingState (modifier)
96- is Success -> if (feedState.feed.isNotEmpty()) {
97- BookmarksGrid (feedState, removeFromBookmarks, onNewsResourceViewed, onTopicClick, modifier)
98- } else {
99- EmptyState (modifier)
114+ val bookmarkRemovedMessage = stringResource(id = R .string.bookmark_removed)
115+ val undoText = stringResource(id = R .string.undo)
116+ val snackbarHostState = remember { SnackbarHostState () }
117+
118+ LaunchedEffect (shouldDisplayUndoBookmark) {
119+ if (shouldDisplayUndoBookmark) {
120+ val snackBarResult = snackbarHostState.showSnackbar(
121+ message = bookmarkRemovedMessage,
122+ actionLabel = undoText,
123+ duration = Short ,
124+ )
125+ when (snackBarResult) {
126+ ActionPerformed -> { undoBookmarkRemoval() }
127+ else -> { clearUndoState() }
128+ }
129+ }
130+ }
131+
132+ val lifecycleOwner = LocalLifecycleOwner .current
133+ DisposableEffect (lifecycleOwner) {
134+ val observer = LifecycleEventObserver { _, event ->
135+ if (event == Lifecycle .Event .ON_STOP ) {
136+ clearUndoState()
137+ }
138+ }
139+ lifecycleOwner.lifecycle.addObserver(observer)
140+ onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
141+ }
142+
143+ Scaffold (snackbarHost = { SnackbarHost (hostState = snackbarHostState) }) {
144+ Box (
145+ modifier = Modifier .padding(it).fillMaxSize(),
146+ ) {
147+ when (feedState) {
148+ Loading -> LoadingState (modifier)
149+ is Success -> if (feedState.feed.isNotEmpty()) {
150+ BookmarksGrid (feedState, removeFromBookmarks, onNewsResourceViewed, onTopicClick, modifier)
151+ } else {
152+ EmptyState (modifier)
153+ }
154+ }
100155 }
101156 }
102157 TrackScreenViewEvent (screenName = " Saved" )
0 commit comments