feat: bulk remove bookmarks with undo snackbar restoring bookmarks and notes

Co-Authored-By: Claude <noreply@anthropic.com>
pull/2125/head
Rohit Karadkar 1 week ago
parent 3bc15f9e25
commit ad557a39cf

@ -126,6 +126,10 @@ internal fun BookmarksScreen(
exitSelectionMode = viewModel::exitSelectionMode,
toggleSelection = viewModel::toggleSelection,
selectAll = viewModel::selectAll,
shouldDisplayUndoBulkRemove = viewModel.shouldDisplayUndoBulkRemove,
removeSelected = viewModel::removeSelected,
undoBulkRemove = viewModel::undoBulkRemove,
clearBulkUndoState = viewModel::clearBulkUndoState,
)
editingNoteId?.let { id ->
@ -163,9 +167,14 @@ internal fun BookmarksScreen(
exitSelectionMode: () -> Unit = {},
toggleSelection: (String) -> Unit = {},
selectAll: () -> Unit = {},
shouldDisplayUndoBulkRemove: Boolean = false,
removeSelected: () -> Unit = {},
undoBulkRemove: () -> Unit = {},
clearBulkUndoState: () -> Unit = {},
) {
val bookmarkRemovedMessage = stringResource(id = R.string.feature_bookmarks_api_removed)
val undoText = stringResource(id = R.string.feature_bookmarks_api_undo)
val removedCount = remember { androidx.compose.runtime.mutableIntStateOf(0) }
BackHandler(enabled = isInSelectionMode) {
exitSelectionMode()
@ -182,6 +191,20 @@ internal fun BookmarksScreen(
}
}
LaunchedEffect(shouldDisplayUndoBulkRemove) {
if (shouldDisplayUndoBulkRemove) {
val result = onShowSnackbar(
"${removedCount.intValue} bookmarks removed",
undoText,
)
if (result) {
undoBulkRemove()
} else {
clearBulkUndoState()
}
}
}
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
clearUndoState()
}
@ -222,7 +245,10 @@ internal fun BookmarksScreen(
if (isInSelectionMode && selectedIds.isNotEmpty()) {
Button(
onClick = { selectedIds.forEach { removeFromBookmarks(it) } },
onClick = {
removedCount.intValue = selectedIds.size
removeSelected()
},
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(16.dp),

@ -107,6 +107,43 @@ class BookmarksViewModel @Inject constructor(
lastRemovedBookmarkId = null
}
var shouldDisplayUndoBulkRemove by mutableStateOf(false)
private set
private var bulkRemoveSnapshot: List<Pair<String, String?>> = emptyList()
fun removeSelected() {
val currentFeed = (feedUiState.value as? NewsFeedUiState.Success)?.feed ?: return
bulkRemoveSnapshot = selectedIds.map { id ->
id to currentFeed.find { it.id == id }?.bookmarkNote
}
val toRemove = selectedIds.toSet()
viewModelScope.launch {
toRemove.forEach { id ->
userDataRepository.setNewsResourceBookmarked(id, false)
}
}
exitSelectionMode()
shouldDisplayUndoBulkRemove = true
}
fun undoBulkRemove() {
viewModelScope.launch {
bulkRemoveSnapshot.forEach { (id, note) ->
userDataRepository.setNewsResourceBookmarked(id, true)
if (!note.isNullOrBlank()) {
userDataRepository.setBookmarkNote(id, note)
}
}
}
clearBulkUndoState()
}
fun clearBulkUndoState() {
shouldDisplayUndoBulkRemove = false
bulkRemoveSnapshot = emptyList()
}
fun updateNote(newsResourceId: String, note: String) {
viewModelScope.launch {
if (note.isNotBlank()) {

@ -136,6 +136,24 @@ class BookmarksViewModelTest {
assertTrue(viewModel.selectedIds.isEmpty())
}
@Test
fun removeSelected_capturesSnapshotBeforeRemoval() = runTest {
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() }
newsRepository.sendNewsResources(newsResourcesTestData)
userDataRepository.setNewsResourceBookmarked(newsResourcesTestData[0].id, true)
userDataRepository.setNewsResourceBookmarked(newsResourcesTestData[1].id, true)
viewModel.enterSelectionMode(newsResourcesTestData[0].id)
viewModel.toggleSelection(newsResourcesTestData[1].id)
viewModel.removeSelected()
assertTrue(viewModel.shouldDisplayUndoBulkRemove)
assertFalse(viewModel.isInSelectionMode)
assertTrue(viewModel.selectedIds.isEmpty())
}
@Test
fun feedUiState_undoneBookmarkRemoval_bookmarkIsRestored() = runTest {
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() }

Loading…
Cancel
Save