Skip to content

Commit c6e3758

Browse files
authored
Add inlineContent slot to ReadMoreText composables. (#120)
* Add inlineContent slot to ReadMoreText composables * Add screenshots * Update metalava signatures
1 parent b79eafc commit c6e3758

File tree

37 files changed

+790
-4
lines changed

37 files changed

+790
-4
lines changed

readmore-foundation/api/current.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
package com.webtoonscorp.android.readmore.foundation {
33

44
public final class BasicReadMoreTextKt {
5+
method @androidx.compose.runtime.Composable public static void BasicReadMoreText(androidx.compose.ui.text.AnnotatedString text, boolean expanded, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onExpandedChange, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional boolean softWrap, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional String readMoreText, optional int readMoreMaxLines, optional int readMoreOverflow, optional androidx.compose.ui.text.SpanStyle readMoreStyle, optional String readLessText, optional androidx.compose.ui.text.SpanStyle readLessStyle, optional int toggleArea);
56
method @androidx.compose.runtime.Composable public static void BasicReadMoreText(String text, boolean expanded, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onExpandedChange, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional boolean softWrap, optional String readMoreText, optional int readMoreMaxLines, optional int readMoreOverflow, optional androidx.compose.ui.text.SpanStyle readMoreStyle, optional String readLessText, optional androidx.compose.ui.text.SpanStyle readLessStyle, optional int toggleArea);
6-
method @androidx.compose.runtime.Composable public static void BasicReadMoreText(androidx.compose.ui.text.AnnotatedString text, boolean expanded, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onExpandedChange, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional boolean softWrap, optional String readMoreText, optional int readMoreMaxLines, optional int readMoreOverflow, optional androidx.compose.ui.text.SpanStyle readMoreStyle, optional String readLessText, optional androidx.compose.ui.text.SpanStyle readLessStyle, optional int toggleArea);
77
}
88

99
@kotlin.RequiresOptIn(message="This API is experimental and is likely to change or to be removed in the future.") @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalReadMoreApi {
7.54 KB
Loading
16.8 KB
Loading
6.88 KB
Loading
16 KB
Loading
6.88 KB
Loading
16.8 KB
Loading
7.54 KB
Loading
16 KB
Loading

readmore-foundation/src/main/java/com/webtoonscorp/android/readmore/foundation/BasicReadMoreText.kt

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
*/
1616
package com.webtoonscorp.android.readmore.foundation
1717

18+
import android.annotation.SuppressLint
1819
import android.util.Log
1920
import androidx.compose.foundation.clickable
2021
import androidx.compose.foundation.layout.BoxWithConstraints
2122
import androidx.compose.foundation.layout.PaddingValues
2223
import androidx.compose.foundation.layout.padding
2324
import androidx.compose.foundation.text.BasicText
25+
import androidx.compose.foundation.text.InlineTextContent
2426
import androidx.compose.runtime.Composable
2527
import androidx.compose.runtime.LaunchedEffect
2628
import androidx.compose.runtime.Stable
@@ -31,6 +33,7 @@ import androidx.compose.runtime.setValue
3133
import androidx.compose.ui.Modifier
3234
import androidx.compose.ui.text.AnnotatedString
3335
import androidx.compose.ui.text.LinkAnnotation
36+
import androidx.compose.ui.text.Placeholder
3437
import androidx.compose.ui.text.SpanStyle
3538
import androidx.compose.ui.text.TextLayoutResult
3639
import androidx.compose.ui.text.TextMeasurer
@@ -132,6 +135,8 @@ public fun BasicReadMoreText(
132135
* @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
133136
* text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
134137
* [readMoreOverflow] and TextAlign may have unexpected effects.
138+
* @param inlineContent A map store composables that replaces certain ranges of the text. It's used
139+
* to insert composables into text layout. Check [InlineTextContent] for more information.
135140
* @param readMoreText The read more text to be displayed in the collapsed state.
136141
* @param readMoreMaxLines An optional maximum number of lines for the text to span, wrapping if
137142
* necessary. If the text exceeds the given number of lines, it will be truncated according to
@@ -154,6 +159,7 @@ public fun BasicReadMoreText(
154159
style: TextStyle = TextStyle.Default,
155160
onTextLayout: (TextLayoutResult) -> Unit = {},
156161
softWrap: Boolean = true,
162+
inlineContent: Map<String, InlineTextContent> = mapOf(),
157163
readMoreText: String = "",
158164
readMoreMaxLines: Int = 2,
159165
readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis,
@@ -171,6 +177,7 @@ public fun BasicReadMoreText(
171177
style = style,
172178
onTextLayout = onTextLayout,
173179
softWrap = softWrap,
180+
inlineContent = inlineContent,
174181
readMoreText = readMoreText,
175182
readMoreMaxLines = readMoreMaxLines,
176183
readMoreOverflow = readMoreOverflow,
@@ -188,6 +195,7 @@ public fun BasicReadMoreText(
188195
private const val ReadMoreTag = "read_more"
189196
private const val ReadLessTag = "read_less"
190197

198+
@SuppressLint("UnusedBoxWithConstraintsScope")
191199
@Composable
192200
private fun CoreReadMoreText(
193201
text: AnnotatedString,
@@ -198,6 +206,7 @@ private fun CoreReadMoreText(
198206
style: TextStyle = TextStyle.Default,
199207
onTextLayout: (TextLayoutResult) -> Unit = {},
200208
softWrap: Boolean = true,
209+
inlineContent: Map<String, InlineTextContent> = mapOf(),
201210
readMoreText: String = "",
202211
readMoreMaxLines: Int = 2,
203212
readMoreOverflow: ReadMoreTextOverflow = ReadMoreTextOverflow.Ellipsis,
@@ -304,6 +313,7 @@ private fun CoreReadMoreText(
304313
overflow = TextOverflow.Ellipsis,
305314
softWrap = softWrap,
306315
maxLines = if (expanded) Int.MAX_VALUE else readMoreMaxLines,
316+
inlineContent = inlineContent,
307317
)
308318

309319
val constraints = Constraints(maxWidth = constraints.maxWidth)
@@ -317,6 +327,7 @@ private fun CoreReadMoreText(
317327
text,
318328
readMoreMaxLines,
319329
softWrap,
330+
inlineContent,
320331
) {
321332
state.applyCollapsedText(
322333
textMeasurer = textMeasurer,
@@ -328,6 +339,7 @@ private fun CoreReadMoreText(
328339
text = text,
329340
readMoreMaxLines = readMoreMaxLines,
330341
softWrap = softWrap,
342+
inlineContent = inlineContent,
331343
)
332344
}
333345
}
@@ -346,7 +358,7 @@ private class ReadMoreState {
346358

347359
var collapsedText: AnnotatedString
348360
get() = _collapsedText
349-
internal set(value) {
361+
private set(value) {
350362
if (value != _collapsedText) {
351363
_collapsedText = value
352364
if (DebugLog) {
@@ -368,6 +380,7 @@ private class ReadMoreState {
368380
text: AnnotatedString,
369381
readMoreMaxLines: Int,
370382
softWrap: Boolean,
383+
inlineContent: Map<String, InlineTextContent>,
371384
) {
372385
val overflowTextWidth = if (overflowText.isNotEmpty()) {
373386
textMeasurer.measure(
@@ -392,6 +405,7 @@ private class ReadMoreState {
392405
overflow = TextOverflow.Clip,
393406
softWrap = softWrap,
394407
constraints = constraints,
408+
placeholders = extractPlaceholders(text, inlineContent),
395409
)
396410

397411
val clipTextCount = textLayout.getLineEnd(lineIndex = textLayout.lineCount - 1)
@@ -410,6 +424,7 @@ private class ReadMoreState {
410424
text = subText,
411425
style = style,
412426
softWrap = softWrap,
427+
placeholders = extractPlaceholders(subText, inlineContent),
413428
).size.width
414429
},
415430
)
@@ -452,6 +467,40 @@ private class ReadMoreState {
452467
return replacedCount
453468
}
454469

470+
/**
471+
* Converts AnnotatedString and inlineContent map to placeholders for use with TextMeasurer.
472+
*
473+
* @param text The annotated string containing inline content annotations
474+
* @param inlineContent Map of inline content IDs to their corresponding InlineTextContent
475+
* @return List of Range<Placeholder> objects representing the inline content positions
476+
*/
477+
private fun extractPlaceholders(
478+
text: AnnotatedString,
479+
inlineContent: Map<String, InlineTextContent>,
480+
): List<AnnotatedString.Range<Placeholder>> {
481+
if (inlineContent.isEmpty()) {
482+
return emptyList()
483+
}
484+
485+
// Get all string annotations with the "androidx.compose.foundation.text.inlineContent" tag
486+
val inlineContentAnnotations = text.getStringAnnotations(
487+
tag = "androidx.compose.foundation.text.inlineContent",
488+
start = 0,
489+
end = text.length,
490+
)
491+
492+
// Map each annotation to a Range<Placeholder> if it exists in the inlineContent map
493+
return inlineContentAnnotations.mapNotNull { annotation ->
494+
inlineContent[annotation.item]?.let { content ->
495+
AnnotatedString.Range(
496+
item = content.placeholder,
497+
start = annotation.start,
498+
end = annotation.end,
499+
)
500+
}
501+
}
502+
}
503+
455504
override fun toString(): String {
456505
return "ReadMoreState(" +
457506
"collapsedText=$collapsedText" +

0 commit comments

Comments
 (0)