Refactor SeriesScreen components and update UI text for clarity

This commit is contained in:
2026-03-30 08:32:46 +02:00
parent 1c5bb604f8
commit 94f1eb2883
4 changed files with 41 additions and 102 deletions

View File

@@ -42,10 +42,11 @@ class SeriesScreenContentTest {
composeRule.onNodeWithText("Severance").assertIsDisplayed()
composeRule.onNodeWithText("Overview").assertIsDisplayed()
composeRule.onNodeWithText("Up Next").assertIsDisplayed()
composeRule.onNodeWithText("Continue Watching").assertIsDisplayed()
composeRule.onNodeWithTag(SeriesPlayButtonTag).assertIsDisplayed()
composeRule.onNodeWithText("Season 1").assertIsDisplayed()
composeRule.onNodeWithText("Good News About Hell").assertIsDisplayed()
composeRule.onNodeWithText("Episode 1 • 57m").assertIsDisplayed()
composeRule.onNodeWithContentDescription("Back")
.assertIsDisplayed()
.assertIsFocused()
@@ -70,7 +71,7 @@ class SeriesScreenContentTest {
composeRule.waitForIdle()
composeRule.onNodeWithText("Overview").assertIsDisplayed()
composeRule.onNodeWithText("Library Status").assertIsDisplayed()
composeRule.onNodeWithText("Choose a season below to start watching.").assertIsDisplayed()
composeRule.onNodeWithTag(SeriesFirstSeasonTabTag).assertIsDisplayed()
composeRule.onNodeWithContentDescription("Back")
.assertIsDisplayed()

View File

@@ -56,7 +56,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.common.ui.MediaCastRow
import hu.bbara.purefin.common.ui.MediaMetaChip
import hu.bbara.purefin.common.ui.components.MediaDetailsTopBar
import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle
import hu.bbara.purefin.common.ui.components.MediaProgressBar
import hu.bbara.purefin.common.ui.components.MediaResumeButton
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
@@ -228,6 +227,33 @@ internal fun SeriesHeroSection(
SeriesMetaChips(series = series)
Spacer(modifier = Modifier.height(24.dp))
if (nextUpEpisode != null) {
Text(
text = nextUpEpisode.heroStatusText(),
color = scheme.primary,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(10.dp))
Text(
text = nextUpEpisode.title,
color = scheme.onBackground,
fontSize = 22.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = "Episode ${nextUpEpisode.index}${nextUpEpisode.runtime}",
color = mutedStrong,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(24.dp))
MediaResumeButton(
text = nextUpEpisode.playButtonText(),
progress = nextUpEpisode.progress?.div(100)?.toFloat() ?: 0f,
@@ -249,48 +275,6 @@ internal fun SeriesHeroSection(
}
}
@Composable
internal fun SeriesStatusPanel(
nextUpEpisode: Episode?,
seasonCount: Int,
unwatchedEpisodeCount: Int,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
val mutedStrong = scheme.onSurfaceVariant.copy(alpha = 0.85f)
Column(modifier = modifier) {
MediaDetailSectionTitle(
text = if (nextUpEpisode != null) "Up Next" else "Library Status",
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(14.dp))
Text(
text = if (nextUpEpisode != null) {
nextUpEpisode.title
} else {
"$seasonCount seasons ready to browse"
},
color = scheme.onSurface,
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = if (nextUpEpisode != null) {
"Episode ${nextUpEpisode.index}${nextUpEpisode.runtime}"
} else {
"$unwatchedEpisodeCount unwatched episodes"
},
color = mutedStrong,
fontSize = 14.sp,
fontWeight = FontWeight.Medium
)
}
}
@Composable
private fun EpisodeCard(
viewModel: SeriesViewModel = hiltViewModel(),
@@ -409,3 +393,7 @@ internal fun CastRow(cast: List<CastMember>, modifier: Modifier = Modifier) {
private fun Episode.playButtonText(): String {
return if ((progress ?: 0.0) > 0.0 && !watched) "Resume" else "Play"
}
private fun Episode.heroStatusText(): String {
return if ((progress ?: 0.0) > 0.0 && !watched) "Continue Watching" else "Up Next"
}

View File

@@ -15,12 +15,10 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
import hu.bbara.purefin.common.ui.components.MediaDetailHeaderRow
import hu.bbara.purefin.common.ui.components.MediaDetailOverviewSection
import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle
import hu.bbara.purefin.common.ui.components.TvMediaDetailScaffold
import hu.bbara.purefin.core.data.navigation.SeriesDto
import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.core.model.Season
import hu.bbara.purefin.core.model.Series
import hu.bbara.purefin.feature.shared.content.series.SeriesViewModel
@@ -92,25 +90,13 @@ internal fun SeriesScreenContent(
)
},
heroContent = {
MediaDetailHeaderRow(
leftContent = { headerModifier ->
SeriesHeroSection(
series = series,
nextUpEpisode = nextUpEpisode,
onPlayEpisode = { onPlayEpisode(it.id) },
playFocusRequester = playFocusRequester,
firstContentFocusRequester = firstContentFocusRequester,
modifier = headerModifier
)
},
rightContent = { panelModifier ->
SeriesStatusPanel(
nextUpEpisode = nextUpEpisode,
seasonCount = series.seasonCount,
unwatchedEpisodeCount = series.unwatchedEpisodeCount,
modifier = panelModifier
)
}
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(12.dp))
}

View File

@@ -1,12 +1,10 @@
package hu.bbara.purefin.common.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -15,12 +13,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.text.font.FontWeight
@@ -29,7 +25,6 @@ import androidx.compose.ui.unit.sp
import hu.bbara.purefin.common.ui.MediaSynopsis
internal val MediaDetailHorizontalPadding = 48.dp
private val MediaDetailPanelShape = RoundedCornerShape(28.dp)
@Composable
internal fun TvMediaDetailScaffold(
@@ -81,37 +76,6 @@ internal fun TvMediaDetailScaffold(
}
}
@Composable
internal fun MediaDetailHeaderRow(
modifier: Modifier = Modifier,
leftWeight: Float = 1.1f,
rightWeight: Float = 0.9f,
verticalAlignment: Alignment.Vertical = Alignment.Bottom,
leftContent: @Composable (Modifier) -> Unit,
rightContent: @Composable ColumnScope.(Modifier) -> Unit
) {
val scheme = MaterialTheme.colorScheme
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(40.dp),
verticalAlignment = verticalAlignment
) {
leftContent(Modifier.weight(leftWeight))
Column(
modifier = Modifier
.weight(rightWeight)
.background(
color = scheme.surface.copy(alpha = 0.9f),
shape = MediaDetailPanelShape
)
.padding(28.dp)
) {
rightContent(Modifier.fillMaxWidth())
}
}
}
@Composable
internal fun MediaDetailSectionTitle(
text: String,