mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
Refactor media screens to use a unified hero section with gradient overlays
Moved title, metadata chips, and hero images into new `MediaHeroSection`, `EpisodeHeroSection`, and `SeriesHeroSection` components across Movie, Episode, and Series screens. This change: - Increases hero section height to 40% of the screen. - Adds a vertical gradient overlay to the hero image to improve text legibility. - Layers title and metadata directly over the hero section instead of placing them in the scrollable content body. - Cleans up unused imports and experimental annotations in component files.
This commit is contained in:
@@ -6,8 +6,6 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.fillMaxWidth
|
||||
@@ -40,7 +38,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.common.ui.MediaCastRow
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.MediaSynopsis
|
||||
import hu.bbara.purefin.common.ui.components.GhostIconButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
@@ -114,7 +111,6 @@ internal fun EpisodeTopBar(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
internal fun EpisodeDetails(
|
||||
episode: Episode,
|
||||
@@ -134,37 +130,6 @@ internal fun EpisodeDetails(
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Episode ${episode.index}",
|
||||
color = scheme.onBackground,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = episode.releaseDate)
|
||||
MediaMetaChip(text = episode.rating)
|
||||
MediaMetaChip(text = episode.runtime)
|
||||
MediaMetaChip(
|
||||
text = episode.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
MediaSynopsis(
|
||||
synopsis = episode.synopsis
|
||||
)
|
||||
|
||||
@@ -4,30 +4,44 @@ import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
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.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
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.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
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.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.core.data.navigation.EpisodeDto
|
||||
import hu.bbara.purefin.core.data.navigation.LocalNavigationBackStack
|
||||
import hu.bbara.purefin.core.data.navigation.LocalNavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.core.model.CastMember
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.shared.content.episode.EpisodeScreenViewModel
|
||||
import hu.bbara.purefin.ui.theme.AppTheme
|
||||
@@ -39,7 +53,6 @@ fun EpisodeScreen(
|
||||
viewModel: EpisodeScreenViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val navigationManager = LocalNavigationManager.current
|
||||
val backStack = LocalNavigationBackStack.current
|
||||
val previousRoute = remember(backStack) { backStack.getOrNull(backStack.lastIndex - 1) }
|
||||
|
||||
@@ -120,12 +133,7 @@ private fun EpisodeScreenInternal(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
heightFraction = 0.30f,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
EpisodeHeroSection(episode = episode)
|
||||
EpisodeDetails(
|
||||
episode = episode,
|
||||
downloadState = downloadState,
|
||||
@@ -139,6 +147,86 @@ private fun EpisodeScreenInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EpisodeHeroSection(
|
||||
episode: Episode,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
|
||||
val sectionHeight = screenHeight * 0.4f
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(sectionHeight)
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
backgroundColor = scheme.background,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Transparent,
|
||||
scheme.background.copy(alpha = 0.5f),
|
||||
scheme.background
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Episode ${episode.index}",
|
||||
color = scheme.onBackground,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
EpisodeMetaChips(episode = episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun EpisodeMetaChips(episode: Episode) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = episode.releaseDate)
|
||||
MediaMetaChip(text = episode.rating)
|
||||
MediaMetaChip(text = episode.runtime)
|
||||
MediaMetaChip(
|
||||
text = episode.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EpisodeScreenPreview() {
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.fillMaxWidth
|
||||
@@ -12,9 +11,7 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Cast
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
@@ -33,7 +30,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.common.ui.MediaCastRow
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.MediaSynopsis
|
||||
import hu.bbara.purefin.common.ui.components.GhostIconButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
@@ -89,30 +85,6 @@ internal fun MovieDetails(
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = movie.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = movie.year)
|
||||
MediaMetaChip(text = movie.rating)
|
||||
MediaMetaChip(text = movie.runtime)
|
||||
MediaMetaChip(
|
||||
text = movie.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
MediaSynopsis(
|
||||
synopsis = movie.synopsis
|
||||
)
|
||||
|
||||
@@ -4,21 +4,35 @@ import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
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.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
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.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
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.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.core.data.navigation.MovieDto
|
||||
@@ -93,12 +107,7 @@ private fun MovieScreenInternal(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
heightFraction = 0.30f,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
MediaHeroSection(movie = movie)
|
||||
MovieDetails(
|
||||
movie = movie,
|
||||
downloadState = downloadState,
|
||||
@@ -112,6 +121,69 @@ private fun MovieScreenInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MediaHeroSection(
|
||||
movie: Movie,
|
||||
modifier: Modifier = Modifier) {
|
||||
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
|
||||
val sectionHeight = screenHeight * 0.4f
|
||||
|
||||
Box (
|
||||
modifier = modifier
|
||||
.height(sectionHeight)
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Transparent,
|
||||
scheme.background.copy(alpha = 0.5f),
|
||||
scheme.background
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column (
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = movie.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = movie.year)
|
||||
MediaMetaChip(text = movie.rating)
|
||||
MediaMetaChip(text = movie.runtime)
|
||||
MediaMetaChip(
|
||||
text = movie.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MovieScreenPreview() {
|
||||
|
||||
@@ -19,7 +19,13 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -128,28 +134,13 @@ private fun SeriesScreenInternal(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = series.heroImageUrl,
|
||||
heightFraction = 0.30f,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SeriesHeroSection(series = series)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = innerPadding.calculateBottomPadding())
|
||||
) {
|
||||
Text(
|
||||
text = series.name,
|
||||
color = scheme.onBackground,
|
||||
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(
|
||||
nextUpEpisode = nextUpEpisode,
|
||||
seriesDownloadState = seriesDownloadState,
|
||||
@@ -192,6 +183,58 @@ private fun SeriesScreenInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeriesHeroSection(
|
||||
series: Series,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
|
||||
val sectionHeight = screenHeight * 0.4f
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(sectionHeight)
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = series.heroImageUrl,
|
||||
backgroundColor = scheme.background,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Transparent,
|
||||
scheme.background.copy(alpha = 0.5f),
|
||||
scheme.background
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = series.name,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 36.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SeriesMetaChips(series = series)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SeriesScreenPreview() {
|
||||
|
||||
Reference in New Issue
Block a user