Skip to content

Add a screen to show the details of the selected episode #1304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import androidx.tv.material3.NavigationDrawer
import androidx.tv.material3.NavigationDrawerItem
import androidx.tv.material3.Text
import com.example.jetcaster.tv.ui.discover.DiscoverScreen
import com.example.jetcaster.tv.ui.episode.EpisodeScreen
import com.example.jetcaster.tv.ui.episode.EpisodeScreenViewModel
import com.example.jetcaster.tv.ui.library.LibraryScreen
import com.example.jetcaster.tv.ui.podcast.PodcastScreen
import com.example.jetcaster.tv.ui.podcast.PodcastScreenViewModel
Expand Down Expand Up @@ -124,6 +126,9 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
showPodcastDetails = {
jetcasterAppState.showPodcastDetails(it.uri)
},
showEpisodeDetails = {
jetcasterAppState.showEpisodeDetails(it.episode.uri)
},
modifier = Modifier
.padding(JetcasterAppDefaults.overScanMargin.default.intoPaddingValues())
.fillMaxSize()
Expand All @@ -138,6 +143,9 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
showPodcastDetails = {
jetcasterAppState.showPodcastDetails(it.podcast.uri)
},
showEpisodeDetails = {
jetcasterAppState.showEpisodeDetails(it.episode.uri)
},
modifier = Modifier
.padding(JetcasterAppDefaults.overScanMargin.default.intoPaddingValues())
.fillMaxSize()
Expand All @@ -164,12 +172,26 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
podcastScreenViewModel = podcastScreenViewModel,
backToHomeScreen = jetcasterAppState::navigateToDiscover,
playEpisode = {},
showEpisodeDetails = { jetcasterAppState.showEpisodeDetails(it.episode.uri) },
modifier = Modifier
.padding(JetcasterAppDefaults.overScanMargin.podcastDetails.intoPaddingValues())
.padding(JetcasterAppDefaults.overScanMargin.podcast.intoPaddingValues())
.fillMaxSize(),
)
}

composable(Screen.Episode.route) {
val episodeScreenViewModel: EpisodeScreenViewModel = viewModel(
factory = EpisodeScreenViewModel.factory
)
EpisodeScreen(
playEpisode = {
jetcasterAppState.playEpisode(it.uri)
},
backToHome = jetcasterAppState::navigateToDiscover,
episodeScreenViewModel = episodeScreenViewModel,
)
}

