Display unread state on the news feed and bottom nav bar

When a news resource is unread, display a dot on its card in the news
feed.  When the For You section has unread resources, display a dot on
its icon in the navigation bar.

Update the read status when a resource is opened.
pull/595/head
James Rose 2 years ago
parent bd450099fb
commit ebfbb5bafd

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

@ -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<HiltComponentActivity>()
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,
)
}
}

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

@ -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<TopLevelDestination>,
destinationsWithUnreadResources: Set<TopLevelDestination>,
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
},
)
}
}

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

@ -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 = {},
)
}

@ -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))
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 = {},

@ -37,6 +37,7 @@ import com.google.samples.apps.nowinandroid.core.domain.model.UserNewsResource
fun LazyListScope.userNewsResourceCardItems(
items: List<UserNewsResource>,
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)
}
},

@ -19,6 +19,8 @@
<string name="unbookmark">Unbookmark</string>
<string name="back">Back</string>
<string name="unread_resource_dot_content_description">Unread</string>
<string name="card_tap_action">Open Resource Link</string>
<string name="card_meta_data_text">%1$s • %2$s</string>

@ -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 = { _, _ -> },
)
}

@ -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 = {},
)
}

@ -55,4 +55,10 @@ class BookmarksViewModel @Inject constructor(
userDataRepository.updateNewsResourceBookmark(newsResourceId, false)
}
}
fun updateNewsResourceViewed(newsResourceId: String, isViewed: Boolean) {
viewModelScope.launch {
userDataRepository.updateNewsResourceViewed(newsResourceId, isViewed)
}
}
}

@ -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 = { _, _ -> },
)
}

@ -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 = {},
)
}

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

@ -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 = { _, _ -> },
)
}

@ -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 = {},
)
}

@ -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(

Loading…
Cancel
Save