From 8a5e0f50034e52fccb2b3a932a6f197c15bdf36e Mon Sep 17 00:00:00 2001
From: Taras Smakula <tarassmakula@gmail.com>
Date: Wed, 29 Nov 2023 18:46:52 +0200
Subject: [PATCH] Move makdown parsing to mapping stage

---
 app/build.gradle                              |   8 -
 .../circles/extensions/EditableExtensions.kt  |  28 ----
 .../DisplayableEventFormatter.kt              |   2 +-
 .../timeline/list/TimelineViewHolder.kt       |   7 +-
 .../post/create/CreatePostDialogFragment.kt   |   2 +-
 .../post/markdown/EnhancedMovementMethod.kt   |  58 -------
 .../post/markdown/span/OrderedListItemSpan.kt |  56 -------
 .../timeline/post/markdown/span/TextStyle.kt  |  28 ----
 .../org/futo/circles/view/ComposerEditText.kt | 123 ---------------
 .../org/futo/circles/view/PillImageSpan.kt    | 141 ------------------
 .../java/org/futo/circles/view/PostLayout.kt  |   6 +-
 .../org/futo/circles/view/PreviewPostView.kt  |  29 ++--
 app/src/main/res/layout/view_preview_post.xml |   2 +-
 core/build.gradle                             |   8 +
 .../core/feature}/markdown/MarkdownParser.kt  |   4 +-
 .../markdown/mentions/MentionsAdapter.kt      |   2 +-
 .../markdown/mentions/MentionsPresenter.kt    |   2 +-
 .../plugin/MentionDelimiterProcessor.kt       |   2 +-
 .../markdown/mentions/plugin/MentionNode.kt   |   2 +-
 .../markdown/mentions/plugin/MentionPlugin.kt |   4 +-
 .../feature}/markdown/span/MentionSpan.kt     |   4 +-
 .../timeline/builder/MultiTimelineBuilder.kt  |  10 +-
 .../timeline/builder/SingleTimelineBuilder.kt |  11 +-
 .../data_source/BaseTimelineDataSource.kt     |  18 ++-
 .../timeline/post/PostContentDataSource.kt    |  10 +-
 .../timeline/post/PostOptionsDataSource.kt    |   2 +-
 .../core/mapping/MediaPostContentMapping.kt   |   9 +-
 .../core/mapping/TextPostContentMapping.kt    |  10 +-
 .../core/mapping/TimelineEventMapping.kt      |  20 +--
 .../futo/circles/core/model/PostContent.kt    |   4 +-
 .../src/main/res/drawable/ic_mention.xml      |   0
 {app => core}/src/main/res/xml/bg_chip.xml    |   0
 32 files changed, 108 insertions(+), 504 deletions(-)
 delete mode 100644 app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt
 delete mode 100644 app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt
 delete mode 100644 app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt
 delete mode 100644 app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt
 delete mode 100644 app/src/main/java/org/futo/circles/view/ComposerEditText.kt
 delete mode 100644 app/src/main/java/org/futo/circles/view/PillImageSpan.kt
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/MarkdownParser.kt (94%)
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/mentions/MentionsAdapter.kt (91%)
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/mentions/MentionsPresenter.kt (96%)
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/mentions/plugin/MentionDelimiterProcessor.kt (93%)
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/mentions/plugin/MentionNode.kt (72%)
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/mentions/plugin/MentionPlugin.kt (88%)
 rename {app/src/main/java/org/futo/circles/feature/timeline/post => core/src/main/java/org/futo/circles/core/feature}/markdown/span/MentionSpan.kt (87%)
 rename {app => core}/src/main/res/drawable/ic_mention.xml (100%)
 rename {app => core}/src/main/res/xml/bg_chip.xml (100%)

