Skip to content

Commit

Permalink
Implement list/detail view for interests and topics
Browse files Browse the repository at this point in the history
Change-Id: Idc1bcb00882d9e7b60baa620c05d07e6721a4984
  • Loading branch information
mmoczkowski committed May 26, 2023
1 parent 2939446 commit 6d77b0c
Show file tree
Hide file tree
Showing 34 changed files with 686 additions and 1,128 deletions.
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ dependencies {
implementation(project(":feature:interests"))
implementation(project(":feature:foryou"))
implementation(project(":feature:bookmarks"))
implementation(project(":feature:topic"))
implementation(project(":feature:search"))
implementation(project(":feature:settings"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,22 +242,4 @@ class NavigationTest {
onNodeWithText(forYou).assertExists()
}
}

@Test
fun navigationBar_multipleBackStackInterests() {
composeTestRule.apply {
onNodeWithText(interests).performClick()
// TODO: Grab string from fake data
onNodeWithText("Android Studio & Tools").performClick()

// Switch tab
onNodeWithText(forYou).performClick()

// Come back to Interests
onNodeWithText(interests).performClick()

// Verify we're not in the list of interests
onNodeWithText("Android Auto").assertDoesNotExist() // TODO: Grab string from fake data
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class NiaAppStateTest {
}

// Update currentDestination whenever it changes
currentDestination = state.currentDestination?.route
currentDestination = state.currentBackStackEntry?.destination?.route

// Navigate to destination b once
LaunchedEffect(Unit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@

package com.google.samples.apps.nowinandroid.navigation

import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouNavigationRoute
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouScreen
import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsGraph
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests
import com.google.samples.apps.nowinandroid.feature.search.navigation.searchScreen
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS
import com.google.samples.apps.nowinandroid.ui.NiaAppState

/**
Expand All @@ -39,33 +39,32 @@ import com.google.samples.apps.nowinandroid.ui.NiaAppState
@Composable
fun NiaNavHost(
appState: NiaAppState,
snackbarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
startDestination: String = forYouNavigationRoute,
) {
val navController = appState.navController
val interestsScrollState = rememberLazyGridState()
NavHost(
navController = navController,
startDestination = startDestination,
modifier = modifier,
) {
// TODO: handle topic clicks from each top level destination
forYouScreen(onTopicClick = {})
bookmarksScreen(onTopicClick = {})
forYouScreen(onTopicClick = navController::navigateToInterests)
bookmarksScreen(
onTopicClick = navController::navigateToInterests,
snackbarHostState = snackbarHostState,
)
searchScreen(
onBackClick = navController::popBackStack,
onInterestsClick = { appState.navigateToTopLevelDestination(INTERESTS) },
onTopicClick = navController::navigateToTopic,
onInterestsClick = navController::navigateToInterests,
onTopicClick = navController::navigateToInterests,
)
interestsGraph(
onTopicClick = { topicId ->
navController.navigateToTopic(topicId)
},
nestedGraphs = {
topicScreen(
onBackClick = navController::popBackStack,
onTopicClick = {},
)
},
listState = interestsScrollState,
shouldShowTwoPane = appState.shouldShowTwoPane,
onTopicClick = navController::navigateToInterests,
onBackClick = navController::navigateToInterests,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.ImageVec
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColors
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.feature.search.navigation.navigateToSearch
import com.google.samples.apps.nowinandroid.feature.settings.SettingsDialog
import com.google.samples.apps.nowinandroid.navigation.NiaNavHost
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
Expand All @@ -94,15 +95,13 @@ fun NiaApp(
userNewsResourceRepository = userNewsResourceRepository,
),
) {
val shouldShowGradientBackground =
appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU
var showSettingsDialog by rememberSaveable {
mutableStateOf(false)
}

NiaBackground {
NiaGradientBackground(
gradientColors = if (shouldShowGradientBackground) {
gradientColors = if (appState.shouldShowGradientBackground) {
LocalGradientColors.current
} else {
GradientColors()
Expand Down Expand Up @@ -144,7 +143,7 @@ fun NiaApp(
destinations = appState.topLevelDestinations,
destinationsWithUnreadResources = unreadDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination,
currentDestination = appState.currentBackStackEntry?.destination,
modifier = Modifier.testTag("NiaBottomBar"),
)
}
Expand All @@ -165,7 +164,7 @@ fun NiaApp(
NiaNavRail(
destinations = appState.topLevelDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination,
currentDestination = appState.currentBackStackEntry?.destination,
modifier = Modifier
.testTag("NiaNavRail")
.safeDrawingPadding(),
Expand All @@ -190,11 +189,11 @@ fun NiaApp(
containerColor = Color.Transparent,
),
onActionClick = { showSettingsDialog = true },
onNavigationClick = { appState.navigateToSearch() },
onNavigationClick = { appState.navController.navigateToSearch() },
)
}

NiaNavHost(appState)
NiaNavHost(appState, snackbarHostState)
}

// TODO: We may want to add padding or spacer when the snackbar is shown so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

package com.google.samples.apps.nowinandroid.ui

import android.os.Bundle
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
Expand All @@ -38,8 +39,8 @@ import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigat
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouNavigationRoute
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.navigateToForYou
import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsRoute
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterestsGraph
import com.google.samples.apps.nowinandroid.feature.search.navigation.navigateToSearch
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests
import com.google.samples.apps.nowinandroid.feature.interests.navigation.topicIdArg
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.BOOKMARKS
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.FOR_YOU
Expand Down Expand Up @@ -85,24 +86,34 @@ class NiaAppState(
networkMonitor: NetworkMonitor,
userNewsResourceRepository: UserNewsResourceRepository,
) {
val currentDestination: NavDestination?
val currentBackStackEntry: NavBackStackEntry?
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
.currentBackStackEntryAsState().value

val currentTopLevelDestination: TopLevelDestination?
@Composable get() = when (currentDestination?.route) {
forYouNavigationRoute -> FOR_YOU
bookmarksRoute -> BOOKMARKS
interestsRoute -> INTERESTS
else -> null
@Composable get() {
val route: String? = currentBackStackEntry?.destination?.route
val arguments: Bundle? = currentBackStackEntry?.arguments
return when {
route == forYouNavigationRoute -> FOR_YOU
route == bookmarksRoute -> BOOKMARKS
route == interestsRoute &&
(arguments?.getString(topicIdArg) == null || shouldShowTwoPane) -> INTERESTS
else -> null
}
}
val shouldShowGradientBackground: Boolean
@Composable get() = currentBackStackEntry?.destination?.route == forYouNavigationRoute

val shouldShowBottomBar: Boolean
get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact

val shouldShowNavRail: Boolean
get() = !shouldShowBottomBar

val shouldShowTwoPane: Boolean
get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded

val isOffline = networkMonitor.isOnline
.map(Boolean::not)
.stateIn(
Expand Down Expand Up @@ -159,14 +170,10 @@ class NiaAppState(
when (topLevelDestination) {
FOR_YOU -> navController.navigateToForYou(topLevelNavOptions)
BOOKMARKS -> navController.navigateToBookmarks(topLevelNavOptions)
INTERESTS -> navController.navigateToInterestsGraph(topLevelNavOptions)
INTERESTS -> navController.navigateToInterests(navOptions = topLevelNavOptions)
}
}
}

fun navigateToSearch() {
navController.navigateToSearch()
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks

import androidx.activity.ComponentActivity
import androidx.compose.material3.SnackbarHostState
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.filter
Expand Down Expand Up @@ -50,6 +51,7 @@ class BookmarksScreenTest {
composeTestRule.setContent {
BookmarksScreen(
feedState = NewsFeedUiState.Loading,
snackbarHostState = SnackbarHostState(),
removeFromBookmarks = {},
onTopicClick = {},
onNewsResourceViewed = {},
Expand All @@ -70,6 +72,7 @@ class BookmarksScreenTest {
feedState = NewsFeedUiState.Success(
userNewsResourcesTestData.take(2),
),
snackbarHostState = SnackbarHostState(),
removeFromBookmarks = {},
onTopicClick = {},
onNewsResourceViewed = {},
Expand Down Expand Up @@ -110,6 +113,7 @@ class BookmarksScreenTest {
feedState = NewsFeedUiState.Success(
userNewsResourcesTestData.take(2),
),
snackbarHostState = SnackbarHostState(),
removeFromBookmarks = { newsResourceId ->
assertEquals(userNewsResourcesTestData[0].id, newsResourceId)
removeFromBookmarksCalled = true
Expand Down Expand Up @@ -144,6 +148,7 @@ class BookmarksScreenTest {
composeTestRule.setContent {
BookmarksScreen(
feedState = NewsFeedUiState.Success(emptyList()),
snackbarHostState = SnackbarHostState(),
removeFromBookmarks = {},
onTopicClick = {},
onNewsResourceViewed = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,15 @@ import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration.Short
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult.ActionPerformed
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
Expand Down Expand Up @@ -79,12 +75,14 @@ import com.google.samples.apps.nowinandroid.core.ui.newsFeed
@Composable
internal fun BookmarksRoute(
onTopicClick: (String) -> Unit,
snackbarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
viewModel: BookmarksViewModel = hiltViewModel(),
) {
val feedState by viewModel.feedUiState.collectAsStateWithLifecycle()
BookmarksScreen(
feedState = feedState,
snackbarHostState = snackbarHostState,
removeFromBookmarks = viewModel::removeFromSavedResources,
onNewsResourceViewed = { viewModel.setNewsResourceViewed(it, true) },
onTopicClick = onTopicClick,
Expand All @@ -98,11 +96,11 @@ internal fun BookmarksRoute(
/**
* Displays the user's bookmarked articles. Includes support for loading and empty states.
*/
@OptIn(ExperimentalMaterial3Api::class)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@Composable
internal fun BookmarksScreen(
feedState: NewsFeedUiState,
snackbarHostState: SnackbarHostState,
removeFromBookmarks: (String) -> Unit,
onNewsResourceViewed: (String) -> Unit,
onTopicClick: (String) -> Unit,
Expand All @@ -113,7 +111,6 @@ internal fun BookmarksScreen(
) {
val bookmarkRemovedMessage = stringResource(id = R.string.bookmark_removed)
val undoText = stringResource(id = R.string.undo)
val snackbarHostState = remember { SnackbarHostState() }

LaunchedEffect(shouldDisplayUndoBookmark) {
if (shouldDisplayUndoBookmark) {
Expand All @@ -140,20 +137,19 @@ internal fun BookmarksScreen(
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}

Scaffold(snackbarHost = { SnackbarHost(hostState = snackbarHostState) }) {
Box(
modifier = Modifier.padding(it).fillMaxSize(),
) {
when (feedState) {
Loading -> LoadingState(modifier)
is Success -> if (feedState.feed.isNotEmpty()) {
BookmarksGrid(feedState, removeFromBookmarks, onNewsResourceViewed, onTopicClick, modifier)
} else {
EmptyState(modifier)
}
Box(
modifier = Modifier.fillMaxSize(),
) {
when (feedState) {
Loading -> LoadingState(modifier)
is Success -> if (feedState.feed.isNotEmpty()) {
BookmarksGrid(feedState, removeFromBookmarks, onNewsResourceViewed, onTopicClick, modifier)
} else {
EmptyState(modifier)
}
}
}

TrackScreenViewEvent(screenName = "Saved")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.samples.apps.nowinandroid.feature.bookmarks.navigation

import androidx.compose.material3.SnackbarHostState
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
Expand All @@ -28,8 +29,11 @@ fun NavController.navigateToBookmarks(navOptions: NavOptions? = null) {
this.navigate(bookmarksRoute, navOptions)
}

fun NavGraphBuilder.bookmarksScreen(onTopicClick: (String) -> Unit) {
fun NavGraphBuilder.bookmarksScreen(
onTopicClick: (String) -> Unit,
snackbarHostState: SnackbarHostState,
) {
composable(route = bookmarksRoute) {
BookmarksRoute(onTopicClick)
BookmarksRoute(onTopicClick, snackbarHostState)
}
}
Loading

0 comments on commit 6d77b0c

Please sign in to comment.