mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
Refactor TV home media cards smaller
This commit is contained in:
@@ -18,12 +18,11 @@ import androidx.compose.ui.input.key.Key
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import hu.bbara.purefin.core.model.Episode
|
import hu.bbara.purefin.core.model.Episode
|
||||||
import hu.bbara.purefin.core.model.LibraryKind
|
import hu.bbara.purefin.core.model.LibraryKind
|
||||||
import hu.bbara.purefin.core.model.MediaKind
|
|
||||||
import hu.bbara.purefin.core.model.Movie
|
import hu.bbara.purefin.core.model.Movie
|
||||||
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
|
import hu.bbara.purefin.core.ui.model.EpisodeUiModel
|
||||||
|
import hu.bbara.purefin.core.ui.model.MediaUiModel
|
||||||
|
import hu.bbara.purefin.core.ui.model.MovieUiModel
|
||||||
import hu.bbara.purefin.feature.browse.home.LibraryItem
|
import hu.bbara.purefin.feature.browse.home.LibraryItem
|
||||||
import hu.bbara.purefin.feature.browse.home.NextUpItem
|
|
||||||
import hu.bbara.purefin.feature.browse.home.PosterItem
|
|
||||||
import hu.bbara.purefin.ui.screen.home.TvHomeScreen
|
import hu.bbara.purefin.ui.screen.home.TvHomeScreen
|
||||||
import hu.bbara.purefin.ui.theme.AppTheme
|
import hu.bbara.purefin.ui.theme.AppTheme
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@@ -68,7 +67,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatching(),
|
continueWatching = sampleContinueWatching(),
|
||||||
nextUp = emptyList(),
|
nextUp = emptyList(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -80,7 +78,6 @@ class TvHomeContentTest {
|
|||||||
|
|
||||||
composeRule.onNodeWithTag(TvHomeInitialFocusTag).assertIsFocused()
|
composeRule.onNodeWithTag(TvHomeInitialFocusTag).assertIsFocused()
|
||||||
composeRule.onNodeWithTag(TvHomeHeroTitleTag).assertTextEquals("Blade Runner 2049")
|
composeRule.onNodeWithTag(TvHomeHeroTitleTag).assertTextEquals("Blade Runner 2049")
|
||||||
composeRule.onNodeWithTag(TvHomeHeroProgressLabelTag).assertTextEquals("42%")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -92,7 +89,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatchingRow(),
|
continueWatching = sampleContinueWatchingRow(),
|
||||||
nextUp = emptyList(),
|
nextUp = emptyList(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -126,7 +122,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatching(),
|
continueWatching = sampleContinueWatching(),
|
||||||
nextUp = emptyList(),
|
nextUp = emptyList(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -156,7 +151,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatching(),
|
continueWatching = sampleContinueWatching(),
|
||||||
nextUp = sampleNextUp(),
|
nextUp = sampleNextUp(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -196,7 +190,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatching(),
|
continueWatching = sampleContinueWatching(),
|
||||||
nextUp = emptyList(),
|
nextUp = emptyList(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -228,7 +221,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatching(),
|
continueWatching = sampleContinueWatching(),
|
||||||
nextUp = sampleNextUp(),
|
nextUp = sampleNextUp(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -266,7 +258,6 @@ class TvHomeContentTest {
|
|||||||
libraryContent = sampleLibraryContent(),
|
libraryContent = sampleLibraryContent(),
|
||||||
continueWatching = sampleContinueWatching(),
|
continueWatching = sampleContinueWatching(),
|
||||||
nextUp = sampleNextUpRow(),
|
nextUp = sampleNextUpRow(),
|
||||||
serverUrl = "",
|
|
||||||
onMovieSelected = {},
|
onMovieSelected = {},
|
||||||
onSeriesSelected = {},
|
onSeriesSelected = {},
|
||||||
onEpisodeSelected = { _, _, _ -> }
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
@@ -295,11 +286,49 @@ class TvHomeContentTest {
|
|||||||
assertEquals(alignedTopBefore, alignedTopAfter, 2f)
|
assertEquals(alignedTopBefore, alignedTopAfter, 2f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sampleContinueWatching(): List<ContinueWatchingItem> {
|
@Test
|
||||||
|
fun tvHomeScreen_usesReducedMediaCardWidths() {
|
||||||
|
composeRule.setContent {
|
||||||
|
AppTheme {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(width = 960.dp, height = 540.dp)
|
||||||
|
) {
|
||||||
|
TvHomeScreen(
|
||||||
|
libraries = sampleLibraries(),
|
||||||
|
libraryContent = sampleLibraryContent(),
|
||||||
|
continueWatching = sampleContinueWatching(),
|
||||||
|
nextUp = emptyList(),
|
||||||
|
onMovieSelected = {},
|
||||||
|
onSeriesSelected = {},
|
||||||
|
onEpisodeSelected = { _, _, _ -> }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composeRule.waitForIdle()
|
||||||
|
|
||||||
|
val expectedLandscapeWidth = with(composeRule.density) { 223.2.dp.toPx() }
|
||||||
|
assertEquals(expectedLandscapeWidth, nodeWidth(TvHomeInitialFocusTag), 2f)
|
||||||
|
|
||||||
|
composeRule.onRoot().performKeyInput {
|
||||||
|
pressKey(Key.DirectionDown)
|
||||||
|
}
|
||||||
|
composeRule.waitForIdle()
|
||||||
|
|
||||||
|
val expectedPosterWidth = with(composeRule.density) { 122.4.dp.toPx() }
|
||||||
|
composeRule.onNodeWithTag(tvHomeLibraryFirstItemTag(sampleLibraryId())).assertIsDisplayed()
|
||||||
|
assertEquals(
|
||||||
|
expectedPosterWidth,
|
||||||
|
nodeWidth(tvHomeLibraryFirstItemTag(sampleLibraryId())),
|
||||||
|
2f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sampleContinueWatching(): List<MediaUiModel> {
|
||||||
return listOf(
|
return listOf(
|
||||||
ContinueWatchingItem(
|
MovieUiModel(
|
||||||
type = MediaKind.MOVIE,
|
sampleMovie(
|
||||||
movie = sampleMovie(
|
|
||||||
id = "11111111-1111-1111-1111-111111111111",
|
id = "11111111-1111-1111-1111-111111111111",
|
||||||
title = "Blade Runner 2049",
|
title = "Blade Runner 2049",
|
||||||
progress = 42.0
|
progress = 42.0
|
||||||
@@ -308,19 +337,17 @@ class TvHomeContentTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sampleContinueWatchingRow(): List<ContinueWatchingItem> {
|
private fun sampleContinueWatchingRow(): List<MediaUiModel> {
|
||||||
return listOf(
|
return listOf(
|
||||||
ContinueWatchingItem(
|
MovieUiModel(
|
||||||
type = MediaKind.MOVIE,
|
sampleMovie(
|
||||||
movie = sampleMovie(
|
|
||||||
id = "11111111-1111-1111-1111-111111111111",
|
id = "11111111-1111-1111-1111-111111111111",
|
||||||
title = "Blade Runner 2049",
|
title = "Blade Runner 2049",
|
||||||
progress = 42.0
|
progress = 42.0
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
ContinueWatchingItem(
|
MovieUiModel(
|
||||||
type = MediaKind.MOVIE,
|
sampleMovie(
|
||||||
movie = sampleMovie(
|
|
||||||
id = "55555555-5555-5555-5555-555555555555",
|
id = "55555555-5555-5555-5555-555555555555",
|
||||||
title = "Mad Max: Fury Road",
|
title = "Mad Max: Fury Road",
|
||||||
progress = 8.0
|
progress = 8.0
|
||||||
@@ -329,10 +356,10 @@ class TvHomeContentTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sampleNextUp(): List<NextUpItem> {
|
private fun sampleNextUp(): List<MediaUiModel> {
|
||||||
return listOf(
|
return listOf(
|
||||||
NextUpItem(
|
EpisodeUiModel(
|
||||||
episode = sampleEpisode(
|
sampleEpisode(
|
||||||
id = "66666666-6666-6666-6666-666666666666",
|
id = "66666666-6666-6666-6666-666666666666",
|
||||||
title = "The Train Job"
|
title = "The Train Job"
|
||||||
)
|
)
|
||||||
@@ -340,16 +367,16 @@ class TvHomeContentTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sampleNextUpRow(): List<NextUpItem> {
|
private fun sampleNextUpRow(): List<MediaUiModel> {
|
||||||
return listOf(
|
return listOf(
|
||||||
NextUpItem(
|
EpisodeUiModel(
|
||||||
episode = sampleEpisode(
|
sampleEpisode(
|
||||||
id = "66666666-6666-6666-6666-666666666666",
|
id = "66666666-6666-6666-6666-666666666666",
|
||||||
title = "The Train Job"
|
title = "The Train Job"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
NextUpItem(
|
EpisodeUiModel(
|
||||||
episode = sampleEpisode(
|
sampleEpisode(
|
||||||
id = "77777777-7777-7777-7777-777777777777",
|
id = "77777777-7777-7777-7777-777777777777",
|
||||||
title = "Safe",
|
title = "Safe",
|
||||||
releaseDate = "2024-02-09"
|
releaseDate = "2024-02-09"
|
||||||
@@ -370,14 +397,13 @@ class TvHomeContentTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sampleLibraryContent(): Map<UUID, List<PosterItem>> {
|
private fun sampleLibraryContent(): Map<UUID, List<MediaUiModel>> {
|
||||||
val libraryId = sampleLibraryId()
|
val libraryId = sampleLibraryId()
|
||||||
|
|
||||||
return mapOf(
|
return mapOf(
|
||||||
libraryId to listOf(
|
libraryId to listOf(
|
||||||
PosterItem(
|
MovieUiModel(
|
||||||
type = MediaKind.MOVIE,
|
sampleMovie(
|
||||||
movie = sampleMovie(
|
|
||||||
id = "44444444-4444-4444-4444-444444444444",
|
id = "44444444-4444-4444-4444-444444444444",
|
||||||
title = "Arrival",
|
title = "Arrival",
|
||||||
progress = null,
|
progress = null,
|
||||||
@@ -462,4 +488,11 @@ class TvHomeContentTest {
|
|||||||
.boundsInRoot
|
.boundsInRoot
|
||||||
.top
|
.top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun nodeWidth(tag: String): Float {
|
||||||
|
return composeRule.onNodeWithTag(tag, useUnmergedTree = true)
|
||||||
|
.fetchSemanticsNode()
|
||||||
|
.boundsInRoot
|
||||||
|
.width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ fun PosterCard(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
imageModifier: Modifier = Modifier,
|
imageModifier: Modifier = Modifier,
|
||||||
posterWidth: Dp = 144.dp,
|
posterWidth: Dp = 144.dp,
|
||||||
|
contentScale: Float = 1f,
|
||||||
showSecondaryText: Boolean = false,
|
showSecondaryText: Boolean = false,
|
||||||
indicatorSize: Int = 28,
|
indicatorSize: Int = 28,
|
||||||
indicatorPadding: Dp = 8.dp,
|
indicatorPadding: Dp = 8.dp,
|
||||||
@@ -39,6 +40,7 @@ fun PosterCard(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
imageModifier = imageModifier,
|
imageModifier = imageModifier,
|
||||||
posterWidth = posterWidth,
|
posterWidth = posterWidth,
|
||||||
|
contentScale = contentScale,
|
||||||
showSecondaryText = showSecondaryText,
|
showSecondaryText = showSecondaryText,
|
||||||
indicatorSize = indicatorSize,
|
indicatorSize = indicatorSize,
|
||||||
indicatorPadding = indicatorPadding,
|
indicatorPadding = indicatorPadding,
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
@@ -43,8 +47,21 @@ fun TvHomeContent(
|
|||||||
val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() }
|
val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() }
|
||||||
val hasContinueWatching = continueWatching.isNotEmpty()
|
val hasContinueWatching = continueWatching.isNotEmpty()
|
||||||
val hasNextUp = nextUp.isNotEmpty()
|
val hasNextUp = nextUp.isNotEmpty()
|
||||||
|
val hasLibraryItems = libraries.any { libraryContent[it.id].orEmpty().isNotEmpty() }
|
||||||
|
val firstLibraryWithItemsId = libraries.firstOrNull {
|
||||||
|
libraryContent[it.id].orEmpty().isNotEmpty()
|
||||||
|
}?.id
|
||||||
val hasVisibleContent = hasContinueWatching || hasNextUp
|
val hasVisibleContent = hasContinueWatching || hasNextUp
|
||||||
|
val hasInitialFocusableItem = hasContinueWatching || hasNextUp || hasLibraryItems
|
||||||
val initialFocusRequester = remember { FocusRequester() }
|
val initialFocusRequester = remember { FocusRequester() }
|
||||||
|
var initialFocusApplied by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(hasInitialFocusableItem, initialFocusApplied) {
|
||||||
|
if (!initialFocusApplied && hasInitialFocusableItem) {
|
||||||
|
initialFocusRequester.requestFocus()
|
||||||
|
initialFocusApplied = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalBringIntoViewSpec provides tvHomeColumnBringIntoViewSpec(topOffsetPx)
|
LocalBringIntoViewSpec provides tvHomeColumnBringIntoViewSpec(topOffsetPx)
|
||||||
@@ -94,7 +111,10 @@ fun TvHomeContent(
|
|||||||
title = library.name,
|
title = library.name,
|
||||||
items = libraryContent[library.id].orEmpty(),
|
items = libraryContent[library.id].orEmpty(),
|
||||||
onFocusedItem = onMediaFocused,
|
onFocusedItem = onMediaFocused,
|
||||||
firstItemFocusRequester = initialFocusRequester,
|
firstItemFocusRequester = initialFocusRequester.takeIf {
|
||||||
|
!hasContinueWatching && !hasNextUp && library.id == firstLibraryWithItemsId
|
||||||
|
},
|
||||||
|
firstItemTestTag = tvHomeLibraryFirstItemTag(library.id),
|
||||||
onMovieSelected = onMovieSelected,
|
onMovieSelected = onMovieSelected,
|
||||||
onSeriesSelected = onSeriesSelected,
|
onSeriesSelected = onSeriesSelected,
|
||||||
onEpisodeSelected = onEpisodeSelected
|
onEpisodeSelected = onEpisodeSelected
|
||||||
|
|||||||
@@ -50,12 +50,25 @@ import hu.bbara.purefin.ui.common.bar.MediaProgressBar
|
|||||||
import hu.bbara.purefin.ui.common.card.PosterCard
|
import hu.bbara.purefin.ui.common.card.PosterCard
|
||||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private val TvHomeSectionsThumbShape = RoundedCornerShape(20.dp)
|
private const val TvHomeMediaCardScale = 0.9f
|
||||||
|
private val TvHomeSectionsThumbShape = RoundedCornerShape((20f * TvHomeMediaCardScale).dp)
|
||||||
private val TvHomeSectionsHorizontalPadding = 32.dp
|
private val TvHomeSectionsHorizontalPadding = 32.dp
|
||||||
private val TvHomeSectionsRowSpacing = 18.dp
|
private val TvHomeSectionsRowSpacing = 18.dp
|
||||||
private val TvHomeLandscapeCardWidth = 248.dp
|
private val TvHomeLandscapeCardWidth = (248f * TvHomeMediaCardScale).dp
|
||||||
private val TvHomePosterCardWidth = 136.dp
|
private val TvHomePosterCardWidth = (136f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeFocusedBorderWidth = (2f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeUnfocusedBorderWidth = (1f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeProgressBarHeight = (6f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeProgressBarPadding = (8f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeMetadataSpacing = (4f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeMetadataTopPadding = (12f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeMetadataHorizontalPadding = (4f * TvHomeMediaCardScale).dp
|
||||||
|
private val TvHomeLandscapeTitleFontSize = (15f * TvHomeMediaCardScale).sp
|
||||||
|
private val TvHomeLandscapeSupportingFontSize = (11f * TvHomeMediaCardScale).sp
|
||||||
|
private val TvHomePosterIndicatorSize = (24f * TvHomeMediaCardScale).roundToInt()
|
||||||
|
private val TvHomePosterIndicatorPadding = (6f * TvHomeMediaCardScale).dp
|
||||||
internal const val TvHomeSectionRowTagPrefix = "tv-home-section-row-"
|
internal const val TvHomeSectionRowTagPrefix = "tv-home-section-row-"
|
||||||
internal const val TvHomeContinueWatchingRowTag = "${TvHomeSectionRowTagPrefix}continue-watching"
|
internal const val TvHomeContinueWatchingRowTag = "${TvHomeSectionRowTagPrefix}continue-watching"
|
||||||
internal const val TvHomeNextUpRowTag = "${TvHomeSectionRowTagPrefix}next-up"
|
internal const val TvHomeNextUpRowTag = "${TvHomeSectionRowTagPrefix}next-up"
|
||||||
@@ -63,6 +76,9 @@ internal const val TvHomeNextUpRowTag = "${TvHomeSectionRowTagPrefix}next-up"
|
|||||||
internal fun tvHomeLibraryRowTag(libraryId: UUID): String =
|
internal fun tvHomeLibraryRowTag(libraryId: UUID): String =
|
||||||
"${TvHomeSectionRowTagPrefix}library-$libraryId"
|
"${TvHomeSectionRowTagPrefix}library-$libraryId"
|
||||||
|
|
||||||
|
internal fun tvHomeLibraryFirstItemTag(libraryId: UUID): String =
|
||||||
|
"${TvHomeSectionRowTagPrefix}library-item-$libraryId"
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TvContinueWatchingSection(
|
fun TvContinueWatchingSection(
|
||||||
items: List<MediaUiModel>,
|
items: List<MediaUiModel>,
|
||||||
@@ -190,9 +206,10 @@ fun TvLibraryPosterSection(
|
|||||||
PosterCard(
|
PosterCard(
|
||||||
item = item,
|
item = item,
|
||||||
posterWidth = TvHomePosterCardWidth,
|
posterWidth = TvHomePosterCardWidth,
|
||||||
|
contentScale = TvHomeMediaCardScale,
|
||||||
showSecondaryText = true,
|
showSecondaryText = true,
|
||||||
indicatorSize = 24,
|
indicatorSize = TvHomePosterIndicatorSize,
|
||||||
indicatorPadding = 6.dp,
|
indicatorPadding = TvHomePosterIndicatorPadding,
|
||||||
imageModifier = Modifier
|
imageModifier = Modifier
|
||||||
.then(
|
.then(
|
||||||
if (index == 0 && firstItemFocusRequester != null) {
|
if (index == 0 && firstItemFocusRequester != null) {
|
||||||
@@ -296,7 +313,7 @@ private fun TvHomeLandscapeCard(
|
|||||||
.aspectRatio(16f / 9f)
|
.aspectRatio(16f / 9f)
|
||||||
.clip(TvHomeSectionsThumbShape)
|
.clip(TvHomeSectionsThumbShape)
|
||||||
.border(
|
.border(
|
||||||
width = if (isFocused) 2.dp else 1.dp,
|
width = if (isFocused) TvHomeLandscapeFocusedBorderWidth else TvHomeLandscapeUnfocusedBorderWidth,
|
||||||
color = if (isFocused) scheme.primary else scheme.outlineVariant.copy(alpha = 0.6f),
|
color = if (isFocused) scheme.primary else scheme.outlineVariant.copy(alpha = 0.6f),
|
||||||
shape = TvHomeSectionsThumbShape
|
shape = TvHomeSectionsThumbShape
|
||||||
)
|
)
|
||||||
@@ -335,21 +352,28 @@ private fun TvHomeLandscapeCard(
|
|||||||
foregroundColor = scheme.primary,
|
foregroundColor = scheme.primary,
|
||||||
backgroundColor = scheme.surfaceVariant,
|
backgroundColor = scheme.surfaceVariant,
|
||||||
contentPadding = PaddingValues(0.dp),
|
contentPadding = PaddingValues(0.dp),
|
||||||
barHeight = 6.dp,
|
barHeight = TvHomeLandscapeProgressBarHeight,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomStart)
|
.align(Alignment.BottomStart)
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
.padding(
|
||||||
|
horizontal = TvHomeLandscapeProgressBarPadding,
|
||||||
|
vertical = TvHomeLandscapeProgressBarPadding
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(TvHomeLandscapeMetadataSpacing),
|
||||||
modifier = Modifier.padding(top = 12.dp, start = 4.dp, end = 4.dp)
|
modifier = Modifier.padding(
|
||||||
|
top = TvHomeLandscapeMetadataTopPadding,
|
||||||
|
start = TvHomeLandscapeMetadataHorizontalPadding,
|
||||||
|
end = TvHomeLandscapeMetadataHorizontalPadding
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
color = scheme.onBackground,
|
color = scheme.onBackground,
|
||||||
fontSize = 15.sp,
|
fontSize = TvHomeLandscapeTitleFontSize,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
@@ -358,7 +382,7 @@ private fun TvHomeLandscapeCard(
|
|||||||
Text(
|
Text(
|
||||||
text = supporting,
|
text = supporting,
|
||||||
color = scheme.onSurfaceVariant,
|
color = scheme.onSurfaceVariant,
|
||||||
fontSize = 11.sp,
|
fontSize = TvHomeLandscapeSupportingFontSize,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ fun PosterCardContent(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
imageModifier: Modifier = Modifier,
|
imageModifier: Modifier = Modifier,
|
||||||
posterWidth: Dp = 144.dp,
|
posterWidth: Dp = 144.dp,
|
||||||
|
contentScale: Float = 1f,
|
||||||
showSecondaryText: Boolean = false,
|
showSecondaryText: Boolean = false,
|
||||||
indicatorSize: Int = 28,
|
indicatorSize: Int = 28,
|
||||||
indicatorPadding: Dp = 8.dp,
|
indicatorPadding: Dp = 8.dp,
|
||||||
@@ -74,6 +75,14 @@ fun PosterCardContent(
|
|||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
val shape = RoundedCornerShape((14f * contentScale).dp)
|
||||||
|
val unfocusedBorderWidth = (1f * contentScale).dp
|
||||||
|
val scaledFocusedBorderWidth = focusedBorderWidth * contentScale
|
||||||
|
val contentTopPadding = (8f * contentScale).dp
|
||||||
|
val contentHorizontalPadding = (4f * contentScale).dp
|
||||||
|
val contentBottomPadding = (8f * contentScale).dp
|
||||||
|
val titleFontSize = (13f * contentScale).sp
|
||||||
|
val secondaryFontSize = (11f * contentScale).sp
|
||||||
val scale by animateFloatAsState(
|
val scale by animateFloatAsState(
|
||||||
targetValue = if (isFocused) focusedScale else 1f,
|
targetValue = if (isFocused) focusedScale else 1f,
|
||||||
label = "scale"
|
label = "scale"
|
||||||
@@ -94,11 +103,11 @@ fun PosterCardContent(
|
|||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = imageModifier
|
modifier = imageModifier
|
||||||
.aspectRatio(2f / 3f)
|
.aspectRatio(2f / 3f)
|
||||||
.clip(RoundedCornerShape(14.dp))
|
.clip(shape)
|
||||||
.border(
|
.border(
|
||||||
width = if (isFocused) focusedBorderWidth else 1.dp,
|
width = if (isFocused) scaledFocusedBorderWidth else unfocusedBorderWidth,
|
||||||
color = if (isFocused) scheme.primary else scheme.outlineVariant.copy(alpha = 0.3f),
|
color = if (isFocused) scheme.primary else scheme.outlineVariant.copy(alpha = 0.3f),
|
||||||
shape = RoundedCornerShape(14.dp)
|
shape = shape
|
||||||
)
|
)
|
||||||
.background(scheme.surfaceVariant)
|
.background(scheme.surfaceVariant)
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
@@ -125,12 +134,17 @@ fun PosterCardContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(top = 8.dp, start = 4.dp, end = 4.dp, bottom = 8.dp)
|
modifier = Modifier.padding(
|
||||||
|
top = contentTopPadding,
|
||||||
|
start = contentHorizontalPadding,
|
||||||
|
end = contentHorizontalPadding,
|
||||||
|
bottom = contentBottomPadding
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = model.primaryText,
|
text = model.primaryText,
|
||||||
color = scheme.onBackground,
|
color = scheme.onBackground,
|
||||||
fontSize = 13.sp,
|
fontSize = titleFontSize,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
@@ -142,7 +156,7 @@ fun PosterCardContent(
|
|||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
color = scheme.onSurfaceVariant,
|
color = scheme.onSurfaceVariant,
|
||||||
fontSize = 11.sp,
|
fontSize = secondaryFontSize,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user