mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor: remove overcomplicated TvHomeHeroState
This commit is contained in:
@@ -1,25 +1,23 @@
|
||||
package hu.bbara.purefin.ui.screen.home
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.ui.screen.home.components.TvFocusedItemHero
|
||||
import hu.bbara.purefin.ui.screen.home.components.TvHomeContent
|
||||
import hu.bbara.purefin.ui.screen.home.components.rememberTvHomeHeroState
|
||||
import java.util.UUID
|
||||
|
||||
private const val TvHomeHeroHeightFraction = 0.32f
|
||||
private val TvHomeMinHeroHeight = 160.dp
|
||||
private val TvHomeMaxHeroHeight = 200.dp
|
||||
|
||||
@Composable
|
||||
fun TvHomeScreen(
|
||||
libraries: List<LibraryItem>,
|
||||
@@ -32,39 +30,29 @@ fun TvHomeScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val heroState = rememberTvHomeHeroState(
|
||||
libraries = libraries,
|
||||
libraryContent = libraryContent,
|
||||
continueWatching = continueWatching,
|
||||
nextUp = nextUp
|
||||
)
|
||||
val focusedMediaUiModel = remember { mutableStateOf<MediaUiModel>(MovieUiModel.createPlaceholder()) }
|
||||
|
||||
BoxWithConstraints(
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(scheme.background)
|
||||
) {
|
||||
val heroHeight = heroState.focusedHero?.let {
|
||||
(maxHeight * TvHomeHeroHeightFraction)
|
||||
.coerceIn(TvHomeMinHeroHeight, TvHomeMaxHeroHeight)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
heroState.focusedHero?.let { hero ->
|
||||
TvFocusedItemHero(
|
||||
item = hero,
|
||||
height = heroHeight ?: TvHomeMinHeroHeight
|
||||
)
|
||||
}
|
||||
TvFocusedItemHero(
|
||||
item = focusedMediaUiModel.value,
|
||||
height = 250.dp
|
||||
)
|
||||
TvHomeContent(
|
||||
libraries = libraries,
|
||||
libraryContent = libraryContent,
|
||||
continueWatching = continueWatching,
|
||||
nextUp = nextUp,
|
||||
//TODO implement it by throwing out complex TvHomeHeroState shit ass code. Fuck ai btw
|
||||
onMediaFocused = {},
|
||||
onMediaFocused = {
|
||||
focusedMediaUiModel.value = it
|
||||
},
|
||||
onMovieSelected = onMovieSelected,
|
||||
onSeriesSelected = onSeriesSelected,
|
||||
onEpisodeSelected = onEpisodeSelected,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package hu.bbara.purefin.ui.screen.home.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -12,16 +11,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
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.setValue
|
||||
import androidx.compose.runtime.withFrameNanos
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.ui.model.MediaUiModel
|
||||
@@ -45,117 +37,67 @@ fun TvHomeContent(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val itemRegistry = rememberTvHomeItemRegistry(
|
||||
libraries = libraries,
|
||||
libraryContent = libraryContent,
|
||||
continueWatching = continueWatching,
|
||||
nextUp = nextUp
|
||||
)
|
||||
val visibleLibraries = itemRegistry.visibleLibraries
|
||||
val hasContinueWatching = continueWatching.isNotEmpty()
|
||||
val hasNextUp = nextUp.isNotEmpty()
|
||||
val hasLibraries = visibleLibraries.isNotEmpty()
|
||||
val hasVisibleContent = hasContinueWatching || hasNextUp || hasLibraries
|
||||
val firstVisibleLibraryId = visibleLibraries.firstOrNull()?.id
|
||||
val hasVisibleContent = hasContinueWatching || hasNextUp
|
||||
val initialFocusRequester = remember { FocusRequester() }
|
||||
val firstAvailableItemKey = itemRegistry.firstAvailableItemId
|
||||
var initialFocusApplied by remember { mutableStateOf(false) }
|
||||
val topOffsetPx = with(LocalDensity.current) { TvHomeFocusedItemTopOffset.toPx() }
|
||||
val columnBringIntoViewSpec = remember(topOffsetPx) {
|
||||
tvHomeColumnBringIntoViewSpec(topOffsetPx = topOffsetPx)
|
||||
}
|
||||
|
||||
LaunchedEffect(firstAvailableItemKey, initialFocusApplied) {
|
||||
if (!initialFocusApplied && firstAvailableItemKey != null) {
|
||||
withFrameNanos { }
|
||||
initialFocusRequester.requestFocus()
|
||||
initialFocusApplied = true
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(scheme.background)
|
||||
.testTag(TvHomeContentViewportTag),
|
||||
contentPadding = contentPadding
|
||||
) {
|
||||
item(key = "tv-home-top-spacer") {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalBringIntoViewSpec provides columnBringIntoViewSpec) {
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(scheme.background)
|
||||
.testTag(TvHomeContentViewportTag),
|
||||
contentPadding = contentPadding
|
||||
) {
|
||||
item(key = "tv-home-top-spacer") {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
if (hasContinueWatching) {
|
||||
item(key = "tv-home-continue-watching") {
|
||||
TvContinueWatchingSection(
|
||||
items = continueWatching,
|
||||
onFocusedItem = onMediaFocused,
|
||||
onMovieSelected = onMovieSelected,
|
||||
onEpisodeSelected = onEpisodeSelected,
|
||||
firstItemFocusRequester = initialFocusRequester,
|
||||
firstItemTestTag = TvHomeInitialFocusTag,
|
||||
rowTestTag = TvHomeContinueWatchingRowTag
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasContinueWatching && (hasNextUp || hasLibraries)) {
|
||||
item(key = "tv-home-post-continue-gap") {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNextUp) {
|
||||
item(key = "tv-home-next-up") {
|
||||
TvNextUpSection(
|
||||
items = nextUp,
|
||||
onFocusedItem = onMediaFocused,
|
||||
onEpisodeSelected = onEpisodeSelected,
|
||||
firstItemFocusRequester = initialFocusRequester.takeIf { !hasContinueWatching },
|
||||
firstItemTestTag = TvHomeInitialFocusTag.takeIf { !hasContinueWatching },
|
||||
rowTestTag = TvHomeNextUpRowTag
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLibraries && (hasContinueWatching || hasNextUp)) {
|
||||
item(key = "tv-home-post-next-up-gap") {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = visibleLibraries,
|
||||
key = { _, library -> library.id }
|
||||
) { index, library ->
|
||||
TvLibraryPosterSection(
|
||||
title = library.name,
|
||||
items = libraryContent[library.id].orEmpty(),
|
||||
if (hasContinueWatching) {
|
||||
item(key = "tv-home-continue-watching") {
|
||||
TvContinueWatchingSection(
|
||||
items = continueWatching,
|
||||
onFocusedItem = onMediaFocused,
|
||||
firstItemFocusRequester = initialFocusRequester.takeIf {
|
||||
!hasContinueWatching &&
|
||||
!hasNextUp &&
|
||||
library.id == firstVisibleLibraryId
|
||||
},
|
||||
firstItemTestTag = TvHomeInitialFocusTag.takeIf {
|
||||
!hasContinueWatching &&
|
||||
!hasNextUp &&
|
||||
library.id == firstVisibleLibraryId
|
||||
},
|
||||
rowTestTag = tvHomeLibraryRowTag(library.id),
|
||||
onMovieSelected = onMovieSelected,
|
||||
onSeriesSelected = onSeriesSelected,
|
||||
onEpisodeSelected = onEpisodeSelected
|
||||
onEpisodeSelected = onEpisodeSelected,
|
||||
firstItemFocusRequester = initialFocusRequester,
|
||||
firstItemTestTag = TvHomeInitialFocusTag,
|
||||
rowTestTag = TvHomeContinueWatchingRowTag
|
||||
)
|
||||
|
||||
if (index < visibleLibraries.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasVisibleContent) {
|
||||
item(key = "tv-home-trailing-space") {
|
||||
Spacer(modifier = Modifier.height(TvHomeBringIntoViewTrailingSpace))
|
||||
}
|
||||
if (hasNextUp) {
|
||||
item(key = "tv-home-next-up") {
|
||||
TvNextUpSection(
|
||||
items = nextUp,
|
||||
onFocusedItem = onMediaFocused,
|
||||
onEpisodeSelected = onEpisodeSelected,
|
||||
firstItemFocusRequester = initialFocusRequester.takeIf { !hasContinueWatching },
|
||||
firstItemTestTag = TvHomeInitialFocusTag.takeIf { !hasContinueWatching },
|
||||
rowTestTag = TvHomeNextUpRowTag
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = libraries,
|
||||
key = { _, library -> library.id }
|
||||
) { index, library ->
|
||||
TvLibraryPosterSection(
|
||||
title = library.name,
|
||||
items = libraryContent[library.id].orEmpty(),
|
||||
onFocusedItem = onMediaFocused,
|
||||
firstItemFocusRequester = initialFocusRequester,
|
||||
onMovieSelected = onMovieSelected,
|
||||
onSeriesSelected = onSeriesSelected,
|
||||
onEpisodeSelected = onEpisodeSelected
|
||||
)
|
||||
}
|
||||
|
||||
if (hasVisibleContent) {
|
||||
item(key = "tv-home-trailing-space") {
|
||||
Spacer(modifier = Modifier.height(TvHomeBringIntoViewTrailingSpace))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package hu.bbara.purefin.ui.screen.home.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import hu.bbara.purefin.core.ui.model.MediaUiModel
|
||||
import hu.bbara.purefin.feature.browse.home.FocusableItem
|
||||
import hu.bbara.purefin.feature.browse.home.LibraryItem
|
||||
import java.util.UUID
|
||||
|
||||
//TODO throw this out and simplify it.
|
||||
|
||||
internal data class TvHomeItemRegistry(
|
||||
val visibleLibraries: List<LibraryItem>,
|
||||
private val libraryContent: Map<UUID, List<MediaUiModel>>,
|
||||
private val continueWatching: List<MediaUiModel>,
|
||||
private val nextUp: List<MediaUiModel>,
|
||||
val firstAvailableItemId: UUID?,
|
||||
) {
|
||||
val firstAvailableItem: MediaUiModel?
|
||||
get() = firstAvailableItemId?.let(::itemById)
|
||||
|
||||
fun itemById(id: UUID): MediaUiModel? {
|
||||
return continueWatching.firstOrNull { it.id == id }
|
||||
?: nextUp.firstOrNull { it.id == id }
|
||||
?: visibleLibraries.asSequence()
|
||||
.mapNotNull { library -> libraryContent[library.id] }
|
||||
.flatten()
|
||||
.firstOrNull { it.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
internal class TvHomeHeroState(
|
||||
val focusedHero: MediaUiModel?,
|
||||
val onMediaFocused: (FocusableItem) -> Unit,
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun rememberTvHomeItemRegistry(
|
||||
libraries: List<LibraryItem>,
|
||||
libraryContent: Map<UUID, List<MediaUiModel>>,
|
||||
continueWatching: List<MediaUiModel>,
|
||||
nextUp: List<MediaUiModel>,
|
||||
): TvHomeItemRegistry {
|
||||
return remember(libraries, libraryContent, continueWatching, nextUp) {
|
||||
createTvHomeItemRegistry(
|
||||
libraries = libraries,
|
||||
libraryContent = libraryContent,
|
||||
continueWatching = continueWatching,
|
||||
nextUp = nextUp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createTvHomeItemRegistry(
|
||||
libraries: List<LibraryItem>,
|
||||
libraryContent: Map<UUID, List<MediaUiModel>>,
|
||||
continueWatching: List<MediaUiModel>,
|
||||
nextUp: List<MediaUiModel>,
|
||||
): TvHomeItemRegistry {
|
||||
val visibleLibraries = libraries.filter { libraryContent[it.id]?.isEmpty() != true }
|
||||
val firstAvailableItemId = continueWatching.firstOrNull()?.id
|
||||
?: nextUp.firstOrNull()?.id
|
||||
?: visibleLibraries.firstOrNull()?.id?.let { libraryId ->
|
||||
libraryContent[libraryId]?.firstOrNull()?.id
|
||||
}
|
||||
|
||||
return TvHomeItemRegistry(
|
||||
visibleLibraries = visibleLibraries,
|
||||
libraryContent = libraryContent,
|
||||
continueWatching = continueWatching,
|
||||
nextUp = nextUp,
|
||||
firstAvailableItemId = firstAvailableItemId
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun rememberTvHomeHeroState(
|
||||
libraries: List<LibraryItem>,
|
||||
libraryContent: Map<UUID, List<MediaUiModel>>,
|
||||
continueWatching: List<MediaUiModel>,
|
||||
nextUp: List<MediaUiModel>,
|
||||
): TvHomeHeroState {
|
||||
val itemRegistry = rememberTvHomeItemRegistry(
|
||||
libraries = libraries,
|
||||
libraryContent = libraryContent,
|
||||
continueWatching = continueWatching,
|
||||
nextUp = nextUp
|
||||
)
|
||||
var focusedItemId by remember { mutableStateOf<UUID?>(null) }
|
||||
val focusedItem = remember(focusedItemId, itemRegistry) {
|
||||
focusedItemId?.let(itemRegistry::itemById)
|
||||
}
|
||||
val focusedHero = remember(focusedItem, itemRegistry) {
|
||||
(focusedItem ?: itemRegistry.firstAvailableItem)
|
||||
}
|
||||
val onMediaFocused: (FocusableItem) -> Unit = remember {
|
||||
{ item ->
|
||||
focusedItemId = item.id
|
||||
}
|
||||
}
|
||||
|
||||
return remember(focusedHero, onMediaFocused) {
|
||||
TvHomeHeroState(
|
||||
focusedHero = focusedHero,
|
||||
onMediaFocused = onMediaFocused
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user