diff --git a/emoji-compose-m2/src/commonMain/kotlin/org/kodein/emoji/compose/m2/text.kt b/emoji-compose-m2/src/commonMain/kotlin/org/kodein/emoji/compose/m2/text.kt index 69bc6d2..1b10732 100644 --- a/emoji-compose-m2/src/commonMain/kotlin/org/kodein/emoji/compose/m2/text.kt +++ b/emoji-compose-m2/src/commonMain/kotlin/org/kodein/emoji/compose/m2/text.kt @@ -49,9 +49,13 @@ public fun TextWithPlatformEmoji( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithPlatformEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithPlatformEmoji( + text = text, + fixedImageSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -102,9 +106,13 @@ public fun TextWithPlatformEmoji( minLines: Int = 1, inlineContent: Map = mapOf(), onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithPlatformEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithPlatformEmoji( + text = text, + fixedImageSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -152,9 +160,13 @@ public fun TextWithNotoImageEmoji( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithNotoImageEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithNotoImageEmoji( + text = text, + fixedSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -203,9 +215,13 @@ public fun TextWithNotoImageEmoji( minLines: Int = 1, inlineContent: Map = mapOf(), onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithNotoImageEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithNotoImageEmoji( + text = text, + fixedSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -241,8 +257,6 @@ public fun TextWithNotoImageEmoji( public fun TextWithNotoAnimatedEmoji( text: String, modifier: Modifier = Modifier, - iterations: Int = Int.MAX_VALUE, - speed: Float = 1f, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, @@ -257,12 +271,16 @@ public fun TextWithNotoAnimatedEmoji( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + emojiAnimationIterations: Int = Int.MAX_VALUE, + emojiAnimationSpeed: Float = 1f, + fixedEmojiSize: Boolean = false, ) { WithNotoAnimatedEmoji( text = text, - iterations = iterations, - speed = speed + iterations = emojiAnimationIterations, + speed = emojiAnimationSpeed, + fixedSize = fixedEmojiSize, ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, @@ -299,8 +317,6 @@ public fun TextWithNotoAnimatedEmoji( public fun TextWithNotoAnimatedEmoji( text: AnnotatedString, modifier: Modifier = Modifier, - iterations: Int = Int.MAX_VALUE, - speed: Float = 1f, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, @@ -316,12 +332,16 @@ public fun TextWithNotoAnimatedEmoji( minLines: Int = 1, inlineContent: Map = mapOf(), onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, + emojiAnimationIterations: Int = Int.MAX_VALUE, + emojiAnimationSpeed: Float = 1f, ) { WithNotoAnimatedEmoji( text = text, - iterations = iterations, - speed = speed + iterations = emojiAnimationIterations, + speed = emojiAnimationSpeed, + fixedSize = fixedEmojiSize, ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, diff --git a/emoji-compose-m3/src/commonMain/kotlin/org/kodein/emoji/compose/m3/text.kt b/emoji-compose-m3/src/commonMain/kotlin/org/kodein/emoji/compose/m3/text.kt index 92245d0..fbbeea1 100644 --- a/emoji-compose-m3/src/commonMain/kotlin/org/kodein/emoji/compose/m3/text.kt +++ b/emoji-compose-m3/src/commonMain/kotlin/org/kodein/emoji/compose/m3/text.kt @@ -45,9 +45,13 @@ public fun TextWithPlatformEmoji( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithPlatformEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithPlatformEmoji( + text = text, + fixedImageSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -98,9 +102,13 @@ public fun TextWithPlatformEmoji( minLines: Int = 1, inlineContent: Map = mapOf(), onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithPlatformEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithPlatformEmoji( + text = text, + fixedImageSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -148,9 +156,13 @@ public fun TextWithNotoImageEmoji( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithNotoImageEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithNotoImageEmoji( + text = text, + fixedSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -199,9 +211,13 @@ public fun TextWithNotoImageEmoji( minLines: Int = 1, inlineContent: Map = mapOf(), onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + fixedEmojiSize: Boolean = false, ) { - WithNotoImageEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithNotoImageEmoji( + text = text, + fixedSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -251,9 +267,17 @@ public fun TextWithNotoAnimatedEmoji( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + emojiAnimationIterations: Int = Int.MAX_VALUE, + emojiAnimationSpeed: Float = 1f, + fixedEmojiSize: Boolean = false, ) { - WithNotoAnimatedEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithNotoAnimatedEmoji( + text = text, + iterations = emojiAnimationIterations, + speed = emojiAnimationSpeed, + fixedSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, @@ -304,9 +328,17 @@ public fun TextWithNotoAnimatedEmoji( minLines: Int = 1, inlineContent: Map = mapOf(), onTextLayout: (TextLayoutResult) -> Unit = {}, - style: TextStyle = LocalTextStyle.current + style: TextStyle = LocalTextStyle.current, + emojiAnimationIterations: Int = Int.MAX_VALUE, + emojiAnimationSpeed: Float = 1f, + fixedEmojiSize: Boolean = false, ) { - WithNotoAnimatedEmoji(text) { emojiAnnotatedString, emojiInlineContent -> + WithNotoAnimatedEmoji( + text = text, + iterations = emojiAnimationIterations, + speed = emojiAnimationSpeed, + fixedSize = fixedEmojiSize, + ) { emojiAnnotatedString, emojiInlineContent -> Text( text = emojiAnnotatedString, modifier = modifier, diff --git a/emoji-compose/src/commonMain/kotlin/org/kodein/emoji/compose/text.kt b/emoji-compose/src/commonMain/kotlin/org/kodein/emoji/compose/text.kt index 9d20837..0a32624 100644 --- a/emoji-compose/src/commonMain/kotlin/org/kodein/emoji/compose/text.kt +++ b/emoji-compose/src/commonMain/kotlin/org/kodein/emoji/compose/text.kt @@ -4,10 +4,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.Placeholder @@ -33,7 +30,7 @@ public fun String.withEmoji(): String { } @Composable -private fun WithNotoEmoji( +private fun WithDynamicSizedNotoEmoji( text: CharSequence, content: @Composable (AnnotatedString, Map) -> Unit, createInlineTextContent: suspend (FoundEmoji) -> InlineTextContent? @@ -83,6 +80,59 @@ private fun WithNotoEmoji( content(annotatedString, inlineContent) } +@Composable +private fun WithFixedSizedNotoEmoji( + text: CharSequence, + content: @Composable (AnnotatedString, Map) -> Unit, + createInlineTextContent: suspend (FoundEmoji) -> InlineTextContent? +) { + val service = EmojiService.get() ?: return + + val all = remember(text) { service.finder.findEmoji(text).toList() } + + val inlineContent = HashMap() + val annotatedString = buildAnnotatedString { + var start = 0 + all.forEach { found -> + if (text is AnnotatedString) + append(text.subSequence(start, found.start)) + else + append(text.substring(start, found.start)) + val inlineContentID = "emoji:${found.emoji}" + inlineContent[inlineContentID] = InlineTextContent(Placeholder(1.em, 1.em, PlaceholderVerticalAlign.Center)) { + var itc: InlineTextContent? by remember { mutableStateOf(null) } + LaunchedEffect(null) { + itc = createInlineTextContent(found) + } + if (itc == null) { + content(AnnotatedString(found.emoji.details.string), emptyMap()) + } else { + itc!!.children("") + } + } + appendInlineContent(inlineContentID) + start = found.end + } + if (text is AnnotatedString) + append(text.subSequence(start, text.length)) + else + append(text.substring(start, text.length)) + } + + content(annotatedString, inlineContent) +} + +@Composable +private fun WithNotoEmoji( + text: CharSequence, + content: @Composable (AnnotatedString, Map) -> Unit, + createInlineTextContent: suspend (FoundEmoji) -> InlineTextContent?, + fixedSize: Boolean, +) { + if (fixedSize) WithFixedSizedNotoEmoji(text, content, createInlineTextContent) + else WithDynamicSizedNotoEmoji(text, content, createInlineTextContent) +} + private suspend fun createNotoSvgInlineContent(emoji: Emoji, download: suspend (EmojiUrl) -> ByteArray): InlineTextContent? { try { val bytes = download(EmojiUrl.from(emoji, EmojiUrl.Type.SVG)) @@ -90,7 +140,7 @@ private suspend fun createNotoSvgInlineContent(emoji: Emoji, download: suspend ( return InlineTextContent( placeholder = Placeholder(1.em, 1.em / svg.sizeRatio(), PlaceholderVerticalAlign.Center), children = { - SVGImage(svg, "${emoji.details.description} emoji", Modifier.size(20.dp)) + SVGImage(svg, "${emoji.details.description} emoji", Modifier.fillMaxSize()) } ) } catch (t: Throwable) { @@ -104,19 +154,22 @@ private suspend fun createNotoSvgInlineContent(emoji: Emoji, download: suspend ( * Replaces all emojis with [NotoImageEmoji]. * * @param text The text to with Emoji UTF characters. + * @param fixedSize If true, then the emoji will not be resized once downloaded. * @param content A lambda that receives the `AnnotatedString` and its corresponding `InlineTextContent` map * These should be used to display: `{ astr, map -> Text(astr, inlineContent = map) }`. */ @Composable public fun WithNotoImageEmoji( text: CharSequence, + fixedSize: Boolean = false, content: @Composable (AnnotatedString, Map) -> Unit ) { val download = LocalEmojiDownloader.current WithNotoEmoji( text = text, content = content, - createInlineTextContent = { found -> createNotoSvgInlineContent(found.emoji, download) } + createInlineTextContent = { found -> createNotoSvgInlineContent(found.emoji, download) }, + fixedSize = fixedSize, ) } @@ -149,6 +202,7 @@ private suspend fun createNotoLottieInlineContent( * @param text The text to with Emoji UTF characters. * @param iterations The number of times that the animations will be played (default is infinite). * @param speed Speed at which the animations will be rendered. + * @param fixedSize If true, then the emoji will not be resized once downloaded. * @param content A lambda that receives the `AnnotatedString` and its corresponding `InlineTextContent` map * These should be used to display: `{ astr, map -> Text(astr, inlineContent = map) }`. */ @@ -157,13 +211,15 @@ public fun WithNotoAnimatedEmoji( text: CharSequence, iterations: Int = Int.MAX_VALUE, speed: Float = 1f, + fixedSize: Boolean = false, content: @Composable (AnnotatedString, Map) -> Unit ) { val download = LocalEmojiDownloader.current WithNotoEmoji( text = text, content = content, - createInlineTextContent = { found -> createNotoLottieInlineContent(found.emoji, iterations, speed, download) } + createInlineTextContent = { found -> createNotoLottieInlineContent(found.emoji, iterations, speed, download) }, + fixedSize = fixedSize ) } @@ -174,11 +230,13 @@ public fun WithNotoAnimatedEmoji( * - On all other platforms: does not modify the text at all (map will be empty). * * @param text The text to with Emoji UTF characters. + * @param fixedImageSize If true, then the emoji will not be resized once downloaded. * @param content A lambda that receives the `AnnotatedString` and its corresponding `InlineTextContent` map * These should be used to display: `{ astr, map -> Text(astr, inlineContent = map) }`. */ @Composable public expect fun WithPlatformEmoji( text: CharSequence, + fixedImageSize: Boolean = false, content: @Composable (AnnotatedString, Map) -> Unit ) diff --git a/emoji-compose/src/iosMain/kotlin/org/kodein/emoji/compose/iosPlatform.kt b/emoji-compose/src/iosMain/kotlin/org/kodein/emoji/compose/iosPlatform.kt index 4118e59..dcb8a3d 100644 --- a/emoji-compose/src/iosMain/kotlin/org/kodein/emoji/compose/iosPlatform.kt +++ b/emoji-compose/src/iosMain/kotlin/org/kodein/emoji/compose/iosPlatform.kt @@ -36,6 +36,7 @@ internal actual suspend fun platformDownloadBytes(url: String): ByteArray { @Composable public actual fun WithPlatformEmoji( text: CharSequence, + fixedImageSize: Boolean, content: @Composable (AnnotatedString, Map) -> Unit ) { val annotatedString = remember(text) { AnnotatedString.Builder().append(text).toAnnotatedString() } diff --git a/emoji-compose/src/jvmBasedMain/kotlin/org/kodein/emoji/compose/jvmPlatform.kt b/emoji-compose/src/jvmBasedMain/kotlin/org/kodein/emoji/compose/jvmPlatform.kt index 4d5299a..90bf0be 100644 --- a/emoji-compose/src/jvmBasedMain/kotlin/org/kodein/emoji/compose/jvmPlatform.kt +++ b/emoji-compose/src/jvmBasedMain/kotlin/org/kodein/emoji/compose/jvmPlatform.kt @@ -19,6 +19,7 @@ internal actual suspend fun platformDownloadBytes(url: String): ByteArray = @Composable public actual fun WithPlatformEmoji( text: CharSequence, + fixedImageSize: Boolean, content: @Composable (AnnotatedString, Map) -> Unit ) { val annotatedString = remember(text) { AnnotatedString.Builder().append(text).toAnnotatedString() } diff --git a/emoji-compose/src/wasmJsMain/kotlin/org/kodein/emoji/compose/platformWasm.kt b/emoji-compose/src/wasmJsMain/kotlin/org/kodein/emoji/compose/platformWasm.kt index 1c93393..d72a55a 100644 --- a/emoji-compose/src/wasmJsMain/kotlin/org/kodein/emoji/compose/platformWasm.kt +++ b/emoji-compose/src/wasmJsMain/kotlin/org/kodein/emoji/compose/platformWasm.kt @@ -26,9 +26,14 @@ internal actual suspend fun platformDownloadBytes(url: String): ByteArray { @Composable public actual fun WithPlatformEmoji( text: CharSequence, + fixedImageSize: Boolean, content: @Composable (AnnotatedString, Map) -> Unit ) { - WithNotoImageEmoji(text, content) + WithNotoImageEmoji( + text = text, + fixedSize = fixedImageSize, + content = content + ) } @Composable