Merge branch 'main' into dt/nav-safe-args (AnimatedPane broken)

* main:
  Save nested nav key in instance state
  🤖 Updates baselines for Dependency Guard
  Recreate nested nav to work with AnimatedPane
  Remove forgotten Trace.endSection()
  Offload connectivity monitor to a background thread

Change-Id: I4002a07484a4d633c57406aedabf9f5d813a8592
dt/nav-safe-args-android-dependency
Don Turner 8 months ago
commit f711e69cd2

@ -18,14 +18,22 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi 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.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue 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.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
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.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.compose.runtime.setValue
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.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
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.navigateToTopic
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
import java.util.UUID
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable object TopicPlaceholderDestination @Serializable object TopicPlaceholderDestination
@ -67,18 +77,46 @@ internal fun InterestsListDetailScreen(
selectedTopicId: String?, selectedTopicId: String?,
onTopicClick: (String) -> Unit, onTopicClick: (String) -> Unit,
) { ) {
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator() val listDetailNavigator = rememberListDetailPaneScaffoldNavigator(
initialDestinationHistory = listOfNotNull(
ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
ThreePaneScaffoldDestinationItem<Nothing>(ListDetailPaneScaffoldRole.Detail).takeIf {
selectedTopicId != null
},
),
)
BackHandler(listDetailNavigator.canNavigateBack()) { BackHandler(listDetailNavigator.canNavigateBack()) {
listDetailNavigator.navigateBack() 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) { fun onTopicClickShowDetailPane(topicId: String) {
onTopicClick(topicId) onTopicClick(topicId)
nestedNavController.navigateToTopic(topicId) {
popUpTo<DetailPaneNavHostDestination>() // TODO (merge): Fix this
} //if (listDetailNavigator.isDetailPaneVisible()) {
// If the detail pane was visible, then use the nestedNavController navigate call
// directly
nestedNavController.navigateToTopic(topicId) {
popUpTo<DetailPaneNavHostDestination>()
}
/*} else {
// Otherwise, recreate the NavHost entirely, and start at the new destination
nestedNavHostStartDestination = TopicDestination(id = topicId)
nestedNavKey = UUID.randomUUID()
}*/
listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
} }
@ -86,28 +124,37 @@ internal fun InterestsListDetailScreen(
value = listDetailNavigator.scaffoldValue, value = listDetailNavigator.scaffoldValue,
directive = listDetailNavigator.scaffoldDirective, directive = listDetailNavigator.scaffoldDirective,
listPane = { listPane = {
InterestsRoute( // TODO (merge): Fix this
onTopicClick = ::onTopicClickShowDetailPane, //AnimatedPane {
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), InterestsRoute(
)
},
detailPane = {
NavHost(
navController = nestedNavController,
startDestination = TopicPlaceholderDestination,
route = DetailPaneNavHostDestination::class,
) {
topicScreen(
showBackButton = !listDetailNavigator.isListPaneVisible(),
onBackClick = listDetailNavigator::navigateBack,
onTopicClick = ::onTopicClickShowDetailPane, onTopicClick = ::onTopicClickShowDetailPane,
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
) )
composable<TopicPlaceholderDestination> { //}
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<TopicPlaceholderDestination> {
TopicDetailPlaceholder()
}
}
// }
//}
}, },
) )
// TODO (merge): Remove
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (selectedTopicId != null) { if (selectedTopicId != null) {
// Initial topic ID was provided when navigating to Interests, so show its details. // Initial topic ID was provided when navigating to Interests, so show its details.

@ -26,57 +26,68 @@ import android.net.NetworkRequest.Builder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
import androidx.core.content.getSystemService 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 dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import javax.inject.Inject import javax.inject.Inject
internal class ConnectivityManagerNetworkMonitor @Inject constructor( internal class ConnectivityManagerNetworkMonitor @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : NetworkMonitor { ) : NetworkMonitor {
override val isOnline: Flow<Boolean> = callbackFlow { override val isOnline: Flow<Boolean> = callbackFlow {
val connectivityManager = context.getSystemService<ConnectivityManager>() trace("NetworkMonitor.callbackFlow") {
if (connectivityManager == null) { val connectivityManager = context.getSystemService<ConnectivityManager>()
channel.trySend(false) if (connectivityManager == null) {
channel.close() channel.trySend(false)
return@callbackFlow channel.close()
} return@callbackFlow
}
/** /**
* The callback's methods are invoked on changes to *any* network matching the [NetworkRequest], * 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]. * not just the active network. So we can simply track the presence (or absence) of such [Network].
*/ */
val callback = object : NetworkCallback() { val callback = object : NetworkCallback() {
private val networks = mutableSetOf<Network>() private val networks = mutableSetOf<Network>()
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
networks += network networks += network
channel.trySend(true) channel.trySend(true)
} }
override fun onLost(network: Network) { override fun onLost(network: Network) {
networks -= network networks -= network
channel.trySend(networks.isNotEmpty()) channel.trySend(networks.isNotEmpty())
}
} }
}
val request = Builder() trace("NetworkMonitor.registerNetworkCallback") {
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) val request = Builder()
.build() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
connectivityManager.registerNetworkCallback(request, callback) .build()
connectivityManager.registerNetworkCallback(request, callback)
}
/** /**
* Sends the latest connectivity status to the underlying channel. * Sends the latest connectivity status to the underlying channel.
*/ */
channel.trySend(connectivityManager.isCurrentlyConnected()) channel.trySend(connectivityManager.isCurrentlyConnected())
awaitClose { awaitClose {
connectivityManager.unregisterNetworkCallback(callback) connectivityManager.unregisterNetworkCallback(callback)
}
} }
} }
.flowOn(ioDispatcher)
.conflate() .conflate()
@Suppress("DEPRECATION") @Suppress("DEPRECATION")

Loading…
Cancel
Save