diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt new file mode 100644 index 000000000..5bb22f057 --- /dev/null +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2024 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.ui.interests2pane + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.navigation.toRoute +import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination +import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_KEY +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +const val TOPIC_ID_KEY = "selectedTopicId" + +@HiltViewModel +class Interests2PaneViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, +) : ViewModel() { + + val destination = savedStateHandle.toRoute() + val selectedTopicId: StateFlow = savedStateHandle.getStateFlow( + key = TOPIC_ID_KEY, + initialValue = destination.initialTopicId, + ) + + fun onTopicClick(topicId: String?) { + savedStateHandle[TOPIC_ID_KEY] = topicId + } +} diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt index 3e2491875..1f2bbf6df 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -26,14 +26,13 @@ import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaf import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import androidx.navigation.toRoute import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder @@ -46,13 +45,22 @@ import kotlinx.serialization.Serializable @Serializable object DetailPaneNavHostDestination fun NavGraphBuilder.interestsListDetailScreen() { - composable { backStackEntry -> - val topicIdArgument = backStackEntry.toRoute().topicId - var topicId: String? by rememberSaveable { mutableStateOf(topicIdArgument) } - InterestsListDetailScreen(selectedTopicId = topicId, onTopicClick = { topicId = it }) + composable { + InterestsListDetailScreen() } } +@Composable +internal fun InterestsListDetailScreen( + viewModel: Interests2PaneViewModel = hiltViewModel(), +) { + val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle() + InterestsListDetailScreen( + selectedTopicId = selectedTopicId, + onTopicClick = viewModel::onTopicClick, + ) +} + @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable internal fun InterestsListDetailScreen( @@ -86,7 +94,7 @@ internal fun InterestsListDetailScreen( detailPane = { NavHost( navController = nestedNavController, - startDestination = TopicPlaceholderDestination::class, + startDestination = TopicPlaceholderDestination, route = DetailPaneNavHostDestination::class, ) { topicScreen( diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt index c61bfe907..4704f20fd 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt @@ -35,7 +35,7 @@ fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(route = Fo fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) { composable( deepLinks = listOf( - navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN }, + navDeepLink(basePath = DEEP_LINK_URI_PATTERN), ), ) { ForYouRoute(onTopicClick) diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt index 4678c28b2..468550878 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt @@ -46,7 +46,10 @@ fun InterestsRoute( InterestsScreen( uiState = uiState, followTopic = viewModel::followTopic, - onTopicClick = onTopicClick, + onTopicClick = { + viewModel.onTopicClick(it) + onTopicClick(it) + }, highlightSelectedTopic = highlightSelectedTopic, modifier = modifier, ) diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt index 777d85e5f..2cc81b649 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt @@ -25,37 +25,47 @@ import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCa import com.google.samples.apps.nowinandroid.core.domain.TopicSortField import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination +import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_KEY import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class InterestsViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, + private val savedStateHandle: SavedStateHandle, val userDataRepository: UserDataRepository, getFollowableTopics: GetFollowableTopicsUseCase, ) : ViewModel() { private val interestsDestination: InterestsDestination = savedStateHandle.toRoute() + private val selectedTopicId = savedStateHandle.getStateFlow( + key = TOPIC_ID_KEY, + initialValue = interestsDestination.initialTopicId, + ) - val uiState: StateFlow = - getFollowableTopics(sortBy = TopicSortField.NAME).map { topics -> - InterestsUiState.Interests(interestsDestination.topicId, topics) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = InterestsUiState.Loading, - ) + val uiState: StateFlow = combine( + selectedTopicId, + getFollowableTopics(sortBy = TopicSortField.NAME), + InterestsUiState::Interests, + ).stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = InterestsUiState.Loading, + ) fun followTopic(followedTopicId: String, followed: Boolean) { viewModelScope.launch { userDataRepository.setTopicIdFollowed(followedTopicId, followed) } } + + fun onTopicClick(topicId: String?) { + savedStateHandle[TOPIC_ID_KEY] = topicId + } } sealed interface InterestsUiState { diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt index 487bb0d8c..89950f09a 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt @@ -20,10 +20,16 @@ import androidx.navigation.NavController import androidx.navigation.NavOptions import kotlinx.serialization.Serializable -const val TOPIC_ID_ARG = "topicId" +const val TOPIC_ID_KEY = "topicId" -@Serializable data class InterestsDestination(val topicId: String?) +@Serializable data class InterestsDestination( + // The ID of the topic which will be initially selected at this destination + val initialTopicId: String?, +) -fun NavController.navigateToInterests(topicId: String? = null, navOptions: NavOptions? = null) { - navigate(route = InterestsDestination(topicId), navOptions) +fun NavController.navigateToInterests( + initialTopicId: String? = null, + navOptions: NavOptions? = null, +) { + navigate(route = InterestsDestination(initialTopicId), navOptions) } diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index 3d36dbdf5..3a487be79 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -48,12 +48,10 @@ class TopicViewModel @Inject constructor( userNewsResourceRepository: UserNewsResourceRepository, ) : ViewModel() { - private val topicDestination: TopicDestination = savedStateHandle.toRoute() - - val topicId = topicDestination.id + val topicId = savedStateHandle.toRoute().id val topicUiState: StateFlow = topicUiState( - topicId = topicDestination.id, + topicId = topicId, userDataRepository = userDataRepository, topicsRepository = topicsRepository, ) @@ -64,7 +62,7 @@ class TopicViewModel @Inject constructor( ) val newsUiState: StateFlow = newsUiState( - topicId = topicDestination.id, + topicId = topicId, userDataRepository = userDataRepository, userNewsResourceRepository = userNewsResourceRepository, ) @@ -76,7 +74,7 @@ class TopicViewModel @Inject constructor( fun followTopicToggle(followed: Boolean) { viewModelScope.launch { - userDataRepository.setTopicIdFollowed(topicDestination.id, followed) + userDataRepository.setTopicIdFollowed(topicId, followed) } }