Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class TestEpisodeStore : EpisodeStore {
it + episodes
}

override suspend fun deleteEpisode(episode: Episode) =
episodesFlow.update {
it - episode
}

override suspend fun isEmpty(): Boolean =
episodesFlow.first().isEmpty()
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ interface EpisodeStore {
*/
suspend fun addEpisodes(episodes: Collection<Episode>)

/**
* Deletes an [Episode] from this store.
*/
suspend fun deleteEpisode(episode: Episode)

suspend fun isEmpty(): Boolean
}

Expand Down Expand Up @@ -86,6 +91,7 @@ class LocalEpisodeStore(
): Flow<List<EpisodeToPodcast>> {
return episodesDao.episodesForPodcastUri(podcastUri, limit)
}

/**
* Returns a list of episodes for the given podcast URIs ordering by most recently published
* to least recently published.
Expand All @@ -104,5 +110,12 @@ class LocalEpisodeStore(
override suspend fun addEpisodes(episodes: Collection<Episode>) =
episodesDao.insertAll(episodes)

/**
* Deletes an [Episode] from this store.
*/
override suspend fun deleteEpisode(episode: Episode) {
episodesDao.delete(episode)
}

override suspend fun isEmpty(): Boolean = episodesDao.count() == 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ fun PodcastImage(
podcastImageUrl: String,
contentDescription: String?,
modifier: Modifier = Modifier,
// TODO: Remove the nested component modifier when shared elements are applied to entire app
imageModifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Crop,
placeholderBrush: Brush = thumbnailPlaceholderDefaultBrush(),
) {
Expand Down Expand Up @@ -80,7 +82,7 @@ fun PodcastImage(
}
else -> {
Box(
modifier = Modifier
modifier = modifier
.background(placeholderBrush)
.fillMaxSize()

Expand All @@ -92,7 +94,7 @@ fun PodcastImage(
painter = imageLoader,
contentDescription = contentDescription,
contentScale = contentScale,
modifier = modifier,
modifier = modifier.then(imageModifier)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import java.time.OffsetDateTime
*/
data class EpisodeInfo(
val uri: String = "",
val podcastUri: String = "",
val title: String = "",
val subTitle: String = "",
val summary: String = "",
Expand All @@ -36,10 +37,23 @@ data class EpisodeInfo(
fun Episode.asExternalModel(): EpisodeInfo =
EpisodeInfo(
uri = uri,
podcastUri = podcastUri,
title = title,
subTitle = subtitle ?: "",
summary = summary ?: "",
author = author ?: "",
published = published,
duration = duration,
)

fun EpisodeInfo.asDaoModel(): Episode =
Episode(
uri = uri,
title = title,
subtitle = subTitle,
summary = summary,
author = author,
published = published,
duration = duration,
podcastUri = podcastUri
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@
* limitations under the License.
*/

@file:OptIn(ExperimentalSharedTransitionApi::class)

package com.example.jetcaster.ui

import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.res.stringResource
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
Expand All @@ -30,32 +37,46 @@ import com.example.jetcaster.R
import com.example.jetcaster.ui.home.MainScreen
import com.example.jetcaster.ui.player.PlayerScreen

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun JetcasterApp(
displayFeatures: List<DisplayFeature>,
appState: JetcasterAppState = rememberJetcasterAppState()
) {
val adaptiveInfo = currentWindowAdaptiveInfo()
if (appState.isOnline) {
NavHost(
navController = appState.navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) { backStackEntry ->
MainScreen(
windowSizeClass = adaptiveInfo.windowSizeClass,
navigateToPlayer = { episode ->
appState.navigateToPlayer(episode.uri, backStackEntry)
SharedTransitionLayout {
CompositionLocalProvider(
LocalSharedTransitionScope provides this
) {
NavHost(
navController = appState.navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) { backStackEntry ->
CompositionLocalProvider(
LocalAnimatedVisibilityScope provides this
) {
MainScreen(
windowSizeClass = adaptiveInfo.windowSizeClass,
navigateToPlayer = { episode ->
appState.navigateToPlayer(episode.uri, backStackEntry)
},
)
}
}
)
}
composable(Screen.Player.route) {
PlayerScreen(
windowSizeClass = adaptiveInfo.windowSizeClass,
displayFeatures = displayFeatures,
onBackPress = appState::navigateBack
)
composable(Screen.Player.route) {
CompositionLocalProvider(
LocalAnimatedVisibilityScope provides this
) {
PlayerScreen(
windowSizeClass = adaptiveInfo.windowSizeClass,
displayFeatures = displayFeatures,
onBackPress = appState::navigateBack,
)
}
}
}
}
}
} else {
Expand All @@ -76,3 +97,6 @@ fun OfflineDialog(onRetry: () -> Unit) {
}
)
}

val LocalAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null }
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@
* limitations under the License.
*/

@file:OptIn(ExperimentalFoundationApi::class)

package com.example.jetcaster.ui.home

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -148,6 +145,7 @@ data class HomeState(
val navigateToPlayer: (EpisodeInfo) -> Unit,
val onTogglePodcastFollowed: (PodcastInfo) -> Unit,
val onLibraryPodcastSelected: (PodcastInfo?) -> Unit,
val removeFromQueue: (EpisodeInfo) -> Unit = {},
val onQueueEpisode: (PlayerEpisode) -> Unit,
)

Expand Down Expand Up @@ -317,7 +315,8 @@ private fun HomeScreenReady(
navigateToPlayer = navigateToPlayer,
onTogglePodcastFollowed = viewModel::onTogglePodcastFollowed,
onLibraryPodcastSelected = viewModel::onLibraryPodcastSelected,
onQueueEpisode = viewModel::onQueueEpisode
onQueueEpisode = viewModel::onQueueEpisode,
removeFromQueue = viewModel::deleteEpisode
)

Surface {
Expand Down Expand Up @@ -481,7 +480,8 @@ private fun HomeScreen(
snackbarHostState.showSnackbar(snackBarText)
}
homeState.onQueueEpisode(it)
}
},
removeFromQueue = homeState.removeFromQueue
)
}
}
Expand All @@ -505,6 +505,7 @@ private fun HomeContent(
onTogglePodcastFollowed: (PodcastInfo) -> Unit,
onLibraryPodcastSelected: (PodcastInfo?) -> Unit,
onQueueEpisode: (PlayerEpisode) -> Unit,
removeFromQueue: (EpisodeInfo) -> Unit,
) {
val pagerState = rememberPagerState { featuredPodcasts.size }
LaunchedEffect(pagerState, featuredPodcasts) {
Expand Down Expand Up @@ -532,6 +533,7 @@ private fun HomeContent(
navigateToPlayer = navigateToPlayer,
onTogglePodcastFollowed = onTogglePodcastFollowed,
onQueueEpisode = onQueueEpisode,
removeFromQueue = removeFromQueue,
)
}

Expand All @@ -553,6 +555,7 @@ private fun HomeContentGrid(
navigateToPlayer: (EpisodeInfo) -> Unit,
onTogglePodcastFollowed: (PodcastInfo) -> Unit,
onQueueEpisode: (PlayerEpisode) -> Unit,
removeFromQueue: (EpisodeInfo) -> Unit,
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(362.dp),
Expand Down Expand Up @@ -590,7 +593,8 @@ private fun HomeContentGrid(
libraryItems(
library = library,
navigateToPlayer = navigateToPlayer,
onQueueEpisode = onQueueEpisode
onQueueEpisode = onQueueEpisode,
removeFromQueue = removeFromQueue,
)
}

Expand All @@ -602,7 +606,8 @@ private fun HomeContentGrid(
navigateToPlayer = navigateToPlayer,
onCategorySelected = onCategorySelected,
onTogglePodcastFollowed = onTogglePodcastFollowed,
onQueueEpisode = onQueueEpisode
onQueueEpisode = onQueueEpisode,
removeFromQueue = removeFromQueue,
)
}
}
Expand All @@ -625,7 +630,7 @@ private fun FollowedPodcastItem(
items = items,
onPodcastUnfollowed = onPodcastUnfollowed,
navigateToPodcastDetails = navigateToPodcastDetails,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
)

Spacer(Modifier.height(16.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import com.example.jetcaster.core.data.repository.PodcastsRepository
import com.example.jetcaster.core.domain.FilterableCategoriesUseCase
import com.example.jetcaster.core.domain.PodcastCategoryFilterUseCase
import com.example.jetcaster.core.model.CategoryInfo
import com.example.jetcaster.core.model.EpisodeInfo
import com.example.jetcaster.core.model.FilterableCategoriesModel
import com.example.jetcaster.core.model.LibraryInfo
import com.example.jetcaster.core.model.PodcastCategoryFilterResult
import com.example.jetcaster.core.model.PodcastInfo
import com.example.jetcaster.core.model.asDaoModel
import com.example.jetcaster.core.model.asExternalModel
import com.example.jetcaster.core.model.asPodcastToEpisodeInfo
import com.example.jetcaster.core.player.EpisodePlayer
Expand Down Expand Up @@ -176,6 +178,12 @@ class HomeViewModel @Inject constructor(
fun onQueueEpisode(episode: PlayerEpisode) {
episodePlayer.addToQueue(episode)
}

fun deleteEpisode(episode: EpisodeInfo) {
viewModelScope.launch {
episodeStore.deleteEpisode(episode.asDaoModel())
}
}
}

private fun List<EpisodeToPodcast>.asLibrary(): LibraryInfo =
Expand Down
Loading