diff --git a/app/build.gradle b/app/build.gradle
index 81f1d8a1c..85584f433 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -88,14 +88,6 @@ dependencies {
     //Emoji
     implementation 'com.vanniktech:emoji-google:0.17.0'
 
-    //Markdown
-    def markwon_version = "4.6.2"
-    implementation "io.noties.markwon:core:$markwon_version"
-    implementation "io.noties.markwon:linkify:$markwon_version"
-    implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
-    implementation "io.noties.markwon:ext-tasklist:$markwon_version"
-    implementation "io.element.android:wysiwyg:2.18.0"
-
     //Log
     implementation 'com.jakewharton.timber:timber:5.0.1'
 
diff --git a/app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt b/app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt
deleted file mode 100644
index 99b0cfcdd..000000000
--- a/app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.futo.circles.extensions
-
-import android.text.Editable
-import android.view.View
-import android.view.inputmethod.InputMethodManager
-import androidx.core.content.getSystemService
-import org.futo.circles.feature.timeline.post.markdown.span.TextStyle
-import org.futo.circles.feature.timeline.post.markdown.span.toSpanClass
-
-fun Editable.getGivenSpansAt(
-    vararg span: TextStyle,
-    start: Int = 0,
-    end: Int = length
-): MutableList<Any> {
-    val spanList = mutableListOf<Any>()
-    for (selectedSpan in span) {
-        getSpans(start, end, selectedSpan.toSpanClass()).forEach { spanList.add(it) }
-    }
-    return spanList
-}
-
-fun View.showKeyboard(andRequestFocus: Boolean = false) {
-    if (andRequestFocus) {
-        requestFocus()
-    }
-    val imm = context?.getSystemService<InputMethodManager>()
-    imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt b/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt
index ce28f3d31..dca27d5ce 100644
--- a/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt
+++ b/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt
@@ -4,7 +4,7 @@ import android.content.Context
 import dagger.hilt.android.qualifiers.ApplicationContext
 import org.futo.circles.R
 import org.futo.circles.core.provider.MatrixSessionProvider
-import org.futo.circles.feature.timeline.post.markdown.MarkdownParser
+import org.futo.circles.core.feature.markdown.MarkdownParser
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt
index d43530f63..02c0c5c03 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt
+++ b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt
@@ -21,7 +21,7 @@ import org.futo.circles.core.model.Post
 import org.futo.circles.core.model.TextContent
 import org.futo.circles.databinding.ViewPollPostBinding
 import org.futo.circles.databinding.ViewTextMediaPostBinding
-import org.futo.circles.feature.timeline.post.markdown.MarkdownParser
+import org.futo.circles.core.feature.markdown.MarkdownParser
 import org.futo.circles.model.*
 import org.futo.circles.view.PostLayout
 import org.futo.circles.view.PostOptionsListener
@@ -31,7 +31,6 @@ sealed class PostViewHolder(view: View, private val isThread: Boolean) :
     RecyclerView.ViewHolder(view) {
 
     abstract val postLayout: PostLayout
-    protected val markwon = MarkdownParser.markwonBuilder(context)
 
     open fun bind(post: Post, userPowerLevel: Int) {
         postLayout.setData(post, userPowerLevel, isThread)
@@ -87,7 +86,7 @@ class TextMediaPostViewHolder(
 
     private fun bindTextPost(content: TextContent) {
         binding.tvTextContent.apply {
-            setText(markwon.toMarkdown(content.message), TextView.BufferType.SPANNABLE)
+            setText(content.message, TextView.BufferType.SPANNABLE)
             visible()
         }
         binding.vMediaContent.lMedia.gone()
@@ -104,7 +103,7 @@ class TextMediaPostViewHolder(
         binding.tvTextContent.apply {
             val caption = content.caption
             setIsVisible(caption != null)
-            caption?.let { setText(markwon.toMarkdown(it), TextView.BufferType.SPANNABLE) }
+            caption?.let { setText(it, TextView.BufferType.SPANNABLE) }
         }
     }
 
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt
index 37b629d3a..845be5b72 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt
+++ b/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt
@@ -66,7 +66,7 @@ class CreatePostDialogFragment :
                 PostContentType.IMAGE_CONTENT, PostContentType.VIDEO_CONTENT ->
                     binding.vPostPreview.setMediaFromExistingPost(it as MediaContent)
 
-                else -> binding.vPostPreview.setText((it as TextContent).message)
+                else -> binding.vPostPreview.setText((it as TextContent).message.toString())
             }
         }
     }
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt
deleted file mode 100644
index c330c3167..000000000
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.futo.circles.feature.timeline.post.markdown
-
-
-import android.text.Selection
-import android.text.Spannable
-import android.text.method.ArrowKeyMovementMethod
-import android.text.method.MovementMethod
-import android.text.style.ClickableSpan
-import android.view.MotionEvent
-import android.widget.TextView
-
-
-class EnhancedMovementMethod : ArrowKeyMovementMethod() {
-    private var sInstance: EnhancedMovementMethod? = null
-
-    fun getsInstance(): MovementMethod? {
-        if (sInstance == null) {
-            sInstance = EnhancedMovementMethod()
-        }
-        return sInstance
-    }
-
-    override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
-        val action = event.action
-        if (action == MotionEvent.ACTION_UP ||
-            action == MotionEvent.ACTION_DOWN
-        ) {
-            var x = event.x.toInt()
-            var y = event.y.toInt()
-            x -= widget.totalPaddingLeft
-            y -= widget.totalPaddingTop
-            x += widget.scrollX
-            y += widget.scrollY
-            val layout = widget.layout
-            val line = layout.getLineForVertical(y)
-            val off = layout.getOffsetForHorizontal(line, x.toFloat())
-            val link = buffer.getSpans(
-                off, off,
-                ClickableSpan::class.java
-            )
-            if (link.isNotEmpty()) {
-                if (action == MotionEvent.ACTION_UP) {
-                    if (x < layout.getLineMax(0)) {
-                        link[0].onClick(widget)
-                    }
-                } else {
-                    Selection.setSelection(
-                        buffer,
-                        buffer.getSpanStart(link[0]),
-                        buffer.getSpanEnd(link[0])
-                    )
-                }
-                return true
-            }
-        }
-        return super.onTouchEvent(widget, buffer, event)
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt
deleted file mode 100644
index fc008d79e..000000000
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.futo.circles.feature.timeline.post.markdown.span
-
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.text.Layout
-import android.text.style.LeadingMarginSpan
-import io.noties.markwon.core.MarkwonTheme
-import io.noties.markwon.utils.LeadingMarginUtils
-
-
-class OrderedListItemSpan(
-    private val theme: MarkwonTheme,
-    val number: String
-) : LeadingMarginSpan {
-    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
-
-    private var margin = 0
-    override fun getLeadingMargin(first: Boolean): Int = margin.coerceAtLeast(theme.blockMargin)
-
-    override fun drawLeadingMargin(
-        c: Canvas,
-        p: Paint,
-        x: Int,
-        dir: Int,
-        top: Int,
-        baseline: Int,
-        bottom: Int,
-        text: CharSequence,
-        start: Int,
-        end: Int,
-        first: Boolean,
-        layout: Layout
-    ) {
-        if (!first || !LeadingMarginUtils.selfStart(start, text, this)) return
-
-        paint.set(p)
-        theme.applyListItemStyle(paint)
-
-        val numberWidth = (paint.measureText(number) + .5f).toInt()
-
-        var width = theme.blockMargin
-        if (numberWidth > width) {
-            width = numberWidth
-            margin = numberWidth
-        } else {
-            margin = 0
-        }
-        val left: Int = if (dir > 0) {
-            x + width * dir - numberWidth
-        } else {
-            x + width * dir + (width - numberWidth)
-        }
-
-        c.drawText(number, left.toFloat(), baseline.toFloat(), paint)
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt
deleted file mode 100644
index 1e06a1b9f..000000000
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.futo.circles.feature.timeline.post.markdown.span
-
-import android.text.style.StrikethroughSpan
-import io.noties.markwon.core.spans.BulletListItemSpan
-import io.noties.markwon.core.spans.EmphasisSpan
-import io.noties.markwon.core.spans.LinkSpan
-import io.noties.markwon.core.spans.StrongEmphasisSpan
-import io.noties.markwon.ext.tasklist.TaskListSpan
-
-enum class TextStyle {
-    BOLD,
-    ITALIC,
-    STRIKE,
-    LINK,
-    UNORDERED_LIST,
-    ORDERED_LIST,
-    TASKS_LIST
-}
-
-fun TextStyle.toSpanClass() = when (this) {
-    TextStyle.BOLD -> StrongEmphasisSpan::class.java
-    TextStyle.ITALIC -> EmphasisSpan::class.java
-    TextStyle.STRIKE -> StrikethroughSpan::class.java
-    TextStyle.LINK -> LinkSpan::class.java
-    TextStyle.UNORDERED_LIST -> BulletListItemSpan::class.java
-    TextStyle.ORDERED_LIST -> OrderedListItemSpan::class.java
-    TextStyle.TASKS_LIST -> TaskListSpan::class.java
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/view/ComposerEditText.kt b/app/src/main/java/org/futo/circles/view/ComposerEditText.kt
deleted file mode 100644
index 8dba74e1c..000000000
--- a/app/src/main/java/org/futo/circles/view/ComposerEditText.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.futo.circles.view
-
-import android.content.Context
-import android.net.Uri
-import android.os.Build
-import android.text.Editable
-import android.util.AttributeSet
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputConnection
-import androidx.annotation.RequiresApi
-import androidx.appcompat.widget.AppCompatEditText
-import androidx.core.view.ViewCompat
-import androidx.core.view.inputmethod.EditorInfoCompat
-import androidx.core.view.inputmethod.InputConnectionCompat
-import timber.log.Timber
-
-class ComposerEditText @JvmOverloads constructor(
-        context: Context,
-        attrs: AttributeSet? = null,
-        defStyleAttr: Int = android.R.attr.editTextStyle
-) : AppCompatEditText(context, attrs, defStyleAttr) {
-
-    interface Callback {
-        fun onRichContentSelected(contentUri: Uri): Boolean
-        fun onTextChanged(text: CharSequence)
-    }
-
-    var callback: Callback? = null
-
-    override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection? {
-        var ic = super.onCreateInputConnection(editorInfo) ?: return null
-        val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this) ?: arrayOf("image/*")
-
-        EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes)
-        ic = InputConnectionCompat.createWrapper(this, ic, editorInfo)
-
-        ViewCompat.setOnReceiveContentListener(
-                this,
-                mimeTypes,
-                UriContentListener { callback?.onRichContentSelected(it) }
-        )
-
-        return ic
-    }
-
-    /** Set whether the keyboard should disable personalized learning. */
-    @RequiresApi(Build.VERSION_CODES.O)
-    fun setUseIncognitoKeyboard(useIncognitoKeyboard: Boolean) {
-        imeOptions = if (useIncognitoKeyboard) {
-            imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
-        } else {
-            imeOptions and EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING.inv()
-        }
-    }
-
-    /** Set whether enter should send the message or add a new line. */
-    fun setSendMessageWithEnter(sendMessageWithEnter: Boolean) {
-        if (sendMessageWithEnter) {
-            inputType = inputType and EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE.inv()
-            imeOptions = imeOptions or EditorInfo.IME_ACTION_SEND
-        } else {
-            inputType = inputType or EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
-            imeOptions = imeOptions and EditorInfo.IME_ACTION_SEND.inv()
-        }
-    }
-
-    init {
-        addTextChangedListener(
-                object : SimpleTextWatcher() {
-                    var spanToRemove: PillImageSpan? = null
-
-                    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
-                        Timber.v("Pills: beforeTextChanged: start:$start count:$count after:$after")
-
-                        if (count > after) {
-                            // A char has been deleted
-                            val deleteCharPosition = start + count
-                            Timber.v("Pills: beforeTextChanged: deleted char at $deleteCharPosition")
-
-                            // Get the first span at this position
-                            spanToRemove = editableText.getSpans(deleteCharPosition, deleteCharPosition, PillImageSpan::class.java)
-                                    .ooi { Timber.v("Pills: beforeTextChanged: found ${it.size} span(s)") }
-                                    .firstOrNull()
-                        }
-                    }
-
-                    override fun afterTextChanged(s: Editable) {
-                        if (spanToRemove != null) {
-                            val start = editableText.getSpanStart(spanToRemove)
-                            val end = editableText.getSpanEnd(spanToRemove)
-                            Timber.v("Pills: afterTextChanged Removing the span start:$start end:$end")
-                            // Must be done before text replacement
-                            editableText.removeSpan(spanToRemove)
-                            if (start != -1 && end != -1) {
-                                editableText.replace(start, end, "")
-                            }
-                            spanToRemove = null
-                        }
-                        callback?.onTextChanged(s.toString())
-                    }
-                }
-        )
-    }
-}
-
-inline fun <T> T.ooi(block: (T) -> Unit): T = also(block)
diff --git a/app/src/main/java/org/futo/circles/view/PillImageSpan.kt b/app/src/main/java/org/futo/circles/view/PillImageSpan.kt
deleted file mode 100644
index ba4a0f78d..000000000
--- a/app/src/main/java/org/futo/circles/view/PillImageSpan.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.futo.circles.view
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.text.TextUtils
-import android.text.style.ReplacementSpan
-import android.widget.TextView
-import androidx.annotation.UiThread
-import androidx.appcompat.widget.ThemeUtils
-import androidx.core.content.ContextCompat
-import com.google.android.material.chip.ChipDrawable
-import org.futo.circles.R
-import org.futo.circles.core.glide.GlideRequests
-import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
-import org.matrix.android.sdk.api.util.MatrixItem
-import java.lang.ref.WeakReference
-
-/**
- * This span is able to replace a text by a [ChipDrawable]
- * It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached.
- * Implements MatrixItemSpan so that it could be automatically transformed in matrix links and displayed as pills.
- */
-class PillImageSpan(
-    private val glideRequests: GlideRequests,
-    private val context: Context,
-    override val matrixItem: MatrixItem
-) : ReplacementSpan(), MatrixItemSpan {
-
-    private val pillDrawable = createChipDrawable()
-   // private val target = PillImageSpanTarget(this)
-    private var tv: WeakReference<TextView>? = null
-
-    @UiThread
-    fun bind(textView: TextView) {
-        tv = WeakReference(textView)
-        //avatarRenderer.render(glideRequests, matrixItem, target)
-    }
-
-    // ReplacementSpan *****************************************************************************
-
-    override fun getSize(
-        paint: Paint, text: CharSequence,
-        start: Int,
-        end: Int,
-        fm: Paint.FontMetricsInt?
-    ): Int {
-        val rect = pillDrawable.bounds
-        if (fm != null) {
-            val fmPaint = paint.fontMetricsInt
-            val fontHeight = fmPaint.bottom - fmPaint.top
-            val drHeight = rect.bottom - rect.top
-            val top = drHeight / 2 - fontHeight / 4
-            val bottom = drHeight / 2 + fontHeight / 4
-            fm.ascent = -bottom
-            fm.top = -bottom
-            fm.bottom = top
-            fm.descent = top
-        }
-        return rect.right
-    }
-
-    override fun draw(
-        canvas: Canvas, text: CharSequence,
-        start: Int,
-        end: Int,
-        x: Float,
-        top: Int,
-        y: Int,
-        bottom: Int,
-        paint: Paint
-    ) {
-        canvas.save()
-        val fm = paint.fontMetricsInt
-        val transY: Int = y + (fm.descent + fm.ascent - pillDrawable.bounds.bottom) / 2
-        canvas.save()
-        canvas.translate(x, transY.toFloat())
-
-        val rect = Rect()
-        canvas.getClipBounds(rect)
-        val maxWidth = rect.right
-        if (pillDrawable.intrinsicWidth > maxWidth) {
-            pillDrawable.setBounds(0, 0, maxWidth, pillDrawable.intrinsicHeight)
-            pillDrawable.ellipsize = TextUtils.TruncateAt.END
-        }
-
-        pillDrawable.draw(canvas)
-        canvas.restore()
-    }
-
-    internal fun updateAvatarDrawable(drawable: Drawable?) {
-        pillDrawable.chipIcon = drawable
-        tv?.get()?.invalidate()
-    }
-
-    // Private methods *****************************************************************************
-
-    private fun createChipDrawable(): ChipDrawable {
-        val textPadding = context.resources.getDimension(R.dimen.divider_height)
-        val icon = when {
-//            matrixItem is MatrixItem.RoomAliasItem && matrixItem.avatarUrl.isNullOrEmpty() &&
-//                    matrixItem.displayName == context.getString(R.string.followed_by_format, matrixItem.id) -> {
-//                ContextCompat.getDrawable(context, R.drawable.ic_permalink_round)
-//            }
-//            matrixItem is MatrixItem.RoomItem && matrixItem.avatarUrl.isNullOrEmpty() && (
-//                    matrixItem.displayName == context.getString(R.string.pill_message_in_unknown_room) ||
-//                            matrixItem.displayName == context.getString(R.string.pill_message_unknown_room_or_space) ||
-//                            matrixItem.displayName == context.getString(R.string.pill_message_from_unknown_user)
-//                    ) -> {
-//                ContextCompat.getDrawable(context, R.drawable.ic_composer_bold)
-//            }
-            matrixItem is MatrixItem.UserItem && matrixItem.avatarUrl.isNullOrEmpty()  -> {
-                ContextCompat.getDrawable(context, R.drawable.ic_composer_bold)
-            }
-            else -> {
-                try {
-                  //  avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
-                } catch (exception: Exception) {
-                   // avatarRenderer.getPlaceholderDrawable(matrixItem)
-                }
-            }
-        }
-
-        return ChipDrawable.createFromResource(context, R.xml.bg_chip).apply {
-            text = "matrixItem.getBestName()"
-            textEndPadding = textPadding
-            textStartPadding = textPadding
-            setChipMinHeightResource(R.dimen.rich_text_composer_corner_radius_expanded)
-            setChipIconSizeResource(R.dimen.rich_text_composer_corner_radius_expanded)
-            //chipIcon = icon
-            if (matrixItem is MatrixItem.EveryoneInRoomItem) {
-                // setTextColor API does not exist right now for ChipDrawable, use textAppearance
-            }
-            setBounds(0, 0, intrinsicWidth, intrinsicHeight)
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/view/PostLayout.kt b/app/src/main/java/org/futo/circles/view/PostLayout.kt
index 40f74e3a9..808ffc9dd 100644
--- a/app/src/main/java/org/futo/circles/view/PostLayout.kt
+++ b/app/src/main/java/org/futo/circles/view/PostLayout.kt
@@ -17,7 +17,7 @@ import org.futo.circles.core.model.Post
 import org.futo.circles.core.model.PostContent
 import org.futo.circles.core.model.TextContent
 import org.futo.circles.databinding.LayoutPostBinding
-import org.futo.circles.feature.timeline.post.markdown.MarkdownParser
+import org.futo.circles.core.feature.markdown.MarkdownParser
 import org.futo.circles.model.PostItemPayload
 import org.matrix.android.sdk.api.session.room.send.SendState
 
@@ -114,10 +114,10 @@ class PostLayout(
     private fun setMentionBorder(content: PostContent) {
         val hasMention = when (content) {
             is MediaContent -> content.caption?.let {
-                MarkdownParser.hasCurrentUserMention(it)
+                MarkdownParser.hasCurrentUserMention(it.toString())
             } ?: false
 
-            is TextContent -> MarkdownParser.hasCurrentUserMention(content.message)
+            is TextContent -> MarkdownParser.hasCurrentUserMention(content.message.toString())
             is PollContent -> false
         }
         if (hasMention) binding.lCard.setBackgroundResource(R.drawable.bg_mention_highlight)
diff --git a/app/src/main/java/org/futo/circles/view/PreviewPostView.kt b/app/src/main/java/org/futo/circles/view/PreviewPostView.kt
index 785cb745d..7b9946bd3 100644
--- a/app/src/main/java/org/futo/circles/view/PreviewPostView.kt
+++ b/app/src/main/java/org/futo/circles/view/PreviewPostView.kt
@@ -6,10 +6,12 @@ import android.content.Context
 import android.graphics.drawable.ColorDrawable
 import android.net.Uri
 import android.text.Editable
+import android.text.Spanned
 import android.util.AttributeSet
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
+import android.view.inputmethod.InputMethodManager
 import android.widget.LinearLayout
 import androidx.annotation.DrawableRes
 import androidx.cardview.widget.CardView
@@ -39,9 +41,9 @@ import org.futo.circles.core.utils.VideoUtils.getVideoDurationString
 import org.futo.circles.databinding.ViewPreviewPostBinding
 import org.futo.circles.databinding.ViewRichTextMenuButtonBinding
 import org.futo.circles.extensions.convertDpToPixel
-import org.futo.circles.extensions.showKeyboard
 import org.futo.circles.feature.timeline.post.create.PreviewPostListener
-import org.futo.circles.feature.timeline.post.markdown.mentions.MentionsPresenter
+import org.futo.circles.core.feature.markdown.mentions.MentionsPresenter
+import org.futo.circles.core.feature.markdown.span.MentionSpan
 import org.futo.circles.model.CreatePostContent
 import org.futo.circles.model.MediaPostContent
 import org.futo.circles.model.TextPostContent
@@ -87,8 +89,6 @@ class PreviewPostView(
             doAfterTextChanged {
                 binding.btnSend.isEnabled = it?.toString()?.isNotBlank() == true
             }
-            setShadowLayer(paddingBottom.toFloat(), 0f, 0f, 0)
-            disallowParentInterceptTouchEvent(this)
         }
         setupRichTextMenu()
     }
@@ -130,10 +130,10 @@ class PreviewPostView(
     fun setMediaFromExistingPost(mediaContent: MediaContent) {
         canEditMedia = false
         val caption = mediaContent.caption ?: ""
-        setText(caption)
+        setText(caption.toString())
         val uri = Uri.parse(mediaContent.mediaFileData.fileUrl)
         val mediaType = mediaContent.getMediaType()
-        postContent = MediaPostContent(caption, uri, mediaType)
+        postContent = MediaPostContent(caption.toString(), uri, mediaType)
         updateContentView()
         loadMediaCover(mediaContent)
         val isVideo = mediaType == MediaType.Video
@@ -235,14 +235,19 @@ class PreviewPostView(
     }
 
     private fun requestFocusOnText() {
-        with(binding.etTextPost) {
-            this.post {
-                showKeyboard(true)
-                text?.length?.let { setSelection(it) }
-            }
+        binding.etTextPost.post {
+            requestFocus()
+            binding.etTextPost.text?.let { binding.etTextPost.setSelection(it.length) }
+            showKeyboard()
         }
     }
 
+    private fun showKeyboard() {
+        val inputMethodManager: InputMethodManager =
+            context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        inputMethodManager.showSoftInput(binding.etTextPost, 0)
+    }
+
     private fun getMyUser(): User? {
         val session = MatrixSessionProvider.currentSession
         return session?.myUserId?.let { session.getUser(it) }
@@ -257,7 +262,7 @@ class PreviewPostView(
         addMenuItem(binding.lMainMenu, R.drawable.ic_emoji) {
             listener?.onEmojiClicked()
         }
-        addMenuItem(binding.lMainMenu, R.drawable.ic_mention) {
+        addMenuItem(binding.lMainMenu, org.futo.circles.core.R.drawable.ic_mention) {
             binding.etTextPost.append("@")
         }
         addMenuItem(binding.lMainMenu, R.drawable.ic_link) {
diff --git a/app/src/main/res/layout/view_preview_post.xml b/app/src/main/res/layout/view_preview_post.xml
index 858792b93..f7fafaf9e 100644
--- a/app/src/main/res/layout/view_preview_post.xml
+++ b/app/src/main/res/layout/view_preview_post.xml
@@ -61,7 +61,7 @@
                 android:hint="@string/enter_your_message_here"
                 app:bulletGap="8sp"
                 app:bulletRadius="4sp"
-                app:codeBlockBackgroundDrawable="@drawable/bg_code_block"
+                app:pillBackgroundColor="@color/white"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent"
diff --git a/core/build.gradle b/core/build.gradle
index cca12aac7..eaf7e8a60 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -95,6 +95,14 @@ dependencies {
     //QR
     api 'com.google.zxing:core:3.5.2'
 
+    //Markdown
+    def markwon_version = "4.6.2"
+    api "io.noties.markwon:core:$markwon_version"
+    api "io.noties.markwon:linkify:$markwon_version"
+    api "io.noties.markwon:ext-strikethrough:$markwon_version"
+    api "io.noties.markwon:ext-tasklist:$markwon_version"
+    api "io.element.android:wysiwyg:2.18.0"
+
     //Shake detection
     implementation 'com.squareup:seismic:1.0.3'
 
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/MarkdownParser.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/MarkdownParser.kt
similarity index 94%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/MarkdownParser.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/MarkdownParser.kt
index b867c69b9..38040c8b6 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/MarkdownParser.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/MarkdownParser.kt
@@ -1,4 +1,4 @@
-package org.futo.circles.feature.timeline.post.markdown
+package org.futo.circles.core.feature.markdown
 
 import android.content.Context
 import android.graphics.Typeface
@@ -13,7 +13,7 @@ import org.commonmark.node.Emphasis
 import org.commonmark.node.StrongEmphasis
 import org.futo.circles.core.extensions.notEmptyDisplayName
 import org.futo.circles.core.provider.MatrixSessionProvider
-import org.futo.circles.feature.timeline.post.markdown.mentions.plugin.MentionPlugin
+import org.futo.circles.core.feature.markdown.mentions.plugin.MentionPlugin
 import org.matrix.android.sdk.api.session.getUserOrDefault
 
 
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsAdapter.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsAdapter.kt
similarity index 91%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsAdapter.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsAdapter.kt
index f2d8de199..43a4416fc 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsAdapter.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsAdapter.kt
@@ -1,4 +1,4 @@
-package org.futo.circles.feature.timeline.post.markdown.mentions
+package org.futo.circles.core.feature.markdown.mentions
 
 import android.view.ViewGroup
 import org.futo.circles.core.base.list.BaseRvAdapter
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsPresenter.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsPresenter.kt
similarity index 96%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsPresenter.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsPresenter.kt
index 1024ed548..bedc93b4a 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsPresenter.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsPresenter.kt
@@ -1,4 +1,4 @@
-package org.futo.circles.feature.timeline.post.markdown.mentions
+package org.futo.circles.core.feature.markdown.mentions
 
 import android.content.Context
 import androidx.recyclerview.widget.RecyclerView
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionDelimiterProcessor.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionDelimiterProcessor.kt
similarity index 93%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionDelimiterProcessor.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionDelimiterProcessor.kt
index 66c2bbf46..817dc7d4d 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionDelimiterProcessor.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionDelimiterProcessor.kt
@@ -1,4 +1,4 @@
-package org.futo.circles.feature.timeline.post.markdown.mentions.plugin
+package org.futo.circles.core.feature.markdown.mentions.plugin
 
 import org.commonmark.node.Node
 import org.commonmark.node.Text
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionNode.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionNode.kt
similarity index 72%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionNode.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionNode.kt
index 4e0274255..6aca6a1a2 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionNode.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionNode.kt
@@ -1,4 +1,4 @@
-package org.futo.circles.feature.timeline.post.markdown.mentions.plugin
+package org.futo.circles.core.feature.markdown.mentions.plugin
 
 import org.commonmark.node.CustomNode
 import org.commonmark.node.Visitor
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionPlugin.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionPlugin.kt
similarity index 88%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionPlugin.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionPlugin.kt
index 797fc787e..2a2b1ff6b 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionPlugin.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionPlugin.kt
@@ -1,11 +1,11 @@
-package org.futo.circles.feature.timeline.post.markdown.mentions.plugin
+package org.futo.circles.core.feature.markdown.mentions.plugin
 
 import android.content.Context
 import io.noties.markwon.AbstractMarkwonPlugin
 import io.noties.markwon.MarkwonVisitor
 import io.noties.markwon.SpannableBuilder
 import org.commonmark.parser.Parser
-import org.futo.circles.feature.timeline.post.markdown.span.MentionSpan
+import org.futo.circles.core.feature.markdown.span.MentionSpan
 
 
 class MentionPlugin(private val context: Context) : AbstractMarkwonPlugin() {
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/MentionSpan.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/span/MentionSpan.kt
similarity index 87%
rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/MentionSpan.kt
rename to core/src/main/java/org/futo/circles/core/feature/markdown/span/MentionSpan.kt
index 04f900ea4..bb3313815 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/MentionSpan.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/markdown/span/MentionSpan.kt
@@ -1,11 +1,11 @@
-package org.futo.circles.feature.timeline.post.markdown.span
+package org.futo.circles.core.feature.markdown.span
 
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.text.style.DynamicDrawableSpan
 import androidx.core.content.ContextCompat
 import com.google.android.material.chip.ChipDrawable
-import org.futo.circles.R
+import org.futo.circles.core.R
 
 class MentionSpan(
     private val context: Context,
diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/builder/MultiTimelineBuilder.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/builder/MultiTimelineBuilder.kt
index 1e9d80c71..fd857635f 100644
--- a/core/src/main/java/org/futo/circles/core/feature/timeline/builder/MultiTimelineBuilder.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/timeline/builder/MultiTimelineBuilder.kt
@@ -1,5 +1,8 @@
 package org.futo.circles.core.feature.timeline.builder
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import org.futo.circles.core.feature.markdown.MarkdownParser
 import org.futo.circles.core.mapping.toPost
 import org.futo.circles.core.model.Post
 import org.futo.circles.core.provider.MatrixSessionProvider
@@ -7,16 +10,19 @@ import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import javax.inject.Inject
 
-class MultiTimelineBuilder @Inject constructor() : BaseTimelineBuilder() {
+class MultiTimelineBuilder @Inject constructor(
+    @ApplicationContext context: Context
+) : BaseTimelineBuilder() {
 
     private var currentSnapshotMap: MutableMap<String, List<Post>> = mutableMapOf()
     private var readReceiptMap: MutableMap<String, List<Long>> = mutableMapOf()
+    private val markwon = MarkdownParser.markwonBuilder(context)
 
     override fun List<TimelineEvent>.processSnapshot(isThread: Boolean): List<Post> {
         val roomId = firstOrNull()?.roomId ?: return emptyList()
         val room = MatrixSessionProvider.currentSession?.getRoom(roomId) ?: return emptyList()
         val receipts = getReadReceipts(room).also { readReceiptMap[roomId] = it }
-        currentSnapshotMap[roomId] = this.map { it.toPost(receipts) }
+        currentSnapshotMap[roomId] = this.map { it.toPost(markwon, receipts) }
         val fullTimelineEventList = currentSnapshotMap.flatMap { (_, value) -> value }
         return sortList(fullTimelineEventList, isThread)
     }
diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/builder/SingleTimelineBuilder.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/builder/SingleTimelineBuilder.kt
index 019b399b1..9684e294a 100644
--- a/core/src/main/java/org/futo/circles/core/feature/timeline/builder/SingleTimelineBuilder.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/timeline/builder/SingleTimelineBuilder.kt
@@ -1,5 +1,8 @@
 package org.futo.circles.core.feature.timeline.builder
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import org.futo.circles.core.feature.markdown.MarkdownParser
 import org.futo.circles.core.mapping.toPost
 import org.futo.circles.core.model.Post
 import org.futo.circles.core.provider.MatrixSessionProvider
@@ -7,13 +10,17 @@ import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import javax.inject.Inject
 
-class SingleTimelineBuilder @Inject constructor() : BaseTimelineBuilder() {
+class SingleTimelineBuilder @Inject constructor(
+    @ApplicationContext context: Context
+) : BaseTimelineBuilder() {
+
+    private val markwon = MarkdownParser.markwonBuilder(context)
 
     override fun List<TimelineEvent>.processSnapshot(isThread: Boolean): List<Post> {
         val room = MatrixSessionProvider.currentSession?.getRoom(firstOrNull()?.roomId ?: "")
             ?: return emptyList()
         val receipts = getReadReceipts(room)
-        return sortList(this.map { it.toPost(receipts) }, isThread)
+        return sortList(this.map { it.toPost(markwon, receipts) }, isThread)
     }
 
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt
index 82715bea1..79975d7b5 100644
--- a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt
@@ -1,13 +1,15 @@
 package org.futo.circles.core.feature.timeline.data_source
 
+import android.content.Context
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.SavedStateHandle
+import dagger.hilt.android.qualifiers.ApplicationContext
 import org.futo.circles.core.extensions.getOrThrow
-import org.futo.circles.core.model.Post
-import org.futo.circles.core.provider.MatrixSessionProvider
 import org.futo.circles.core.feature.timeline.builder.BaseTimelineBuilder
 import org.futo.circles.core.feature.timeline.builder.MultiTimelineBuilder
 import org.futo.circles.core.feature.timeline.builder.SingleTimelineBuilder
+import org.futo.circles.core.model.Post
+import org.futo.circles.core.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -21,10 +23,16 @@ abstract class BaseTimelineDataSource(
     private val timelineBuilder: BaseTimelineBuilder
 ) : Timeline.Listener {
 
-    class Factory @Inject constructor(private val savedStateHandle: SavedStateHandle) {
+    class Factory @Inject constructor(
+        private val savedStateHandle: SavedStateHandle,
+        @ApplicationContext private val context: Context
+    ) {
         fun create(isMultiTimelines: Boolean): BaseTimelineDataSource =
-            if (isMultiTimelines) MultiTimelinesDataSource(savedStateHandle, MultiTimelineBuilder())
-            else SingleTimelineDataSource(savedStateHandle, SingleTimelineBuilder())
+            if (isMultiTimelines) MultiTimelinesDataSource(
+                savedStateHandle,
+                MultiTimelineBuilder(context)
+            )
+            else SingleTimelineDataSource(savedStateHandle, SingleTimelineBuilder(context))
     }
 
     protected val roomId: String = savedStateHandle.getOrThrow("roomId")
diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt
index aae616280..e8442ebb6 100644
--- a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt
@@ -1,6 +1,9 @@
 package org.futo.circles.core.feature.timeline.post
 
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.android.scopes.ViewModelScoped
+import org.futo.circles.core.feature.markdown.MarkdownParser
 import org.futo.circles.core.mapping.toPost
 import org.futo.circles.core.model.Post
 import org.futo.circles.core.model.PostContent
@@ -11,14 +14,17 @@ import javax.inject.Inject
 
 
 @ViewModelScoped
-class PostContentDataSource @Inject constructor() {
+class PostContentDataSource @Inject constructor(
+    @ApplicationContext context: Context
+) {
 
     private val session = MatrixSessionProvider.currentSession
+    private val markwon = MarkdownParser.markwonBuilder(context)
 
     fun getPost(roomId: String, eventId: String): Post? {
         val roomForMessage = session?.getRoom(roomId)
         val timelineEvent = roomForMessage?.getTimelineEvent(eventId) ?: return null
-        return timelineEvent.toPost()
+        return timelineEvent.toPost(markwon)
     }
 
     fun getPostContent(roomId: String, eventId: String): PostContent? =
diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt
index 7db832ffd..bae01ac4f 100644
--- a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt
@@ -44,7 +44,7 @@ class PostOptionsDataSource @Inject constructor(
     suspend fun getShareableContent(content: PostContent): ShareableContent? = onBG {
         when (content) {
             is MediaContent -> getShareableMediaContent(content.mediaFileData)
-            is TextContent -> TextShareable(content.message)
+            is TextContent -> TextShareable(content.message.toString())
             else -> throw IllegalArgumentException("Not shareable post content")
         }
     }
diff --git a/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt
index d6aeb3833..36b694c50 100644
--- a/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt
+++ b/core/src/main/java/org/futo/circles/core/mapping/MediaPostContentMapping.kt
@@ -1,6 +1,7 @@
 package org.futo.circles.core.mapping
 
 import com.bumptech.glide.request.target.Target
+import io.noties.markwon.Markwon
 import org.futo.circles.core.base.MediaCaptionFieldKey
 import org.futo.circles.core.model.MediaContent
 import org.futo.circles.core.model.MediaFileData
@@ -15,18 +16,18 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileName
 import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 
-fun TimelineEvent.toMediaContent(mediaType: MediaType): MediaContent = MediaContent(
+fun TimelineEvent.toMediaContent(mediaType: MediaType, markwon: Markwon): MediaContent = MediaContent(
     type = if (mediaType == MediaType.Image) PostContentType.IMAGE_CONTENT else PostContentType.VIDEO_CONTENT,
-    caption = getCaption(),
+    caption = getCaption(markwon),
     mediaFileData = toMediaFileData(mediaType),
     thumbnailFileData = toThumbnailFileData(mediaType),
     thumbHash = getThumbHash(mediaType)
 )
 
-private fun TimelineEvent.getCaption(): String? {
+private fun TimelineEvent.getCaption(markwon: Markwon): CharSequence? {
     val lastContent =
         annotations?.editSummary?.latestEdit?.getClearContent() ?: root.getClearContent()
-    return lastContent?.get(MediaCaptionFieldKey)?.toString()
+    return lastContent?.get(MediaCaptionFieldKey)?.toString()?.let { markwon.toMarkdown(it) }
 }
 
 private fun TimelineEvent.getThumbHash(mediaType: MediaType) = when (mediaType) {
diff --git a/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt
index a8104f4eb..95634a367 100644
--- a/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt
+++ b/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt
@@ -1,9 +1,13 @@
 package org.futo.circles.core.mapping
 
+import io.noties.markwon.Markwon
 import org.futo.circles.core.model.TextContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
 
-fun TimelineEvent.toTextContent(): TextContent = TextContent(
-    message = getTextEditableContent(false)
-)
\ No newline at end of file
+fun TimelineEvent.toTextContent(markwon: Markwon): TextContent = TextContent(
+    message = markwon.toMarkdown(getTextEditableContent(false))
+
+)
+
+
diff --git a/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt
index c024e6572..8a5d87b14 100644
--- a/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt
+++ b/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt
@@ -1,5 +1,6 @@
 package org.futo.circles.core.mapping
 
+import io.noties.markwon.Markwon
 import org.futo.circles.core.extensions.getPostContentType
 import org.futo.circles.core.model.MediaType
 import org.futo.circles.core.model.Post
@@ -10,9 +11,9 @@ import org.futo.circles.core.model.ReactionsData
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
 
-fun TimelineEvent.toPost(readReceipts: List<Long> = emptyList()): Post = Post(
+fun TimelineEvent.toPost(markwon: Markwon, readReceipts: List<Long> = emptyList()): Post = Post(
     postInfo = toPostInfo(),
-    content = toPostContent(),
+    content = toPostContent(markwon),
     sendState = root.sendState,
     readByCount = getReadByCount(readReceipts),
     repliesCount = root.threadDetails?.numberOfThreads ?: 0,
@@ -30,13 +31,14 @@ private fun TimelineEvent.toPostInfo(): PostInfo = PostInfo(
     isEdited = hasBeenEdited()
 )
 
-private fun TimelineEvent.toPostContent(): PostContent = when (getPostContentType()) {
-    PostContentType.TEXT_CONTENT -> toTextContent()
-    PostContentType.IMAGE_CONTENT -> toMediaContent(MediaType.Image)
-    PostContentType.VIDEO_CONTENT -> toMediaContent(MediaType.Video)
-    PostContentType.POLL_CONTENT -> toPollContent()
-    else -> toTextContent()
-}
+private fun TimelineEvent.toPostContent(markwon: Markwon): PostContent =
+    when (getPostContentType()) {
+        PostContentType.TEXT_CONTENT -> toTextContent(markwon)
+        PostContentType.IMAGE_CONTENT -> toMediaContent(MediaType.Image, markwon)
+        PostContentType.VIDEO_CONTENT -> toMediaContent(MediaType.Video, markwon)
+        PostContentType.POLL_CONTENT -> toPollContent()
+        else -> toTextContent(markwon)
+    }
 
 private fun TimelineEvent.getReadByCount(receipts: List<Long>): Int {
     val eventTime = root.originServerTs ?: 0
diff --git a/core/src/main/java/org/futo/circles/core/model/PostContent.kt b/core/src/main/java/org/futo/circles/core/model/PostContent.kt
index 7fd70f87f..8db795efd 100644
--- a/core/src/main/java/org/futo/circles/core/model/PostContent.kt
+++ b/core/src/main/java/org/futo/circles/core/model/PostContent.kt
@@ -18,12 +18,12 @@ sealed class PostContent(open val type: PostContentType) {
 }
 
 data class TextContent(
-    val message: String
+    val message: CharSequence
 ) : PostContent(PostContentType.TEXT_CONTENT)
 
 data class MediaContent(
     override val type: PostContentType,
-    val caption: String?,
+    val caption: CharSequence?,
     val mediaFileData: MediaFileData,
     val thumbnailFileData: MediaFileData?,
     val thumbHash: String?
diff --git a/app/src/main/res/drawable/ic_mention.xml b/core/src/main/res/drawable/ic_mention.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_mention.xml
rename to core/src/main/res/drawable/ic_mention.xml
diff --git a/app/src/main/res/xml/bg_chip.xml b/core/src/main/res/xml/bg_chip.xml
similarity index 100%
rename from app/src/main/res/xml/bg_chip.xml
rename to core/src/main/res/xml/bg_chip.xml
-- 
GitLab