Add Undo snackbar on Bookmark removal

Change-Id: I1fefd6e72378e26ae35b66e032529a116cff9a79
pull/640/head
Neelansh Sahai 2 years ago
parent 2c18740d62
commit bf747434cd

@ -14,8 +14,6 @@
* limitations under the License.
*/
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
id("nowinandroid.android.feature")
id("nowinandroid.android.library.compose")
@ -28,4 +26,4 @@ android {
dependencies {
implementation(libs.androidx.compose.material3.windowSizeClass)
}
}

@ -19,6 +19,7 @@ package com.google.samples.apps.nowinandroid.feature.bookmarks
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
@ -34,13 +35,23 @@ import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration.Short
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult.ActionPerformed
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -50,6 +61,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalTintTheme
@ -76,12 +89,16 @@ internal fun BookmarksRoute(
onNewsResourceViewed = { viewModel.setNewsResourceViewed(it, true) },
onTopicClick = onTopicClick,
modifier = modifier,
shouldDisplayUndoBookmark = viewModel.shouldDisplayUndoBookmark,
undoBookmarkRemoval = viewModel::undoBookmarkRemoval,
clearUndoState = viewModel::clearUndoState,
)
}
/**
* Displays the user's bookmarked articles. Includes support for loading and empty states.
*/
@OptIn(ExperimentalMaterial3Api::class)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@Composable
internal fun BookmarksScreen(
@ -90,13 +107,51 @@ internal fun BookmarksScreen(
onNewsResourceViewed: (String) -> Unit,
onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier,
shouldDisplayUndoBookmark: Boolean = false,
undoBookmarkRemoval: () -> Unit = {},
clearUndoState: () -> Unit = {},
) {
when (feedState) {
Loading -> LoadingState(modifier)
is Success -> if (feedState.feed.isNotEmpty()) {
BookmarksGrid(feedState, removeFromBookmarks, onNewsResourceViewed, onTopicClick, modifier)
} else {
EmptyState(modifier)
val bookmarkRemovedMessage = stringResource(id = R.string.bookmark_removed)
val undoText = stringResource(id = R.string.undo)
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(shouldDisplayUndoBookmark) {
if (shouldDisplayUndoBookmark) {
val snackBarResult = snackbarHostState.showSnackbar(
message = bookmarkRemovedMessage,
actionLabel = undoText,
duration = Short,
)
when (snackBarResult) {
ActionPerformed -> { undoBookmarkRemoval() }
else -> { clearUndoState() }
}
}
}
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_STOP) {
clearUndoState()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
Scaffold(snackbarHost = { SnackbarHost(hostState = snackbarHostState) }) {
Box(
modifier = Modifier.padding(it).fillMaxSize(),
) {
when (feedState) {
Loading -> LoadingState(modifier)
is Success -> if (feedState.feed.isNotEmpty()) {
BookmarksGrid(feedState, removeFromBookmarks, onNewsResourceViewed, onTopicClick, modifier)
} else {
EmptyState(modifier)
}
}
}
}
TrackScreenViewEvent(screenName = "Saved")

@ -16,6 +16,9 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
@ -38,6 +41,9 @@ class BookmarksViewModel @Inject constructor(
userNewsResourceRepository: UserNewsResourceRepository,
) : ViewModel() {
var shouldDisplayUndoBookmark by mutableStateOf(false)
private var lastRemovedBookmarkId: String? = null
val feedUiState: StateFlow<NewsFeedUiState> =
userNewsResourceRepository.observeAllBookmarked()
.map<List<UserNewsResource>, NewsFeedUiState>(NewsFeedUiState::Success)
@ -50,6 +56,8 @@ class BookmarksViewModel @Inject constructor(
fun removeFromSavedResources(newsResourceId: String) {
viewModelScope.launch {
shouldDisplayUndoBookmark = true
lastRemovedBookmarkId = newsResourceId
userDataRepository.updateNewsResourceBookmark(newsResourceId, false)
}
}
@ -59,4 +67,18 @@ class BookmarksViewModel @Inject constructor(
userDataRepository.setNewsResourceViewed(newsResourceId, viewed)
}
}
fun undoBookmarkRemoval() {
viewModelScope.launch {
lastRemovedBookmarkId?.let {
userDataRepository.updateNewsResourceBookmark(it, true)
}
}
clearUndoState()
}
fun clearUndoState() {
shouldDisplayUndoBookmark = false
lastRemovedBookmarkId = null
}
}

@ -22,4 +22,6 @@
<string name="top_app_bar_action_menu">Menu</string>
<string name="bookmarks_empty_error">No saved updates</string>
<string name="bookmarks_empty_description">Updates you save will be stored here\nto read later</string>
</resources>
<string name="bookmark_removed">Bookmark removed</string>
<string name="undo">UNDO</string>
</resources>

Loading…
Cancel
Save