feat: expose bookmark notes through UserData, UserNewsResource, and UserDataRepository

Co-Authored-By: Claude <noreply@anthropic.com>
pull/2125/head
Rohit Karadkar 2 weeks ago
parent 01ed90dcde
commit a37cc724f1

@ -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)
}
}

@ -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)
}
}

@ -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)
}

@ -56,6 +56,7 @@ class NiaPreferencesDataSource @Inject constructor(
},
useDynamicColor = it.useDynamicColor,
shouldHideOnboarding = it.shouldHideOnboarding,
bookmarkNotes = it.bookmarkNotesMap,
)
}

@ -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"])
}
}

@ -27,4 +27,5 @@ data class UserData(
val darkThemeConfig: DarkThemeConfig,
val useDynamicColor: Boolean,
val shouldHideOnboarding: Boolean,
val bookmarkNotes: Map<String, String> = emptyMap(),
)

@ -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<FollowableTopic>,
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<FollowableTopic>
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<NewsResource>.mapToUserNewsResources(userData: UserData): List<UserNewsResource> =

@ -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.
*/

Loading…
Cancel
Save