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 1f2bbf6df..d1ead05dd 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 @@ -18,14 +18,22 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane import androidx.activity.compose.BackHandler import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.Saver +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 @@ -36,8 +44,10 @@ import androidx.navigation.compose.rememberNavController 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 +import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicDestination import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen +import java.util.UUID import kotlinx.serialization.Serializable @Serializable object TopicPlaceholderDestination @@ -67,18 +77,46 @@ internal fun InterestsListDetailScreen( selectedTopicId: String?, onTopicClick: (String) -> Unit, ) { - val listDetailNavigator = rememberListDetailPaneScaffoldNavigator() + val listDetailNavigator = rememberListDetailPaneScaffoldNavigator( + initialDestinationHistory = listOfNotNull( + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List), + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail).takeIf { + selectedTopicId != null + }, + ), + ) BackHandler(listDetailNavigator.canNavigateBack()) { listDetailNavigator.navigateBack() } - val nestedNavController = rememberNavController() + var nestedNavHostStartDestination by remember { + val destination = selectedTopicId?.let { TopicDestination(id = it) } ?: TopicPlaceholderDestination + mutableStateOf(destination) + } + var nestedNavKey by rememberSaveable( + stateSaver = Saver({ it.toString() }, UUID::fromString), + ) { + mutableStateOf(UUID.randomUUID()) + } + val nestedNavController = key(nestedNavKey) { + rememberNavController() + } fun onTopicClickShowDetailPane(topicId: String) { onTopicClick(topicId) - nestedNavController.navigateToTopic(topicId) { - popUpTo() - } + + // TODO (merge): Fix this + //if (listDetailNavigator.isDetailPaneVisible()) { + // If the detail pane was visible, then use the nestedNavController navigate call + // directly + nestedNavController.navigateToTopic(topicId) { + popUpTo() + } + /*} else { + // Otherwise, recreate the NavHost entirely, and start at the new destination + nestedNavHostStartDestination = TopicDestination(id = topicId) + nestedNavKey = UUID.randomUUID() + }*/ listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) } @@ -86,28 +124,37 @@ internal fun InterestsListDetailScreen( value = listDetailNavigator.scaffoldValue, directive = listDetailNavigator.scaffoldDirective, listPane = { - InterestsRoute( - onTopicClick = ::onTopicClickShowDetailPane, - highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), - ) - }, - detailPane = { - NavHost( - navController = nestedNavController, - startDestination = TopicPlaceholderDestination, - route = DetailPaneNavHostDestination::class, - ) { - topicScreen( - showBackButton = !listDetailNavigator.isListPaneVisible(), - onBackClick = listDetailNavigator::navigateBack, + // TODO (merge): Fix this + //AnimatedPane { + InterestsRoute( onTopicClick = ::onTopicClickShowDetailPane, + highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), ) - composable { - TopicDetailPlaceholder() - } - } + //} + }, + detailPane = { + // TODO (merge): Fix this + //AnimatedPane { + // key(nestedNavKey) { + NavHost( + navController = nestedNavController, + startDestination = nestedNavHostStartDestination, + route = DetailPaneNavHostDestination::class, + ) { + topicScreen( + showBackButton = !listDetailNavigator.isListPaneVisible(), + onBackClick = listDetailNavigator::navigateBack, + onTopicClick = ::onTopicClickShowDetailPane, + ) + composable { + TopicDetailPlaceholder() + } + } + // } + //} }, ) + // TODO (merge): Remove LaunchedEffect(Unit) { if (selectedTopicId != null) { // Initial topic ID was provided when navigating to Interests, so show its details. diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt index e9599c555..b2a642cf9 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt @@ -26,57 +26,68 @@ import android.net.NetworkRequest.Builder import android.os.Build.VERSION import android.os.Build.VERSION_CODES import androidx.core.content.getSystemService +import androidx.tracing.trace +import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject internal class ConnectivityManagerNetworkMonitor @Inject constructor( @ApplicationContext private val context: Context, + @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, ) : NetworkMonitor { override val isOnline: Flow = callbackFlow { - val connectivityManager = context.getSystemService() - if (connectivityManager == null) { - channel.trySend(false) - channel.close() - return@callbackFlow - } + trace("NetworkMonitor.callbackFlow") { + val connectivityManager = context.getSystemService() + if (connectivityManager == null) { + channel.trySend(false) + channel.close() + return@callbackFlow + } - /** - * The callback's methods are invoked on changes to *any* network matching the [NetworkRequest], - * not just the active network. So we can simply track the presence (or absence) of such [Network]. - */ - val callback = object : NetworkCallback() { + /** + * The callback's methods are invoked on changes to *any* network matching the [NetworkRequest], + * not just the active network. So we can simply track the presence (or absence) of such [Network]. + */ + val callback = object : NetworkCallback() { - private val networks = mutableSetOf() + private val networks = mutableSetOf() - override fun onAvailable(network: Network) { - networks += network - channel.trySend(true) - } + override fun onAvailable(network: Network) { + networks += network + channel.trySend(true) + } - override fun onLost(network: Network) { - networks -= network - channel.trySend(networks.isNotEmpty()) + override fun onLost(network: Network) { + networks -= network + channel.trySend(networks.isNotEmpty()) + } } - } - val request = Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build() - connectivityManager.registerNetworkCallback(request, callback) + trace("NetworkMonitor.registerNetworkCallback") { + val request = Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + connectivityManager.registerNetworkCallback(request, callback) + } - /** - * Sends the latest connectivity status to the underlying channel. - */ - channel.trySend(connectivityManager.isCurrentlyConnected()) + /** + * Sends the latest connectivity status to the underlying channel. + */ + channel.trySend(connectivityManager.isCurrentlyConnected()) - awaitClose { - connectivityManager.unregisterNetworkCallback(callback) + awaitClose { + connectivityManager.unregisterNetworkCallback(callback) + } } } + .flowOn(ioDispatcher) .conflate() @Suppress("DEPRECATION")