0

So the issue I'm facing is I have a button which can have dynamic text, button have a state isLoading which when turned true, View is recomposed to show loader. Now Loader have its own width & height. How do I manage the loader to follow the same dimensions as of the text which was shown earlier. Right now when loader appears the button size grow large & after the loading is complete & isLoading is false again, the button would come to its actually size with text inside it.

Edit 1: The approach suggested by @thracian is working fine, other than a weird issue where the button gets some predefined height (& so resulting in same value while calculating the view height), which makes the button looks taller than what I'm planning to implement. Attaching code for ref, how do I make this button follow wrapped content & at the same time get the same calculated value in view size as well.

@Composable
fun GreenCTA(
    modifier: Modifier = Modifier, onClick: () -> Unit, title: String, isShowLoader: Boolean
) {
    var buttonSize by remember {
        mutableStateOf(DpSize.Zero)
    }
    val density = LocalDensity.current
    Button(
        modifier = modifier
            .then(if (buttonSize != DpSize.Zero) modifier.size(buttonSize) else modifier)
            .onSizeChanged { newSize ->
                if (buttonSize == DpSize.Zero) {
                    buttonSize = with(density) {
                        newSize
                            .toSize()
                            .toDpSize()
                    }
                }
            },
        onClick = { onClick.invoke() },
        border = BorderStroke(1.dp, color = colorResource(id = R.color.pass_color_c2e6cd)),
        colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(id = R.color.pass_color_e3f6ec)),
        shape = RoundedCornerShape(6.dp),
        contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
        elevation = ButtonDefaults.elevation(0.dp),
    ) {
        if (isShowLoader) BlueLoader(
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(3f)
        )
        else {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Image(
                    painter = painterResource(id = R.drawable.ic_add_tag),
                    contentDescription = "Button Logo"
                )
                Text(
                    text = title,
                    style = MaterialTheme.typography.body2.copy(lineHeight = 20.sp),
                    fontWeight = FontWeight.SemiBold,
                )
            }
        }
    }
}
caffmaniac
  • 72
  • 7

2 Answers2

1

If you wish to adjust size of the button based on Text which is composed initially you can use Modifier.onSizeChanged for Button dimensions while Text is displayed and then set these dimensions as size to button while Text is out of Composition such as

Preview
@Composable
private fun Test() {

    var isLoading by remember { mutableStateOf(false) }
    var buttonSize by remember { mutableStateOf(DpSize.Zero) }
    val density = LocalDensity.current

    OutlinedButton(
        modifier = Modifier
            .then(
                if (buttonSize != DpSize.Zero) Modifier.size(buttonSize) else Modifier
            )
            .onSizeChanged { newSize ->
                if (buttonSize == DpSize.Zero) {
                    buttonSize = with(density) {
                        newSize
                            .toSize()
                            .toDpSize()
                    }
                }
            },
        onClick = { isLoading = isLoading.not() }) {
        if (isLoading) {
            CircularProgressIndicator(
                modifier = Modifier
                    .fillMaxHeight()
                    .aspectRatio(1f)
            )
        } else {
            Text(text = "Click me")
        }
    }
}

Also if you wish to adjust size of a Button to a Composable that is not composed yet, for instance loader size, you can use SubcomposeLayout to get dimensions beforehand as in this answer

How to adjust size of component to it's child and remain unchanged when it's child size will change? (Jetpack Compose)

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Hey @thracian The solution worked for me but facing a weird issue, the button is getting a set height which is more than what is the total of text height & vertical padding. How to make button follow the wrap content. – caffmaniac Jun 14 '23 at 02:22
  • 1
    You need to change PaddingValues of Button with `contentPadding: PaddingValues = ButtonDefaults.ContentPadding`. The value you get onSizeChanged is the total height and width of the Button. As you can see from the param it comes with default padding values – Thracian Jun 14 '23 at 13:23
0

You can use below Custom-Composable for achieving your requirements:

@Composable
fun ButtonWithLoader(
    buttonText: String,
    backgroundColor: Color,
    contentColor: Color = White,
    showLoader: Boolean = false,
    showBorder: Boolean,
    onClick: () -> Unit,
) {
    if (showLoader) {
        Row(
            modifier = Modifier
                .widthIn(max = 600.dp)
                .fillMaxWidth()
                .padding(start = 20.dp, end = 20.dp, top = 20.dp)
                .height(48.dp)
                .motionClickEvent { onClick() }
                .border(
                    if (showBorder) 1.dp else 0.dp,
                    colors.textPrimary.copy(0.08f),
                    RoundedCornerShape(50.dp)
                )
                .background(backgroundColor, RoundedCornerShape(50.dp)),
            horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically
        ) {
            CircularProgressIndicator(color = colors.primary, modifier = Modifier.size(28.dp))
        }
    } else {
        Row(
            modifier = Modifier
                .widthIn(max = 600.dp)
                .fillMaxWidth()
                .padding(start = 20.dp, end = 20.dp, top = 20.dp)
                .height(48.dp)
                .motionClickEvent { onClick() } // motionClickEvent is my custom click modifier, use clickable modifier over here
                .border(
                    if (showBorder) 1.dp else 0.dp,
                    colors.textPrimary.copy(0.08f),
                    RoundedCornerShape(50.dp)
                )
                .background(backgroundColor, RoundedCornerShape(50.dp)),
            horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = buttonText,
                style = AppTheme.typography.buttonStyle,
                color = contentColor,
                modifier = Modifier.padding(horizontal = 12.dp)
            )
        }
    }
}

Usage:

var isGoogleLoginInProgress by remember { mutableStateOf(false) }
ButtonWithLoader(
   buttonText = stringResource(R.string.sign_in_method_sign_in_with_google_btn_text),
   backgroundColor = White,
   contentColor = textColor.copy(0.87f),
   showBorder = true,
   showLoader = isGoogleLoginInProgress
) {
     isGoogleLoginInProgress = !isGoogleLoginInProgress // Replace this with your logic
}

Sample output:

enter image description here

Megh
  • 831
  • 2
  • 12
  • If you don't want to use `Row{}` and `Text()` combination to show `Sign In with Google` text then you can add Button over there. It's upto you – Megh Jun 13 '23 at 12:11