Skip to content
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

Jetcaster UI updates #1498

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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 @@ -149,6 +146,7 @@ data class HomeState(
val onTogglePodcastFollowed: (PodcastInfo) -> Unit,
val onLibraryPodcastSelected: (PodcastInfo?) -> Unit,
val onQueueEpisode: (PlayerEpisode) -> Unit,
val removeFromQueue: (EpisodeInfo) -> Unit = {},
)

private val HomeState.showHomeCategoryTabs: Boolean
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