diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt index 61ab422af..4c2b4ce7c 100644 --- a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt @@ -65,4 +65,12 @@ class FakeUserDataRepository @Inject constructor( override suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) { niaPreferencesDataSource.setShouldHideOnboarding(shouldHideOnboarding) } + + override suspend fun setBookmarkNote(newsResourceId: String, note: String) { + niaPreferencesDataSource.setBookmarkNote(newsResourceId, note) + } + + override suspend fun removeBookmarkNote(newsResourceId: String) { + niaPreferencesDataSource.removeBookmarkNote(newsResourceId) + } } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt index 089b7087d..359da4721 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt @@ -72,4 +72,12 @@ internal class OfflineFirstUserDataRepository @Inject constructor( niaPreferencesDataSource.setShouldHideOnboarding(shouldHideOnboarding) analyticsHelper.logOnboardingStateChanged(shouldHideOnboarding) } + + override suspend fun setBookmarkNote(newsResourceId: String, note: String) { + niaPreferencesDataSource.setBookmarkNote(newsResourceId, note) + } + + override suspend fun removeBookmarkNote(newsResourceId: String) { + niaPreferencesDataSource.removeBookmarkNote(newsResourceId) + } } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt index c5202b02b..346a41ca4 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt @@ -67,4 +67,8 @@ interface UserDataRepository { * Sets whether the user has completed the onboarding process. */ suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) + + suspend fun setBookmarkNote(newsResourceId: String, note: String) + + suspend fun removeBookmarkNote(newsResourceId: String) } diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt index c11c2aa16..89c0aca99 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt @@ -56,6 +56,7 @@ class NiaPreferencesDataSource @Inject constructor( }, useDynamicColor = it.useDynamicColor, shouldHideOnboarding = it.shouldHideOnboarding, + bookmarkNotes = it.bookmarkNotesMap, ) } diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt index 80d62ece1..e819a2c0e 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSourceTest.kt @@ -23,7 +23,9 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNull import kotlin.test.assertTrue class NiaPreferencesDataSourceTest { @@ -87,6 +89,27 @@ class NiaPreferencesDataSourceTest { assertTrue(subject.userData.first().useDynamicColor) } - // TODO(Task 2): Add tests for setBookmarkNote, removeBookmarkNote, and - // bookmarkNotes auto-delete on unbookmark once UserData exposes bookmarkNotes. + @Test + fun setBookmarkNote_persistsNote() = runTest { + subject.setBookmarkNote("news1", "my note") + val prefs = subject.userData.first() + assertEquals("my note", prefs.bookmarkNotes["news1"]) + } + + @Test + fun removeBookmarkNote_deletesNote() = runTest { + subject.setBookmarkNote("news1", "my note") + subject.removeBookmarkNote("news1") + val prefs = subject.userData.first() + assertNull(prefs.bookmarkNotes["news1"]) + } + + @Test + fun setNewsResourceBookmarked_false_deletesNote() = runTest { + subject.setNewsResourceBookmarked("news1", true) + subject.setBookmarkNote("news1", "my note") + subject.setNewsResourceBookmarked("news1", false) + val prefs = subject.userData.first() + assertNull(prefs.bookmarkNotes["news1"]) + } } diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt index 6a22e4ff5..d20a49c9d 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt @@ -27,4 +27,5 @@ data class UserData( val darkThemeConfig: DarkThemeConfig, val useDynamicColor: Boolean, val shouldHideOnboarding: Boolean, + val bookmarkNotes: Map = emptyMap(), ) diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt index a56bbcb8d..f874f80fc 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt @@ -23,34 +23,27 @@ import kotlinx.datetime.Instant * news resource's topics and whether they have saved (bookmarked) this news resource. */ data class UserNewsResource internal constructor( - val id: String, - val title: String, - val content: String, - val url: String, - val headerImageUrl: String?, - val publishDate: Instant, - val type: String, - val followableTopics: List, - val isSaved: Boolean, - val hasBeenViewed: Boolean, + val newsResource: NewsResource, + val userData: UserData, ) { - constructor(newsResource: NewsResource, userData: UserData) : this( - id = newsResource.id, - title = newsResource.title, - content = newsResource.content, - url = newsResource.url, - headerImageUrl = newsResource.headerImageUrl, - publishDate = newsResource.publishDate, - type = newsResource.type, - followableTopics = newsResource.topics.map { topic -> + val id: String get() = newsResource.id + val title: String get() = newsResource.title + val content: String get() = newsResource.content + val url: String get() = newsResource.url + val headerImageUrl: String? get() = newsResource.headerImageUrl + val publishDate: Instant get() = newsResource.publishDate + val type: String get() = newsResource.type + val followableTopics: List + get() = newsResource.topics.map { topic -> FollowableTopic( topic = topic, isFollowed = topic.id in userData.followedTopics, ) - }, - isSaved = newsResource.id in userData.bookmarkedNewsResources, - hasBeenViewed = newsResource.id in userData.viewedNewsResources, - ) + } + val isSaved: Boolean get() = newsResource.id in userData.bookmarkedNewsResources + val hasBeenViewed: Boolean get() = newsResource.id in userData.viewedNewsResources + val bookmarkNote: String? + get() = userData.bookmarkNotes[newsResource.id].takeIf { !it.isNullOrBlank() } } fun List.mapToUserNewsResources(userData: UserData): List = diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt index be76112dc..df7609a54 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt @@ -112,6 +112,22 @@ class TestUserDataRepository : UserDataRepository { } } + override suspend fun setBookmarkNote(newsResourceId: String, note: String) { + currentUserData.let { current -> + _userData.tryEmit( + current.copy(bookmarkNotes = current.bookmarkNotes + (newsResourceId to note)), + ) + } + } + + override suspend fun removeBookmarkNote(newsResourceId: String) { + currentUserData.let { current -> + _userData.tryEmit( + current.copy(bookmarkNotes = current.bookmarkNotes - newsResourceId), + ) + } + } + /** * A test-only API to allow setting of user data directly. */