Add legacy call invite state events and notifications (#2552)

* Add state timeline events and notifications for legacy call invites

* Update screenshots

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-03-14 16:29:06 +01:00 committed by GitHub
parent 207d142bfd
commit 67d79059f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
179 changed files with 211 additions and 13 deletions

View file

@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParse
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
@ -109,6 +110,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> {
prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom)
}
is LegacyCallInviteContent -> sp.getString(CommonStrings.common_call_invite)
}?.take(MAX_SAFE_LENGTH)
}

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
@ -59,6 +60,9 @@ class DefaultTimelineEventFormatter @Inject constructor(
is StateContent -> {
stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.Timeline)
}
is LegacyCallInviteContent -> {
sp.getString(CommonStrings.common_call_invite)
}
RedactedContent,
is StickerContent,
is PollContent,

View file

@ -49,7 +49,9 @@ data class NotificationData(
sealed interface NotificationContent {
sealed interface MessageLike : NotificationContent {
data object CallAnswer : MessageLike
data object CallInvite : MessageLike
data class CallInvite(
val senderId: UserId,
) : MessageLike
data object CallHangup : MessageLike
data object CallCandidates : MessageLike
data object KeyVerificationReady : MessageLike

View file

@ -98,4 +98,6 @@ data class FailedToParseStateContent(
val error: String
) : EventContent
data object LegacyCallInviteContent : EventContent
data object UnknownContent : EventContent

View file

@ -78,7 +78,7 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon
MessageLikeEventContent.CallAnswer -> NotificationContent.MessageLike.CallAnswer
MessageLikeEventContent.CallCandidates -> NotificationContent.MessageLike.CallCandidates
MessageLikeEventContent.CallHangup -> NotificationContent.MessageLike.CallHangup
MessageLikeEventContent.CallInvite -> NotificationContent.MessageLike.CallInvite
MessageLikeEventContent.CallInvite -> NotificationContent.MessageLike.CallInvite(senderId)
MessageLikeEventContent.KeyVerificationAccept -> NotificationContent.MessageLike.KeyVerificationAccept
MessageLikeEventContent.KeyVerificationCancel -> NotificationContent.MessageLike.KeyVerificationCancel
MessageLikeEventContent.KeyVerificationDone -> NotificationContent.MessageLike.KeyVerificationDone

View file

@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
@ -120,6 +121,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
data = kind.msg.map()
)
}
is TimelineItemContentKind.CallInvite -> LegacyCallInviteContent
else -> UnknownContent
}
}

View file

@ -141,10 +141,26 @@ class NotifiableEventResolver @Inject constructor(
}
NotificationContent.MessageLike.CallAnswer,
NotificationContent.MessageLike.CallCandidates,
NotificationContent.MessageLike.CallHangup,
NotificationContent.MessageLike.CallInvite -> null.also {
NotificationContent.MessageLike.CallHangup -> null.also {
Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}")
}
is NotificationContent.MessageLike.CallInvite -> {
buildNotifiableMessageEvent(
sessionId = userId,
senderId = content.senderId,
roomId = roomId,
eventId = eventId,
noisy = isNoisy,
timestamp = this.timestamp,
senderName = null,
body = stringProvider.getString(CommonStrings.common_call_invite),
imageUriString = fetchImageIfPresent(client)?.toString(),
roomName = roomDisplayName,
roomIsDirect = isDirect,
roomAvatarPath = roomAvatarUrl,
senderAvatarPath = senderAvatarUrl,
)
}
NotificationContent.MessageLike.KeyVerificationAccept,
NotificationContent.MessageLike.KeyVerificationCancel,
NotificationContent.MessageLike.KeyVerificationDone,

View file

@ -152,9 +152,11 @@ class RoomGroupMessageCreator @Inject constructor(
private fun createFirstMessageSummaryLine(event: NotifiableMessageEvent, roomName: String, roomIsDirect: Boolean): CharSequence {
return if (roomIsDirect) {
buildSpannedString {
inSpans(StyleSpan(Typeface.BOLD)) {
append(event.senderName)
append(": ")
event.senderName?.let {
inSpans(StyleSpan(Typeface.BOLD)) {
append(it)
append(": ")
}
}
append(event.description)
}
@ -163,8 +165,10 @@ class RoomGroupMessageCreator @Inject constructor(
inSpans(StyleSpan(Typeface.BOLD)) {
append(roomName)
append(": ")
append(event.senderName)
append(" ")
event.senderName?.let {
append(it)
append(" ")
}
}
append(event.description)
}

View file

@ -442,10 +442,45 @@ class NotifiableEventResolverTest {
assertThat(result).isEqualTo(expectedResult)
}
@Test
fun `resolve CallInvite`() = runTest {
val sut = createNotifiableEventResolver(
notificationResult = Result.success(
createNotificationData(
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2)
)
)
)
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = NotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
eventId = AN_EVENT_ID,
editedEventId = null,
canBeReplaced = false,
senderId = A_USER_ID_2,
noisy = false,
timestamp = A_TIMESTAMP,
senderName = null,
body = "Call in progress (unsupported)",
imageUriString = null,
threadId = null,
roomName = null,
roomIsDirect = false,
roomAvatarPath = null,
senderAvatarPath = null,
soundName = null,
outGoingMessage = false,
outGoingMessageFailed = false,
isRedacted = false,
isUpdated = false
)
assertThat(result).isEqualTo(expectedResult)
}
@Test
fun `resolve null cases`() {
testNull(NotificationContent.MessageLike.CallAnswer)
testNull(NotificationContent.MessageLike.CallInvite)
testNull(NotificationContent.MessageLike.CallHangup)
testNull(NotificationContent.MessageLike.CallCandidates)
testNull(NotificationContent.MessageLike.KeyVerificationReady)