From f903d3fa357371f28cc1c5617ffcdbfe97c19789 Mon Sep 17 00:00:00 2001
From: James Rich <2199651+jamesarich@users.noreply.github.com>
Date: Sat, 4 Oct 2025 10:01:26 -0500
Subject: [PATCH] Revert markdown message rendering (#3328)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
.../mesh/ui/message/components/MessageItem.kt | 4 +-
.../core/ui/component/AutoLinkText.kt | 90 ++++++++++++++++
.../meshtastic/core/ui/component/MDText.kt | 102 ------------------
3 files changed, 92 insertions(+), 104 deletions(-)
create mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/AutoLinkText.kt
delete mode 100644 core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MDText.kt
diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt
index e02b0b463..efdfa044e 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt
@@ -54,7 +54,7 @@ import org.meshtastic.core.database.model.Message
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.R
-import org.meshtastic.core.ui.component.MDText
+import org.meshtastic.core.ui.component.AutoLinkText
import org.meshtastic.core.ui.component.NodeChip
import org.meshtastic.core.ui.component.Rssi
import org.meshtastic.core.ui.component.Snr
@@ -158,7 +158,7 @@ internal fun MessageItem(
}
Column(modifier = Modifier.padding(horizontal = 8.dp)) {
- MDText(
+ AutoLinkText(
modifier = Modifier.fillMaxWidth(),
text = message.text,
style = MaterialTheme.typography.bodyMedium,
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/AutoLinkText.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/AutoLinkText.kt
new file mode 100644
index 000000000..e9f14b8b6
--- /dev/null
+++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/AutoLinkText.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2025 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.meshtastic.core.ui.component
+
+import android.text.Spannable
+import android.text.Spannable.Factory
+import android.text.style.URLSpan
+import android.text.util.Linkify
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.withLink
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.core.text.util.LinkifyCompat
+import org.meshtastic.core.ui.theme.HyperlinkBlue
+
+private val DefaultTextLinkStyles =
+ TextLinkStyles(style = SpanStyle(color = HyperlinkBlue, textDecoration = TextDecoration.Underline))
+
+@Composable
+fun AutoLinkText(
+ text: String,
+ modifier: Modifier = Modifier,
+ style: TextStyle = TextStyle.Default,
+ linkStyles: TextLinkStyles = DefaultTextLinkStyles,
+ color: Color = Color.Unspecified,
+) {
+ val spannable = remember(text) { linkify(text) }
+ Text(text = spannable.toAnnotatedString(linkStyles), modifier = modifier, style = style.copy(color = color))
+}
+
+private fun linkify(text: String) = Factory.getInstance().newSpannable(text).also {
+ LinkifyCompat.addLinks(it, Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES or Linkify.PHONE_NUMBERS)
+}
+
+private fun Spannable.toAnnotatedString(linkStyles: TextLinkStyles): AnnotatedString = buildAnnotatedString {
+ val spannable = this@toAnnotatedString
+ var lastEnd = 0
+ spannable.getSpans(0, spannable.length, Any::class.java).forEach { span ->
+ val start = spannable.getSpanStart(span)
+ val end = spannable.getSpanEnd(span)
+ append(spannable.subSequence(lastEnd, start))
+ when (span) {
+ is URLSpan ->
+ withLink(LinkAnnotation.Url(url = span.url, styles = linkStyles)) {
+ append(spannable.subSequence(start, end))
+ }
+
+ else -> append(spannable.subSequence(start, end))
+ }
+ lastEnd = end
+ }
+ append(spannable.subSequence(lastEnd, spannable.length))
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun AutoLinkTextPreview() {
+ AutoLinkText("A text containing a link https://example.com")
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun AutoLinkTextPreview2() {
+ AutoLinkText("")
+}
diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MDText.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MDText.kt
deleted file mode 100644
index f146400b5..000000000
--- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/MDText.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2025 Meshtastic LLC
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.meshtastic.core.ui.component
-
-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.text.SpanStyle
-import androidx.compose.ui.text.TextLinkStyles
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.style.TextDecoration
-import androidx.compose.ui.tooling.preview.Preview
-import com.mikepenz.markdown.compose.components.markdownComponents
-import com.mikepenz.markdown.m3.Markdown
-import com.mikepenz.markdown.model.DefaultMarkdownColors
-import com.mikepenz.markdown.model.DefaultMarkdownTypography
-import org.meshtastic.core.ui.theme.HyperlinkBlue
-
-@Composable
-fun MDText(
- text: String,
- modifier: Modifier = Modifier,
- style: TextStyle = MaterialTheme.typography.bodyMedium,
- color: Color = Color.Unspecified,
-) {
- val colors =
- DefaultMarkdownColors(
- text = color,
- codeBackground = MaterialTheme.colorScheme.surfaceContainerHigh,
- inlineCodeBackground = MaterialTheme.colorScheme.surfaceContainerHigh,
- dividerColor = MaterialTheme.colorScheme.onSurface,
- tableBackground = MaterialTheme.colorScheme.surfaceContainer,
- )
-
- val typography =
- DefaultMarkdownTypography(
- // Restrict max size of the text
- h1 = MaterialTheme.typography.headlineMedium.copy(color = color),
- h2 = MaterialTheme.typography.headlineMedium.copy(color = color),
- h3 = MaterialTheme.typography.headlineSmall.copy(color = color),
- h4 = MaterialTheme.typography.titleLarge.copy(color = color),
- h5 = MaterialTheme.typography.titleMedium.copy(color = color),
- h6 = MaterialTheme.typography.titleSmall.copy(color = color),
- text = style,
- code =
- MaterialTheme.typography.bodyMedium.copy(
- fontFamily = FontFamily.Monospace,
- color = MaterialTheme.colorScheme.onSurface,
- ),
- inlineCode =
- MaterialTheme.typography.bodyMedium.copy(
- fontFamily = FontFamily.Monospace,
- color = MaterialTheme.colorScheme.onSurface,
- background = MaterialTheme.colorScheme.surfaceContainerHigh,
- ),
- quote = MaterialTheme.typography.bodyLarge.copy(color = color),
- paragraph = MaterialTheme.typography.bodyMedium.copy(color = color),
- ordered = MaterialTheme.typography.bodyMedium.copy(color = color),
- bullet = MaterialTheme.typography.bodyMedium.copy(color = color),
- list = MaterialTheme.typography.bodyMedium.copy(color = color),
- textLink =
- TextLinkStyles(style = SpanStyle(color = HyperlinkBlue, textDecoration = TextDecoration.Underline)),
- table = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface),
- )
-
- // Custom Markdown components to disable image rendering
- val customComponents = markdownComponents(image = { /* Empty composable to disable image rendering */ })
-
- Markdown(
- content = text,
- modifier = modifier,
- colors = colors,
- typography = typography,
- components = customComponents, // Use custom components
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun AutoLinkTextPreview() {
- MDText(
- "A text containing a link https://example.com **bold** _Italics_" +
- "\n # hello \n ## hello \n ### hello \n #### hello \n ##### hello \n ###### hello \n ```code```",
- )
-}