mirror of
https://github.com/bbara04/Purefin.git
synced 2026-03-31 17:10:08 +02:00
Fix TV home row focus navigation
This commit is contained in:
@@ -24,6 +24,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusProperties
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
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.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -93,7 +94,11 @@ fun PosterCard(
|
|||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(posterWidth)
|
.width(posterWidth)
|
||||||
.graphicsLayer { scaleX = scale; scaleY = scale }
|
.graphicsLayer {
|
||||||
|
scaleX = scale
|
||||||
|
scaleY = scale
|
||||||
|
transformOrigin = TransformOrigin(0.5f, 0f)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Box() {
|
Box() {
|
||||||
PurefinAsyncImage(
|
PurefinAsyncImage(
|
||||||
|
|||||||
@@ -33,15 +33,31 @@ fun TvHomeContent(
|
|||||||
val visibleLibraries = remember(libraries, libraryContent) {
|
val visibleLibraries = remember(libraries, libraryContent) {
|
||||||
libraries.filter { libraryContent[it.id]?.isEmpty() != true }
|
libraries.filter { libraryContent[it.id]?.isEmpty() != true }
|
||||||
}
|
}
|
||||||
val continueWatchingFocusRequester = remember { FocusRequester() }
|
val continueWatchingSectionFocusRequester = remember { FocusRequester() }
|
||||||
val nextUpFocusRequester = remember { FocusRequester() }
|
val continueWatchingFirstItemFocusRequester = remember { FocusRequester() }
|
||||||
val libraryFocusRequesters = remember(visibleLibraries.map { it.id }) {
|
val nextUpSectionFocusRequester = remember { FocusRequester() }
|
||||||
visibleLibraries.associate { it.id to FocusRequester() }
|
val nextUpFirstItemFocusRequester = remember { FocusRequester() }
|
||||||
|
val librarySectionFocusRequesters = remember(visibleLibraries.map { it.id }) {
|
||||||
|
visibleLibraries.associate { library -> library.id to FocusRequester() }
|
||||||
|
}
|
||||||
|
val libraryFirstItemFocusRequesters = remember(visibleLibraries.map { it.id }) {
|
||||||
|
visibleLibraries.associate { library -> library.id to FocusRequester() }
|
||||||
}
|
}
|
||||||
val firstSectionFocusRequester = when {
|
val firstSectionFocusRequester = when {
|
||||||
continueWatching.isNotEmpty() -> continueWatchingFocusRequester
|
continueWatching.isNotEmpty() -> continueWatchingSectionFocusRequester
|
||||||
nextUp.isNotEmpty() -> nextUpFocusRequester
|
nextUp.isNotEmpty() -> nextUpSectionFocusRequester
|
||||||
else -> visibleLibraries.firstOrNull()?.let { libraryFocusRequesters[it.id] }
|
else -> visibleLibraries.firstOrNull()?.let { librarySectionFocusRequesters[it.id] }
|
||||||
|
}
|
||||||
|
val firstVisibleLibrarySectionFocusRequester = visibleLibraries.firstOrNull()
|
||||||
|
?.let { librarySectionFocusRequesters[it.id] }
|
||||||
|
val firstHomeRowBelowContinueWatching = when {
|
||||||
|
nextUp.isNotEmpty() -> nextUpSectionFocusRequester
|
||||||
|
else -> firstVisibleLibrarySectionFocusRequester
|
||||||
|
}
|
||||||
|
val firstHomeRowAboveLibraries = when {
|
||||||
|
nextUp.isNotEmpty() -> nextUpSectionFocusRequester
|
||||||
|
continueWatching.isNotEmpty() -> continueWatchingSectionFocusRequester
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(firstSectionFocusRequester) {
|
LaunchedEffect(firstSectionFocusRequester) {
|
||||||
@@ -59,11 +75,9 @@ fun TvHomeContent(
|
|||||||
item {
|
item {
|
||||||
TvContinueWatchingSection(
|
TvContinueWatchingSection(
|
||||||
items = continueWatching,
|
items = continueWatching,
|
||||||
firstItemFocusRequester = continueWatchingFocusRequester,
|
sectionFocusRequester = continueWatchingSectionFocusRequester,
|
||||||
downFocusRequester = when {
|
firstItemFocusRequester = continueWatchingFirstItemFocusRequester,
|
||||||
nextUp.isNotEmpty() -> nextUpFocusRequester
|
downFocusRequester = firstHomeRowBelowContinueWatching,
|
||||||
else -> visibleLibraries.firstOrNull()?.let { libraryFocusRequesters[it.id] }
|
|
||||||
},
|
|
||||||
onMovieSelected = onMovieSelected,
|
onMovieSelected = onMovieSelected,
|
||||||
onEpisodeSelected = onEpisodeSelected
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
@@ -74,9 +88,11 @@ fun TvHomeContent(
|
|||||||
item {
|
item {
|
||||||
TvNextUpSection(
|
TvNextUpSection(
|
||||||
items = nextUp,
|
items = nextUp,
|
||||||
firstItemFocusRequester = nextUpFocusRequester,
|
sectionFocusRequester = nextUpSectionFocusRequester,
|
||||||
upFocusRequester = continueWatching.takeIf { it.isNotEmpty() }?.let { continueWatchingFocusRequester },
|
firstItemFocusRequester = nextUpFirstItemFocusRequester,
|
||||||
downFocusRequester = visibleLibraries.firstOrNull()?.let { libraryFocusRequesters[it.id] },
|
upFocusRequester = continueWatching.takeIf { it.isNotEmpty() }
|
||||||
|
?.let { continueWatchingSectionFocusRequester },
|
||||||
|
downFocusRequester = firstVisibleLibrarySectionFocusRequester,
|
||||||
onEpisodeSelected = onEpisodeSelected
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -88,28 +104,25 @@ fun TvHomeContent(
|
|||||||
key = { it.id }
|
key = { it.id }
|
||||||
) { item ->
|
) { item ->
|
||||||
val libraryIndex = visibleLibraries.indexOfFirst { it.id == item.id }
|
val libraryIndex = visibleLibraries.indexOfFirst { it.id == item.id }
|
||||||
val previousLibraryFocusRequester = visibleLibraries
|
val previousLibrarySectionFocusRequester = visibleLibraries
|
||||||
.getOrNull(libraryIndex - 1)
|
.getOrNull(libraryIndex - 1)
|
||||||
?.let { libraryFocusRequesters[it.id] }
|
?.let { librarySectionFocusRequesters[it.id] }
|
||||||
val nextLibraryFocusRequester = visibleLibraries
|
val nextLibrarySectionFocusRequester = visibleLibraries
|
||||||
.getOrNull(libraryIndex + 1)
|
.getOrNull(libraryIndex + 1)
|
||||||
?.let { libraryFocusRequesters[it.id] }
|
?.let { librarySectionFocusRequesters[it.id] }
|
||||||
|
|
||||||
TvLibraryPosterSection(
|
TvLibraryPosterSection(
|
||||||
title = item.name,
|
title = item.name,
|
||||||
items = libraryContent[item.id] ?: emptyList(),
|
items = libraryContent[item.id] ?: emptyList(),
|
||||||
action = "See All",
|
action = "See All",
|
||||||
firstItemFocusRequester = libraryFocusRequesters[item.id],
|
sectionFocusRequester = librarySectionFocusRequesters.getValue(item.id),
|
||||||
|
firstItemFocusRequester = libraryFirstItemFocusRequesters.getValue(item.id),
|
||||||
upFocusRequester = if (libraryIndex == 0) {
|
upFocusRequester = if (libraryIndex == 0) {
|
||||||
when {
|
firstHomeRowAboveLibraries
|
||||||
nextUp.isNotEmpty() -> nextUpFocusRequester
|
|
||||||
continueWatching.isNotEmpty() -> continueWatchingFocusRequester
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
previousLibraryFocusRequester
|
previousLibrarySectionFocusRequester
|
||||||
},
|
},
|
||||||
downFocusRequester = nextLibraryFocusRequester,
|
downFocusRequester = nextLibrarySectionFocusRequester,
|
||||||
onMovieSelected = onMovieSelected,
|
onMovieSelected = onMovieSelected,
|
||||||
onSeriesSelected = onSeriesSelected,
|
onSeriesSelected = onSeriesSelected,
|
||||||
onEpisodeSelected = onEpisodeSelected
|
onEpisodeSelected = onEpisodeSelected
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.compose.ui.focus.focusProperties
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
|
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.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -54,6 +56,7 @@ import kotlin.math.nextUp
|
|||||||
@Composable
|
@Composable
|
||||||
fun TvContinueWatchingSection(
|
fun TvContinueWatchingSection(
|
||||||
items: List<ContinueWatchingItem>,
|
items: List<ContinueWatchingItem>,
|
||||||
|
sectionFocusRequester: FocusRequester,
|
||||||
firstItemFocusRequester: FocusRequester? = null,
|
firstItemFocusRequester: FocusRequester? = null,
|
||||||
upFocusRequester: FocusRequester? = null,
|
upFocusRequester: FocusRequester? = null,
|
||||||
downFocusRequester: FocusRequester? = null,
|
downFocusRequester: FocusRequester? = null,
|
||||||
@@ -67,7 +70,10 @@ fun TvContinueWatchingSection(
|
|||||||
action = null
|
action = null
|
||||||
)
|
)
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(sectionFocusRequester)
|
||||||
|
.focusRestorer(firstItemFocusRequester ?: FocusRequester.Default),
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -161,7 +167,11 @@ fun TvContinueWatchingCard(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(cardWidth)
|
.width(cardWidth)
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.graphicsLayer { scaleX = scale; scaleY = scale }
|
.graphicsLayer {
|
||||||
|
scaleX = scale
|
||||||
|
scaleY = scale
|
||||||
|
transformOrigin = TransformOrigin(0.5f, 0f)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -217,6 +227,7 @@ fun TvContinueWatchingCard(
|
|||||||
@Composable
|
@Composable
|
||||||
fun TvNextUpSection(
|
fun TvNextUpSection(
|
||||||
items: List<NextUpItem>,
|
items: List<NextUpItem>,
|
||||||
|
sectionFocusRequester: FocusRequester,
|
||||||
firstItemFocusRequester: FocusRequester? = null,
|
firstItemFocusRequester: FocusRequester? = null,
|
||||||
upFocusRequester: FocusRequester? = null,
|
upFocusRequester: FocusRequester? = null,
|
||||||
downFocusRequester: FocusRequester? = null,
|
downFocusRequester: FocusRequester? = null,
|
||||||
@@ -229,7 +240,10 @@ fun TvNextUpSection(
|
|||||||
action = null
|
action = null
|
||||||
)
|
)
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(sectionFocusRequester)
|
||||||
|
.focusRestorer(firstItemFocusRequester ?: FocusRequester.Default),
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -313,7 +327,11 @@ fun TvNextUpCard(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(cardWidth)
|
.width(cardWidth)
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.graphicsLayer { scaleX = scale; scaleY = scale }
|
.graphicsLayer {
|
||||||
|
scaleX = scale
|
||||||
|
scaleY = scale
|
||||||
|
transformOrigin = TransformOrigin(0.5f, 0f)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -364,6 +382,7 @@ fun TvLibraryPosterSection(
|
|||||||
title: String,
|
title: String,
|
||||||
items: List<PosterItem>,
|
items: List<PosterItem>,
|
||||||
action: String?,
|
action: String?,
|
||||||
|
sectionFocusRequester: FocusRequester,
|
||||||
firstItemFocusRequester: FocusRequester? = null,
|
firstItemFocusRequester: FocusRequester? = null,
|
||||||
upFocusRequester: FocusRequester? = null,
|
upFocusRequester: FocusRequester? = null,
|
||||||
downFocusRequester: FocusRequester? = null,
|
downFocusRequester: FocusRequester? = null,
|
||||||
@@ -377,7 +396,10 @@ fun TvLibraryPosterSection(
|
|||||||
action = action
|
action = action
|
||||||
)
|
)
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(sectionFocusRequester)
|
||||||
|
.focusRestorer(firstItemFocusRequester ?: FocusRequester.Default),
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user