From 321d52cb1c39138c5d7f011a846af42a6c8fac4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=82osz=20Moczkowski?= Date: Fri, 21 Jul 2023 15:29:35 +0200 Subject: [PATCH] Migrate to ListDetailPaneScaffold Change-Id: I5dfc70a595838ff39c40d7063f86bb8ceb810d4b --- app/build.gradle.kts | 1 + .../samples/apps/nowinandroid/ui/NiaApp.kt | 74 +++++++++---------- .../nowinandroid/feature/topic/TopicScreen.kt | 6 ++ .../feature/topic/TopicViewModel.kt | 45 +++++++---- settings.gradle.kts | 1 + 5 files changed, 73 insertions(+), 54 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6f9726826..6e11ef5a5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -73,6 +73,7 @@ dependencies { implementation(project(":feature:interests")) implementation(project(":feature:topic")) + implementation("androidx.compose.material3:material3-adaptive-android:1.0.0-SNAPSHOT") implementation(libs.accompanist.systemuicontroller) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index 468b2c15d..d4e1a082c 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -16,58 +16,50 @@ package com.google.samples.apps.nowinandroid.ui -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material3.Scaffold +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.rememberListDetailPaneScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument +import androidx.compose.ui.unit.dp import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute -import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsRoute import com.google.samples.apps.nowinandroid.feature.topic.TopicRoute -import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic -import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicIdArg @OptIn( - ExperimentalLayoutApi::class, + ExperimentalMaterial3AdaptiveApi::class, ) @Composable fun NiaApp() { - Scaffold { padding -> - val navController = rememberNavController() - NavHost( - navController = navController, - startDestination = interestsRoute, - modifier = Modifier - .fillMaxSize() - .padding(padding) - .consumeWindowInsets(padding) - .windowInsetsPadding(WindowInsets.safeDrawing), - ) { - composable(route = interestsRoute) { - InterestsRoute(onTopicClick = navController::navigateToTopic) - } - composable( - route = "topic_route/{$topicIdArg}", - arguments = listOf( - navArgument(topicIdArg) { type = NavType.StringType }, - ), - ) { + val layoutState = rememberListDetailPaneScaffoldState( + initialFocus = ListDetailPaneScaffoldRole.List + ) + var selectedTopicId: String? by remember { mutableStateOf(null) } + ListDetailPaneScaffold( + layoutState = layoutState, + listPane = { + InterestsRoute(onTopicClick = { topicId -> + selectedTopicId = topicId + layoutState.navigateTo(ListDetailPaneScaffoldRole.Detail) + }) + }, + detailPane = { + selectedTopicId?.let { TopicRoute( - onBackClick = navController::popBackStack, - onTopicClick = navController::navigateToTopic + topicId = it, + onTopicClick = {}, + onBackClick = { + layoutState.navigateBack(true) + layoutState.navigateTo(ListDetailPaneScaffoldRole.List) + }, + //modifier = Modifier.preferredWidth(400.dp) ) } - } - } + }, + ) } diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index adc1874e6..6c44ffa9e 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -61,11 +62,16 @@ import com.google.samples.apps.nowinandroid.feature.topic.R.string @Composable fun TopicRoute( + topicId: String, onBackClick: () -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, viewModel: TopicViewModel = hiltViewModel(), ) { + LaunchedEffect(topicId) { + viewModel.onTopicIdChange(topicId) + } + val topicUiState: TopicUiState by viewModel.topicUiState.collectAsStateWithLifecycle() val newsUiState: NewsUiState by viewModel.newUiState.collectAsStateWithLifecycle() 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 b0f1e756a..3d369bf3c 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 @@ -32,48 +32,67 @@ import com.google.samples.apps.nowinandroid.core.result.asResult import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class TopicViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, stringDecoder: StringDecoder, private val userDataRepository: UserDataRepository, topicsRepository: TopicsRepository, userNewsResourceRepository: UserNewsResourceRepository, ) : ViewModel() { - private val topicArgs: TopicArgs = TopicArgs(savedStateHandle, stringDecoder) + private val _topicId: MutableStateFlow = MutableStateFlow(null) + private val topicId: SharedFlow = _topicId.filterNotNull().shareIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + ) - val topicUiState: StateFlow = topicUiState( - topicId = topicArgs.topicId, - userDataRepository = userDataRepository, - topicsRepository = topicsRepository, - ).stateIn( + val topicUiState: StateFlow = topicId.flatMapLatest { id -> + topicUiState( + topicId = id, + userDataRepository = userDataRepository, + topicsRepository = topicsRepository, + ) + }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = TopicUiState.Loading, ) - val newUiState: StateFlow = newsUiState( - topicId = topicArgs.topicId, - userDataRepository = userDataRepository, - userNewsResourceRepository = userNewsResourceRepository, - ).stateIn( + val newUiState: StateFlow = topicId.flatMapLatest { id -> + newsUiState( + topicId = id, + userDataRepository = userDataRepository, + userNewsResourceRepository = userNewsResourceRepository, + ) + }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = NewsUiState.Loading, ) + fun onTopicIdChange(topicId: String) { + viewModelScope.launch { + _topicId.value = topicId + } + } + fun followTopicToggle(followed: Boolean) { viewModelScope.launch { - userDataRepository.toggleFollowedTopicId(topicArgs.topicId, followed) + userDataRepository.toggleFollowedTopicId(topicId.first(), followed) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 92ec76bbc..68227a379 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url = uri("https://androidx.dev/snapshots/builds/10531429/artifacts/repository") } } } rootProject.name = "nowinandroid"