diff --git a/app/src/main/kotlin/com/arflix/tv/ui/components/TrailerPlayer.kt b/app/src/main/kotlin/com/arflix/tv/ui/components/TrailerPlayer.kt index 6814a430..458269b0 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/components/TrailerPlayer.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/components/TrailerPlayer.kt @@ -50,9 +50,7 @@ fun TrailerPlayer( modifier: Modifier = Modifier, delayMs: Long = 0L, volume: Float = 0f, - onPlayingChanged: (Boolean) -> Unit = {}, - onFirstFrameRendered: () -> Unit = {}, - onEnded: () -> Unit = {} + onPlayingChanged: (Boolean) -> Unit = {} ) { val context = LocalContext.current var shouldPlay by remember { mutableStateOf(false) } @@ -84,6 +82,7 @@ fun TrailerPlayer( } if (videoUrl != null) { shouldPlay = true + onPlayingChanged(true) } else { onPlayingChanged(false) } @@ -107,13 +106,8 @@ fun TrailerPlayer( if (playbackState == Player.STATE_ENDED) { shouldPlay = false onPlayingChanged(false) - onEnded() } } - override fun onRenderedFirstFrame() { - onPlayingChanged(true) - onFirstFrameRendered() - } }) } } 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 c76612b7..e7b77b8c 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 @@ -40,7 +40,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed as standardItemsIndexed import androidx.compose.foundation.rememberScrollState @@ -1839,7 +1838,7 @@ private fun DetailsContent( val ratingValue = parseRatingValue(rating) val primaryNetworkLogo = item.primaryNetworkLogo?.takeIf { it.isNotBlank() } val budgetText = budget?.trim()?.takeIf { it.isNotEmpty() && item.mediaType == MediaType.MOVIE } - val overviewMaxHeight = if (isCompactHeight) 96.dp else 104.dp + val overviewMaxHeight = if (isCompactHeight) 68.dp else 72.dp val separatorStyle = ArflixTypography.caption.copy( fontSize = 13.sp, @@ -1847,7 +1846,7 @@ private fun DetailsContent( ) Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(3.dp), verticalAlignment = Alignment.CenterVertically ) { Text( @@ -1861,7 +1860,7 @@ private fun DetailsContent( ) if (displayDate.isNotEmpty()) { - Text(text = "•", style = separatorStyle, color = Color.White.copy(alpha = 0.5f)) + Text(text = "|", style = separatorStyle, color = Color.White.copy(alpha = 0.7f)) Text( text = displayDate, style = ArflixTypography.caption.copy( @@ -1874,7 +1873,7 @@ private fun DetailsContent( } if (hasDuration) { - Text(text = "•", style = separatorStyle, color = Color.White.copy(alpha = 0.5f)) + Text(text = "|", style = separatorStyle, color = Color.White.copy(alpha = 0.7f)) Text( text = item.duration, style = ArflixTypography.caption.copy( @@ -1887,32 +1886,32 @@ private fun DetailsContent( } if (primaryNetworkLogo != null) { - Box(modifier = Modifier.height(14.dp).width(1.dp).background(Color.White.copy(alpha = 0.3f))) + Text(text = "|", style = separatorStyle, color = Color.White.copy(alpha = 0.7f)) AsyncImage( model = primaryNetworkLogo, imageLoader = metadataLogoImageLoader, contentDescription = "Primary streaming provider", contentScale = ContentScale.Fit, modifier = Modifier - .height(20.dp) - .widthIn(max = 64.dp) + .height(16.dp) + .width(52.dp) ) } if (ratingValue > 0f) { - Box(modifier = Modifier.height(14.dp).width(1.dp).background(Color.White.copy(alpha = 0.3f))) + Text(text = "|", style = separatorStyle, color = Color.White.copy(alpha = 0.7f)) DetailsImdbSvgRatingBadge( rating = rating, imageLoader = metadataLogoImageLoader, ratingFontSize = 13, - logoWidth = 48.dp, - logoHeight = 20.dp, + logoWidth = 34.dp, + logoHeight = 14.dp, textShadow = textShadow ) } if (!budgetText.isNullOrBlank()) { - Box(modifier = Modifier.height(14.dp).width(1.dp).background(Color.White.copy(alpha = 0.3f))) + Text(text = "|", style = separatorStyle, color = Color.White.copy(alpha = 0.7f)) Text( text = "${stringResource(R.string.budget)} $budgetText", style = ArflixTypography.caption.copy( @@ -1925,24 +1924,24 @@ private fun DetailsContent( } } - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(6.dp)) val displayOverview = item.overview Box( modifier = Modifier - .widthIn(max = 560.dp) + .width(360.dp) .height(overviewMaxHeight) ) { Text( text = displayOverview, style = ArflixTypography.body.copy( - fontSize = 16.sp, + fontSize = 12.sp, fontWeight = FontWeight.Normal, - lineHeight = 23.sp, + lineHeight = 16.sp, shadow = textShadow ), - color = Color(0xDFFFFFFF), + color = Color.White.copy(alpha = 0.9f), maxLines = 4, overflow = TextOverflow.Ellipsis ) 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 573b5b84..081edc98 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 @@ -47,8 +47,6 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -71,7 +69,6 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -183,7 +180,6 @@ import okhttp3.ConnectionPool import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlin.math.max @@ -845,13 +841,9 @@ fun HomeScreen( else -> null } - val scope = rememberCoroutineScope() var isTrailerPlaying by remember { mutableStateOf(false) } var trailerSuppressed by remember { mutableStateOf(false) } - LaunchedEffect(displayHeroItem?.id) { - trailerSuppressed = false - isTrailerPlaying = false - } + LaunchedEffect(displayHeroItem?.id) { trailerSuppressed = false } val heroRowIsContinueWatching = latestDisplayCategories .getOrNull(focusState.currentRowIndex)?.id == "continue_watching" val trailerOverlayAlpha = remember { Animatable(1f) } @@ -1026,19 +1018,7 @@ fun HomeScreen( youtubeKey = uiState.heroTrailerKey!!, delayMs = uiState.trailerDelaySeconds * 1000L, volume = if (uiState.trailerSoundEnabled) 1f else 0f, - onFirstFrameRendered = { - scope.launch { - delay(500) - isTrailerPlaying = true - } - }, - onEnded = { - isTrailerPlaying = false - scope.launch { - delay(500) - trailerSuppressed = true - } - }, + onPlayingChanged = { playing -> isTrailerPlaying = playing }, modifier = Modifier.fillMaxSize() ) } @@ -1117,17 +1097,7 @@ fun HomeScreen( usePosterCards = usePosterCards, isContextMenuOpen = showContextMenu, trailerIsPlaying = isTrailerPlaying, - onTrailerStop = { - if (isTrailerPlaying) { - isTrailerPlaying = false - scope.launch { - delay(500) - trailerSuppressed = true - } - } else { - trailerSuppressed = true - } - }, + onTrailerStop = { trailerSuppressed = true }, isMobile = isMobile, heroItem = displayHeroItem, heroOverviewOverride = displayHeroOverview, @@ -1344,7 +1314,7 @@ private fun HeroSection( // Use primary shadow for text (Compose only supports one shadow per text) // But the frosted pill provides additional protection val textShadow = textShadowPrimary - val heroTextWidth = 560.dp + val heroTextWidth = 360.dp Column( modifier = modifier, @@ -1425,7 +1395,7 @@ private fun HeroSection( } } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(4.dp)) // Performance: Use key instead of AnimatedContent for faster transitions key(item.id) { @@ -1492,10 +1462,10 @@ private fun HeroSection( Column( modifier = Modifier.width(heroTextWidth), - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(3.dp) ) { Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(3.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { @@ -1503,7 +1473,7 @@ private fun HeroSection( Text( text = displayDate, style = ArflixTypography.caption.copy( - fontSize = 16.sp, + fontSize = 13.sp, fontWeight = FontWeight.Bold, shadow = textShadow ), @@ -1513,12 +1483,12 @@ private fun HeroSection( if (hasGenre || hasDuration) { Text( - text = "•", + text = "|", style = ArflixTypography.caption.copy( - fontSize = 16.sp, + fontSize = 13.sp, shadow = textShadow ), - color = Color.White.copy(alpha = 0.5f) + color = Color.White.copy(alpha = 0.7f) ) } } @@ -1527,7 +1497,7 @@ private fun HeroSection( Text( text = genreText, style = ArflixTypography.caption.copy( - fontSize = 16.sp, + fontSize = 13.sp, fontWeight = FontWeight.Bold, shadow = textShadow ), @@ -1541,18 +1511,18 @@ private fun HeroSection( if (hasDuration) { if (hasGenre) { Text( - text = "•", + text = "|", style = ArflixTypography.caption.copy( - fontSize = 16.sp, + fontSize = 13.sp, shadow = textShadow ), - color = Color.White.copy(alpha = 0.5f) + color = Color.White.copy(alpha = 0.7f) ) } Text( text = currentItem.duration, style = ArflixTypography.caption.copy( - fontSize = 16.sp, + fontSize = 13.sp, fontWeight = FontWeight.Bold, shadow = textShadow ), @@ -1564,8 +1534,9 @@ private fun HeroSection( if (hasSecondaryMetadata) { Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() ) { if (primaryNetworkLogo != null) { AsyncImage( @@ -1574,16 +1545,18 @@ private fun HeroSection( contentDescription = "Primary streaming provider", contentScale = ContentScale.Fit, modifier = Modifier - .height(24.dp) - .widthIn(max = 72.dp) + .height(16.dp) + .width(58.dp) ) if (hasRatingMetadata || hasBudgetMetadata) { - Box( - modifier = Modifier - .height(24.dp) - .width(1.dp) - .background(Color.White.copy(alpha = 0.3f)) + Text( + text = "|", + style = ArflixTypography.caption.copy( + fontSize = 12.sp, + shadow = textShadow + ), + color = Color.White.copy(alpha = 0.58f) ) } } @@ -1592,18 +1565,20 @@ private fun HeroSection( ImdbSvgRatingBadge( rating = rating, imageLoader = metadataLogoImageLoader, - ratingFontSize = 16, - logoWidth = 58.dp, - logoHeight = 24.dp, + ratingFontSize = 13, + logoWidth = 36.dp, + logoHeight = 15.dp, textShadow = textShadow ) if (hasBudgetMetadata) { - Box( - modifier = Modifier - .height(24.dp) - .width(1.dp) - .background(Color.White.copy(alpha = 0.3f)) + Text( + text = "|", + style = ArflixTypography.caption.copy( + fontSize = 12.sp, + shadow = textShadow + ), + color = Color.White.copy(alpha = 0.58f) ) } } @@ -1612,13 +1587,14 @@ private fun HeroSection( Text( text = "Budget $budgetText", style = ArflixTypography.caption.copy( - fontSize = 16.sp, + fontSize = 12.sp, fontWeight = FontWeight.Medium, shadow = textShadow ), color = Color.White.copy(alpha = 0.74f), maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) ) } } @@ -1626,29 +1602,29 @@ private fun HeroSection( } } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(6.dp)) // Overview text (EPG data for IPTV, synopsis for movies/shows) val displayOverview = remember(overviewOverride, currentItem.overview) { cleanOverviewText(overviewOverride ?: currentItem.overview) } - val overviewMaxHeight = 96.dp + val overviewMaxHeight = 72.dp Box( modifier = Modifier - .width(heroTextWidth) + .width(360.dp) .height(overviewMaxHeight) ) { Text( text = displayOverview, style = ArflixTypography.body.copy( - fontSize = 16.sp, + fontSize = 12.sp, fontWeight = FontWeight.Normal, - lineHeight = 22.4.sp, + lineHeight = 16.sp, shadow = textShadow ), color = Color.White.copy(alpha = 0.9f), - maxLines = 2, + maxLines = 4, overflow = TextOverflow.Ellipsis ) } @@ -1733,8 +1709,11 @@ private fun HomeHeroLayer( } else { // TV hero: full-screen overlay with clearlogo val configuration = LocalConfiguration.current - val rowsViewportHeight = (configuration.screenHeightDp * 0.31f).dp.coerceIn(260.dp, 340.dp) - val heroBottomMargin = rowsViewportHeight + 48.dp + val contentRowHeight = (configuration.screenHeightDp * 0.34f).dp.coerceIn(240.dp, 320.dp) + val contentRowBottomPadding = 12.dp + val contentRowTopPadding = contentRowHeight + contentRowBottomPadding + val buttonsBottomPadding = contentRowTopPadding - 10.dp + val heroBottomPadding = buttonsBottomPadding + if (configuration.screenHeightDp < 720) 34.dp else 34.dp Box( modifier = Modifier @@ -1751,11 +1730,8 @@ private fun HomeHeroLayer( showBudget = showBudget, modifier = Modifier .align(Alignment.BottomStart) - .padding( - start = contentStartPadding, - end = 400.dp, - bottom = heroBottomMargin - ) + .padding(start = contentStartPadding, end = 400.dp) + .offset(y = -heroBottomPadding) ) } }