https://kotlinlang.org logo
#compose
Title
# compose
m

mattinger

10/19/2020, 6:53 PM
I also don’t want the button to go grey so setting enabled = false isn’t my answer i don’t think.
j

Javier

10/19/2020, 6:55 PM
Maybe you should override the disabled theme
m

mattinger

10/19/2020, 6:57 PM
Honestly, the ideal thing is to stop the entire screen (or portion) from being clicked on, because there could be other content which is clickable.
s

Sergey Y.

10/19/2020, 6:57 PM
Have you tried:
Copy code
onClick { if (buttonState == IDLE) proceedClick() }
m

mattinger

10/19/2020, 6:57 PM
@Sergey Y. yes, but it allows other things to be clicked., plus i still see the ripple.
j

Javier

10/19/2020, 6:58 PM
onClick can be null?
if it is null, is it showing the ripple?
m

mattinger

10/19/2020, 6:58 PM
I accomplished this in the legacy android world by posting a panel over the top of the content which would swallow all the clicks.
@Javier nope, non nullable
also tried Modifier.clickable, which doesn’t change the behavior.
s

Sergey Y.

10/19/2020, 7:03 PM
Or you could try a more declarative-ish way and conditionally replace the real clickable button with a progress placeholder. For instance,
Copy code
@Composable
fun ProgressButton(state) {
   if (state == PROGRESS) {
       ProgressStubComposable()
   } else {
       StandardButton(...)
   }
}
For smooth transition you could use
Crossfade
composable function
Copy code
@Composable
fun ProgressButton(state) {
   Crossfade(state) {
      when(it) {
        PROGRESS -> ProgressStubComposable()
        IDLE -> StandardButton(...)
      }
   }
}
something like that
a

Adam Powell

10/19/2020, 7:35 PM
beware placing blocking elements overlapping things to accomplish this sort of thing; keyboard focus and accessibility services will click right through them 🙂
z

Zach Klippenstein (he/him) [MOD]

10/19/2020, 8:04 PM
How would you block acessibility? Is it enough to do something like
Modifier.semantics(mergeAllDescendants = true) { disabled() }
?
m

mattinger

10/21/2020, 2:14 PM
So I think i settled on a composable which alters the theme to make sure that disabled buttons look the same as enabled buttons.
And then i can just set the enabled flag on the button
Copy code
AlwaysEnabledButtonAppearance {
            StandardButton(
                onClick = {
                    if (buttonState.value == LoadingState.IDLE) {
                        buttonState.value = LoadingState.LOADING
                        GlobalScope.launch {
                            delay(3000L)
                            buttonState.value = LoadingState.IDLE
                        }
                    }
                },
                enabled = buttonState.value == LoadingState.IDLE
            ) {
                ButtonLoadingIndicator(buttonState)
                Text(text = "Foobar")
            }
        }
of course, this only works if that button is always in the enabled state, but i think that can be handled with a conditional if i need to go from a disabled button to a loading style button
j

Javier

10/21/2020, 2:21 PM
I was searching for a way to change the disabled color but it is not a property, it is an alpha of an existing color, how have you changed it? Custom theme?
z

Zach Klippenstein (he/him) [MOD]

10/21/2020, 2:24 PM
I think you change disabled color by providing an EmphasisAmbient
m

mattinger

10/21/2020, 2:24 PM
Yes.
I have my own theme where i specifically define the colors used in certain scenarios. That’s what StandardButton uses
Copy code
border = BorderStroke(
            XDSTheme.dimensions.standardButtonBorderWidth,
            ButtonConstants.defaultButtonBackgroundColor(
                enabled = enabled,
                defaultColor = XDSTheme.colors.buttonPrimaryColor,
                disabledColor = XDSTheme.colors.buttonDisabledColor
            )
        )
likewise for contentColor
So i can just wrap the existing theme and change the disabled colors to look like it’s an enabled button.
This makes it pretty simple now:
Copy code
@Composable
fun StandardLoadingButton(
    onClick: suspend () -> Unit,
    onClickScope: CoroutineScope = GlobalScope,
    modifier: Modifier = Modifier,
    buttonState: MutableState<LoadingState> = mutableStateOf(LoadingState.IDLE),
    content: @Composable RowScope.() -> Unit
) {
    AlwaysEnabledButtonAppearance {
        StandardButton(
            modifier = modifier,
            onClick = {
                if (buttonState.value == LoadingState.IDLE) {
                    buttonState.value = LoadingState.LOADING
                    onClickScope.launch {
                        onClick()
                        buttonState.value = LoadingState.IDLE
                    }
                }
            },
            enabled = buttonState.value == LoadingState.IDLE
        ) {
            ButtonLoadingIndicator(buttonState)
            content()
        }
    }
}
j

Javier

10/21/2020, 2:28 PM
interesting, and I would like to check the ambient solution, for apps which doesnt use custom themes can be enough
m

mattinger

10/21/2020, 2:28 PM
but the general trick is that if you change the border, backgroundColor and contentColor parameters on the Button you are instantiating you can also get this sort of control
this is where the source code comes in handy.
Copy code
border: BorderStroke? = null,
    backgroundColor: Color = ButtonConstants.defaultButtonBackgroundColor(enabled),
    contentColor: Color = ButtonConstants.defaultButtonContentColor(
        enabled,
        contentColorFor(backgroundColor)
    )
My StandardButton is overriding all 3 of these defaults to provide colors from my theme instead of the Material theme.
But if you’re just using material theme, you can just specify these directly in your composable
👍 1
you can just always pass true for the enabled flag to the functions shown above
3 Views