diff --git a/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt b/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt index c5b722c4..1ef07d35 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/common/ui/PosterCard.kt @@ -20,9 +20,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusProperties -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer @@ -42,9 +39,6 @@ import org.jellyfin.sdk.model.api.BaseItemKind fun PosterCard( item: PosterItem, modifier: Modifier = Modifier, - focusRequester: FocusRequester? = null, - upFocusRequester: FocusRequester? = null, - downFocusRequester: FocusRequester? = null, onMovieSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit, @@ -67,19 +61,6 @@ fun PosterCard( } } - val imageFocusModifier = Modifier - .then(if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier) - .then( - if (upFocusRequester != null || downFocusRequester != null) { - Modifier.focusProperties { - upFocusRequester?.let { up = it } - downFocusRequester?.let { down = it } - } - } else { - Modifier - } - ) - Column( modifier = modifier .width(posterWidth) @@ -93,7 +74,7 @@ fun PosterCard( PurefinAsyncImage( model = item.imageUrl, contentDescription = null, - modifier = imageFocusModifier + modifier = Modifier .aspectRatio(2f / 3f) .clip(RoundedCornerShape(14.dp)) .border( diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt index cac0c034..69bfbf73 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeContent.kt @@ -1,5 +1,6 @@ package hu.bbara.purefin.tv.home.ui +import android.annotation.SuppressLint import androidx.compose.foundation.background import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -8,10 +9,13 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component1 +import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component2 +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.unit.dp import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem import hu.bbara.purefin.feature.shared.home.LibraryItem @@ -19,6 +23,7 @@ import hu.bbara.purefin.feature.shared.home.NextUpItem import hu.bbara.purefin.feature.shared.home.PosterItem import org.jellyfin.sdk.model.UUID +@SuppressLint("RememberInComposition") @Composable fun TvHomeContent( libraries: List, @@ -33,36 +38,12 @@ fun TvHomeContent( val visibleLibraries = remember(libraries, libraryContent) { libraries.filter { libraryContent[it.id]?.isEmpty() != true } } - val continueWatchingSectionFocusRequester = remember { FocusRequester() } - val continueWatchingFirstItemFocusRequester = remember { FocusRequester() } - val nextUpSectionFocusRequester = remember { 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 { - continueWatching.isNotEmpty() -> continueWatchingSectionFocusRequester - nextUp.isNotEmpty() -> nextUpSectionFocusRequester - 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 + + val ( nextUpRef, continueWatchingRef ) = remember { FocusRequester.createRefs() } + val libraryRefs = remember(libraryContent) { + libraryContent.keys.associateWith { FocusRequester() } } - LaunchedEffect(firstSectionFocusRequester) { - firstSectionFocusRequester?.requestFocus() - } LazyColumn( modifier = modifier @@ -75,11 +56,12 @@ fun TvHomeContent( item { TvContinueWatchingSection( items = continueWatching, - sectionFocusRequester = continueWatchingSectionFocusRequester, - firstItemFocusRequester = continueWatchingFirstItemFocusRequester, - downFocusRequester = firstHomeRowBelowContinueWatching, onMovieSelected = onMovieSelected, - onEpisodeSelected = onEpisodeSelected + onEpisodeSelected = onEpisodeSelected, + modifier = Modifier.focusRequester(continueWatchingRef) + .focusProperties{ + down = nextUpRef + } ) } item { @@ -88,12 +70,12 @@ fun TvHomeContent( item { TvNextUpSection( items = nextUp, - sectionFocusRequester = nextUpSectionFocusRequester, - firstItemFocusRequester = nextUpFirstItemFocusRequester, - upFocusRequester = continueWatching.takeIf { it.isNotEmpty() } - ?.let { continueWatchingSectionFocusRequester }, - downFocusRequester = firstVisibleLibrarySectionFocusRequester, - onEpisodeSelected = onEpisodeSelected + onEpisodeSelected = onEpisodeSelected, + modifier = Modifier.focusRequester(nextUpRef) + .focusProperties{ + up = continueWatchingRef + libraryRefs.values.firstOrNull()?.let { down = it } + } ) } item { @@ -103,29 +85,19 @@ fun TvHomeContent( items = visibleLibraries, key = { it.id } ) { item -> - val libraryIndex = visibleLibraries.indexOfFirst { it.id == item.id } - val previousLibrarySectionFocusRequester = visibleLibraries - .getOrNull(libraryIndex - 1) - ?.let { librarySectionFocusRequesters[it.id] } - val nextLibrarySectionFocusRequester = visibleLibraries - .getOrNull(libraryIndex + 1) - ?.let { librarySectionFocusRequesters[it.id] } - + val ref = libraryRefs[item.id] TvLibraryPosterSection( title = item.name, items = libraryContent[item.id] ?: emptyList(), action = "See All", - sectionFocusRequester = librarySectionFocusRequesters.getValue(item.id), - firstItemFocusRequester = libraryFirstItemFocusRequesters.getValue(item.id), - upFocusRequester = if (libraryIndex == 0) { - firstHomeRowAboveLibraries - } else { - previousLibrarySectionFocusRequester - }, - downFocusRequester = nextLibrarySectionFocusRequester, onMovieSelected = onMovieSelected, onSeriesSelected = onSeriesSelected, - onEpisodeSelected = onEpisodeSelected + onEpisodeSelected = onEpisodeSelected, + modifier = if (ref != null) Modifier.focusRequester(ref) else Modifier + .focusProperties { + up = nextUpRef + libraryRefs.values.dropWhile { it != ref }.drop(1).firstOrNull()?.let { down = it } + } ) Spacer(modifier = Modifier.height(8.dp)) } diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt index 70dbf85e..3e44bff7 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/home/ui/TvHomeSections.kt @@ -28,16 +28,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusProperties -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -55,10 +49,6 @@ import kotlin.math.nextUp @Composable fun TvContinueWatchingSection( items: List, - sectionFocusRequester: FocusRequester, - firstItemFocusRequester: FocusRequester? = null, - upFocusRequester: FocusRequester? = null, - downFocusRequester: FocusRequester? = null, onMovieSelected: (UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit, modifier: Modifier = Modifier @@ -70,20 +60,15 @@ fun TvContinueWatchingSection( ) LazyRow( modifier = modifier - .fillMaxWidth() - .focusRequester(sectionFocusRequester) - .focusRestorer(firstItemFocusRequester ?: FocusRequester.Default), + .fillMaxWidth(), contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { itemsIndexed(items = items) { index, item -> TvContinueWatchingCard( item = item, - focusRequester = if (index == 0) firstItemFocusRequester else null, isFirstItem = index == 0, isLastItem = index == items.lastIndex, - upFocusRequester = upFocusRequester, - downFocusRequester = downFocusRequester, onMovieSelected = onMovieSelected, onEpisodeSelected = onEpisodeSelected ) @@ -95,18 +80,13 @@ fun TvContinueWatchingSection( fun TvContinueWatchingCard( item: ContinueWatchingItem, modifier: Modifier = Modifier, - focusRequester: FocusRequester? = null, isFirstItem: Boolean = false, isLastItem: Boolean = false, - upFocusRequester: FocusRequester? = null, - downFocusRequester: FocusRequester? = null, onMovieSelected: (UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit, ) { val scheme = MaterialTheme.colorScheme - val context = LocalContext.current - val density = LocalDensity.current var isFocused by remember { mutableStateOf(false) } val scale by animateFloatAsState(targetValue = if (isFocused) 1.07f else 1.0f, label = "scale") @@ -130,32 +110,6 @@ fun TvContinueWatchingCard( } } - val imageFocusModifier = Modifier - .then(if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier) - .then( - if (upFocusRequester != null || downFocusRequester != null) { - Modifier.focusProperties { - upFocusRequester?.let { up = it } - downFocusRequester?.let { down = it } - if (isFirstItem) { - left = FocusRequester.Cancel - } - if (isLastItem) { - right = FocusRequester.Cancel - } - } - } else { - Modifier.focusProperties { - if (isFirstItem) { - left = FocusRequester.Cancel - } - if (isLastItem) { - right = FocusRequester.Cancel - } - } - } - ) - Column( modifier = modifier .width(cardWidth) @@ -181,7 +135,7 @@ fun TvContinueWatchingCard( PurefinAsyncImage( model = imageUrl, contentDescription = null, - modifier = imageFocusModifier + modifier = Modifier .fillMaxSize() .onFocusChanged { isFocused = it.isFocused } .clickable { @@ -220,10 +174,6 @@ fun TvContinueWatchingCard( @Composable fun TvNextUpSection( items: List, - sectionFocusRequester: FocusRequester, - firstItemFocusRequester: FocusRequester? = null, - upFocusRequester: FocusRequester? = null, - downFocusRequester: FocusRequester? = null, onEpisodeSelected: (UUID, UUID, UUID) -> Unit, modifier: Modifier = Modifier ) { @@ -234,9 +184,7 @@ fun TvNextUpSection( ) LazyRow( modifier = modifier - .fillMaxWidth() - .focusRequester(sectionFocusRequester) - .focusRestorer(firstItemFocusRequester ?: FocusRequester.Default), + .fillMaxWidth(), contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { @@ -246,11 +194,8 @@ fun TvNextUpSection( ) { index, item -> TvNextUpCard( item = item, - focusRequester = if (index == 0) firstItemFocusRequester else null, isFirstItem = index == 0, isLastItem = index == items.lastIndex, - upFocusRequester = upFocusRequester, - downFocusRequester = downFocusRequester, onEpisodeSelected = onEpisodeSelected ) } @@ -261,11 +206,8 @@ fun TvNextUpSection( fun TvNextUpCard( item: NextUpItem, modifier: Modifier = Modifier, - focusRequester: FocusRequester? = null, isFirstItem: Boolean = false, isLastItem: Boolean = false, - upFocusRequester: FocusRequester? = null, - downFocusRequester: FocusRequester? = null, onEpisodeSelected: (UUID, UUID, UUID) -> Unit, ) { val scheme = MaterialTheme.colorScheme @@ -282,32 +224,6 @@ fun TvNextUpCard( onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id) } - val imageFocusModifier = Modifier - .then(if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier) - .then( - if (upFocusRequester != null || downFocusRequester != null) { - Modifier.focusProperties { - upFocusRequester?.let { up = it } - downFocusRequester?.let { down = it } - if (isFirstItem) { - left = FocusRequester.Cancel - } - if (isLastItem) { - right = FocusRequester.Cancel - } - } - } else { - Modifier.focusProperties { - if (isFirstItem) { - left = FocusRequester.Cancel - } - if (isLastItem) { - right = FocusRequester.Cancel - } - } - } - ) - Column( modifier = modifier .width(cardWidth) @@ -333,7 +249,7 @@ fun TvNextUpCard( PurefinAsyncImage( model = imageUrl, contentDescription = null, - modifier = imageFocusModifier + modifier = Modifier .fillMaxSize() .onFocusChanged { isFocused = it.isFocused } .clickable { @@ -367,10 +283,6 @@ fun TvLibraryPosterSection( title: String, items: List, action: String?, - sectionFocusRequester: FocusRequester, - firstItemFocusRequester: FocusRequester? = null, - upFocusRequester: FocusRequester? = null, - downFocusRequester: FocusRequester? = null, modifier: Modifier = Modifier, onMovieSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit, @@ -382,9 +294,7 @@ fun TvLibraryPosterSection( ) LazyRow( modifier = modifier - .fillMaxWidth() - .focusRequester(sectionFocusRequester) - .focusRestorer(firstItemFocusRequester ?: FocusRequester.Default), + .fillMaxWidth(), contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { @@ -394,9 +304,6 @@ fun TvLibraryPosterSection( ) { index, item -> PosterCard( item = item, - focusRequester = if (index == 0) firstItemFocusRequester else null, - upFocusRequester = upFocusRequester, - downFocusRequester = downFocusRequester, onMovieSelected = onMovieSelected, onSeriesSelected = onSeriesSelected, onEpisodeSelected = onEpisodeSelected