diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt index 6303563d7..1f61a0a7c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.nuvio.app.core.ui.NuvioScreen @@ -44,10 +45,12 @@ import com.nuvio.app.features.watching.domain.buildPlaybackVideoId import com.nuvio.app.features.collection.CollectionRepository import com.nuvio.app.features.home.components.HomeCollectionRowSection import com.nuvio.app.features.watching.domain.isReleasedBy +import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit +import com.nuvio.app.features.home.components.ContinueWatchingLayout import com.nuvio.app.features.home.components.homeSectionHorizontalPaddingForWidth import com.nuvio.app.features.home.components.rememberContinueWatchingLayout @@ -322,6 +325,21 @@ fun HomeScreen( BoxWithConstraints(modifier = modifier.fillMaxSize()) { val homeSectionPadding = homeSectionHorizontalPaddingForWidth(maxWidth.value) val continueWatchingLayout = rememberContinueWatchingLayout(maxWidth.value) + val mobileHeroBelowSectionHeightHint = remember( + maxWidth.value, + continueWatchingPreferences.isVisible, + continueWatchingPreferences.style, + continueWatchingItems.isNotEmpty(), + continueWatchingLayout, + ) { + heroMobileBelowSectionHeightHint( + maxWidthDp = maxWidth.value, + continueWatchingVisible = continueWatchingPreferences.isVisible, + hasContinueWatchingItems = continueWatchingItems.isNotEmpty(), + continueWatchingStyle = continueWatchingPreferences.style, + continueWatchingLayout = continueWatchingLayout, + ) + } NuvioScreen( modifier = Modifier.fillMaxSize(), @@ -335,12 +353,14 @@ fun HomeScreen( showHeroSkeleton -> HomeSkeletonHero( modifier = Modifier, viewportHeight = maxHeight, + mobileBelowSectionHeightHint = mobileHeroBelowSectionHeightHint, ) homeUiState.heroItems.isNotEmpty() -> HomeHeroSection( items = homeUiState.heroItems, modifier = Modifier, viewportHeight = maxHeight, + mobileBelowSectionHeightHint = mobileHeroBelowSectionHeightHint, listState = homeListState, onItemClick = onPosterClick, ) @@ -348,6 +368,7 @@ fun HomeScreen( else -> HomeHeroReservedSpace( modifier = Modifier, viewportHeight = maxHeight, + mobileBelowSectionHeightHint = mobileHeroBelowSectionHeightHint, ) } } @@ -467,6 +488,22 @@ fun HomeScreen( private const val HOME_CATALOG_PREVIEW_LIMIT = 18 private const val TRAKT_CONTINUE_WATCHING_DAYS_CAP_DEFAULT = 60 +private fun heroMobileBelowSectionHeightHint( + maxWidthDp: Float, + continueWatchingVisible: Boolean, + hasContinueWatchingItems: Boolean, + continueWatchingStyle: ContinueWatchingSectionStyle, + continueWatchingLayout: ContinueWatchingLayout, +): Dp? { + if (maxWidthDp >= 600f || !continueWatchingVisible || !hasContinueWatchingItems) return null + + return when (continueWatchingStyle) { + ContinueWatchingSectionStyle.Wide -> continueWatchingLayout.wideCardHeight + 56.dp + ContinueWatchingSectionStyle.Poster -> + continueWatchingLayout.posterCardHeight + continueWatchingLayout.posterTitleBlockHeight + 70.dp + } +} + internal fun buildHomeContinueWatchingItems( visibleEntries: List, cachedInProgressByVideoId: Map = emptyMap(), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt index 73c47e4aa..15f08ebb8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt @@ -63,6 +63,9 @@ private const val HERO_SCROLL_UP_SCALE_MULTIPLIER = 0.002f private const val HERO_SCROLL_MAX_SCALE = 1.3f private const val HERO_SWIPE_THRESHOLD_FRACTION = 0.16f private const val HERO_SWIPE_VELOCITY_THRESHOLD = 300f +private const val MOBILE_HERO_VIEWPORT_RATIO = 0.78f +private const val MOBILE_HERO_MIN_HEIGHT_DP = 360f +private const val MOBILE_HERO_MAX_HEIGHT_DP = 760f internal data class HomeHeroLayout( val isTablet: Boolean, @@ -80,6 +83,7 @@ fun HomeHeroSection( items: List, modifier: Modifier = Modifier, viewportHeight: Dp? = null, + mobileBelowSectionHeightHint: Dp? = null, listState: LazyListState? = null, onItemClick: ((MetaPreview) -> Unit)? = null, ) { @@ -101,6 +105,7 @@ fun HomeHeroSection( val layout = homeHeroLayout( maxWidthDp = maxWidth.value, viewportHeightDp = viewportHeight?.value, + mobileBelowSectionHeightHintDp = mobileBelowSectionHeightHint?.value, ) val heroWidthPx = with(LocalDensity.current) { maxWidth.toPx() } val heroHeightPx = with(LocalDensity.current) { layout.heroHeight.toPx() } @@ -313,6 +318,7 @@ private fun heroPageVisibility( fun HomeHeroReservedSpace( modifier: Modifier = Modifier, viewportHeight: Dp? = null, + mobileBelowSectionHeightHint: Dp? = null, ) { BoxWithConstraints( modifier = modifier @@ -322,6 +328,7 @@ fun HomeHeroReservedSpace( val layout = homeHeroLayout( maxWidthDp = maxWidth.value, viewportHeightDp = viewportHeight?.value, + mobileBelowSectionHeightHintDp = mobileBelowSectionHeightHint?.value, ) Spacer( @@ -414,6 +421,7 @@ private fun HeroMetaText(text: String) { internal fun homeHeroLayout( maxWidthDp: Float, viewportHeightDp: Float? = null, + mobileBelowSectionHeightHintDp: Float? = null, ): HomeHeroLayout = when { maxWidthDp >= 1200f -> HomeHeroLayout( @@ -448,7 +456,11 @@ internal fun homeHeroLayout( ) else -> HomeHeroLayout( isTablet = false, - heroHeight = viewportHeightDp?.let { (it * 0.78f).dp } ?: (maxWidthDp * 1.16f).dp.coerceIn(420.dp, 760.dp), + heroHeight = mobileHeroHeight( + maxWidthDp = maxWidthDp, + viewportHeightDp = viewportHeightDp, + mobileBelowSectionHeightHintDp = mobileBelowSectionHeightHintDp, + ), contentMaxWidth = 480.dp, contentWidthFraction = 1f, contentHorizontalPadding = 24.dp, @@ -458,6 +470,25 @@ internal fun homeHeroLayout( ) } +private fun mobileHeroHeight( + maxWidthDp: Float, + viewportHeightDp: Float?, + mobileBelowSectionHeightHintDp: Float?, +): Dp { + val viewportDrivenHeight = viewportHeightDp?.let { (it * MOBILE_HERO_VIEWPORT_RATIO).dp } + val widthFallbackHeight = (maxWidthDp * 1.16f).dp + val baseHeight = viewportDrivenHeight ?: widthFallbackHeight + + val cappedHeight = if (viewportHeightDp != null && mobileBelowSectionHeightHintDp != null) { + val maxAllowedFromViewport = (viewportHeightDp - mobileBelowSectionHeightHintDp).dp + baseHeight.coerceAtMost(maxAllowedFromViewport) + } else { + baseHeight + } + + return cappedHeight.coerceIn(MOBILE_HERO_MIN_HEIGHT_DP.dp, MOBILE_HERO_MAX_HEIGHT_DP.dp) +} + @Composable private fun HeroMetaDot() { Box( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt index f7195bf38..f034f7f3c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeSkeletonLoading.kt @@ -61,6 +61,7 @@ private fun rememberHomeSkeletonBrush(): Brush { fun HomeSkeletonHero( modifier: Modifier = Modifier, viewportHeight: Dp? = null, + mobileBelowSectionHeightHint: Dp? = null, ) { val brush = rememberHomeSkeletonBrush() @@ -72,6 +73,7 @@ fun HomeSkeletonHero( val layout = homeHeroLayout( maxWidthDp = maxWidth.value, viewportHeightDp = viewportHeight?.value, + mobileBelowSectionHeightHintDp = mobileBelowSectionHeightHint?.value, ) val containerWidth = maxWidth