refactor: Extract and generalize MediaHero component. Now MovieScreen, SeriesScreen and EpisodeScreen also use the same Component.

This commit is contained in:
2026-01-21 20:48:53 +01:00
parent 6fa8ab1c56
commit d2f5f8547a
6 changed files with 94 additions and 141 deletions

View File

@@ -27,9 +27,9 @@ 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.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -38,9 +38,9 @@ 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.MediaHero
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
@@ -68,27 +68,6 @@ internal fun EpisodeTopBar(
}
}
@Composable
internal fun EpisodeHero(
episode: EpisodeUiModel,
height: Dp,
isWide: Boolean,
onPlayClick: () -> Unit,
modifier: Modifier = Modifier
) {
val colors = rememberEpisodeColors().toMediaDetailColors()
MediaHero(
imageUrl = episode.heroImageUrl,
colors = colors,
height = height,
isWide = isWide,
modifier = modifier,
showPlayButton = true,
playButtonSize = if (isWide) 96.dp else 80.dp,
onPlayClick = onPlayClick
)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
internal fun EpisodeDetails(
@@ -164,6 +143,7 @@ internal fun EpisodeDetails(
@Composable
fun EpisodeCard(
episode: EpisodeUiModel,
backGroundColor: Color,
modifier: Modifier = Modifier,
) {
val colors = rememberEpisodeColors().toMediaDetailColors()
@@ -187,11 +167,10 @@ fun EpisodeCard(
Box(modifier = Modifier.fillMaxSize()) {
if (isWide) {
Row(modifier = Modifier.fillMaxSize()) {
EpisodeHero(
episode = episode,
MediaHero(
imageUrl = episode.heroImageUrl,
height = 300.dp,
isWide = true,
onPlayClick = playAction,
backgroundColor = backGroundColor,
modifier = Modifier
.fillMaxHeight()
.weight(0.5f)
@@ -216,11 +195,10 @@ fun EpisodeCard(
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
EpisodeHero(
episode = episode,
MediaHero(
imageUrl = episode.heroImageUrl,
backgroundColor = backGroundColor,
height = 400.dp,
isWide = false,
onPlayClick = playAction,
modifier = Modifier.fillMaxWidth()
)
EpisodeDetails(
@@ -242,7 +220,8 @@ fun EpisodeCard(
if (!isWide) {
MediaFloatingPlayButton(
colors = colors,
containerColor = colors.primary,
onContainerColor = colors.onPrimary,
onClick = playAction,
modifier = Modifier
.align(Alignment.BottomEnd)

View File

@@ -1,5 +1,6 @@
package hu.bbara.purefin.app.content.episode
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -24,7 +25,8 @@ fun EpisodeScreen(
if (episode.value != null) {
EpisodeCard(
episode = episode.value!!,
modifier = modifier
modifier = modifier,
backGroundColor = MaterialTheme.colorScheme.background
)
} else {
PurefinWaitingScreen()

View File

@@ -22,6 +22,7 @@ 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
@@ -29,7 +30,6 @@ 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.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -38,9 +38,9 @@ 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.MediaHero
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
@@ -68,27 +68,6 @@ internal fun MovieTopBar(
}
}
@Composable
internal fun MovieHero(
movie: MovieUiModel,
height: Dp,
isWide: Boolean,
onPlayClick: () -> Unit,
modifier: Modifier = Modifier
) {
val colors = rememberMovieColors().toMediaDetailColors()
MediaHero(
imageUrl = movie.heroImageUrl,
colors = colors,
height = height,
isWide = isWide,
modifier = modifier,
showPlayButton = true,
playButtonSize = if (isWide) 96.dp else 80.dp,
onPlayClick = onPlayClick
)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
internal fun MovieDetails(
@@ -161,12 +140,13 @@ internal fun MovieDetails(
}
}
@Composable
fun MovieCard(
movie: MovieUiModel,
modifier: Modifier = Modifier,
) {
val colors = rememberMovieColors().toMediaDetailColors()
val context = LocalContext.current
val playAction = remember(movie.id) {
{
@@ -179,7 +159,7 @@ fun MovieCard(
BoxWithConstraints(
modifier = modifier
.fillMaxSize()
.background(colors.background)
.background(MaterialTheme.colorScheme.background)
) {
val isWide = maxWidth >= 900.dp
val contentPadding = if (isWide) 32.dp else 20.dp
@@ -187,11 +167,10 @@ fun MovieCard(
Box(modifier = Modifier.fillMaxSize()) {
if (isWide) {
Row(modifier = Modifier.fillMaxSize()) {
MovieHero(
movie = movie,
MediaHero(
imageUrl = movie.heroImageUrl,
backgroundColor = MaterialTheme.colorScheme.background,
height = 300.dp,
isWide = true,
onPlayClick = playAction,
modifier = Modifier
.fillMaxHeight()
.weight(0.5f)
@@ -216,11 +195,10 @@ fun MovieCard(
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
MovieHero(
movie = movie,
MediaHero(
imageUrl = movie.heroImageUrl,
height = 400.dp,
isWide = false,
onPlayClick = playAction,
backgroundColor = MaterialTheme.colorScheme.background,
modifier = Modifier.fillMaxWidth()
)
MovieDetails(
@@ -242,7 +220,9 @@ fun MovieCard(
if (!isWide) {
MediaFloatingPlayButton(
colors = colors,
containerColor = MaterialTheme.colorScheme.primary,
onContainerColor = MaterialTheme.colorScheme.onPrimary,
onClick = playAction,
modifier = Modifier
.align(Alignment.BottomEnd)

View File

@@ -29,6 +29,7 @@ import androidx.compose.material.icons.outlined.Cast
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.PlayCircle
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -47,8 +48,8 @@ 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.MediaGhostIconButton
import hu.bbara.purefin.common.ui.MediaHero
import hu.bbara.purefin.common.ui.MediaMetaChip
import hu.bbara.purefin.common.ui.components.MediaHero
import hu.bbara.purefin.common.ui.toMediaDetailColors
@Composable
@@ -83,12 +84,9 @@ internal fun SeriesHero(
val colors = rememberSeriesColors().toMediaDetailColors()
MediaHero(
imageUrl = imageUrl,
colors = colors,
backgroundColor = MaterialTheme.colorScheme.background,
height = height,
isWide = false,
modifier = modifier,
showPlayButton = false,
horizontalGradientOnWide = false
)
}

View File

@@ -37,7 +37,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
@@ -128,71 +127,6 @@ fun MediaGhostIconButton(
}
}
@Composable
fun MediaHero(
imageUrl: String,
colors: MediaDetailColors,
height: Dp,
isWide: Boolean,
modifier: Modifier = Modifier,
showPlayButton: Boolean = false,
playButtonSize: Dp = 80.dp,
onPlayClick: (() -> Unit)? = null,
horizontalGradientOnWide: Boolean = true
) {
Box(
modifier = modifier
.height(height)
.background(colors.background)
) {
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.matchParentSize()
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
colors.background.copy(alpha = 0.4f),
colors.background
)
)
)
)
if (horizontalGradientOnWide && isWide) {
Box(
modifier = Modifier
.matchParentSize()
.background(
Brush.horizontalGradient(
colors = listOf(
Color.Transparent,
colors.background.copy(alpha = 0.8f)
)
)
)
)
}
if (showPlayButton && onPlayClick != null) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
MediaPlayButton(
colors = colors,
size = playButtonSize,
onClick = onPlayClick
)
}
}
}
}
@Composable
fun MediaMetaChip(
colors: MediaDetailColors,
@@ -469,7 +403,8 @@ fun MediaPlayButton(
@Composable
fun MediaFloatingPlayButton(
colors: MediaDetailColors,
containerColor: Color,
onContainerColor: Color,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
@@ -478,14 +413,14 @@ fun MediaFloatingPlayButton(
.size(56.dp)
.shadow(20.dp, CircleShape)
.clip(CircleShape)
.background(colors.primary)
.background(containerColor)
.clickable { onClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = "Play",
tint = colors.onPrimary
tint = onContainerColor
)
}
}

View File

@@ -0,0 +1,59 @@
package hu.bbara.purefin.common.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import coil3.compose.AsyncImage
@Composable
fun MediaHero(
imageUrl: String,
backgroundColor: Color,
height: Dp,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.height(height)
.background(backgroundColor)
) {
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.matchParentSize()
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
backgroundColor.copy(alpha = 0.4f),
backgroundColor
)
)
)
)
Box(
modifier = Modifier
.matchParentSize()
.background(
Brush.horizontalGradient(
colors = listOf(
Color.Transparent,
backgroundColor.copy(alpha = 0.8f)
)
)
)
)
}
}