Merge branch 'develop' into feature/fga/draft_support
This commit is contained in:
commit
1b56d1b97a
485 changed files with 2939 additions and 1591 deletions
|
|
@ -52,9 +52,9 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
|
|||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetailsProvider
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
|
|
@ -70,7 +70,7 @@ import io.element.android.libraries.textcomposer.components.VoiceMessageRecordin
|
|||
import io.element.android.libraries.textcomposer.components.markdown.MarkdownTextInput
|
||||
import io.element.android.libraries.textcomposer.components.markdown.aMarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
|
||||
import io.element.android.libraries.textcomposer.mentions.rememberMentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.Suggestion
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
|
|
@ -93,7 +93,6 @@ fun TextComposer(
|
|||
permalinkParser: PermalinkParser,
|
||||
composerMode: MessageComposerMode,
|
||||
enableVoiceMessages: Boolean,
|
||||
currentUserId: UserId,
|
||||
onRequestFocus: () -> Unit,
|
||||
onSendMessage: () -> Unit,
|
||||
onResetComposerMode: () -> Unit,
|
||||
|
|
@ -145,6 +144,8 @@ fun TextComposer(
|
|||
}
|
||||
}
|
||||
|
||||
val userProfileCache = LocalRoomMemberProfilesCache.current
|
||||
|
||||
val placeholder = if (composerMode.inThread) {
|
||||
stringResource(id = CommonStrings.action_reply_in_thread)
|
||||
} else {
|
||||
|
|
@ -154,17 +155,22 @@ fun TextComposer(
|
|||
is TextEditorState.Rich -> {
|
||||
remember(state.richTextEditorState, subcomposing, composerMode, onResetComposerMode, onError) {
|
||||
@Composable {
|
||||
val mentionSpanProvider = rememberMentionSpanProvider(
|
||||
currentUserId = currentUserId,
|
||||
permalinkParser = permalinkParser,
|
||||
)
|
||||
val mentionSpanProvider = LocalMentionSpanProvider.current
|
||||
TextInput(
|
||||
state = state.richTextEditorState,
|
||||
subcomposing = subcomposing,
|
||||
placeholder = placeholder,
|
||||
composerMode = composerMode,
|
||||
onResetComposerMode = onResetComposerMode,
|
||||
resolveMentionDisplay = { text, url -> TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url)) },
|
||||
resolveMentionDisplay = { text, url ->
|
||||
val permalinkData = permalinkParser.parse(url)
|
||||
if (permalinkData is PermalinkData.UserLink) {
|
||||
val displayNameOrId = userProfileCache.getDisplayName(permalinkData.userId) ?: permalinkData.userId.value
|
||||
TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(displayNameOrId, url))
|
||||
} else {
|
||||
TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor(text, url))
|
||||
}
|
||||
},
|
||||
resolveRoomMentionDisplay = { TextDisplay.Custom(mentionSpanProvider.getMentionSpanFor("@room", "#")) },
|
||||
onError = onError,
|
||||
onTyping = onTyping,
|
||||
|
|
@ -518,7 +524,6 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
|||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost"),
|
||||
)
|
||||
},
|
||||
{
|
||||
|
|
@ -527,7 +532,6 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
|||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
},
|
||||
{
|
||||
|
|
@ -541,7 +545,6 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
|||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
},
|
||||
{
|
||||
|
|
@ -550,7 +553,6 @@ internal fun TextComposerSimplePreview() = ElementPreview {
|
|||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -567,7 +569,6 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
|
|||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}, {
|
||||
ATextComposer(
|
||||
|
|
@ -576,7 +577,6 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
|
|||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}, {
|
||||
ATextComposer(
|
||||
|
|
@ -589,7 +589,6 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
|
|||
showTextFormatting = true,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
|
@ -603,7 +602,6 @@ internal fun TextComposerEditPreview() = ElementPreview {
|
|||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Edit(EventId("$1234"), TransactionId("1234"), "Some text"),
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
|
@ -617,7 +615,6 @@ internal fun MarkdownTextComposerEditPreview() = ElementPreview {
|
|||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Edit(EventId("$1234"), TransactionId("1234"), "Some text"),
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
|
@ -626,13 +623,12 @@ internal fun MarkdownTextComposerEditPreview() = ElementPreview {
|
|||
@Composable
|
||||
internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
|
||||
ATextComposer(
|
||||
TextEditorState.Rich(aRichTextEditorState()),
|
||||
state = TextEditorState.Rich(aRichTextEditorState()),
|
||||
voiceMessageState = VoiceMessageState.Idle,
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = inReplyToDetails,
|
||||
),
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -647,7 +643,6 @@ internal fun TextComposerVoicePreview() = ElementPreview {
|
|||
voiceMessageState = voiceMessageState,
|
||||
composerMode = MessageComposerMode.Normal,
|
||||
enableVoiceMessages = true,
|
||||
currentUserId = UserId("@alice:localhost")
|
||||
)
|
||||
PreviewColumn(items = persistentListOf({
|
||||
VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, createFakeWaveform()))
|
||||
|
|
@ -708,7 +703,6 @@ private fun ATextComposer(
|
|||
voiceMessageState: VoiceMessageState,
|
||||
composerMode: MessageComposerMode,
|
||||
enableVoiceMessages: Boolean,
|
||||
currentUserId: UserId,
|
||||
showTextFormatting: Boolean = false,
|
||||
) {
|
||||
TextComposer(
|
||||
|
|
@ -720,7 +714,6 @@ private fun ATextComposer(
|
|||
},
|
||||
composerMode = composerMode,
|
||||
enableVoiceMessages = enableVoiceMessages,
|
||||
currentUserId = currentUserId,
|
||||
onRequestFocus = {},
|
||||
onSendMessage = {},
|
||||
onResetComposerMode = {},
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ import android.graphics.Paint
|
|||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import android.text.style.ReplacementSpan
|
||||
import androidx.core.text.getSpans
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.wysiwyg.view.spans.CustomMentionSpan
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class MentionSpan(
|
||||
val text: String,
|
||||
text: String,
|
||||
val rawValue: String,
|
||||
val type: Type,
|
||||
val backgroundColor: Int,
|
||||
|
|
@ -39,23 +41,27 @@ class MentionSpan(
|
|||
private const val MAX_LENGTH = 20
|
||||
}
|
||||
|
||||
private var actualText: CharSequence? = null
|
||||
private var textWidth = 0
|
||||
private val backgroundPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = backgroundColor
|
||||
}
|
||||
|
||||
var text: String = text
|
||||
set(value) {
|
||||
field = value
|
||||
mentionText = getActualText(text)
|
||||
}
|
||||
|
||||
private var mentionText: CharSequence = getActualText(text)
|
||||
|
||||
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
|
||||
val mentionText = getActualText(this.text)
|
||||
paint.typeface = typeface
|
||||
textWidth = paint.measureText(mentionText, 0, mentionText.length).roundToInt()
|
||||
return textWidth + startPadding + endPadding
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
|
||||
val mentionText = getActualText(this.text)
|
||||
|
||||
// Extra vertical space to add below the baseline (y). This helps us center the span vertically
|
||||
val extraVerticalSpace = y + paint.ascent() + paint.descent() - top
|
||||
|
||||
|
|
@ -68,7 +74,6 @@ class MentionSpan(
|
|||
}
|
||||
|
||||
private fun getActualText(text: String): CharSequence {
|
||||
if (actualText != null) return actualText!!
|
||||
return buildString {
|
||||
val mentionText = text.orEmpty()
|
||||
when (type) {
|
||||
|
|
@ -87,7 +92,6 @@ class MentionSpan(
|
|||
if (mentionText.length > MAX_LENGTH) {
|
||||
append("…")
|
||||
}
|
||||
actualText = this
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,3 +100,18 @@ class MentionSpan(
|
|||
ROOM,
|
||||
}
|
||||
}
|
||||
|
||||
fun CharSequence.getMentionSpans(): List<MentionSpan> {
|
||||
return if (this is android.text.Spanned) {
|
||||
val customMentionSpans = getSpans<CustomMentionSpan>()
|
||||
if (customMentionSpans.isNotEmpty()) {
|
||||
// If we have custom mention spans created by the RTE, we need to extract the provided spans and filter them
|
||||
customMentionSpans.map { it.providedSpan }.filterIsInstance<MentionSpan>()
|
||||
} else {
|
||||
// Otherwise try to get the spans directly
|
||||
getSpans<MentionSpan>().toList()
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.textcomposer.mentions
|
|||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
|
|
@ -25,12 +26,16 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.text.buildSpannedString
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -40,7 +45,6 @@ import io.element.android.libraries.designsystem.theme.currentUserMentionPillTex
|
|||
import io.element.android.libraries.designsystem.theme.mentionPillBackground
|
||||
import io.element.android.libraries.designsystem.theme.mentionPillText
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
|
|
@ -48,22 +52,28 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Stable
|
||||
class MentionSpanProvider(
|
||||
private val currentSessionId: SessionId,
|
||||
class MentionSpanProvider @AssistedInject constructor(
|
||||
@Assisted private val currentSessionId: String,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private var currentUserTextColor: Int = 0,
|
||||
private var currentUserBackgroundColor: Int = Color.WHITE,
|
||||
private var otherTextColor: Int = 0,
|
||||
private var otherBackgroundColor: Int = Color.WHITE,
|
||||
) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(currentSessionId: String): MentionSpanProvider
|
||||
}
|
||||
|
||||
private val paddingValues = PaddingValues(start = 4.dp, end = 6.dp)
|
||||
|
||||
private val paddingValuesPx = mutableStateOf(0 to 0)
|
||||
private val typeface = mutableStateOf(Typeface.DEFAULT)
|
||||
|
||||
internal var currentUserTextColor: Int = 0
|
||||
internal var currentUserBackgroundColor: Int = Color.WHITE
|
||||
internal var otherTextColor: Int = 0
|
||||
internal var otherBackgroundColor: Int = Color.WHITE
|
||||
|
||||
@Suppress("ComposableNaming")
|
||||
@Composable
|
||||
internal fun setup() {
|
||||
fun updateStyles() {
|
||||
currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb()
|
||||
currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb()
|
||||
otherTextColor = ElementTheme.colors.mentionPillText.toArgb()
|
||||
|
|
@ -82,7 +92,7 @@ class MentionSpanProvider(
|
|||
val (startPaddingPx, endPaddingPx) = paddingValuesPx.value
|
||||
return when {
|
||||
permalinkData is PermalinkData.UserLink -> {
|
||||
val isCurrentUser = permalinkData.userId == currentSessionId
|
||||
val isCurrentUser = permalinkData.userId.value == currentSessionId
|
||||
MentionSpan(
|
||||
text = text,
|
||||
rawValue = permalinkData.userId.toString(),
|
||||
|
|
@ -134,43 +144,30 @@ class MentionSpanProvider(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberMentionSpanProvider(
|
||||
currentUserId: SessionId,
|
||||
permalinkParser: PermalinkParser,
|
||||
): MentionSpanProvider {
|
||||
val provider = remember(currentUserId) {
|
||||
MentionSpanProvider(
|
||||
currentSessionId = currentUserId,
|
||||
permalinkParser = permalinkParser,
|
||||
)
|
||||
}
|
||||
provider.setup()
|
||||
return provider
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MentionSpanPreview() {
|
||||
val provider = rememberMentionSpanProvider(
|
||||
currentUserId = SessionId("@me:matrix.org"),
|
||||
permalinkParser = object : PermalinkParser {
|
||||
override fun parse(uriString: String): PermalinkData {
|
||||
return when (uriString) {
|
||||
"https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org"))
|
||||
"https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org"))
|
||||
"https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(),
|
||||
eventId = null,
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
else -> throw AssertionError("Unexpected value $uriString")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ElementPreview {
|
||||
provider.setup()
|
||||
val provider = remember {
|
||||
MentionSpanProvider(
|
||||
currentSessionId = "@me:matrix.org",
|
||||
permalinkParser = object : PermalinkParser {
|
||||
override fun parse(uriString: String): PermalinkData {
|
||||
return when (uriString) {
|
||||
"https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink(UserId("@me:matrix.org"))
|
||||
"https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink(UserId("@other:matrix.org"))
|
||||
"https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink(
|
||||
roomIdOrAlias = RoomAlias("#room:matrix.org").toRoomIdOrAlias(),
|
||||
eventId = null,
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
else -> throw AssertionError("Unexpected value $uriString")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
provider.updateStyles()
|
||||
|
||||
val textColor = ElementTheme.colors.textPrimary.toArgb()
|
||||
fun mentionSpanMe() = provider.getMentionSpanFor("mention", "https://matrix.to/#/@me:matrix.org")
|
||||
|
|
@ -199,3 +196,14 @@ internal fun MentionSpanPreview() {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
val LocalMentionSpanProvider = staticCompositionLocalOf {
|
||||
MentionSpanProvider(
|
||||
currentSessionId = "@dummy:value.org",
|
||||
permalinkParser = object : PermalinkParser {
|
||||
override fun parse(uriString: String): PermalinkData {
|
||||
return PermalinkData.FallbackLink(Uri.EMPTY)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class MarkdownTextEditorState(
|
|||
replace(start, end, "@room")
|
||||
} else {
|
||||
val link = permalinkBuilder.permalinkForUser(UserId(mention.rawValue)).getOrNull() ?: continue
|
||||
replace(start, end, "[${mention.text}]($link)")
|
||||
replace(start, end, "[${mention.rawValue}]($link)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ class MarkdownTextInputTest {
|
|||
editor = it.findEditor()
|
||||
state.insertMention(
|
||||
ResolvedMentionSuggestion.Member(roomMember = aRoomMember()),
|
||||
MentionSpanProvider(currentSessionId = A_SESSION_ID, permalinkParser = permalinkParser),
|
||||
MentionSpanProvider(currentSessionId = A_SESSION_ID.value, permalinkParser = permalinkParser),
|
||||
permalinkBuilder,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,13 +43,14 @@ class MentionSpanProviderTest {
|
|||
|
||||
private val permalinkParser = FakePermalinkParser()
|
||||
private val mentionSpanProvider = MentionSpanProvider(
|
||||
currentSessionId = currentUserId,
|
||||
currentSessionId = currentUserId.value,
|
||||
permalinkParser = permalinkParser,
|
||||
currentUserBackgroundColor = myUserColor,
|
||||
currentUserTextColor = myUserColor,
|
||||
otherBackgroundColor = otherColor,
|
||||
otherTextColor = otherColor,
|
||||
)
|
||||
).apply {
|
||||
currentUserBackgroundColor = myUserColor
|
||||
currentUserTextColor = myUserColor
|
||||
otherBackgroundColor = otherColor
|
||||
otherTextColor = otherColor
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getting mention span for current user should return a MentionSpan with custom colors`() {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class MarkdownTextEditorStateTest {
|
|||
val markdown = state.getMessageMarkdown(permalinkBuilder = permalinkBuilder)
|
||||
|
||||
assertThat(markdown).isEqualTo(
|
||||
"Hello [@Alice](https://matrix.to/#/@alice:matrix.org) and everyone in @room"
|
||||
"Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ class MarkdownTextEditorStateTest {
|
|||
currentSessionId: SessionId = A_SESSION_ID,
|
||||
permalinkParser: FakePermalinkParser = FakePermalinkParser(),
|
||||
): MentionSpanProvider {
|
||||
return MentionSpanProvider(currentSessionId, permalinkParser)
|
||||
return MentionSpanProvider(currentSessionId.value, permalinkParser)
|
||||
}
|
||||
|
||||
private fun aMarkdownTextWithMentions(): CharSequence {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue