Skip to content
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 @@ -36,6 +36,7 @@ abstract class EpisodesDao : BaseDao<Episode> {
)
abstract fun episode(uri: String): Flow<Episode>

@Transaction
@Query(
"""
SELECT episodes.* FROM episodes
Expand All @@ -45,6 +46,7 @@ abstract class EpisodesDao : BaseDao<Episode> {
)
abstract fun episodeAndPodcast(episodeUri: String): Flow<EpisodeToPodcast>

@Transaction
@Query(
"""
SELECT * FROM episodes WHERE podcast_uri = :podcastUri
Expand Down Expand Up @@ -75,6 +77,7 @@ abstract class EpisodesDao : BaseDao<Episode> {
@Query("SELECT COUNT(*) FROM episodes")
abstract suspend fun count(): Int

@Transaction
@Query(
"""
SELECT * FROM episodes WHERE podcast_uri IN (:podcastUris)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,26 @@ package com.example.jetcaster.core.data.model

import com.example.jetcaster.core.data.database.model.EpisodeToPodcast
import java.time.Duration
import java.time.OffsetDateTime

/**
* Episode data with necessary information to be used within a player.
*/
data class PlayerEpisode(
val uri: String = "",
val title: String = "",
val subTitle: String = "",
val published: OffsetDateTime = OffsetDateTime.MIN,
val duration: Duration? = null,
val podcastName: String = "",
val author: String = "",
val summary: String = "",
val podcastImageUrl: String = "",
val uri: String = ""
) {
constructor(podcastInfo: PodcastInfo, episodeInfo: EpisodeInfo) : this(
title = episodeInfo.title,
subTitle = episodeInfo.subTitle,
published = episodeInfo.published,
duration = episodeInfo.duration,
podcastName = podcastInfo.title,
author = episodeInfo.author,
Expand All @@ -46,10 +49,13 @@ data class PlayerEpisode(

fun EpisodeToPodcast.toPlayerEpisode(): PlayerEpisode =
PlayerEpisode(
uri = episode.uri,
title = episode.title,
subTitle = episode.subtitle ?: "",
published = episode.published,
duration = episode.duration,
podcastName = podcast.title,
author = episode.author ?: podcast.author ?: "",
summary = episode.summary ?: "",
podcastImageUrl = podcast.imageUrl ?: "",
uri = episode.uri
)
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ interface EpisodePlayer {
*/
fun play()

/**
* Plays the specified episode
*/
fun play(playerEpisode: PlayerEpisode)

/**
* Pauses the currently played episode
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class MockEpisodePlayer(
private val coroutineScope = CoroutineScope(mainDispatcher)

private var timerJob: Job? = null

init {
coroutineScope.launch {
// Combine streams here
Expand Down Expand Up @@ -103,6 +104,32 @@ class MockEpisodePlayer(
}
}

override fun play(playerEpisode: PlayerEpisode) {
if (isPlaying.value) {
pause()
}

// Keep the currently playing episode in the queue
val playingEpisode = _currentEpisode.value
queue.update {
val previousList = if (it.contains(playerEpisode)) {
val mutableList = it.toMutableList()
mutableList.remove(playerEpisode)
mutableList
} else {
it
}
if (playingEpisode != null) {
listOf(playerEpisode, playingEpisode) + previousList
} else {
listOf(playerEpisode) + previousList
}
}

next()
play()
}

override fun pause() {
isPlaying.value = false

Expand Down
2 changes: 1 addition & 1 deletion Jetcaster/tv-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ dependencies {
implementation(libs.androidx.tv.foundation)
implementation(libs.androidx.tv.material)
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.coil.kt.compose)
Expand All @@ -85,7 +86,6 @@ dependencies {
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)


implementation(project(":core"))
implementation(project(":designsystem"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,11 @@ package com.example.jetcaster.tv.model

import androidx.compose.runtime.Immutable
import com.example.jetcaster.core.data.database.model.EpisodeToPodcast
import com.example.jetcaster.core.data.model.PlayerEpisode

@Immutable
data class EpisodeList(val member: List<EpisodeToPodcast>) : List<EpisodeToPodcast> by member

// ToDo: merge into EpisodeList as PlayerEpisode is the exposed data structure of EpisodeToPodcast
@Immutable
data class PlayerEpisodeList(val member: List<PlayerEpisode>) : List<PlayerEpisode> by member
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.VideoLibrary
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
Expand All @@ -40,13 +39,13 @@ 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.library.LibraryScreen
import com.example.jetcaster.tv.ui.player.PlayerScreen
import com.example.jetcaster.tv.ui.podcast.PodcastScreen
import com.example.jetcaster.tv.ui.profile.ProfileScreen
import com.example.jetcaster.tv.ui.search.SearchScreen
import com.example.jetcaster.tv.ui.settings.SettingsScreen
import com.example.jetcaster.tv.ui.theme.JetcasterAppDefaults

@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun JetcasterApp(jetcasterAppState: JetcasterAppState = rememberJetcasterAppState()) {
Route(jetcasterAppState = jetcasterAppState)
Expand Down Expand Up @@ -164,7 +163,8 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
composable(Screen.Podcast.route) {
PodcastScreen(
backToHomeScreen = jetcasterAppState::navigateToDiscover,
playEpisode = {},
playEpisode = {
},
showEpisodeDetails = { jetcasterAppState.showEpisodeDetails(it.episode.uri) },
modifier = Modifier
.padding(JetcasterAppDefaults.overScanMargin.podcast.intoPaddingValues())
Expand All @@ -175,14 +175,18 @@ private fun Route(jetcasterAppState: JetcasterAppState) {
composable(Screen.Episode.route) {
EpisodeScreen(
playEpisode = {
jetcasterAppState.playEpisode(it.uri)
jetcasterAppState.playEpisode()
},
backToHome = jetcasterAppState::navigateToDiscover,
backToHome = jetcasterAppState::backToHome,
)
}

composable(Screen.Player.route) {
Text(text = "Player")
PlayerScreen(
backToHome = jetcasterAppState::backToHome,
modifier = Modifier.fillMaxSize(),
showDetails = jetcasterAppState::showEpisodeDetails,
)
}

composable(Screen.Profile.route) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.example.jetcaster.core.data.model.PlayerEpisode

class JetcasterAppState(
val navHostController: NavHostController
Expand Down Expand Up @@ -57,9 +58,17 @@ class JetcasterAppState(
navHostController.navigate(screen.route)
}

fun playEpisode(episodeUri: String) {
val screen = Screen.Player(episodeUri)
navHostController.navigate(screen.route)
fun showEpisodeDetails(playerEpisode: PlayerEpisode) {
showEpisodeDetails(playerEpisode.uri)
}

fun playEpisode() {
navHostController.navigate(Screen.Player.route)
}

fun backToHome() {
navHostController.popBackStack()
navigateToDiscover()
}

fun navigateBack() {
Expand Down Expand Up @@ -118,13 +127,7 @@ sealed interface Screen {
}
}

data class Player(private val episodeUri: String) : Screen {
override val route = "$ROOT/$episodeUri"

companion object : Screen {
private const val ROOT = "player"
const val PARAMETER_NAME = "episodeUri"
override val route = "$ROOT/{$PARAMETER_NAME}"
}
data object Player : Screen {
override val route = "player"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@

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

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
Expand All @@ -28,6 +32,7 @@ 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
import com.example.jetcaster.core.data.model.PlayerEpisode

@Composable
internal fun Background(
Expand All @@ -41,9 +46,37 @@ internal fun Background(
)
drawRect(brush, blendMode = BlendMode.Multiply)
}
) = Background(imageUrl = podcast.imageUrl, modifier, overlay)

@Composable
internal fun Background(
episode: PlayerEpisode,
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)
}
) = Background(imageUrl = episode.podcastImageUrl, modifier, overlay)

@Composable
internal fun Background(
imageUrl: String?,
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,
model = imageUrl,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = modifier
Expand All @@ -56,3 +89,24 @@ internal fun Background(
}
)
}

@Composable
internal fun BackgroundContainer(
playerEpisode: PlayerEpisode,
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)
},
contentAlignment: Alignment = Alignment.Center,
content: @Composable BoxScope.() -> Unit
) {
Box(modifier = modifier, contentAlignment = contentAlignment) {
Background(episode = playerEpisode, overlay = overlay, modifier = Modifier.fillMaxSize())
content()
}
}
Loading