mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
refactor UI styling and image handling
- Remove custom color token classes (`EpisodeColors`, `MovieColors`, `SeriesColors`, `HomeColors`) in favor of standard `MaterialTheme.colorScheme`. - Introduce `PurefinAsyncImage` component to provide consistent theme-synced placeholders for asynchronous images. - Refactor various UI components (`MediaGhostIconButton`, `MediaMetaChip`, `MediaCastRow`, `PosterCard`) to use `MaterialTheme` directly instead of custom color objects. - Simplify `MediaDetailColors` mapping logic by removing redundant conversion functions. - Update `PurefinTheme` to disable dynamic color by default. - Force light mode in `PlayerActivity` temporarily. - Replace standard Coil `AsyncImage` with `PurefinAsyncImage` across the application.
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
package hu.bbara.purefin.app.content.episode
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal data class EpisodeColors(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val background: Color,
|
||||
val surface: Color,
|
||||
val surfaceAlt: Color,
|
||||
val surfaceBorder: Color,
|
||||
val textPrimary: Color,
|
||||
val textSecondary: Color,
|
||||
val textMuted: Color,
|
||||
val textMutedStrong: Color
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun rememberEpisodeColors(): EpisodeColors {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
return remember(scheme) {
|
||||
EpisodeColors(
|
||||
primary = scheme.primary,
|
||||
onPrimary = scheme.onPrimary,
|
||||
background = scheme.background,
|
||||
surface = scheme.surface,
|
||||
surfaceAlt = scheme.surfaceVariant,
|
||||
surfaceBorder = scheme.outlineVariant,
|
||||
textPrimary = scheme.onBackground,
|
||||
textSecondary = scheme.onSurface,
|
||||
textMuted = scheme.onSurfaceVariant,
|
||||
textMutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaPlayButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings
|
||||
import hu.bbara.purefin.common.ui.toMediaDetailColors
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
|
||||
@Composable
|
||||
@@ -44,7 +43,6 @@ internal fun EpisodeTopBar(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val colors = rememberEpisodeColors().toMediaDetailColors()
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -54,14 +52,13 @@ internal fun EpisodeTopBar(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MediaGhostIconButton(
|
||||
colors = colors,
|
||||
icon = Icons.Outlined.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
onClick = onBack
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
MediaGhostIconButton(colors = colors, icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })
|
||||
MediaGhostIconButton(colors = colors, icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
|
||||
MediaGhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })
|
||||
MediaGhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +69,8 @@ internal fun EpisodeDetails(
|
||||
episode: EpisodeUiModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
val context = LocalContext.current
|
||||
val playAction = remember(episode.id) {
|
||||
{
|
||||
@@ -81,11 +80,10 @@ internal fun EpisodeDetails(
|
||||
}
|
||||
}
|
||||
|
||||
val colors = rememberEpisodeColors().toMediaDetailColors()
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
@@ -95,29 +93,28 @@ internal fun EpisodeDetails(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(colors = colors, text = episode.releaseDate)
|
||||
MediaMetaChip(colors = colors, text = episode.rating)
|
||||
MediaMetaChip(colors = colors, text = episode.runtime)
|
||||
MediaMetaChip(text = episode.releaseDate)
|
||||
MediaMetaChip(text = episode.rating)
|
||||
MediaMetaChip(text = episode.runtime)
|
||||
MediaMetaChip(
|
||||
colors = colors,
|
||||
text = episode.format,
|
||||
background = colors.primary.copy(alpha = 0.2f),
|
||||
border = colors.primary.copy(alpha = 0.3f),
|
||||
textColor = colors.primary
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = "Synopsis",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = episode.synopsis,
|
||||
color = colors.textMuted,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 15.sp,
|
||||
lineHeight = 22.sp
|
||||
)
|
||||
@@ -165,13 +162,12 @@ internal fun EpisodeDetails(
|
||||
|
||||
Text(
|
||||
text = "Cast",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
MediaCastRow(
|
||||
colors = colors,
|
||||
cast = episode.cast.map { it.toMediaCastMember() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package hu.bbara.purefin.app.content.movie
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal data class MovieColors(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val background: Color,
|
||||
val surface: Color,
|
||||
val surfaceAlt: Color,
|
||||
val surfaceBorder: Color,
|
||||
val textPrimary: Color,
|
||||
val textSecondary: Color,
|
||||
val textMuted: Color,
|
||||
val textMutedStrong: Color
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun rememberMovieColors(): MovieColors {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
return remember(scheme) {
|
||||
MovieColors(
|
||||
primary = scheme.primary,
|
||||
onPrimary = scheme.onPrimary,
|
||||
background = scheme.background,
|
||||
surface = scheme.surface,
|
||||
surfaceAlt = scheme.surfaceVariant,
|
||||
surfaceBorder = scheme.outlineVariant,
|
||||
textPrimary = scheme.onBackground,
|
||||
textSecondary = scheme.onSurface,
|
||||
textMuted = scheme.onSurfaceVariant,
|
||||
textMutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaPlayButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings
|
||||
import hu.bbara.purefin.common.ui.toMediaDetailColors
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
|
||||
@Composable
|
||||
@@ -44,7 +43,7 @@ internal fun MovieTopBar(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val colors = rememberMovieColors().toMediaDetailColors()
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -54,14 +53,13 @@ internal fun MovieTopBar(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MediaGhostIconButton(
|
||||
colors = colors,
|
||||
icon = Icons.Outlined.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
onClick = onBack
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
MediaGhostIconButton(colors = colors, icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })
|
||||
MediaGhostIconButton(colors = colors, icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
|
||||
MediaGhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })
|
||||
MediaGhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +70,8 @@ internal fun MovieDetails(
|
||||
movie: MovieUiModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
val context = LocalContext.current
|
||||
val playAction = remember(movie.id) {
|
||||
{
|
||||
@@ -81,11 +81,10 @@ internal fun MovieDetails(
|
||||
}
|
||||
}
|
||||
|
||||
val colors = rememberMovieColors().toMediaDetailColors()
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = movie.title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
@@ -95,29 +94,28 @@ internal fun MovieDetails(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(colors = colors, text = movie.year)
|
||||
MediaMetaChip(colors = colors, text = movie.rating)
|
||||
MediaMetaChip(colors = colors, text = movie.runtime)
|
||||
MediaMetaChip(text = movie.year)
|
||||
MediaMetaChip(text = movie.rating)
|
||||
MediaMetaChip(text = movie.runtime)
|
||||
MediaMetaChip(
|
||||
colors = colors,
|
||||
text = movie.format,
|
||||
background = colors.primary.copy(alpha = 0.2f),
|
||||
border = colors.primary.copy(alpha = 0.3f),
|
||||
textColor = colors.primary
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = "Synopsis",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = movie.synopsis,
|
||||
color = colors.textMuted,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 15.sp,
|
||||
lineHeight = 22.sp
|
||||
)
|
||||
@@ -165,13 +163,12 @@ internal fun MovieDetails(
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = "Cast",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
MediaCastRow(
|
||||
colors = colors,
|
||||
cast = movie.cast.map { it.toMediaCastMember() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package hu.bbara.purefin.app.content.series
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal data class SeriesColors(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val background: Color,
|
||||
val surface: Color,
|
||||
val surfaceAlt: Color,
|
||||
val surfaceBorder: Color,
|
||||
val textPrimary: Color,
|
||||
val textSecondary: Color,
|
||||
val textMuted: Color,
|
||||
val textMutedStrong: Color
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun rememberSeriesColors(): SeriesColors {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
return remember(scheme) {
|
||||
SeriesColors(
|
||||
primary = scheme.primary,
|
||||
onPrimary = scheme.onPrimary,
|
||||
background = scheme.background,
|
||||
surface = scheme.surface,
|
||||
surfaceAlt = scheme.surfaceVariant,
|
||||
surfaceBorder = scheme.outlineVariant,
|
||||
textPrimary = scheme.onBackground,
|
||||
textSecondary = scheme.onSurface,
|
||||
textMuted = scheme.onSurfaceVariant,
|
||||
textMutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -46,21 +46,19 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import coil3.compose.AsyncImage
|
||||
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.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.common.ui.toMediaDetailColors
|
||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||
|
||||
@Composable
|
||||
internal fun SeriesTopBar(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val colors = rememberSeriesColors().toMediaDetailColors()
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -70,13 +68,12 @@ internal fun SeriesTopBar(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MediaGhostIconButton(
|
||||
colors = colors,
|
||||
onClick = onBack,
|
||||
icon = Icons.Outlined.ArrowBack,
|
||||
contentDescription = "Back")
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
MediaGhostIconButton(colors = colors, icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })
|
||||
MediaGhostIconButton(colors = colors, icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
|
||||
MediaGhostIconButton(icon = Icons.Outlined.Cast, contentDescription = "Cast", onClick = { })
|
||||
MediaGhostIconButton(icon = Icons.Outlined.MoreVert, contentDescription = "More", onClick = { })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +84,6 @@ internal fun SeriesHero(
|
||||
height: Dp,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val colors = rememberSeriesColors().toMediaDetailColors()
|
||||
MediaHero(
|
||||
imageUrl = imageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
@@ -99,27 +95,25 @@ internal fun SeriesHero(
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
internal fun SeriesMetaChips(series: SeriesUiModel) {
|
||||
val colors = rememberSeriesColors().toMediaDetailColors()
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(colors = colors, text = series.year)
|
||||
MediaMetaChip(colors = colors, text = series.rating)
|
||||
MediaMetaChip(colors = colors, text = series.seasons)
|
||||
MediaMetaChip(text = series.year)
|
||||
MediaMetaChip(text = series.rating)
|
||||
MediaMetaChip(text = series.seasons)
|
||||
MediaMetaChip(
|
||||
colors = colors,
|
||||
text = series.format,
|
||||
background = colors.primary.copy(alpha = 0.2f),
|
||||
border = colors.primary.copy(alpha = 0.3f),
|
||||
textColor = colors.primary
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SeriesActionButtons(modifier: Modifier = Modifier) {
|
||||
val colors = rememberSeriesColors().toMediaDetailColors()
|
||||
Row() {
|
||||
MediaActionButton(
|
||||
backgroundColor = MaterialTheme.colorScheme.secondary,
|
||||
@@ -153,9 +147,10 @@ internal fun SeasonTabs(seasons: List<SeriesSeasonUiModel>, modifier: Modifier =
|
||||
|
||||
@Composable
|
||||
private fun SeasonTab(name: String, isSelected: Boolean) {
|
||||
val colors = rememberSeriesColors()
|
||||
val color = if (isSelected) colors.primary else colors.textMutedStrong
|
||||
val borderColor = if (isSelected) colors.primary else Color.Transparent
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
val color = if (isSelected) scheme.primary else mutedStrong
|
||||
val borderColor = if (isSelected) scheme.primary else Color.Transparent
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp)
|
||||
@@ -195,13 +190,14 @@ private fun EpisodeCard(
|
||||
viewModel: SeriesViewModel = hiltViewModel(),
|
||||
episode: SeriesEpisodeUiModel
|
||||
) {
|
||||
val colors = rememberSeriesColors()
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(260.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(colors.surfaceAlt.copy(alpha = 0.6f))
|
||||
.border(1.dp, colors.surfaceBorder, RoundedCornerShape(16.dp))
|
||||
.background(scheme.surfaceVariant.copy(alpha = 0.6f))
|
||||
.border(1.dp, scheme.outlineVariant, RoundedCornerShape(16.dp))
|
||||
.padding(12.dp)
|
||||
.clickable { viewModel.onSelectEpisode(episode.id) },
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
@@ -211,10 +207,10 @@ private fun EpisodeCard(
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(16f / 9f)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(colors.surface)
|
||||
.border(1.dp, colors.surfaceBorder, RoundedCornerShape(12.dp))
|
||||
.background(scheme.surface)
|
||||
.border(1.dp, scheme.outlineVariant, RoundedCornerShape(12.dp))
|
||||
) {
|
||||
AsyncImage(
|
||||
PurefinAsyncImage(
|
||||
model = episode.imageUrl,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -223,12 +219,12 @@ private fun EpisodeCard(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(colors.background.copy(alpha = 0.2f))
|
||||
.background(scheme.background.copy(alpha = 0.2f))
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.PlayCircle,
|
||||
contentDescription = null,
|
||||
tint = colors.textPrimary,
|
||||
tint = scheme.onBackground,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(32.dp)
|
||||
@@ -237,12 +233,12 @@ private fun EpisodeCard(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(6.dp)
|
||||
.background(colors.background.copy(alpha = 0.8f), RoundedCornerShape(6.dp))
|
||||
.background(scheme.background.copy(alpha = 0.8f), RoundedCornerShape(6.dp))
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = episode.duration,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
@@ -253,7 +249,7 @@ private fun EpisodeCard(
|
||||
) {
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
@@ -261,7 +257,7 @@ private fun EpisodeCard(
|
||||
)
|
||||
Text(
|
||||
text = episode.description,
|
||||
color = colors.textMutedStrong,
|
||||
color = mutedStrong,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
maxLines = 2,
|
||||
@@ -273,9 +269,7 @@ private fun EpisodeCard(
|
||||
|
||||
@Composable
|
||||
internal fun CastRow(cast: List<SeriesCastMemberUiModel>, modifier: Modifier = Modifier) {
|
||||
val colors = rememberSeriesColors().toMediaDetailColors()
|
||||
MediaCastRow(
|
||||
colors = colors,
|
||||
cast = cast.map { it.toMediaCastMember() },
|
||||
modifier = modifier,
|
||||
cardWidth = 84.dp,
|
||||
|
||||
@@ -53,7 +53,8 @@ private fun SeriesScreenInternal(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val colors = rememberSeriesColors()
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val textMutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
@@ -83,7 +84,7 @@ private fun SeriesScreenInternal(
|
||||
) {
|
||||
Text(
|
||||
text = series.title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 36.sp
|
||||
@@ -95,20 +96,20 @@ private fun SeriesScreenInternal(
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = "Synopsis",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = series.synopsis,
|
||||
color = colors.textMutedStrong,
|
||||
color = textMutedStrong,
|
||||
fontSize = 13.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Text(
|
||||
text = "Episodes",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
@@ -123,7 +124,7 @@ private fun SeriesScreenInternal(
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Text(
|
||||
text = "Cast",
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.outlined.Collections
|
||||
import androidx.compose.material.icons.outlined.Movie
|
||||
import androidx.compose.material.icons.outlined.Tv
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -23,7 +24,6 @@ import hu.bbara.purefin.app.home.ui.HomeDrawerContent
|
||||
import hu.bbara.purefin.app.home.ui.HomeMockData
|
||||
import hu.bbara.purefin.app.home.ui.HomeNavItem
|
||||
import hu.bbara.purefin.app.home.ui.HomeTopBar
|
||||
import hu.bbara.purefin.app.home.ui.rememberHomeColors
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
|
||||
@@ -32,7 +32,6 @@ fun HomePage(
|
||||
viewModel: HomePageViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val colors = rememberHomeColors()
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
@@ -56,13 +55,12 @@ fun HomePage(
|
||||
modifier = Modifier
|
||||
.width(280.dp)
|
||||
.fillMaxSize(),
|
||||
drawerContainerColor = colors.drawerBackground,
|
||||
drawerContentColor = colors.textPrimary
|
||||
drawerContainerColor = MaterialTheme.colorScheme.surface,
|
||||
drawerContentColor = MaterialTheme.colorScheme.onBackground
|
||||
) {
|
||||
HomeDrawerContent(
|
||||
title = "Jellyfin",
|
||||
subtitle = "Library Dashboard",
|
||||
colors = colors,
|
||||
primaryNavItems = libraries,
|
||||
secondaryNavItems = HomeMockData.secondaryNavItems,
|
||||
user = HomeMockData.user,
|
||||
@@ -72,18 +70,16 @@ fun HomePage(
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
containerColor = colors.background,
|
||||
contentColor = colors.textPrimary,
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
topBar = {
|
||||
HomeTopBar(
|
||||
title = "Home",
|
||||
colors = colors,
|
||||
onMenuClick = { coroutineScope.launch { drawerState.open() } }
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
HomeContent(
|
||||
colors = colors,
|
||||
continueWatching = continueWatching.value,
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -17,7 +18,6 @@ import hu.bbara.purefin.app.home.HomePageViewModel
|
||||
@Composable
|
||||
fun HomeContent(
|
||||
viewModel: HomePageViewModel = hiltViewModel(),
|
||||
colors: HomeColors,
|
||||
continueWatching: List<ContinueWatchingItem>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -28,15 +28,14 @@ fun HomeContent(
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(colors.background)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
item {
|
||||
ContinueWatchingSection(
|
||||
items = continueWatching,
|
||||
colors = colors
|
||||
items = continueWatching
|
||||
)
|
||||
}
|
||||
items(
|
||||
@@ -47,7 +46,6 @@ fun HomeContent(
|
||||
title = item.name,
|
||||
items = libraryContent[item.id] ?: emptyList(),
|
||||
action = "See All",
|
||||
colors = colors
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.outlined.Person
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
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
|
||||
@@ -32,7 +33,6 @@ import hu.bbara.purefin.app.home.HomePageViewModel
|
||||
fun HomeDrawerContent(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
colors: HomeColors,
|
||||
primaryNavItems: List<HomeNavItem>,
|
||||
secondaryNavItems: List<HomeNavItem>,
|
||||
user: HomeUser,
|
||||
@@ -41,16 +41,14 @@ fun HomeDrawerContent(
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
HomeDrawerHeader(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
colors = colors
|
||||
subtitle = subtitle
|
||||
)
|
||||
HomeDrawerNav(
|
||||
primaryItems = primaryNavItems,
|
||||
secondaryItems = secondaryNavItems,
|
||||
colors = colors,
|
||||
secondaryItems = secondaryNavItems
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
HomeDrawerFooter(user = user, colors = colors)
|
||||
HomeDrawerFooter(user = user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +56,10 @@ fun HomeDrawerContent(
|
||||
fun HomeDrawerHeader(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -70,38 +69,37 @@ fun HomeDrawerHeader(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.background(colors.primary, RoundedCornerShape(12.dp)),
|
||||
.background(scheme.primary, RoundedCornerShape(12.dp)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = "Play",
|
||||
tint = colors.onPrimary
|
||||
tint = scheme.onPrimary
|
||||
)
|
||||
}
|
||||
Column(modifier = Modifier.padding(start = 12.dp)) {
|
||||
Text(
|
||||
text = title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = colors.textSecondary,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider(color = colors.textSecondary.copy(alpha = 0.2f))
|
||||
HorizontalDivider(color = scheme.onSurfaceVariant.copy(alpha = 0.2f))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeDrawerNav(
|
||||
primaryItems: List<HomeNavItem>,
|
||||
secondaryItems: List<HomeNavItem>,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
@@ -110,16 +108,16 @@ fun HomeDrawerNav(
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
primaryItems.forEach { item ->
|
||||
HomeDrawerNavItem(item = item, colors = colors)
|
||||
HomeDrawerNavItem(item = item)
|
||||
}
|
||||
if (secondaryItems.isNotEmpty()) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 12.dp),
|
||||
color = colors.divider
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
secondaryItems.forEach { item ->
|
||||
HomeDrawerNavItem(item = item, colors = colors)
|
||||
HomeDrawerNavItem(item = item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,12 +126,12 @@ fun HomeDrawerNav(
|
||||
@Composable
|
||||
fun HomeDrawerNavItem(
|
||||
item: HomeNavItem,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: HomePageViewModel = hiltViewModel(),
|
||||
) {
|
||||
val background = if (item.selected) colors.primary.copy(alpha = 0.12f) else Color.Transparent
|
||||
val tint = if (item.selected) colors.primary else colors.textSecondary
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val background = if (item.selected) scheme.primary.copy(alpha = 0.12f) else Color.Transparent
|
||||
val tint = if (item.selected) scheme.primary else scheme.onSurfaceVariant
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -150,7 +148,7 @@ fun HomeDrawerNavItem(
|
||||
)
|
||||
Text(
|
||||
text = item.label,
|
||||
color = if (item.selected) colors.primary else colors.textPrimary,
|
||||
color = if (item.selected) scheme.primary else scheme.onBackground,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
@@ -162,30 +160,31 @@ fun HomeDrawerNavItem(
|
||||
fun HomeDrawerFooter (
|
||||
viewModel: HomePageViewModel = hiltViewModel(),
|
||||
user: HomeUser,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.background(colors.drawerFooterBackground, RoundedCornerShape(12.dp))
|
||||
.background(scheme.surfaceVariant, RoundedCornerShape(12.dp))
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
HomeAvatar(
|
||||
size = 32.dp,
|
||||
borderWidth = 1.dp,
|
||||
borderColor = colors.divider,
|
||||
backgroundColor = colors.avatarBackground,
|
||||
borderColor = scheme.outlineVariant,
|
||||
backgroundColor = scheme.primaryContainer,
|
||||
icon = Icons.Outlined.Person,
|
||||
iconTint = colors.textPrimary
|
||||
iconTint = scheme.onBackground
|
||||
)
|
||||
Column(modifier = Modifier.padding(start = 12.dp)
|
||||
.clickable {viewModel.logout()}) {
|
||||
Text(
|
||||
text = user.name,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
@@ -193,7 +192,7 @@ fun HomeDrawerFooter (
|
||||
)
|
||||
Text(
|
||||
text = user.plan,
|
||||
color = colors.textSecondary,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 11.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.PlayArrow
|
||||
import androidx.compose.material3.Button
|
||||
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
|
||||
@@ -36,9 +37,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import hu.bbara.purefin.app.home.HomePageViewModel
|
||||
import hu.bbara.purefin.common.ui.PosterCard
|
||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
@@ -46,10 +47,12 @@ import kotlin.math.nextUp
|
||||
|
||||
@Composable
|
||||
fun ContinueWatchingSection(
|
||||
items: List<ContinueWatchingItem>, colors: HomeColors, modifier: Modifier = Modifier
|
||||
items: List<ContinueWatchingItem>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
SectionHeader(
|
||||
title = "Continue Watching", action = null, colors = colors
|
||||
title = "Continue Watching",
|
||||
action = null
|
||||
)
|
||||
LazyRow(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
@@ -59,7 +62,7 @@ fun ContinueWatchingSection(
|
||||
items(
|
||||
items = items, key = { it.id }) { item ->
|
||||
ContinueWatchingCard(
|
||||
item = item, colors = colors
|
||||
item = item
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -68,10 +71,11 @@ fun ContinueWatchingSection(
|
||||
@Composable
|
||||
fun ContinueWatchingCard(
|
||||
item: ContinueWatchingItem,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: HomePageViewModel = hiltViewModel()
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
fun openItem(item: ContinueWatchingItem) {
|
||||
@@ -92,9 +96,9 @@ fun ContinueWatchingCard(
|
||||
.aspectRatio(16f / 9f)
|
||||
.shadow(12.dp, RoundedCornerShape(16.dp))
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(colors.card)
|
||||
.background(scheme.surfaceVariant)
|
||||
) {
|
||||
AsyncImage(
|
||||
PurefinAsyncImage(
|
||||
model = viewModel.getImageUrl(itemId = item.id, type = ImageType.PRIMARY),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
@@ -103,25 +107,24 @@ fun ContinueWatchingCard(
|
||||
openItem(item)
|
||||
},
|
||||
contentScale = ContentScale.Crop,
|
||||
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(colors.textPrimary.copy(alpha = 0.2f))
|
||||
.background(scheme.onBackground.copy(alpha = 0.2f))
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
.background(colors.textPrimary.copy(alpha = 0.2f))
|
||||
.background(scheme.onBackground.copy(alpha = 0.2f))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(item.progress.toFloat().nextUp().div(100))
|
||||
.background(colors.primary)
|
||||
.background(scheme.primary)
|
||||
)
|
||||
}
|
||||
Button(
|
||||
@@ -136,7 +139,7 @@ fun ContinueWatchingCard(
|
||||
Column(modifier = Modifier.padding(top = 12.dp)) {
|
||||
Text(
|
||||
text = item.primaryText,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
@@ -144,7 +147,7 @@ fun ContinueWatchingCard(
|
||||
)
|
||||
Text(
|
||||
text = item.secondaryText,
|
||||
color = colors.textSecondary,
|
||||
color = scheme.onSurfaceVariant,
|
||||
fontSize = 13.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -158,11 +161,11 @@ fun LibraryPosterSection(
|
||||
title: String,
|
||||
items: List<PosterItem>,
|
||||
action: String?,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
SectionHeader(
|
||||
title = title, action = action, colors = colors
|
||||
title = title,
|
||||
action = action
|
||||
)
|
||||
LazyRow(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
@@ -173,7 +176,6 @@ fun LibraryPosterSection(
|
||||
items = items, key = { it.id }) { item ->
|
||||
PosterCard(
|
||||
item = item,
|
||||
colors = colors,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -183,10 +185,11 @@ fun LibraryPosterSection(
|
||||
fun SectionHeader(
|
||||
title: String,
|
||||
action: String?,
|
||||
colors: HomeColors,
|
||||
modifier: Modifier = Modifier,
|
||||
onActionClick: () -> Unit = {}
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -195,12 +198,15 @@ fun SectionHeader(
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = title, color = colors.textPrimary, fontSize = 20.sp, fontWeight = FontWeight.Bold
|
||||
text = title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
if (action != null) {
|
||||
Text(
|
||||
text = action,
|
||||
color = colors.primary,
|
||||
color = scheme.primary,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.clickable { onActionClick() })
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package hu.bbara.purefin.app.home.ui
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
data class HomeColors(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val background: Color,
|
||||
val drawerBackground: Color,
|
||||
val card: Color,
|
||||
val textPrimary: Color,
|
||||
val textSecondary: Color,
|
||||
val divider: Color,
|
||||
val avatarBackground: Color,
|
||||
val avatarBorder: Color,
|
||||
val drawerFooterBackground: Color
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun rememberHomeColors(): HomeColors {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
return remember(scheme) {
|
||||
HomeColors(
|
||||
primary = scheme.primary,
|
||||
onPrimary = scheme.onPrimary,
|
||||
background = scheme.background,
|
||||
drawerBackground = scheme.surface,
|
||||
card = scheme.surfaceVariant,
|
||||
textPrimary = scheme.onBackground,
|
||||
textSecondary = scheme.onSurfaceVariant,
|
||||
divider = scheme.outlineVariant,
|
||||
avatarBackground = scheme.primaryContainer,
|
||||
avatarBorder = scheme.outline,
|
||||
drawerFooterBackground = scheme.surfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.outlined.Refresh
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -30,24 +31,25 @@ import hu.bbara.purefin.app.home.HomePageViewModel
|
||||
fun HomeTopBar(
|
||||
viewModel: HomePageViewModel = hiltViewModel(),
|
||||
title: String,
|
||||
colors: HomeColors,
|
||||
onMenuClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
actions: @Composable RowScope.() -> Unit = {
|
||||
HomeAvatar(
|
||||
size = 36.dp,
|
||||
borderWidth = 2.dp,
|
||||
borderColor = colors.avatarBorder,
|
||||
backgroundColor = colors.avatarBackground,
|
||||
borderColor = MaterialTheme.colorScheme.outline,
|
||||
backgroundColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
icon = Icons.Outlined.Person,
|
||||
iconTint = colors.onPrimary
|
||||
iconTint = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(colors.background.copy(alpha = 0.95f))
|
||||
.background(scheme.background.copy(alpha = 0.95f))
|
||||
.zIndex(1f)
|
||||
) {
|
||||
Row(
|
||||
@@ -63,12 +65,12 @@ fun HomeTopBar(
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Menu,
|
||||
contentDescription = "Menu",
|
||||
tint = colors.textPrimary
|
||||
tint = scheme.onBackground
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Person
|
||||
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
|
||||
@@ -36,94 +37,39 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import hu.bbara.purefin.app.content.episode.EpisodeColors
|
||||
import hu.bbara.purefin.app.content.movie.MovieColors
|
||||
import hu.bbara.purefin.app.content.series.SeriesColors
|
||||
|
||||
data class MediaDetailColors(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val background: Color,
|
||||
val surface: Color,
|
||||
val surfaceAlt: Color,
|
||||
val surfaceBorder: Color,
|
||||
val textPrimary: Color,
|
||||
val textSecondary: Color,
|
||||
val textMuted: Color,
|
||||
val textMutedStrong: Color
|
||||
)
|
||||
|
||||
internal fun MovieColors.toMediaDetailColors() = MediaDetailColors(
|
||||
primary = primary,
|
||||
onPrimary = onPrimary,
|
||||
background = background,
|
||||
surface = surface,
|
||||
surfaceAlt = surfaceAlt,
|
||||
surfaceBorder = surfaceBorder,
|
||||
textPrimary = textPrimary,
|
||||
textSecondary = textSecondary,
|
||||
textMuted = textMuted,
|
||||
textMutedStrong = textMutedStrong
|
||||
)
|
||||
|
||||
internal fun EpisodeColors.toMediaDetailColors() = MediaDetailColors(
|
||||
primary = primary,
|
||||
onPrimary = onPrimary,
|
||||
background = background,
|
||||
surface = surface,
|
||||
surfaceAlt = surfaceAlt,
|
||||
surfaceBorder = surfaceBorder,
|
||||
textPrimary = textPrimary,
|
||||
textSecondary = textSecondary,
|
||||
textMuted = textMuted,
|
||||
textMutedStrong = textMutedStrong
|
||||
)
|
||||
|
||||
internal fun SeriesColors.toMediaDetailColors() = MediaDetailColors(
|
||||
primary = primary,
|
||||
onPrimary = onPrimary,
|
||||
background = background,
|
||||
surface = surface,
|
||||
surfaceAlt = surfaceAlt,
|
||||
surfaceBorder = surfaceBorder,
|
||||
textPrimary = textPrimary,
|
||||
textSecondary = textSecondary,
|
||||
textMuted = textMuted,
|
||||
textMutedStrong = textMutedStrong
|
||||
)
|
||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||
|
||||
@Composable
|
||||
fun MediaGhostIconButton(
|
||||
colors: MediaDetailColors,
|
||||
icon: ImageVector,
|
||||
contentDescription: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(colors.background.copy(alpha = 0.4f))
|
||||
.background(scheme.background.copy(alpha = 0.4f))
|
||||
.clickable { onClick() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
tint = colors.textPrimary
|
||||
tint = scheme.onBackground
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MediaMetaChip(
|
||||
colors: MediaDetailColors,
|
||||
text: String,
|
||||
background: Color = colors.surfaceAlt,
|
||||
background: Color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
border: Color = Color.Transparent,
|
||||
textColor: Color = colors.textSecondary,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
@@ -153,13 +99,15 @@ data class MediaCastMember(
|
||||
|
||||
@Composable
|
||||
fun MediaCastRow(
|
||||
colors: MediaDetailColors,
|
||||
cast: List<MediaCastMember>,
|
||||
modifier: Modifier = Modifier,
|
||||
cardWidth: Dp = 96.dp,
|
||||
nameSize: TextUnit = 12.sp,
|
||||
roleSize: TextUnit = 10.sp
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
|
||||
LazyRow(
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(horizontal = 4.dp),
|
||||
@@ -171,23 +119,23 @@ fun MediaCastRow(
|
||||
modifier = Modifier
|
||||
.aspectRatio(4f / 5f)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(colors.surfaceAlt)
|
||||
.background(scheme.surfaceVariant)
|
||||
) {
|
||||
if (member.imageUrl == null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(colors.surfaceAlt.copy(alpha = 0.6f)),
|
||||
.background(scheme.surfaceVariant.copy(alpha = 0.6f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Person,
|
||||
contentDescription = null,
|
||||
tint = colors.textMutedStrong
|
||||
tint = mutedStrong
|
||||
)
|
||||
}
|
||||
} else {
|
||||
AsyncImage(
|
||||
PurefinAsyncImage(
|
||||
model = member.imageUrl,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -198,7 +146,7 @@ fun MediaCastRow(
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = member.name,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = nameSize,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
@@ -206,7 +154,7 @@ fun MediaCastRow(
|
||||
)
|
||||
Text(
|
||||
text = member.role,
|
||||
color = colors.textMutedStrong,
|
||||
color = mutedStrong,
|
||||
fontSize = roleSize,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -18,21 +19,20 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import hu.bbara.purefin.app.home.HomePageViewModel
|
||||
import hu.bbara.purefin.app.home.ui.HomeColors
|
||||
import hu.bbara.purefin.app.home.ui.PosterItem
|
||||
import hu.bbara.purefin.app.home.ui.rememberHomeColors
|
||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
|
||||
@Composable
|
||||
fun PosterCard(
|
||||
item: PosterItem,
|
||||
colors: HomeColors = rememberHomeColors(),
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: HomePageViewModel = hiltViewModel()
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
fun openItem(posterItem: PosterItem) {
|
||||
when (posterItem.type) {
|
||||
BaseItemKind.MOVIE -> viewModel.onMovieSelected(posterItem.id.toString())
|
||||
@@ -45,20 +45,20 @@ fun PosterCard(
|
||||
modifier = Modifier
|
||||
.width(144.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
PurefinAsyncImage(
|
||||
model = viewModel.getImageUrl(item.imageItemId, ImageType.PRIMARY),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.aspectRatio(2f / 3f)
|
||||
.shadow(10.dp, RoundedCornerShape(14.dp))
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.background(colors.card)
|
||||
.background(scheme.surfaceVariant)
|
||||
.clickable(onClick = { openItem(item) }),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
Text(
|
||||
text = item.title,
|
||||
color = colors.textPrimary,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(top = 8.dp, start = 4.dp, end = 4.dp, bottom = 8.dp),
|
||||
|
||||
@@ -10,7 +10,6 @@ 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(
|
||||
@@ -24,7 +23,7 @@ fun MediaHero(
|
||||
.height(height)
|
||||
.background(backgroundColor)
|
||||
) {
|
||||
AsyncImage(
|
||||
PurefinAsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package hu.bbara.purefin.common.ui.components
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import coil3.compose.AsyncImage
|
||||
|
||||
/**
|
||||
* Async image that falls back to theme-synced color blocks so loading/error states
|
||||
* stay aligned with PurefinTheme's colorScheme.
|
||||
*/
|
||||
@Composable
|
||||
fun PurefinAsyncImage(
|
||||
model: Any?,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
contentScale: ContentScale = ContentScale.Crop
|
||||
) {
|
||||
val placeholderPainter = ColorPainter(MaterialTheme.colorScheme.surfaceVariant)
|
||||
|
||||
AsyncImage(
|
||||
model = model,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
contentScale = contentScale,
|
||||
placeholder = placeholderPainter,
|
||||
error = placeholderPainter,
|
||||
fallback = placeholderPainter
|
||||
)
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class PlayerActivity : ComponentActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
PurefinTheme {
|
||||
PurefinTheme(darkTheme = false) {
|
||||
val viewModel = hiltViewModel<PlayerViewModel>()
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
|
||||
@@ -51,7 +51,7 @@ private val DarkColorScheme = darkColorScheme(
|
||||
@Composable
|
||||
fun PurefinTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
dynamicColor: Boolean = true,
|
||||
dynamicColor: Boolean = false,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
|
||||
Reference in New Issue
Block a user