diff --git a/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/Destinations.kt b/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/Destinations.kt index 464f044d..ceef5581 100644 --- a/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/Destinations.kt +++ b/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/Destinations.kt @@ -23,25 +23,19 @@ sealed interface Destinations { data object Explore : Destinations @Serializable - sealed class PodcastDetails(val podcastId: Long) : Destinations + data class PodcastDetailsSubscriptionsGraph(val id: Long) : Destinations @Serializable - data class PodcastDetailsSubscriptionsGraph(val id: Long) : PodcastDetails(id) + data class PodcastDetailsExploreGraph(val id: Long) : Destinations @Serializable - data class PodcastDetailsExploreGraph(val id: Long) : PodcastDetails(id) + data class EpisodeDetailsSubscriptionsGraph(val id: Long, val artworkUrl: String) : Destinations @Serializable - sealed class EpisodeDetails(val episodeId: Long, val podcastArtworkUrl: String) : Destinations + data class EpisodeDetailsExploreGraph(val id: Long, val artworkUrl: String) : Destinations @Serializable - data class EpisodeDetailsSubscriptionsGraph(val id: Long, val artworkUrl: String) : EpisodeDetails(id, artworkUrl) - - @Serializable - data class EpisodeDetailsExploreGraph(val id: Long, val artworkUrl: String) : EpisodeDetails(id, artworkUrl) - - @Serializable - data class EpisodeDetailsLibraryGraph(val id: Long, val artworkUrl: String) : EpisodeDetails(id, artworkUrl) + data class EpisodeDetailsLibraryGraph(val id: Long, val artworkUrl: String) : Destinations @Serializable data object Library : Destinations diff --git a/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/NavGraph.kt b/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/NavGraph.kt index 2f006d92..2ce3ad70 100644 --- a/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/NavGraph.kt +++ b/app/src/main/kotlin/com/mr3y/podcaster/ui/navigation/NavGraph.kt @@ -65,7 +65,7 @@ fun PodcasterNavGraph( contentPadding = contentPadding, excludedWindowInsets = excludedWindowInsets, viewModel = hiltViewModel( - creationCallback = { factory -> factory.create(navBackStackEntry.toRoute().podcastId) }, + creationCallback = { factory -> factory.create(navBackStackEntry.toRoute().id) }, ), ) } @@ -80,7 +80,7 @@ fun PodcasterNavGraph( viewModel = hiltViewModel( creationCallback = { factory -> val destination = navBackStackEntry.toRoute() - factory.create(destination.episodeId, destination.podcastArtworkUrl) + factory.create(destination.id, destination.artworkUrl) }, ), ) @@ -108,7 +108,7 @@ fun PodcasterNavGraph( contentPadding = contentPadding, excludedWindowInsets = excludedWindowInsets, viewModel = hiltViewModel( - creationCallback = { factory -> factory.create(navBackStackEntry.toRoute().podcastId) }, + creationCallback = { factory -> factory.create(navBackStackEntry.toRoute().id) }, ), ) } @@ -123,7 +123,7 @@ fun PodcasterNavGraph( viewModel = hiltViewModel( creationCallback = { factory -> val destination = navBackStackEntry.toRoute() - factory.create(destination.episodeId, destination.podcastArtworkUrl) + factory.create(destination.id, destination.artworkUrl) }, ), ) @@ -174,7 +174,7 @@ fun PodcasterNavGraph( viewModel = hiltViewModel( creationCallback = { factory -> val destination = navBackStackEntry.toRoute() - factory.create(destination.episodeId, destination.podcastArtworkUrl) + factory.create(destination.id, destination.artworkUrl) }, ), ) diff --git a/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/HomeScreen.kt b/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/HomeScreen.kt index 5748cbd4..d17a34ab 100644 --- a/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/HomeScreen.kt +++ b/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/HomeScreen.kt @@ -56,6 +56,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LifecycleStartEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController @@ -234,6 +235,22 @@ fun HomeScreen( onToggleFavoriteStatus = { appState.toggleEpisodeFavoriteStatus(isFavorite = it, episode = activeEpisode.episode) }, + onEpisodeClick = { + scope.launch { state.animateTo(PlayerViewState.Collapsed) } + + val isOnExploreTab = currentDestination?.hierarchy?.any { it.route == createRoutePattern() } == true + if (!isOnExploreTab) { + navController.navigateToGraph(Destinations.ExploreGraph) + } + // Avoid having multiple copies of the same episode stacked on top of each other similar to `launchSingleTop` + // but the problem with `launchSingleTop` is it will prevent navigating to the same destination that has a different arguments + if ( + currentDestination?.hasRoute(route = Destinations.EpisodeDetailsExploreGraph::class) == false || + navBackStackEntry?.arguments?.getLong("id") != activeEpisode.episode.id + ) { + navController.navigate(Destinations.EpisodeDetailsExploreGraph(activeEpisode.episode.id, activeEpisode.episode.artworkUrl)) + } + }, onBack = { scope.launch { state.animateTo(PlayerViewState.Collapsed) } }, containerColor = containerColor, ) @@ -313,13 +330,7 @@ private fun BottomBar( selected = isSelected, onClick = { if (!isSelected) { - navController.navigate(tab.destination) { - popUpTo(navController.graph.findStartDestination().id) { - saveState = true - } - launchSingleTop = true - restoreState = true - } + navController.navigateToGraph(tab.destination) } else if (currentDestination?.route !in bottomBarTabs.map { it.route }) { currentDestination?.parent?.findStartDestination()?.id?.let { navController.popBackStackOnce(it) @@ -338,6 +349,16 @@ private fun BottomBar( } } +private fun NavHostController.navigateToGraph(destinationGraph: Destinations) { + navigate(destinationGraph) { + popUpTo(graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } +} + /** * Idempotent version of [NavHostController.popBackStack] function. */ diff --git a/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/PlayerViewScreen.kt b/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/PlayerViewScreen.kt index 5631513d..0bb09c9d 100644 --- a/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/PlayerViewScreen.kt +++ b/app/src/main/kotlin/com/mr3y/podcaster/ui/screens/PlayerViewScreen.kt @@ -12,6 +12,8 @@ import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -94,6 +96,7 @@ fun ExpandedPlayerView( onSeekToNext: () -> Unit, onSeekToPrevious: () -> Unit, onToggleFavoriteStatus: (Boolean) -> Unit, + onEpisodeClick: () -> Unit, onBack: () -> Unit, containerColor: Color, modifier: Modifier = Modifier, @@ -137,6 +140,8 @@ fun ExpandedPlayerView( modifier = Modifier .size(360.dp), ) + + val strings = LocalStrings.current Text( text = targetEpisode.title, color = MaterialTheme.colorScheme.onSurface, @@ -145,9 +150,14 @@ fun ExpandedPlayerView( maxLines = 3, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClickLabel = strings.navigate_to_episode_a11y_label(targetEpisode.title), + onClick = onEpisodeClick + ) ) - val strings = LocalStrings.current Text( text = if (playingStatus == PlayingStatus.Loading) { strings.buffering_playback @@ -445,6 +455,7 @@ fun ExpandedPlayerViewPreview( progress = 1150, onSeeking = {}, onToggleFavoriteStatus = {}, + onEpisodeClick = {}, onBack = {}, containerColor = MaterialTheme.colorScheme.surface, modifier = Modifier.fillMaxSize(), diff --git a/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterEnStrings.kt b/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterEnStrings.kt index f6675c97..68899841 100644 --- a/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterEnStrings.kt +++ b/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterEnStrings.kt @@ -25,6 +25,7 @@ val EnStrings = PodcasterStrings( retry_label = "Retry", currently_playing = "Currently Playing", buffering_playback = "Buffering...", + navigate_to_episode_a11y_label = { "Navigate to $it details page" }, search_for_podcast_placeholder = "Search for a podcast or add RSS Url", recent_searches_label = "Recent Searches", close_label = "CLOSE", diff --git a/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterStrings.kt b/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterStrings.kt index a3284ae2..6d500924 100644 --- a/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterStrings.kt +++ b/ui/resources/src/main/kotlin/com/mr3y/podcaster/ui/resources/PodcasterStrings.kt @@ -20,6 +20,7 @@ data class PodcasterStrings( val retry_label: String, val currently_playing: String, val buffering_playback: String, + val navigate_to_episode_a11y_label: (String) -> String, val search_for_podcast_placeholder: String, val recent_searches_label: String, val close_label: String,