mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
feature: add content progress and autoscroll to next up media to SeriesComponents
This commit is contained in:
@@ -18,21 +18,27 @@ object ContentMockData {
|
|||||||
title = "E1: The Beginning",
|
title = "E1: The Beginning",
|
||||||
description = "The crew assembles for the first time as the anomaly begins to expand rapidly near Saturn's rings.",
|
description = "The crew assembles for the first time as the anomaly begins to expand rapidly near Saturn's rings.",
|
||||||
duration = "58m",
|
duration = "58m",
|
||||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0",
|
||||||
|
progress = 40.0,
|
||||||
|
watched = false
|
||||||
)
|
)
|
||||||
val episode2 = SeriesEpisodeUiModel(
|
val episode2 = SeriesEpisodeUiModel(
|
||||||
id = "2",
|
id = "2",
|
||||||
title = "E2: Event Horizon",
|
title = "E2: Event Horizon",
|
||||||
description = "Dr. Cole discovers a frequency embedded in the rift's radiation that suggests intelligent design.",
|
description = "Dr. Cole discovers a frequency embedded in the rift's radiation that suggests intelligent design.",
|
||||||
duration = "54m",
|
duration = "54m",
|
||||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc",
|
||||||
|
progress = 100.0,
|
||||||
|
watched = true
|
||||||
)
|
)
|
||||||
val episode3 = SeriesEpisodeUiModel(
|
val episode3 = SeriesEpisodeUiModel(
|
||||||
id = "3",
|
id = "3",
|
||||||
title = "E3: Singularity",
|
title = "E3: Singularity",
|
||||||
description = "Tension rises as the ship approaches the event horizon, and the AI begins to behave erratically.",
|
description = "Tension rises as the ship approaches the event horizon, and the AI begins to behave erratically.",
|
||||||
duration = "1h 02m",
|
duration = "1h 02m",
|
||||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U"
|
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U",
|
||||||
|
progress = 40.0,
|
||||||
|
watched = false
|
||||||
)
|
)
|
||||||
return SeriesUiModel(
|
return SeriesUiModel(
|
||||||
title = "Interstellar Horizon: The Series",
|
title = "Interstellar Horizon: The Series",
|
||||||
@@ -45,18 +51,18 @@ object ContentMockData {
|
|||||||
seasonTabs = listOf(
|
seasonTabs = listOf(
|
||||||
SeriesSeasonUiModel(
|
SeriesSeasonUiModel(
|
||||||
name = "Season 1",
|
name = "Season 1",
|
||||||
isSelected = true,
|
episodes = listOf(episode1, episode2, episode3),
|
||||||
episodes = listOf(episode1, episode2, episode3)
|
unplayedCount = 2
|
||||||
),
|
),
|
||||||
SeriesSeasonUiModel(
|
SeriesSeasonUiModel(
|
||||||
name = "Season 2",
|
name = "Season 2",
|
||||||
isSelected = false,
|
episodes = listOf(episode1, episode2, episode3),
|
||||||
episodes = listOf(episode1, episode2, episode3)
|
unplayedCount = 0
|
||||||
),
|
),
|
||||||
SeriesSeasonUiModel(
|
SeriesSeasonUiModel(
|
||||||
name = "Season 3",
|
name = "Season 3",
|
||||||
isSelected = false,
|
episodes = listOf(episode1, episode2, episode3),
|
||||||
episodes = listOf(episode1, episode2, episode3)
|
unplayedCount = 1
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
cast = listOf(
|
cast = listOf(
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.statusBarsPadding
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -169,9 +171,20 @@ private fun SeasonTab(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun EpisodeCarousel(episodes: List<SeriesEpisodeUiModel>, modifier: Modifier = Modifier) {
|
internal fun EpisodeCarousel(episodes: List<SeriesEpisodeUiModel>, modifier: Modifier = Modifier) {
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
LaunchedEffect(episodes) {
|
||||||
|
val firstUnwatchedIndex = episodes.indexOfFirst { !it.watched }.let { if (it == -1) 0 else it }
|
||||||
|
if (firstUnwatchedIndex != 0) {
|
||||||
|
listState.animateScrollToItem(firstUnwatchedIndex)
|
||||||
|
} else {
|
||||||
|
listState.scrollToItem(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
|
state = listState,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
// contentPadding = PaddingValues(horizontal = 20.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
items(episodes) { episode ->
|
items(episodes) { episode ->
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ data class SeriesEpisodeUiModel(
|
|||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val duration: String,
|
val duration: String,
|
||||||
val imageUrl: String
|
val imageUrl: String,
|
||||||
|
val watched: Boolean,
|
||||||
|
val progress: Double?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SeriesSeasonUiModel(
|
data class SeriesSeasonUiModel(
|
||||||
val name: String,
|
val name: String,
|
||||||
val isSelected: Boolean,
|
val episodes: List<SeriesEpisodeUiModel>,
|
||||||
val episodes: List<SeriesEpisodeUiModel>
|
val unplayedCount: Int?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class SeriesCastMemberUiModel(
|
data class SeriesCastMemberUiModel(
|
||||||
@@ -30,4 +32,15 @@ data class SeriesUiModel(
|
|||||||
val heroImageUrl: String,
|
val heroImageUrl: String,
|
||||||
val seasonTabs: List<SeriesSeasonUiModel>,
|
val seasonTabs: List<SeriesSeasonUiModel>,
|
||||||
val cast: List<SeriesCastMemberUiModel>
|
val cast: List<SeriesCastMemberUiModel>
|
||||||
)
|
) {
|
||||||
|
fun getNextEpisode(): SeriesEpisodeUiModel {
|
||||||
|
for (season in seasonTabs) {
|
||||||
|
for (episode in season.episodes) {
|
||||||
|
if (!episode.watched) {
|
||||||
|
return episode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seasonTabs.first().episodes.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,7 +61,14 @@ private fun SeriesScreenInternal(
|
|||||||
val textMutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
val textMutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||||
|
|
||||||
fun getDefaultSeason() : SeriesSeasonUiModel {
|
fun getDefaultSeason() : SeriesSeasonUiModel {
|
||||||
// TODO get next next episodes season selected or add logic to it.
|
for (season in series.seasonTabs) {
|
||||||
|
val firstUnwatchedEpisode = season.episodes.firstOrNull {
|
||||||
|
it.watched.not()
|
||||||
|
}
|
||||||
|
if (firstUnwatchedEpisode != null) {
|
||||||
|
return season
|
||||||
|
}
|
||||||
|
}
|
||||||
return series.seasonTabs.first()
|
return series.seasonTabs.first()
|
||||||
}
|
}
|
||||||
val selectedSeason = remember { mutableStateOf<SeriesSeasonUiModel>(getDefaultSeason()) }
|
val selectedSeason = remember { mutableStateOf<SeriesSeasonUiModel>(getDefaultSeason()) }
|
||||||
|
|||||||
@@ -79,14 +79,15 @@ class SeriesViewModel @Inject constructor(
|
|||||||
title = episode.name ?: "Unknown",
|
title = episode.name ?: "Unknown",
|
||||||
description = episode.overview ?: "",
|
description = episode.overview ?: "",
|
||||||
duration = "58m",
|
duration = "58m",
|
||||||
imageUrl = JellyfinImageHelper.toImageUrl(url = serverUrl, itemId = episode.id, type = ImageType.PRIMARY)
|
imageUrl = JellyfinImageHelper.toImageUrl(url = serverUrl, itemId = episode.id, type = ImageType.PRIMARY),
|
||||||
|
progress = episode.userData!!.playedPercentage,
|
||||||
|
watched = episode.userData!!.played
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SeriesSeasonUiModel(
|
SeriesSeasonUiModel(
|
||||||
name = season.name ?: "Unknown",
|
name = season.name ?: "Unknown",
|
||||||
episodes = episodeItemUiModels,
|
episodes = episodeItemUiModels,
|
||||||
// TODO add actual logic or remove
|
unplayedCount = season.userData!!.unplayedItemCount
|
||||||
isSelected = false,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return SeriesUiModel(
|
return SeriesUiModel(
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ class JellyfinApiClient @Inject constructor(
|
|||||||
val result = api.tvShowsApi.getSeasons(
|
val result = api.tvShowsApi.getSeasons(
|
||||||
userId = getUserId(),
|
userId = getUserId(),
|
||||||
seriesId = seriesId,
|
seriesId = seriesId,
|
||||||
|
enableUserData = true
|
||||||
)
|
)
|
||||||
Log.d("getSeasons response: {}", result.content.toString())
|
Log.d("getSeasons response: {}", result.content.toString())
|
||||||
return result.content.items
|
return result.content.items
|
||||||
|
|||||||
Reference in New Issue
Block a user