diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index 1f3a94b2ad..b665ef7116 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -43,8 +43,9 @@ sealed class MatrixItem( override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar) } + // TODO is it correct to represent it by a Matrix Item ? => to confirm data class EveryoneInRoomItem(override val id: String, - override val displayName: String? = null, + override val displayName: String = NOTIFY_EVERYONE, override val avatarUrl: String? = null, val roomDisplayName: String? = null) : MatrixItem(id, displayName, avatarUrl) { @@ -190,7 +191,7 @@ fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) { fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) -fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(roomId, MatrixItem.NOTIFY_EVERYONE, avatarUrl, displayName) +fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(id = roomId, avatarUrl = avatarUrl, roomDisplayName = displayName) // If no name is available, use room alias as Riot-Web does fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt index 33cb0db243..ccbfbfcded 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.send.pills import android.text.SpannableString import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan +import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver import java.util.Collections import javax.inject.Inject @@ -51,6 +52,8 @@ internal class TextPillsUtils @Inject constructor( val pills = spannableString ?.getSpans(0, text.length, MatrixItemSpan::class.java) ?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) } + // we use the raw text for @room notification instead of a link + ?.filterNot { it.span.matrixItem is MatrixItem.EveryoneInRoomItem } ?.toMutableList() ?.takeIf { it.isNotEmpty() } ?: return null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt index 38548377aa..be5f9c0bb4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt @@ -238,7 +238,7 @@ class AutoCompleter @AssistedInject constructor( // Adding trailing space " " or ": " if the user started mention someone val displayNameSuffix = - if (firstChar == TRIGGER_AUTO_COMPLETE_MEMBERS && startIndex == 0) { + if (matrixItem is MatrixItem.UserItem) { ": " } else { " " diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 0c836748c8..9ab532e5be 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -549,6 +549,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageTextItem? { + // TODO process body to add pills for @room texts: create a dedicated renderer with PillsPostProcessor ? val bindingOptions = spanUtils.getBindingOptions(body) val linkifiedBody = body.linkify(callback) diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt index 8376d25065..b2dc190c6f 100644 --- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt +++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt @@ -36,58 +36,109 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI private val context: Context, private val avatarRenderer: AvatarRenderer, private val sessionHolder: ActiveSessionHolder) : - EventHtmlRenderer.PostProcessor { + EventHtmlRenderer.PostProcessor { @AssistedFactory interface Factory { fun create(roomId: String?): PillsPostProcessor } + /////////////////////////////////////////////////////////////////////////// + // SPECIALIZATION + /////////////////////////////////////////////////////////////////////////// + override fun afterRender(renderedText: Spannable) { addPillSpans(renderedText, roomId) } + /////////////////////////////////////////////////////////////////////////// + // HELPER METHODS + /////////////////////////////////////////////////////////////////////////// + private fun addPillSpans(renderedText: Spannable, roomId: String?) { + addLinkSpans(renderedText, roomId) + roomId?.let { id -> addRawTextSpans(renderedText, id) } + } + + private fun addPillSpan( + renderedText: Spannable, + pillSpan: PillImageSpan, + startSpan: Int, + endSpan: Int + ) { + renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + private fun addLinkSpans(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) + addPillSpan(renderedText, pillSpan, startSpan, endSpan) } } + // TODO move this into another PostProcessor when there is no html + private fun addRawTextSpans(renderedText: Spannable, roomId: String) { + if (renderedText.contains(MatrixItem.NOTIFY_EVERYONE)) { + addNotifyEveryoneSpans(renderedText, roomId) + } + } + + private fun addNotifyEveryoneSpans(renderedText: Spannable, roomId: String) { + val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId) + val matrixItem = MatrixItem.EveryoneInRoomItem( + id = roomId, + avatarUrl = room?.avatarUrl, + roomDisplayName = room?.displayName + ) + val pillSpan = createPillImageSpan(matrixItem) + + // search for notify everyone text + var foundIndex = renderedText.indexOf(MatrixItem.NOTIFY_EVERYONE, 0) + while (foundIndex >= 0) { + val endSpan = foundIndex + MatrixItem.NOTIFY_EVERYONE.length + addPillSpan(renderedText, pillSpan, foundIndex, endSpan) + foundIndex = renderedText.indexOf(MatrixItem.NOTIFY_EVERYONE, endSpan) + } + } + + private fun createPillImageSpan(matrixItem: MatrixItem) = + PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem) + private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? { - // TODO handle @room text to create associated chip - 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) - } + val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) { + is PermalinkData.UserLink -> permalinkData.toMatrixItem(roomId) + is PermalinkData.RoomLink -> permalinkData.toMatrixItem() + is PermalinkData.GroupLink -> permalinkData.toMatrixItem() else -> null } ?: return null - return PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem) + return createPillImageSpan(matrixItem) + } + + private fun PermalinkData.UserLink.toMatrixItem(roomId: String?): MatrixItem? = + if (roomId == null) { + sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem() + } else { + sessionHolder.getSafeActiveSession()?.getRoomMember(userId, roomId)?.toMatrixItem() + } + + private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem? = + if (eventId == null) { + val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomIdOrAlias) + when { + isRoomAlias -> MatrixItem.RoomAliasItem(roomIdOrAlias, room?.displayName, room?.avatarUrl) + else -> MatrixItem.RoomItem(roomIdOrAlias, room?.displayName, room?.avatarUrl) + } + } else { + // Exclude event link (used in reply events, we do not want to pill the "in reply to") + null + } + + private fun PermalinkData.GroupLink.toMatrixItem(): MatrixItem? { + val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(groupId) + return MatrixItem.GroupItem(groupId, group?.displayName, group?.avatarUrl) } }