From e551ed0a9f1edd0970c7e19c71d23b357692307a Mon Sep 17 00:00:00 2001 From: Hassan Date: Sun, 19 Jul 2026 04:30:05 +0300 Subject: [PATCH 1/2] fix: fit subtitle settings on phone landscape --- .../app/features/player/SubtitleModal.kt | 330 +++++++++++------- .../player/SubtitleModalLayoutTest.kt | 48 +++ 2 files changed, 261 insertions(+), 117 deletions(-) create mode 100644 composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt index 83f491c37..13e647add 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt @@ -147,138 +147,168 @@ fun SubtitleModal( visible = visible, onDismiss = onDismiss, modifier = modifier, - contentPadding = PaddingValues(start = 52.dp, end = 52.dp, top = 36.dp, bottom = 76.dp), ) { BoxWithConstraints(modifier = Modifier.fillMaxSize()) { - val railMaxHeight = (maxHeight - 72.dp).coerceAtLeast(120.dp) + val layout = SubtitleModalLayoutMetrics.from(maxWidth, maxHeight) + val railMaxHeight = ( + maxHeight - + layout.topPadding - + layout.bottomPadding - + layout.titleReservedHeight + ).coerceAtLeast(120.dp) - Column( - modifier = Modifier.align(Alignment.BottomStart), - verticalArrangement = Arrangement.Bottom, + Box( + modifier = Modifier + .fillMaxSize() + .padding( + start = layout.horizontalPadding, + end = layout.horizontalPadding, + top = layout.topPadding, + bottom = layout.bottomPadding, + ), ) { - Text( - text = stringResource(Res.string.compose_player_subtitles), - color = Color.White, - style = MaterialTheme.typography.headlineMedium, - modifier = Modifier.padding(bottom = 12.dp), - ) - - Row( - modifier = Modifier.horizontalScroll(rememberScrollState()), - horizontalArrangement = Arrangement.spacedBy(14.dp), - verticalAlignment = Alignment.Top, + Column( + modifier = Modifier.align(Alignment.BottomStart), + verticalArrangement = Arrangement.Bottom, ) { - SubtitleRail( - title = stringResource(Res.string.compose_player_languages), - width = 200.dp, - ) { - LazyColumn( - modifier = Modifier.heightIn(max = railMaxHeight), - verticalArrangement = Arrangement.spacedBy(4.dp), - contentPadding = PaddingValues(vertical = 8.dp), - ) { - items(languageItems, key = { it.key }) { item -> - SubtitleLanguageRow( - item = item, - selected = item.key == activeLanguageKey, - onClick = { - activeLanguageKey = item.key - val availableOptions = buildSubtitleSelectionOptions( - item.key, - subtitleTracks, - addonSubtitles, - ) - pendingOptionId = playbackOptionId?.takeIf { id -> - availableOptions.any { it.id == id } - } - if (item.key == SubtitleOffLanguageKey) { - onBuiltInTrackSelected(-1) - } - }, - ) - } - } - } + Text( + text = stringResource(Res.string.compose_player_subtitles), + color = Color.White, + style = if (layout.isCompact) { + MaterialTheme.typography.headlineSmall + } else { + MaterialTheme.typography.headlineMedium + }, + modifier = Modifier.padding(bottom = layout.titleBottomPadding), + ) - AnimatedVisibility( - visible = activeLanguageKey != SubtitleOffLanguageKey, - enter = fadeIn(), - exit = fadeOut(), + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(layout.railGap), + verticalAlignment = Alignment.Top, ) { SubtitleRail( - title = stringResource(Res.string.compose_player_subtitles), - width = 300.dp, + title = stringResource(Res.string.compose_player_languages), + width = layout.languageRailWidth, + isCompact = layout.isCompact, ) { - when { - options.isEmpty() && isLoadingAddonSubtitles -> { - PlayerModalLoading(modifier = Modifier.padding(vertical = 24.dp)) - } - - options.isEmpty() -> { - SubtitleRailEmptyState( - text = stringResource(Res.string.compose_player_fetch_subtitles), - onClick = onFetchAddonSubtitles, + LazyColumn( + modifier = Modifier.heightIn(max = railMaxHeight), + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues(vertical = if (layout.isCompact) 4.dp else 8.dp), + ) { + items(languageItems, key = { it.key }) { item -> + SubtitleLanguageRow( + item = item, + selected = item.key == activeLanguageKey, + isCompact = layout.isCompact, + onClick = { + activeLanguageKey = item.key + val availableOptions = buildSubtitleSelectionOptions( + item.key, + subtitleTracks, + addonSubtitles, + ) + pendingOptionId = playbackOptionId?.takeIf { id -> + availableOptions.any { it.id == id } + } + if (item.key == SubtitleOffLanguageKey) { + onBuiltInTrackSelected(-1) + } + }, ) } + } + } - else -> { - LazyColumn( - modifier = Modifier.heightIn(max = railMaxHeight), - verticalArrangement = Arrangement.spacedBy(4.dp), - contentPadding = PaddingValues(vertical = 8.dp), - ) { - items(options, key = { it.id }) { option -> - SubtitleOptionRow( - option = option, - selected = option.id == selectedOptionId, - onClick = { - pendingOptionId = option.id - when (option) { - is SubtitleSelectionOption.BuiltIn -> { - onBuiltInTrackSelected(option.track.index) - } + AnimatedVisibility( + visible = activeLanguageKey != SubtitleOffLanguageKey, + enter = fadeIn(), + exit = fadeOut(), + ) { + SubtitleRail( + title = stringResource(Res.string.compose_player_subtitles), + width = layout.subtitleRailWidth, + isCompact = layout.isCompact, + ) { + when { + options.isEmpty() && isLoadingAddonSubtitles -> { + PlayerModalLoading(modifier = Modifier.padding(vertical = 24.dp)) + } - is SubtitleSelectionOption.Addon -> { - onAddonSubtitleSelected(option.subtitle) + options.isEmpty() -> { + SubtitleRailEmptyState( + text = stringResource(Res.string.compose_player_fetch_subtitles), + isCompact = layout.isCompact, + onClick = onFetchAddonSubtitles, + ) + } + + else -> { + LazyColumn( + modifier = Modifier.heightIn(max = railMaxHeight), + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues( + vertical = if (layout.isCompact) 4.dp else 8.dp, + ), + ) { + items(options, key = { it.id }) { option -> + SubtitleOptionRow( + option = option, + selected = option.id == selectedOptionId, + isCompact = layout.isCompact, + onClick = { + pendingOptionId = option.id + when (option) { + is SubtitleSelectionOption.BuiltIn -> { + onBuiltInTrackSelected(option.track.index) + } + + is SubtitleSelectionOption.Addon -> { + onAddonSubtitleSelected(option.subtitle) + } } - } - }, - ) + }, + ) + } } } } } } - } - AnimatedVisibility( - visible = styleVisible, - enter = fadeIn(), - exit = fadeOut(), - ) { - SubtitleRail( - title = stringResource(Res.string.compose_player_style), - width = 280.dp, + AnimatedVisibility( + visible = styleVisible, + enter = fadeIn(), + exit = fadeOut(), ) { - Column( - modifier = Modifier - .heightIn(max = railMaxHeight) - .verticalScroll(rememberScrollState()), + SubtitleRail( + title = stringResource(Res.string.compose_player_style), + width = layout.styleRailWidth, + isCompact = layout.isCompact, ) { - SubtitleStylePanel( - style = subtitleStyle, - subtitleDelayMs = subtitleDelayMs, - selectedAddonSubtitle = effectiveSelectedAddonSubtitle, - subtitleAutoSyncState = subtitleAutoSyncState, - isCompact = railMaxHeight < 420.dp, - showHeader = false, - onStyleChanged = onStyleChanged, - onSubtitleDelayChanged = onSubtitleDelayChanged, - onSubtitleDelayReset = onSubtitleDelayReset, - onAutoSyncCapture = onAutoSyncCapture, - onAutoSyncCueSelected = onAutoSyncCueSelected, - onAutoSyncReload = onAutoSyncReload, - ) + Column( + modifier = Modifier + .heightIn(max = railMaxHeight) + .verticalScroll(rememberScrollState()), + ) { + SubtitleStylePanel( + style = subtitleStyle, + subtitleDelayMs = subtitleDelayMs, + selectedAddonSubtitle = effectiveSelectedAddonSubtitle, + subtitleAutoSyncState = subtitleAutoSyncState, + isCompact = layout.isCompact || railMaxHeight < 420.dp, + showHeader = false, + onStyleChanged = onStyleChanged, + onSubtitleDelayChanged = onSubtitleDelayChanged, + onSubtitleDelayReset = onSubtitleDelayReset, + onAutoSyncCapture = onAutoSyncCapture, + onAutoSyncCueSelected = onAutoSyncCueSelected, + onAutoSyncReload = onAutoSyncReload, + ) + } } } } @@ -292,6 +322,7 @@ fun SubtitleModal( private fun SubtitleRail( title: String, width: Dp, + isCompact: Boolean, content: @Composable ColumnScope.() -> Unit, ) { val tokens = MaterialTheme.nuvio @@ -303,7 +334,7 @@ private fun SubtitleRail( Text( text = title, color = tokens.colors.textMuted, - style = MaterialTheme.typography.labelLarge, + style = if (isCompact) MaterialTheme.typography.labelMedium else MaterialTheme.typography.labelLarge, maxLines = 1, overflow = TextOverflow.Ellipsis, ) @@ -315,6 +346,7 @@ private fun SubtitleRail( private fun SubtitleLanguageRow( item: SubtitleLanguageItem, selected: Boolean, + isCompact: Boolean, onClick: () -> Unit, ) { val tokens = MaterialTheme.nuvio @@ -330,7 +362,10 @@ private fun SubtitleLanguageRow( .clip(RoundedCornerShape(12.dp)) .background(if (selected) tokens.colors.accent else Color.Transparent) .clickable(onClick = onClick) - .padding(horizontal = 10.dp, vertical = 8.dp), + .padding( + horizontal = if (isCompact) 8.dp else 10.dp, + vertical = if (isCompact) 6.dp else 8.dp, + ), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -338,7 +373,7 @@ private fun SubtitleLanguageRow( text = label, modifier = Modifier.weight(1f, fill = false), color = if (selected) tokens.colors.onAccent else Color.White, - style = MaterialTheme.typography.bodyLarge, + style = if (isCompact) MaterialTheme.typography.bodyMedium else MaterialTheme.typography.bodyLarge, maxLines = 1, overflow = TextOverflow.Ellipsis, ) @@ -364,6 +399,7 @@ private fun SubtitleLanguageRow( private fun SubtitleOptionRow( option: SubtitleSelectionOption, selected: Boolean, + isCompact: Boolean, onClick: () -> Unit, ) { val tokens = MaterialTheme.nuvio @@ -399,19 +435,22 @@ private fun SubtitleOptionRow( .clip(RoundedCornerShape(12.dp)) .background(if (selected) tokens.colors.accent else Color.Transparent) .clickable(onClick = onClick) - .padding(horizontal = 12.dp, vertical = 9.dp), + .padding( + horizontal = if (isCompact) 10.dp else 12.dp, + vertical = if (isCompact) 7.dp else 9.dp, + ), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Column( modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(if (isCompact) 4.dp else 6.dp), ) { SubtitleSourceChip(label = sourceLabel, selected = selected) Text( text = title, color = if (selected) tokens.colors.onAccent else Color.White, - style = MaterialTheme.typography.bodyLarge, + style = if (isCompact) MaterialTheme.typography.bodyMedium else MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis, ) @@ -475,6 +514,7 @@ private fun SubtitleSourceChip( @Composable private fun SubtitleRailEmptyState( text: String, + isCompact: Boolean, onClick: () -> Unit, ) { val tokens = MaterialTheme.nuvio @@ -497,7 +537,63 @@ private fun SubtitleRailEmptyState( Text( text = text, color = tokens.colors.textMuted, - style = MaterialTheme.typography.bodyLarge, + style = if (isCompact) MaterialTheme.typography.bodyMedium else MaterialTheme.typography.bodyLarge, ) } } + +internal data class SubtitleModalLayoutMetrics( + val isCompact: Boolean, + val horizontalPadding: Dp, + val topPadding: Dp, + val bottomPadding: Dp, + val titleReservedHeight: Dp, + val titleBottomPadding: Dp, + val railGap: Dp, + val languageRailWidth: Dp, + val subtitleRailWidth: Dp, + val styleRailWidth: Dp, +) { + val totalRailWidth: Dp + get() = languageRailWidth + subtitleRailWidth + styleRailWidth + railGap * 2 + + companion object { + fun from(maxWidth: Dp, maxHeight: Dp): SubtitleModalLayoutMetrics { + val isCompact = maxWidth < 960.dp || maxHeight < 500.dp + if (!isCompact) { + return SubtitleModalLayoutMetrics( + isCompact = false, + horizontalPadding = 52.dp, + topPadding = 36.dp, + bottomPadding = 76.dp, + titleReservedHeight = 72.dp, + titleBottomPadding = 12.dp, + railGap = 14.dp, + languageRailWidth = 200.dp, + subtitleRailWidth = 300.dp, + styleRailWidth = 280.dp, + ) + } + + val horizontalPadding = 32.dp + val railGap = 10.dp + val availableWidth = (maxWidth - horizontalPadding * 2).coerceAtLeast(0.dp) + val languageWidth = (availableWidth * 0.2f).coerceIn(132.dp, 168.dp) + val subtitleWidth = (availableWidth * 0.34f).coerceIn(210.dp, 260.dp) + val remainingStyleWidth = availableWidth - languageWidth - subtitleWidth - railGap * 2 + + return SubtitleModalLayoutMetrics( + isCompact = true, + horizontalPadding = horizontalPadding, + topPadding = 24.dp, + bottomPadding = 60.dp, + titleReservedHeight = 52.dp, + titleBottomPadding = 8.dp, + railGap = railGap, + languageRailWidth = languageWidth, + subtitleRailWidth = subtitleWidth, + styleRailWidth = remainingStyleWidth.coerceIn(210.dp, 260.dp), + ) + } + } +} diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt new file mode 100644 index 000000000..ab0527a92 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt @@ -0,0 +1,48 @@ +package com.nuvio.app.features.player + +import androidx.compose.ui.unit.dp +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class SubtitleModalLayoutTest { + + @Test + fun `phone landscape uses compact rails that fit the available width`() { + val metrics = SubtitleModalLayoutMetrics.from( + maxWidth = 884.dp, + maxHeight = 400.dp, + ) + + assertTrue(metrics.isCompact) + assertTrue(metrics.totalRailWidth <= 884.dp - metrics.horizontalPadding * 2) + assertTrue(metrics.styleRailWidth >= 210.dp) + } + + @Test + fun `large landscape keeps the existing tablet layout`() { + val metrics = SubtitleModalLayoutMetrics.from( + maxWidth = 1280.dp, + maxHeight = 720.dp, + ) + + assertFalse(metrics.isCompact) + assertEquals(200.dp, metrics.languageRailWidth) + assertEquals(300.dp, metrics.subtitleRailWidth) + assertEquals(280.dp, metrics.styleRailWidth) + } + + @Test + fun `narrow landscape keeps usable minimum rail widths`() { + val metrics = SubtitleModalLayoutMetrics.from( + maxWidth = 640.dp, + maxHeight = 360.dp, + ) + + assertTrue(metrics.isCompact) + assertTrue(metrics.languageRailWidth >= 132.dp) + assertTrue(metrics.subtitleRailWidth >= 210.dp) + assertTrue(metrics.styleRailWidth >= 210.dp) + } +} From 9fda62222d716a12a14c3df45d3f91e79cffec1a Mon Sep 17 00:00:00 2001 From: Hassan Date: Wed, 22 Jul 2026 01:12:51 +0300 Subject: [PATCH 2/2] fix(player): scale subtitle modal layout safely --- .../app/features/player/SubtitleModal.kt | 51 ++++++++++++------- .../player/SubtitleModalLayoutTest.kt | 15 ++++++ 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt index 13e647add..b77158d39 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleModal.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -149,13 +150,15 @@ fun SubtitleModal( modifier = modifier, ) { BoxWithConstraints(modifier = Modifier.fillMaxSize()) { - val layout = SubtitleModalLayoutMetrics.from(maxWidth, maxHeight) - val railMaxHeight = ( - maxHeight - - layout.topPadding - - layout.bottomPadding - - layout.titleReservedHeight - ).coerceAtLeast(120.dp) + val compactLayout = SubtitleModalLayoutMetrics.usesCompactLayout(maxWidth, maxHeight) + val titleStyle = if (compactLayout) { + MaterialTheme.typography.headlineSmall + } else { + MaterialTheme.typography.headlineMedium + } + val titleLineHeight = with(LocalDensity.current) { titleStyle.lineHeight.toDp() } + val layout = SubtitleModalLayoutMetrics.from(maxWidth, maxHeight, titleLineHeight) + val railMaxHeight = layout.railMaxHeight(maxHeight) Box( modifier = Modifier @@ -174,11 +177,7 @@ fun SubtitleModal( Text( text = stringResource(Res.string.compose_player_subtitles), color = Color.White, - style = if (layout.isCompact) { - MaterialTheme.typography.headlineSmall - } else { - MaterialTheme.typography.headlineMedium - }, + style = titleStyle, modifier = Modifier.padding(bottom = layout.titleBottomPadding), ) @@ -359,6 +358,7 @@ private fun SubtitleLanguageRow( Row( modifier = Modifier .fillMaxWidth() + .heightIn(min = 48.dp) .clip(RoundedCornerShape(12.dp)) .background(if (selected) tokens.colors.accent else Color.Transparent) .clickable(onClick = onClick) @@ -432,6 +432,7 @@ private fun SubtitleOptionRow( Row( modifier = Modifier .fillMaxWidth() + .heightIn(min = 48.dp) .clip(RoundedCornerShape(12.dp)) .background(if (selected) tokens.colors.accent else Color.Transparent) .clickable(onClick = onClick) @@ -522,6 +523,7 @@ private fun SubtitleRailEmptyState( Row( modifier = Modifier .fillMaxWidth() + .heightIn(min = 48.dp) .clip(RoundedCornerShape(12.dp)) .clickable(onClick = onClick) .padding(horizontal = 6.dp, vertical = 10.dp), @@ -557,17 +559,29 @@ internal data class SubtitleModalLayoutMetrics( val totalRailWidth: Dp get() = languageRailWidth + subtitleRailWidth + styleRailWidth + railGap * 2 + fun railMaxHeight(maxHeight: Dp): Dp = ( + maxHeight - topPadding - bottomPadding - titleReservedHeight + ).coerceAtLeast(120.dp) + companion object { - fun from(maxWidth: Dp, maxHeight: Dp): SubtitleModalLayoutMetrics { - val isCompact = maxWidth < 960.dp || maxHeight < 500.dp + fun usesCompactLayout(maxWidth: Dp, maxHeight: Dp): Boolean = + maxWidth < 960.dp || maxHeight < 500.dp + + fun from( + maxWidth: Dp, + maxHeight: Dp, + titleLineHeight: Dp, + ): SubtitleModalLayoutMetrics { + val isCompact = usesCompactLayout(maxWidth, maxHeight) if (!isCompact) { + val titleBottomPadding = 12.dp return SubtitleModalLayoutMetrics( isCompact = false, horizontalPadding = 52.dp, topPadding = 36.dp, bottomPadding = 76.dp, - titleReservedHeight = 72.dp, - titleBottomPadding = 12.dp, + titleReservedHeight = titleLineHeight + titleBottomPadding, + titleBottomPadding = titleBottomPadding, railGap = 14.dp, languageRailWidth = 200.dp, subtitleRailWidth = 300.dp, @@ -581,14 +595,15 @@ internal data class SubtitleModalLayoutMetrics( val languageWidth = (availableWidth * 0.2f).coerceIn(132.dp, 168.dp) val subtitleWidth = (availableWidth * 0.34f).coerceIn(210.dp, 260.dp) val remainingStyleWidth = availableWidth - languageWidth - subtitleWidth - railGap * 2 + val titleBottomPadding = 8.dp return SubtitleModalLayoutMetrics( isCompact = true, horizontalPadding = horizontalPadding, topPadding = 24.dp, bottomPadding = 60.dp, - titleReservedHeight = 52.dp, - titleBottomPadding = 8.dp, + titleReservedHeight = titleLineHeight + titleBottomPadding, + titleBottomPadding = titleBottomPadding, railGap = railGap, languageRailWidth = languageWidth, subtitleRailWidth = subtitleWidth, diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt index ab0527a92..ceac4853c 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/player/SubtitleModalLayoutTest.kt @@ -13,6 +13,7 @@ class SubtitleModalLayoutTest { val metrics = SubtitleModalLayoutMetrics.from( maxWidth = 884.dp, maxHeight = 400.dp, + titleLineHeight = 32.dp, ) assertTrue(metrics.isCompact) @@ -25,6 +26,7 @@ class SubtitleModalLayoutTest { val metrics = SubtitleModalLayoutMetrics.from( maxWidth = 1280.dp, maxHeight = 720.dp, + titleLineHeight = 36.dp, ) assertFalse(metrics.isCompact) @@ -38,6 +40,7 @@ class SubtitleModalLayoutTest { val metrics = SubtitleModalLayoutMetrics.from( maxWidth = 640.dp, maxHeight = 360.dp, + titleLineHeight = 32.dp, ) assertTrue(metrics.isCompact) @@ -45,4 +48,16 @@ class SubtitleModalLayoutTest { assertTrue(metrics.subtitleRailWidth >= 210.dp) assertTrue(metrics.styleRailWidth >= 210.dp) } + + @Test + fun `compact title reservation follows scaled typography line height`() { + val metrics = SubtitleModalLayoutMetrics.from( + maxWidth = 884.dp, + maxHeight = 400.dp, + titleLineHeight = 64.dp, + ) + + assertEquals(72.dp, metrics.titleReservedHeight) + assertEquals(244.dp, metrics.railMaxHeight(400.dp)) + } }