mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feat: add media suggestions to home screen
Introduce a new Suggestions section on the home screen that fetches and displays recommended content from the Jellyfin API. This replaces the previous manual featured items logic with a more robust suggestion system supporting movies, series, and episodes. Key changes: - Implement `getSuggestions` in `JellyfinApiClient` to fetch video content. - Update `AppContentRepository` and its implementation to manage suggestions, including caching and background refreshing. - Add `SuggestedItem` models and update `AppViewModel` to expose suggestions as a state flow. - Replace `HomeFeaturedSection` with `SuggestionsSection` using a horizontal pager. - Implement auto-scrolling logic in `HomeContent` to ensure suggestions are visible upon initial load if the user hasn't already interacted.
This commit is contained in:
@@ -39,12 +39,6 @@ class AppViewModel @Inject constructor(
|
||||
private val _isRefreshing = MutableStateFlow(false)
|
||||
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
|
||||
|
||||
private val _url = userSessionRepository.serverUrl.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = ""
|
||||
)
|
||||
|
||||
val libraries = appContentRepository.libraries.map { libraries ->
|
||||
libraries.map {
|
||||
LibraryItem(
|
||||
@@ -61,6 +55,32 @@ class AppViewModel @Inject constructor(
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
|
||||
|
||||
val suggestions = combine(
|
||||
appContentRepository.suggestions,
|
||||
appContentRepository.movies,
|
||||
appContentRepository.series,
|
||||
appContentRepository.episodes
|
||||
) { list, moviesMap, seriesMap, episodesMap ->
|
||||
list.mapNotNull { media ->
|
||||
when (media) {
|
||||
is Media.MovieMedia -> moviesMap[media.movieId]?.let {
|
||||
SuggestedMovie(movie = it)
|
||||
}
|
||||
is Media.SeriesMedia -> seriesMap[media.seriesId]?.let {
|
||||
SuggestedSeries(series = it)
|
||||
}
|
||||
is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let {
|
||||
SuggestedEpisode(episode = it)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}.distinctBy { it.id }
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
val continueWatching = combine(
|
||||
appContentRepository.continueWatching,
|
||||
appContentRepository.movies,
|
||||
|
||||
@@ -7,6 +7,76 @@ import org.jellyfin.sdk.model.UUID
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
|
||||
|
||||
sealed interface SuggestedItem {
|
||||
val id: UUID
|
||||
val badge: String
|
||||
val title: String
|
||||
val supportingText: String
|
||||
val description: String
|
||||
val metadata: List<String>
|
||||
val imageUrl: String
|
||||
val ctaLabel: String
|
||||
val progress: Float?
|
||||
val type: BaseItemKind
|
||||
}
|
||||
|
||||
data class SuggestedEpisode (
|
||||
val episode: Episode,
|
||||
override val badge: String = "",
|
||||
override val supportingText: String = listOf("Episode ${episode.index}", episode.runtime)
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(" • "),
|
||||
override val metadata: List<String> =
|
||||
listOf(episode.releaseDate, episode.runtime, episode.rating, episode.format)
|
||||
.filter { it.isNotBlank() },
|
||||
override val ctaLabel: String = "Open",
|
||||
override val progress: Float? = episode.progress?.toFloat(),
|
||||
override val type: BaseItemKind = BaseItemKind.EPISODE,
|
||||
override val id: UUID = episode.id,
|
||||
override val title: String = episode.title,
|
||||
override val description: String = episode.synopsis,
|
||||
override val imageUrl: String = episode.heroImageUrl
|
||||
) : SuggestedItem
|
||||
|
||||
data class SuggestedSeries (
|
||||
val series: Series,
|
||||
override val badge: String = "",
|
||||
override val supportingText: String =
|
||||
if (series.unwatchedEpisodeCount > 0) {
|
||||
"${series.unwatchedEpisodeCount} unwatched episodes"
|
||||
} else {
|
||||
"${series.seasonCount} seasons"
|
||||
},
|
||||
override val metadata: List<String> =
|
||||
listOf(series.year, "${series.seasonCount} seasons").filter { it.isNotBlank() },
|
||||
override val ctaLabel: String = "Open",
|
||||
override val progress: Float? = null,
|
||||
override val type: BaseItemKind = BaseItemKind.SERIES,
|
||||
override val id: UUID = series.id,
|
||||
override val title: String = series.name,
|
||||
override val description: String = series.synopsis,
|
||||
override val imageUrl: String = series.heroImageUrl
|
||||
) : SuggestedItem
|
||||
|
||||
data class SuggestedMovie (
|
||||
val movie: Movie,
|
||||
override val badge: String = "",
|
||||
override val supportingText: String = listOf(movie.year, movie.runtime)
|
||||
.filter { it.isNotBlank() }
|
||||
.joinToString(" • "),
|
||||
override val metadata: List<String> =
|
||||
listOf(movie.year, movie.runtime, movie.rating, movie.format)
|
||||
.filter { it.isNotBlank() },
|
||||
override val ctaLabel: String = "Open",
|
||||
override val progress: Float? = movie.progress?.toFloat(),
|
||||
override val type: BaseItemKind = BaseItemKind.MOVIE,
|
||||
override val id: UUID = movie.id,
|
||||
override val title: String = movie.title,
|
||||
override val description: String = movie.synopsis,
|
||||
override val imageUrl: String = movie.heroImageUrl
|
||||
) : SuggestedItem
|
||||
|
||||
data class ContinueWatchingItem(
|
||||
val type: BaseItemKind,
|
||||
val movie: Movie? = null,
|
||||
|
||||
Reference in New Issue
Block a user