Fix issue where selected topic in list was not showing as selected. Minor tidy ups.

Change-Id: Icfa79eac6f7327c365f79fd7d15dfa1f8c77184d
dt/nav-safe-args-android-dependency
Don Turner 8 months ago
parent 2264451bb8
commit 7ec21d9d2f

@ -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<InterestsDestination>()
val selectedTopicId: StateFlow<String?> = savedStateHandle.getStateFlow(
key = TOPIC_ID_KEY,
initialValue = destination.initialTopicId,
)
fun onTopicClick(topicId: String?) {
savedStateHandle[TOPIC_ID_KEY] = topicId
}
}

@ -26,14 +26,13 @@ import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaf
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController 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.InterestsRoute
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsDestination
import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder
@ -46,13 +45,22 @@ import kotlinx.serialization.Serializable
@Serializable object DetailPaneNavHostDestination @Serializable object DetailPaneNavHostDestination
fun NavGraphBuilder.interestsListDetailScreen() { fun NavGraphBuilder.interestsListDetailScreen() {
composable<InterestsDestination> { backStackEntry -> composable<InterestsDestination> {
val topicIdArgument = backStackEntry.toRoute<InterestsDestination>().topicId InterestsListDetailScreen()
var topicId: String? by rememberSaveable { mutableStateOf(topicIdArgument) }
InterestsListDetailScreen(selectedTopicId = topicId, onTopicClick = { topicId = it })
} }
} }
@Composable
internal fun InterestsListDetailScreen(
viewModel: Interests2PaneViewModel = hiltViewModel(),
) {
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()
InterestsListDetailScreen(
selectedTopicId = selectedTopicId,
onTopicClick = viewModel::onTopicClick,
)
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class) @OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable @Composable
internal fun InterestsListDetailScreen( internal fun InterestsListDetailScreen(
@ -86,7 +94,7 @@ internal fun InterestsListDetailScreen(
detailPane = { detailPane = {
NavHost( NavHost(
navController = nestedNavController, navController = nestedNavController,
startDestination = TopicPlaceholderDestination::class, startDestination = TopicPlaceholderDestination,
route = DetailPaneNavHostDestination::class, route = DetailPaneNavHostDestination::class,
) { ) {
topicScreen( topicScreen(

@ -35,7 +35,7 @@ fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(route = Fo
fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) { fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) {
composable<ForYouDestination>( composable<ForYouDestination>(
deepLinks = listOf( deepLinks = listOf(
navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN }, navDeepLink<ForYouDestination>(basePath = DEEP_LINK_URI_PATTERN),
), ),
) { ) {
ForYouRoute(onTopicClick) ForYouRoute(onTopicClick)

@ -46,7 +46,10 @@ fun InterestsRoute(
InterestsScreen( InterestsScreen(
uiState = uiState, uiState = uiState,
followTopic = viewModel::followTopic, followTopic = viewModel::followTopic,
onTopicClick = onTopicClick, onTopicClick = {
viewModel.onTopicClick(it)
onTopicClick(it)
},
highlightSelectedTopic = highlightSelectedTopic, highlightSelectedTopic = highlightSelectedTopic,
modifier = modifier, modifier = modifier,
) )

@ -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.domain.TopicSortField
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic 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.InterestsDestination
import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_KEY
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class InterestsViewModel @Inject constructor( class InterestsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle, private val savedStateHandle: SavedStateHandle,
val userDataRepository: UserDataRepository, val userDataRepository: UserDataRepository,
getFollowableTopics: GetFollowableTopicsUseCase, getFollowableTopics: GetFollowableTopicsUseCase,
) : ViewModel() { ) : ViewModel() {
private val interestsDestination: InterestsDestination = savedStateHandle.toRoute() private val interestsDestination: InterestsDestination = savedStateHandle.toRoute()
private val selectedTopicId = savedStateHandle.getStateFlow(
key = TOPIC_ID_KEY,
initialValue = interestsDestination.initialTopicId,
)
val uiState: StateFlow<InterestsUiState> = val uiState: StateFlow<InterestsUiState> = combine(
getFollowableTopics(sortBy = TopicSortField.NAME).map { topics -> selectedTopicId,
InterestsUiState.Interests(interestsDestination.topicId, topics) getFollowableTopics(sortBy = TopicSortField.NAME),
}.stateIn( InterestsUiState::Interests,
scope = viewModelScope, ).stateIn(
started = SharingStarted.WhileSubscribed(5_000), scope = viewModelScope,
initialValue = InterestsUiState.Loading, started = SharingStarted.WhileSubscribed(5_000),
) initialValue = InterestsUiState.Loading,
)
fun followTopic(followedTopicId: String, followed: Boolean) { fun followTopic(followedTopicId: String, followed: Boolean) {
viewModelScope.launch { viewModelScope.launch {
userDataRepository.setTopicIdFollowed(followedTopicId, followed) userDataRepository.setTopicIdFollowed(followedTopicId, followed)
} }
} }
fun onTopicClick(topicId: String?) {
savedStateHandle[TOPIC_ID_KEY] = topicId
}
} }
sealed interface InterestsUiState { sealed interface InterestsUiState {

@ -20,10 +20,16 @@ import androidx.navigation.NavController
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import kotlinx.serialization.Serializable 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) { fun NavController.navigateToInterests(
navigate(route = InterestsDestination(topicId), navOptions) initialTopicId: String? = null,
navOptions: NavOptions? = null,
) {
navigate(route = InterestsDestination(initialTopicId), navOptions)
} }

@ -48,12 +48,10 @@ class TopicViewModel @Inject constructor(
userNewsResourceRepository: UserNewsResourceRepository, userNewsResourceRepository: UserNewsResourceRepository,
) : ViewModel() { ) : ViewModel() {
private val topicDestination: TopicDestination = savedStateHandle.toRoute() val topicId = savedStateHandle.toRoute<TopicDestination>().id
val topicId = topicDestination.id
val topicUiState: StateFlow<TopicUiState> = topicUiState( val topicUiState: StateFlow<TopicUiState> = topicUiState(
topicId = topicDestination.id, topicId = topicId,
userDataRepository = userDataRepository, userDataRepository = userDataRepository,
topicsRepository = topicsRepository, topicsRepository = topicsRepository,
) )
@ -64,7 +62,7 @@ class TopicViewModel @Inject constructor(
) )
val newsUiState: StateFlow<NewsUiState> = newsUiState( val newsUiState: StateFlow<NewsUiState> = newsUiState(
topicId = topicDestination.id, topicId = topicId,
userDataRepository = userDataRepository, userDataRepository = userDataRepository,
userNewsResourceRepository = userNewsResourceRepository, userNewsResourceRepository = userNewsResourceRepository,
) )
@ -76,7 +74,7 @@ class TopicViewModel @Inject constructor(
fun followTopicToggle(followed: Boolean) { fun followTopicToggle(followed: Boolean) {
viewModelScope.launch { viewModelScope.launch {
userDataRepository.setTopicIdFollowed(topicDestination.id, followed) userDataRepository.setTopicIdFollowed(topicId, followed)
} }
} }

Loading…
Cancel
Save