Question regarding handling Focus the right way fo...
# compose
o
Question regarding handling Focus the right way for Leanback devices in jetpack compose. I am trying to solve a focus issue in case of jetpack compose where the focus skips and moves to away from the required composable in case of Dpad UP. Hence trying to find a right way to fix this problem. On Dpad down- the collapsed state of the view is visible as ashown in the video On Dpad up - the expanded state of the view is visible as shown in the video in case of dpad up, i want the
child box of parent column 2
to receive focus first before showing the collapsed state os the view. but in my case it just skips setting the focus there first and then showing the collapsed state. 🧵
Screen_recording_20231217_115104.webm
Copy code
AnimatedVisibility(
        visible = isVisible && menuState == MenuState.Collapsed,
        modifier = modifier
            .padding(bottom = minimizedHeightDp)
            .align(Alignment.BottomCenter)
            .fillMaxWidth()
            .height(controlsHeightDp),
        enter = fadeIn(),
        exit = fadeOut()
    ) {
        LogCompositions(message = "TvMenuOverlay Controls AnimatedVisibility Scope")
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(PlayerMenuComponentTokens.Color.Bg.Container.normal)
                .ifElse(
                    { isControlsRegionFocused.value },
                    Modifier.border(2.dp, Color.White),
                    Modifier.border(2.dp, Color.Yellow)
                )
                .onFocusChanged {
                    if (it.hasFocus) {
                        Logger.d("TEST", "OverlaysState.Collapsed")
                        updateOverlaysState(OverlaysState.MetadataWithCollapsedMenu)
                    }
                    isControlsRegionFocused.value = it.hasFocus
                }
        ) {
            LogCompositions(message = "TvMenuOverlay Controls Scope")
            Text(
                text = "Parent Column 1",
                color = Color.Green,
                fontFamily = FontFamily.Default,
                textAlign = TextAlign.Center
            )
            Row(
                modifier = Modifier
                    .padding(
                        top = PlayerMenuComponentTokens.Padding.Top.Container.normal,
                        bottom = PlayerMenuComponentTokens.Spacing.btwnButtonsScrubber,
                        start = PlayerMenuComponentTokens.Padding.LR.Container.normal,
                        end = PlayerMenuComponentTokens.Padding.LR.Container.normal
                    )
                    .fillMaxWidth()
                    .height(ButtonComponentTokens.Height.normal)
                    .ifElse(
                        { isButtonsRowFocused.value },
                        Modifier.border(2.dp, Color.White),
                        Modifier.border(2.dp, Color.Yellow)
                    )
                    .onFocusChanged { isButtonsRowFocused.value = it.isFocused }
                    .focusRequester(buttonsRowFocusRequester)
                    .focusProperties {
                        up = FocusRequester.Cancel
                        down = FocusRequester.Default
                        left = FocusRequester.Cancel
                        right = FocusRequester.Cancel
                    }
                    .focusable()
            ) {
                Text(
                    text = "Child Row of Parent Column 1",
                    color = Color.Green,
                    fontFamily = FontFamily.Default,
                    textAlign = TextAlign.Center
                )
                LogCompositions(message = "TvMenuOverlay Buttons Scope")
            }
            Box(
                modifier = Modifier
                    .padding(
                        start = PlayerMenuComponentTokens.Padding.LR.Container.normal,
                        end = PlayerMenuComponentTokens.Padding.LR.Container.normal
                    )
                    .fillMaxWidth()
                    .height(PlaybackScrubberComponentTokens.Size.Handle.focused)
                    .ifElse(
                        { isScrubberFocused.value },
                        Modifier.border(2.dp, Color.White),
                        Modifier.border(2.dp, Color.Yellow)
                    )
                    .onFocusChanged { isScrubberFocused.value = it.isFocused }
                    .focusRequester(scrubberFocusRequester)
                    .focusProperties {
                        up = FocusRequester.Default
                        down = FocusRequester.Default
                        left = FocusRequester.Cancel
                        right = FocusRequester.Cancel
                    }
                    .focusable()
            ) {
                Text(
                    text = "Child Box of Parent Column 1",
                    color = Color.Green,
                    fontFamily = FontFamily.Default,
                    textAlign = TextAlign.Center
                )
                LogCompositions(message = "TvMenuOverlay Scrubber Scope")
            }
        }
    }

    AnimatedVisibility(
        visible = isVisible,
        modifier = modifier
            .graphicsLayer { this.translationY = translationY.value }
            .align(Alignment.BottomCenter)
            .fillMaxWidth()
            .height(expandedHeightDp),
        enter = fadeIn(),
        exit = fadeOut()
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(PlayerMenuComponentTokens.Color.Bg.Container.normal)
                .ifElse(
                    { isContentRegionFocused.value },
                    Modifier.border(2.dp, Color.White),
                    Modifier.border(2.dp, Color.Yellow)
                )
                .onFocusChanged {
                    if (it.hasFocus) {
                        Logger.d("TEST", "OverlaysState.ExpandedMenu")
                        updateOverlaysState(OverlaysState.ExpandedMenu)
                    }
                    isContentRegionFocused.value = it.hasFocus
                }
        ) {
            Text(
                text = "Parent Column 2",
                color = Color.Green,
                fontFamily = FontFamily.Default,
                textAlign = TextAlign.Center
            )
            Box(
                modifier = Modifier
                    .padding(
                        top = PlayerMenuComponentTokens.Padding.Top.Container.normal,
                        bottom = PlayerMenuComponentTokens.Spacing.btwnTabsSection,
                        start = PlayerMenuComponentTokens.Padding.LR.Container.normal,
                        end = PlayerMenuComponentTokens.Padding.LR.Container.normal
                    )
                    .fillMaxWidth()
                    .height(TabsComponentTokens.Height.Bg.TabItem.normal)
                    .ifElse(
                        { isTabsRowFocused.value },
                        Modifier.border(2.dp, Color.White),
                        Modifier.border(2.dp, Color.Yellow)
                    )
                    .onKeyEvent { keyEvent ->
                        val nativeKeyEvent = keyEvent.nativeKeyEvent
                        if (nativeKeyEvent.isDPadUp()) {
                            nativeKeyEvent.onAction(
                                down = { return@onAction true },
                                up= {
                                    updateOverlaysState(OverlaysState.MetadataWithCollapsedMenu)
                                    return@onAction true
                                }
                            )
                        }
                        return@onKeyEvent false
                    }
                    .onFocusChanged { isTabsRowFocused.value = it.isFocused }
                    .focusRequester(tabsRowFocusRequester)
                    .focusProperties {
                        up = FocusRequester.Default
                        down = FocusRequester.Default
                        left = FocusRequester.Cancel
                        right = FocusRequester.Cancel
                    }
                    .focusable()
            ) {
                Text(
                    text = "Child Box of Parent Column 2",
                    color = Color.Green,
                    fontFamily = FontFamily.Default,
                    textAlign = TextAlign.Center
                )
                LogCompositions(message = "TvMenuOverlay Tabs & Hint Scope")
            }
            Box(
                modifier = Modifier
                    .padding(bottom = PlayerMenuComponentTokens.Padding.Btm.Container.normal)
                    .fillMaxSize()
                    .ifElse(
                        { isContentFocused.value },
                        Modifier.border(2.dp, Color.White),
                        Modifier.border(2.dp, Color.Yellow)
                    )
                    .onFocusChanged { isContentFocused.value = it.isFocused }
                    .focusRequester(contentFocusRequester)
                    .focusProperties {
                        up = FocusRequester.Default
                        down = FocusRequester.Cancel
                        left = FocusRequester.Cancel
                        right = FocusRequester.Cancel
                    }
                    .focusable()
            ) {
                Text(
                    text = "Child Box of Parent Column 2",
                    color = Color.Green,
                    fontFamily = FontFamily.Default,
                    textAlign = TextAlign.Center
                )
                LogCompositions(message = "TvMenuOverlay Content Scope")
            }
        }
    }
it seems like the
onKeyEvent
for the
Child Box of Parent Column 2
when receiving focus and the key event triggers the dpad up lambda which results in the focus being skipped from that composable completely. I need some guidance on how to handle the focus correctly in the above case to set it first onto
Child Box of Parent Column 2
and then show the collapsed menu state.