Move extensions and navigation related things to navigation module.

Change-Id: Icd75f90868dccb6b443e72765d4afc9a2ceb9778
pull/2046/head
Jaehwa Noh 2 weeks ago
parent 6ac8d138c8
commit 44dbd93b5b

@ -74,18 +74,16 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColo
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.toEntries
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksEntry
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouEntry
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsNavKey
import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsEntry
import com.google.samples.apps.nowinandroid.feature.search.navigation.SearchNavKey
import com.google.samples.apps.nowinandroid.feature.search.navigation.searchEntry
import com.google.samples.apps.nowinandroid.feature.settings.SettingsDialog
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicEntry
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_NAV_ITEMS
import com.google.samples.apps.nowinandroid.navigation.navigateToTopic
import com.google.samples.apps.nowinandroid.navigation.bookmarks.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.navigation.bookmarks.bookmarksEntry
import com.google.samples.apps.nowinandroid.navigation.foryou.forYouEntry
import com.google.samples.apps.nowinandroid.navigation.interests.interestsEntry
import com.google.samples.apps.nowinandroid.navigation.navigateToSearch
import com.google.samples.apps.nowinandroid.navigation.search.searchEntry
import com.google.samples.apps.nowinandroid.navigation.shouldShowGradiantBackground
import com.google.samples.apps.nowinandroid.navigation.topic.topicEntry
import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR
@Composable
@ -94,7 +92,7 @@ fun NiaApp(
modifier: Modifier = Modifier,
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
val shouldShowGradientBackground = appState.navigationState.currentTopLevelKey == ForYouNavKey
val shouldShowGradientBackground = appState.navigationState.shouldShowGradiantBackground()
var showSettingsDialog by rememberSaveable { mutableStateOf(false) }
NiaBackground(modifier = modifier) {
@ -243,7 +241,7 @@ internal fun NiaApp(
containerColor = Color.Transparent,
),
onActionClick = { onTopAppBarActionClick() },
onNavigationClick = { navigator.navigate(SearchNavKey) },
onNavigationClick = { navigator.navigateToSearch() },
)
}
@ -260,18 +258,11 @@ internal fun NiaApp(
val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>()
val entryProvider = entryProvider {
forYouEntry(onTopicClick = navigator::navigateToTopic)
bookmarksEntry(onTopicClick = navigator::navigateToTopic)
interestsEntry(onTopicClick = navigator::navigateToTopic)
topicEntry(
onBackClick = { navigator.goBack() },
onTopicClick = navigator::navigateToTopic,
)
searchEntry(
onBackClick = { navigator.goBack() },
onInterestsClick = { navigator.navigate(InterestsNavKey()) },
onTopicClick = navigator::navigateToTopic,
)
forYouEntry(navigator = navigator)
bookmarksEntry(navigator = navigator)
interestsEntry(navigator = navigator)
topicEntry(navigator = navigator)
searchEntry(navigator = navigator)
}
NavDisplay(

@ -25,11 +25,9 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.navigation.NavigationState
import com.google.samples.apps.nowinandroid.core.navigation.rememberNavigationState
import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_NAV_ITEMS
import com.google.samples.apps.nowinandroid.navigation.getNavigationState
import com.google.samples.apps.nowinandroid.navigation.getTopLevelNavKeysWithUnreadResources
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -45,7 +43,7 @@ fun rememberNiaAppState(
timeZoneMonitor: TimeZoneMonitor,
coroutineScope: CoroutineScope = rememberCoroutineScope(),
): NiaAppState {
val navigationState = rememberNavigationState(ForYouNavKey, TOP_LEVEL_NAV_ITEMS.keys)
val navigationState = getNavigationState()
NavigationTrackingSideEffect(navigationState)
@ -88,10 +86,7 @@ class NiaAppState(
val topLevelNavKeysWithUnreadResources: StateFlow<Set<NavKey>> =
userNewsResourceRepository.observeAllForFollowedTopics()
.combine(userNewsResourceRepository.observeAllBookmarked()) { forYouNewsResources, bookmarkedNewsResources ->
setOfNotNull(
ForYouNavKey.takeIf { forYouNewsResources.any { !it.hasBeenViewed } },
BookmarksNavKey.takeIf { bookmarkedNewsResources.any { !it.hasBeenViewed } },
)
getTopLevelNavKeysWithUnreadResources(forYouNewsResources, bookmarkedNewsResources)
}
.stateIn(
coroutineScope,

@ -67,7 +67,7 @@ import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.navigation.bookmarks.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest

@ -40,7 +40,7 @@ import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.navigation.bookmarks.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest

@ -76,7 +76,7 @@ import com.google.samples.apps.nowinandroid.core.ui.UserNewsResourcePreviewParam
import com.google.samples.apps.nowinandroid.core.ui.newsFeed
@Composable
internal fun BookmarksScreen(
fun BookmarksScreen(
onTopicClick: (String) -> Unit,
onShowSnackbar: suspend (String, String?) -> Boolean,
modifier: Modifier = Modifier,

@ -32,7 +32,6 @@ import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPer
import com.google.samples.apps.nowinandroid.core.testing.data.followableTopicTestData
import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.feature.foryou.impl.R
import org.junit.Rule
import org.junit.Test
@ -154,12 +153,12 @@ class ForYouScreenTest {
ForYouScreen(
isSyncing = false,
onboardingUiState =
OnboardingUiState.Shown(
// Follow one topic
topics = followableTopicTestData.mapIndexed { index, testTopic ->
testTopic.copy(isFollowed = index == 1)
},
),
OnboardingUiState.Shown(
// Follow one topic
topics = followableTopicTestData.mapIndexed { index, testTopic ->
testTopic.copy(isFollowed = index == 1)
},
),
feedState = NewsFeedUiState.Success(
feed = emptyList(),
),
@ -201,9 +200,9 @@ class ForYouScreenTest {
ForYouScreen(
isSyncing = false,
onboardingUiState =
OnboardingUiState.Shown(
topics = followableTopicTestData,
),
OnboardingUiState.Shown(
topics = followableTopicTestData,
),
feedState = NewsFeedUiState.Loading,
deepLinkedUserNewsResource = null,
onTopicCheckedChanged = { _, _ -> },

@ -96,7 +96,7 @@ import com.google.samples.apps.nowinandroid.core.ui.newsFeed
import com.google.samples.apps.nowinandroid.feature.search.R as searchR
@Composable
internal fun SearchScreen(
fun SearchScreen(
onBackClick: () -> Unit,
onInterestsClick: () -> Unit,
onTopicClick: (String) -> Unit,

@ -17,10 +17,15 @@
package com.google.samples.apps.nowinandroid.navigation
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicNavKey
import com.google.samples.apps.nowinandroid.feature.search.navigation.SearchNavKey
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicNavKey
fun Navigator.navigateToTopic(
topicId: String,
) {
navigate(TopicNavKey(topicId))
}
fun Navigator.navigateToSearch() {
navigate(SearchNavKey)
}

@ -1,5 +1,5 @@
/*
* Copyright 2025 The Android Open Source Project
* Copyright 2026 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.
@ -18,7 +18,6 @@ package com.google.samples.apps.nowinandroid.navigation
import androidx.annotation.StringRes
import androidx.compose.ui.graphics.vector.ImageVector
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouNavKey
@ -49,7 +48,7 @@ val FOR_YOU = TopLevelNavItem(
selectedIcon = NiaIcons.Upcoming,
unselectedIcon = NiaIcons.UpcomingBorder,
iconTextId = forYouR.string.feature_foryou_api_title,
titleTextId = R.string.app_name,
titleTextId = forYouR.string.feature_foryou_api_app_name,
)
val BOOKMARKS = TopLevelNavItem(

@ -0,0 +1,39 @@
/*
* Copyright 2026 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.navigation
import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.navigation.NavigationState
import com.google.samples.apps.nowinandroid.core.navigation.rememberNavigationState
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouNavKey
fun getTopLevelNavKeysWithUnreadResources(
forYouNewsResources: List<UserNewsResource>,
bookmarkedNewsResources: List<UserNewsResource>,
): Set<NavKey> = setOfNotNull(
ForYouNavKey.takeIf { forYouNewsResources.any { !it.hasBeenViewed } },
BookmarksNavKey.takeIf { bookmarkedNewsResources.any { !it.hasBeenViewed } },
)
@Composable
fun getNavigationState(): NavigationState =
rememberNavigationState(startKey = ForYouNavKey, topLevelKeys = TOP_LEVEL_NAV_ITEMS.keys)
fun NavigationState.shouldShowGradiantBackground(): Boolean = currentTopLevelKey == ForYouNavKey

@ -1,5 +1,5 @@
/*
* Copyright 2025 The Android Open Source Project
* Copyright 2026 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.bookmarks.navigation
package com.google.samples.apps.nowinandroid.navigation.bookmarks
import androidx.compose.material3.SnackbarDuration.Short
import androidx.compose.material3.SnackbarHostState
@ -22,15 +22,16 @@ import androidx.compose.material3.SnackbarResult.ActionPerformed
import androidx.compose.runtime.compositionLocalOf
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksScreen
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.navigation.navigateToTopic
fun EntryProviderScope<NavKey>.bookmarksEntry(
onTopicClick: (String) -> Unit,
) {
fun EntryProviderScope<NavKey>.bookmarksEntry(navigator: Navigator) {
entry<BookmarksNavKey> {
val snackbarHostState = LocalSnackbarHostState.current
BookmarksScreen(
onTopicClick = onTopicClick,
onTopicClick = navigator::navigateToTopic,
onShowSnackbar = { message, action ->
snackbarHostState.showSnackbar(
message = message,

@ -1,5 +1,5 @@
/*
* Copyright 2025 The Android Open Source Project
* Copyright 2026 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.
@ -14,16 +14,19 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.foryou.navigation
package com.google.samples.apps.nowinandroid.navigation.foryou
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.foryou.ForYouScreen
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.navigation.navigateToTopic
fun EntryProviderScope<NavKey>.forYouEntry(onTopicClick: (String) -> Unit) {
fun EntryProviderScope<NavKey>.forYouEntry(navigator: Navigator) {
entry<ForYouNavKey> {
ForYouScreen(
onTopicClick = onTopicClick,
onTopicClick = navigator::navigateToTopic,
)
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2025 The Android Open Source Project
* Copyright 2026 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.
@ -14,19 +14,22 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.interests.navigation
package com.google.samples.apps.nowinandroid.navigation.interests
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.interests.InterestsDetailPlaceholder
import com.google.samples.apps.nowinandroid.feature.interests.InterestsScreen
import com.google.samples.apps.nowinandroid.feature.interests.InterestsViewModel
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsNavKey
import com.google.samples.apps.nowinandroid.navigation.navigateToTopic
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
fun EntryProviderScope<NavKey>.interestsEntry(onTopicClick: (String) -> Unit) {
fun EntryProviderScope<NavKey>.interestsEntry(navigator: Navigator) {
entry<InterestsNavKey>(
metadata = ListDetailSceneStrategy.listPane {
InterestsDetailPlaceholder()
@ -37,7 +40,7 @@ fun EntryProviderScope<NavKey>.interestsEntry(onTopicClick: (String) -> Unit) {
}
InterestsScreen(
// TODO: This event should either be provided by the ViewModel or by the navigator, not both
onTopicClick = onTopicClick,
onTopicClick = navigator::navigateToTopic,
// TODO: This should be dynamically calculated based on the rendering scene
// See https://github.com/android/nav3-recipes/commit/488f4811791ca3ed7192f4fe3c86e7371b32ebdc#diff-374e02026cdd2f68057dd940f203dc4ba7319930b33e9555c61af7e072211cabR89

@ -1,5 +1,5 @@
/*
* Copyright 2025 The Android Open Source Project
* Copyright 2026 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.
@ -14,22 +14,22 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.search.navigation
package com.google.samples.apps.nowinandroid.navigation.search
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsNavKey
import com.google.samples.apps.nowinandroid.feature.search.SearchScreen
import com.google.samples.apps.nowinandroid.feature.search.navigation.SearchNavKey
import com.google.samples.apps.nowinandroid.navigation.navigateToTopic
fun EntryProviderScope<NavKey>.searchEntry(
onBackClick: () -> Unit,
onInterestsClick: () -> Unit,
onTopicClick: (String) -> Unit,
) {
fun EntryProviderScope<NavKey>.searchEntry(navigator: Navigator) {
entry<SearchNavKey> {
SearchScreen(
onBackClick = onBackClick,
onInterestsClick = onInterestsClick,
onTopicClick = onTopicClick,
onBackClick = { navigator.goBack() },
onInterestsClick = { navigator.navigate(InterestsNavKey()) },
onTopicClick = navigator::navigateToTopic,
)
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2025 The Android Open Source Project
* Copyright 2026 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.
@ -14,31 +14,30 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.topic.navigation
package com.google.samples.apps.nowinandroid.navigation.topic
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.feature.topic.TopicScreen
import com.google.samples.apps.nowinandroid.feature.topic.TopicViewModel
import com.google.samples.apps.nowinandroid.feature.topic.TopicViewModel.Factory
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicNavKey
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicNavKey
import com.google.samples.apps.nowinandroid.navigation.navigateToTopic
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
fun EntryProviderScope<NavKey>.topicEntry(
onBackClick: () -> Unit,
onTopicClick: (String) -> Unit,
) {
fun EntryProviderScope<NavKey>.topicEntry(navigator: Navigator) {
entry<TopicNavKey>(
metadata = ListDetailSceneStrategy.detailPane(),
) { key ->
val id = key.id
TopicScreen(
showBackButton = true,
onBackClick = onBackClick,
onTopicClick = onTopicClick,
onBackClick = { navigator.goBack() },
onTopicClick = navigator::navigateToTopic,
viewModel = hiltViewModel<TopicViewModel, Factory>(
key = id,
) { factory ->
Loading…
Cancel
Save