Merge pull request #1549 from vector-im/feature/bma/unknownMsgtype

Render unknown msgtype
This commit is contained in:
Benoit Marty 2023-10-12 10:56:35 +02:00 committed by GitHub
commit 7d985d4588
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 260 additions and 148 deletions

1
changelog.d/1539.bugfix Normal file
View file

@ -0,0 +1 @@
Render body of unknown msgtype in the timeline and in the room list

View file

@ -25,7 +25,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemNoticeContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.features.messages.impl.timeline.util.FileExtensionExtractor
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
@ -49,7 +48,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
) {
fun create(content: MessageContent, senderDisplayName: String): TimelineItemEventContent {
return when (val messageType = content.type ?: UnknownMessageType) {
return when (val messageType = content.type) {
is EmoteMessageType -> TimelineItemEmoteContent(
body = "* $senderDisplayName ${messageType.body}",
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* senderDisplayName"),
@ -131,7 +130,12 @@ class TimelineItemContentMessageFactory @Inject constructor(
htmlDocument = messageType.formatted?.toHtmlDocument(),
isEdited = content.isEdited,
)
UnknownMessageType -> TimelineItemUnknownContent
UnknownMessageType -> TimelineItemTextContent(
// Display the body as a fallback
body = content.body,
htmlDocument = null,
isEdited = content.isEdited,
)
}
}

View file

@ -106,9 +106,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
}
private fun processMessageContents(messageContent: MessageContent, senderDisplayName: String, isDmRoom: Boolean): CharSequence? {
val messageType: MessageType = messageContent.type ?: return null
val internalMessage = when (messageType) {
val internalMessage = when (val messageType: MessageType = messageContent.type) {
// Doesn't need a prefix
is EmoteMessageType -> {
return "* $senderDisplayName ${messageType.body}"
@ -132,7 +130,8 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
sp.getString(CommonStrings.common_audio)
}
UnknownMessageType -> {
sp.getString(CommonStrings.common_unsupported_event)
// Display the body as a fallback
messageContent.body
}
is NoticeMessageType -> {
messageType.body

View file

@ -202,10 +202,11 @@ class DefaultRoomLastMessageFormatterTests {
is FileMessageType -> "File"
is LocationMessageType -> "Shared location"
is EmoteMessageType -> "* $senderName ${type.body}"
is TextMessageType, is NoticeMessageType -> body
UnknownMessageType -> "Unsupported event"
is TextMessageType,
is NoticeMessageType,
UnknownMessageType -> body
}
Truth.assertWithMessage("$type was not properly handled").that(result).isEqualTo(expectedResult)
Truth.assertWithMessage("$type was not properly handled for DM").that(result).isEqualTo(expectedResult)
}
// Verify results of Room mode
@ -217,9 +218,10 @@ class DefaultRoomLastMessageFormatterTests {
is ImageMessageType -> "$senderName: Image"
is FileMessageType -> "$senderName: File"
is LocationMessageType -> "$senderName: Shared location"
is TextMessageType,
is NoticeMessageType,
UnknownMessageType -> "$senderName: $body"
is EmoteMessageType -> "* $senderName ${type.body}"
is TextMessageType, is NoticeMessageType -> "$senderName: $body"
UnknownMessageType -> "$senderName: Unsupported event"
}
val shouldCreateAnnotatedString = when (type) {
is VideoMessageType -> true
@ -236,7 +238,7 @@ class DefaultRoomLastMessageFormatterTests {
.that(result)
.isInstanceOf(AnnotatedString::class.java)
}
Truth.assertWithMessage("$type was not properly handled").that(string).isEqualTo(expectedResult)
Truth.assertWithMessage("$type was not properly handled for room").that(string).isEqualTo(expectedResult)
}
}

View file

@ -16,13 +16,8 @@
package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.poll.PollKind
@ -33,36 +28,9 @@ data class MessageContent(
val inReplyTo: InReplyTo?,
val isEdited: Boolean,
val isThreaded: Boolean,
val type: MessageType?
val type: MessageType
) : EventContent
sealed interface InReplyTo {
/** The event details are not loaded yet. We can fetch them. */
data class NotLoaded(val eventId: EventId) : InReplyTo
/** The event details are pending to be fetched. We should **not** fetch them again. */
data object Pending : InReplyTo
/** The event details are available. */
data class Ready(
val eventId: EventId,
val content: EventContent,
val senderId: UserId,
val senderDisplayName: String?,
val senderAvatarUrl: String?,
) : InReplyTo
/**
* Fetching the event details failed.
*
* We can try to fetch them again **with a proper retry strategy**, but not blindly:
*
* If the reason for the failure is consistent on the server, we'd enter a loop
* where we keep trying to fetch the same event.
* */
data object Error : InReplyTo
}
data object RedactedContent : EventContent
data class StickerContent(
@ -125,105 +93,3 @@ data class FailedToParseStateContent(
) : EventContent
data object UnknownContent : EventContent
sealed interface MessageType
data object UnknownMessageType : MessageType
enum class MessageFormat {
HTML, UNKNOWN
}
data class FormattedBody(
val format: MessageFormat,
val body: String
)
data class EmoteMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
data class ImageMessageType(
val body: String,
val source: MediaSource,
val info: ImageInfo?
) : MessageType
data class LocationMessageType(
val body: String,
val geoUri: String,
val description: String?,
) : MessageType
data class AudioMessageType(
val body: String,
val source: MediaSource,
val info: AudioInfo?
) : MessageType
data class VideoMessageType(
val body: String,
val source: MediaSource,
val info: VideoInfo?
) : MessageType
data class FileMessageType(
val body: String,
val source: MediaSource,
val info: FileInfo?
) : MessageType
data class NoticeMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
data class TextMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
enum class MembershipChange {
NONE,
ERROR,
JOINED,
LEFT,
BANNED,
UNBANNED,
KICKED,
INVITED,
KICKED_AND_BANNED,
INVITATION_ACCEPTED,
INVITATION_REJECTED,
INVITATION_REVOKED,
KNOCKED,
KNOCK_ACCEPTED,
KNOCK_RETRACTED,
KNOCK_DENIED,
NOT_IMPLEMENTED;
}
sealed interface OtherState {
data object PolicyRuleRoom : OtherState
data object PolicyRuleServer : OtherState
data object PolicyRuleUser : OtherState
data object RoomAliases : OtherState
data class RoomAvatar(val url: String?) : OtherState
data object RoomCanonicalAlias : OtherState
data object RoomCreate : OtherState
data object RoomEncryption : OtherState
data object RoomGuestAccess : OtherState
data object RoomHistoryVisibility : OtherState
data object RoomJoinRules : OtherState
data class RoomName(val name: String?) : OtherState
data object RoomPinnedEvents : OtherState
data object RoomPowerLevels : OtherState
data object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(val displayName: String?) : OtherState
data object RoomTombstone : OtherState
data class RoomTopic(val topic: String?) : OtherState
data object SpaceChild : OtherState
data object SpaceParent : OtherState
data class Custom(val eventType: String) : OtherState
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.timeline.item.event
data class FormattedBody(
val format: MessageFormat,
val body: String
)

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
sealed interface InReplyTo {
/** The event details are not loaded yet. We can fetch them. */
data class NotLoaded(val eventId: EventId) : InReplyTo
/** The event details are pending to be fetched. We should **not** fetch them again. */
data object Pending : InReplyTo
/** The event details are available. */
data class Ready(
val eventId: EventId,
val content: EventContent,
val senderId: UserId,
val senderDisplayName: String?,
val senderAvatarUrl: String?,
) : InReplyTo
/**
* Fetching the event details failed.
*
* We can try to fetch them again **with a proper retry strategy**, but not blindly:
*
* If the reason for the failure is consistent on the server, we'd enter a loop
* where we keep trying to fetch the same event.
* */
data object Error : InReplyTo
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.timeline.item.event
enum class MembershipChange {
NONE,
ERROR,
JOINED,
LEFT,
BANNED,
UNBANNED,
KICKED,
INVITED,
KICKED_AND_BANNED,
INVITATION_ACCEPTED,
INVITATION_REJECTED,
INVITATION_REVOKED,
KNOCKED,
KNOCK_ACCEPTED,
KNOCK_RETRACTED,
KNOCK_DENIED,
NOT_IMPLEMENTED;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.timeline.item.event
enum class MessageFormat {
HTML, UNKNOWN
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.VideoInfo
sealed interface MessageType
data object UnknownMessageType : MessageType
data class EmoteMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
data class ImageMessageType(
val body: String,
val source: MediaSource,
val info: ImageInfo?
) : MessageType
data class LocationMessageType(
val body: String,
val geoUri: String,
val description: String?,
) : MessageType
data class AudioMessageType(
val body: String,
val source: MediaSource,
val info: AudioInfo?
) : MessageType
data class VideoMessageType(
val body: String,
val source: MediaSource,
val info: VideoInfo?
) : MessageType
data class FileMessageType(
val body: String,
val source: MediaSource,
val info: FileInfo?
) : MessageType
data class NoticeMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
data class TextMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.timeline.item.event
sealed interface OtherState {
data object PolicyRuleRoom : OtherState
data object PolicyRuleServer : OtherState
data object PolicyRuleUser : OtherState
data object RoomAliases : OtherState
data class RoomAvatar(val url: String?) : OtherState
data object RoomCanonicalAlias : OtherState
data object RoomCreate : OtherState
data object RoomEncryption : OtherState
data object RoomGuestAccess : OtherState
data object RoomHistoryVisibility : OtherState
data object RoomJoinRules : OtherState
data class RoomName(val name: String?) : OtherState
data object RoomPinnedEvents : OtherState
data object RoomPowerLevels : OtherState
data object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(val displayName: String?) : OtherState
data object RoomTombstone : OtherState
data class RoomTopic(val topic: String?) : OtherState
data object SpaceChild : OtherState
data object SpaceParent : OtherState
data class Custom(val eventType: String) : OtherState
}