I've been experimenting with "in place modal editi...
# compose
t
I've been experimenting with "in place modal editing". An example would be a label that becomes an editable text field right in place, but does so modally, freezing out interactions with the rest of the UI until "completed". I'm curious how more seasoned composers would go about this? I've tried two approaches with mixed results so far. 1. Just use an AlertDialog. For this implementation, I use
onGloballyPositioned
to capture the box of the "target widget", then when I open a full screen alert dialog that covers it all, I place whatever edit widget (e.g. BasicTextField) directly over the target widget. To get that precise placement, I had to use a fillFullSize() Column, which meant there was no dismiss region, so that had to be emulated through the column's clickable, and tweak the indication so there was no full screen ripple. This requires a bit of what feels like "transfer of state" between the modal dialog back to the normal tree. This was a fun experiment though, because it allowed me to learn more about Dialog(s). 2. For my second attempt, I chose to learn more about custom CompositionLocals. I created a ScrimShield compasable that's meant to sit at the top of the screen and makes an optional scrimhole (giggle) state available anywhere in the tree below. This allows a widget that wants to become modal to be right in the normal tree, but just throw its globallyPositioned box in the scrimhole, which the root ScrimShield then uses to place a view over the whole screen. Actually, it places a series of views "around" the hole which do an alpha overlay and eat events, but allow the widget in the "hole" to be the only widget capable of interacting. So it essentially casts an event eating/color shading set of views over all of the rest of the screen. I like this approach (a little) better, it seems to fit the "composable" nature of the behavior. But I wish that the scrim shield could be implemented a little more gracefully (I wish you could define a Layout that drew an arbitrary cover Path, and could then filter touch inputs based on whether they were in the said path or not)
m
Can’t you just use a “drawWithContent” modifier to draw the scrim over the existing page, and set the view to ignore inputs anywhere but the view that’s exposed?
Copy code
@Composable
fun ScrimShieldLayout(
    modifier: Modifier = Modifier,
    showScrim: Boolean = false,
    content: @Composable () -> Unit,
) {
    Box(
        modifier = modifier.then(
            if (showScrim) {
                Modifier.drawWithContent {
                    drawContent()
                    drawRoundRect(
                        color = Color.Green,
                        alpha = 0.3f,
                        size = size
                    )
                }
            } else {
                Modifier
            }
        )
    ) {
        content()
    }
}


@Composable
@Preview(showBackground = true)
fun ScrimShieldLayoutPreview() {
    ScrimShieldLayout(
        showScrim = true,
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "booya")
    }
}
this is simplistic and you’d obviously have to draw multiple rects based on the position of the actual element you wanted to shield. This saves you having to add actual composables to your hierarchy when you can draw the scrim this way.
t
IIUC, this just puts a cover (scrim) over the current composition. It won't be able to go to the edges of the screen (unless I make this my top level composition). I do agree that I can probably draw the scrim, rather than using a panopoly of views with background. BUT, that would only solve the visual part. It wouldn't mask out all user input, except for in the hole area. It's not clear to me how to use the modifiers to do fine tuned pointer detection, it seems that the pointer detection is very much assuming the layout rectangles?
m
Yeah, i’ve never really attempted that, but if you look at how the Scrim function works, it’s using the “pointerInput” modifier, which might be able to help you detect if the input is within the region you’re interested in and drop it if it’s not