mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
refactor media screens and consolidate mock data
- Move `EpisodeCard` and `MovieCard` logic directly into `EpisodeScreen` and `MovieScreen` as internal components. - Delete `SeriesCard.kt` and merge its content into `SeriesScreen.kt`. - Create `ContentMockData.kt` to centralize mock data for movies, series, and episodes, replacing local mock objects. - Add Compose previews for `EpisodeScreen`, `MovieScreen`, and `SeriesScreen`. - Clean up unused imports and streamline layout structures in content components.
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package hu.bbara.purefin.app.content
|
||||
|
||||
import hu.bbara.purefin.app.content.episode.EpisodeUiModel
|
||||
import hu.bbara.purefin.app.content.movie.MovieUiModel
|
||||
import hu.bbara.purefin.app.content.series.SeriesCastMemberUiModel
|
||||
import hu.bbara.purefin.app.content.series.SeriesEpisodeUiModel
|
||||
import hu.bbara.purefin.app.content.series.SeriesSeasonUiModel
|
||||
import hu.bbara.purefin.app.content.series.SeriesUiModel
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import hu.bbara.purefin.app.content.episode.CastMember as EpisodeCastMember
|
||||
import hu.bbara.purefin.app.content.movie.CastMember as MovieCastMember
|
||||
|
||||
object ContentMockData {
|
||||
fun series(): SeriesUiModel {
|
||||
val heroUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuD3hBjDpw00tDCQsK5xNcnJra301k1T4LksWVZzHieH9KHQItEQkVzhwevJvf8RkaQKdVKvObzRlfDDqa3_PNwLUlUQc1LpDih8p94VTGobEV62qi7QrmNyQm_o55KRMNWiTG3zLLpblGqo3uUNQcYmPFqfNML95dClXQ4lQNl85-zgerPPAbGPr23dswbIYCigyTAaXgrmdV_nbNQ5LdDB0Wh5cMHtP0uxz6k3ARjNom6clhphGIUF9e6YSvKuwuiZ-1lMYFg8C_4"
|
||||
val episode1 = SeriesEpisodeUiModel(
|
||||
id = "1",
|
||||
title = "E1: The Beginning",
|
||||
description = "The crew assembles for the first time as the anomaly begins to expand rapidly near Saturn's rings.",
|
||||
duration = "58m",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
||||
)
|
||||
val episode2 = SeriesEpisodeUiModel(
|
||||
id = "2",
|
||||
title = "E2: Event Horizon",
|
||||
description = "Dr. Cole discovers a frequency embedded in the rift's radiation that suggests intelligent design.",
|
||||
duration = "54m",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
||||
)
|
||||
val episode3 = SeriesEpisodeUiModel(
|
||||
id = "3",
|
||||
title = "E3: Singularity",
|
||||
description = "Tension rises as the ship approaches the event horizon, and the AI begins to behave erratically.",
|
||||
duration = "1h 02m",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U"
|
||||
)
|
||||
return SeriesUiModel(
|
||||
title = "Interstellar Horizon: The Series",
|
||||
year = "2024",
|
||||
rating = "TV-MA",
|
||||
seasons = "3 Seasons",
|
||||
format = "4K HDR",
|
||||
synopsis = "When a mysterious cosmic rift appears near Saturn, a team of seasoned astronauts and theoretical physicists must embark on a high-stakes voyage across dimensions. They seek to unlock the secrets of time-dilated anomalies that threaten the very fabric of human existence on Earth.",
|
||||
heroImageUrl = heroUrl,
|
||||
seasonTabs = listOf(
|
||||
SeriesSeasonUiModel(
|
||||
name = "Season 1",
|
||||
isSelected = true,
|
||||
episodes = listOf(episode1, episode2, episode3)
|
||||
),
|
||||
SeriesSeasonUiModel(
|
||||
name = "Season 2",
|
||||
isSelected = false,
|
||||
episodes = listOf(episode1, episode2, episode3)
|
||||
),
|
||||
SeriesSeasonUiModel(
|
||||
name = "Season 3",
|
||||
isSelected = false,
|
||||
episodes = listOf(episode1, episode2, episode3)
|
||||
)
|
||||
),
|
||||
cast = listOf(
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Marcus Thorne",
|
||||
role = "Cmdr. Vance",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Elena Rossi",
|
||||
role = "Dr. Sarah Cole",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Julian Chen",
|
||||
role = "Tech Officer Lin",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Sarah Jenkins",
|
||||
role = "Mission Pilot",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBN6_72VggBdNx7ITLvEvIA6OSre5iJI6kQiUVMpKAlYgd8TpT-Jx6DzZwGsGACLnAXOUuzT2R7mx9A9DNZcqi5BF_jSaEdeYpfcBvJttmVPAwiCiq1_PI2BwoZZH_Ccmq2AHV5lQqcYaA2rPkf4e7YLLLgpmVbGjKhncTotQtxiZvmLNzCbLUdlEb7XLgHKfjS6FU6djV9ocOo9bxZ_YtrQj-mMFvYGzCxeFYC8OF0kIV2NN3kQYH8x1X-rYMqu2-d7klJfQdhKHw"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "David Wu",
|
||||
role = "The AI (Voice)",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuCnNkjaBc2hU2zJ5hAF8iZZ_ZZvMlU79o4JtPNCP2MEfttpF0fe_BHWsMMl6h3S37FJ1dTLk8AQuvRQ_ggy1u-71xlQWULB76rT8pdZiRE7TkInQ8gwpigs84KNWbTRxVUI7Nia9RPyJeFE7egZqnT46TQWUeN8llWF9EDQ6mpfVLH0vHhKUlko39iDgMnBIequYntugSFgWJQc1jH-AxZ4OpJr_-uZGkwtQ_CVYNV69u9y107gk5BwaUFwPeipe8Bn9I655kyHIuQ"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Alex Reed",
|
||||
role = "Engineer",
|
||||
imageUrl = null
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun movie(): MovieUiModel {
|
||||
val castMembers = listOf(
|
||||
MovieCastMember(
|
||||
name = "Elena Rossi",
|
||||
role = "Dr. Sarah Cole",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
||||
),
|
||||
MovieCastMember(
|
||||
name = "Marcus Thorne",
|
||||
role = "Cmdr. Vance",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
||||
),
|
||||
MovieCastMember(
|
||||
name = "Julian Chen",
|
||||
role = "Tech Officer Lin",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U"
|
||||
)
|
||||
)
|
||||
return MovieUiModel(
|
||||
id = UUID.randomUUID(),
|
||||
title = "Interstellar Horizon",
|
||||
year = "2024",
|
||||
rating = "PG-13",
|
||||
runtime = "2h 14m",
|
||||
format = "4K HDR",
|
||||
synopsis = "A deep-space rescue crew is dispatched to intercept a derelict vessel drifting back from a temporal rift. As timelines fracture around them, they must decide which reality is worth saving.",
|
||||
heroImageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuD3hBjDpw00tDCQsK5xNcnJra301k1T4LksWVZzHieH9KHQItEQkVzhwevJvf8RkaQKdVKvObzRlfDDqa3_PNwLUlUQc1LpDih8p94VTGobEV62qi7QrmNyQm_o55KRMNWiTG3zLLpblGqo3uUNQcYmPFqfNML95dClXQ4lQNl85-zgerPPAbGPr23dswbIYCigyTAaXgrmdV_nbNQ5LdDB0Wh5cMHtP0uxz6k3ARjNom6clhphGIUF9e6YSvKuwuiZ-1lMYFg8C_4",
|
||||
audioTrack = "English (Dolby Atmos)",
|
||||
subtitles = "English, Spanish, French",
|
||||
cast = castMembers
|
||||
)
|
||||
}
|
||||
|
||||
fun episode(): EpisodeUiModel {
|
||||
val castMembers = listOf(
|
||||
EpisodeCastMember(
|
||||
name = "Elena Rossi",
|
||||
role = "Dr. Sarah Cole",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
||||
),
|
||||
EpisodeCastMember(
|
||||
name = "Marcus Thorne",
|
||||
role = "Cmdr. Vance",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
||||
),
|
||||
EpisodeCastMember(
|
||||
name = "David Wu",
|
||||
role = "The AI (Voice)",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuCnNkjaBc2hU2zJ5hAF8iZZ_ZZvMlU79o4JtPNCP2MEfttpF0fe_BHWsMMl6h3S37FJ1dTLk8AQuvRQ_ggy1u-71xlQWULB76rT8pdZiRE7TkInQ8gwpigs84KNWbTRxVUI7Nia9RPyJeFE7egZqnT46TQWUeN8llWF9EDQ6mpfVLH0vHhKUlko39iDgMnBIequYntugSFgWJQc1jH-AxZ4OpJr_-uZGkwtQ_CVYNV69u9y107gk5BwaUFwPeipe8Bn9I655kyHIuQ"
|
||||
)
|
||||
)
|
||||
return EpisodeUiModel(
|
||||
id = UUID.randomUUID(),
|
||||
title = "S1E1 · Event Horizon",
|
||||
releaseDate = "Oct 12, 2024",
|
||||
rating = "TV-MA",
|
||||
runtime = "58m",
|
||||
format = "4K HDR",
|
||||
synopsis = "As the anomaly near Saturn destabilizes, the crew boards the research vessel Helios to intercept what might be a distress signal from another timeline.",
|
||||
heroImageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0",
|
||||
audioTrack = "English (Dolby Atmos)",
|
||||
subtitles = "English, Spanish, German",
|
||||
cast = castMembers
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,20 @@
|
||||
package hu.bbara.purefin.app.content.episode
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Cast
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -36,13 +22,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.common.ui.MediaActionButtons
|
||||
import hu.bbara.purefin.common.ui.MediaCastMember
|
||||
import hu.bbara.purefin.common.ui.MediaCastRow
|
||||
import hu.bbara.purefin.common.ui.MediaFloatingPlayButton
|
||||
import hu.bbara.purefin.common.ui.MediaGhostIconButton
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.MediaPlaybackSettings
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.common.ui.toMediaDetailColors
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
|
||||
@Composable
|
||||
internal fun EpisodeTopBar(
|
||||
@@ -140,99 +123,6 @@ internal fun EpisodeDetails(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EpisodeCard(
|
||||
episode: EpisodeUiModel,
|
||||
backGroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = rememberEpisodeColors().toMediaDetailColors()
|
||||
val context = LocalContext.current
|
||||
val playAction = remember(episode.id) {
|
||||
{
|
||||
val intent = Intent(context, PlayerActivity::class.java)
|
||||
intent.putExtra("MEDIA_ID", episode.id.toString())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(colors.background)
|
||||
) {
|
||||
val isWide = maxWidth >= 900.dp
|
||||
val contentPadding = if (isWide) 32.dp else 20.dp
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (isWide) {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
height = 300.dp,
|
||||
backgroundColor = backGroundColor,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(0.5f)
|
||||
)
|
||||
EpisodeDetails(
|
||||
episode = episode,
|
||||
modifier = Modifier
|
||||
.weight(0.5f)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
start = contentPadding,
|
||||
end = contentPadding,
|
||||
top = 96.dp,
|
||||
bottom = 32.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
backgroundColor = backGroundColor,
|
||||
height = 400.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
EpisodeDetails(
|
||||
episode = episode,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = contentPadding)
|
||||
.offset(y = (-48).dp)
|
||||
.padding(bottom = 96.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EpisodeTopBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
)
|
||||
|
||||
if (!isWide) {
|
||||
MediaFloatingPlayButton(
|
||||
containerColor = colors.primary,
|
||||
onContainerColor = colors.onPrimary,
|
||||
onClick = playAction,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun CastMember.toMediaCastMember() = MediaCastMember(
|
||||
name = name,
|
||||
role = role,
|
||||
|
||||
@@ -1,13 +1,37 @@
|
||||
package hu.bbara.purefin.app.content.episode
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.app.content.ContentMockData
|
||||
import hu.bbara.purefin.common.ui.MediaFloatingPlayButton
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.common.ui.toMediaDetailColors
|
||||
import hu.bbara.purefin.navigation.ItemDto
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
|
||||
@Composable
|
||||
fun EpisodeScreen(
|
||||
@@ -22,13 +46,115 @@ fun EpisodeScreen(
|
||||
|
||||
val episode = viewModel.episode.collectAsState()
|
||||
|
||||
if (episode.value != null) {
|
||||
EpisodeCard(
|
||||
episode = episode.value!!,
|
||||
modifier = modifier,
|
||||
backGroundColor = MaterialTheme.colorScheme.background
|
||||
)
|
||||
} else {
|
||||
if (episode.value == null) {
|
||||
PurefinWaitingScreen()
|
||||
return
|
||||
}
|
||||
|
||||
EpisodeScreenInternal(
|
||||
episode = episode.value!!,
|
||||
modifier = modifier,
|
||||
backGroundColor = MaterialTheme.colorScheme.background
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EpisodeScreenInternal(
|
||||
episode: EpisodeUiModel,
|
||||
backGroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = rememberEpisodeColors().toMediaDetailColors()
|
||||
val context = LocalContext.current
|
||||
val playAction = remember(episode.id) {
|
||||
{
|
||||
val intent = Intent(context, PlayerActivity::class.java)
|
||||
intent.putExtra("MEDIA_ID", episode.id.toString())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(colors.background)
|
||||
) {
|
||||
val isWide = maxWidth >= 900.dp
|
||||
val contentPadding = if (isWide) 32.dp else 20.dp
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (isWide) {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
height = 300.dp,
|
||||
backgroundColor = backGroundColor,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(0.5f)
|
||||
)
|
||||
EpisodeDetails(
|
||||
episode = episode,
|
||||
modifier = Modifier
|
||||
.weight(0.5f)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
start = contentPadding,
|
||||
end = contentPadding,
|
||||
top = 96.dp,
|
||||
bottom = 32.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
backgroundColor = backGroundColor,
|
||||
height = 400.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
EpisodeDetails(
|
||||
episode = episode,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = contentPadding)
|
||||
.offset(y = (-48).dp)
|
||||
.padding(bottom = 96.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EpisodeTopBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
)
|
||||
|
||||
if (!isWide) {
|
||||
MediaFloatingPlayButton(
|
||||
containerColor = colors.primary,
|
||||
onContainerColor = colors.onPrimary,
|
||||
onClick = playAction,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun EpisodeScreenPreview() {
|
||||
EpisodeScreenInternal(
|
||||
episode = ContentMockData.episode(),
|
||||
backGroundColor = MaterialTheme.colorScheme.background
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
package hu.bbara.purefin.app.content.movie
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Cast
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -36,13 +22,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.common.ui.MediaActionButtons
|
||||
import hu.bbara.purefin.common.ui.MediaCastMember
|
||||
import hu.bbara.purefin.common.ui.MediaCastRow
|
||||
import hu.bbara.purefin.common.ui.MediaFloatingPlayButton
|
||||
import hu.bbara.purefin.common.ui.MediaGhostIconButton
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.MediaPlaybackSettings
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.common.ui.toMediaDetailColors
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
|
||||
@Composable
|
||||
internal fun MovieTopBar(
|
||||
@@ -140,100 +123,6 @@ internal fun MovieDetails(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
fun MovieCard(
|
||||
movie: MovieUiModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val playAction = remember(movie.id) {
|
||||
{
|
||||
val intent = Intent(context, PlayerActivity::class.java)
|
||||
intent.putExtra("MEDIA_ID", movie.id.toString())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
val isWide = maxWidth >= 900.dp
|
||||
val contentPadding = if (isWide) 32.dp else 20.dp
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (isWide) {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
height = 300.dp,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(0.5f)
|
||||
)
|
||||
MovieDetails(
|
||||
movie = movie,
|
||||
modifier = Modifier
|
||||
.weight(0.5f)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
start = contentPadding,
|
||||
end = contentPadding,
|
||||
top = 96.dp,
|
||||
bottom = 32.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
height = 400.dp,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
MovieDetails(
|
||||
movie = movie,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = contentPadding)
|
||||
.offset(y = (-48).dp)
|
||||
.padding(bottom = 96.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MovieTopBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
)
|
||||
|
||||
if (!isWide) {
|
||||
MediaFloatingPlayButton(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
onContainerColor = MaterialTheme.colorScheme.onPrimary,
|
||||
|
||||
onClick = playAction,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun CastMember.toMediaCastMember() = MediaCastMember(
|
||||
name = name,
|
||||
role = role,
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
package hu.bbara.purefin.app.content.movie
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.app.content.ContentMockData
|
||||
import hu.bbara.purefin.common.ui.MediaFloatingPlayButton
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.navigation.ItemDto
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
|
||||
@Composable
|
||||
fun MovieScreen(
|
||||
movie: ItemDto,
|
||||
viewModel: MovieScreenViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier
|
||||
movie: ItemDto, viewModel: MovieScreenViewModel = hiltViewModel(), modifier: Modifier = Modifier
|
||||
) {
|
||||
LaunchedEffect(movie.id) {
|
||||
viewModel.selectMovie(movie.id)
|
||||
@@ -21,11 +42,107 @@ fun MovieScreen(
|
||||
val movieItem = viewModel.movie.collectAsState()
|
||||
|
||||
if (movieItem.value != null) {
|
||||
MovieCard(
|
||||
movie = movieItem.value!!,
|
||||
modifier = modifier
|
||||
MovieScreenInternal(
|
||||
movie = movieItem.value!!, modifier = modifier
|
||||
)
|
||||
} else {
|
||||
PurefinWaitingScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MovieScreenInternal(
|
||||
movie: MovieUiModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val playAction = remember(movie.id) {
|
||||
{
|
||||
val intent = Intent(context, PlayerActivity::class.java)
|
||||
intent.putExtra("MEDIA_ID", movie.id.toString())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
val isWide = maxWidth >= 900.dp
|
||||
val contentPadding = if (isWide) 32.dp else 20.dp
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (isWide) {
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
height = 300.dp,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(0.5f)
|
||||
)
|
||||
MovieDetails(
|
||||
movie = movie,
|
||||
modifier = Modifier
|
||||
.weight(0.5f)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
start = contentPadding,
|
||||
end = contentPadding,
|
||||
top = 96.dp,
|
||||
bottom = 32.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
height = 400.dp,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
MovieDetails(
|
||||
movie = movie,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = contentPadding)
|
||||
.offset(y = (-48).dp)
|
||||
.padding(bottom = 96.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MovieTopBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
)
|
||||
|
||||
if (!isWide) {
|
||||
MediaFloatingPlayButton(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
onContainerColor = MaterialTheme.colorScheme.onPrimary,
|
||||
|
||||
onClick = playAction,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun MovieScreenPreview() {
|
||||
MovieScreenInternal(movie = ContentMockData.movie())
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
package hu.bbara.purefin.app.content.series
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun SeriesCard(
|
||||
series: SeriesUiModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = rememberSeriesColors()
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(colors.background)
|
||||
) {
|
||||
val heroHeight = maxHeight * 0.4f
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SeriesHero(
|
||||
imageUrl = series.heroImageUrl,
|
||||
height = heroHeight
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-96).dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
Text(
|
||||
text = series.title,
|
||||
color = colors.textPrimary,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 36.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SeriesMetaChips(series = series)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
SeriesActionButtons()
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = "Synopsis",
|
||||
color = colors.textPrimary,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = series.synopsis,
|
||||
color = colors.textMutedStrong,
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Text(
|
||||
text = "Episodes",
|
||||
color = colors.textPrimary,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
SeasonTabs(seasons = series.seasonTabs)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
EpisodeCarousel(
|
||||
episodes = series.seasonTabs.firstOrNull { it.isSelected }?.episodes
|
||||
?: series.seasonTabs.firstOrNull()?.episodes
|
||||
?: emptyList()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 0.dp, bottom = 0.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Cast",
|
||||
color = colors.textPrimary,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
CastRow(cast = series.cast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SeriesTopBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SeriesCardPreview() {
|
||||
SeriesCard(series = SeriesMockData.series())
|
||||
}
|
||||
@@ -31,88 +31,3 @@ data class SeriesUiModel(
|
||||
val seasonTabs: List<SeriesSeasonUiModel>,
|
||||
val cast: List<SeriesCastMemberUiModel>
|
||||
)
|
||||
|
||||
internal object SeriesMockData {
|
||||
fun series(): SeriesUiModel {
|
||||
val heroUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuD3hBjDpw00tDCQsK5xNcnJra301k1T4LksWVZzHieH9KHQItEQkVzhwevJvf8RkaQKdVKvObzRlfDDqa3_PNwLUlUQc1LpDih8p94VTGobEV62qi7QrmNyQm_o55KRMNWiTG3zLLpblGqo3uUNQcYmPFqfNML95dClXQ4lQNl85-zgerPPAbGPr23dswbIYCigyTAaXgrmdV_nbNQ5LdDB0Wh5cMHtP0uxz6k3ARjNom6clhphGIUF9e6YSvKuwuiZ-1lMYFg8C_4"
|
||||
val episode1 = SeriesEpisodeUiModel(
|
||||
id = "1",
|
||||
title = "E1: The Beginning",
|
||||
description = "The crew assembles for the first time as the anomaly begins to expand rapidly near Saturn's rings.",
|
||||
duration = "58m",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
||||
)
|
||||
val episode2 = SeriesEpisodeUiModel(
|
||||
id = "2",
|
||||
title = "E2: Event Horizon",
|
||||
description = "Dr. Cole discovers a frequency embedded in the rift's radiation that suggests intelligent design.",
|
||||
duration = "54m",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
||||
)
|
||||
val episode3 = SeriesEpisodeUiModel(
|
||||
id = "3",
|
||||
title = "E3: Singularity",
|
||||
description = "Tension rises as the ship approaches the event horizon, and the AI begins to behave erratically.",
|
||||
duration = "1h 02m",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U"
|
||||
)
|
||||
return SeriesUiModel(
|
||||
title = "Interstellar Horizon: The Series",
|
||||
year = "2024",
|
||||
rating = "TV-MA",
|
||||
seasons = "3 Seasons",
|
||||
format = "4K HDR",
|
||||
synopsis = "When a mysterious cosmic rift appears near Saturn, a team of seasoned astronauts and theoretical physicists must embark on a high-stakes voyage across dimensions. They seek to unlock the secrets of time-dilated anomalies that threaten the very fabric of human existence on Earth.",
|
||||
heroImageUrl = heroUrl,
|
||||
seasonTabs = listOf(
|
||||
SeriesSeasonUiModel(
|
||||
name = "Season 1",
|
||||
isSelected = true,
|
||||
episodes = listOf(episode1, episode2, episode3)
|
||||
),
|
||||
SeriesSeasonUiModel(
|
||||
name = "Season 2",
|
||||
isSelected = false,
|
||||
episodes = listOf(episode1, episode2, episode3)
|
||||
),
|
||||
SeriesSeasonUiModel(
|
||||
name = "Season 3",
|
||||
isSelected = false,
|
||||
episodes = listOf(episode1, episode2, episode3)
|
||||
)
|
||||
),
|
||||
cast = listOf(
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Marcus Thorne",
|
||||
role = "Cmdr. Vance",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuC6OPszCXCIP_FMO3BJJUrjpCtDNw9aeHYOGyOAXdqF078hDFNrH7KXbaQ7qtipz6aIPLivd8VBBffNMbeAiYIjjWjn5GMb6Xn9iiJz0D2rzhCKi0TBeFrN6tC1IXJkzQyQKJNhTnyokWy9dd-YtN65V7er7RT6hP5jdVBXhtK1xZMjlgrm1bk_FTTmKd8Afu3zPtJCaaC98Z608vav5zhYlkrdA1wKNSTWTpzwMSyDIY3pNQNPFauWf0n-iEu7QsYTAwhCG_zfxz0"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Elena Rossi",
|
||||
role = "Dr. Sarah Cole",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBExsf-wEzAVjMxasU2ImGhlreqQo9biBSN1yHyAbW8MyuhuppRw9ho7OD3vsbySSJ3kNluEgH1Qun45PmLnZWixZsFU4Qc7UGGJNKMS5Nkm4GZAsKdFvb3z_i1tkCvaXXvGpqmwI0qjFuo1QyjjhYPA5Yp3I8ZhrnDYdQv_GxbhR6Vl3mY1rbxd2BIUEE5oMTwTF-QmJztUEaViZkSGSG2VgVXZ5VAREn4xWE902OH2sysllvXQJQIaj439JIC2_Vg61m0-F-F1Vc"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Julian Chen",
|
||||
role = "Tech Officer Lin",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuA5CFDWsWYO4YxdRoLd2QfH5Su2KLhtj5xSDb8qmzWHvPE888ac_HAAj1wu1uqdFNSncdmmJ-bWsc--h6NYKxVXkhd4vHaFWi0XTJXgsR0F3cBu_l2SynSX4TMNSy5C3XWDurgeSH789byOe1HvoxHCHTJYaSf3OyEbil-NOp9g_9mZ24CIZOI79nx57CRzmooxoswycqssPpfTNkrnoYrrAczt5qbncwLM9NVU442YxyBFisr2Ds9H-CNBOakiCtaKnoJ6npznM7U"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Sarah Jenkins",
|
||||
role = "Mission Pilot",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuBN6_72VggBdNx7ITLvEvIA6OSre5iJI6kQiUVMpKAlYgd8TpT-Jx6DzZwGsGACLnAXOUuzT2R7mx9A9DNZcqi5BF_jSaEdeYpfcBvJttmVPAwiCiq1_PI2BwoZZH_Ccmq2AHV5lQqcYaA2rPkf4e7YLLLgpmVbGjKhncTotQtxiZvmLNzCbLUdlEb7XLgHKfjS6FU6djV9ocOo9bxZ_YtrQj-mMFvYGzCxeFYC8OF0kIV2NN3kQYH8x1X-rYMqu2-d7klJfQdhKHw"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "David Wu",
|
||||
role = "The AI (Voice)",
|
||||
imageUrl = "https://lh3.googleusercontent.com/aida-public/AB6AXuCnNkjaBc2hU2zJ5hAF8iZZ_ZZvMlU79o4JtPNCP2MEfttpF0fe_BHWsMMl6h3S37FJ1dTLk8AQuvRQ_ggy1u-71xlQWULB76rT8pdZiRE7TkInQ8gwpigs84KNWbTRxVUI7Nia9RPyJeFE7egZqnT46TQWUeN8llWF9EDQ6mpfVLH0vHhKUlko39iDgMnBIequYntugSFgWJQc1jH-AxZ4OpJr_-uZGkwtQ_CVYNV69u9y107gk5BwaUFwPeipe8Bn9I655kyHIuQ"
|
||||
),
|
||||
SeriesCastMemberUiModel(
|
||||
name = "Alex Reed",
|
||||
role = "Engineer",
|
||||
imageUrl = null
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
package hu.bbara.purefin.app.content.series
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.app.content.ContentMockData
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.navigation.ItemDto
|
||||
|
||||
@@ -21,7 +39,7 @@ fun SeriesScreen(
|
||||
val series = viewModel.series.collectAsState()
|
||||
|
||||
if (series.value != null) {
|
||||
SeriesCard(
|
||||
SeriesScreenInternal(
|
||||
series = series.value!!,
|
||||
modifier = modifier
|
||||
)
|
||||
@@ -29,3 +47,110 @@ fun SeriesScreen(
|
||||
PurefinWaitingScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeriesScreenInternal(
|
||||
series: SeriesUiModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = rememberSeriesColors()
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(colors.background)
|
||||
) {
|
||||
val heroHeight = maxHeight * 0.4f
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SeriesHero(
|
||||
imageUrl = series.heroImageUrl,
|
||||
height = heroHeight
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.offset(y = (-96).dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
Text(
|
||||
text = series.title,
|
||||
color = colors.textPrimary,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 36.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SeriesMetaChips(series = series)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
SeriesActionButtons()
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = "Synopsis",
|
||||
color = colors.textPrimary,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = series.synopsis,
|
||||
color = colors.textMutedStrong,
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Text(
|
||||
text = "Episodes",
|
||||
color = colors.textPrimary,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
SeasonTabs(seasons = series.seasonTabs)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
EpisodeCarousel(
|
||||
episodes = series.seasonTabs.firstOrNull { it.isSelected }?.episodes
|
||||
?: series.seasonTabs.firstOrNull()?.episodes
|
||||
?: emptyList()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 0.dp, bottom = 0.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Cast",
|
||||
color = colors.textPrimary,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
CastRow(cast = series.cast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SeriesTopBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SeriesScreenPreview() {
|
||||
SeriesScreenInternal(series = ContentMockData.series())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user