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.MediaGhostIconButton
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.MediaPlayButton
import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings
@@ -105,18 +106,8 @@ internal fun EpisodeDetails(
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "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
MediaSynopsis(
synopsis = episode.synopsis
)
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.MediaGhostIconButton
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.MediaPlayButton
import hu.bbara.purefin.common.ui.components.MediaPlaybackSettings
@@ -106,18 +107,8 @@ internal fun MovieDetails(
}
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "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
MediaSynopsis(
synopsis = movie.synopsis
)
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 hu.bbara.purefin.app.content.ContentMockData
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.navigation.ItemDto
@@ -96,17 +97,12 @@ private fun SeriesScreenInternal(
Spacer(modifier = Modifier.height(24.dp))
SeriesActionButtons()
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "Synopsis",
color = scheme.onBackground,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = series.synopsis,
color = textMutedStrong,
fontSize = 13.sp,
MediaSynopsis(
synopsis = series.synopsis,
bodyColor = textMutedStrong,
bodyFontSize = 13.sp,
bodyLineHeight = null,
titleSpacing = 8.dp
)
Spacer(modifier = Modifier.height(28.dp))
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
}
}
)
}
}