Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
34 changes: 16 additions & 18 deletions Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ import com.example.jetcaster.R
import com.example.jetcaster.core.data.database.model.Category
import com.example.jetcaster.core.data.database.model.EpisodeToPodcast
import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo
import com.example.jetcaster.core.data.model.FilterableCategory
import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult
import com.example.jetcaster.designsystem.theme.Keyline1
import com.example.jetcaster.ui.home.category.PodcastCategoryViewState
import com.example.jetcaster.ui.home.discover.DiscoverViewState
import com.example.jetcaster.ui.home.discover.discoverItems
import com.example.jetcaster.ui.home.library.libraryItems
import com.example.jetcaster.ui.theme.JetcasterTheme
Expand All @@ -102,8 +102,8 @@ fun Home(
isRefreshing = viewState.refreshing,
homeCategories = viewState.homeCategories,
selectedHomeCategory = viewState.selectedHomeCategory,
discoverViewState = viewState.discoverViewState,
podcastCategoryViewState = viewState.podcastCategoryViewState,
filterableCategories = viewState.filterableCategories,
podcastCategoryFilterResult = viewState.podcastCategoryFilterResult,
libraryEpisodes = viewState.libraryEpisodes,
onHomeCategorySelected = viewModel::onHomeCategorySelected,
onCategorySelected = viewModel::onCategorySelected,
Expand Down Expand Up @@ -159,15 +159,14 @@ fun HomeAppBar(
)
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Home(
featuredPodcasts: PersistentList<PodcastWithExtraInfo>,
isRefreshing: Boolean,
selectedHomeCategory: HomeCategory,
homeCategories: List<HomeCategory>,
discoverViewState: DiscoverViewState,
podcastCategoryViewState: PodcastCategoryViewState,
filterableCategories: List<FilterableCategory>,
podcastCategoryFilterResult: PodcastCategoryFilterResult,
libraryEpisodes: List<EpisodeToPodcast>,
modifier: Modifier = Modifier,
onPodcastUnfollowed: (String) -> Unit,
Expand Down Expand Up @@ -214,8 +213,8 @@ fun Home(
isRefreshing = isRefreshing,
selectedHomeCategory = selectedHomeCategory,
homeCategories = homeCategories,
discoverViewState = discoverViewState,
podcastCategoryViewState = podcastCategoryViewState,
filterableCategories = filterableCategories,
podcastCategoryFilterResult = podcastCategoryFilterResult,
libraryEpisodes = libraryEpisodes,
scrimColor = scrimColor,
onPodcastUnfollowed = onPodcastUnfollowed,
Expand All @@ -234,8 +233,8 @@ private fun HomeContent(
isRefreshing: Boolean,
selectedHomeCategory: HomeCategory,
homeCategories: List<HomeCategory>,
discoverViewState: DiscoverViewState,
podcastCategoryViewState: PodcastCategoryViewState,
filterableCategories: List<FilterableCategory>,
podcastCategoryFilterResult: PodcastCategoryFilterResult,
libraryEpisodes: List<EpisodeToPodcast>,
scrimColor: Color,
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -286,8 +285,8 @@ private fun HomeContent(

HomeCategory.Discover -> {
discoverItems(
discoverViewState = discoverViewState,
podcastCategoryViewState = podcastCategoryViewState,
filterableCategories = filterableCategories,
podcastCategoryFilterResult = podcastCategoryFilterResult,
navigateToPlayer = navigateToPlayer,
onCategorySelected = onCategorySelected,
onTogglePodcastFollowed = onTogglePodcastFollowed
Expand Down Expand Up @@ -472,11 +471,10 @@ fun PreviewHomeContent() {
isRefreshing = false,
homeCategories = HomeCategory.entries,
selectedHomeCategory = HomeCategory.Discover,
discoverViewState = DiscoverViewState(
categories = PreviewCategories,
selectedCategory = PreviewCategories.first(),
),
podcastCategoryViewState = PodcastCategoryViewState(
filterableCategories = PreviewCategories.map {
FilterableCategory(it, it == PreviewCategories.first())
},
podcastCategoryFilterResult = PodcastCategoryFilterResult(
topPodcasts = PreviewPodcastsWithExtraInfo,
episodes = PreviewEpisodeToPodcasts,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ import com.example.jetcaster.core.data.database.model.Category
import com.example.jetcaster.core.data.database.model.EpisodeToPodcast
import com.example.jetcaster.core.data.database.model.PodcastWithExtraInfo
import com.example.jetcaster.core.data.di.Graph
import com.example.jetcaster.core.data.repository.CategoryStore
import com.example.jetcaster.core.data.repository.EpisodeStore
import com.example.jetcaster.core.data.domain.FilterableCategoriesUseCase
import com.example.jetcaster.core.data.domain.GetLatestFollowedEpisodesUseCase
import com.example.jetcaster.core.data.domain.PodcastCategoryFilterUseCase
import com.example.jetcaster.core.data.model.FilterableCategory
import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult
import com.example.jetcaster.core.data.repository.PodcastStore
import com.example.jetcaster.core.data.repository.PodcastsRepository
import com.example.jetcaster.ui.home.category.PodcastCategoryViewState
import com.example.jetcaster.ui.home.discover.DiscoverViewState
import com.example.jetcaster.util.combine
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
Expand All @@ -36,17 +37,19 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModel(
private val podcastsRepository: PodcastsRepository = Graph.podcastRepository,
private val categoryStore: CategoryStore = Graph.categoryStore,
private val podcastStore: PodcastStore = Graph.podcastStore,
private val episodeStore: EpisodeStore = Graph.episodeStore
private val getLatestFollowedEpisodesUseCase: GetLatestFollowedEpisodesUseCase =
Graph.getLatestFollowedEpisodesUseCase,
private val podcastCategoryFilterUseCase: PodcastCategoryFilterUseCase =
Graph.podcastCategoryFilterUseCase,
private val filterableCategoriesUseCase: FilterableCategoriesUseCase =
Graph.filterableCategoriesUseCase
) : ViewModel() {
// Holds our currently selected home category
private val selectedHomeCategory = MutableStateFlow(HomeCategory.Discover)
Expand All @@ -59,64 +62,6 @@ class HomeViewModel(
// Holds the view state if the UI is refreshing for new data
private val refreshing = MutableStateFlow(false)

@OptIn(ExperimentalCoroutinesApi::class)
private val libraryEpisodes =
podcastStore.followedPodcastsSortedByLastEpisode()
.flatMapLatest { followedPodcasts ->
if (followedPodcasts.isEmpty()) {
flowOf(emptyList())
} else {
combine(
followedPodcasts.map { p ->
episodeStore.episodesInPodcast(p.podcast.uri, 5)
}
) { allEpisodes ->
allEpisodes.toList().flatten().sortedByDescending { it.episode.published }
}
}
}

private val discover = combine(
categoryStore.categoriesSortedByPodcastCount()
.onEach { categories ->
// If we haven't got a selected category yet, select the first
if (categories.isNotEmpty() && _selectedCategory.value == null) {
_selectedCategory.value = categories[0]
}
},
_selectedCategory
) { categories, selectedCategory ->
DiscoverViewState(
categories = categories,
selectedCategory = selectedCategory
)
}

@OptIn(ExperimentalCoroutinesApi::class)
private val podcastCategory = _selectedCategory.flatMapLatest { category ->
if (category == null) {
return@flatMapLatest flowOf(PodcastCategoryViewState())
}

val recentPodcastsFlow = categoryStore.podcastsInCategorySortedByPodcastCount(
category.id,
limit = 10
)

val episodesFlow = categoryStore.episodesFromPodcastsInCategory(
category.id,
limit = 20
)

// Combine our flows and collect them into the view state StateFlow
combine(recentPodcastsFlow, episodesFlow) { topPodcasts, episodes ->
PodcastCategoryViewState(
topPodcasts = topPodcasts,
episodes = episodes
)
}
}

val state: StateFlow<HomeViewState>
get() = _state

Expand All @@ -129,23 +74,25 @@ class HomeViewModel(
selectedHomeCategory,
podcastStore.followedPodcastsSortedByLastEpisode(limit = 20),
refreshing,
discover,
podcastCategory,
libraryEpisodes
filterableCategoriesUseCase(_selectedCategory),
_selectedCategory.flatMapLatest {
podcastCategoryFilterUseCase(it)
},
getLatestFollowedEpisodesUseCase()
) { homeCategories,
selectedHomeCategory,
podcasts,
refreshing,
discoverViewState,
podcastCategoryViewState,
filterableCategories,
podcastCategoryFilterResult,
libraryEpisodes ->
HomeViewState(
homeCategories = homeCategories,
selectedHomeCategory = selectedHomeCategory,
featuredPodcasts = podcasts.toPersistentList(),
refreshing = refreshing,
discoverViewState = discoverViewState,
podcastCategoryViewState = podcastCategoryViewState,
filterableCategories = filterableCategories,
podcastCategoryFilterResult = podcastCategoryFilterResult,
libraryEpisodes = libraryEpisodes,
errorMessage = null, /* TODO */
)
Expand Down Expand Up @@ -202,8 +149,8 @@ data class HomeViewState(
val refreshing: Boolean = false,
val selectedHomeCategory: HomeCategory = HomeCategory.Discover,
val homeCategories: List<HomeCategory> = emptyList(),
val discoverViewState: DiscoverViewState = DiscoverViewState(),
val podcastCategoryViewState: PodcastCategoryViewState = PodcastCategoryViewState(),
val filterableCategories: List<FilterableCategory> = emptyList(),
val podcastCategoryFilterResult: PodcastCategoryFilterResult = PodcastCategoryFilterResult(),
val libraryEpisodes: List<EpisodeToPodcast> = emptyList(),
val errorMessage: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle

data class PodcastCategoryViewState(
val topPodcasts: List<PodcastWithExtraInfo> = emptyList(),
val episodes: List<EpisodeToPodcast> = emptyList()
)

fun LazyListScope.podcastCategory(
topPodcasts: List<PodcastWithExtraInfo>,
episodes: List<EpisodeToPodcast>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,19 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.jetcaster.R
import com.example.jetcaster.core.data.database.model.Category
import com.example.jetcaster.core.data.model.FilterableCategory
import com.example.jetcaster.core.data.model.PodcastCategoryFilterResult
import com.example.jetcaster.designsystem.theme.Keyline1
import com.example.jetcaster.ui.home.category.PodcastCategoryViewState
import com.example.jetcaster.ui.home.category.podcastCategory

data class DiscoverViewState(
val categories: List<Category> = emptyList(),
val selectedCategory: Category? = null
)

fun LazyListScope.discoverItems(
discoverViewState: DiscoverViewState,
podcastCategoryViewState: PodcastCategoryViewState,
filterableCategories: List<FilterableCategory>,
podcastCategoryFilterResult: PodcastCategoryFilterResult,
navigateToPlayer: (String) -> Unit,
onCategorySelected: (Category) -> Unit,
onTogglePodcastFollowed: (String) -> Unit,
) {
if (discoverViewState.categories.isEmpty() || discoverViewState.selectedCategory == null) {
if (filterableCategories.isEmpty() || !filterableCategories.any { it.isSelected }) {
// TODO: empty state
return
}
Expand All @@ -63,8 +59,7 @@ fun LazyListScope.discoverItems(
Spacer(Modifier.height(8.dp))

PodcastCategoryTabs(
categories = discoverViewState.categories,
selectedCategory = discoverViewState.selectedCategory,
filterableCategories = filterableCategories,
onCategorySelected = onCategorySelected,
modifier = Modifier.fillMaxWidth()
)
Expand All @@ -73,8 +68,8 @@ fun LazyListScope.discoverItems(
}

podcastCategory(
topPodcasts = podcastCategoryViewState.topPodcasts,
episodes = podcastCategoryViewState.episodes,
topPodcasts = podcastCategoryFilterResult.topPodcasts,
episodes = podcastCategoryFilterResult.episodes,
navigateToPlayer = navigateToPlayer,
onTogglePodcastFollowed = onTogglePodcastFollowed
)
Expand All @@ -84,26 +79,25 @@ private val emptyTabIndicator: @Composable (List<TabPosition>) -> Unit = {}

@Composable
private fun PodcastCategoryTabs(
categories: List<Category>,
selectedCategory: Category,
filterableCategories: List<FilterableCategory>,
onCategorySelected: (Category) -> Unit,
modifier: Modifier = Modifier
) {
val selectedIndex = categories.indexOfFirst { it == selectedCategory }
val selectedIndex = filterableCategories.indexOfFirst { it.isSelected }
ScrollableTabRow(
selectedTabIndex = selectedIndex,
divider = {}, /* Disable the built-in divider */
edgePadding = Keyline1,
indicator = emptyTabIndicator,
modifier = modifier
) {
categories.forEachIndexed { index, category ->
filterableCategories.forEachIndexed { index, filterableCategory ->
Tab(
selected = index == selectedIndex,
onClick = { onCategorySelected(category) }
onClick = { onCategorySelected(filterableCategory.category) }
) {
ChoiceChipContent(
text = category.name,
text = filterableCategory.category.name,
selected = index == selectedIndex,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 16.dp)
)
Expand Down
4 changes: 4 additions & 0 deletions Jetcaster/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ dependencies {
implementation(libs.rometools.modules)

coreLibraryDesugaring(libs.core.jdk.desugaring)

// Testing
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
}
Loading