mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-25 13:58:34 +01:00
Added auto markdown (as per preference)
Fix / show formatted message preview upon composer in edit/quote/reply Fix / use aggregated content to decide for actions on long click
This commit is contained in:
parent
00d66ffd48
commit
4a4c0a3da1
@ -91,6 +91,7 @@ dependencies {
|
|||||||
def moshi_version = '1.8.0'
|
def moshi_version = '1.8.0'
|
||||||
def lifecycle_version = '2.0.0'
|
def lifecycle_version = '2.0.0'
|
||||||
def coroutines_version = "1.0.1"
|
def coroutines_version = "1.0.1"
|
||||||
|
def markwon_version = '3.0.0-SNAPSHOT'
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
@ -112,6 +113,8 @@ dependencies {
|
|||||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||||
|
|
||||||
|
implementation "ru.noties.markwon:core:$markwon_version"
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
@ -60,7 +60,7 @@ interface RelationService {
|
|||||||
* @param newBodyText The edited body
|
* @param newBodyText The edited body
|
||||||
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
||||||
*/
|
*/
|
||||||
fun editTextMessage(targetEventId: String, newBodyText: String, compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
fun editTextMessage(targetEventId: String, newBodyText: String, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
|
||||||
fun replyToMessage(eventReplied: Event, replyText: String) : Cancelable?
|
fun replyToMessage(eventReplied: Event, replyText: String) : Cancelable?
|
||||||
|
@ -33,7 +33,7 @@ interface SendService {
|
|||||||
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun sendTextMessage(text: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
|
fun sendTextMessage(text: String, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false): Cancelable
|
||||||
fun sendFormattedTextMessage(text: String,formattedText: String): Cancelable
|
fun sendFormattedTextMessage(text: String,formattedText: String): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,8 +160,8 @@ internal class DefaultRelationService(private val roomId: String,
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editTextMessage(targetEventId: String, newBodyText: String, compatibilityBodyText: String): Cancelable {
|
override fun editTextMessage(targetEventId: String, newBodyText: String, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String): Cancelable {
|
||||||
val event = eventFactory.createReplaceTextEvent(roomId, targetEventId, newBodyText, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
|
val event = eventFactory.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
|
val sendContentWorkerParams = SendEventWorker.Params(roomId, event)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ internal class DefaultSendService(private val roomId: String,
|
|||||||
: SendService {
|
: SendService {
|
||||||
|
|
||||||
|
|
||||||
override fun sendTextMessage(text: String, msgType: String): Cancelable {
|
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||||
val event = eventFactory.createTextEvent(roomId, msgType, text).also {
|
val event = eventFactory.createTextEvent(roomId, msgType, text, autoMarkdown).also {
|
||||||
saveLocalEcho(it)
|
saveLocalEcho(it)
|
||||||
}
|
}
|
||||||
val sendWork = createSendEventWork(event)
|
val sendWork = createSendEventWork(event)
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
|
import android.text.TextUtils
|
||||||
import im.vector.matrix.android.R
|
import im.vector.matrix.android.R
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
import im.vector.matrix.android.api.permalinks.PermalinkFactory
|
||||||
@ -29,10 +30,21 @@ import im.vector.matrix.android.api.session.room.model.annotation.ReplyToContent
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.*
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||||
import im.vector.matrix.android.internal.util.StringProvider
|
import im.vector.matrix.android.internal.util.StringProvider
|
||||||
|
import org.commonmark.parser.Parser
|
||||||
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
|
|
||||||
internal class LocalEchoEventFactory(private val credentials: Credentials, private val stringProvider: StringProvider) {
|
internal class LocalEchoEventFactory(private val credentials: Credentials, private val stringProvider: StringProvider) {
|
||||||
|
|
||||||
fun createTextEvent(roomId: String, msgType: String, text: String): Event {
|
fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event {
|
||||||
|
if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) {
|
||||||
|
val parser = Parser.builder().build()
|
||||||
|
val document = parser.parse(text)
|
||||||
|
val renderer = HtmlRenderer.builder().build()
|
||||||
|
val htmlText = renderer.render(document)
|
||||||
|
if (!TextUtils.equals(text, htmlText)) {
|
||||||
|
return createFormattedTextEvent(roomId, text, htmlText)
|
||||||
|
}
|
||||||
|
}
|
||||||
val content = MessageTextContent(type = msgType, body = text)
|
val content = MessageTextContent(type = msgType, body = text)
|
||||||
return createEvent(roomId, content)
|
return createEvent(roomId, content)
|
||||||
}
|
}
|
||||||
@ -48,15 +60,32 @@ internal class LocalEchoEventFactory(private val credentials: Credentials, priva
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createReplaceTextEvent(roomId: String, targetEventId: String, newBodyText: String, msgType: String, compatibilityText: String): Event {
|
fun createReplaceTextEvent(roomId: String, targetEventId: String, newBodyText: String, newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String): Event {
|
||||||
|
|
||||||
|
var newContent = MessageTextContent(
|
||||||
|
type = MessageType.MSGTYPE_TEXT,
|
||||||
|
body = newBodyText
|
||||||
|
)
|
||||||
|
if (newBodyAutoMarkdown) {
|
||||||
|
val parser = Parser.builder().build()
|
||||||
|
val document = parser.parse(newBodyText)
|
||||||
|
val renderer = HtmlRenderer.builder().build()
|
||||||
|
val htmlText = renderer.render(document)
|
||||||
|
if (!TextUtils.equals(newBodyText, htmlText)) {
|
||||||
|
newContent = MessageTextContent(
|
||||||
|
type = MessageType.MSGTYPE_TEXT,
|
||||||
|
format = MessageType.FORMAT_MATRIX_HTML,
|
||||||
|
body = newBodyText,
|
||||||
|
formattedBody = htmlText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val content = MessageTextContent(
|
val content = MessageTextContent(
|
||||||
type = msgType,
|
type = msgType,
|
||||||
body = compatibilityText,
|
body = compatibilityText,
|
||||||
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId),
|
||||||
newContent = MessageTextContent(
|
newContent = newContent.toContent()
|
||||||
type = MessageType.MSGTYPE_TEXT,
|
|
||||||
body = newBodyText
|
|
||||||
).toContent()
|
|
||||||
)
|
)
|
||||||
return createEvent(roomId, content)
|
return createEvent(roomId, content)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|||||||
|
|
||||||
sealed class RoomDetailActions {
|
sealed class RoomDetailActions {
|
||||||
|
|
||||||
data class SendMessage(val text: String) : RoomDetailActions()
|
data class SendMessage(val text: String, val autoMarkdown: Boolean) : RoomDetailActions()
|
||||||
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
|
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
|
||||||
object IsDisplayed : RoomDetailActions()
|
object IsDisplayed : RoomDetailActions()
|
||||||
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
|
||||||
|
@ -93,13 +93,17 @@ import im.vector.riotredesign.features.media.ImageMediaViewerActivity
|
|||||||
import im.vector.riotredesign.features.media.VideoContentRenderer
|
import im.vector.riotredesign.features.media.VideoContentRenderer
|
||||||
import im.vector.riotredesign.features.media.VideoMediaViewerActivity
|
import im.vector.riotredesign.features.media.VideoMediaViewerActivity
|
||||||
import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity
|
import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity
|
||||||
|
import im.vector.riotredesign.features.settings.PreferencesManager
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||||
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
|
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
|
||||||
|
import org.commonmark.parser.Parser
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.android.scope.ext.android.bindScope
|
import org.koin.android.scope.ext.android.bindScope
|
||||||
import org.koin.android.scope.ext.android.getOrCreateScope
|
import org.koin.android.scope.ext.android.getOrCreateScope
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
|
import ru.noties.markwon.Markwon
|
||||||
|
import ru.noties.markwon.html.HtmlPlugin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -227,12 +231,20 @@ class RoomDetailFragment :
|
|||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: event.root.content.toModel()
|
?: event.root.content.toModel()
|
||||||
val eventTextBody = messageContent?.body
|
val nonFormattedBody = messageContent?.body ?: ""
|
||||||
composerLayout.composerRelatedMessageContent.text = eventTextBody
|
var formattedBody: CharSequence? = null
|
||||||
|
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||||
|
val parser = Parser.builder().build()
|
||||||
|
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
|
||||||
|
formattedBody = Markwon.builder(requireContext())
|
||||||
|
.usePlugin(HtmlPlugin.create()).build().render(document)
|
||||||
|
}
|
||||||
|
composerLayout.composerRelatedMessageContent.text = formattedBody ?: nonFormattedBody
|
||||||
|
|
||||||
|
|
||||||
if (mode == SendMode.EDIT) {
|
if (mode == SendMode.EDIT) {
|
||||||
composerLayout.composerEditText.setText(eventTextBody)
|
//TODO if it's a reply we should trim the top part of message
|
||||||
|
composerLayout.composerEditText.setText(nonFormattedBody)
|
||||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
|
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_edit))
|
||||||
} else if (mode == SendMode.QUOTE) {
|
} else if (mode == SendMode.QUOTE) {
|
||||||
composerLayout.composerEditText.setText("")
|
composerLayout.composerEditText.setText("")
|
||||||
@ -378,7 +390,7 @@ class RoomDetailFragment :
|
|||||||
composerLayout.sendButton.setOnClickListener {
|
composerLayout.sendButton.setOnClickListener {
|
||||||
val textMessage = composerLayout.composerEditText.text.toString()
|
val textMessage = composerLayout.composerEditText.text.toString()
|
||||||
if (textMessage.isNotBlank()) {
|
if (textMessage.isNotBlank()) {
|
||||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage))
|
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, PreferencesManager.isMarkdownEnabled(requireContext())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
when (slashCommandResult) {
|
when (slashCommandResult) {
|
||||||
is ParsedCommand.ErrorNotACommand -> {
|
is ParsedCommand.ErrorNotACommand -> {
|
||||||
// Send the text message to the room
|
// Send the text message to the room
|
||||||
room.sendTextMessage(action.text)
|
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorSyntax -> {
|
is ParsedCommand.ErrorSyntax -> {
|
||||||
@ -199,7 +199,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SendMode.EDIT -> {
|
SendMode.EDIT -> {
|
||||||
room.editTextMessage(state?.selectedEvent?.root?.eventId ?: "", action.text)
|
room.editTextMessage(state.selectedEvent?.root?.eventId ?: "", action.text, action.autoMarkdown)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
sendMode = SendMode.REGULAR,
|
sendMode = SendMode.REGULAR,
|
||||||
|
@ -50,7 +50,8 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
|
|||||||
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
|
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val messageContent: MessageContent = event.root.content.toModel() ?: return null
|
val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
|
?: event.root.content.toModel() ?: return null
|
||||||
val type = messageContent.type
|
val type = messageContent.type
|
||||||
|
|
||||||
if (event.sendState == SendState.UNSENT) {
|
if (event.sendState == SendState.UNSENT) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user