diff --git a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt index c915d78d8..6330a5c38 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt @@ -59,7 +59,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.getSelectedText -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.jerboa.R import com.jerboa.api.uploadPictrsImage @@ -580,23 +579,10 @@ fun TextMarkdownBarPreview() { ) } -@Composable -fun PreviewLines( - text: String, - modifier: Modifier = Modifier, -) { - Text( - text = text, - style = MaterialTheme.typography.bodyMedium, - maxLines = 5, - overflow = TextOverflow.Ellipsis, - modifier = modifier, - ) -} - @Composable fun MyMarkdownText( markdown: String, + modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.onSurface, onClick: () -> Unit, onLongClick: (() -> Unit)? = null, @@ -606,6 +592,7 @@ fun MyMarkdownText( color = color, onClick = onClick, onLongClick = onLongClick, + modifier = modifier, ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt index f63d66f2e..ce6ec924e 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt @@ -4,10 +4,15 @@ import android.content.Context import android.os.Build import android.text.Spannable import android.text.SpannableStringBuilder +import android.text.TextUtils import android.text.style.URLSpan import android.text.util.Linkify import android.util.TypedValue import android.view.View +import android.view.View.NOT_FOCUSABLE +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.LinearLayout import android.widget.TextView import androidx.annotation.FontRes import androidx.annotation.IdRes @@ -16,7 +21,7 @@ import androidx.compose.material.LocalContentAlpha import androidx.compose.material.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.graphics.toArgb @@ -44,6 +49,7 @@ import io.noties.markwon.ext.strikethrough.StrikethroughPlugin import io.noties.markwon.ext.tables.TableAwareMovementMethod import io.noties.markwon.ext.tables.TablePlugin import io.noties.markwon.html.HtmlPlugin +import io.noties.markwon.html.TagHandlerNoOp import io.noties.markwon.image.AsyncDrawableSpan import io.noties.markwon.image.coil.CoilImagesPlugin import io.noties.markwon.linkify.LinkifyPlugin @@ -127,6 +133,7 @@ class LemmyLinkPlugin : AbstractMarkwonPlugin() { object MarkdownHelper { private var markwon: Markwon? = null + private var previewMarkwon: Markwon? = null fun init(navController: NavController, useCustomTabs: Boolean, usePrivateTabs: Boolean) { val context = navController.context @@ -135,17 +142,18 @@ object MarkdownHelper { .placeholder(R.drawable.ic_launcher_foreground) .build() + // main markdown parser has coil + html on markwon = Markwon.builder(context) - .usePlugin(CoilImagesPlugin.create(context, loader)) // email urls interfere with lemmy links .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) .usePlugin(LemmyLinkPlugin()) .usePlugin(StrikethroughPlugin.create()) .usePlugin(TablePlugin.create(context)) + .usePlugin(CoilImagesPlugin.create(context, loader)) + .usePlugin(HtmlPlugin.create()) // use TableAwareLinkMovementMethod to handle clicks inside tables, // wraps LinkMovementMethod internally .usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create())) - .usePlugin(HtmlPlugin.create()) .usePlugin(object : AbstractMarkwonPlugin() { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { builder.linkResolver { _, link -> @@ -154,6 +162,21 @@ object MarkdownHelper { } }) .build() + + // no image parser has html off + previewMarkwon = Markwon.builder(context) + // email urls interfere with lemmy links + .usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS)) + .usePlugin(LemmyLinkPlugin()) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(TablePlugin.create(context)) + .usePlugin(HtmlPlugin.create { plugin -> plugin.addHandler(TagHandlerNoOp.create("img")) }) + .usePlugin(object : AbstractMarkwonPlugin() { + override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { + builder.linkResolver { _, _ -> } + } + }) + .build() } /* @@ -161,17 +184,18 @@ object MarkdownHelper { */ fun init(context: Context) { markwon = Markwon.builder(context).build() + previewMarkwon = Markwon.builder(context).build() } - @OptIn(ExperimentalComposeUiApi::class) @Composable fun CreateMarkdownView( markdown: String, + modifier: Modifier = Modifier, color: Color = Color.Unspecified, onClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, + style: TextStyle = MaterialTheme.typography.bodyLarge, ) { - val style = MaterialTheme.typography.bodyLarge val defaultColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current) BoxWithConstraints { @@ -197,11 +221,9 @@ object MarkdownHelper { img.drawable.initWithKnownDimensions(canvasWidthMaybe, textSizeMaybe) } markwon!!.setParsedMarkdown(textView, md) - // if (disableLinkMovementMethod) { - // textView.movementMethod = null - // } }, onReset = {}, + modifier = modifier, ) } } @@ -212,7 +234,6 @@ object MarkdownHelper { defaultColor: Color, fontSize: TextUnit = TextUnit.Unspecified, textAlign: TextAlign? = null, - maxLines: Int = Int.MAX_VALUE, @FontRes fontResource: Int? = null, style: TextStyle, @IdRes viewId: Int? = null, @@ -231,7 +252,6 @@ object MarkdownHelper { onClick?.let { setOnClickListener { onClick() } } onLongClick?.let { setOnLongClickListener { onLongClick(); true } } setTextColor(textColor.toArgb()) - setMaxLines(maxLines) setTextSize(TypedValue.COMPLEX_UNIT_SP, mergedStyle.fontSize.value) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { setLineHeight(convertSpToPx(mergedStyle.lineHeight, context)) @@ -253,4 +273,65 @@ object MarkdownHelper { } } } + + @Composable + fun CreateMarkdownPreview( + markdown: String, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.onSurface, + onClick: (() -> Unit)? = null, + style: TextStyle, + defaultColor: Color, + ) { + AndroidView( + factory = { ctx -> + createTextViewPreview( + context = ctx, + color = color, + defaultColor = defaultColor, + fontSize = TextUnit.Unspecified, + style = style, + onClick = onClick, + ) + }, + update = { textView -> + previewMarkwon!!.setMarkdown(textView, markdown) + }, + onReset = {}, + modifier = modifier, + ) + } + + private fun createTextViewPreview( + context: Context, + color: Color = Color.Unspecified, + defaultColor: Color, + fontSize: TextUnit = TextUnit.Unspecified, + maxLines: Int = 5, + style: TextStyle, + onClick: (() -> Unit)? = null, + ): TextView { + val textColor = color.takeOrElse { style.color.takeOrElse { defaultColor } } + val mergedStyle = style.merge( + TextStyle( + color = textColor, + fontSize = if (fontSize != TextUnit.Unspecified) fontSize else style.fontSize, + ), + ) + return TextView(context).apply { + onClick?.let { setOnClickListener { onClick() } } + setTextColor(textColor.toArgb()) + setTextSize(TypedValue.COMPLEX_UNIT_SP, mergedStyle.fontSize.value) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + lineHeight = convertSpToPx(mergedStyle.lineHeight, context) + } + width = maxWidth + layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + this.movementMethod = null + this.linksClickable = false + ellipsize = TextUtils.TruncateAt.END + setMaxLines(maxLines) + focusable = NOT_FOCUSABLE + } + } } diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt index 3242baf63..550950add 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt @@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.LocalContentColor import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.outlined.Block @@ -50,6 +52,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag @@ -89,11 +92,11 @@ import com.jerboa.ui.components.common.CommentOrPostNodeHeader import com.jerboa.ui.components.common.DotSpacer import com.jerboa.ui.components.common.IconAndTextDrawerItem import com.jerboa.ui.components.common.ImageViewerDialog +import com.jerboa.ui.components.common.MarkdownHelper.CreateMarkdownPreview import com.jerboa.ui.components.common.MyMarkdownText import com.jerboa.ui.components.common.NsfwBadge import com.jerboa.ui.components.common.PictrsThumbnailImage import com.jerboa.ui.components.common.PictrsUrlImage -import com.jerboa.ui.components.common.PreviewLines import com.jerboa.ui.components.common.ScoreAndTime import com.jerboa.ui.components.common.SimpleTopAppBar import com.jerboa.ui.components.common.TimeAgo @@ -402,6 +405,7 @@ fun PostBody( useCustomTabs: Boolean, usePrivateTabs: Boolean, blurNSFW: Boolean, + clickBody: () -> Unit = {}, ) { val post = postView.post Column( @@ -452,10 +456,14 @@ fun PostBody( } } } else { - PreviewLines( - text = text, - modifier = Modifier - .padding(MEDIUM_PADDING), + val defaultColor: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current) + + CreateMarkdownPreview( + markdown = text, + defaultColor = defaultColor, + onClick = clickBody, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(MEDIUM_PADDING), ) } }, @@ -1388,6 +1396,7 @@ fun PostListingCard( useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, blurNSFW = blurNSFW, + clickBody = { onPostClick(postView) }, ) // Footer bar