From 1c8d33713f259b55a21cc27a243aefe6aca09f67 Mon Sep 17 00:00:00 2001 From: DatLag Date: Wed, 29 Nov 2023 14:40:52 +0100 Subject: [PATCH] fix read more description --- .../shared/ui/custom/ExpandableText.kt | 322 +++++++++++++ .../ui/custom/readmore/BasicReadMoreText.kt | 430 ------------------ .../shared/ui/custom/readmore/ReadMoreText.kt | 392 ---------------- .../custom/readmore/ReadMoreTextOverflow.kt | 30 -- .../shared/ui/custom/readmore/ToggleArea.kt | 30 -- .../series/component/DescriptionText.kt | 54 ++- 6 files changed, 356 insertions(+), 902 deletions(-) create mode 100644 app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt delete mode 100644 app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/BasicReadMoreText.kt delete mode 100644 app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreText.kt delete mode 100644 app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreTextOverflow.kt delete mode 100644 app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ToggleArea.kt diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt new file mode 100644 index 00000000..d1a1c7b5 --- /dev/null +++ b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/ExpandableText.kt @@ -0,0 +1,322 @@ +package dev.datlag.burningseries.shared.ui.custom + +import androidx.compose.foundation.text.InlineTextContent +import androidx.compose.foundation.text.appendInlineContent +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.material3.LocalContentColor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.Placeholder +import androidx.compose.ui.text.PlaceholderVerticalAlign +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.ResolvedTextDirection +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +import kotlin.math.max + + +/** + * @author dokar3 + * https://github.com/dokar3/ExpandableText + */ + +private const val INLINE_CONTENT_ID = "EXPANDABLE_TEXT_TOGGLE" + +private data class ToggleSize( + val width: Int = 0, + val widthSp: TextUnit = 0.sp, + val height: Int = 0, + val heightSp: TextUnit = 0.sp +) + +private data class ExpandableTextInfo( + val visibleCharCount: Int, + val shouldShowToggleContent: Boolean, +) + +/** + * Display an expandable text, require `maxLines` to make text expandable. + * + * @param expanded Controls the expanded state of text. + * @param text Text to display. + * @param collapsedMaxLines The max lines when [expanded] is false. + * @param expandedMaxLines The max lines when [expanded] is true. Defaults to [Int.MAX_VALUE]. + * @param toggle The toggle displayed at end of the text if text can not be fully displayed. + * @see [Text] + */ +@Composable +fun ExpandableText( + expanded: Boolean, + text: String, + collapsedMaxLines: Int, + modifier: Modifier = Modifier, + expandedMaxLines: Int = Int.MAX_VALUE, + toggle: @Composable (() -> Unit)? = null, + color: Color = LocalContentColor.current, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + inlineContent: Map = mapOf(), + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current +) { + val annotatedString = remember(text) { AnnotatedString(text) } + ExpandableText( + expanded = expanded, + text = annotatedString, + modifier = modifier, + toggle = toggle, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + collapsedMaxLines = collapsedMaxLines, + expandedMaxLines = expandedMaxLines, + inlineContent = inlineContent, + onTextLayout = onTextLayout, + style = style + ) +} + +/** + * Display an expandable text, require `maxLines` to make text expandable. + * + * @param expanded Controls the expanded state of text. + * @param text Text to display. + * @param collapsedMaxLines The max lines when [expanded] is false. + * @param expandedMaxLines The max lines when [expanded] is true. Defaults to [Int.MAX_VALUE]. + * @param toggle The toggle displayed at end of the text if text can not be fully displayed. + * @see [Text] + */ +@Composable +fun ExpandableText( + expanded: Boolean, + text: AnnotatedString, + collapsedMaxLines: Int, + modifier: Modifier = Modifier, + expandedMaxLines: Int = Int.MAX_VALUE, + toggle: @Composable (() -> Unit)? = null, + color: Color = LocalContentColor.current, + fontSize: TextUnit = TextUnit.Unspecified, + fontStyle: FontStyle? = null, + fontWeight: FontWeight? = null, + fontFamily: FontFamily? = null, + letterSpacing: TextUnit = TextUnit.Unspecified, + textDecoration: TextDecoration? = null, + textAlign: TextAlign? = null, + lineHeight: TextUnit = TextUnit.Unspecified, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + inlineContent: Map = mapOf(), + onTextLayout: (TextLayoutResult) -> Unit = {}, + style: TextStyle = LocalTextStyle.current +) { + var textInfo by remember(text) { + mutableStateOf( + ExpandableTextInfo( + visibleCharCount = text.length, + shouldShowToggleContent = false, + ) + ) + } + + val expandableText = remember(text, toggle as Any?, textInfo) { + if (textInfo.shouldShowToggleContent && toggle != null) { + buildAnnotatedString { + append(text.subSequence(0, textInfo.visibleCharCount)) + appendInlineContent(INLINE_CONTENT_ID) + } + } else { + text + } + } + + val layoutResult = remember { mutableStateOf(null) } + + val toggleSize = measureToggle(toggle) + + val expandableInlineContent = remember( + inlineContent, + toggle as Any?, + textInfo, + toggleSize, + ) { + if (textInfo.shouldShowToggleContent && toggle != null) { + val content = InlineTextContent( + placeholder = Placeholder( + width = toggleSize.widthSp, + height = toggleSize.heightSp, + placeholderVerticalAlign = PlaceholderVerticalAlign.Center, + ), + children = { toggle() } + ) + inlineContent + Pair(INLINE_CONTENT_ID, content) + } else { + inlineContent + } + } + + fun tryUpdateTextInfo( + toggleSize: ToggleSize, + layoutRet: TextLayoutResult, + ) { + if (toggleSize.width == 0) return + val actualMaxLines = if (expanded) expandedMaxLines else collapsedMaxLines + if (layoutRet.lineCount == actualMaxLines) { + val lineEnd = layoutRet.getLineEnd(layoutRet.lineCount - 1) + if (lineEnd == expandableText.length) { + // Text is fully displayed + val visibleChars = if (textInfo.shouldShowToggleContent) { + expandableText.length - 1 + } else { + expandableText.length + } + textInfo = textInfo.copy(visibleCharCount = visibleChars) + return + } + val lineTop = layoutRet.getLineTop(layoutRet.lineCount - 1) + val isLtr = try { + layoutRet.getParagraphDirection(lineEnd) == ResolvedTextDirection.Ltr + } catch (e: ArrayIndexOutOfBoundsException) { + // Error occurred in MultiParagraph.getParagraphDirection() + true + } + val visibleChars = if (isLtr) { + val toggleTopLeft = Offset( + x = layoutRet.size.width - toggleSize.width.toFloat(), + y = lineTop + toggleSize.height / 2f, + ) + var count = layoutRet.getOffsetForPosition(toggleTopLeft) + while (count > 0) { + val charRight = layoutRet.getBoundingBox(offset = count - 1).right + val isOverlapped = charRight >= toggleTopLeft.x + val isWhitespace = text[count - 1].isWhitespace() + if (isOverlapped || isWhitespace) { + count-- + } else { + break + } + } + count + } else { + val toggleTopRight = Offset( + x = toggleSize.width.toFloat(), + y = lineTop + toggleSize.height / 2f, + ) + var count = layoutRet.getOffsetForPosition(toggleTopRight) + while (count > 0) { + val charLeft = layoutRet.getBoundingBox(offset = count - 1).left + val isOverlapped = charLeft <= toggleTopRight.x + val isWhitespace = text[count - 1].isWhitespace() + if (isOverlapped || isWhitespace) { + count-- + } else { + break + } + } + count + } + textInfo = textInfo.copy( + visibleCharCount = visibleChars, + shouldShowToggleContent = true, + ) + } else { + textInfo = textInfo.copy(visibleCharCount = text.length) + } + } + + LaunchedEffect( + expanded, + collapsedMaxLines, + expandedMaxLines, + toggleSize, + layoutResult.value, + ) { + val layoutRet = layoutResult.value ?: return@LaunchedEffect + if (toggleSize.width > 0) { + tryUpdateTextInfo(toggleSize, layoutRet) + } + } + + Text( + text = expandableText, + modifier = modifier, + color = color, + fontSize = fontSize, + fontStyle = fontStyle, + fontWeight = fontWeight, + fontFamily = fontFamily, + letterSpacing = letterSpacing, + textDecoration = textDecoration, + textAlign = textAlign, + lineHeight = lineHeight, + overflow = overflow, + softWrap = softWrap, + maxLines = if (expanded) expandedMaxLines else collapsedMaxLines, + inlineContent = expandableInlineContent, + onTextLayout = { + onTextLayout(it) + layoutResult.value = it + }, + style = style + ) +} + +@Composable +private fun measureToggle( + content: @Composable (() -> Unit)?, +): ToggleSize { + var size by remember(content as Any?) { mutableStateOf(ToggleSize()) } + if (content != null) { + Layout(content = content) { measurables, constraints -> + var maxWidth = 0 + var maxHeight = 0 + measurables.map { + it.measure(constraints) + }.forEach { + maxWidth = max(maxWidth, it.measuredWidth) + maxHeight = max(maxHeight, it.measuredHeight) + } + size = ToggleSize( + width = maxWidth, + widthSp = maxWidth.toSp(), + height = maxHeight, + heightSp = maxHeight.toSp(), + ) + layout(0, 0) {} + } + } + return size +} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/BasicReadMoreText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/BasicReadMoreText.kt deleted file mode 100644 index ea2874b1..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/BasicReadMoreText.kt +++ /dev/null @@ -1,430 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.readmore - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.text.BasicText -import androidx.compose.foundation.text.ClickableText -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.DrawModifier -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.drawscope.ContentDrawScope -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextLayoutResult -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.dp -import dev.datlag.burningseries.shared.common.onClick - -/** - * Basic element that displays text with read more. - * Typically you will instead want to use [com.webtoonscorp.android.readmore.material.ReadMoreText], - * which is a higher level Text element that contains semantics and consumes style information from - * a theme. - * - * @param text The text to be displayed. - * @param expanded whether this text is expanded or collapsed. - * @param modifier [Modifier] to apply to this layout node. - * @param onExpandedChange called when this text is clicked. If `null`, then this text will not be - * interactable, unless something else handles its input events and updates its state. - * @param contentPadding a padding around the text. - * @param style Style configuration for the text such as color, font, line height etc. - * @param onTextLayout Callback that is executed when a new text layout is calculated. A - * [TextLayoutResult] object that callback provides contains paragraph information, size of the - * text, baselines and other details. The callback can be used to add additional decoration or - * functionality to the text. For example, to draw selection around the text. - * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the - * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false, - * [readMoreOverflow] and TextAlign may have unexpected effects. - * @param readMoreText The read more text to be displayed in the collapsed state. - * @param readMoreMaxLines An optional maximum number of lines for the text to span, wrapping if - * necessary. If the text exceeds the given number of lines, it will be truncated according to - * [readMoreOverflow]. If it is not null, then it must be greater than zero. - * @param readMoreOverflow How visual overflow should be handled in the collapsed state. - * @param readMoreStyle Style configuration for the read more text such as color, font, line height - * etc. - * @param readLessText The read less text to be displayed in the expanded state. - * @param readLessStyle Style configuration for the read less text such as color, font, line height - * etc. - * @param toggleArea A clickable area of text to toggle. - */ -@Composable -public fun BasicReadMoreText( - text: String, - expanded: Boolean, - modifier: Modifier = Modifier, - onExpandedChange: ((Boolean) -> Unit)? = null, - contentPadding: PaddingValues = PaddingValues(0.dp), - style: TextStyle = TextStyle.Default, - onTextLayout: (TextLayoutResult) -> Unit = {}, - softWrap: Boolean = true, - readMoreText: String = "", - readMoreMaxLines: Int = 2, - readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis, - readMoreStyle: SpanStyle = style.toSpanStyle(), - readLessText: String = "", - readLessStyle: SpanStyle = readMoreStyle, - toggleArea: ToggleArea = ToggleArea.All, -) { - CoreReadMoreText( - text = AnnotatedString(text), - expanded = expanded, - modifier = modifier, - onExpandedChange = onExpandedChange, - contentPadding = contentPadding, - style = style, - onTextLayout = onTextLayout, - softWrap = softWrap, - readMoreText = readMoreText, - readMoreMaxLines = readMoreMaxLines, - readMoreOverflow = readMoreOverflow, - readMoreStyle = readMoreStyle, - readLessText = readLessText, - readLessStyle = readLessStyle, - toggleArea = toggleArea, - ) -} - -/** - * Basic element that displays text with read more. - * Typically you will instead want to use [com.webtoonscorp.android.readmore.material.ReadMoreText], - * which is a higher level Text element that contains semantics and consumes style information from - * a theme. - * - * @param text The text to be displayed. - * @param expanded whether this text is expanded or collapsed. - * @param modifier [Modifier] to apply to this layout node. - * @param onExpandedChange called when this text is clicked. If `null`, then this text will not be - * interactable, unless something else handles its input events and updates its state. - * @param contentPadding a padding around the text. - * @param style Style configuration for the text such as color, font, line height etc. - * @param onTextLayout Callback that is executed when a new text layout is calculated. A - * [TextLayoutResult] object that callback provides contains paragraph information, size of the - * text, baselines and other details. The callback can be used to add additional decoration or - * functionality to the text. For example, to draw selection around the text. - * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the - * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false, - * [readMoreOverflow] and TextAlign may have unexpected effects. - * @param readMoreText The read more text to be displayed in the collapsed state. - * @param readMoreMaxLines An optional maximum number of lines for the text to span, wrapping if - * necessary. If the text exceeds the given number of lines, it will be truncated according to - * [readMoreOverflow]. If it is not null, then it must be greater than zero. - * @param readMoreOverflow How visual overflow should be handled in the collapsed state. - * @param readMoreStyle Style configuration for the read more text such as color, font, line height - * etc. - * @param readLessText The read less text to be displayed in the expanded state. - * @param readLessStyle Style configuration for the read less text such as color, font, line height - * etc. - * @param toggleArea A clickable area of text to toggle. - */ -@Composable -public fun BasicReadMoreText( - text: AnnotatedString, - expanded: Boolean, - modifier: Modifier = Modifier, - onExpandedChange: ((Boolean) -> Unit)? = null, - contentPadding: PaddingValues = PaddingValues(0.dp), - style: TextStyle = TextStyle.Default, - onTextLayout: (TextLayoutResult) -> Unit = {}, - softWrap: Boolean = true, - readMoreText: String = "", - readMoreMaxLines: Int = 2, - readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis, - readMoreStyle: SpanStyle = style.toSpanStyle(), - readLessText: String = "", - readLessStyle: SpanStyle = readMoreStyle, - toggleArea: ToggleArea = ToggleArea.All, -) { - CoreReadMoreText( - text = text, - expanded = expanded, - modifier = modifier, - onExpandedChange = onExpandedChange, - contentPadding = contentPadding, - style = style, - onTextLayout = onTextLayout, - softWrap = softWrap, - readMoreText = readMoreText, - readMoreMaxLines = readMoreMaxLines, - readMoreOverflow = readMoreOverflow, - readMoreStyle = readMoreStyle, - readLessText = readLessText, - readLessStyle = readLessStyle, - toggleArea = toggleArea, - ) -} - -// //////////////////////////////////// -// CoreReadMoreText -// //////////////////////////////////// - -private const val ReadMoreTag = "read_more" -private const val ReadLessTag = "read_less" - -@Composable -private fun CoreReadMoreText( - text: AnnotatedString, - expanded: Boolean, - modifier: Modifier = Modifier, - onExpandedChange: ((Boolean) -> Unit)? = null, - contentPadding: PaddingValues = PaddingValues(0.dp), - style: TextStyle = TextStyle.Default, - onTextLayout: (TextLayoutResult) -> Unit = {}, - softWrap: Boolean = true, - readMoreText: String = "", - readMoreMaxLines: Int = 2, - readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis, - readMoreStyle: SpanStyle = style.toSpanStyle(), - readLessText: String = "", - readLessStyle: SpanStyle = readMoreStyle, - toggleArea: ToggleArea = ToggleArea.All, -) { - require(readMoreMaxLines > 0) { "readMoreMaxLines should be greater than 0" } - - val overflowText: String = remember(readMoreOverflow) { - buildString { - when (readMoreOverflow) { - ReadMoreTextOverflow.Clip -> { - } - ReadMoreTextOverflow.Ellipsis -> { - append(Typography.ellipsis) - } - } - if (readMoreText.isNotEmpty()) { - append(Typography.nbsp) - } - } - } - val readMoreTextWithStyle: AnnotatedString = remember(readMoreText, readMoreStyle) { - buildAnnotatedString { - if (readMoreText.isNotEmpty()) { - withStyle(readMoreStyle) { - append(readMoreText.replace(' ', Typography.nbsp)) - } - } - } - } - val readLessTextWithStyle: AnnotatedString = remember(readLessText, readLessStyle) { - buildAnnotatedString { - if (readLessText.isNotEmpty()) { - withStyle(readLessStyle) { - append(readLessText) - } - } - } - } - - val state = remember(text, readMoreMaxLines) { - ReadMoreState( - originalText = text, - readMoreMaxLines = readMoreMaxLines - ) - } - val currentText = buildAnnotatedString { - if (expanded) { - append(text) - if (readLessTextWithStyle.isNotEmpty()) { - append(' ') - pushStringAnnotation(tag = ReadLessTag, annotation = "") - append(readLessTextWithStyle) - pop() - } - } else { - val collapsedText = state.collapsedText - if (collapsedText.isNotEmpty()) { - append(collapsedText) - append(overflowText) - - pushStringAnnotation(tag = ReadMoreTag, annotation = "") - append(readMoreTextWithStyle) - pop() - } else { - append(text) - } - } - } - val toggleableModifier = if (onExpandedChange != null && toggleArea == ToggleArea.All) { - Modifier.onClick( - enabled = state.isCollapsible, - onClick = { onExpandedChange(!expanded) }, - ) - } else { - Modifier - } - Box( - modifier = modifier - .then(toggleableModifier) - .padding(contentPadding) - ) { - if (toggleArea == ToggleArea.More) { - ClickableText( - text = currentText, - modifier = Modifier, - style = style, - onTextLayout = { - state.onTextLayout(it) - onTextLayout(it) - }, - overflow = TextOverflow.Ellipsis, - softWrap = softWrap, - maxLines = if (expanded) Int.MAX_VALUE else readMoreMaxLines, - onClick = { offset -> - currentText.getStringAnnotations( - tag = ReadMoreTag, - start = offset, - end = offset - ).firstOrNull()?.let { - onExpandedChange?.invoke(true) - } - currentText.getStringAnnotations( - tag = ReadLessTag, - start = offset, - end = offset - ).firstOrNull()?.let { - onExpandedChange?.invoke(false) - } - }, - ) - } else { - BasicText( - text = currentText, - modifier = Modifier, - style = style, - onTextLayout = { - state.onTextLayout(it) - onTextLayout(it) - }, - overflow = TextOverflow.Ellipsis, - softWrap = softWrap, - maxLines = if (expanded) Int.MAX_VALUE else readMoreMaxLines, - ) - } - if (expanded.not()) { - BasicText( - text = overflowText, - onTextLayout = { state.onOverflowTextLayout(it) }, - modifier = Modifier.notDraw(), - style = style - ) - BasicText( - text = readMoreTextWithStyle, - onTextLayout = { state.onReadMoreTextLayout(it) }, - modifier = Modifier.notDraw(), - style = style.merge(readMoreStyle) - ) - } - } -} - -private fun Modifier.notDraw(): Modifier { - return then(NotDrawModifier) -} - -private object NotDrawModifier : DrawModifier { - - override fun ContentDrawScope.draw() { - // not draws content. - } -} - -// //////////////////////////////////// -// ReadMoreState -// //////////////////////////////////// - -private const val DebugLog = false -private const val Tag = "ReadMoreState" - -@Stable -private class ReadMoreState( - private val originalText: AnnotatedString, - private val readMoreMaxLines: Int -) { - private var textLayout: TextLayoutResult? = null - private var overflowTextLayout: TextLayoutResult? = null - private var readMoreTextLayout: TextLayoutResult? = null - - private var _collapsedText: AnnotatedString by mutableStateOf(AnnotatedString("")) - - var collapsedText: AnnotatedString - get() = _collapsedText - internal set(value) { - if (value != _collapsedText) { - _collapsedText = value - } - } - - val isCollapsible: Boolean - get() = collapsedText.isNotEmpty() - - fun onTextLayout(result: TextLayoutResult) { - val lastLineIndex = readMoreMaxLines - 1 - val previous = textLayout - val old = previous != null && - previous.lineCount >= readMoreMaxLines && - previous.isLineEllipsized(lastLineIndex) - val new = result.lineCount >= readMoreMaxLines && - result.isLineEllipsized(lastLineIndex) - val changed = previous != result && old != new - if (changed) { - textLayout = result - updateCollapsedText() - } - } - - fun onOverflowTextLayout(result: TextLayoutResult) { - val changed = overflowTextLayout?.size?.width != result.size.width - if (changed) { - overflowTextLayout = result - updateCollapsedText() - } - } - - fun onReadMoreTextLayout(result: TextLayoutResult) { - val changed = readMoreTextLayout?.size?.width != result.size.width - if (changed) { - readMoreTextLayout = result - updateCollapsedText() - } - } - - private fun updateCollapsedText() { - val lastLineIndex = readMoreMaxLines - 1 - val textLayout = textLayout - val overflowTextLayout = overflowTextLayout - val readMoreTextLayout = readMoreTextLayout - if (textLayout != null && - overflowTextLayout != null && - readMoreTextLayout != null && - textLayout.lineCount >= readMoreMaxLines && - textLayout.isLineEllipsized(lastLineIndex) - ) { - val countUntilMaxLine = textLayout.getLineEnd(readMoreMaxLines - 1, visibleEnd = true) - val readMoreWidth = overflowTextLayout.size.width + readMoreTextLayout.size.width - val maximumWidth = textLayout.size.width - readMoreWidth - var replacedEndIndex = countUntilMaxLine + 1 - var currentTextBounds: Rect - do { - replacedEndIndex -= 1 - currentTextBounds = textLayout.getCursorRect(replacedEndIndex) - } while (currentTextBounds.left > maximumWidth) - collapsedText = originalText.subSequence(startIndex = 0, endIndex = replacedEndIndex) - } - } - - override fun toString(): String { - return "ReadMoreState(" + - "originalText=$originalText, " + - "readMoreMaxLines=$readMoreMaxLines, " + - "collapsedText=$collapsedText" + - ")" - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreText.kt deleted file mode 100644 index c7c3d52c..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreText.kt +++ /dev/null @@ -1,392 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.readmore - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.Paragraph -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextLayoutResult -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -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.style.TextDecoration -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.dp - -/** - * High level element that displays text with read more. - * - * The default [style] uses the [LocalTextStyle] provided by the [MaterialTheme] / components. If - * you are setting your own style, you may want to consider first retrieving [LocalTextStyle], - * and using [TextStyle.copy] to keep any theme defined attributes, only modifying the specific - * attributes you want to override. - * - * For ease of use, commonly used parameters from [TextStyle] are also present here. The order of - * precedence is as follows: - * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]), - * then this parameter will always be used. - * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value - * from [style] will be used instead. - * - * Additionally, for [color], if [color] is not set, and [style] does not have a color, then - * [LocalContentColor] will be used. - * - * @param text The text to be displayed. - * @param expanded whether this text is expanded or collapsed. - * @param modifier [Modifier] to apply to this layout node. - * @param onExpandedChange called when this text is clicked. If `null`, then this text will not be - * interactable, unless something else handles its input events and updates its state. - * @param contentPadding a padding around the text. - * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set, - * this will be [LocalContentColor]. - * @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize]. - * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic). - * See [TextStyle.fontStyle]. - * @param fontWeight The typeface thickness to use when painting the text (e.g., [FontWeight.Bold]). - * @param fontFamily The font family to be used when rendering the text. See [TextStyle.fontFamily]. - * @param letterSpacing The amount of space to add between each letter. - * See [TextStyle.letterSpacing]. - * @param textDecoration The decorations to paint on the text (e.g., an underline). - * See [TextStyle.textDecoration]. - * @param textAlign The alignment of the text within the lines of the paragraph. - * See [TextStyle.textAlign]. - * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM. - * See [TextStyle.lineHeight]. - * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the - * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false, - * [readMoreOverflow] and TextAlign may have unexpected effects. - * @param onTextLayout Callback that is executed when a new text layout is calculated. A - * [TextLayoutResult] object that callback provides contains paragraph information, size of the - * text, baselines and other details. The callback can be used to add additional decoration or - * functionality to the text. For example, to draw selection around the text. - * @param style Style configuration for the text such as color, font, line height etc. - * @param readMoreText The read more text to be displayed in the collapsed state. - * @param readMoreColor [Color] to apply to the read more text. If [Color.Unspecified], and [style] - * has no color set, this will be [LocalContentColor]. - * @param readMoreFontSize The size of glyphs to use when painting the read more text. - * See [TextStyle.fontSize]. - * @param readMoreFontStyle The typeface variant to use when drawing the read more letters - * (e.g., italic). See [TextStyle.fontStyle]. - * @param readMoreFontWeight The typeface thickness to use when painting the read more text - * (e.g., [FontWeight.Bold]). - * @param readMoreFontFamily The font family to be used when rendering the read more text. - * See [TextStyle.fontFamily]. - * @param readMoreTextDecoration The decorations to paint on the read more text - * (e.g., an underline). See [TextStyle.textDecoration]. - * @param readMoreMaxLines An optional maximum number of lines for the text to span, wrapping if - * necessary. If the text exceeds the given number of lines, it will be truncated according to - * [readMoreOverflow]. If it is not null, then it must be greater than zero. - * @param readMoreOverflow How visual overflow should be handled in the collapsed state. - * @param readMoreStyle Style configuration for the read more text such as color, font, line height - * etc. - * @param readLessText The read less text to be displayed in the expanded state. - * @param readLessColor [Color] to apply to the read less text. If [Color.Unspecified], and [style] - * has no color set, this will be [LocalContentColor]. - * @param readLessFontSize The size of glyphs to use when painting the read less text. - * See [TextStyle.fontSize]. - * @param readLessFontStyle The typeface variant to use when drawing the read less letters - * (e.g., italic). See [TextStyle.fontStyle]. - * @param readLessFontWeight The typeface thickness to use when painting the read less text - * (e.g., [FontWeight.Bold]). - * @param readLessFontFamily The font family to be used when rendering the read less text. - * See [TextStyle.fontFamily]. - * @param readLessTextDecoration The decorations to paint on the read less text - * (e.g., an underline). See [TextStyle.textDecoration]. - * @param readLessStyle Style configuration for the read less text such as color, font, line height - * etc. - * @param toggleArea A clickable area of text to toggle. - */ -@Composable -public fun ReadMoreText( - text: String, - expanded: Boolean, - modifier: Modifier = Modifier, - onExpandedChange: ((Boolean) -> Unit)? = null, - contentPadding: PaddingValues = PaddingValues(0.dp), - color: Color = Color.Unspecified, - fontSize: TextUnit = TextUnit.Unspecified, - fontStyle: FontStyle? = null, - fontWeight: FontWeight? = null, - fontFamily: FontFamily? = null, - letterSpacing: TextUnit = TextUnit.Unspecified, - textDecoration: TextDecoration? = null, - textAlign: TextAlign? = null, - lineHeight: TextUnit = TextUnit.Unspecified, - softWrap: Boolean = true, - onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current, - readMoreText: String = "", - readMoreColor: Color = Color.Unspecified, - readMoreFontSize: TextUnit = TextUnit.Unspecified, - readMoreFontStyle: FontStyle? = null, - readMoreFontWeight: FontWeight? = null, - readMoreFontFamily: FontFamily? = null, - readMoreTextDecoration: TextDecoration? = null, - readMoreMaxLines: Int = 2, - readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis, - readMoreStyle: SpanStyle = style.toSpanStyle(), - readLessText: String = "", - readLessColor: Color = readMoreColor, - readLessFontSize: TextUnit = readMoreFontSize, - readLessFontStyle: FontStyle? = readMoreFontStyle, - readLessFontWeight: FontWeight? = readMoreFontWeight, - readLessFontFamily: FontFamily? = readMoreFontFamily, - readLessTextDecoration: TextDecoration? = readMoreTextDecoration, - readLessStyle: SpanStyle = readMoreStyle, - toggleArea: ToggleArea = ToggleArea.All, -) { - val textColor = color.takeOrElse { - style.color.takeOrElse { - LocalContentColor.current - } - } - // NOTE(text-perf-review): It might be worthwhile writing a bespoke merge implementation that - // will avoid reallocating if all of the options here are the defaults - val mergedStyle = style.merge( - TextStyle( - color = textColor, - fontSize = fontSize, - fontWeight = fontWeight, - textAlign = textAlign, - lineHeight = lineHeight, - fontFamily = fontFamily, - textDecoration = textDecoration, - fontStyle = fontStyle, - letterSpacing = letterSpacing - ) - ) - val mergedReadMoreStyle = mergedStyle.toSpanStyle() - .merge(readMoreStyle) - .merge( - SpanStyle( - color = readMoreColor, - fontSize = readMoreFontSize, - fontWeight = readMoreFontWeight, - fontFamily = readMoreFontFamily, - textDecoration = readMoreTextDecoration, - fontStyle = readMoreFontStyle - ) - ) - val mergedReadLessStyle = mergedStyle.toSpanStyle() - .merge(readLessStyle) - .merge( - SpanStyle( - color = readLessColor, - fontSize = readLessFontSize, - fontWeight = readLessFontWeight, - fontFamily = readLessFontFamily, - textDecoration = readLessTextDecoration, - fontStyle = readLessFontStyle - ) - ) - BasicReadMoreText( - text = text, - expanded = expanded, - modifier = modifier, - onExpandedChange = onExpandedChange, - contentPadding = contentPadding, - style = mergedStyle, - onTextLayout = onTextLayout, - softWrap = softWrap, - readMoreText = readMoreText, - readMoreMaxLines = readMoreMaxLines, - readMoreOverflow = readMoreOverflow, - readMoreStyle = mergedReadMoreStyle, - readLessText = readLessText, - readLessStyle = mergedReadLessStyle, - toggleArea = toggleArea, - ) -} - -/** - * High level element that displays text with read more. - * - * The default [style] uses the [LocalTextStyle] provided by the [MaterialTheme] / components. If - * you are setting your own style, you may want to consider first retrieving [LocalTextStyle], - * and using [TextStyle.copy] to keep any theme defined attributes, only modifying the specific - * attributes you want to override. - * - * For ease of use, commonly used parameters from [TextStyle] are also present here. The order of - * precedence is as follows: - * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]), - * then this parameter will always be used. - * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value - * from [style] will be used instead. - * - * Additionally, for [color], if [color] is not set, and [style] does not have a color, then - * [LocalContentColor] will be used. - * - * @param text The text to be displayed. - * @param expanded whether this text is expanded or collapsed. - * @param modifier [Modifier] to apply to this layout node. - * @param onExpandedChange called when this text is clicked. If `null`, then this text will not be - * interactable, unless something else handles its input events and updates its state. - * @param contentPadding a padding around the text. - * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set, - * this will be [LocalContentColor]. - * @param fontSize The size of glyphs to use when painting the text. See [TextStyle.fontSize]. - * @param fontStyle The typeface variant to use when drawing the letters (e.g., italic). - * See [TextStyle.fontStyle]. - * @param fontWeight The typeface thickness to use when painting the text (e.g., [FontWeight.Bold]). - * @param fontFamily The font family to be used when rendering the text. See [TextStyle.fontFamily]. - * @param letterSpacing The amount of space to add between each letter. - * See [TextStyle.letterSpacing]. - * @param textDecoration The decorations to paint on the text (e.g., an underline). - * See [TextStyle.textDecoration]. - * @param textAlign The alignment of the text within the lines of the paragraph. - * See [TextStyle.textAlign]. - * @param lineHeight Line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM. - * See [TextStyle.lineHeight]. - * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the - * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false, - * [readMoreOverflow] and TextAlign may have unexpected effects. - * @param onTextLayout Callback that is executed when a new text layout is calculated. A - * [TextLayoutResult] object that callback provides contains paragraph information, size of the - * text, baselines and other details. The callback can be used to add additional decoration or - * functionality to the text. For example, to draw selection around the text. - * @param style Style configuration for the text such as color, font, line height etc. - * @param readMoreText The read more text to be displayed in the collapsed state. - * @param readMoreColor [Color] to apply to the read more text. If [Color.Unspecified], and [style] - * has no color set, this will be [LocalContentColor]. - * @param readMoreFontSize The size of glyphs to use when painting the read more text. - * See [TextStyle.fontSize]. - * @param readMoreFontStyle The typeface variant to use when drawing the read more letters - * (e.g., italic). See [TextStyle.fontStyle]. - * @param readMoreFontWeight The typeface thickness to use when painting the read more text - * (e.g., [FontWeight.Bold]). - * @param readMoreFontFamily The font family to be used when rendering the read more text. - * See [TextStyle.fontFamily]. - * @param readMoreTextDecoration The decorations to paint on the read more text - * (e.g., an underline). See [TextStyle.textDecoration]. - * @param readMoreMaxLines An optional maximum number of lines for the text to span, wrapping if - * necessary. If the text exceeds the given number of lines, it will be truncated according to - * [readMoreOverflow]. If it is not null, then it must be greater than zero. - * @param readMoreOverflow How visual overflow should be handled in the collapsed state. - * @param readMoreStyle Style configuration for the read more text such as color, font, line height - * etc. - * @param readLessText The read less text to be displayed in the expanded state. - * @param readLessColor [Color] to apply to the read less text. If [Color.Unspecified], and [style] - * has no color set, this will be [LocalContentColor]. - * @param readLessFontSize The size of glyphs to use when painting the read less text. - * See [TextStyle.fontSize]. - * @param readLessFontStyle The typeface variant to use when drawing the read less letters - * (e.g., italic). See [TextStyle.fontStyle]. - * @param readLessFontWeight The typeface thickness to use when painting the read less text - * (e.g., [FontWeight.Bold]). - * @param readLessFontFamily The font family to be used when rendering the read less text. - * See [TextStyle.fontFamily]. - * @param readLessTextDecoration The decorations to paint on the read less text - * (e.g., an underline). See [TextStyle.textDecoration]. - * @param readLessStyle Style configuration for the read less text such as color, font, line height - * etc. - * @param toggleArea A clickable area of text to toggle. - */ -@Composable -public fun ReadMoreText( - text: AnnotatedString, - expanded: Boolean, - modifier: Modifier = Modifier, - onExpandedChange: ((Boolean) -> Unit)? = null, - contentPadding: PaddingValues = PaddingValues(0.dp), - color: Color = Color.Unspecified, - fontSize: TextUnit = TextUnit.Unspecified, - fontStyle: FontStyle? = null, - fontWeight: FontWeight? = null, - fontFamily: FontFamily? = null, - letterSpacing: TextUnit = TextUnit.Unspecified, - textDecoration: TextDecoration? = null, - textAlign: TextAlign? = null, - lineHeight: TextUnit = TextUnit.Unspecified, - softWrap: Boolean = true, - onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current, - readMoreText: String = "", - readMoreColor: Color = Color.Unspecified, - readMoreFontSize: TextUnit = TextUnit.Unspecified, - readMoreFontStyle: FontStyle? = null, - readMoreFontWeight: FontWeight? = null, - readMoreFontFamily: FontFamily? = null, - readMoreTextDecoration: TextDecoration? = null, - readMoreMaxLines: Int = 2, - readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis, - readMoreStyle: SpanStyle = style.toSpanStyle(), - readLessText: String = "", - readLessColor: Color = readMoreColor, - readLessFontSize: TextUnit = readMoreFontSize, - readLessFontStyle: FontStyle? = readMoreFontStyle, - readLessFontWeight: FontWeight? = readMoreFontWeight, - readLessFontFamily: FontFamily? = readMoreFontFamily, - readLessTextDecoration: TextDecoration? = readMoreTextDecoration, - readLessStyle: SpanStyle = readMoreStyle, - toggleArea: ToggleArea = ToggleArea.All, -) { - val textColor = color.takeOrElse { - style.color.takeOrElse { - LocalContentColor.current - } - } - // NOTE(text-perf-review): It might be worthwhile writing a bespoke merge implementation that - // will avoid reallocating if all of the options here are the defaults - val mergedStyle = style.merge( - TextStyle( - color = textColor, - fontSize = fontSize, - fontWeight = fontWeight, - textAlign = textAlign, - lineHeight = lineHeight, - fontFamily = fontFamily, - textDecoration = textDecoration, - fontStyle = fontStyle, - letterSpacing = letterSpacing - ) - ) - val mergedReadMoreStyle = mergedStyle.toSpanStyle() - .merge(readMoreStyle) - .merge( - SpanStyle( - color = readMoreColor, - fontSize = readMoreFontSize, - fontWeight = readMoreFontWeight, - fontFamily = readMoreFontFamily, - textDecoration = readMoreTextDecoration, - fontStyle = readMoreFontStyle - ) - ) - val mergedReadLessStyle = mergedStyle.toSpanStyle() - .merge(readLessStyle) - .merge( - SpanStyle( - color = readLessColor, - fontSize = readLessFontSize, - fontWeight = readLessFontWeight, - fontFamily = readLessFontFamily, - textDecoration = readLessTextDecoration, - fontStyle = readLessFontStyle - ) - ) - BasicReadMoreText( - text = text, - expanded = expanded, - modifier = modifier, - onExpandedChange = onExpandedChange, - contentPadding = contentPadding, - style = mergedStyle, - onTextLayout = onTextLayout, - softWrap = softWrap, - readMoreText = readMoreText, - readMoreMaxLines = readMoreMaxLines, - readMoreOverflow = readMoreOverflow, - readMoreStyle = mergedReadMoreStyle, - readLessText = readLessText, - readLessStyle = mergedReadLessStyle, - toggleArea = toggleArea, - ) -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreTextOverflow.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreTextOverflow.kt deleted file mode 100644 index 9b643c7f..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ReadMoreTextOverflow.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.readmore - -import androidx.compose.runtime.Stable -import kotlin.jvm.JvmInline - -@JvmInline -public value class ReadMoreTextOverflow private constructor(internal val value: Int) { - - override fun toString(): String { - return when (this) { - Clip -> "Clip" - Ellipsis -> "Ellipsis" - else -> "Invalid" - } - } - - companion object { - /** - * Clip the overflowing text to fix its container. - */ - @Stable - val Clip: ReadMoreTextOverflow = ReadMoreTextOverflow(1) - - /** - * Use an ellipsis to indicate that the text has overflowed. - */ - @Stable - val Ellipsis: ReadMoreTextOverflow = ReadMoreTextOverflow(2) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ToggleArea.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ToggleArea.kt deleted file mode 100644 index c4499572..00000000 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/custom/readmore/ToggleArea.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.datlag.burningseries.shared.ui.custom.readmore - -import androidx.compose.runtime.Stable -import kotlin.jvm.JvmInline - -@JvmInline -value class ToggleArea private constructor(internal val value: Int) { - - override fun toString(): String { - return when (this) { - All -> "All" - More -> "More" - else -> "Invalid" - } - } - - companion object { - /** - * All area of the text is clickable to toggle. - */ - @Stable - val All: ToggleArea = ToggleArea(1) - - /** - * 'More' and 'Less' area of the text is clickable to toggle. - */ - @Stable - val More: ToggleArea = ToggleArea(2) - } -} \ No newline at end of file diff --git a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt index d60ea80c..9553bf81 100644 --- a/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt +++ b/app/shared/src/commonMain/kotlin/dev/datlag/burningseries/shared/ui/screen/initial/series/component/DescriptionText.kt @@ -1,35 +1,49 @@ package dev.datlag.burningseries.shared.ui.screen.initial.series.component +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ExpandLess +import androidx.compose.material.icons.filled.ExpandMore +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight import dev.datlag.burningseries.shared.SharedRes -import dev.datlag.burningseries.shared.ui.custom.readmore.ReadMoreText -import dev.datlag.burningseries.shared.ui.custom.readmore.ReadMoreTextOverflow -import dev.datlag.burningseries.shared.ui.custom.readmore.ToggleArea +import dev.datlag.burningseries.shared.ui.custom.ExpandableText import dev.icerock.moko.resources.compose.stringResource @Composable fun DescriptionText(description: String) { - var expanded by remember { mutableStateOf(true) } + var expanded by remember { mutableStateOf(false) } - ReadMoreText( - text = description, + ExpandableText( expanded = expanded, - onExpandedChange = { - expanded = it - }, - modifier = Modifier.fillMaxWidth(), - readMoreText = stringResource(SharedRes.strings.read_more), - readMoreColor = MaterialTheme.colorScheme.primary, - readMoreFontWeight = FontWeight.SemiBold, - readMoreMaxLines = 2, - readMoreOverflow = ReadMoreTextOverflow.Ellipsis, - readLessText = stringResource(SharedRes.strings.read_less), - readLessColor = MaterialTheme.colorScheme.primary, - readLessFontWeight = FontWeight.SemiBold, - toggleArea = ToggleArea.All + text = description, + collapsedMaxLines = 2, + modifier = Modifier.fillMaxWidth().animateContentSize(), + toggle = { + IconButton( + onClick = { + expanded = !expanded + } + ) { + val (icon, iconDescription) = if (expanded) { + Icons.Default.ExpandLess to stringResource(SharedRes.strings.read_less) + } else { + Icons.Default.ExpandMore to stringResource(SharedRes.strings.read_more) + } + + Icon( + imageVector = icon, + contentDescription = iconDescription, + tint = MaterialTheme.colorScheme.primary + ) + } + } ) } \ No newline at end of file