diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 81c128b91..8197ad57b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -86,9 +86,11 @@ dependencies { implementation(project(":feature:settings")) implementation(project(":core:common")) + implementation(project(":core:domain")) implementation(project(":core:ui")) implementation(project(":core:designsystem")) implementation(project(":core:data")) + implementation(project(":core:domain")) implementation(project(":core:model")) implementation(project(":core:analytics")) diff --git a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt index c498c03dd..a0a737237 100644 --- a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt +++ b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt @@ -26,10 +26,15 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor +import com.google.samples.apps.nowinandroid.core.domain.repository.CompositeUserNewsResourceRepository +import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository +import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Before import org.junit.Rule import org.junit.Test @@ -63,6 +68,12 @@ class NavigationUiTest { @get:Rule(order = 2) val composeTestRule = createAndroidComposeRule() + val userNewsResourceRepository = CompositeUserNewsResourceRepository( + coroutineScope = TestScope(UnconfinedTestDispatcher()), + newsRepository = TestNewsRepository(), + userDataRepository = TestUserDataRepository(), + ) + @Inject lateinit var networkMonitor: NetworkMonitor @@ -81,6 +92,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -100,6 +112,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -119,6 +132,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -138,6 +152,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -157,6 +172,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -176,6 +192,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -195,6 +212,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -214,6 +232,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } @@ -233,6 +252,7 @@ class NavigationUiTest { DpSize(maxWidth, maxHeight), ), networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, ) } } diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt index 5fc9d0525..200c963b7 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -42,6 +42,7 @@ import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme +import com.google.samples.apps.nowinandroid.core.domain.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.ui.NiaApp @@ -67,6 +68,9 @@ class MainActivity : ComponentActivity() { @Inject lateinit var analyticsHelper: AnalyticsHelper + @Inject + lateinit var userNewsResourceRepository: UserNewsResourceRepository + val viewModel: MainActivityViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -119,6 +123,7 @@ class MainActivity : ComponentActivity() { NiaApp( networkMonitor = networkMonitor, windowSizeClass = calculateWindowSizeClass(this), + userNewsResourceRepository = userNewsResourceRepository, ) } } diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index 9565af2f8..c8648b666 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -44,12 +44,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hierarchy @@ -67,6 +70,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.ImageVec import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColors import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors +import com.google.samples.apps.nowinandroid.core.domain.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.feature.settings.SettingsDialog import com.google.samples.apps.nowinandroid.navigation.NiaNavHost import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination @@ -85,6 +89,7 @@ fun NiaApp( networkMonitor = networkMonitor, windowSizeClass = windowSizeClass, ), + userNewsResourceRepository: UserNewsResourceRepository, ) { val shouldShowGradientBackground = appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU @@ -128,8 +133,17 @@ fun NiaApp( snackbarHost = { SnackbarHost(snackbarHostState) }, bottomBar = { if (appState.shouldShowBottomBar) { + val forYouNewsResources by userNewsResourceRepository.getUserNewsResourcesForFollowedTopics() + .collectAsStateWithLifecycle(emptyList()) + val unreadDestinations = + when { + forYouNewsResources.all { it.isViewed } -> emptySet() + else -> setOf(TopLevelDestination.FOR_YOU) + } + NiaBottomBar( destinations = appState.topLevelDestinations, + destinationsWithUnreadResources = unreadDestinations, onNavigateToDestination = appState::navigateToTopLevelDestination, currentDestination = appState.currentDestination, modifier = Modifier.testTag("NiaBottomBar"), @@ -211,6 +225,7 @@ private fun NiaNavRail( imageVector = icon.imageVector, contentDescription = null, ) + is DrawableResourceIcon -> Icon( painter = painterResource(id = icon.id), contentDescription = null, @@ -218,6 +233,7 @@ private fun NiaNavRail( } }, label = { Text(stringResource(destination.iconTextId)) }, + ) } } @@ -226,6 +242,7 @@ private fun NiaNavRail( @Composable private fun NiaBottomBar( destinations: List, + destinationsWithUnreadResources: Set, onNavigateToDestination: (TopLevelDestination) -> Unit, currentDestination: NavDestination?, modifier: Modifier = Modifier, @@ -234,6 +251,7 @@ private fun NiaBottomBar( modifier = modifier, ) { destinations.forEach { destination -> + val hasUnread = destinationsWithUnreadResources.contains(destination) val selected = currentDestination.isTopLevelDestinationInHierarchy(destination) NiaNavigationBarItem( selected = selected, @@ -257,6 +275,25 @@ private fun NiaBottomBar( } }, label = { Text(stringResource(destination.iconTextId)) }, + modifier = if (hasUnread) { + val tertiaryColor = MaterialTheme.colorScheme.tertiary + Modifier.drawWithContent { + drawContent() + drawCircle( + tertiaryColor, + radius = 5.dp.toPx(), + // This is based on the dimensions of the NavigationBar's "indicator pill"; + // however, its parameters are private, so we must depend on them implicitly + // (NavigationBarTokens.ActiveIndicatorWidth = 64.dp) + center = center + Offset( + 64.dp.toPx() * .45f, + 32.dp.toPx() * -.45f - 6.dp.toPx(), + ), + ) + } + } else { + Modifier + }, ) } } diff --git a/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt b/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt index 712771422..8e2e8fb4a 100644 --- a/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt +++ b/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt @@ -20,6 +20,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.test.assertContentDescriptionEquals import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData @@ -39,6 +40,7 @@ class NewsResourceCardTest { NewsResourceCardExpanded( userNewsResource = newsWithKnownResourceType, isBookmarked = false, + isViewed = false, onToggleBookmark = {}, onClick = {}, onTopicClick = {}, @@ -67,6 +69,7 @@ class NewsResourceCardTest { NewsResourceCardExpanded( userNewsResource = newsWithUnknownResourceType, isBookmarked = false, + isViewed = false, onToggleBookmark = {}, onClick = {}, onTopicClick = {}, @@ -101,4 +104,52 @@ class NewsResourceCardTest { .assertContentDescriptionEquals(expectedContentDescription) } } + + @Test + fun testUnreadDot_displayedWhenUnread() { + val unreadNews = userNewsResourcesTestData[2] + + composeTestRule.setContent { + NewsResourceCardExpanded( + userNewsResource = unreadNews, + isBookmarked = false, + isViewed = false, + onToggleBookmark = {}, + onClick = {}, + onTopicClick = {}, + ) + } + + composeTestRule + .onNodeWithContentDescription( + composeTestRule.activity.getString( + R.string.unread_resource_dot_content_description, + ), + ) + .assertIsDisplayed() + } + + @Test + fun testUnreadDot_notDisplayedWhenRead() { + val readNews = userNewsResourcesTestData[0] + + composeTestRule.setContent { + NewsResourceCardExpanded( + userNewsResource = readNews, + isBookmarked = false, + isViewed = true, + onToggleBookmark = {}, + onClick = {}, + onTopicClick = {}, + ) + } + + composeTestRule + .onNodeWithContentDescription( + composeTestRule.activity.getString( + R.string.unread_resource_dot_content_description, + ), + ) + .assertDoesNotExist() + } } diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index 3b0015bab..fb1fb56b7 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -48,6 +48,7 @@ import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource fun LazyGridScope.newsFeed( feedState: NewsFeedUiState, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, ) { when (feedState) { @@ -70,7 +71,9 @@ fun LazyGridScope.newsFeed( newsResourceTitle = userNewsResource.title, ) launchCustomChromeTab(context, resourceUrl, backgroundColor) + onNewsResourcesViewedChanged(userNewsResource.id, true) }, + isViewed = userNewsResource.isViewed, onToggleBookmark = { onNewsResourcesCheckedChanged( userNewsResource.id, @@ -122,6 +125,7 @@ private fun NewsFeedLoadingPreview() { newsFeed( feedState = NewsFeedUiState.Loading, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -140,6 +144,7 @@ private fun NewsFeedContentPreview( newsFeed( feedState = NewsFeedUiState.Success(userNewsResources), onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index cffa59436..67a41fece 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.core.ui +import androidx.compose.foundation.Canvas import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -25,6 +26,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card @@ -40,7 +42,9 @@ 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.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode @@ -77,6 +81,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.R as DesignsystemR fun NewsResourceCardExpanded( userNewsResource: UserNewsResource, isBookmarked: Boolean, + isViewed: Boolean, onToggleBookmark: () -> Unit, onClick: () -> Unit, onTopicClick: (String) -> Unit, @@ -113,7 +118,16 @@ fun NewsResourceCardExpanded( BookmarkButton(isBookmarked, onToggleBookmark) } Spacer(modifier = Modifier.height(12.dp)) - NewsResourceMetaData(userNewsResource.publishDate, userNewsResource.type) + Row(verticalAlignment = Alignment.CenterVertically) { + if (!isViewed) { + Dot( + color = MaterialTheme.colorScheme.tertiary, + modifier = Modifier.size(8.dp), + ) + Spacer(modifier = Modifier.size(6.dp)) + } + NewsResourceMetaData(userNewsResource.publishDate, userNewsResource.type) + } Spacer(modifier = Modifier.height(12.dp)) NewsResourceShortDescription(userNewsResource.content) Spacer(modifier = Modifier.height(12.dp)) @@ -181,6 +195,24 @@ fun BookmarkButton( ) } +@Composable +fun Dot( + color: Color, + modifier: Modifier = Modifier, +) { + val description = stringResource(R.string.unread_resource_dot_content_description) + Canvas( + modifier = modifier + .semantics { contentDescription = description }, + onDraw = { + drawCircle( + color, + radius = size.minDimension / 2, + ) + }, + ) +} + @Composable fun dateFormatted(publishDate: Instant): String { var zoneId by remember { mutableStateOf(ZoneId.systemDefault()) } @@ -301,6 +333,7 @@ private fun ExpandedNewsResourcePreview( NewsResourceCardExpanded( userNewsResource = userNewsResources[0], isBookmarked = true, + isViewed = false, onToggleBookmark = {}, onClick = {}, onTopicClick = {}, diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt index 0f6861fbc..6c971e7a2 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardList.kt @@ -37,6 +37,7 @@ import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource fun LazyListScope.userNewsResourceCardItems( items: List, onToggleBookmark: (item: UserNewsResource) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, onItemClick: ((item: UserNewsResource) -> Unit)? = null, onTopicClick: (String) -> Unit, itemModifier: Modifier = Modifier, @@ -52,6 +53,7 @@ fun LazyListScope.userNewsResourceCardItems( NewsResourceCardExpanded( userNewsResource = userNewsResource, isBookmarked = userNewsResource.isSaved, + isViewed = userNewsResource.isViewed, onToggleBookmark = { onToggleBookmark(userNewsResource) }, onClick = { analyticsHelper.logNewsResourceOpened( @@ -59,7 +61,10 @@ fun LazyListScope.userNewsResourceCardItems( newsResourceTitle = userNewsResource.title, ) when (onItemClick) { - null -> launchCustomChromeTab(context, resourceUrl, backgroundColor) + null -> { + launchCustomChromeTab(context, resourceUrl, backgroundColor) + onNewsResourcesViewedChanged(userNewsResource.id, true) + } else -> onItemClick(userNewsResource) } }, diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index bfb1d38de..d21a5ea36 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -19,6 +19,8 @@ Unbookmark Back + Unread + Open Resource Link %1$s • %2$s diff --git a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt index 3662bd47f..c5ddd5c10 100644 --- a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt +++ b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt @@ -52,6 +52,7 @@ class BookmarksScreenTest { feedState = NewsFeedUiState.Loading, removeFromBookmarks = {}, onTopicClick = {}, + onNewsResourcesViewedChanged = { _, _ -> }, ) } @@ -71,6 +72,7 @@ class BookmarksScreenTest { ), removeFromBookmarks = {}, onTopicClick = {}, + onNewsResourcesViewedChanged = { _, _ -> }, ) } @@ -113,6 +115,7 @@ class BookmarksScreenTest { removeFromBookmarksCalled = true }, onTopicClick = {}, + onNewsResourcesViewedChanged = { _, _ -> }, ) } @@ -143,6 +146,7 @@ class BookmarksScreenTest { feedState = NewsFeedUiState.Success(emptyList()), removeFromBookmarks = {}, onTopicClick = {}, + onNewsResourcesViewedChanged = { _, _ -> }, ) } diff --git a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt index 3e0bb5784..b39f189d1 100644 --- a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt +++ b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt @@ -73,6 +73,7 @@ internal fun BookmarksRoute( BookmarksScreen( feedState = feedState, removeFromBookmarks = viewModel::removeFromSavedResources, + onNewsResourcesViewedChanged = viewModel::updateNewsResourceViewed, onTopicClick = onTopicClick, modifier = modifier, ) @@ -86,13 +87,14 @@ internal fun BookmarksRoute( internal fun BookmarksScreen( feedState: NewsFeedUiState, removeFromBookmarks: (String) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, ) { when (feedState) { Loading -> LoadingState(modifier) is Success -> if (feedState.feed.isNotEmpty()) { - BookmarksGrid(feedState, removeFromBookmarks, onTopicClick, modifier) + BookmarksGrid(feedState, removeFromBookmarks, onNewsResourcesViewedChanged, onTopicClick, modifier) } else { EmptyState(modifier) } @@ -115,6 +117,7 @@ private fun LoadingState(modifier: Modifier = Modifier) { private fun BookmarksGrid( feedState: NewsFeedUiState, removeFromBookmarks: (String) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, ) { @@ -133,6 +136,7 @@ private fun BookmarksGrid( newsFeed( feedState = feedState, onNewsResourcesCheckedChanged = { id, _ -> removeFromBookmarks(id) }, + onNewsResourcesViewedChanged = onNewsResourcesViewedChanged, onTopicClick = onTopicClick, ) item(span = { GridItemSpan(maxLineSpan) }) { @@ -198,6 +202,7 @@ private fun BookmarksGridPreview( BookmarksGrid( feedState = Success(userNewsResources), removeFromBookmarks = {}, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } diff --git a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt index 91d9355ae..7d0003aed 100644 --- a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt +++ b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModel.kt @@ -55,4 +55,10 @@ class BookmarksViewModel @Inject constructor( userDataRepository.updateNewsResourceBookmark(newsResourceId, false) } } + + fun updateNewsResourceViewed(newsResourceId: String, isViewed: Boolean) { + viewModelScope.launch { + userDataRepository.updateNewsResourceViewed(newsResourceId, isViewed) + } + } } diff --git a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt index ab712cbb5..a3566fc31 100644 --- a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt +++ b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt @@ -56,6 +56,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } } @@ -79,6 +80,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } } @@ -108,6 +110,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } } @@ -152,6 +155,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } } @@ -189,6 +193,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } } @@ -212,6 +217,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } } @@ -236,6 +242,7 @@ class ForYouScreenTest { onTopicClick = {}, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index aa4dc5f26..44a323868 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -107,6 +107,7 @@ internal fun ForYouRoute( onTopicClick = onTopicClick, saveFollowedTopics = viewModel::dismissOnboarding, onNewsResourcesCheckedChanged = viewModel::updateNewsResourceSaved, + onNewsResourcesViewedChanged = viewModel::updateNewsResourceViewed, modifier = modifier, ) } @@ -120,6 +121,7 @@ internal fun ForYouScreen( onTopicClick: (String) -> Unit, saveFollowedTopics: () -> Unit, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, modifier: Modifier = Modifier, ) { val isOnboardingLoading = onboardingUiState is OnboardingUiState.Loading @@ -177,6 +179,7 @@ internal fun ForYouScreen( newsFeed( feedState = feedState, onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged, + onNewsResourcesViewedChanged = onNewsResourcesViewedChanged, onTopicClick = onTopicClick, ) @@ -413,6 +416,7 @@ fun ForYouScreenPopulatedFeed( onTopicCheckedChanged = { _, _ -> }, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -436,6 +440,7 @@ fun ForYouScreenOfflinePopulatedFeed( onTopicCheckedChanged = { _, _ -> }, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -461,6 +466,7 @@ fun ForYouScreenTopicSelection( onTopicCheckedChanged = { _, _ -> }, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -479,6 +485,7 @@ fun ForYouScreenLoading() { onTopicCheckedChanged = { _, _ -> }, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -502,6 +509,7 @@ fun ForYouScreenPopulatedAndLoading( onTopicCheckedChanged = { _, _ -> }, saveFollowedTopics = {}, onNewsResourcesCheckedChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt index cece3a6c3..363356aef 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModel.kt @@ -95,6 +95,12 @@ class ForYouViewModel @Inject constructor( } } + fun updateNewsResourceViewed(newsResourceId: String, isViewed: Boolean) { + viewModelScope.launch { + userDataRepository.updateNewsResourceViewed(newsResourceId, isViewed) + } + } + fun dismissOnboarding() { viewModelScope.launch { userDataRepository.setShouldHideOnboarding(true) diff --git a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt index 3a267d7e7..65d923442 100644 --- a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt +++ b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt @@ -59,6 +59,7 @@ class TopicScreenTest { onFollowClick = {}, onTopicClick = {}, onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } @@ -78,6 +79,7 @@ class TopicScreenTest { onFollowClick = {}, onTopicClick = {}, onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } @@ -102,6 +104,7 @@ class TopicScreenTest { onFollowClick = {}, onTopicClick = {}, onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } @@ -124,6 +127,7 @@ class TopicScreenTest { onFollowClick = {}, onTopicClick = {}, onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, ) } diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index da6981010..4fc9faaca 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -79,6 +79,7 @@ internal fun TopicRoute( onBackClick = onBackClick, onFollowClick = viewModel::followTopicToggle, onBookmarkChanged = viewModel::bookmarkNews, + onNewsResourcesViewedChanged = viewModel::updateNewsResourceViewed, onTopicClick = onTopicClick, ) } @@ -92,6 +93,7 @@ internal fun TopicScreen( onFollowClick: (Boolean) -> Unit, onTopicClick: (String) -> Unit, onBookmarkChanged: (String, Boolean) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, modifier: Modifier = Modifier, ) { val state = rememberLazyListState() @@ -127,6 +129,7 @@ internal fun TopicScreen( news = newsUiState, imageUrl = topicUiState.followableTopic.topic.imageUrl, onBookmarkChanged = onBookmarkChanged, + onNewsResourcesViewedChanged = onNewsResourcesViewedChanged, onTopicClick = onTopicClick, ) } @@ -143,6 +146,7 @@ private fun LazyListScope.TopicBody( news: NewsUiState, imageUrl: String, onBookmarkChanged: (String, Boolean) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, ) { // TODO: Show icon if available @@ -150,7 +154,7 @@ private fun LazyListScope.TopicBody( TopicHeader(name, description, imageUrl) } - userNewsResourceCards(news, onBookmarkChanged, onTopicClick) + userNewsResourceCards(news, onBookmarkChanged, onNewsResourcesViewedChanged, onTopicClick) } @Composable @@ -181,6 +185,7 @@ private fun TopicHeader(name: String, description: String, imageUrl: String) { private fun LazyListScope.userNewsResourceCards( news: NewsUiState, onBookmarkChanged: (String, Boolean) -> Unit, + onNewsResourcesViewedChanged: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, ) { when (news) { @@ -188,6 +193,7 @@ private fun LazyListScope.userNewsResourceCards( userNewsResourceCardItems( items = news.news, onToggleBookmark = { onBookmarkChanged(it.id, !it.isSaved) }, + onNewsResourcesViewedChanged = onNewsResourcesViewedChanged, onTopicClick = onTopicClick, itemModifier = Modifier.padding(24.dp), ) @@ -214,6 +220,7 @@ private fun TopicBodyPreview() { news = NewsUiState.Success(emptyList()), imageUrl = "", onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -271,6 +278,7 @@ fun TopicScreenPopulated( onBackClick = {}, onFollowClick = {}, onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } @@ -288,6 +296,7 @@ fun TopicScreenLoading() { onBackClick = {}, onFollowClick = {}, onBookmarkChanged = { _, _ -> }, + onNewsResourcesViewedChanged = { _, _ -> }, onTopicClick = {}, ) } diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index bb03f9ae6..4dac25983 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -86,6 +86,12 @@ class TopicViewModel @Inject constructor( userDataRepository.updateNewsResourceBookmark(newsResourceId, bookmarked) } } + + fun updateNewsResourceViewed(newsResourceId: String, isViewed: Boolean) { + viewModelScope.launch { + userDataRepository.updateNewsResourceViewed(newsResourceId, isViewed) + } + } } private fun topicUiState(