Android Jetpack Compose, How to get Semantics from Another Composable?

Viewed 145

Introduction

I made a wrapper which gives a Composable a disabled look and prevents click events from being passed to it.

@Composable
fun DisableOverlay(enabled: Boolean, alpha: Float = 0.45F, content: @Composable () -> Unit) {
    if (enabled) {
        content()
    } else {
        Box(
            modifier = Modifier
                .wrapContentWidth()
                .wrapContentHeight()
                .alpha(alpha)
        ) {
            content()

            Box(modifier = Modifier
                .matchParentSize()
                .clickable(enabled = false){
                    //absorb the clicks
                }
            )
        }
    }
}

Below is an example usage.

        DisableOverlay(enabled = false) {
            Column{
                Text(text = "Some Text")
            }
        }

The Problem

It works fine but has some issues with accessibility reader (TalkBack) is on. The reader does not read the content Composable on some devices. It reads the text on Pixel devices but not Samsung. I noticed on the Pixel device it read the text content but does not honour any semantics set on it i.e. a contentDescription.

For example, the reader would not read "my content description" on this Text.

        DisableOverlay(enabled = false) {
            Column{
                Text(text = "Some Text", 
                    modifier = Modifier.semantics { 
                        contentDescription = "my content description"
                })
            }
        }

Attempted Solution 1 (FAILED)

I added clearAndSetSemantics on the Box that overlaps the content composable. My theory was the reader can ignore the element and just read out content. This did not work, the reader completely skips the content.

fun DisableOverlay(enabled: Boolean, alpha: Float = 0.45F, content: @Composable () -> Unit) {
    if (enabled) {
        content()
    } else {
        Box(
            modifier = Modifier
                .wrapContentWidth()
                .wrapContentHeight()
                .alpha(alpha)
        ) {
            content()

            Box(modifier = Modifier
                .matchParentSize()
                .clearAndSetSemantics {

                }
                .clickable(enabled = false){
                    //absorb the clicks
                }
            )
        }
    }
}

Attempted Solution 2 (FAILED)

I found a property called invisibleToUser(). This had the same effect as clearAndSetSemantics seen in Solution 1.

.semantics {
    invisibleToUser()
}

Attempted Solution 3 (FAILED)

I looked up a different way to disable click events using a blank pointerInput implementation. Since there is no clickable modifier the accessibility reader reads out the content composable and honours its semantics.

There is a deal breaker though. While this blocked touch events the accessibility reader is still able to select it.

@Composable
fun DisableOverlay(enabled: Boolean, alpha: Float = 0.45F, content: @Composable () -> Unit) {
    if (enabled) {
        content()
    } else {
        Box(
            modifier = Modifier
                .wrapContentWidth()
                .wrapContentHeight()
                .alpha(alpha)
        ) {
            content()

            Box(modifier = Modifier
                .matchParentSize()
                .pointerInput(Unit){
                    //absorb the clicks
                }
            )
        }
    }
}

Idea 1

If I set contentDescription on the Box that overlaps content, the reader reads it as expected. Ideally, I don't want to pass through a contentDescription, I want to just extract the semantics from content and just read it out. Is this possible?

@Composable
fun DisableOverlay(enabled: Boolean, alpha: Float = 0.45F, content: @Composable () -> Unit) {
    if (enabled) {
        content()
    } else {
        Box(
            modifier = Modifier
                .wrapContentWidth()
                .wrapContentHeight()
                .alpha(alpha)
        ) {
            content()

            Box(modifier = Modifier
                .matchParentSize()
                .clickable(enabled = false){
                    //absorb the clicks
                }
                .semantics {
                    contentDescription = "test"
                }
            )
        }
    }
}

Idea 2

My overall goal is to allow a Composable to be disabled just by wrapping it in another Composable. If anyone has an alternative solution to the above, please let me know.

Thanks for reading my question.

0 Answers
Related