package com.suno.android.ui.bottom_sheets.create import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize import androidx.compose.ui.util.lerp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.suno.android.common_ui.R import com.suno.android.common_ui.components.buttons.AuraButton import com.suno.android.common_ui.components.buttons.RoundedButton import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.ui.screens.create.text.CreateTextEvent import com.suno.android.ui.screens.create.text.CreateTextScreenVM import kotlin.math.roundToInt import com.suno.android.common_res.R as CommonResR @Composable fun CreateSheetBottomContent( createTextScreenVM: CreateTextScreenVM, expandedPercent: Float, onClearClick: () -> Unit, onExpandClick: () -> Unit, onCreateClicked: () -> Unit, modifier: Modifier = Modifier, isForMeasure: Boolean, ) { val viewState by createTextScreenVM.state.collectAsStateWithLifecycle() // clear visibility is a value 0f to 1f where 0 is invisible and 1 is visible val clearVisibility by animateFloatAsState( targetValue = if (viewState.isFormPristine) { 0f } else { 1f }, ) Layout( contents = listOf( { ClearAndEditPromptButton( expandedPercent = expandedPercent, onClearClick = onClearClick, onEditClick = onExpandClick, modifier = Modifier.fillMaxHeight(), isForMeasure = isForMeasure, ) }, { CreateButton( expandedPercent = expandedPercent, enabled = viewState.isCreateButtonEnabled, onClick = { createTextScreenVM.sendEvent(CreateTextEvent.OnGenerateClicked) onCreateClicked.invoke() }, modifier = Modifier.fillMaxHeight(), ) }, ), modifier = modifier.fillMaxWidth().height(IntrinsicSize.Min), ) { (clearAndEdit, create), constraints -> val padding = 16.dp.toPx() val space = 8.dp.toPx() val childConstraints = constraints.copy(minWidth = 0, minHeight = 0) val width = constraints.maxWidth // clear offset is a value -1f to 0f, where -1 is off screen and 0 is on screen val clearOffset = clearVisibility - 1 val clearAndEditMaxWidth = constraints.maxWidth.takeIf { constraints.hasBoundedWidth }?.let { maxWidth -> (maxWidth - space) / 2 - padding }?.roundToInt() ?: Int.MAX_VALUE // clear goes off screen if it is hidden val clearAndEditDesiredWidth = clearAndEdit.first().maxIntrinsicWidth(constraints.maxHeight) val clearAndEditWidth = minOf( clearAndEditMaxWidth, clearAndEditDesiredWidth, ) // calculate where to place the clear and edit buttons final positions val clearX = clearOffset * clearAndEditWidth + clearVisibility * padding val editX = padding // get the combined clear/edit button position by lerping by the animation percent val clearAndEditX = lerp(editX, clearX, expandedPercent) // visibility for the space between edit and create. // always visible when collapsed, same as clearVisibility when expanded val spaceVisibility = lerp(1f, clearVisibility, expandedPercent) // lpad only needs to be applied to create button when space is not present (because the edit button is hidden) // otherwise, the clear/edit button is already padded so we don't need to val lPadVisibility = 1 - spaceVisibility // calculate the create buttons position val createX = clearAndEditX + clearAndEditWidth + (space * spaceVisibility) + (padding * lPadVisibility) // calculate the create buttons width (remaining space minus right padding) val createWidth = width - createX - padding // measure children with finalized widths val createPlaceable = create.first().measure( childConstraints.copy( minWidth = createWidth.roundToInt(), maxWidth = createWidth.roundToInt(), ), ) val clearAndEditPlaceable = clearAndEdit.first().measure( childConstraints.copy( minWidth = clearAndEditWidth, maxWidth = clearAndEditWidth, ), ) val height = maxOf(clearAndEditPlaceable.height, createPlaceable.height) layout(width, height) { clearAndEditPlaceable.place(clearAndEditX.roundToInt(), 0) createPlaceable.place(createX.roundToInt(), 0) } } } @Composable private fun ClearAndEditPromptButton( expandedPercent: Float, onClearClick: () -> Unit, onEditClick: () -> Unit, modifier: Modifier = Modifier, isForMeasure: Boolean = false, ) { val collapsedPercent = 1 - expandedPercent RoundedButton( onClick = { if (expandedPercent >= .5f) { onClearClick() } else { onEditClick() } }, modifier = modifier, ) { Layout( contents = listOf( { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier .alpha(collapsedPercent) .holdSize { collapsedPercent == 1f }, ) { Icon( painter = painterResource(R.drawable.pencil), contentDescription = null, modifier = Modifier.size(24.dp), ) Text( text = stringResource(CommonResR.string.edit), textAlign = TextAlign.Center, style = ExtendedTheme.typography.largeSubtitle, overflow = TextOverflow.Visible, modifier = Modifier, ) } }, { Icon( painter = painterResource(R.drawable.trash), contentDescription = stringResource(CommonResR.string.clear), modifier = Modifier.size(24.dp).alpha(expandedPercent), ) }, ), ) { (collapsedContent, expandedContent), constraints -> // custom layout to lerp between the widths of the children based on animation progress val collapsedWidth = collapsedContent.firstOrNull()?.maxIntrinsicWidth(constraints.maxHeight) val expandedWidth = expandedContent.firstOrNull()?.maxIntrinsicWidth(constraints.maxHeight) val width = lerp(collapsedWidth ?: 0, expandedWidth ?: 0, expandedPercent) val childConstraints = constraints.copy( minWidth = 0, minHeight = 0, ) val collapsedPlaceable = collapsedContent.firstOrNull()?.measure(childConstraints) val expandedPlaceable = expandedContent.firstOrNull()?.measure(childConstraints) // to val height = maxOfNotNull( collapsedPlaceable?.height.takeUnless { isForMeasure && expandedPercent == 1f }, expandedPlaceable?.height.takeUnless { isForMeasure && expandedPercent == 0f }, ) ?: 0 layout(width, height) { collapsedPlaceable?.place(0, (height - collapsedPlaceable.height) / 2) expandedPlaceable?.place(0, (height - expandedPlaceable.height) / 2, expandedPercent) } } } } private inline fun > maxOfNotNull( first: T?, second: T?, ): T? = when { first == null -> second second == null -> first else -> maxOf(first, second) } @Composable private fun CreateButton( expandedPercent: Float, enabled: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { val collapsedPercent = 1 - expandedPercent AuraButton( disabled = !enabled, onClick = onClick, modifier = modifier, ) { Box( contentAlignment = Alignment.Center, ) { if (expandedPercent > 0f) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier .alpha(expandedPercent) .holdSize { expandedPercent == 1f }, ) { Icon( imageVector = ImageVector.vectorResource( id = R.drawable.create, ), contentDescription = null, ) Text( text = stringResource(CommonResR.string.create), style = ExtendedTheme.typography.largeSubtitle, textAlign = TextAlign.Center, modifier = Modifier, ) } } if (expandedPercent < 1f) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier .alpha(collapsedPercent) .holdSize { collapsedPercent == 1f }, ) { Icon( imageVector = ImageVector.vectorResource( id = R.drawable.create, ), contentDescription = null, ) Text( text = stringResource(CommonResR.string.generate_more), style = ExtendedTheme.typography.largeSubtitle, textAlign = TextAlign.Center, ) } } } } } @Composable fun Modifier.holdSize( condition: () -> Boolean = { true }, ): Modifier { val density = LocalDensity.current var rememberedWidth by remember { mutableStateOf(null) } return this.run { rememberedWidth?.let { requiredSize(it) } ?: this }.onSizeChanged { if (condition()) { rememberedWidth = with(density) { it.toSize().toDpSize() } } } }