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 07858ca5f7
commit a7093bae8b
179 changed files with 211 additions and 13 deletions

1
changelog.d/2485.feature Normal file
View File

@@ -0,0 +1 @@
Add state timeline events and notifications for legacy call invites.

View File

@@ -65,6 +65,7 @@ dependencies {
implementation(projects.libraries.testtags)
implementation(projects.features.networkmonitor.api)
implementation(projects.services.analytics.api)
implementation(projects.services.toolbox.api)
implementation(libs.coil.compose)
implementation(libs.datetime)
implementation(libs.jsoup)
@@ -86,6 +87,7 @@ dependencies {
testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.features.messages.test)
testImplementation(projects.services.analytics.test)
testImplementation(projects.services.toolbox.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.mediaupload.test)

View File

@@ -50,6 +50,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
@@ -400,6 +401,7 @@ class MessagesPresenter @AssistedInject constructor(
is TimelineItemRedactedContent,
is TimelineItemStateContent,
is TimelineItemEncryptedContent,
is TimelineItemLegacyCallInviteContent,
is TimelineItemUnknownContent -> null
}
val composerMode = MessageComposerMode.Reply(

View File

@@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
@@ -146,6 +147,13 @@ class ActionListPresenter @Inject constructor(
}
}
}
is TimelineItemLegacyCallInviteContent -> {
buildList {
if (isDeveloperModeEnabled) {
add(TimelineItemAction.ViewSource)
}
}
}
else -> buildList<TimelineItemAction> {
if (timelineItem.isRemote) {
// Can only reply or forward messages already uploaded to the server

View File

@@ -60,6 +60,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
@@ -259,6 +260,9 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
is TimelineItemPollContent -> {
content = { ContentForBody(textContent) }
}
is TimelineItemLegacyCallInviteContent -> {
content = { ContentForBody(textContent) }
}
}
Row(modifier = modifier) {
icon()

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.session.SessionState
import io.element.android.libraries.matrix.api.core.EventId
@@ -56,7 +57,7 @@ internal fun TimelineItemRow(
)
}
is TimelineItem.Event -> {
if (timelineItem.content is TimelineItemStateContent) {
if (timelineItem.content is TimelineItemStateContent || timelineItem.content is TimelineItemLegacyCallInviteContent) {
TimelineItemStateEventRow(
event = timelineItem,
renderReadReceipts = renderReadReceipts,

View File

@@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
@@ -96,6 +97,7 @@ fun TimelineItemEventContentView(
onContentLayoutChanged = onContentLayoutChanged,
modifier = modifier
)
is TimelineItemLegacyCallInviteContent -> TimelineItemLegacyCallInviteView(modifier = modifier)
is TimelineItemStateContent -> TimelineItemStateView(
content = content,
modifier = modifier

View File

@@ -0,0 +1,66 @@
/*
* 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.features.messages.impl.timeline.components.event
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun TimelineItemLegacyCallInviteView(
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = CompoundIcons.VoiceCall(),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
color = MaterialTheme.colorScheme.secondary,
style = ElementTheme.typography.fontBodyMdRegular,
text = stringResource(CommonStrings.common_call_invite),
textAlign = TextAlign.Center,
)
}
}
@PreviewsDayNight
@Composable
internal fun TimelineItemLegacyCallInviteViewPreview() = ElementPreview {
TimelineItemLegacyCallInviteView()
}

View File

@@ -17,10 +17,12 @@
package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
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
@@ -43,7 +45,7 @@ class TimelineItemContentFactory @Inject constructor(
private val profileChangeFactory: TimelineItemContentProfileChangeFactory,
private val stateFactory: TimelineItemContentStateFactory,
private val failedToParseMessageFactory: TimelineItemContentFailedToParseMessageFactory,
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory,
) {
suspend fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
return when (val itemContent = eventTimelineItem.content) {
@@ -56,6 +58,7 @@ class TimelineItemContentFactory @Inject constructor(
is ProfileChangeContent -> profileChangeFactory.create(eventTimelineItem)
is RedactedContent -> redactedMessageFactory.create(itemContent)
is RoomMembershipContent -> roomMembershipFactory.create(eventTimelineItem)
is LegacyCallInviteContent -> TimelineItemLegacyCallInviteContent
is StateContent -> stateFactory.create(eventTimelineItem)
is StickerContent -> stickerFactory.create(itemContent)
is PollContent -> pollFactory.create(eventTimelineItem, itemContent)

View File

@@ -21,6 +21,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
@@ -35,6 +36,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
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
@@ -63,7 +65,8 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean {
is TimelineItemPollContent,
is TimelineItemVoiceContent,
TimelineItemRedactedContent,
TimelineItemUnknownContent -> false
TimelineItemUnknownContent,
is TimelineItemLegacyCallInviteContent -> false
is TimelineItemProfileChangeContent,
is TimelineItemRoomMembershipContent,
is TimelineItemStateEventContent -> true
@@ -77,16 +80,19 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean {
*/
internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
return when (event.content) {
// Can be grouped
is FailedToParseMessageLikeContent,
is MessageContent,
RedactedContent,
is StickerContent,
is PollContent,
is UnableToDecryptContent -> true
// Can't be grouped
is FailedToParseStateContent,
is ProfileChangeContent,
is RoomMembershipContent,
UnknownContent,
is LegacyCallInviteContent,
is StateContent -> false
}
}

View File

@@ -25,6 +25,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.PollContent
@@ -132,5 +133,6 @@ internal fun InReplyToDetails.metadata(): InReplyToMetadata? = when (eventConten
is RoomMembershipContent,
is StateContent,
UnknownContent,
is LegacyCallInviteContent,
null -> null
}

View File

@@ -41,6 +41,7 @@ fun TimelineItemEventContent.canBeCopied(): Boolean =
fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
when (this) {
is TimelineItemRedactedContent,
is TimelineItemLegacyCallInviteContent,
is TimelineItemStateContent -> false
else -> true
}
@@ -63,6 +64,7 @@ fun TimelineItemEventContent.canReact(): Boolean =
is TimelineItemVideoContent -> true
is TimelineItemStateContent,
is TimelineItemRedactedContent,
is TimelineItemLegacyCallInviteContent,
TimelineItemUnknownContent -> false
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 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.features.messages.impl.timeline.model.event
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
data object TimelineItemLegacyCallInviteContent : TimelineItemEventContent {
override val type: String
get() = EventType.CALL_INVITE
}

View File

@@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
@@ -63,6 +64,7 @@ class MessageSummaryFormatterImpl @Inject constructor(
is TimelineItemVideoContent -> context.getString(CommonStrings.common_video)
is TimelineItemFileContent -> context.getString(CommonStrings.common_file)
is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio)
is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_call_invite)
}.take(MAX_SAFE_LENGTH)
}
}

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)

Some files were not shown because too many files have changed in this diff Show More