Use room member instead of user when it makes sense

This commit is contained in:
ganfra 2020-11-06 14:41:43 +01:00
parent eb1fa0919f
commit 6dfdc77ebd
15 changed files with 179 additions and 142 deletions

View file

@ -137,7 +137,7 @@ class VectorCallViewModel @AssistedInject constructor(
session.callSignalingService().getCallWithId(it)?.let { mxCall -> session.callSignalingService().getCallWithId(it)?.let { mxCall ->
this.call = mxCall this.call = mxCall
mxCall.otherUserId mxCall.otherUserId
val item: MatrixItem? = session.getUser(mxCall.otherUserId)?.toMatrixItem() val item: MatrixItem? = session.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()
mxCall.addListener(callStateListener) mxCall.addListener(callStateListener)

View file

@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.util.toMatrixItem
import org.webrtc.AudioSource import org.webrtc.AudioSource
import org.webrtc.AudioTrack import org.webrtc.AudioTrack
import org.webrtc.Camera1Enumerator import org.webrtc.Camera1Enumerator
@ -330,8 +331,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
currentCall?.mxCall currentCall?.mxCall
?.takeIf { it.state is CallState.Connected } ?.takeIf { it.state is CallState.Connected }
?.let { mxCall -> ?.let { mxCall ->
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
?: mxCall.roomId ?: mxCall.otherUserId
// Start background service with notification // Start background service with notification
CallService.onPendingCall( CallService.onPendingCall(
context = context, context = context,
@ -388,7 +389,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
val mxCall = callContext.mxCall val mxCall = callContext.mxCall
// Update service state // Update service state
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
?: mxCall.roomId ?: mxCall.roomId
CallService.onPendingCall( CallService.onPendingCall(
context = context, context = context,
@ -576,7 +577,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
?.let { mxCall -> ?.let { mxCall ->
// Start background service with notification // Start background service with notification
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
?: mxCall.otherUserId ?: mxCall.otherUserId
CallService.onOnGoingCallBackground( CallService.onOnGoingCallBackground(
context = context, context = context,
@ -650,7 +651,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
callAudioManager.startForCall(createdCall) callAudioManager.startForCall(createdCall)
currentCall = callContext currentCall = callContext
val name = currentSession?.getUser(createdCall.otherUserId)?.getBestName() val name = currentSession?.getRoomMember(createdCall.otherUserId, createdCall.roomId)?.toMatrixItem()?.getBestName()
?: createdCall.otherUserId ?: createdCall.otherUserId
CallService.onOutgoingCallRinging( CallService.onOutgoingCallRinging(
context = context.applicationContext, context = context.applicationContext,
@ -706,7 +707,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
// Start background service with notification // Start background service with notification
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
?: mxCall.otherUserId ?: mxCall.otherUserId
CallService.onIncomingCallRinging( CallService.onIncomingCallRinging(
context = context, context = context,
@ -845,7 +846,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
val mxCall = call.mxCall val mxCall = call.mxCall
// Update service state // Update service state
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
?: mxCall.otherUserId ?: mxCall.otherUserId
CallService.onPendingCall( CallService.onPendingCall(
context = context, context = context,

View file

@ -46,7 +46,7 @@ class JitsiCallViewModel @AssistedInject constructor(
} }
init { init {
val me = session.getUser(session.myUserId)?.toMatrixItem() val me = session.getRoomMember(session.myUserId, args.roomId)?.toMatrixItem()
val userInfo = JitsiMeetUserInfo().apply { val userInfo = JitsiMeetUserInfo().apply {
displayName = me?.getBestName() displayName = me?.getBestName()
avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) } avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) }

View file

@ -141,6 +141,7 @@ import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsB
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillImageSpan
import im.vector.app.features.html.PillsPostProcessor
import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.invite.VectorInviteView
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.media.VideoContentRenderer
@ -221,7 +222,8 @@ class RoomDetailFragment @Inject constructor(
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
private val matrixItemColorProvider: MatrixItemColorProvider, private val matrixItemColorProvider: MatrixItemColorProvider,
private val imageContentRenderer: ImageContentRenderer, private val imageContentRenderer: ImageContentRenderer,
private val roomDetailPendingActionStore: RoomDetailPendingActionStore private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
private val pillsPostProcessorFactory: PillsPostProcessor.Factory
) : ) :
VectorBaseFragment(), VectorBaseFragment(),
TimelineEventController.Callback, TimelineEventController.Callback,
@ -254,6 +256,9 @@ class RoomDetailFragment @Inject constructor(
private val glideRequests by lazy { private val glideRequests by lazy {
GlideApp.with(this) GlideApp.with(this)
} }
private val pillsPostProcessor by lazy {
pillsPostProcessorFactory.create(roomDetailArgs.roomId)
}
private val autoCompleter: AutoCompleter by lazy { private val autoCompleter: AutoCompleter by lazy {
autoCompleterFactory.create(roomDetailArgs.roomId) autoCompleterFactory.create(roomDetailArgs.roomId)
@ -848,7 +853,7 @@ class RoomDetailFragment @Inject constructor(
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build() val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody ?: messageContent.body) val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
formattedBody = eventHtmlRenderer.render(document) formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
} }
composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)

View file

@ -1300,7 +1300,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
if (summary.membership == Membership.INVITE) { if (summary.membership == Membership.INVITE) {
summary.inviterId?.let { inviterId -> summary.inviterId?.let { inviterId ->
session.getUser(inviterId) session.getRoomMember(inviterId, summary.roomId)
}?.also { }?.also {
setState { copy(asyncInviter = Success(it)) } setState { copy(asyncInviter = Success(it)) }
} }

View file

@ -60,7 +60,7 @@ data class RoomDetailViewState(
val roomId: String, val roomId: String,
val eventId: String?, val eventId: String?,
val myRoomMember: Async<RoomMemberSummary> = Uninitialized, val myRoomMember: Async<RoomMemberSummary> = Uninitialized,
val asyncInviter: Async<User> = Uninitialized, val asyncInviter: Async<RoomMemberSummary> = Uninitialized,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized, val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val activeRoomWidgets: Async<List<Widget>> = Uninitialized, val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
val typingMessage: String? = null, val typingMessage: String? = null,

View file

@ -123,7 +123,7 @@ class SearchResultController @Inject constructor(
.formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)) .formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE))
.spannable(spannable) .spannable(spannable)
.sender(eventAndSender.sender .sender(eventAndSender.sender
?: eventAndSender.event.senderId?.let { session.getUser(it) }?.toMatrixItem()) ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem())
.listener { listener?.onItemClicked(eventAndSender.event) } .listener { listener?.onItemClicked(eventAndSender.event) }
.let { result.add(it) } .let { result.add(it) }
} }

View file

@ -28,6 +28,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillsPostProcessor
import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.html.VectorHtmlCompressor
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
@ -57,18 +58,22 @@ import java.util.ArrayList
* Information related to an event and used to display preview in contextual bottom sheet. * Information related to an event and used to display preview in contextual bottom sheet.
*/ */
class MessageActionsViewModel @AssistedInject constructor(@Assisted class MessageActionsViewModel @AssistedInject constructor(@Assisted
initialState: MessageActionState, private val initialState: MessageActionState,
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>, private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
private val htmlCompressor: VectorHtmlCompressor, private val htmlCompressor: VectorHtmlCompressor,
private val session: Session, private val session: Session,
private val noticeEventFormatter: NoticeEventFormatter, private val noticeEventFormatter: NoticeEventFormatter,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
private val vectorPreferences: VectorPreferences private val vectorPreferences: VectorPreferences
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) { ) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
private val eventId = initialState.eventId private val eventId = initialState.eventId
private val informationData = initialState.informationData private val informationData = initialState.informationData
private val room = session.getRoom(initialState.roomId) private val room = session.getRoom(initialState.roomId)
private val pillsPostProcessor by lazy {
pillsPostProcessorFactory.create(initialState.roomId)
}
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -164,7 +169,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
return when (timelineEvent.root.getClearType()) { return when (timelineEvent.root.getClearType()) {
EventType.MESSAGE, EventType.MESSAGE,
EventType.STICKER -> { EventType.STICKER -> {
val messageContent: MessageContent? = timelineEvent.getLastMessageContent() val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val html = messageContent.formattedBody val html = messageContent.formattedBody
@ -172,7 +177,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
?.let { htmlCompressor.compress(it) } ?.let { htmlCompressor.compress(it) }
?: messageContent.body ?: messageContent.body
eventHtmlRenderer.get().render(html) eventHtmlRenderer.get().render(html, pillsPostProcessor)
} else if (messageContent is MessageVerificationRequestContent) { } else if (messageContent is MessageVerificationRequestContent) {
stringProvider.getString(R.string.verification_request) stringProvider.getString(R.string.verification_request)
} else { } else {

View file

@ -60,6 +60,7 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement
import im.vector.app.features.home.room.detail.timeline.tools.linkify import im.vector.app.features.home.room.detail.timeline.tools.linkify
import im.vector.app.features.html.CodeVisitor import im.vector.app.features.html.CodeVisitor
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillsPostProcessor
import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.html.VectorHtmlCompressor
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.media.VideoContentRenderer
@ -106,15 +107,19 @@ class MessageItemFactory @Inject constructor(
private val defaultItemFactory: DefaultItemFactory, private val defaultItemFactory: DefaultItemFactory,
private val noticeItemFactory: NoticeItemFactory, private val noticeItemFactory: NoticeItemFactory,
private val avatarSizeProvider: AvatarSizeProvider, private val avatarSizeProvider: AvatarSizeProvider,
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
private val session: Session) { private val session: Session) {
private val pillsPostProcessor by lazy {
pillsPostProcessorFactory.create(roomSummaryHolder.roomSummary?.roomId)
}
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
highlight: Boolean, highlight: Boolean,
callback: TimelineEventController.Callback? callback: TimelineEventController.Callback?
): VectorEpoxyModel<*>? { ): VectorEpoxyModel<*>? {
event.root.eventId ?: return null event.root.eventId ?: return null
val informationData = messageInformationDataFactory.create(event, nextEvent) val informationData = messageInformationDataFactory.create(event, nextEvent)
if (event.root.isRedacted()) { if (event.root.isRedacted()) {
@ -139,16 +144,16 @@ class MessageItemFactory @Inject constructor(
// val all = event.root.toContent() // val all = event.root.toContent()
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes)
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
} }
} }
@ -159,7 +164,7 @@ class MessageItemFactory @Inject constructor(
callback: TimelineEventController.Callback?, callback: TimelineEventController.Callback?,
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
return when (messageContent.optionType) { return when (messageContent.optionType) {
OPTION_TYPE_POLL -> { OPTION_TYPE_POLL -> {
MessagePollItem_() MessagePollItem_()
.attributes(attributes) .attributes(attributes)
.callback(callback) .callback(callback)
@ -217,13 +222,17 @@ class MessageItemFactory @Inject constructor(
attributes: AbsMessageItem.Attributes): VerificationRequestItem? { attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
// If this request is not sent by me or sent to me, we should ignore it in timeline // If this request is not sent by me or sent to me, we should ignore it in timeline
val myUserId = session.myUserId val myUserId = session.myUserId
val roomId = roomSummaryHolder.roomSummary?.roomId
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) { if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
return null return null
} }
val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId
val otherUserName = if (informationData.sentByMe) session.getUser(messageContent.toUserId)?.displayName val otherUserName = if (informationData.sentByMe) {
else informationData.memberName session.getRoomMember(messageContent.toUserId, roomId ?: "")?.displayName
} else {
informationData.memberName
}
return VerificationRequestItem_() return VerificationRequestItem_()
.attributes( .attributes(
VerificationRequestItem.Attributes( VerificationRequestItem.Attributes(
@ -362,7 +371,7 @@ class MessageItemFactory @Inject constructor(
val codeVisitor = CodeVisitor() val codeVisitor = CodeVisitor()
codeVisitor.visit(localFormattedBody) codeVisitor.visit(localFormattedBody)
when (codeVisitor.codeKind) { when (codeVisitor.codeKind) {
CodeVisitor.Kind.BLOCK -> { CodeVisitor.Kind.BLOCK -> {
val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody) val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody)
if (codeFormattedBlock == null) { if (codeFormattedBlock == null) {
buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes) buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
@ -378,7 +387,7 @@ class MessageItemFactory @Inject constructor(
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes) buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
} }
} }
CodeVisitor.Kind.NONE -> { CodeVisitor.Kind.NONE -> {
buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes) buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
} }
} }
@ -393,7 +402,7 @@ class MessageItemFactory @Inject constructor(
callback: TimelineEventController.Callback?, callback: TimelineEventController.Callback?,
attributes: AbsMessageItem.Attributes): MessageTextItem? { attributes: AbsMessageItem.Attributes): MessageTextItem? {
val compressed = htmlCompressor.compress(messageContent.formattedBody!!) val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
val formattedBody = htmlRenderer.get().render(compressed) val formattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor)
return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes) return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
} }
@ -528,7 +537,7 @@ class MessageItemFactory @Inject constructor(
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence { private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
return matrixFormattedBody return matrixFormattedBody
?.let { htmlCompressor.compress(it) } ?.let { htmlCompressor.compress(it) }
?.let { htmlRenderer.get().render(it) } ?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
?: body ?: body
} }

View file

@ -17,21 +17,23 @@
package im.vector.app.features.html package im.vector.app.features.html
import android.content.Context import android.content.Context
import im.vector.app.core.di.ActiveSessionHolder import android.text.Spannable
import im.vector.app.core.glide.GlideApp import androidx.core.text.toSpannable
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.features.home.AvatarRenderer
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.html.TagHandlerNoOp
import org.commonmark.node.Node import org.commonmark.node.Node
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class EventHtmlRenderer @Inject constructor(context: Context, class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfigure,
htmlConfigure: MatrixHtmlPluginConfigure) { context: Context) {
interface PostProcessor {
fun afterRender(renderedText: Spannable)
}
private val markwon = Markwon.builder(context) private val markwon = Markwon.builder(context)
.usePlugin(HtmlPlugin.create(htmlConfigure)) .usePlugin(HtmlPlugin.create(htmlConfigure))
@ -41,35 +43,47 @@ class EventHtmlRenderer @Inject constructor(context: Context,
return markwon.parse(text) return markwon.parse(text)
} }
fun render(text: String): CharSequence { /**
* @param text the text you want to render
* @param postProcessors an optional array of post processor to add any span if needed
*/
fun render(text: String, vararg postProcessors: PostProcessor): CharSequence {
return try { return try {
markwon.toMarkdown(text) val parsed = markwon.parse(text)
renderAndProcess(parsed, postProcessors)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.v("Fail to render $text to html") Timber.v("Fail to render $text to html")
text text
} }
} }
fun render(node: Node): CharSequence? { /**
* @param node the node you want to render
* @param postProcessors an optional array of post processor to add any span if needed
*/
fun render(node: Node, vararg postProcessors: PostProcessor): CharSequence? {
return try { return try {
markwon.render(node) renderAndProcess(node, postProcessors)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.v("Fail to render $node to html") Timber.v("Fail to render $node to html")
return null return null
} }
} }
private fun renderAndProcess(node: Node, postProcessors: Array<out PostProcessor>): CharSequence {
val renderedText = markwon.render(node).toSpannable()
postProcessors.forEach {
it.afterRender(renderedText)
}
return renderedText
}
} }
class MatrixHtmlPluginConfigure @Inject constructor(private val context: Context, class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: ColorProvider) : HtmlPlugin.HtmlConfigure {
private val colorProvider: ColorProvider,
private val avatarRenderer: AvatarRenderer,
private val session: ActiveSessionHolder) : HtmlPlugin.HtmlConfigure {
override fun configureHtml(plugin: HtmlPlugin) { override fun configureHtml(plugin: HtmlPlugin) {
plugin plugin
.addHandler(TagHandlerNoOp.create("a"))
.addHandler(FontTagHandler()) .addHandler(FontTagHandler())
.addHandler(MxLinkTagHandler(GlideApp.with(context), context, avatarRenderer, session))
.addHandler(MxReplyTagHandler()) .addHandler(MxReplyTagHandler())
.addHandler(SpanHandler(colorProvider)) .addHandler(SpanHandler(colorProvider))
} }

View file

@ -1,89 +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 im.vector.app.features.html
import android.content.Context
import android.text.style.URLSpan
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideRequests
import im.vector.app.features.home.AvatarRenderer
import io.noties.markwon.MarkwonVisitor
import io.noties.markwon.SpannableBuilder
import io.noties.markwon.html.HtmlTag
import io.noties.markwon.html.MarkwonHtmlRenderer
import io.noties.markwon.html.tag.LinkHandler
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem
class MxLinkTagHandler(private val glideRequests: GlideRequests,
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val sessionHolder: ActiveSessionHolder) : LinkHandler() {
override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
val link = tag.attributes()["href"]
if (link != null) {
val permalinkData = PermalinkParser.parse(link)
val matrixItem = when (permalinkData) {
is PermalinkData.UserLink -> {
val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl)
}
is PermalinkData.RoomLink -> {
if (permalinkData.eventId == null) {
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias)
if (permalinkData.isRoomAlias) {
MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
} else {
MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
}
} else {
// Exclude event link (used in reply events, we do not want to pill the "in reply to")
null
}
}
is PermalinkData.GroupLink -> {
val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId)
MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl)
}
else -> null
}
if (matrixItem == null) {
super.handle(visitor, renderer, tag)
} else {
val span = PillImageSpan(glideRequests, avatarRenderer, context, matrixItem)
SpannableBuilder.setSpans(
visitor.builder(),
span,
tag.start(),
tag.end()
)
SpannableBuilder.setSpans(
visitor.builder(),
URLSpan(link),
tag.start(),
tag.end()
)
}
} else {
super.handle(visitor, renderer, tag)
}
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2020 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 im.vector.app.features.html
import android.content.Context
import android.text.Spannable
import android.text.Spanned
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer
import io.noties.markwon.core.spans.LinkSpan
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomId: String?,
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val sessionHolder: ActiveSessionHolder)
: EventHtmlRenderer.PostProcessor {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String?): PillsPostProcessor
}
override fun afterRender(renderedText: Spannable) {
addPillSpans(renderedText, roomId)
}
private fun addPillSpans(renderedText: Spannable, roomId: String?) {
// We let markdown handle links and then we add PillImageSpan if needed.
val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java)
linkSpans.forEach { linkSpan ->
val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach
val startSpan = renderedText.getSpanStart(linkSpan)
val endSpan = renderedText.getSpanEnd(linkSpan)
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? {
val permalinkData = PermalinkParser.parse(url)
val matrixItem = when (permalinkData) {
is PermalinkData.UserLink -> {
if (roomId == null) {
sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)?.toMatrixItem()
} else {
sessionHolder.getSafeActiveSession()?.getRoomMember(permalinkData.userId, roomId)?.toMatrixItem()
}
}
is PermalinkData.RoomLink -> {
if (permalinkData.eventId == null) {
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias)
if (permalinkData.isRoomAlias) {
MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
} else {
MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
}
} else {
// Exclude event link (used in reply events, we do not want to pill the "in reply to")
null
}
}
is PermalinkData.GroupLink -> {
val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId)
MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl)
}
else -> null
} ?: return null
return PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
}
}

View file

@ -27,6 +27,7 @@ import im.vector.app.core.platform.ButtonStateView
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.vector_invite_view.view.* import kotlinx.android.synthetic.main.vector_invite_view.view.*
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -73,7 +74,7 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
} }
} }
fun render(sender: User, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) { fun render(sender: RoomMemberSummary, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) {
if (mode == Mode.LARGE) { if (mode == Mode.LARGE) {
updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT } updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT }
avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView) avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView)

View file

@ -163,7 +163,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? { private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
val content = event.content?.toModel<RoomMemberContent>() ?: return null val content = event.content?.toModel<RoomMemberContent>() ?: return null
val roomId = event.roomId ?: return null val roomId = event.roomId ?: return null
val dName = event.senderId?.let { session.getUser(it)?.displayName } val dName = event.senderId?.let { session.getRoomMember(it, roomId)?.displayName }
if (Membership.INVITE == content.membership) { if (Membership.INVITE == content.membership) {
val body = noticeEventFormatter.format(event, dName, session.getRoomSummary(roomId)) val body = noticeEventFormatter.format(event, dName, session.getRoomSummary(roomId))
?: stringProvider.getString(R.string.notification_new_invitation) ?: stringProvider.getString(R.string.notification_new_invitation)

View file

@ -120,7 +120,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
null, null,
false, false,
System.currentTimeMillis(), System.currentTimeMillis(),
session.getUser(session.myUserId)?.displayName session.getRoomMember(session.myUserId, room.roomId)?.displayName
?: context?.getString(R.string.notification_sender_me), ?: context?.getString(R.string.notification_sender_me),
session.myUserId, session.myUserId,
message, message,