composable(Screen.Player.route) {
Text(text = "Player")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class JetcasterAppState(
navHostController.navigate(screen.route)
}

fun showEpisodeDetails(episodeUri: String) {
val encodeUrl = Uri.encode(episodeUri)
val screen = Screen.Episode(encodeUrl)
navHostController.navigate(screen.route)
}

fun playEpisode(episodeUri: String) {
val screen = Screen.Player(episodeUri)
navHostController.navigate(screen.route)
Expand Down Expand Up @@ -97,7 +103,17 @@ sealed interface Screen {

companion object : Screen {
private const val ROOT = "podcast"
private const val PARAMETER_NAME = "podcastUri"
const val PARAMETER_NAME = "podcastUri"
override val route = "$ROOT/{$PARAMETER_NAME}"
}
}

data class Episode(private val episodeUri: String) : Screen {

override val route: String = "$ROOT/$episodeUri"
companion object : Screen {
private const val ROOT = "episode"
const val PARAMETER_NAME = "episodeUri"
override val route = "$ROOT/{$PARAMETER_NAME}"
}
}
Expand All @@ -107,7 +123,7 @@ sealed interface Screen {

companion object : Screen {
private const val ROOT = "player"
private const val PARAMETER_NAME = "episodeUri"
const val PARAMETER_NAME = "episodeUri"
override val route = "$ROOT/{$PARAMETER_NAME}"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.jetcaster.tv.ui.component

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.layout.ContentScale
import coil.compose.AsyncImage
import com.example.jetcaster.core.data.database.model.Podcast

@Composable
internal fun Background(
podcast: Podcast,
modifier: Modifier = Modifier,
overlay: DrawScope.() -> Unit = {
val brush = Brush.radialGradient(
listOf(Color.Black, Color.Transparent),
center = Offset(0f, size.height),
radius = size.width * 1.5f
)
drawRect(brush, blendMode = BlendMode.Multiply)
}
) {
AsyncImage(
model = podcast.imageUrl,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = modifier
.fillMaxWidth()
.drawWithCache {
onDrawWithContent {
drawContent()
overlay()
}
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.jetcaster.tv.ui.component

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material.icons.outlined.PlayArrow
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.tv.material3.Button
import androidx.tv.material3.ButtonDefaults
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Icon
import androidx.tv.material3.IconButton
import androidx.tv.material3.Text
import com.example.jetcaster.tv.R

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
internal fun PlayButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) =
ButtonWithIcon(
icon = Icons.Outlined.PlayArrow,
label = stringResource(R.string.label_play),
onClick = onClick,
modifier = modifier
)

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
internal fun EnqueueButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(onClick = onClick, modifier = modifier) {
Icon(
Icons.AutoMirrored.Filled.PlaylistAdd,
contentDescription = stringResource(R.string.label_add_playlist),
)
}
}

@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
internal fun ButtonWithIcon(
icon: ImageVector,
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) =
Button(onClick = onClick, modifier = modifier) {
Icon(
icon,
contentDescription = null,
modifier = Modifier
.width(ButtonDefaults.IconSize)
.padding(end = ButtonDefaults.IconSpacing)
)
Text(text = label)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyListState
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.foundation.lazy.list.rememberTvLazyListState
import androidx.tv.material3.Card
import androidx.tv.material3.CardScale
import androidx.tv.material3.ExperimentalTvMaterial3Api
Expand All @@ -55,14 +59,17 @@ internal fun Catalog(
podcastList: PodcastList,
latestEpisodeList: EpisodeList,
onPodcastSelected: (PodcastWithExtraInfo) -> Unit,
onEpisodeSelected: (EpisodeToPodcast) -> Unit,
modifier: Modifier = Modifier,
state: TvLazyListState = rememberTvLazyListState(),
header: (@Composable () -> Unit)? = null,
) {
TvLazyColumn(
modifier = modifier,
contentPadding = JetcasterAppDefaults.overScanMargin.catalog.intoPaddingValues(),
verticalArrangement =
Arrangement.spacedBy(JetcasterAppDefaults.gapSettings.catalogSectionGap)
Arrangement.spacedBy(JetcasterAppDefaults.gap.section),
state = state,
) {
if (header != null) {
item { header() }
Expand All @@ -77,13 +84,14 @@ internal fun Catalog(
item {
LatestEpisodeSection(
episodeList = latestEpisodeList,
onEpisodeSelected = {},
onEpisodeSelected = onEpisodeSelected,
title = stringResource(R.string.label_latest_episode)
)
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun PodcastSection(
podcastList: PodcastList,
Expand All @@ -95,10 +103,15 @@ private fun PodcastSection(
title = title,
modifier = modifier
) {
PodcastRow(podcastList = podcastList, onPodcastSelected = onPodcastSelected)
PodcastRow(
podcastList = podcastList,
onPodcastSelected = onPodcastSelected,
modifier = Modifier.focusRestorer()
)
}
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun LatestEpisodeSection(
episodeList: EpisodeList,
Expand All @@ -110,7 +123,11 @@ private fun LatestEpisodeSection(
modifier = modifier,
title = title
) {
EpisodeRow(episodeList = episodeList, onEpisodeSelected = onEpisodeSelected)
EpisodeRow(
episodeList = episodeList,
onEpisodeSelected = onEpisodeSelected,
modifier = Modifier.focusRestorer()
)
}
}

Expand Down Expand Up @@ -141,7 +158,7 @@ private fun PodcastRow(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
horizontalArrangement: Arrangement.Horizontal =
Arrangement.spacedBy(JetcasterAppDefaults.gapSettings.catalogItemGap),
Arrangement.spacedBy(JetcasterAppDefaults.gap.podcastRow),
) {
TvLazyRow(
contentPadding = contentPadding,
Expand Down Expand Up @@ -189,7 +206,7 @@ private fun EpisodeRow(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
horizontalArrangement: Arrangement.Horizontal =
Arrangement.spacedBy(JetcasterAppDefaults.gapSettings.catalogItemGap),
Arrangement.spacedBy(JetcasterAppDefaults.gap.episodeRow),
) {
TvLazyRow(
contentPadding = contentPadding,
Expand Down Expand Up @@ -261,7 +278,7 @@ private fun EpisodeMetaData(episode: EpisodeToPodcast, modifier: Modifier = Modi
Text(text = episode.podcast.title, style = MaterialTheme.typography.bodySmall)
if (duration != null) {
Spacer(
modifier = Modifier.height(JetcasterAppDefaults.gapSettings.catalogItemGap * 0.8f)
modifier = Modifier.height(JetcasterAppDefaults.gap.podcastRow * 0.8f)
)
EpisodeDataAndDuration(offsetDateTime = publishedDate, duration = duration)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fun ErrorState(
Button(
onClick = backToHome,
modifier
.padding(top = JetcasterAppDefaults.gapSettings.catalogItemGap)
.padding(top = JetcasterAppDefaults.gap.podcastRow)
.focusRequester(focusRequester)
) {
Text(text = stringResource(R.string.label_back_to_home))
Expand Down
Loading