implement reusable MediaSynopsis component

- Create `MediaSynopsis` as a common UI component with support for expandable text, custom styling, and overflow detection.
- Refactor `EpisodeComponents`, `MovieComponents`, and `SeriesScreen` to use the new `MediaSynopsis` component.
- Standardize the synopsis layout across different media detail screens.
This commit is contained in:
2026-01-24 14:10:53 +01:00
parent bc6938fe93
commit d102d80c09
4 changed files with 88 additions and 35 deletions

View File

@@ -33,6 +33,7 @@ import hu.bbara.purefin.common.ui.MediaCastMember
import hu.bbara.purefin.common.ui.MediaCastRow import hu.bbara.purefin.common.ui.MediaCastRow
import hu.bbara.purefin.common.ui.MediaGhostIconButton import hu.bbara.purefin.common.ui.MediaGhostIconButton
import hu.bbara.purefin.common.ui.MediaMetaChip import hu.bbara.purefin.common.ui.MediaMetaChip
import hu.bbara.purefin.common.ui.MediaSynopsis
import hu.bbara.purefin.common.ui.components.MediaActionButton import hu.bbara.purefin.common.ui.components.MediaActionButton
import hu.bbara.purefin.common.ui.components.MediaPlayButton import hu.bbara.purefin.common.ui.components.MediaPlayButton
import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings
@@ -105,18 +106,8 @@ internal fun EpisodeDetails(
} }
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Text( MediaSynopsis(
text = "Synopsis", synopsis = episode.synopsis
color = scheme.onBackground,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = episode.synopsis,
color = scheme.onSurfaceVariant,
fontSize = 15.sp,
lineHeight = 22.sp
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))

View File

@@ -33,6 +33,7 @@ import hu.bbara.purefin.common.ui.MediaCastMember
import hu.bbara.purefin.common.ui.MediaCastRow import hu.bbara.purefin.common.ui.MediaCastRow
import hu.bbara.purefin.common.ui.MediaGhostIconButton import hu.bbara.purefin.common.ui.MediaGhostIconButton
import hu.bbara.purefin.common.ui.MediaMetaChip import hu.bbara.purefin.common.ui.MediaMetaChip
import hu.bbara.purefin.common.ui.MediaSynopsis
import hu.bbara.purefin.common.ui.components.MediaActionButton import hu.bbara.purefin.common.ui.components.MediaActionButton
import hu.bbara.purefin.common.ui.components.MediaPlayButton import hu.bbara.purefin.common.ui.components.MediaPlayButton
import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings
@@ -106,18 +107,8 @@ internal fun MovieDetails(
} }
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Text( MediaSynopsis(
text = "Synopsis", synopsis = movie.synopsis
color = scheme.onBackground,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = movie.synopsis,
color = scheme.onSurfaceVariant,
fontSize = 15.sp,
lineHeight = 22.sp
) )
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))

View File

@@ -22,6 +22,7 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.app.content.ContentMockData import hu.bbara.purefin.app.content.ContentMockData
import hu.bbara.purefin.common.ui.PurefinWaitingScreen import hu.bbara.purefin.common.ui.PurefinWaitingScreen
import hu.bbara.purefin.common.ui.MediaSynopsis
import hu.bbara.purefin.common.ui.components.MediaHero import hu.bbara.purefin.common.ui.components.MediaHero
import hu.bbara.purefin.navigation.ItemDto import hu.bbara.purefin.navigation.ItemDto
@@ -96,17 +97,12 @@ private fun SeriesScreenInternal(
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
SeriesActionButtons() SeriesActionButtons()
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Text( MediaSynopsis(
text = "Synopsis", synopsis = series.synopsis,
color = scheme.onBackground, bodyColor = textMutedStrong,
fontSize = 18.sp, bodyFontSize = 13.sp,
fontWeight = FontWeight.Bold bodyLineHeight = null,
) titleSpacing = 8.dp
Spacer(modifier = Modifier.height(8.dp))
Text(
text = series.synopsis,
color = textMutedStrong,
fontSize = 13.sp,
) )
Spacer(modifier = Modifier.height(28.dp)) Spacer(modifier = Modifier.height(28.dp))
Text( Text(

View File

@@ -0,0 +1,75 @@
package hu.bbara.purefin.common.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnit.Companion.Unspecified
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun MediaSynopsis(
synopsis: String,
modifier: Modifier = Modifier,
title: String = "Synopsis",
titleColor: Color = MaterialTheme.colorScheme.onBackground,
bodyColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
titleFontSize: TextUnit = 18.sp,
bodyFontSize: TextUnit = 15.sp,
bodyLineHeight: TextUnit? = 22.sp,
titleSpacing: Dp = 12.dp,
collapsedLines: Int = 3,
collapseInitially: Boolean = true
) {
var isExpanded by remember(synopsis) { mutableStateOf(!collapseInitially) }
var isOverflowing by remember(synopsis) { mutableStateOf(false) }
val containerModifier = if (isOverflowing) {
modifier.clickable(role = Role.Button) { isExpanded = !isExpanded }
} else {
modifier
}
Column(modifier = containerModifier) {
Text(
text = title,
color = titleColor,
fontSize = titleFontSize,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(titleSpacing))
Text(
text = synopsis,
color = bodyColor,
fontSize = bodyFontSize,
lineHeight = bodyLineHeight ?: Unspecified,
maxLines = if (isExpanded) Int.MAX_VALUE else collapsedLines,
overflow = if (isExpanded) TextOverflow.Clip else TextOverflow.Ellipsis,
onTextLayout = { result ->
val overflowed = if (isExpanded) {
result.lineCount > collapsedLines
} else {
result.hasVisualOverflow || result.lineCount > collapsedLines
}
if (overflowed != isOverflowing) {
isOverflowing = overflowed
}
}
)
}
}