mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
fix(tv): streamline focus management and improve component structure in TV sections
This commit is contained in:
@@ -29,12 +29,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||||
|
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||||
import hu.bbara.purefin.ui.model.EpisodeUiModel
|
import hu.bbara.purefin.ui.model.EpisodeUiModel
|
||||||
import hu.bbara.purefin.ui.model.MediaUiModel
|
import hu.bbara.purefin.ui.model.MediaUiModel
|
||||||
import hu.bbara.purefin.ui.model.MovieUiModel
|
import hu.bbara.purefin.ui.model.MovieUiModel
|
||||||
import hu.bbara.purefin.ui.model.SeriesUiModel
|
import hu.bbara.purefin.ui.model.SeriesUiModel
|
||||||
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
|
||||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -43,7 +43,6 @@ 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,
|
||||||
@@ -65,7 +64,6 @@ 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,
|
||||||
@@ -82,11 +80,10 @@ 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,
|
||||||
onFocused: () -> Unit = {},
|
onFocused: () -> Unit,
|
||||||
focusedScale: Float = 1f,
|
focusedScale: Float = 1f,
|
||||||
focusedBorderWidth: Dp = 1.dp,
|
focusedBorderWidth: Dp = 1.dp,
|
||||||
focusedTransformOrigin: TransformOrigin = TransformOrigin(0.5f, 0f)
|
focusedTransformOrigin: TransformOrigin = TransformOrigin(0.5f, 0f)
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
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.focus.focusRequester
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.testTag
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import hu.bbara.purefin.ui.model.MediaUiModel
|
|
||||||
import hu.bbara.purefin.ui.model.LibraryUiModel
|
import hu.bbara.purefin.ui.model.LibraryUiModel
|
||||||
|
import hu.bbara.purefin.ui.model.MediaUiModel
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item"
|
internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item"
|
||||||
@@ -46,9 +46,6 @@ fun TvHomeContent(
|
|||||||
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 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 hasInitialFocusableItem = hasContinueWatching || hasNextUp || hasLibraryItems
|
||||||
val initialFocusRequester = remember { FocusRequester() }
|
val initialFocusRequester = remember { FocusRequester() }
|
||||||
@@ -68,7 +65,7 @@ fun TvHomeContent(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(scheme.background)
|
.background(scheme.background)
|
||||||
.testTag(TvHomeContentViewportTag),
|
.focusRequester(initialFocusRequester),
|
||||||
contentPadding = contentPadding
|
contentPadding = contentPadding
|
||||||
) {
|
) {
|
||||||
item(key = "tv-home-top-spacer") {
|
item(key = "tv-home-top-spacer") {
|
||||||
@@ -80,9 +77,6 @@ fun TvHomeContent(
|
|||||||
items = continueWatching,
|
items = continueWatching,
|
||||||
onFocusedItem = onMediaFocused,
|
onFocusedItem = onMediaFocused,
|
||||||
onMediaSelected = onMediaSelected,
|
onMediaSelected = onMediaSelected,
|
||||||
firstItemFocusRequester = initialFocusRequester,
|
|
||||||
firstItemTestTag = TvHomeInitialFocusTag,
|
|
||||||
rowTestTag = TvHomeContinueWatchingRowTag
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,9 +87,6 @@ fun TvHomeContent(
|
|||||||
items = nextUp,
|
items = nextUp,
|
||||||
onFocusedItem = onMediaFocused,
|
onFocusedItem = onMediaFocused,
|
||||||
onMediaSelected = onMediaSelected,
|
onMediaSelected = onMediaSelected,
|
||||||
firstItemFocusRequester = initialFocusRequester.takeIf { !hasContinueWatching },
|
|
||||||
firstItemTestTag = TvHomeInitialFocusTag.takeIf { !hasContinueWatching },
|
|
||||||
rowTestTag = TvHomeNextUpRowTag
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,10 +99,6 @@ fun TvHomeContent(
|
|||||||
title = library.name,
|
title = library.name,
|
||||||
items = libraryContent[library.id].orEmpty(),
|
items = libraryContent[library.id].orEmpty(),
|
||||||
onFocusedItem = onMediaFocused,
|
onFocusedItem = onMediaFocused,
|
||||||
firstItemFocusRequester = initialFocusRequester.takeIf {
|
|
||||||
!hasContinueWatching && !hasNextUp && library.id == firstLibraryWithItemsId
|
|
||||||
},
|
|
||||||
firstItemTestTag = tvHomeLibraryFirstItemTag(library.id),
|
|
||||||
onMediaSelected = onMediaSelected
|
onMediaSelected = onMediaSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -17,9 +18,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -28,26 +29,27 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.TransformOrigin
|
import androidx.compose.ui.graphics.TransformOrigin
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.testTag
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import hu.bbara.purefin.ui.model.MediaUiModel
|
|
||||||
import hu.bbara.purefin.ui.common.bar.MediaProgressBar
|
import hu.bbara.purefin.ui.common.bar.MediaProgressBar
|
||||||
import hu.bbara.purefin.ui.common.card.PosterCardContent
|
import hu.bbara.purefin.ui.common.card.PosterCardContent
|
||||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||||
|
import hu.bbara.purefin.ui.model.MediaUiModel
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -69,7 +71,6 @@ private val TvHomeLandscapeSupportingFontSize = (11f * TvHomeMediaCardScale).sp
|
|||||||
private val TvHomePosterIndicatorSize = (24f * TvHomeMediaCardScale).roundToInt()
|
private val TvHomePosterIndicatorSize = (24f * TvHomeMediaCardScale).roundToInt()
|
||||||
private val TvHomePosterIndicatorPadding = (6f * TvHomeMediaCardScale).dp
|
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 TvHomeNextUpRowTag = "${TvHomeSectionRowTagPrefix}next-up"
|
internal const val TvHomeNextUpRowTag = "${TvHomeSectionRowTagPrefix}next-up"
|
||||||
|
|
||||||
internal fun tvHomeLibraryRowTag(libraryId: UUID): String =
|
internal fun tvHomeLibraryRowTag(libraryId: UUID): String =
|
||||||
@@ -81,57 +82,35 @@ internal fun tvHomeLibraryFirstItemTag(libraryId: UUID): String =
|
|||||||
@Composable
|
@Composable
|
||||||
fun TvContinueWatchingSection(
|
fun TvContinueWatchingSection(
|
||||||
items: List<MediaUiModel>,
|
items: List<MediaUiModel>,
|
||||||
onFocusedItem: (MediaUiModel) -> Unit = {},
|
onFocusedItem: (MediaUiModel) -> Unit,
|
||||||
onMediaSelected: (MediaUiModel) -> Unit,
|
onMediaSelected: (MediaUiModel) -> Unit,
|
||||||
firstItemFocusRequester: FocusRequester? = null,
|
|
||||||
firstItemTestTag: String? = null,
|
|
||||||
rowTestTag: String? = null,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
if (items.isEmpty()) return
|
if (items.isEmpty()) return
|
||||||
TvSectionHeader(
|
|
||||||
title = "Continue Watching",
|
|
||||||
)
|
|
||||||
TvHomeSectionRow(
|
TvHomeSectionRow(
|
||||||
|
items = items,
|
||||||
|
onFocused = onFocusedItem,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
rowTestTag = rowTestTag
|
) { item, onFocused, focusModifier ->
|
||||||
) {
|
TvHomeLandscapeCard(
|
||||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
title = item.primaryText,
|
||||||
TvHomeLandscapeCard(
|
supporting = item.secondaryText,
|
||||||
title = item.primaryText,
|
imageUrl = item.primaryImageUrl,
|
||||||
supporting = item.secondaryText,
|
progress = item.progress ?: 0f,
|
||||||
imageUrl = item.primaryImageUrl,
|
imageModifier = focusModifier,
|
||||||
progress = item.progress ?: 0f,
|
onFocusedItem = {
|
||||||
imageModifier = Modifier
|
onFocused(item)
|
||||||
.then(
|
},
|
||||||
if (index == 0 && firstItemFocusRequester != null) {
|
onClick = { onMediaSelected(item) }
|
||||||
Modifier.focusRequester(firstItemFocusRequester)
|
)
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
if (index == 0 && firstItemTestTag != null) {
|
|
||||||
Modifier.testTag(firstItemTestTag)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onFocusedItem = { onFocusedItem(item) },
|
|
||||||
onClick = { onMediaSelected(item) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TvNextUpSection(
|
fun TvNextUpSection(
|
||||||
items: List<MediaUiModel>,
|
items: List<MediaUiModel>,
|
||||||
onFocusedItem: (MediaUiModel) -> Unit = {},
|
onFocusedItem: (MediaUiModel) -> Unit,
|
||||||
onMediaSelected: (MediaUiModel) -> Unit,
|
onMediaSelected: (MediaUiModel) -> Unit,
|
||||||
firstItemFocusRequester: FocusRequester? = null,
|
|
||||||
firstItemTestTag: String? = null,
|
|
||||||
rowTestTag: String? = null,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
if (items.isEmpty()) return
|
if (items.isEmpty()) return
|
||||||
@@ -139,33 +118,18 @@ fun TvNextUpSection(
|
|||||||
title = "Next Up",
|
title = "Next Up",
|
||||||
)
|
)
|
||||||
TvHomeSectionRow(
|
TvHomeSectionRow(
|
||||||
|
items = items,
|
||||||
|
onFocused = onFocusedItem,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
rowTestTag = rowTestTag
|
) { item, onFocused, focusModifier ->
|
||||||
) {
|
TvHomeLandscapeCard(
|
||||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
title = item.primaryText,
|
||||||
TvHomeLandscapeCard(
|
supporting = item.secondaryText,
|
||||||
title = item.primaryText,
|
imageUrl = item.primaryImageUrl,
|
||||||
supporting = item.secondaryText,
|
imageModifier = focusModifier,
|
||||||
imageUrl = item.primaryImageUrl,
|
onFocusedItem = { onFocused(item) },
|
||||||
imageModifier = Modifier
|
onClick = { onMediaSelected(item) }
|
||||||
.then(
|
)
|
||||||
if (index == 0 && firstItemFocusRequester != null) {
|
|
||||||
Modifier.focusRequester(firstItemFocusRequester)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
if (index == 0 && firstItemTestTag != null) {
|
|
||||||
Modifier.testTag(firstItemTestTag)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onFocusedItem = { onFocusedItem(item) },
|
|
||||||
onClick = { onMediaSelected(item) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,74 +137,75 @@ fun TvNextUpSection(
|
|||||||
fun TvLibraryPosterSection(
|
fun TvLibraryPosterSection(
|
||||||
title: String,
|
title: String,
|
||||||
items: List<MediaUiModel>,
|
items: List<MediaUiModel>,
|
||||||
onFocusedItem: (MediaUiModel) -> Unit = {},
|
onFocusedItem: (MediaUiModel) -> Unit,
|
||||||
firstItemFocusRequester: FocusRequester? = null,
|
|
||||||
firstItemTestTag: String? = null,
|
|
||||||
rowTestTag: String? = null,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onMediaSelected: (MediaUiModel) -> Unit,
|
onMediaSelected: (MediaUiModel) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (items.isEmpty()) return
|
||||||
|
|
||||||
TvSectionHeader(
|
TvSectionHeader(
|
||||||
title = title,
|
title = title,
|
||||||
)
|
)
|
||||||
TvHomeSectionRow(
|
TvHomeSectionRow(
|
||||||
|
items = items,
|
||||||
|
onFocused = onFocusedItem,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
rowTestTag = rowTestTag
|
) { item, onFocused, focusModifier ->
|
||||||
) {
|
PosterCardContent(
|
||||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
model = item,
|
||||||
PosterCardContent(
|
onClick = { onMediaSelected(item) },
|
||||||
model = item,
|
onFocused = { onFocused(item) },
|
||||||
onClick = { onMediaSelected(item) },
|
posterWidth = TvHomePosterCardWidth,
|
||||||
posterWidth = TvHomePosterCardWidth,
|
showSecondaryText = true,
|
||||||
contentScale = TvHomeMediaCardScale,
|
indicatorSize = TvHomePosterIndicatorSize,
|
||||||
showSecondaryText = true,
|
indicatorPadding = TvHomePosterIndicatorPadding,
|
||||||
indicatorSize = TvHomePosterIndicatorSize,
|
imageModifier = focusModifier,
|
||||||
indicatorPadding = TvHomePosterIndicatorPadding,
|
focusedScale = 1.07f,
|
||||||
imageModifier = Modifier
|
focusedBorderWidth = 2.dp
|
||||||
.then(
|
)
|
||||||
if (index == 0 && firstItemFocusRequester != null) {
|
|
||||||
Modifier.focusRequester(firstItemFocusRequester)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
if (index == 0 && firstItemTestTag != null) {
|
|
||||||
Modifier.testTag(firstItemTestTag)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onFocused = { onFocusedItem(item) },
|
|
||||||
focusedScale = 1.07f,
|
|
||||||
focusedBorderWidth = 2.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TvHomeSectionRow(
|
private fun TvHomeSectionRow(
|
||||||
|
items: List<MediaUiModel>,
|
||||||
|
onFocused: (MediaUiModel) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
rowTestTag: String? = null,
|
childComponent: @Composable (
|
||||||
content: LazyListScope.() -> Unit,
|
item: MediaUiModel,
|
||||||
|
onFocused: (MediaUiModel) -> Unit,
|
||||||
|
focusModifier: Modifier,
|
||||||
|
) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
var focusedItemId by rememberSaveable { mutableStateOf(items.first().id) }
|
||||||
|
val rowScopedFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
CompositionLocalProvider(LocalBringIntoViewSpec provides TvHomeRowBringIntoViewSpec) {
|
CompositionLocalProvider(LocalBringIntoViewSpec provides TvHomeRowBringIntoViewSpec) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
|
state = listState,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.then(
|
.focusRestorer(rowScopedFocusRequester)
|
||||||
if (rowTestTag != null) {
|
.focusGroup(),
|
||||||
Modifier.testTag(rowTestTag)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentPadding = PaddingValues(horizontal = TvHomeSectionsHorizontalPadding),
|
contentPadding = PaddingValues(horizontal = TvHomeSectionsHorizontalPadding),
|
||||||
horizontalArrangement = Arrangement.spacedBy(TvHomeSectionsRowSpacing),
|
horizontalArrangement = Arrangement.spacedBy(TvHomeSectionsRowSpacing)
|
||||||
content = content
|
) {
|
||||||
)
|
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
||||||
|
val focusModifier =
|
||||||
|
if (item.id == focusedItemId) Modifier.focusRequester(rowScopedFocusRequester) else Modifier
|
||||||
|
childComponent(
|
||||||
|
item,
|
||||||
|
{
|
||||||
|
focusedItemId = item.id
|
||||||
|
onFocused(item)
|
||||||
|
},
|
||||||
|
focusModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +244,10 @@ private fun TvHomeLandscapeCard(
|
|||||||
) {
|
) {
|
||||||
val scheme = MaterialTheme.colorScheme
|
val scheme = MaterialTheme.colorScheme
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
val scale by animateFloatAsState(targetValue = if (isFocused) 1.055f else 1f, label = "tv-home-landscape-scale")
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isFocused) 1.055f else 1f,
|
||||||
|
label = "tv-home-landscape-scale"
|
||||||
|
)
|
||||||
val cardWidth = TvHomeLandscapeCardWidth
|
val cardWidth = TvHomeLandscapeCardWidth
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
Reference in New Issue
Block a user