`. This hot flow is created by combining two data streams:
-* List of authors (`getAuthors`)
-* List of author IDs which the current user is following
* List of topics
* List of topic IDs which the current user is following
-The list of `Author`s is mapped to a new list of `FollowableAuthor`s. `FollowableAuthor` is a wrapper for `Author` which also indicates whether the current user is following that author. The same transformation is applied for the list of `Topic`s.
+The list of `Topic`s is mapped to a new list of `FollowableTopic`s. `FollowableTopic` is a wrapper for `Topic` which also indicates whether the current user is following that topic.
-The two new lists are used to create a `InterestsUiState.Interests` state which is exposed to the UI.
+The new list is used to create a `InterestsUiState.Interests` state which is exposed to the UI.
### Processing user interactions
diff --git a/docs/ModularizationLearningJourney.md b/docs/ModularizationLearningJourney.md
index fbb0ac791..32f1c2249 100644
--- a/docs/ModularizationLearningJourney.md
+++ b/docs/ModularizationLearningJourney.md
@@ -143,12 +143,12 @@ Using the above modularization strategy, the Now in Android app has the followin
Functionality associated with a specific feature or user journey. Typically contains UI components and ViewModels which read data from other modules.
Examples include:
- feature:author displays information about an author on the AuthorScreen.
+ feature:topic displays information about a topic on the TopicScreen.
feature:foryou which displays the user's news feed, and onboarding during first run, on the For You screen.
|
- AuthorScreen
- AuthorViewModel
+ | TopicScreen
+ TopicViewModel
|
@@ -157,7 +157,6 @@ Using the above modularization strategy, the Now in Android app has the followin
Fetching app data from multiple sources, shared by different features.
|
TopicsRepository
- AuthorsRepository
|
@@ -219,7 +218,7 @@ Using the above modularization strategy, the Now in Android app has the followin
Model classes used throughout the app.
|
- Author
+ | Topic
Episode
NewsResource
|
diff --git a/feature/author/.gitignore b/feature/author/.gitignore
deleted file mode 100644
index 42afabfd2..000000000
--- a/feature/author/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/feature/author/README.md b/feature/author/README.md
deleted file mode 100644
index 7a8ee1c34..000000000
--- a/feature/author/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# :feature:author module
-
-![Dependency graph](../../docs/images/graphs/dep_graph_feature_author.png)
diff --git a/feature/author/build.gradle.kts b/feature/author/build.gradle.kts
deleted file mode 100644
index cf52f1751..000000000
--- a/feature/author/build.gradle.kts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-plugins {
- id("nowinandroid.android.feature")
- id("nowinandroid.android.library.compose")
- id("nowinandroid.android.library.jacoco")
-}
-
-android {
- namespace = "com.google.samples.apps.nowinandroid.feature.author"
-}
-
-dependencies {
- implementation(libs.kotlinx.datetime)
-}
diff --git a/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt b/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt
deleted file mode 100644
index b955912f2..000000000
--- a/feature/author/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreenTest.kt
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.samples.apps.nowinandroid.feature.author
-
-import androidx.activity.ComponentActivity
-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.domain.model.FollowableAuthor
-import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.Author
-import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video
-import kotlinx.datetime.Instant
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * UI test for checking the correct behaviour of the Author screen;
- * Verifies that, when a specific UiState is set, the corresponding
- * composables and details are shown
- */
-class AuthorScreenTest {
-
- @get:Rule
- val composeTestRule = createAndroidComposeRule()
-
- private lateinit var authorLoading: String
-
- @Before
- fun setup() {
- composeTestRule.activity.apply {
- authorLoading = getString(R.string.author_loading)
- }
- }
-
- @Test
- fun niaLoadingWheel_whenScreenIsLoading_showLoading() {
- composeTestRule.setContent {
- AuthorScreen(
- authorUiState = AuthorUiState.Loading,
- newsUiState = NewsUiState.Loading,
- onBackClick = { },
- onFollowClick = { },
- onBookmarkChanged = { _, _ -> },
- )
- }
-
- composeTestRule
- .onNodeWithContentDescription(authorLoading)
- .assertExists()
- }
-
- @Test
- fun authorTitle_whenAuthorIsSuccess_isShown() {
- val testAuthor = testAuthors.first()
- composeTestRule.setContent {
- AuthorScreen(
- authorUiState = AuthorUiState.Success(testAuthor),
- newsUiState = NewsUiState.Loading,
- onBackClick = { },
- onFollowClick = { },
- onBookmarkChanged = { _, _ -> },
- )
- }
-
- // Name is shown
- composeTestRule
- .onNodeWithText(testAuthor.author.name)
- .assertExists()
-
- // Bio is shown
- composeTestRule
- .onNodeWithText(testAuthor.author.bio)
- .assertExists()
- }
-
- @Test
- fun news_whenAuthorIsLoading_isNotShown() {
- composeTestRule.setContent {
- AuthorScreen(
- authorUiState = AuthorUiState.Loading,
- newsUiState = NewsUiState.Success(
- sampleNewsResources.mapIndexed { index, newsResource ->
- SaveableNewsResource(
- newsResource = newsResource,
- isSaved = index % 2 == 0,
- )
- }
- ),
- onBackClick = { },
- onFollowClick = { },
- onBookmarkChanged = { _, _ -> },
- )
- }
-
- // Loading indicator shown
- composeTestRule
- .onNodeWithContentDescription(authorLoading)
- .assertExists()
- }
-
- @Test
- fun news_whenSuccessAndAuthorIsSuccess_isShown() {
- val testAuthor = testAuthors.first()
- composeTestRule.setContent {
- AuthorScreen(
- authorUiState = AuthorUiState.Success(testAuthor),
- newsUiState = NewsUiState.Success(
- sampleNewsResources.mapIndexed { index, newsResource ->
- SaveableNewsResource(
- newsResource = newsResource,
- isSaved = index % 2 == 0,
- )
- }
- ),
- onBackClick = { },
- onFollowClick = { },
- onBookmarkChanged = { _, _ -> },
- )
- }
-
- // First news title shown
- composeTestRule
- .onNodeWithText(sampleNewsResources.first().title)
- .assertExists()
- }
-}
-
-private const val AUTHOR_1_NAME = "Author 1"
-private const val AUTHOR_2_NAME = "Author 2"
-private const val AUTHOR_3_NAME = "Author 3"
-private const val AUTHOR_BIO = "At vero eos et accusamus et iusto odio dignissimos ducimus qui."
-
-private val testAuthors = listOf(
- FollowableAuthor(
- Author(
- id = "0",
- name = AUTHOR_1_NAME,
- twitter = "",
- bio = AUTHOR_BIO,
- mediumPage = "",
- imageUrl = ""
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "1",
- name = AUTHOR_2_NAME,
- twitter = "",
- bio = AUTHOR_BIO,
- mediumPage = "",
- imageUrl = ""
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- Author(
- id = "2",
- name = AUTHOR_3_NAME,
- twitter = "",
- bio = AUTHOR_BIO,
- mediumPage = "",
- imageUrl = ""
- ),
- isFollowed = false
- )
-)
-
-private val sampleNewsResources = listOf(
- NewsResource(
- id = "1",
- title = "Thanks for helping us reach 1M YouTube Subscribers",
- content = "Thank you everyone for following the Now in Android series and everything the " +
- "Android Developers YouTube channel has to offer. During the Android Developer " +
- "Summit, our YouTube channel reached 1 million subscribers! Here’s a small video to " +
- "thank you all.",
- url = "https://youtu.be/-fJ6poHQrjM",
- headerImageUrl = "https://i.ytimg.com/vi/-fJ6poHQrjM/maxresdefault.jpg",
- publishDate = Instant.parse("2021-11-09T00:00:00.000Z"),
- type = Video,
- authors = listOf(
- Author(
- id = "0",
- name = "Headlines",
- twitter = "",
- bio = AUTHOR_BIO,
- mediumPage = "",
- imageUrl = ""
- )
- ),
- topics = emptyList()
- )
-)
diff --git a/feature/author/src/main/AndroidManifest.xml b/feature/author/src/main/AndroidManifest.xml
deleted file mode 100644
index 547d48054..000000000
--- a/feature/author/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
diff --git a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreen.kt b/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreen.kt
deleted file mode 100644
index 8f92a07d4..000000000
--- a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreen.kt
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.samples.apps.nowinandroid.feature.author
-
-import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.safeDrawing
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.windowInsetsBottomHeight
-import androidx.compose.foundation.layout.windowInsetsTopHeight
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyListScope
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import coil.compose.AsyncImage
-import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
-import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilterChip
-import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
-import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
-import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
-import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.Author
-import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
-import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
-import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews
-import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank
-import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems
-
-@OptIn(ExperimentalLifecycleComposeApi::class)
-@Composable
-internal fun AuthorRoute(
- onBackClick: () -> Unit,
- modifier: Modifier = Modifier,
- viewModel: AuthorViewModel = hiltViewModel(),
-) {
- val authorUiState: AuthorUiState by viewModel.authorUiState.collectAsStateWithLifecycle()
- val newsUiState: NewsUiState by viewModel.newsUiState.collectAsStateWithLifecycle()
-
- AuthorScreen(
- authorUiState = authorUiState,
- newsUiState = newsUiState,
- modifier = modifier,
- onBackClick = onBackClick,
- onFollowClick = viewModel::followAuthorToggle,
- onBookmarkChanged = viewModel::bookmarkNews,
- )
-}
-
-@VisibleForTesting
-@Composable
-internal fun AuthorScreen(
- authorUiState: AuthorUiState,
- newsUiState: NewsUiState,
- onBackClick: () -> Unit,
- onFollowClick: (Boolean) -> Unit,
- onBookmarkChanged: (String, Boolean) -> Unit,
- modifier: Modifier = Modifier,
-) {
- val scrollableState = rememberLazyListState()
- TrackScrollJank(scrollableState = scrollableState, stateName = "author:column")
- LazyColumn(
- modifier = modifier,
- horizontalAlignment = Alignment.CenterHorizontally,
- state = scrollableState
- ) {
- item {
- Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing))
- }
- when (authorUiState) {
- AuthorUiState.Loading -> {
- item {
- NiaLoadingWheel(
- modifier = modifier,
- contentDesc = stringResource(id = R.string.author_loading),
- )
- }
- }
- AuthorUiState.Error -> {
- TODO()
- }
- is AuthorUiState.Success -> {
- item {
- AuthorToolbar(
- onBackClick = onBackClick,
- onFollowClick = onFollowClick,
- uiState = authorUiState.followableAuthor,
- )
- }
- authorBody(
- author = authorUiState.followableAuthor.author,
- news = newsUiState,
- onBookmarkChanged = onBookmarkChanged,
- )
- }
- }
- item {
- Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
- }
- }
-}
-
-private fun LazyListScope.authorBody(
- author: Author,
- news: NewsUiState,
- onBookmarkChanged: (String, Boolean) -> Unit
-) {
- item {
- AuthorHeader(author)
- }
-
- authorCards(news, onBookmarkChanged)
-}
-
-@Composable
-private fun AuthorHeader(author: Author) {
- Column(
- modifier = Modifier.padding(horizontal = 24.dp)
- ) {
- AsyncImage(
- modifier = Modifier
- .padding(bottom = 12.dp)
- .size(216.dp)
- .align(Alignment.CenterHorizontally)
- .clip(CircleShape),
- contentScale = ContentScale.Crop,
- model = author.imageUrl,
- contentDescription = "Author profile picture",
- )
- Text(author.name, style = MaterialTheme.typography.displayMedium)
- if (author.bio.isNotEmpty()) {
- Text(
- text = author.bio,
- modifier = Modifier.padding(top = 24.dp),
- style = MaterialTheme.typography.bodyLarge
- )
- }
- }
-}
-
-private fun LazyListScope.authorCards(
- news: NewsUiState,
- onBookmarkChanged: (String, Boolean) -> Unit
-) {
- when (news) {
- is NewsUiState.Success -> {
- newsResourceCardItems(
- items = news.news,
- newsResourceMapper = { it.newsResource },
- isBookmarkedMapper = { it.isSaved },
- onToggleBookmark = { onBookmarkChanged(it.newsResource.id, !it.isSaved) },
- itemModifier = Modifier.padding(24.dp)
- )
- }
- is NewsUiState.Loading -> item {
- NiaLoadingWheel(contentDesc = "Loading news") // TODO
- }
- else -> item {
- Text("Error") // TODO
- }
- }
-}
-
-@Composable
-private fun AuthorToolbar(
- uiState: FollowableAuthor,
- modifier: Modifier = Modifier,
- onBackClick: () -> Unit = {},
- onFollowClick: (Boolean) -> Unit = {},
-) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = modifier
- .fillMaxWidth()
- .padding(bottom = 32.dp)
- ) {
- IconButton(onClick = { onBackClick() }) {
- Icon(
- imageVector = NiaIcons.ArrowBack,
- contentDescription = stringResource(
- id = com.google.samples.apps.nowinandroid.core.ui.R.string.back
- )
- )
- }
- val selected = uiState.isFollowed
- NiaFilterChip(
- modifier = Modifier.padding(horizontal = 16.dp),
- selected = selected,
- onSelectedChange = onFollowClick,
- ) {
- if (selected) {
- Text(stringResource(id = R.string.author_following))
- } else {
- Text(stringResource(id = R.string.author_not_following))
- }
- }
- }
-}
-
-@DevicePreviews
-@Composable
-fun AuthorScreenPopulated() {
- NiaTheme {
- NiaBackground {
- AuthorScreen(
- authorUiState = AuthorUiState.Success(FollowableAuthor(previewAuthors[0], false)),
- newsUiState = NewsUiState.Success(
- previewNewsResources.mapIndexed { index, newsResource ->
- SaveableNewsResource(
- newsResource = newsResource,
- isSaved = index % 2 == 0,
- )
- }
- ),
- onBackClick = {},
- onFollowClick = {},
- onBookmarkChanged = { _, _ -> },
- )
- }
- }
-}
-
-@DevicePreviews
-@Composable
-fun AuthorScreenLoading() {
- NiaTheme {
- NiaBackground {
- AuthorScreen(
- authorUiState = AuthorUiState.Loading,
- newsUiState = NewsUiState.Loading,
- onBackClick = {},
- onFollowClick = {},
- onBookmarkChanged = { _, _ -> },
- )
- }
- }
-}
diff --git a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt b/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt
deleted file mode 100644
index 72b876a02..000000000
--- a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.samples.apps.nowinandroid.feature.author
-
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.google.samples.apps.nowinandroid.core.data.repository.AuthorsRepository
-import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
-import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder
-import com.google.samples.apps.nowinandroid.core.domain.GetSaveableNewsResourcesUseCase
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
-import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.Author
-import com.google.samples.apps.nowinandroid.core.result.Result
-import com.google.samples.apps.nowinandroid.core.result.asResult
-import com.google.samples.apps.nowinandroid.feature.author.navigation.AuthorArgs
-import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-@HiltViewModel
-class AuthorViewModel @Inject constructor(
- savedStateHandle: SavedStateHandle,
- stringDecoder: StringDecoder,
- private val userDataRepository: UserDataRepository,
- authorsRepository: AuthorsRepository,
- getSaveableNewsResources: GetSaveableNewsResourcesUseCase
-) : ViewModel() {
-
- private val authorArgs: AuthorArgs = AuthorArgs(savedStateHandle, stringDecoder)
-
- val authorUiState: StateFlow = authorUiState(
- authorId = authorArgs.authorId,
- userDataRepository = userDataRepository,
- authorsRepository = authorsRepository
- )
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(5_000),
- initialValue = AuthorUiState.Loading
- )
-
- val newsUiState: StateFlow =
- getSaveableNewsResources.newsUiState(authorId = authorArgs.authorId)
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(5_000),
- initialValue = NewsUiState.Loading
- )
-
- fun followAuthorToggle(followed: Boolean) {
- viewModelScope.launch {
- userDataRepository.toggleFollowedAuthorId(authorArgs.authorId, followed)
- }
- }
-
- fun bookmarkNews(newsResourceId: String, bookmarked: Boolean) {
- viewModelScope.launch {
- userDataRepository.updateNewsResourceBookmark(newsResourceId, bookmarked)
- }
- }
-}
-
-private fun authorUiState(
- authorId: String,
- userDataRepository: UserDataRepository,
- authorsRepository: AuthorsRepository,
-): Flow {
- // Observe the followed authors, as they could change over time.
- val followedAuthorIds: Flow> =
- userDataRepository.userData
- .map { it.followedAuthors }
-
- // Observe author information
- val author: Flow = authorsRepository.getAuthor(
- id = authorId
- )
-
- return combine(
- followedAuthorIds,
- author,
- ::Pair
- )
- .asResult()
- .map { followedAuthorToAuthorResult ->
- when (followedAuthorToAuthorResult) {
- is Result.Success -> {
- val (followedAuthors, author) = followedAuthorToAuthorResult.data
- val followed = followedAuthors.contains(authorId)
- AuthorUiState.Success(
- followableAuthor = FollowableAuthor(
- author = author,
- isFollowed = followed
- )
- )
- }
- is Result.Loading -> {
- AuthorUiState.Loading
- }
- is Result.Error -> {
- AuthorUiState.Error
- }
- }
- }
-}
-
-private fun GetSaveableNewsResourcesUseCase.newsUiState(
- authorId: String
-): Flow {
- // Observe news
- return this(
- filterAuthorIds = setOf(element = authorId)
- ).asResult()
- .map { newsResult ->
- when (newsResult) {
- is Result.Success -> NewsUiState.Success(newsResult.data)
- is Result.Loading -> NewsUiState.Loading
- is Result.Error -> NewsUiState.Error
- }
- }
-}
-
-sealed interface AuthorUiState {
- data class Success(val followableAuthor: FollowableAuthor) : AuthorUiState
- object Error : AuthorUiState
- object Loading : AuthorUiState
-}
-
-sealed interface NewsUiState {
- data class Success(val news: List) : NewsUiState
- object Error : NewsUiState
- object Loading : NewsUiState
-}
diff --git a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt b/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt
deleted file mode 100644
index 59b68ce91..000000000
--- a/feature/author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/navigation/AuthorNavigation.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.samples.apps.nowinandroid.feature.author.navigation
-
-import android.net.Uri
-import androidx.annotation.VisibleForTesting
-import androidx.lifecycle.SavedStateHandle
-import androidx.navigation.NavController
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavType
-import androidx.navigation.compose.composable
-import androidx.navigation.navArgument
-import com.google.samples.apps.nowinandroid.core.decoder.StringDecoder
-import com.google.samples.apps.nowinandroid.feature.author.AuthorRoute
-
-@VisibleForTesting
-internal const val authorIdArg = "authorId"
-
-internal class AuthorArgs(val authorId: String) {
- constructor(savedStateHandle: SavedStateHandle, stringDecoder: StringDecoder) :
- this(stringDecoder.decodeString(checkNotNull(savedStateHandle[authorIdArg])))
-}
-
-fun NavController.navigateToAuthor(authorId: String) {
- val encodedString = Uri.encode(authorId)
- this.navigate("author_route/$encodedString")
-}
-
-fun NavGraphBuilder.authorScreen(
- onBackClick: () -> Unit
-) {
- composable(
- route = "author_route/{$authorIdArg}",
- arguments = listOf(
- navArgument(authorIdArg) { type = NavType.StringType }
- )
- ) {
- AuthorRoute(onBackClick = onBackClick)
- }
-}
diff --git a/feature/author/src/main/res/values/strings.xml b/feature/author/src/main/res/values/strings.xml
deleted file mode 100644
index 84b479668..000000000
--- a/feature/author/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
- Author
- Loading author
- FOLLOWING
- NOT FOLLOWING
-
diff --git a/feature/author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt b/feature/author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt
deleted file mode 100644
index 498d9163b..000000000
--- a/feature/author/src/test/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModelTest.kt
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.samples.apps.nowinandroid.feature.author
-
-import androidx.lifecycle.SavedStateHandle
-import com.google.samples.apps.nowinandroid.core.domain.GetSaveableNewsResourcesUseCase
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
-import com.google.samples.apps.nowinandroid.core.model.data.Author
-import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video
-import com.google.samples.apps.nowinandroid.core.testing.decoder.FakeStringDecoder
-import com.google.samples.apps.nowinandroid.core.testing.repository.TestAuthorsRepository
-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.core.testing.util.MainDispatcherRule
-import com.google.samples.apps.nowinandroid.feature.author.navigation.authorIdArg
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertTrue
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
-import kotlinx.datetime.Instant
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-/**
- * To learn more about how this test handles Flows created with stateIn, see
- * https://developer.android.com/kotlin/flow/test#statein
- */
-class AuthorViewModelTest {
-
- @get:Rule
- val dispatcherRule = MainDispatcherRule()
-
- private val userDataRepository = TestUserDataRepository()
- private val authorsRepository = TestAuthorsRepository()
- private val newsRepository = TestNewsRepository()
- private val getSaveableNewsResourcesUseCase = GetSaveableNewsResourcesUseCase(
- newsRepository = newsRepository,
- userDataRepository = userDataRepository
- )
- private lateinit var viewModel: AuthorViewModel
-
- @Before
- fun setup() {
- viewModel = AuthorViewModel(
- savedStateHandle = SavedStateHandle(
- mapOf(
- authorIdArg to testInputAuthors[0].author.id
- )
- ),
- stringDecoder = FakeStringDecoder(),
- userDataRepository = userDataRepository,
- authorsRepository = authorsRepository,
- getSaveableNewsResources = getSaveableNewsResourcesUseCase
- )
- }
-
- @Test
- fun uiStateAuthor_whenSuccess_matchesAuthorFromRepository() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.authorUiState.collect() }
-
- // To make sure AuthorUiState is success
- authorsRepository.sendAuthors(testInputAuthors.map(FollowableAuthor::author))
- userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
-
- val item = viewModel.authorUiState.value
- assertIs(item)
-
- val authorFromRepository = authorsRepository.getAuthor(
- id = testInputAuthors[0].author.id
- ).first()
-
- item.followableAuthor.author
- assertEquals(authorFromRepository, item.followableAuthor.author)
-
- collectJob.cancel()
- }
-
- @Test
- fun uiStateNews_whenInitialized_thenShowLoading() = runTest {
- assertEquals(NewsUiState.Loading, viewModel.newsUiState.value)
- }
-
- @Test
- fun uiStateAuthor_whenInitialized_thenShowLoading() = runTest {
- assertEquals(AuthorUiState.Loading, viewModel.authorUiState.value)
- }
-
- @Test
- fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorLoading_thenShowLoading() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.authorUiState.collect() }
-
- userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
- assertEquals(AuthorUiState.Loading, viewModel.authorUiState.value)
-
- collectJob.cancel()
- }
-
- @Test
- fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorSuccess_thenAuthorSuccessAndNewsLoading() =
- runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) {
- combine(
- viewModel.authorUiState,
- viewModel.newsUiState,
- ::Pair
- ).collect()
- }
-
- authorsRepository.sendAuthors(testInputAuthors.map { it.author })
- userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
- val authorState = viewModel.authorUiState.value
- val newsUiState = viewModel.newsUiState.value
-
- assertIs(authorState)
- assertIs(newsUiState)
-
- collectJob.cancel()
- }
-
- @Test
- fun uiStateAuthor_whenFollowedIdsSuccessAndAuthorSuccessAndNewsIsSuccess_thenAllSuccess() =
- runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) {
- combine(
- viewModel.authorUiState,
- viewModel.newsUiState,
- ::Pair
- ).collect()
- }
-
- authorsRepository.sendAuthors(testInputAuthors.map { it.author })
- userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
- newsRepository.sendNewsResources(sampleNewsResources)
- val authorState = viewModel.authorUiState.value
- val newsUiState = viewModel.newsUiState.value
-
- assertIs(authorState)
- assertIs(newsUiState)
-
- collectJob.cancel()
- }
-
- @Test
- fun uiStateAuthor_whenFollowingAuthor_thenShowUpdatedAuthor() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.authorUiState.collect() }
-
- authorsRepository.sendAuthors(testInputAuthors.map { it.author })
- // Set which author IDs are followed, not including 0.
- userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[1].author.id))
-
- viewModel.followAuthorToggle(true)
-
- assertEquals(
- AuthorUiState.Success(followableAuthor = testOutputAuthors[0]),
- viewModel.authorUiState.value
- )
-
- collectJob.cancel()
- }
-
- @Test
- fun uiStateAuthor_whenNewsBookmarked_thenShowBookmarkedNews() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.newsUiState.collect() }
-
- authorsRepository.sendAuthors(testInputAuthors.map { it.author })
- newsRepository.sendNewsResources(sampleNewsResources)
-
- // Set initial bookmarked status to false
- userDataRepository.updateNewsResourceBookmark(
- newsResourceId = sampleNewsResources.first().id,
- bookmarked = false
- )
-
- viewModel.bookmarkNews(
- newsResourceId = sampleNewsResources.first().id,
- bookmarked = true
- )
-
- assertTrue(
- (viewModel.newsUiState.value as NewsUiState.Success)
- .news
- .first { it.newsResource.id == sampleNewsResources.first().id }
- .isSaved
- )
-
- collectJob.cancel()
- }
-}
-
-private const val AUTHOR_1_NAME = "Author 1"
-private const val AUTHOR_2_NAME = "Author 2"
-private const val AUTHOR_3_NAME = "Author 3"
-private const val AUTHOR_BIO = "At vero eos et accusamus."
-private const val AUTHOR_TWITTER = "dev"
-private const val AUTHOR_MEDIUM_PAGE = "URL"
-private const val AUTHOR_IMAGE_URL = "Image URL"
-
-private val testInputAuthors = listOf(
- FollowableAuthor(
- Author(
- id = "0",
- name = AUTHOR_1_NAME,
- bio = AUTHOR_BIO,
- twitter = AUTHOR_TWITTER,
- mediumPage = AUTHOR_MEDIUM_PAGE,
- imageUrl = AUTHOR_IMAGE_URL,
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "1",
- name = AUTHOR_2_NAME,
- bio = AUTHOR_BIO,
- twitter = AUTHOR_TWITTER,
- mediumPage = AUTHOR_MEDIUM_PAGE,
- imageUrl = AUTHOR_IMAGE_URL,
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- Author(
- id = "2",
- name = AUTHOR_3_NAME,
- bio = AUTHOR_BIO,
- twitter = AUTHOR_TWITTER,
- mediumPage = AUTHOR_MEDIUM_PAGE,
- imageUrl = AUTHOR_IMAGE_URL,
- ),
- isFollowed = false
- )
-)
-
-private val testOutputAuthors = listOf(
- FollowableAuthor(
- Author(
- id = "0",
- name = AUTHOR_1_NAME,
- bio = AUTHOR_BIO,
- twitter = AUTHOR_TWITTER,
- mediumPage = AUTHOR_MEDIUM_PAGE,
- imageUrl = AUTHOR_IMAGE_URL,
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "1",
- name = AUTHOR_2_NAME,
- bio = AUTHOR_BIO,
- twitter = AUTHOR_TWITTER,
- mediumPage = AUTHOR_MEDIUM_PAGE,
- imageUrl = AUTHOR_IMAGE_URL,
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "2",
- name = AUTHOR_3_NAME,
- bio = AUTHOR_BIO,
- twitter = AUTHOR_TWITTER,
- mediumPage = AUTHOR_MEDIUM_PAGE,
- imageUrl = AUTHOR_IMAGE_URL,
- ),
- isFollowed = false
- )
-)
-
-private val sampleNewsResources = listOf(
- NewsResource(
- id = "1",
- title = "Thanks for helping us reach 1M YouTube Subscribers",
- content = "Thank you everyone for following the Now in Android series and everything the " +
- "Android Developers YouTube channel has to offer. During the Android Developer " +
- "Summit, our YouTube channel reached 1 million subscribers! Here’s a small video to " +
- "thank you all.",
- url = "https://youtu.be/-fJ6poHQrjM",
- headerImageUrl = "https://i.ytimg.com/vi/-fJ6poHQrjM/maxresdefault.jpg",
- publishDate = Instant.parse("2021-11-09T00:00:00.000Z"),
- type = Video,
- authors = listOf(
- Author(
- id = "0",
- name = "Android Dev",
- bio = "Hello there!",
- twitter = "dev",
- mediumPage = "URL",
- imageUrl = "image URL",
- )
- ),
- topics = emptyList()
- )
-)
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 d89f5d91f..702a963c1 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
@@ -28,10 +28,8 @@ import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performScrollToNode
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
@@ -57,7 +55,6 @@ class ForYouScreenTest {
onboardingUiState = OnboardingUiState.Loading,
feedState = NewsFeedUiState.Loading,
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -80,7 +77,6 @@ class ForYouScreenTest {
onboardingUiState = OnboardingUiState.NotShown,
feedState = NewsFeedUiState.Success(emptyList()),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -95,7 +91,7 @@ class ForYouScreenTest {
}
@Test
- fun topicSelector_whenNoTopicsSelected_showsAuthorAndTopicChipsAndDisabledDoneButton() {
+ fun topicSelector_whenNoTopicsSelected_showsTopicChipsAndDisabledDoneButton() {
composeTestRule.setContent {
BoxWithConstraints {
ForYouScreen(
@@ -103,26 +99,17 @@ class ForYouScreenTest {
onboardingUiState =
OnboardingUiState.Shown(
topics = testTopics,
- authors = testAuthors
),
feedState = NewsFeedUiState.Success(
feed = emptyList()
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
}
}
- testAuthors.forEach { testAuthor ->
- composeTestRule
- .onNodeWithText(testAuthor.author.name)
- .assertExists()
- .assertHasClickAction()
- }
-
testTopics.forEach { testTopic ->
composeTestRule
.onNodeWithText(testTopic.topic.name)
@@ -144,7 +131,7 @@ class ForYouScreenTest {
}
@Test
- fun topicSelector_whenSomeTopicsSelected_showsAuthorAndTopicChipsAndEnabledDoneButton() {
+ fun topicSelector_whenSomeTopicsSelected_showsTopicChipsAndEnabledDoneButton() {
composeTestRule.setContent {
BoxWithConstraints {
ForYouScreen(
@@ -154,79 +141,18 @@ class ForYouScreenTest {
// Follow one topic
topics = testTopics.mapIndexed { index, testTopic ->
testTopic.copy(isFollowed = index == 1)
- },
- authors = testAuthors
- ),
- feedState = NewsFeedUiState.Success(
- feed = emptyList()
- ),
- onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
- saveFollowedTopics = {},
- onNewsResourcesCheckedChanged = { _, _ -> }
- )
- }
- }
-
- testAuthors.forEach { testAuthor ->
- composeTestRule
- .onNodeWithText(testAuthor.author.name)
- .assertExists()
- .assertHasClickAction()
- }
-
- testTopics.forEach { testTopic ->
- composeTestRule
- .onNodeWithText(testTopic.topic.name)
- .assertExists()
- .assertHasClickAction()
- }
-
- // Scroll until the Done button is visible
- composeTestRule
- .onAllNodes(hasScrollToNodeAction())
- .onFirst()
- .performScrollToNode(doneButtonMatcher)
-
- composeTestRule
- .onNode(doneButtonMatcher)
- .assertExists()
- .assertIsEnabled()
- .assertHasClickAction()
- }
-
- @Test
- fun topicSelector_whenSomeAuthorsSelected_showsAuthorAndTopicChipsAndEnabledDoneButton() {
- composeTestRule.setContent {
- BoxWithConstraints {
- ForYouScreen(
- isSyncing = false,
- onboardingUiState =
- OnboardingUiState.Shown(
- // Follow one topic
- topics = testTopics,
- authors = testAuthors.mapIndexed { index, testAuthor ->
- testAuthor.copy(isFollowed = index == 1)
}
),
feedState = NewsFeedUiState.Success(
feed = emptyList()
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
}
}
- testAuthors.forEach { testAuthor ->
- composeTestRule
- .onNodeWithText(testAuthor.author.name)
- .assertExists()
- .assertHasClickAction()
- }
-
testTopics.forEach { testTopic ->
composeTestRule
.onNodeWithText(testTopic.topic.name)
@@ -254,13 +180,9 @@ class ForYouScreenTest {
ForYouScreen(
isSyncing = false,
onboardingUiState =
- OnboardingUiState.Shown(
- topics = testTopics,
- authors = testAuthors
- ),
+ OnboardingUiState.Shown(topics = testTopics),
feedState = NewsFeedUiState.Loading,
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -283,7 +205,6 @@ class ForYouScreenTest {
onboardingUiState = OnboardingUiState.NotShown,
feedState = NewsFeedUiState.Loading,
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -309,7 +230,6 @@ class ForYouScreenTest {
}
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -349,14 +269,6 @@ private val testTopic = Topic(
url = "",
imageUrl = ""
)
-private val testAuthor = Author(
- id = "",
- name = "",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = ""
-)
private val testTopics = listOf(
FollowableTopic(
topic = testTopic.copy(id = "0", name = "Headlines"),
@@ -371,13 +283,3 @@ private val testTopics = listOf(
isFollowed = false
),
)
-private val testAuthors = listOf(
- FollowableAuthor(
- author = testAuthor.copy(id = "0", name = "Android Dev"),
- isFollowed = false
- ),
- FollowableAuthor(
- author = testAuthor.copy(id = "1", name = "Android Dev 2"),
- isFollowed = false
- ),
-)
diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/AuthorsCarousel.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/AuthorsCarousel.kt
deleted file mode 100644
index 0a7955c2f..000000000
--- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/AuthorsCarousel.kt
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.samples.apps.nowinandroid.feature.foryou
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-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.layout.sizeIn
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.selection.toggleable
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.ripple.rememberRipple
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
-import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
-import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
-import com.google.samples.apps.nowinandroid.core.model.data.Author
-import com.google.samples.apps.nowinandroid.core.ui.TrackScrollJank
-
-@Composable
-fun AuthorsCarousel(
- authors: List,
- onAuthorClick: (String, Boolean) -> Unit,
- modifier: Modifier = Modifier
-) {
- val lazyListState = rememberLazyListState()
- val tag = "forYou:authors"
- TrackScrollJank(scrollableState = lazyListState, stateName = tag)
-
- LazyRow(
- modifier = modifier.testTag(tag),
- contentPadding = PaddingValues(16.dp),
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- state = lazyListState
- ) {
- items(items = authors, key = { item -> item.author.id }) { followableAuthor ->
- AuthorItem(
- author = followableAuthor.author,
- following = followableAuthor.isFollowed,
- onAuthorClick = { following ->
- onAuthorClick(followableAuthor.author.id, following)
- },
- )
- }
- }
-}
-
-@Composable
-fun AuthorItem(
- author: Author,
- following: Boolean,
- onAuthorClick: (Boolean) -> Unit,
- modifier: Modifier = Modifier,
-) {
- val followDescription = if (following) {
- stringResource(R.string.following)
- } else {
- stringResource(R.string.not_following)
- }
- val followActionLabel = if (following) {
- stringResource(R.string.unfollow)
- } else {
- stringResource(R.string.follow)
- }
-
- Column(
- modifier = modifier
- .toggleable(
- value = following,
- role = Role.Button,
- interactionSource = remember { MutableInteractionSource() },
- indication = rememberRipple(bounded = false),
- onValueChange = { newFollowing -> onAuthorClick(newFollowing) },
- )
- .padding(8.dp)
- .sizeIn(maxWidth = 48.dp)
- .semantics(mergeDescendants = true) {
- // Add information for A11y services, explaining what each state means and
- // what will happen when the user interacts with the author item.
- stateDescription = "$followDescription ${author.name}"
- onClick(label = followActionLabel, action = null)
- }
- ) {
- Box(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center,
- ) {
- val authorImageModifier = Modifier
- .size(48.dp)
- .clip(CircleShape)
- if (author.imageUrl.isEmpty()) {
- Icon(
- modifier = authorImageModifier
- .background(MaterialTheme.colorScheme.surface)
- .padding(4.dp),
- imageVector = NiaIcons.Person,
- contentDescription = null // decorative image
- )
- } else {
- AsyncImage(
- modifier = authorImageModifier,
- model = author.imageUrl,
- contentScale = ContentScale.Crop,
- contentDescription = null
- )
- }
- val backgroundColor =
- if (following)
- MaterialTheme.colorScheme.primaryContainer
- else
- MaterialTheme.colorScheme.surface
- Icon(
- imageVector = if (following) NiaIcons.Check else NiaIcons.Add,
- contentDescription = null,
- modifier = Modifier
- .align(Alignment.BottomEnd)
- .size(18.dp)
- .drawBehind {
- drawCircle(
- color = backgroundColor,
- radius = 12.dp.toPx()
- )
- }
- )
- }
- Spacer(modifier = Modifier.height(4.dp))
- Text(
- text = author.name,
- style = MaterialTheme.typography.bodySmall,
- textAlign = TextAlign.Center,
- fontWeight = FontWeight.Medium,
- maxLines = 2,
- modifier = Modifier.fillMaxWidth()
- )
- }
-}
-
-@Preview
-@Composable
-fun AuthorCarouselPreview() {
- NiaTheme {
- Surface {
- AuthorsCarousel(
- authors = listOf(
- FollowableAuthor(
- Author(
- id = "1",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "3",
- name = "Android Dev3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- false
- )
- ),
- onAuthorClick = { _, _ -> },
- )
- }
- }
-}
-
-@Preview
-@Composable
-fun AuthorItemPreview() {
- NiaTheme {
- Surface {
- AuthorItem(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- following = true,
- onAuthorClick = { }
- )
- }
- }
-}
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 b4427b39b..1420a7e47 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
@@ -82,10 +82,8 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaOverl
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
import com.google.samples.apps.nowinandroid.core.model.data.previewNewsResources
import com.google.samples.apps.nowinandroid.core.model.data.previewTopics
import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews
@@ -108,7 +106,6 @@ internal fun ForYouRoute(
onboardingUiState = onboardingUiState,
feedState = feedState,
onTopicCheckedChanged = viewModel::updateTopicSelection,
- onAuthorCheckedChanged = viewModel::updateAuthorSelection,
saveFollowedTopics = viewModel::dismissOnboarding,
onNewsResourcesCheckedChanged = viewModel::updateNewsResourceSaved,
modifier = modifier
@@ -121,7 +118,6 @@ internal fun ForYouScreen(
onboardingUiState: OnboardingUiState,
feedState: NewsFeedUiState,
onTopicCheckedChanged: (String, Boolean) -> Unit,
- onAuthorCheckedChanged: (String, Boolean) -> Unit,
saveFollowedTopics: () -> Unit,
onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
@@ -162,7 +158,6 @@ internal fun ForYouScreen(
) {
onboarding(
onboardingUiState = onboardingUiState,
- onAuthorCheckedChanged = onAuthorCheckedChanged,
onTopicCheckedChanged = onTopicCheckedChanged,
saveFollowedTopics = saveFollowedTopics,
// Custom LayoutModifier to remove the enforced parent 16.dp contentPadding
@@ -224,7 +219,6 @@ internal fun ForYouScreen(
*/
private fun LazyGridScope.onboarding(
onboardingUiState: OnboardingUiState,
- onAuthorCheckedChanged: (String, Boolean) -> Unit,
onTopicCheckedChanged: (String, Boolean) -> Unit,
saveFollowedTopics: () -> Unit,
interestsItemModifier: Modifier = Modifier
@@ -253,13 +247,6 @@ private fun LazyGridScope.onboarding(
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium
)
- AuthorsCarousel(
- authors = onboardingUiState.authors,
- onAuthorClick = onAuthorCheckedChanged,
- modifier = Modifier
- .fillMaxWidth()
- .padding(vertical = 8.dp)
- )
TopicSelection(
onboardingUiState,
onTopicCheckedChanged,
@@ -414,7 +401,6 @@ fun ForYouScreenPopulatedFeed() {
}
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -436,7 +422,6 @@ fun ForYouScreenOfflinePopulatedFeed() {
}
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -453,7 +438,6 @@ fun ForYouScreenTopicSelection() {
isSyncing = false,
onboardingUiState = OnboardingUiState.Shown(
topics = previewTopics.map { FollowableTopic(it, false) },
- authors = previewAuthors.map { FollowableAuthor(it, false) }
),
feedState = NewsFeedUiState.Success(
feed = previewNewsResources.map {
@@ -461,7 +445,6 @@ fun ForYouScreenTopicSelection() {
}
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -479,7 +462,6 @@ fun ForYouScreenLoading() {
onboardingUiState = OnboardingUiState.Loading,
feedState = NewsFeedUiState.Loading,
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
@@ -501,7 +483,6 @@ fun ForYouScreenPopulatedAndLoading() {
}
),
onTopicCheckedChanged = { _, _ -> },
- onAuthorCheckedChanged = { _, _ -> },
saveFollowedTopics = {},
onNewsResourcesCheckedChanged = { _, _ -> }
)
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 928c62cfd..800cba0d3 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
@@ -22,7 +22,6 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserDataReposit
import com.google.samples.apps.nowinandroid.core.data.util.SyncStatusMonitor
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
import com.google.samples.apps.nowinandroid.core.domain.GetSaveableNewsResourcesUseCase
-import com.google.samples.apps.nowinandroid.core.domain.GetSortedFollowableAuthorsUseCase
import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -43,7 +42,6 @@ class ForYouViewModel @Inject constructor(
syncStatusMonitor: SyncStatusMonitor,
private val userDataRepository: UserDataRepository,
private val getSaveableNewsResources: GetSaveableNewsResourcesUseCase,
- getSortedFollowableAuthors: GetSortedFollowableAuthorsUseCase,
getFollowableTopics: GetFollowableTopicsUseCase
) : ViewModel() {
@@ -64,14 +62,12 @@ class ForYouViewModel @Inject constructor(
// show an empty news list to clearly demonstrate that their selections affect the
// news articles they will see.
if (!userData.shouldHideOnboarding &&
- userData.followedAuthors.isEmpty() &&
userData.followedTopics.isEmpty()
) {
flowOf(NewsFeedUiState.Success(emptyList()))
} else {
getSaveableNewsResources(
- filterTopicIds = userData.followedTopics,
- filterAuthorIds = userData.followedAuthors
+ filterTopicIds = userData.followedTopics
).mapToFeedState()
}
}
@@ -88,14 +84,10 @@ class ForYouViewModel @Inject constructor(
val onboardingUiState: StateFlow =
combine(
shouldShowOnboarding,
- getFollowableTopics(),
- getSortedFollowableAuthors()
- ) { shouldShowOnboarding, topics, authors ->
+ getFollowableTopics()
+ ) { shouldShowOnboarding, topics ->
if (shouldShowOnboarding) {
- OnboardingUiState.Shown(
- topics = topics,
- authors = authors
- )
+ OnboardingUiState.Shown(topics = topics)
} else {
OnboardingUiState.NotShown
}
@@ -112,12 +104,6 @@ class ForYouViewModel @Inject constructor(
}
}
- fun updateAuthorSelection(authorId: String, isChecked: Boolean) {
- viewModelScope.launch {
- userDataRepository.toggleFollowedAuthorId(authorId, isChecked)
- }
- }
-
fun updateNewsResourceSaved(newsResourceId: String, isChecked: Boolean) {
viewModelScope.launch {
userDataRepository.updateNewsResourceBookmark(newsResourceId, isChecked)
diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt
index 4760a4d13..73ecfed14 100644
--- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt
+++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt
@@ -16,7 +16,6 @@
package com.google.samples.apps.nowinandroid.feature.foryou
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
/**
@@ -39,16 +38,14 @@ sealed interface OnboardingUiState {
object NotShown : OnboardingUiState
/**
- * There is a onboarding state, with the given lists of topics and authors.
+ * There is a onboarding state, with the given lists of topics.
*/
data class Shown(
- val topics: List,
- val authors: List
+ val topics: List
) : OnboardingUiState {
/**
* True if the onboarding can be dismissed.
*/
- val isDismissable: Boolean get() =
- topics.any { it.isFollowed } || authors.any { it.isFollowed }
+ val isDismissable: Boolean get() = topics.any { it.isFollowed }
}
}
diff --git a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt
index 82117a47e..ad29aba60 100644
--- a/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt
+++ b/feature/foryou/src/test/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouViewModelTest.kt
@@ -18,15 +18,11 @@ package com.google.samples.apps.nowinandroid.feature.foryou
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
import com.google.samples.apps.nowinandroid.core.domain.GetSaveableNewsResourcesUseCase
-import com.google.samples.apps.nowinandroid.core.domain.GetSortedFollowableAuthorsUseCase
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
import com.google.samples.apps.nowinandroid.core.domain.model.SaveableNewsResource
-import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.model.data.NewsResourceType.Video
import com.google.samples.apps.nowinandroid.core.model.data.Topic
-import com.google.samples.apps.nowinandroid.core.testing.repository.TestAuthorsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
@@ -56,17 +52,12 @@ class ForYouViewModelTest {
private val networkMonitor = TestNetworkMonitor()
private val syncStatusMonitor = TestSyncStatusMonitor()
private val userDataRepository = TestUserDataRepository()
- private val authorsRepository = TestAuthorsRepository()
private val topicsRepository = TestTopicsRepository()
private val newsRepository = TestNewsRepository()
private val getSaveableNewsResourcesUseCase = GetSaveableNewsResourcesUseCase(
newsRepository = newsRepository,
userDataRepository = userDataRepository
)
- private val getSortedFollowableAuthors = GetSortedFollowableAuthorsUseCase(
- authorsRepository = authorsRepository,
- userDataRepository = userDataRepository
- )
private val getFollowableTopicsUseCase = GetFollowableTopicsUseCase(
topicsRepository = topicsRepository,
userDataRepository = userDataRepository
@@ -79,7 +70,6 @@ class ForYouViewModelTest {
syncStatusMonitor = syncStatusMonitor,
userDataRepository = userDataRepository,
getSaveableNewsResources = getSaveableNewsResourcesUseCase,
- getSortedFollowableAuthors = getSortedFollowableAuthors,
getFollowableTopics = getFollowableTopicsUseCase
)
}
@@ -126,24 +116,6 @@ class ForYouViewModelTest {
collectJob.cancel()
}
- @Test
- fun stateIsLoadingWhenFollowedAuthorsAreLoading() = runTest {
- val collectJob1 =
- launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
- val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
-
- authorsRepository.sendAuthors(sampleAuthors)
-
- assertEquals(
- OnboardingUiState.Loading,
- viewModel.onboardingUiState.value
- )
- assertEquals(NewsFeedUiState.Loading, viewModel.feedState.value)
-
- collectJob1.cancel()
- collectJob2.cancel()
- }
-
@Test
fun onboardingStateIsLoadingWhenTopicsAreLoading() = runTest {
val collectJob1 =
@@ -162,24 +134,6 @@ class ForYouViewModelTest {
collectJob2.cancel()
}
- @Test
- fun onboardingStateIsLoadingWhenAuthorsAreLoading() = runTest {
- val collectJob1 =
- launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
- val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
-
- userDataRepository.setFollowedAuthorIds(emptySet())
-
- assertEquals(
- OnboardingUiState.Loading,
- viewModel.onboardingUiState.value
- )
- assertEquals(NewsFeedUiState.Success(emptyList()), viewModel.feedState.value)
-
- collectJob1.cancel()
- collectJob2.cancel()
- }
-
@Test
fun onboardingIsShownWhenNewsResourcesAreLoading() = runTest {
val collectJob1 =
@@ -188,8 +142,6 @@ class ForYouViewModelTest {
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(emptySet())
assertEquals(
OnboardingUiState.Shown(
@@ -228,41 +180,6 @@ class ForYouViewModelTest {
isFollowed = false
),
),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
),
viewModel.onboardingUiState.value
)
@@ -278,15 +195,13 @@ class ForYouViewModelTest {
}
@Test
- fun onboardingIsShownAfterLoadingEmptyFollowedTopicsAndAuthors() = runTest {
+ fun onboardingIsShownAfterLoadingEmptyFollowedTopics() = runTest {
val collectJob1 =
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
topicsRepository.sendTopics(sampleTopics)
- authorsRepository.sendAuthors(sampleAuthors)
userDataRepository.setFollowedTopicIds(emptySet())
- userDataRepository.setFollowedAuthorIds(emptySet())
newsRepository.sendNewsResources(sampleNewsResources)
assertEquals(
@@ -326,41 +241,6 @@ class ForYouViewModelTest {
isFollowed = false
),
),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
),
viewModel.onboardingUiState.value
)
@@ -382,8 +262,6 @@ class ForYouViewModelTest {
launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(emptySet())
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(setOf("0", "1"))
viewModel.dismissOnboarding()
@@ -425,8 +303,6 @@ class ForYouViewModelTest {
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(emptySet())
newsRepository.sendNewsResources(sampleNewsResources)
assertEquals(
@@ -466,41 +342,6 @@ class ForYouViewModelTest {
isFollowed = false
)
),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
),
viewModel.onboardingUiState.value
)
@@ -550,232 +391,6 @@ class ForYouViewModelTest {
isFollowed = false
)
),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
- ),
- viewModel.onboardingUiState.value
- )
- assertEquals(
- NewsFeedUiState.Success(
- feed = listOf(
- SaveableNewsResource(
- newsResource = sampleNewsResources[1],
- isSaved = false
- ),
- SaveableNewsResource(
- newsResource = sampleNewsResources[2],
- isSaved = false
- )
- )
- ),
- viewModel.feedState.value
- )
-
- collectJob1.cancel()
- collectJob2.cancel()
- }
-
- @Test
- fun authorSelectionUpdatesAfterSelectingAuthor() = runTest {
- val collectJob1 =
- launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
- val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
-
- topicsRepository.sendTopics(sampleTopics)
- userDataRepository.setFollowedTopicIds(emptySet())
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(emptySet())
- newsRepository.sendNewsResources(sampleNewsResources)
-
- assertEquals(
- OnboardingUiState.Shown(
- topics = listOf(
- FollowableTopic(
- topic = Topic(
- id = "0",
- name = "Headlines",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- ),
- FollowableTopic(
- topic = Topic(
- id = "1",
- name = "UI",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- ),
- FollowableTopic(
- topic = Topic(
- id = "2",
- name = "Tools",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- )
- ),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
- ),
- viewModel.onboardingUiState.value
- )
- assertEquals(
- NewsFeedUiState.Success(
- feed = emptyList(),
- ),
- viewModel.feedState.value
- )
-
- viewModel.updateAuthorSelection("1", isChecked = true)
-
- assertEquals(
- OnboardingUiState.Shown(
- topics = listOf(
- FollowableTopic(
- topic = Topic(
- id = "0",
- name = "Headlines",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- ),
- FollowableTopic(
- topic = Topic(
- id = "1",
- name = "UI",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- ),
- FollowableTopic(
- topic = Topic(
- id = "2",
- name = "Tools",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- )
- ),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
),
viewModel.onboardingUiState.value
)
@@ -807,8 +422,6 @@ class ForYouViewModelTest {
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(emptySet())
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(emptySet())
newsRepository.sendNewsResources(sampleNewsResources)
viewModel.updateTopicSelection("1", isChecked = true)
viewModel.updateTopicSelection("1", isChecked = false)
@@ -851,142 +464,6 @@ class ForYouViewModelTest {
isFollowed = false
)
),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
- ),
- viewModel.onboardingUiState.value
- )
- assertEquals(
- NewsFeedUiState.Success(
- feed = emptyList()
- ),
- viewModel.feedState.value
- )
-
- collectJob1.cancel()
- collectJob2.cancel()
- }
-
- @Test
- fun authorSelectionUpdatesAfterUnselectingAuthor() = runTest {
- val collectJob1 =
- launch(UnconfinedTestDispatcher()) { viewModel.onboardingUiState.collect() }
- val collectJob2 = launch(UnconfinedTestDispatcher()) { viewModel.feedState.collect() }
-
- topicsRepository.sendTopics(sampleTopics)
- userDataRepository.setFollowedTopicIds(emptySet())
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(emptySet())
- newsRepository.sendNewsResources(sampleNewsResources)
- viewModel.updateAuthorSelection("1", isChecked = true)
- viewModel.updateAuthorSelection("1", isChecked = false)
-
- assertEquals(
-
- OnboardingUiState.Shown(
- topics = listOf(
- FollowableTopic(
- topic = Topic(
- id = "0",
- name = "Headlines",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- ),
- FollowableTopic(
- topic = Topic(
- id = "1",
- name = "UI",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- ),
- FollowableTopic(
- topic = Topic(
- id = "2",
- name = "Tools",
- shortDescription = "",
- longDescription = "long description",
- url = "URL",
- imageUrl = "image URL",
- ),
- isFollowed = false
- )
- ),
- authors = listOf(
- FollowableAuthor(
- author = Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- author = Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
- ),
),
viewModel.onboardingUiState.value
)
@@ -1009,8 +486,6 @@ class ForYouViewModelTest {
topicsRepository.sendTopics(sampleTopics)
userDataRepository.setFollowedTopicIds(setOf("1"))
- authorsRepository.sendAuthors(sampleAuthors)
- userDataRepository.setFollowedAuthorIds(setOf("1"))
userDataRepository.setShouldHideOnboarding(true)
newsRepository.sendNewsResources(sampleNewsResources)
viewModel.updateNewsResourceSaved("2", true)
@@ -1040,33 +515,6 @@ class ForYouViewModelTest {
}
}
-private val sampleAuthors = listOf(
- Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- )
-)
-
private val sampleTopics = listOf(
Topic(
id = "0",
@@ -1116,16 +564,6 @@ private val sampleNewsResources = listOf(
imageUrl = "image URL",
)
),
- authors = listOf(
- Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- )
- )
),
NewsResource(
id = "2",
@@ -1147,16 +585,6 @@ private val sampleNewsResources = listOf(
imageUrl = "image URL",
),
),
- authors = listOf(
- Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- )
- )
),
NewsResource(
id = "3",
@@ -1176,15 +604,5 @@ private val sampleNewsResources = listOf(
imageUrl = "image URL",
),
),
- authors = listOf(
- Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- )
- )
),
)
diff --git a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt
index 4b067734f..8bd47a8a8 100644
--- a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt
+++ b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt
@@ -25,12 +25,9 @@ import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
-import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.feature.interests.InterestsScreen
-import com.google.samples.apps.nowinandroid.feature.interests.InterestsTabState
import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState
import com.google.samples.apps.nowinandroid.feature.interests.R
import org.junit.Before
@@ -67,18 +64,7 @@ class InterestsScreenTest {
@Test
fun niaLoadingWheel_inTopics_whenScreenIsLoading_showLoading() {
composeTestRule.setContent {
- InterestsScreen(uiState = InterestsUiState.Loading, tabIndex = 0)
- }
-
- composeTestRule
- .onNodeWithContentDescription(interestsLoading)
- .assertExists()
- }
-
- @Test
- fun niaLoadingWheel_inAuthors_whenScreenIsLoading_showLoading() {
- composeTestRule.setContent {
- InterestsScreen(uiState = InterestsUiState.Loading, tabIndex = 1)
+ InterestsScreen(uiState = InterestsUiState.Loading)
}
composeTestRule
@@ -90,8 +76,7 @@ class InterestsScreenTest {
fun interestsWithTopics_whenTopicsFollowed_showFollowedAndUnfollowedTopicsWithInfo() {
composeTestRule.setContent {
InterestsScreen(
- uiState = InterestsUiState.Interests(topics = testTopics, authors = listOf()),
- tabIndex = 0
+ uiState = InterestsUiState.Interests(topics = testTopics)
)
}
@@ -112,55 +97,12 @@ class InterestsScreenTest {
composeTestRule
.onAllNodesWithContentDescription(interestsTopicCardFollowButton)
.assertCountEquals(numberOfUnfollowedTopics)
-
- composeTestRule
- .onAllNodesWithContentDescription(interestsTopicCardUnfollowButton)
- .assertCountEquals(testAuthors.filter { it.isFollowed }.size)
- }
-
- @Test
- fun interestsWithTopics_whenAuthorsFollowed_showFollowedAndUnfollowedTopicsWithInfo() {
- composeTestRule.setContent {
- InterestsScreen(
- uiState = InterestsUiState.Interests(topics = listOf(), authors = testAuthors),
- tabIndex = 1
- )
- }
-
- composeTestRule
- .onNodeWithText("Android Dev")
- .assertIsDisplayed()
- composeTestRule
- .onNodeWithText("Android Dev 2")
- .assertIsDisplayed()
- composeTestRule
- .onNodeWithText("Android Dev 3")
- .assertIsDisplayed()
-
- composeTestRule
- .onAllNodesWithContentDescription(interestsTopicCardFollowButton)
- .assertCountEquals(numberOfUnfollowedAuthors)
-
- composeTestRule
- .onAllNodesWithContentDescription(interestsTopicCardUnfollowButton)
- .assertCountEquals(testTopics.filter { it.isFollowed }.size)
}
@Test
fun topicsEmpty_whenDataIsEmptyOccurs_thenShowEmptyScreen() {
composeTestRule.setContent {
- InterestsScreen(uiState = InterestsUiState.Empty, tabIndex = 0)
- }
-
- composeTestRule
- .onNodeWithText(interestsEmptyHeader)
- .assertIsDisplayed()
- }
-
- @Test
- fun authorsEmpty_whenDataIsEmptyOccurs_thenShowEmptyScreen() {
- composeTestRule.setContent {
- InterestsScreen(uiState = InterestsUiState.Empty, tabIndex = 1)
+ InterestsScreen(uiState = InterestsUiState.Empty)
}
composeTestRule
@@ -169,18 +111,11 @@ class InterestsScreenTest {
}
@Composable
- private fun InterestsScreen(uiState: InterestsUiState, tabIndex: Int = 0) {
+ private fun InterestsScreen(uiState: InterestsUiState) {
InterestsScreen(
uiState = uiState,
- tabState = InterestsTabState(
- titles = listOf(R.string.interests_topics, R.string.interests_people),
- currentIndex = tabIndex
- ),
- followAuthor = { _, _ -> },
followTopic = { _, _ -> },
- navigateToAuthor = {},
- navigateToTopic = {},
- switchTab = {},
+ navigateToTopic = {}
)
}
}
@@ -229,41 +164,4 @@ private val testTopics = listOf(
)
)
-private val testAuthors = listOf(
- FollowableAuthor(
- Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
-)
-
private val numberOfUnfollowedTopics = testTopics.filter { !it.isFollowed }.size
-private val numberOfUnfollowedAuthors = testAuthors.filter { !it.isFollowed }.size
diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt
index ab50ea0b6..198cab4a8 100644
--- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt
+++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt
@@ -29,57 +29,34 @@ import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
-import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTab
-import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTabRow
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
-import com.google.samples.apps.nowinandroid.core.model.data.previewAuthors
import com.google.samples.apps.nowinandroid.core.model.data.previewTopics
import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews
-import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank
@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
internal fun InterestsRoute(
- navigateToAuthor: (String) -> Unit,
navigateToTopic: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: InterestsViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
- val tabState by viewModel.tabState.collectAsStateWithLifecycle()
InterestsScreen(
uiState = uiState,
- tabState = tabState,
followTopic = viewModel::followTopic,
- followAuthor = viewModel::followAuthor,
- navigateToAuthor = navigateToAuthor,
navigateToTopic = navigateToTopic,
- switchTab = viewModel::switchTab,
modifier = modifier
)
-
- TrackDisposableJank(tabState) { metricsHolder ->
- metricsHolder.state?.putState("Interests:TabState", "currentIndex:${tabState.currentIndex}")
-
- onDispose {
- metricsHolder.state?.removeState("Interests:TabState")
- }
- }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun InterestsScreen(
uiState: InterestsUiState,
- tabState: InterestsTabState,
- followAuthor: (String, Boolean) -> Unit,
followTopic: (String, Boolean) -> Unit,
- navigateToAuthor: (String) -> Unit,
navigateToTopic: (String) -> Unit,
- switchTab: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
@@ -93,56 +70,13 @@ internal fun InterestsScreen(
contentDesc = stringResource(id = R.string.interests_loading),
)
is InterestsUiState.Interests ->
- InterestsContent(
- tabState = tabState,
- switchTab = switchTab,
- uiState = uiState,
- navigateToTopic = navigateToTopic,
- followTopic = followTopic,
- navigateToAuthor = navigateToAuthor,
- followAuthor = followAuthor
- )
- is InterestsUiState.Empty -> InterestsEmptyScreen()
- }
- }
-}
-
-@Composable
-private fun InterestsContent(
- tabState: InterestsTabState,
- switchTab: (Int) -> Unit,
- uiState: InterestsUiState.Interests,
- navigateToTopic: (String) -> Unit,
- followTopic: (String, Boolean) -> Unit,
- navigateToAuthor: (String) -> Unit,
- followAuthor: (String, Boolean) -> Unit,
- modifier: Modifier = Modifier
-) {
- Column(modifier) {
- NiaTabRow(selectedTabIndex = tabState.currentIndex) {
- tabState.titles.forEachIndexed { index, titleId ->
- NiaTab(
- selected = index == tabState.currentIndex,
- onClick = { switchTab(index) },
- text = { Text(text = stringResource(id = titleId)) }
- )
- }
- }
- when (tabState.currentIndex) {
- 0 -> {
TopicsTabContent(
topics = uiState.topics,
onTopicClick = navigateToTopic,
onFollowButtonClick = followTopic,
+ modifier = modifier,
)
- }
- 1 -> {
- AuthorsTabContent(
- authors = uiState.authors,
- onAuthorClick = navigateToAuthor,
- onFollowButtonClick = followAuthor,
- )
- }
+ is InterestsUiState.Empty -> InterestsEmptyScreen()
}
}
}
@@ -159,18 +93,10 @@ fun InterestsScreenPopulated() {
NiaBackground {
InterestsScreen(
uiState = InterestsUiState.Interests(
- authors = previewAuthors.map { FollowableAuthor(it, false) },
topics = previewTopics.map { FollowableTopic(it, false) }
),
- tabState = InterestsTabState(
- titles = listOf(R.string.interests_topics, R.string.interests_people),
- currentIndex = 0
- ),
- followAuthor = { _, _ -> },
followTopic = { _, _ -> },
- navigateToAuthor = {},
navigateToTopic = {},
- switchTab = {}
)
}
}
@@ -183,15 +109,8 @@ fun InterestsScreenLoading() {
NiaBackground {
InterestsScreen(
uiState = InterestsUiState.Loading,
- tabState = InterestsTabState(
- titles = listOf(R.string.interests_topics, R.string.interests_people),
- currentIndex = 0
- ),
- followAuthor = { _, _ -> },
followTopic = { _, _ -> },
- navigateToAuthor = {},
navigateToTopic = {},
- switchTab = {},
)
}
}
@@ -204,15 +123,8 @@ fun InterestsScreenEmpty() {
NiaBackground {
InterestsScreen(
uiState = InterestsUiState.Empty,
- tabState = InterestsTabState(
- titles = listOf(R.string.interests_topics, R.string.interests_people),
- currentIndex = 0
- ),
- followAuthor = { _, _ -> },
followTopic = { _, _ -> },
- navigateToAuthor = {},
navigateToTopic = {},
- switchTab = {}
)
}
}
diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt
index 68afde05b..02d0c7bab 100644
--- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt
+++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt
@@ -20,9 +20,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
-import com.google.samples.apps.nowinandroid.core.domain.GetSortedFollowableAuthorsUseCase
import com.google.samples.apps.nowinandroid.core.domain.TopicSortField
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@@ -30,16 +28,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@HiltViewModel
class InterestsViewModel @Inject constructor(
val userDataRepository: UserDataRepository,
getFollowableTopics: GetFollowableTopicsUseCase,
- getSortedFollowableAuthors: GetSortedFollowableAuthorsUseCase
) : ViewModel() {
private val _tabState = MutableStateFlow(
@@ -50,35 +46,20 @@ class InterestsViewModel @Inject constructor(
)
val tabState: StateFlow = _tabState.asStateFlow()
- val uiState: StateFlow = combine(
- getSortedFollowableAuthors(),
- getFollowableTopics(sortBy = TopicSortField.NAME),
- InterestsUiState::Interests
- ).stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(5_000),
- initialValue = InterestsUiState.Loading
- )
+ val uiState: StateFlow =
+ getFollowableTopics(sortBy = TopicSortField.NAME).map(
+ InterestsUiState::Interests
+ ).stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = InterestsUiState.Loading
+ )
fun followTopic(followedTopicId: String, followed: Boolean) {
viewModelScope.launch {
userDataRepository.toggleFollowedTopicId(followedTopicId, followed)
}
}
-
- fun followAuthor(followedAuthorId: String, followed: Boolean) {
- viewModelScope.launch {
- userDataRepository.toggleFollowedAuthorId(followedAuthorId, followed)
- }
- }
-
- fun switchTab(newIndex: Int) {
- if (newIndex != tabState.value.currentIndex) {
- _tabState.update {
- it.copy(currentIndex = newIndex)
- }
- }
- }
}
data class InterestsTabState(
@@ -90,7 +71,6 @@ sealed interface InterestsUiState {
object Loading : InterestsUiState
data class Interests(
- val authors: List,
val topics: List
) : InterestsUiState
diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt
index aeab2d3e4..0cff8c82c 100644
--- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt
+++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt
@@ -23,13 +23,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
@Composable
@@ -64,35 +61,3 @@ fun TopicsTabContent(
}
}
}
-
-@Composable
-fun AuthorsTabContent(
- authors: List,
- onAuthorClick: (String) -> Unit,
- onFollowButtonClick: (String, Boolean) -> Unit,
- modifier: Modifier = Modifier
-) {
- LazyColumn(
- modifier = modifier
- .padding(horizontal = 16.dp)
- .testTag("interests:people"),
- contentPadding = PaddingValues(top = 8.dp)
- ) {
- authors.forEach { followableAuthor ->
- item {
- InterestsItem(
- name = followableAuthor.author.name,
- following = followableAuthor.isFollowed,
- topicImageUrl = followableAuthor.author.imageUrl,
- onClick = { onAuthorClick(followableAuthor.author.id) },
- onFollowButtonClick = { onFollowButtonClick(followableAuthor.author.id, it) },
- iconModifier = Modifier.clip(CircleShape)
- )
- }
- }
-
- item {
- Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
- }
- }
-}
diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt
index f0604e46f..47facde82 100644
--- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt
+++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt
@@ -32,7 +32,6 @@ fun NavController.navigateToInterestsGraph(navOptions: NavOptions? = null) {
fun NavGraphBuilder.interestsGraph(
navigateToTopic: (String) -> Unit,
- navigateToAuthor: (String) -> Unit,
nestedGraphs: NavGraphBuilder.() -> Unit
) {
navigation(
@@ -42,7 +41,6 @@ fun NavGraphBuilder.interestsGraph(
composable(route = interestsRoute) {
InterestsRoute(
navigateToTopic = navigateToTopic,
- navigateToAuthor = navigateToAuthor,
)
}
nestedGraphs()
diff --git a/feature/interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt b/feature/interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt
index a6e60980e..c84a8ad45 100644
--- a/feature/interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt
+++ b/feature/interests/src/test/java/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt
@@ -17,12 +17,8 @@
package com.google.samples.apps.nowinandroid.interests
import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase
-import com.google.samples.apps.nowinandroid.core.domain.GetSortedFollowableAuthorsUseCase
-import com.google.samples.apps.nowinandroid.core.domain.model.FollowableAuthor
import com.google.samples.apps.nowinandroid.core.domain.model.FollowableTopic
-import com.google.samples.apps.nowinandroid.core.model.data.Author
import com.google.samples.apps.nowinandroid.core.model.data.Topic
-import com.google.samples.apps.nowinandroid.core.testing.repository.TestAuthorsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule
@@ -47,17 +43,11 @@ class InterestsViewModelTest {
val mainDispatcherRule = MainDispatcherRule()
private val userDataRepository = TestUserDataRepository()
- private val authorsRepository = TestAuthorsRepository()
private val topicsRepository = TestTopicsRepository()
private val getFollowableTopicsUseCase = GetFollowableTopicsUseCase(
topicsRepository = topicsRepository,
userDataRepository = userDataRepository
)
- private val getSortedFollowableAuthors =
- GetSortedFollowableAuthorsUseCase(
- authorsRepository = authorsRepository,
- userDataRepository = userDataRepository
- )
private lateinit var viewModel: InterestsViewModel
@Before
@@ -65,7 +55,6 @@ class InterestsViewModelTest {
viewModel = InterestsViewModel(
userDataRepository = userDataRepository,
getFollowableTopics = getFollowableTopicsUseCase,
- getSortedFollowableAuthors = getSortedFollowableAuthors
)
}
@@ -78,31 +67,17 @@ class InterestsViewModelTest {
fun uiState_whenFollowedTopicsAreLoading_thenShowLoading() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
- userDataRepository.setFollowedAuthorIds(setOf("1"))
userDataRepository.setFollowedTopicIds(emptySet())
assertEquals(InterestsUiState.Loading, viewModel.uiState.value)
collectJob.cancel()
}
- @Test
- fun uiState_whenFollowedAuthorsAreLoading_thenShowLoading() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
-
- userDataRepository.setFollowedAuthorIds(emptySet())
- userDataRepository.setFollowedTopicIds(setOf("1"))
- assertEquals(InterestsUiState.Loading, viewModel.uiState.value)
-
- collectJob.cancel()
- }
-
@Test
fun uiState_whenFollowingNewTopic_thenShowUpdatedTopics() = runTest {
val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
val toggleTopicId = testOutputTopics[1].topic.id
- authorsRepository.sendAuthors(emptyList())
- userDataRepository.setFollowedAuthorIds(emptySet())
topicsRepository.sendTopics(testInputTopics.map { it.topic })
userDataRepository.setFollowedTopicIds(setOf(testInputTopics[0].topic.id))
@@ -118,29 +93,7 @@ class InterestsViewModelTest {
)
assertEquals(
- InterestsUiState.Interests(topics = testOutputTopics, authors = emptyList()),
- viewModel.uiState.value
- )
-
- collectJob.cancel()
- }
-
- @Test
- fun uiState_whenFollowingNewAuthor_thenShowUpdatedAuthors() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
-
- authorsRepository.sendAuthors(testInputAuthors.map { it.author })
- userDataRepository.setFollowedAuthorIds(setOf(testInputAuthors[0].author.id))
- topicsRepository.sendTopics(listOf())
- userDataRepository.setFollowedTopicIds(setOf())
-
- viewModel.followAuthor(
- followedAuthorId = testInputAuthors[1].author.id,
- followed = true
- )
-
- assertEquals(
- InterestsUiState.Interests(topics = emptyList(), authors = testOutputAuthors),
+ InterestsUiState.Interests(topics = testOutputTopics),
viewModel.uiState.value
)
@@ -153,8 +106,6 @@ class InterestsViewModelTest {
val toggleTopicId = testOutputTopics[1].topic.id
- authorsRepository.sendAuthors(emptyList())
- userDataRepository.setFollowedAuthorIds(emptySet())
topicsRepository.sendTopics(testOutputTopics.map { it.topic })
userDataRepository.setFollowedTopicIds(
setOf(testOutputTopics[0].topic.id, testOutputTopics[1].topic.id)
@@ -172,31 +123,7 @@ class InterestsViewModelTest {
)
assertEquals(
- InterestsUiState.Interests(topics = testInputTopics, authors = emptyList()),
- viewModel.uiState.value
- )
-
- collectJob.cancel()
- }
-
- @Test
- fun uiState_whenUnfollowingAuthors_thenShowUpdatedAuthors() = runTest {
- val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }
-
- authorsRepository.sendAuthors(testOutputAuthors.map { it.author })
- userDataRepository.setFollowedAuthorIds(
- setOf(testOutputAuthors[0].author.id, testOutputAuthors[1].author.id)
- )
- topicsRepository.sendTopics(listOf())
- userDataRepository.setFollowedTopicIds(setOf())
-
- viewModel.followAuthor(
- followedAuthorId = testOutputAuthors[1].author.id,
- followed = false
- )
-
- assertEquals(
- InterestsUiState.Interests(topics = emptyList(), authors = testInputAuthors),
+ InterestsUiState.Interests(topics = testInputTopics),
viewModel.uiState.value
)
@@ -212,78 +139,6 @@ private const val TOPIC_LONG_DESC = "At vero eos et accusamus et iusto odio dign
private const val TOPIC_URL = "URL"
private const val TOPIC_IMAGE_URL = "Image URL"
-private val testInputAuthors = listOf(
- FollowableAuthor(
- Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- ),
- FollowableAuthor(
- Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
-)
-
-private val testOutputAuthors = listOf(
- FollowableAuthor(
- Author(
- id = "0",
- name = "Android Dev",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "1",
- name = "Android Dev 2",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = true
- ),
- FollowableAuthor(
- Author(
- id = "2",
- name = "Android Dev 3",
- imageUrl = "",
- twitter = "",
- mediumPage = "",
- bio = "",
- ),
- isFollowed = false
- )
-)
-
private val testInputTopics = listOf(
FollowableTopic(
Topic(
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 0f20a5e5e..9bef39a36 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
@@ -209,7 +209,6 @@ private val sampleNewsResources = listOf(
url = "",
imageUrl = ""
)
- ),
- authors = emptyList()
+ )
)
)
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 904e03110..17096ca88 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
@@ -136,7 +136,6 @@ private fun newsUiState(
): Flow {
// Observe news
val news: Flow> = getSaveableNewsResources(
- filterAuthorIds = emptySet(),
filterTopicIds = setOf(element = topicId),
)
diff --git a/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt
index 3dc2551b6..32768c4d7 100644
--- a/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt
+++ b/feature/topic/src/test/java/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt
@@ -267,6 +267,5 @@ private val sampleNewsResources = listOf(
imageUrl = "image URL",
)
),
- authors = emptyList()
)
)
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 295cedf36..809efd27d 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -46,7 +46,6 @@ include(":core:model")
include(":core:network")
include(":core:ui")
include(":core:testing")
-include(":feature:author")
include(":feature:foryou")
include(":feature:interests")
include(":feature:bookmarks")
diff --git a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt
index ffb425d1e..1a7dfb1be 100644
--- a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt
+++ b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt
@@ -25,7 +25,6 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkerParameters
import com.google.samples.apps.nowinandroid.core.data.Synchronizer
-import com.google.samples.apps.nowinandroid.core.data.repository.AuthorsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.datastore.ChangeListVersions
@@ -52,7 +51,6 @@ class SyncWorker @AssistedInject constructor(
private val niaPreferences: NiaPreferencesDataSource,
private val topicRepository: TopicsRepository,
private val newsRepository: NewsRepository,
- private val authorsRepository: AuthorsRepository,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : CoroutineWorker(appContext, workerParams), Synchronizer {
@@ -64,7 +62,6 @@ class SyncWorker @AssistedInject constructor(
// First sync the repositories in parallel
val syncedSuccessfully = awaitAll(
async { topicRepository.sync() },
- async { authorsRepository.sync() },
async { newsRepository.sync() },
).all { it }