From ccd12a78d1d6bb95f4d20c4f2103700573de4540 Mon Sep 17 00:00:00 2001 From: Rohit Karadkar Date: Tue, 9 Jun 2026 09:03:08 +0530 Subject: [PATCH] feat: display and edit bookmark notes on BookmarksScreen Co-Authored-By: Claude --- .../feature/bookmarks/impl/BookmarksScreen.kt | 87 +++++++++++++++++-- .../bookmarks/impl/BookmarksViewModel.kt | 10 +++ 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksScreen.kt b/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksScreen.kt index d10f24d51..6939e8254 100644 --- a/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksScreen.kt +++ b/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksScreen.kt @@ -18,6 +18,7 @@ package com.google.samples.apps.nowinandroid.feature.bookmarks.impl import androidx.annotation.VisibleForTesting import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -38,21 +39,28 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -60,6 +68,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.DraggableScrollbar import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberDraggableScroller @@ -67,13 +76,16 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollba import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalTintTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource +import com.google.samples.apps.nowinandroid.core.ui.BookmarkNoteDialog +import com.google.samples.apps.nowinandroid.core.ui.NewsResourceCardExpanded import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParameterProvider -import com.google.samples.apps.nowinandroid.core.ui.newsFeed +import com.google.samples.apps.nowinandroid.core.ui.launchCustomChromeTab +import com.google.samples.apps.nowinandroid.core.ui.logNewsResourceOpened import com.google.samples.apps.nowinandroid.feature.bookmarks.api.R @Composable @@ -84,6 +96,8 @@ internal fun BookmarksScreen( viewModel: BookmarksViewModel = hiltViewModel(), ) { val feedState by viewModel.feedUiState.collectAsStateWithLifecycle() + var editingNoteId by remember { mutableStateOf(null) } + BookmarksScreen( feedState = feedState, onShowSnackbar = onShowSnackbar, @@ -94,7 +108,19 @@ internal fun BookmarksScreen( shouldDisplayUndoBookmark = viewModel.shouldDisplayUndoBookmark, undoBookmarkRemoval = viewModel::undoBookmarkRemoval, clearUndoState = viewModel::clearUndoState, + onEditNote = { editingNoteId = it }, ) + + editingNoteId?.let { id -> + val currentNote = (feedState as? Success)?.feed?.find { it.id == id }?.bookmarkNote ?: "" + BookmarkNoteDialog( + initialNote = currentNote, + onDismiss = { note -> + viewModel.updateNote(id, note) + editingNoteId = null + }, + ) + } } /** @@ -112,6 +138,7 @@ internal fun BookmarksScreen( shouldDisplayUndoBookmark: Boolean = false, undoBookmarkRemoval: () -> Unit = {}, clearUndoState: () -> Unit = {}, + onEditNote: (String) -> Unit = {}, ) { val bookmarkRemovedMessage = stringResource(id = R.string.feature_bookmarks_api_removed) val undoText = stringResource(id = R.string.feature_bookmarks_api_undo) @@ -139,6 +166,7 @@ internal fun BookmarksScreen( removeFromBookmarks, onNewsResourceViewed, onTopicClick, + onEditNote, modifier, ) } else { @@ -166,6 +194,7 @@ private fun BookmarksGrid( removeFromBookmarks: (String) -> Unit, onNewsResourceViewed: (String) -> Unit, onTopicClick: (String) -> Unit, + onEditNote: (String) -> Unit, modifier: Modifier = Modifier, ) { val scrollableState = rememberLazyStaggeredGridState() @@ -184,12 +213,55 @@ private fun BookmarksGrid( .fillMaxSize() .testTag("bookmarks:feed"), ) { - newsFeed( - feedState = feedState, - onNewsResourcesCheckedChanged = { id, _ -> removeFromBookmarks(id) }, - onNewsResourceViewed = onNewsResourceViewed, - onTopicClick = onTopicClick, - ) + if (feedState is Success) { + items( + items = feedState.feed, + key = { it.id }, + contentType = { "newsFeedItem" }, + ) { userNewsResource -> + val context = LocalContext.current + val analyticsHelper = LocalAnalyticsHelper.current + val backgroundColor = + MaterialTheme.colorScheme.background.toArgb() + Column( + modifier = Modifier + .padding(horizontal = 8.dp) + .animateItem(), + ) { + NewsResourceCardExpanded( + userNewsResource = userNewsResource, + isBookmarked = userNewsResource.isSaved, + onClick = { + analyticsHelper.logNewsResourceOpened( + newsResourceId = userNewsResource.id, + ) + launchCustomChromeTab( + context, + android.net.Uri.parse(userNewsResource.url), + backgroundColor, + ) + onNewsResourceViewed(userNewsResource.id) + }, + hasBeenViewed = userNewsResource.hasBeenViewed, + onToggleBookmark = { removeFromBookmarks(userNewsResource.id) }, + onTopicClick = onTopicClick, + ) + val note = userNewsResource.bookmarkNote + if (note != null) { + Text( + text = note, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .fillMaxWidth() + .clickable { onEditNote(userNewsResource.id) }, + ) + } + } + } + } item(span = StaggeredGridItemSpan.FullLine) { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) } @@ -275,6 +347,7 @@ private fun BookmarksGridPreview( removeFromBookmarks = {}, onNewsResourceViewed = {}, onTopicClick = {}, + onEditNote = {}, ) } } diff --git a/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksViewModel.kt b/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksViewModel.kt index f36c9d31f..96eda3613 100644 --- a/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksViewModel.kt +++ b/feature/bookmarks/impl/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/impl/BookmarksViewModel.kt @@ -81,4 +81,14 @@ class BookmarksViewModel @Inject constructor( shouldDisplayUndoBookmark = false lastRemovedBookmarkId = null } + + fun updateNote(newsResourceId: String, note: String) { + viewModelScope.launch { + if (note.isNotBlank()) { + userDataRepository.setBookmarkNote(newsResourceId, note) + } else { + userDataRepository.removeBookmarkNote(newsResourceId) + } + } + } }