diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt index 334209538..f10046f73 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt @@ -50,6 +50,9 @@ class OfflineFirstUserDataRepository @Inject constructor( ) } + override suspend fun updateNewsResourceViewed(newsResourceId: String, viewed: Boolean) = + niaPreferencesDataSource.toggleNewsResourceViewed(newsResourceId, viewed) + override suspend fun setThemeBrand(themeBrand: ThemeBrand) { niaPreferencesDataSource.setThemeBrand(themeBrand) analyticsHelper.logThemeChanged(themeBrand.name) diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt index ea093852f..2ce84a963 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt @@ -43,6 +43,11 @@ interface UserDataRepository { */ suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean) + /** + * Updates the viewed status for a news resource + */ + suspend fun updateNewsResourceViewed(newsResourceId: String, viewed: Boolean) + /** * Sets the desired theme brand. */ diff --git a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt index af206e5c7..8b8a1f7f8 100644 --- a/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt +++ b/core/data/src/main/java/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt @@ -47,6 +47,9 @@ class FakeUserDataRepository @Inject constructor( niaPreferencesDataSource.toggleNewsResourceBookmark(newsResourceId, bookmarked) } + override suspend fun updateNewsResourceViewed(newsResourceId: String, viewed: Boolean) = + niaPreferencesDataSource.toggleNewsResourceViewed(newsResourceId, viewed) + override suspend fun setThemeBrand(themeBrand: ThemeBrand) { niaPreferencesDataSource.setThemeBrand(themeBrand) } diff --git a/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt b/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt index daf1a6564..994ae71b5 100644 --- a/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt +++ b/core/data/src/test/java/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt @@ -66,6 +66,7 @@ class OfflineFirstUserDataRepositoryTest { assertEquals( UserData( bookmarkedNewsResources = emptySet(), + viewedNewsResources = emptySet(), followedTopics = emptySet(), themeBrand = ThemeBrand.DEFAULT, darkThemeConfig = DarkThemeConfig.FOLLOW_SYSTEM, @@ -160,6 +161,37 @@ class OfflineFirstUserDataRepositoryTest { ) } + @Test + fun offlineFirstUserDataRepository_update_viewed_news_resources_delegates_to_nia_preferences() = + runTest { + subject.updateNewsResourceViewed(newsResourceId = "0", viewed = true) + + assertEquals( + setOf("0"), + subject.userData + .map { it.viewedNewsResources } + .first(), + ) + + subject.updateNewsResourceViewed(newsResourceId = "1", viewed = true) + + assertEquals( + setOf("0", "1"), + subject.userData + .map { it.viewedNewsResources } + .first(), + ) + + assertEquals( + niaPreferencesDataSource.userData + .map { it.viewedNewsResources } + .first(), + subject.userData + .map { it.viewedNewsResources } + .first(), + ) + } + @Test fun offlineFirstUserDataRepository_set_theme_brand_delegates_to_nia_preferences() = testScope.runTest { diff --git a/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt b/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt index f5751193a..91f8a3df2 100644 --- a/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt +++ b/core/datastore/src/main/java/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt @@ -33,6 +33,7 @@ class NiaPreferencesDataSource @Inject constructor( .map { UserData( bookmarkedNewsResources = it.bookmarkedNewsResourceIdsMap.keys, + viewedNewsResources = it.viewedNewsResourceIdsMap.keys, followedTopics = it.followedTopicIdsMap.keys, themeBrand = when (it.themeBrand) { null, @@ -137,6 +138,18 @@ class NiaPreferencesDataSource @Inject constructor( } } + suspend fun toggleNewsResourceViewed(newsResourceId: String, viewed: Boolean) { + userPreferences.updateData { + it.copy { + if (viewed) { + viewedNewsResourceIds.put(newsResourceId, true) + } else { + viewedNewsResourceIds.remove(newsResourceId) + } + } + } + } + suspend fun getChangeListVersions() = userPreferences.data .map { ChangeListVersions( diff --git a/core/datastore/src/main/proto/com/google/samples/apps/nowinandroid/data/user_preferences.proto b/core/datastore/src/main/proto/com/google/samples/apps/nowinandroid/data/user_preferences.proto index 5288c04ea..11386613c 100644 --- a/core/datastore/src/main/proto/com/google/samples/apps/nowinandroid/data/user_preferences.proto +++ b/core/datastore/src/main/proto/com/google/samples/apps/nowinandroid/data/user_preferences.proto @@ -40,6 +40,7 @@ message UserPreferences { map followed_topic_ids = 13; map followed_author_ids = 14; map bookmarked_news_resource_ids = 15; + map viewed_news_resource_ids = 20; ThemeBrandProto theme_brand = 16; DarkThemeConfigProto dark_theme_config = 17; @@ -47,4 +48,6 @@ message UserPreferences { bool should_hide_onboarding = 18; bool use_dynamic_color = 19; + + // NEXT AVAILABLE ID: 21 } diff --git a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt index 4e12ec95b..1d0051918 100644 --- a/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt +++ b/core/domain/src/main/java/com/google/samples/apps/nowinandroid/core/domain/model/UserNewsResource.kt @@ -35,6 +35,7 @@ data class UserNewsResource internal constructor( val type: NewsResourceType, val followableTopics: List, val isSaved: Boolean, + val isViewed: Boolean, ) { constructor(newsResource: NewsResource, userData: UserData) : this( id = newsResource.id, @@ -51,6 +52,7 @@ data class UserNewsResource internal constructor( ) }, isSaved = userData.bookmarkedNewsResources.contains(newsResource.id), + isViewed = userData.viewedNewsResources.contains(newsResource.id), ) } diff --git a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt index 8350c5178..7931d3f80 100644 --- a/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt +++ b/core/domain/src/test/java/com/google/samples/apps/nowinandroid/core/domain/UserNewsResourceTest.kt @@ -68,6 +68,7 @@ class UserNewsResourceTest { val userData = UserData( bookmarkedNewsResources = setOf("N1"), + viewedNewsResources = setOf("N1"), followedTopics = setOf("T1"), themeBrand = DEFAULT, darkThemeConfig = FOLLOW_SYSTEM, diff --git a/core/model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt b/core/model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt index 638b90d36..6a22e4ff5 100644 --- a/core/model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt +++ b/core/model/src/main/java/com/google/samples/apps/nowinandroid/core/model/data/UserData.kt @@ -21,6 +21,7 @@ package com.google.samples.apps.nowinandroid.core.model.data */ data class UserData( val bookmarkedNewsResources: Set, + val viewedNewsResources: Set, val followedTopics: Set, val themeBrand: ThemeBrand, val darkThemeConfig: DarkThemeConfig, diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt index 381160006..f4085d11e 100644 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt +++ b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt @@ -30,6 +30,7 @@ import kotlinx.datetime.toInstant /* ktlint-disable max-line-length */ val userNewsResourcesTestData: List = UserData( bookmarkedNewsResources = setOf("1", "4"), + viewedNewsResources = setOf("1", "2", "4"), followedTopics = emptySet(), themeBrand = ThemeBrand.ANDROID, darkThemeConfig = DarkThemeConfig.DARK, diff --git a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt index e1b86cd63..1b8483d1a 100644 --- a/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt +++ b/core/testing/src/main/java/com/google/samples/apps/nowinandroid/core/testing/repository/TestUserDataRepository.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.filterNotNull val emptyUserData = UserData( bookmarkedNewsResources = emptySet(), + viewedNewsResources = emptySet(), followedTopics = emptySet(), themeBrand = ThemeBrand.DEFAULT, darkThemeConfig = DarkThemeConfig.FOLLOW_SYSTEM, @@ -72,6 +73,21 @@ class TestUserDataRepository : UserDataRepository { } } + override suspend fun updateNewsResourceViewed(newsResourceId: String, viewed: Boolean) { + currentUserData.let { current -> + _userData.tryEmit( + current.copy( + viewedNewsResources = + if (viewed) { + current.viewedNewsResources + newsResourceId + } else { + current.viewedNewsResources - newsResourceId + }, + ), + ) + } + } + override suspend fun setThemeBrand(themeBrand: ThemeBrand) { currentUserData.let { current -> _userData.tryEmit(current.copy(themeBrand = themeBrand)) diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt index e32aa1a57..8a1c108c6 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt @@ -40,6 +40,7 @@ class UserNewsResourcePreviewParameterProvider : PreviewParameterProvider