From 1efbab9e6307466be73869b770d938dc4c3ff769 Mon Sep 17 00:00:00 2001 From: ReichiMD Date: Fri, 4 Sep 2026 19:02:01 +0000 Subject: [PATCH 1/2] feat(details): put the age certification in a rounded chip Show the age certification in a small rounded chip instead of plain text in the metadata row of the movie and show details screens, so it reads as a certification rather than as one more value next to the runtime. Reuses the "In Cinema" pill pattern from the same file (Box + RoundedCornerShape(6.dp) + padding 10.dp/4.dp) with a neutral translucent surface instead of a solid colour, and drops the separator in front of the certification in both layouts - the chip separates itself. Presentation only: no new strings, no change to the certification logic or the models. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FS332afDiMApwV6SzEpvrf --- .../tv/ui/screens/details/DetailsScreen.kt | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt index 4d5f0db97..4f931def2 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt @@ -1578,19 +1578,11 @@ private fun DetailsContent( ) } item.contentRating?.let { contentRating -> - if (ratingValue > 0f || displayDate.isNotEmpty() || hasDuration) { - MobileMetadataSeparator() - } - Text( + ContentRatingChip( text = contentRating, - style = ArflixTypography.caption.copy( - fontSize = 13.sp, - fontWeight = FontWeight.SemiBold, - shadow = textShadow - ), - color = Color.White.copy(alpha = 0.78f), - maxLines = 1, - overflow = TextOverflow.Ellipsis + fontWeight = FontWeight.SemiBold, + textColor = Color.White.copy(alpha = 0.78f), + textShadow = textShadow ) } } @@ -2237,16 +2229,11 @@ private fun DetailsContent( } item.contentRating?.let { contentRating -> - Text(text = "|", style = separatorStyle, color = Color.White.copy(alpha = 0.7f)) - Text( + ContentRatingChip( text = contentRating, - style = ArflixTypography.caption.copy( - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - shadow = textShadow - ), - color = Color.White, - maxLines = 1 + fontWeight = FontWeight.Bold, + textColor = Color.White, + textShadow = textShadow ) } } @@ -3743,6 +3730,36 @@ private fun MobileMetadataSeparator() { ) } +/** + * Age certification chip — same rounded shape and padding as the "In Cinema" pill, + * but on a neutral translucent surface instead of a solid colour. + */ +@Composable +private fun ContentRatingChip( + text: String, + fontWeight: FontWeight, + textColor: Color, + textShadow: Shadow +) { + Box( + modifier = Modifier + .background(Color.White.copy(alpha = 0.14f), RoundedCornerShape(6.dp)) + .padding(horizontal = 10.dp, vertical = 4.dp) + ) { + Text( + text = text, + style = ArflixTypography.caption.copy( + fontSize = 13.sp, + fontWeight = fontWeight, + shadow = textShadow + ), + color = textColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + /** * Mobile action button — labeled, tappable, Netflix-style */ From 78b0f0ebf00733141ade6fcb31b8510b9c4aae3a Mon Sep 17 00:00:00 2001 From: ReichiMD Date: Fri, 4 Sep 2026 20:40:48 +0000 Subject: [PATCH 2/2] feat(home): show the age certification chip in the TV hero preview The hero preview above the rows on the home screen shows date, genre, runtime and the IMDb rating, but not the age certification, even though it already loads the full details for the focused item - the certification was simply not carried through the hero snapshot. Adds it to HeroDetailsSnapshot so the existing details call is reused (no extra request) and renders it in the same rounded chip as on the details screen. The chip moves to ui/components so both screens share one definition instead of repeating the style. The phone home screen is deliberately left alone: it uses a carousel whose cards come straight from the category rows and carry no certification, so showing it there would mean fetching details per card. On the phone the certification stays on the details screen. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FS332afDiMApwV6SzEpvrf --- .../tv/ui/components/ContentRatingChip.kt | 51 +++++++++++++++++++ .../tv/ui/screens/details/DetailsScreen.kt | 43 +++------------- .../arflix/tv/ui/screens/home/HomeScreen.kt | 16 ++++-- .../tv/ui/screens/home/HomeViewModel.kt | 4 ++ 4 files changed, 75 insertions(+), 39 deletions(-) create mode 100644 app/src/main/kotlin/com/arflix/tv/ui/components/ContentRatingChip.kt diff --git a/app/src/main/kotlin/com/arflix/tv/ui/components/ContentRatingChip.kt b/app/src/main/kotlin/com/arflix/tv/ui/components/ContentRatingChip.kt new file mode 100644 index 000000000..ba1ae0f8d --- /dev/null +++ b/app/src/main/kotlin/com/arflix/tv/ui/components/ContentRatingChip.kt @@ -0,0 +1,51 @@ +package com.arflix.tv.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shadow +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.arflix.tv.ui.theme.ArflixTypography + +/** + * Age certification in a small rounded chip — same shape and padding as the "In Cinema" + * pill on the details screen, on a neutral translucent surface instead of a solid colour. + * Deliberately not colour coded by age: the age is only unambiguous for a few numeric + * rating systems, so a colour would have to be guessed for the letter based ones. + */ +@Composable +fun ContentRatingChip( + text: String, + fontWeight: FontWeight, + textColor: Color, + textShadow: Shadow, + fontSize: TextUnit = 13.sp, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .background(Color.White.copy(alpha = 0.14f), RoundedCornerShape(6.dp)) + .padding(horizontal = 10.dp, vertical = 4.dp) + ) { + Text( + text = text, + style = ArflixTypography.caption.copy( + fontSize = fontSize, + fontWeight = fontWeight, + shadow = textShadow + ), + color = textColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt index 4f931def2..d38d790ac 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/details/DetailsScreen.kt @@ -152,23 +152,24 @@ import com.arflix.tv.data.model.MediaType import com.arflix.tv.data.model.Review import com.arflix.tv.data.repository.MdbExternalRating import com.arflix.tv.network.OkHttpProvider -import com.arflix.tv.ui.components.EpisodeContextMenu -import com.arflix.tv.ui.components.KeepScreenOn -import com.arflix.tv.ui.components.SeasonContextMenu -import com.arflix.tv.ui.components.LoadingIndicator import com.arflix.tv.ui.components.AppTopBar import com.arflix.tv.ui.components.AppTopBarContentTopInset import com.arflix.tv.ui.components.CardLayoutMode +import com.arflix.tv.ui.components.ContentRatingChip +import com.arflix.tv.ui.components.EpisodeContextMenu +import com.arflix.tv.ui.components.KeepScreenOn +import com.arflix.tv.ui.components.LoadingIndicator import com.arflix.tv.ui.components.MediaCard import com.arflix.tv.ui.components.PersonModal import com.arflix.tv.ui.components.PosterCard -import com.arflix.tv.ui.components.resolveDetailsBackdropHeightDp -import com.arflix.tv.ui.components.rememberCatalogueRowLayoutMode +import com.arflix.tv.ui.components.SeasonContextMenu import com.arflix.tv.ui.components.SidebarItem import com.arflix.tv.ui.components.SkeletonDetailsPage import com.arflix.tv.ui.components.SkeletonEpisodeCard import com.arflix.tv.ui.components.StreamSelector import com.arflix.tv.ui.components.TrailerPlayer +import com.arflix.tv.ui.components.rememberCatalogueRowLayoutMode +import com.arflix.tv.ui.components.resolveDetailsBackdropHeightDp import androidx.activity.compose.BackHandler import androidx.compose.material.icons.filled.ChevronLeft import androidx.compose.material.icons.filled.ChevronRight @@ -3730,36 +3731,6 @@ private fun MobileMetadataSeparator() { ) } -/** - * Age certification chip — same rounded shape and padding as the "In Cinema" pill, - * but on a neutral translucent surface instead of a solid colour. - */ -@Composable -private fun ContentRatingChip( - text: String, - fontWeight: FontWeight, - textColor: Color, - textShadow: Shadow -) { - Box( - modifier = Modifier - .background(Color.White.copy(alpha = 0.14f), RoundedCornerShape(6.dp)) - .padding(horizontal = 10.dp, vertical = 4.dp) - ) { - Text( - text = text, - style = ArflixTypography.caption.copy( - fontSize = 13.sp, - fontWeight = fontWeight, - shadow = textShadow - ), - color = textColor, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } -} - /** * Mobile action button — labeled, tappable, Netflix-style */ diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt index 7fcce0869..08e00afa0 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt @@ -136,12 +136,13 @@ import com.arflix.tv.data.model.MediaItem import com.arflix.tv.data.model.MediaType import com.arflix.tv.data.model.isPortrait import com.arflix.tv.network.OkHttpProvider +import com.arflix.tv.ui.components.AppTopBar +import com.arflix.tv.ui.components.AppTopBarContentTopInset +import com.arflix.tv.ui.components.CardLayoutMode +import com.arflix.tv.ui.components.ContentRatingChip import com.arflix.tv.ui.components.FeaturedMediaCard import com.arflix.tv.ui.components.MediaCard as ArvioMediaCard import com.arflix.tv.ui.components.TrailerPlayer -import com.arflix.tv.ui.components.CardLayoutMode -import com.arflix.tv.ui.components.AppTopBar -import com.arflix.tv.ui.components.AppTopBarContentTopInset import com.arflix.tv.data.model.SportsAddonCapabilities import com.arflix.tv.ui.components.SkeletonMobileHeroBanner import com.arflix.tv.ui.components.SkeletonPosterCard @@ -1655,6 +1656,15 @@ private fun HeroSection( maxLines = 1 ) } + + currentItem.contentRating?.let { contentRating -> + ContentRatingChip( + text = contentRating, + fontWeight = FontWeight.Bold, + textColor = Color.White, + textShadow = textShadow + ) + } } if (hasSecondaryMetadata) { diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt index 4585f1a6e..a4b67d91e 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt @@ -212,6 +212,7 @@ class HomeViewModel @Inject constructor( val tmdbRating: String, val budget: Long?, val overview: String, + val contentRating: String? = null, val primaryNetworkLogo: String? = null, val fullyLoaded: Boolean = false ) @@ -677,6 +678,7 @@ class HomeViewModel @Inject constructor( tmdbRating = snapshot.tmdbRating.ifEmpty { tmdbRating }, budget = snapshot.budget ?: budget, overview = snapshot.overview.ifBlank { overview }, + contentRating = snapshot.contentRating ?: contentRating, primaryNetworkLogo = snapshot.primaryNetworkLogo ?: primaryNetworkLogo ) } @@ -690,6 +692,7 @@ class HomeViewModel @Inject constructor( tmdbRating = cached.tmdbRating, budget = cached.budget, overview = cached.overview, + contentRating = cached.contentRating, primaryNetworkLogo = cached.primaryNetworkLogo, fullyLoaded = false ) @@ -759,6 +762,7 @@ class HomeViewModel @Inject constructor( tmdbRating = details?.tmdbRating.orEmpty(), budget = details?.budget, overview = resolvedOverview, + contentRating = details?.contentRating, primaryNetworkLogo = primaryNetworkLogo, fullyLoaded = true )