Skip to content

Commit

Permalink
Add specific type for deeplinks
Browse files Browse the repository at this point in the history
Change-Id: I7532f2cf9867e23855ff15dceff12bc9b9d2dd38
  • Loading branch information
dturner committed May 8, 2024
1 parent 3cff2fb commit 028e43a
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fun NiaNavHost(
val navController = appState.navController
NavHost(
navController = navController,
startDestination = ForYouDestination(linkedNewsResourceId = null),
startDestination = ForYouDestination(),
modifier = modifier,
) {
forYouScreen(onTopicClick = navController::navigateToInterests)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,25 @@

package com.google.samples.apps.nowinandroid.ui.interests2pane

import android.adservices.topics.Topic
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.setValue
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.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraphBuilder
Expand All @@ -36,9 +44,11 @@ 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 kotlinx.serialization.Serializable
import java.util.UUID

@Serializable object TopicPlaceholderDestination

Expand All @@ -55,6 +65,7 @@ internal fun InterestsListDetailScreen(
viewModel: Interests2PaneViewModel = hiltViewModel(),
) {
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()

InterestsListDetailScreen(
selectedTopicId = selectedTopicId,
onTopicClick = viewModel::onTopicClick,
Expand All @@ -67,17 +78,45 @@ internal fun InterestsListDetailScreen(
selectedTopicId: String?,
onTopicClick: (String) -> Unit,
) {
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator()
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator(
initialDestinationHistory = listOfNotNull(
ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
ThreePaneScaffoldDestinationItem<Nothing>(ListDetailPaneScaffoldRole.Detail).takeIf {
selectedTopicId != null
},
),
)
BackHandler(listDetailNavigator.canNavigateBack()) {
listDetailNavigator.navigateBack()
}

val nestedNavController = rememberNavController()
var nestedNavHostStartDestination by remember {
mutableStateOf(selectedTopicId?.let {
TopicDestination(id = it)
} ?: TopicPlaceholderDestination)
}

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<DetailPaneNavHostDestination>()
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)
}
Expand All @@ -86,34 +125,34 @@ 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,
//AnimatedPane {
InterestsRoute(
onTopicClick = ::onTopicClickShowDetailPane,
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
)
composable<TopicPlaceholderDestination> {
TopicDetailPlaceholder()
}
}
//}
},
detailPane = {
//AnimatedPane {
//key(nestedNavKey) {
NavHost(
navController = nestedNavController,
startDestination = TopicPlaceholderDestination,
route = DetailPaneNavHostDestination::class,
) {
topicScreen(
showBackButton = !listDetailNavigator.isListPaneVisible(),
onBackClick = listDetailNavigator::navigateBack,
onTopicClick = ::onTopicClickShowDetailPane,
)
composable<TopicPlaceholderDestination> {
TopicDetailPlaceholder()
}
}
// }
//}
},
)
LaunchedEffect(Unit) {
if (selectedTopicId != null) {
// Initial topic ID was provided when navigating to Interests, so show its details.
onTopicClickShowDetailPane(selectedTopicId)
}
}
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> = callbackFlow {
val connectivityManager = context.getSystemService<ConnectivityManager>()
if (connectivityManager == null) {
channel.trySend(false)
channel.close()
return@callbackFlow
}
trace("NetworkMonitor.callbackFlow") {
val connectivityManager = context.getSystemService<ConnectivityManager>()
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<Network>()
private val networks = mutableSetOf<Network>()

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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ const val LINKED_NEWS_RESOURCE_ID = "linkedNewsResourceId"

private const val DEEP_LINK_BASE_PATH = "$DEEP_LINK_SCHEME_AND_HOST/$FOR_YOU_PATH"

@Serializable data class ForYouDestination(val linkedNewsResourceId: String?)
@Serializable data class ForYouDestination(val linkedNewsResourceId: String? = null)
// For deeplinks the news resource ID cannot be null so we use a different type with the same
// argument name to enforce this requirement.
@Serializable data class ForYouDeeplink(val linkedNewsResourceId: String)

fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(route = ForYouDestination(linkedNewsResourceId = null), navOptions)
fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(route = ForYouDestination(), navOptions)

fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) {
composable<ForYouDestination>(
deepLinks = listOf(
navDeepLink<ForYouDestination>(basePath = DEEP_LINK_BASE_PATH),
navDeepLink<ForYouDeeplink>(basePath = DEEP_LINK_BASE_PATH),
),
) {
ForYouRoute(onTopicClick)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ fun NavGraphBuilder.topicScreen(
onTopicClick: (String) -> Unit,
) {
composable<TopicDestination> {
TopicScreen(
/*TopicScreen(
showBackButton = showBackButton,
onBackClick = onBackClick,
onTopicClick = onTopicClick,
)
)*/
}
}

0 comments on commit 028e43a

Please sign in to comment.