|
|
|
@ -253,7 +253,10 @@ class MessageComposerPresenter @Inject constructor(
@@ -253,7 +253,10 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
LaunchedEffect(Unit) { |
|
|
|
|
loadDraft(markdownTextEditorState, richTextEditorState) |
|
|
|
|
val draft = draftService.loadDraft(room.roomId, isVolatile = false) |
|
|
|
|
if (draft != null) { |
|
|
|
|
applyDraft(draft, markdownTextEditorState, richTextEditorState) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
val mentionSpanProvider = LocalMentionSpanProvider.current |
|
|
|
@ -264,26 +267,16 @@ class MessageComposerPresenter @Inject constructor(
@@ -264,26 +267,16 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
MessageComposerEvents.CloseSpecialMode -> { |
|
|
|
|
if (messageComposerContext.composerMode is MessageComposerMode.Edit) { |
|
|
|
|
localCoroutineScope.launch { |
|
|
|
|
textEditorState.reset() |
|
|
|
|
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = true) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
messageComposerContext.composerMode = MessageComposerMode.Normal |
|
|
|
|
} |
|
|
|
|
messageComposerContext.composerMode = MessageComposerMode.Normal |
|
|
|
|
} |
|
|
|
|
is MessageComposerEvents.SendMessage -> { |
|
|
|
|
val html = if (showTextFormatting) { |
|
|
|
|
richTextEditorState.messageHtml |
|
|
|
|
} else { |
|
|
|
|
null |
|
|
|
|
} |
|
|
|
|
val markdown = if (showTextFormatting) { |
|
|
|
|
richTextEditorState.messageMarkdown |
|
|
|
|
} else { |
|
|
|
|
markdownTextEditorState.getMessageMarkdown(permalinkBuilder) |
|
|
|
|
} |
|
|
|
|
appCoroutineScope.sendMessage( |
|
|
|
|
message = Message(html = html, markdown = markdown), |
|
|
|
|
updateComposerMode = { messageComposerContext.composerMode = it }, |
|
|
|
|
textEditorState = textEditorState, |
|
|
|
|
markdownTextEditorState = markdownTextEditorState, |
|
|
|
|
richTextEditorState = richTextEditorState, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
is MessageComposerEvents.SendUri -> appCoroutineScope.sendAttachment( |
|
|
|
@ -386,7 +379,8 @@ class MessageComposerPresenter @Inject constructor(
@@ -386,7 +379,8 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
MessageComposerEvents.SaveDraft -> { |
|
|
|
|
appCoroutineScope.saveDraft(textEditorState) |
|
|
|
|
val draft = createDraftFromState(markdownTextEditorState, richTextEditorState) |
|
|
|
|
appCoroutineScope.updateDraft(draft, isVolatile = false) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -407,42 +401,26 @@ class MessageComposerPresenter @Inject constructor(
@@ -407,42 +401,26 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun CoroutineScope.sendMessage( |
|
|
|
|
message: Message, |
|
|
|
|
updateComposerMode: (newComposerMode: MessageComposerMode) -> Unit, |
|
|
|
|
textEditorState: TextEditorState, |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
) = launch { |
|
|
|
|
val message = currentComposerMessage(markdownTextEditorState, richTextEditorState, withMentions = true) |
|
|
|
|
val capturedMode = messageComposerContext.composerMode |
|
|
|
|
val mentions = when (textEditorState) { |
|
|
|
|
is TextEditorState.Rich -> { |
|
|
|
|
textEditorState.richTextEditorState.mentionsState?.let { state -> |
|
|
|
|
buildList { |
|
|
|
|
if (state.hasAtRoomMention) { |
|
|
|
|
add(Mention.AtRoom) |
|
|
|
|
} |
|
|
|
|
for (userId in state.userIds) { |
|
|
|
|
add(Mention.User(UserId(userId))) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}.orEmpty() |
|
|
|
|
} |
|
|
|
|
is TextEditorState.Markdown -> textEditorState.state.getMentions() |
|
|
|
|
} |
|
|
|
|
// Reset composer right away |
|
|
|
|
textEditorState.reset() |
|
|
|
|
updateComposerMode(MessageComposerMode.Normal) |
|
|
|
|
resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit) |
|
|
|
|
when (capturedMode) { |
|
|
|
|
is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html, mentions = mentions) |
|
|
|
|
is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html, mentions = message.mentions) |
|
|
|
|
is MessageComposerMode.Edit -> { |
|
|
|
|
val eventId = capturedMode.eventId |
|
|
|
|
val transactionId = capturedMode.transactionId |
|
|
|
|
timelineController.invokeOnCurrentTimeline { |
|
|
|
|
editMessage(eventId, transactionId, message.markdown, message.html, mentions) |
|
|
|
|
editMessage(eventId, transactionId, message.markdown, message.html, message.mentions) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
is MessageComposerMode.Reply -> { |
|
|
|
|
timelineController.invokeOnCurrentTimeline { |
|
|
|
|
replyMessage(capturedMode.eventId, message.markdown, message.html, mentions) |
|
|
|
|
replyMessage(capturedMode.eventId, message.markdown, message.html, message.mentions) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -537,21 +515,30 @@ class MessageComposerPresenter @Inject constructor(
@@ -537,21 +515,30 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun CoroutineScope.loadDraft( |
|
|
|
|
private fun CoroutineScope.updateDraft( |
|
|
|
|
draft: ComposerDraft?, |
|
|
|
|
isVolatile: Boolean, |
|
|
|
|
) = launch { |
|
|
|
|
draftService.updateDraft( |
|
|
|
|
roomId = room.roomId, |
|
|
|
|
draft = draft, |
|
|
|
|
isVolatile = isVolatile |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private suspend fun applyDraft( |
|
|
|
|
draft: ComposerDraft, |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
) = launch { |
|
|
|
|
val draft = draftService.loadDraft(room.roomId) ?: return@launch |
|
|
|
|
) { |
|
|
|
|
val htmlText = draft.htmlText |
|
|
|
|
val markdownText = draft.plainText |
|
|
|
|
if (htmlText != null) { |
|
|
|
|
showTextFormatting = true |
|
|
|
|
richTextEditorState.setHtml(htmlText) |
|
|
|
|
richTextEditorState.requestFocus() |
|
|
|
|
setText(htmlText, markdownTextEditorState, richTextEditorState, requestFocus = true) |
|
|
|
|
} else { |
|
|
|
|
showTextFormatting = false |
|
|
|
|
markdownTextEditorState.text.update(markdownText, true) |
|
|
|
|
markdownTextEditorState.requestFocusAction() |
|
|
|
|
setText(markdownText, markdownTextEditorState, richTextEditorState, requestFocus = true) |
|
|
|
|
} |
|
|
|
|
when (val draftType = draft.draftType) { |
|
|
|
|
ComposerDraftType.NewMessage -> messageComposerContext.composerMode = MessageComposerMode.Normal |
|
|
|
@ -570,11 +557,11 @@ class MessageComposerPresenter @Inject constructor(
@@ -570,11 +557,11 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun CoroutineScope.saveDraft( |
|
|
|
|
textEditorState: TextEditorState, |
|
|
|
|
) = launch { |
|
|
|
|
val html = textEditorState.messageHtml() |
|
|
|
|
val markdown = textEditorState.messageMarkdown(permalinkBuilder) |
|
|
|
|
private fun createDraftFromState( |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
): ComposerDraft? { |
|
|
|
|
val message = currentComposerMessage(markdownTextEditorState, richTextEditorState, withMentions = false) |
|
|
|
|
val draftType = when (val mode = messageComposerContext.composerMode) { |
|
|
|
|
is MessageComposerMode.Normal -> ComposerDraftType.NewMessage |
|
|
|
|
is MessageComposerMode.Edit -> { |
|
|
|
@ -582,22 +569,54 @@ class MessageComposerPresenter @Inject constructor(
@@ -582,22 +569,54 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
} |
|
|
|
|
is MessageComposerMode.Reply -> ComposerDraftType.Reply(mode.eventId) |
|
|
|
|
} |
|
|
|
|
val composerDraft = if (draftType == null || markdown.isBlank()) { |
|
|
|
|
return if (draftType == null || message.markdown.isBlank()) { |
|
|
|
|
null |
|
|
|
|
} else { |
|
|
|
|
ComposerDraft( |
|
|
|
|
draftType = draftType, |
|
|
|
|
htmlText = html, |
|
|
|
|
plainText = markdown, |
|
|
|
|
htmlText = message.html, |
|
|
|
|
plainText = message.markdown, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
draftService.updateDraft(room.roomId, composerDraft) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun currentComposerMessage( |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
withMentions: Boolean, |
|
|
|
|
): Message { |
|
|
|
|
return if (showTextFormatting) { |
|
|
|
|
val html = richTextEditorState.messageHtml |
|
|
|
|
val markdown = richTextEditorState.messageMarkdown |
|
|
|
|
val mentions = richTextEditorState.mentionsState |
|
|
|
|
.takeIf { withMentions } |
|
|
|
|
?.let { state -> |
|
|
|
|
buildList { |
|
|
|
|
if (state.hasAtRoomMention) { |
|
|
|
|
add(Mention.AtRoom) |
|
|
|
|
} |
|
|
|
|
for (userId in state.userIds) { |
|
|
|
|
add(Mention.User(UserId(userId))) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.orEmpty() |
|
|
|
|
Message(html = html, markdown = markdown, mentions = mentions) |
|
|
|
|
} else { |
|
|
|
|
val markdown = markdownTextEditorState.getMessageMarkdown(permalinkBuilder) |
|
|
|
|
val mentions = if (withMentions) { |
|
|
|
|
markdownTextEditorState.getMentions() |
|
|
|
|
} else { |
|
|
|
|
emptyList() |
|
|
|
|
} |
|
|
|
|
Message(html = null, markdown = markdown, mentions = mentions) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun CoroutineScope.toggleTextFormatting( |
|
|
|
|
enabled: Boolean, |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState |
|
|
|
|
) = launch { |
|
|
|
|
showTextFormatting = enabled |
|
|
|
|
if (showTextFormatting) { |
|
|
|
@ -615,24 +634,63 @@ class MessageComposerPresenter @Inject constructor(
@@ -615,24 +634,63 @@ class MessageComposerPresenter @Inject constructor(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun CoroutineScope.setMode( |
|
|
|
|
composerMode: MessageComposerMode, |
|
|
|
|
newComposerMode: MessageComposerMode, |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
) = launch { |
|
|
|
|
messageComposerContext.composerMode = composerMode |
|
|
|
|
when (composerMode) { |
|
|
|
|
val currentComposerMode = messageComposerContext.composerMode |
|
|
|
|
when (newComposerMode) { |
|
|
|
|
is MessageComposerMode.Edit -> { |
|
|
|
|
setText(composerMode.content, markdownTextEditorState, richTextEditorState) |
|
|
|
|
if (currentComposerMode !is MessageComposerMode.Edit) { |
|
|
|
|
val draft = createDraftFromState(markdownTextEditorState, richTextEditorState) |
|
|
|
|
updateDraft(draft, isVolatile = true).join() |
|
|
|
|
} |
|
|
|
|
setText(newComposerMode.content, markdownTextEditorState, richTextEditorState) |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
// When coming from edit, just clear the composer as it'd be weird to reset a volatile draft in this scenario. |
|
|
|
|
if (currentComposerMode is MessageComposerMode.Edit) { |
|
|
|
|
setText("", markdownTextEditorState, richTextEditorState) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else -> Unit |
|
|
|
|
} |
|
|
|
|
messageComposerContext.composerMode = newComposerMode |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private suspend fun setText(content: String, markdownTextEditorState: MarkdownTextEditorState, richTextEditorState: RichTextEditorState) { |
|
|
|
|
private suspend fun resetComposer( |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
fromEdit: Boolean, |
|
|
|
|
) { |
|
|
|
|
// Use the volatile draft only when coming from edit mode otherwise. |
|
|
|
|
val draft = draftService.loadDraft(room.roomId, isVolatile = true).takeIf { fromEdit } |
|
|
|
|
if (draft != null) { |
|
|
|
|
applyDraft(draft, markdownTextEditorState, richTextEditorState) |
|
|
|
|
} else { |
|
|
|
|
setText("", markdownTextEditorState, richTextEditorState) |
|
|
|
|
messageComposerContext.composerMode = MessageComposerMode.Normal |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private suspend fun setText( |
|
|
|
|
content: String, |
|
|
|
|
markdownTextEditorState: MarkdownTextEditorState, |
|
|
|
|
richTextEditorState: RichTextEditorState, |
|
|
|
|
requestFocus: Boolean = false, |
|
|
|
|
) { |
|
|
|
|
if (showTextFormatting) { |
|
|
|
|
richTextEditorState.setHtml(content) |
|
|
|
|
if (requestFocus) { |
|
|
|
|
richTextEditorState.requestFocus() |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (content.isEmpty()) { |
|
|
|
|
markdownTextEditorState.selection = IntRange.EMPTY |
|
|
|
|
} |
|
|
|
|
markdownTextEditorState.text.update(content, true) |
|
|
|
|
if (requestFocus) { |
|
|
|
|
markdownTextEditorState.requestFocusAction() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|