Created
May 1, 2025 14:56
-
-
Save nikmax42/d58bd3853da446031f6360ce8aa407ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package attracktorui.core.components | |
import androidx.compose.animation.animateContentSize | |
import androidx.compose.animation.core.FiniteAnimationSpec | |
import androidx.compose.animation.core.Spring | |
import androidx.compose.animation.core.VisibilityThreshold | |
import androidx.compose.animation.core.spring | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.material3.LocalTextStyle | |
import androidx.compose.material3.Surface | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableIntStateOf | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.text.SpanStyle | |
import androidx.compose.ui.text.TextStyle | |
import androidx.compose.ui.text.buildAnnotatedString | |
import androidx.compose.ui.text.font.FontStyle | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.text.style.TextAlign | |
import androidx.compose.ui.text.withStyle | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.IntSize | |
import androidx.compose.ui.unit.TextUnit | |
/* | |
* Credits: | |
* https://medium.com/@munbonecci/how-to-implement-expandable-text-in-jetpack-compose-ca9ba35b645c | |
* */ | |
const val DEFAULT_MAX_LINES = 5 | |
/** | |
* An expandable text component that provides access to truncated text with a dynamic ... Show More/ Show Less button. | |
* | |
* @param modifier Modifier for the Box containing the text. | |
* @param modifier Modifier for the Text composable. | |
* @param style The TextStyle to apply to the text. | |
* @param fontStyle The FontStyle to apply to the text. | |
* @param text The text to be displayed. | |
* @param collapsedMaxLines The maximum number of lines to display when collapsed. | |
* @param showMoreText The text to display for "... Show More" button. | |
* @param showMoreStyle The SpanStyle for "... Show More" button. | |
* @param showLessText The text to display for "Show Less" button. | |
* @param showLessStyle The SpanStyle for "Show Less" button. | |
* @param textAlign The alignment of the text. | |
* @param fontSize The font size of the text. | |
*/ | |
@Composable | |
fun ExpandableText( | |
text: String, | |
showMoreText: String, | |
showLessText: String, | |
modifier: Modifier = Modifier, | |
collapsedMaxLines: Int = DEFAULT_MAX_LINES, | |
showMoreStyle: SpanStyle = SpanStyle(fontWeight = FontWeight.W500), | |
showLessStyle: SpanStyle = showMoreStyle, | |
style: TextStyle = LocalTextStyle.current, | |
fontStyle: FontStyle? = null, | |
fontSize: TextUnit = TextUnit.Unspecified, | |
textAlign: TextAlign? = null, | |
animationSpec: FiniteAnimationSpec<IntSize> = spring( | |
stiffness = Spring.StiffnessMediumLow, | |
visibilityThreshold = IntSize.VisibilityThreshold | |
) | |
) { | |
// State variables to track the expanded state, clickable state, and last character index. | |
var isExpanded by remember { mutableStateOf(false) } | |
var isClickable by remember { mutableStateOf(false) } | |
var lastCharIndex by remember { mutableIntStateOf(0) } | |
// Box composable containing the Text composable. | |
Box( | |
modifier = Modifier | |
.clickable( | |
enabled = isClickable, | |
indication = null, | |
interactionSource = null | |
) { | |
isExpanded = !isExpanded | |
} | |
) { | |
// Text composable with buildAnnotatedString to handle "Show More" and "Show Less" buttons. | |
Text( | |
modifier = modifier | |
.fillMaxWidth() | |
.animateContentSize(animationSpec), | |
text = buildAnnotatedString { | |
if (isClickable) { | |
if (isExpanded) { | |
// Display the full text and "Show Less" button when expanded. | |
append(text) | |
withStyle(style = showLessStyle) { append(showLessText) } | |
} | |
else { | |
// Display truncated text and "Show More" button when collapsed. | |
val adjustText = text.substring(startIndex = 0, endIndex = lastCharIndex) | |
.dropLast(showMoreText.length) | |
.dropLastWhile { Character.isWhitespace(it) || it == '.' } | |
append(adjustText) | |
withStyle(style = showMoreStyle) { append(showMoreText) } | |
} | |
} | |
else { | |
// Display the full text when not clickable. | |
append(text) | |
} | |
}, | |
// Set max lines based on the expanded state. | |
maxLines = if (isExpanded) Int.MAX_VALUE else collapsedMaxLines, | |
fontStyle = fontStyle, | |
// Callback to determine visual overflow and enable click ability. | |
onTextLayout = { textLayoutResult -> | |
if (!isExpanded && textLayoutResult.hasVisualOverflow) { | |
isClickable = true | |
lastCharIndex = textLayoutResult.getLineEnd(collapsedMaxLines - 1) | |
} | |
}, | |
style = style, | |
textAlign = textAlign, | |
fontSize = fontSize | |
) | |
} | |
} | |
@Preview | |
@Composable | |
private fun Preview() { | |
val text = remember { | |
"Text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text " + | |
"text text text text text text text text text text text text" | |
} | |
Surface(Modifier.fillMaxSize()) { | |
ExpandableText( | |
text = text, | |
showMoreText = "... Show More", | |
showLessText = " Show less" | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment