const val GRID_COLUMN_COUNT = 4
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun MoviesScreenTv(
viewModel: MoviesViewModel = koinViewModel(),
onMovieClick: (MovieItem) -> Unit,
onFocusBackToTab: FocusRequester? = null,
//contentEntryRequester: FocusRequester,
homeLeft: FocusRequester?,
homeRight: FocusRequester? = null
//onShowFilters: () -> Unit
) {
val uiState by viewModel.uiState.collectAsState()
val gridState = rememberLazyGridState()
val scope = rememberCoroutineScope()
val shouldLoadMore = remember {
derivedStateOf {
if (uiState.movies.size < 4) {
false
} else {
val lastVisibleItem = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
val totalItems = uiState.movies.size
lastVisibleItem >= totalItems - 4
}
}
}
LaunchedEffect(shouldLoadMore.value) {
val canLoad = uiState.canLoadMore && !uiState.isLoading && !uiState.isLoadingMore
if (shouldLoadMore.value && canLoad) {
viewModel.loadMoreMovies()
}
}
//ALL CONTAINER FOCUS HANDLE
// 1. Создаем FocusRequester'ы
val parentFocusRequester = remember { FocusRequester() }
val childFocusRequester = remember { FocusRequester() }
// 2. Модификатор для родительского контейнера
val parentModifier = Modifier
.focusRequester(parentFocusRequester)
.focusProperties {
onExit = {
parentFocusRequester.saveFocusedChild() // Сохраняем текущий фокус при выходе
FocusRequester.Default
}
enter = {
if (parentFocusRequester.restoreFocusedChild()) {
FocusRequester.Cancel // Если восстановили фокус - отменяем стандартное поведение
} else {
childFocusRequester // Иначе фокусируемся на дочернем элементе
}
}
}
// 3. Модификатор для дочернего элемента
val childModifier = Modifier.focusRequester(childFocusRequester)
val x2 = FocusRequesterModifiers(parentModifier, childModifier)
//ALL CONTAINER FOCUS HANDLE
Column(modifier = x2.parentModifier.fillMaxSize().focusGroup()/*FOCUS_GROUP*/) {
if (uiState.isLoading && uiState.movies.isEmpty()) {
LoadingViewTv(modifier = Modifier.fillMaxSize())
} else if (uiState.error != null && uiState.movies.isEmpty()) {
val backToUpOrHomeModifier = Modifier.focusProperties {
onFocusBackToTab?.let { up = it }
homeLeft?.let { left = it }
}
ErrorViewTv(
modifier = backToUpOrHomeModifier,
message = uiState.error ?: "",
onRetry = {
viewModel.refreshMovies()
})
} else {
var lastFocusedIndex by remember { mutableStateOf<Int?>(0) }
LazyVerticalGrid(
columns = GridCells.Fixed(GRID_COLUMN_COUNT),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxSize().focusGroup(),//@@@@
content = {
itemsIndexed(uiState.movies) { index, movie ->
val modifier = Modifier.focusEdges(
index = index,
gridColumnCount = GRID_COLUMN_COUNT,
homeLeft = homeLeft,
homeRight = homeRight,
scope = scope,
onFocusBackToTab = onFocusBackToTab
)
AnimatedVisibility(
visible = true,
enter = fadeIn(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
),
exit = fadeOut()
) {
MovieCardTv(
modifier = modifier.then(if (index == lastFocusedIndex) x2.childModifier else Modifier)
.onFocusChanged { state ->
if (state.isFocused) {
lastFocusedIndex = index
}
},
movie = movie,
onClick = { onMovieClick(movie) }
)
// LaunchedEffect(lastFocusedIndex) {
// if (lastFocusedIndex == index) {
// itemFocusRequester.requestFocus()
// }
// }
}
}
if (uiState.isLoadingMore) {
item(span = { GridItemSpan(maxLineSpan) }) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
LoadingViewTv(
style = MaterialTheme.typography.bodyLarge
)
}
}
}
},
state = gridState
)
LaunchedEffect(Unit) {
parentFocusRequester.requestFocus(scope = scope)
}
}
}
}
fun Modifier.focusEdges(
index: Int,
gridColumnCount: Int,
homeLeft: FocusRequester? = null,
homeRight: FocusRequester? = null,
scope: CoroutineScope,
onFocusBackToTab: FocusRequester? = null
): Modifier {
var m = this
if (onFocusBackToTab != null && index < gridColumnCount) {
m = m.focusProperties { up = onFocusBackToTab }
}
m = m.onKeyEvent { keyEvent ->
if (keyEvent.type != KeyEventType.KeyDown) return@onKeyEvent false
when (keyEvent.key) {
Key.DirectionLeft -> {
if (index % gridColumnCount == 0 && homeLeft != null) {
homeLeft.requestFocus(scope = scope)
true
} else false
}
Key.DirectionRight -> {
if ((index + 1) % gridColumnCount == 0 && homeRight != null) {
homeRight.requestFocus(scope = scope)
true
} else false
}
else -> false
}
}
return m
}
//@Composable
//fun <T>CG0(
// state: LazyGridState,
// items: List<T>,
// content: LazyGridScope.() -> Unit
//) {
// LazyVerticalGrid(
// columns = GridCells.Fixed(GRID_COLUMN_COUNT),
// contentPadding = PaddingValues(16.dp),
// verticalArrangement = Arrangement.spacedBy(16.dp),
// horizontalArrangement = Arrangement.spacedBy(16.dp),
// modifier = Modifier.fillMaxSize(),
// state = state,
// content = content
// )
//}
@Composable
fun CinemaLazyVerticalGrid(
state: LazyGridState,
content: LazyGridScope.() -> Unit
) {
LazyVerticalGrid(
columns = GridCells.Fixed(GRID_COLUMN_COUNT),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxSize(),
state = state,
content = content
)
}
//@Composable
//fun TvAdaptiveGrid(
// items: List<Any>,
// state: LazyGridState,
// content: @Composable (item: Any, index: Int) -> Unit,
// home: FocusRequester? = null,
//) {
// CG0(state = state) {
// itemsIndexed(items) { index, item ->
// content(item, index)
// }
// }
//}
/**
* Стандартная адаптивная сетка для TV
*/
@Composable
fun TvAdaptiveGridDefault(
items: List<Any>,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = tvScreenPadding,
horizontalArrangement: androidx.compose.foundation.layout.Arrangement.Horizontal = androidx.compose.foundation.layout.Arrangement.spacedBy(
tvCardSpacing
),
verticalArrangement: androidx.compose.foundation.layout.Arrangement.Vertical = androidx.compose.foundation.layout.Arrangement.spacedBy(
tvCardSpacing
),
content: @Composable (item: Any, index: Int) -> Unit
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = TvSizes.GRID_MIN_SIZE.dp),
contentPadding = contentPadding,
horizontalArrangement = horizontalArrangement,
verticalArrangement = verticalArrangement,
modifier = modifier
) {
itemsIndexed(items) { index, item ->
content(item, index)
}
}
}
Комментариев нет:
Отправить комментарий