Merge pull request #6649 from element-hq/feature/valere/call/decline_timeline_rendering

feat: Update call started timeline item + declined support
This commit is contained in:
Valere Fedronic 2026-05-12 16:32:51 +02:00 committed by GitHub
commit 388dd9be67
16 changed files with 360 additions and 56 deletions

View file

@ -8,6 +8,7 @@
package io.element.android.features.messages.impl.timeline.components package io.element.android.features.messages.impl.timeline.components
import androidx.annotation.StringRes
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -22,17 +23,19 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -42,6 +45,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun TimelineItemCallNotifyView( internal fun TimelineItemCallNotifyView(
timelineRoomInfo: TimelineRoomInfo,
event: TimelineItem.Event, event: TimelineItem.Event,
content: TimelineItemRtcNotificationContent, content: TimelineItemRtcNotificationContent,
onLongClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit,
@ -62,37 +66,22 @@ internal fun TimelineItemCallNotifyView(
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Avatar( Icon(
avatarData = event.senderAvatar, modifier = Modifier.size(20.sp.toDp()),
avatarType = AvatarType.User, imageVector = getIcon(timelineRoomInfo, content),
contentDescription = null,
tint = ElementTheme.colors.iconSecondary,
) )
Column(modifier = Modifier.weight(1f)) {
Text( Text(
text = event.safeSenderName, modifier = Modifier.weight(1f),
style = ElementTheme.typography.fontBodyLgMedium, text = stringResource(getTextRes(timelineRoomInfo, content)),
maxLines = 1, style = ElementTheme.typography.fontBodyMdRegular,
overflow = TextOverflow.Ellipsis, color = ElementTheme.colors.textSecondary,
) maxLines = 1,
Row( overflow = TextOverflow.Ellipsis,
horizontalArrangement = Arrangement.spacedBy(4.dp), )
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.size(20.sp.toDp()),
imageVector =
if (content.callIntent == CallIntent.AUDIO) CompoundIcons.VoiceCallSolid() else CompoundIcons.VideoCallSolid(),
contentDescription = null,
tint = ElementTheme.colors.iconSecondary,
)
Text(
text = stringResource(CommonStrings.common_call_started),
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Text( Text(
text = event.sentTime, text = event.sentTime,
style = ElementTheme.typography.fontBodyMdRegular, style = ElementTheme.typography.fontBodyMdRegular,
@ -103,19 +92,56 @@ internal fun TimelineItemCallNotifyView(
} }
} }
@StringRes
private fun getTextRes(
timelineRoomInfo: TimelineRoomInfo,
content: TimelineItemRtcNotificationContent
): Int = if (timelineRoomInfo.isDm) {
when (content.state) {
is RtcNotificationState.Declined -> {
if (content.state.byMe) CommonStrings.common_call_you_declined else CommonStrings.common_call_declined
}
RtcNotificationState.Started -> CommonStrings.common_call_started
}
} else {
// In Rooms, do not show declined info.
CommonStrings.common_call_started
}
@Composable
private fun getIcon(
timelineRoomInfo: TimelineRoomInfo,
content: TimelineItemRtcNotificationContent
): ImageVector {
val showAsDeclined = timelineRoomInfo.isDm && content.state is RtcNotificationState.Declined
val icon = if (showAsDeclined) {
if (content.callIntent == CallIntent.AUDIO) CompoundIcons.VoiceCallDeclinedSolid() else CompoundIcons.VideoCallDeclinedSolid()
} else {
if (content.callIntent == CallIntent.AUDIO) CompoundIcons.VoiceCallSolid() else CompoundIcons.VideoCallSolid()
}
return icon
}
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun TimelineItemCallNotifyViewPreview() = ElementPreview { internal fun TimelineItemCallNotifyViewPreview() = ElementPreview {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(modifier = Modifier.padding(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp)) {
listOf( listOf(false, true).forEach { isDm ->
TimelineItemRtcNotificationContent(CallIntent.AUDIO), listOf(CallIntent.AUDIO, CallIntent.VIDEO).forEach { callIntent ->
TimelineItemRtcNotificationContent(CallIntent.VIDEO), listOf(
).forEach { content -> RtcNotificationState.Started,
TimelineItemCallNotifyView( RtcNotificationState.Declined(byMe = false),
event = aTimelineItemEvent(content = content), RtcNotificationState.Declined(byMe = true),
content = content, ).forEach { state ->
onLongClick = {}, val content = TimelineItemRtcNotificationContent(callIntent, state)
) TimelineItemCallNotifyView(
timelineRoomInfo = aTimelineRoomInfo(isDm = isDm),
event = aTimelineItemEvent(content = content),
content = content,
onLongClick = {},
)
}
}
} }
} }
} }

View file

@ -125,6 +125,7 @@ internal fun TimelineItemRow(
is TimelineItemRtcNotificationContent -> { is TimelineItemRtcNotificationContent -> {
TimelineItemCallNotifyView( TimelineItemCallNotifyView(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
timelineRoomInfo = timelineRoomInfo,
event = timelineItem, event = timelineItem,
content = timelineItem.content, content = timelineItem.content,
onLongClick = onLongClick, onLongClick = onLongClick,

View file

@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline.factories.event
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.location.api.Location import io.element.android.features.location.api.Location
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent 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.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
@ -104,7 +105,12 @@ class TimelineItemContentFactory(
is PollContent -> pollFactory.create(eventId, isEditable, isOutgoing, itemContent) is PollContent -> pollFactory.create(eventId, isEditable, isOutgoing, itemContent)
is UnableToDecryptContent -> utdFactory.create(itemContent) is UnableToDecryptContent -> utdFactory.create(itemContent)
is CallNotifyContent -> TimelineItemRtcNotificationContent( is CallNotifyContent -> TimelineItemRtcNotificationContent(
itemContent.callIntent callIntent = itemContent.callIntent,
state = if (itemContent.declinedBy.isEmpty()) {
RtcNotificationState.Started
} else {
RtcNotificationState.Declined(itemContent.declinedBy.any { it == sessionId })
}
) )
is UnknownContent -> TimelineItemUnknownContent is UnknownContent -> TimelineItemUnknownContent
is LiveLocationContent -> { is LiveLocationContent -> {

View file

@ -9,7 +9,19 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
import io.element.android.libraries.matrix.api.notification.CallIntent import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
class TimelineItemRtcNotificationContent(val callIntent: CallIntent) : TimelineItemEventContent { // State of the call, for now only isDeclined but in the future could be missed, active.
override val type: String = "org.matrix.msc4075.rtc.notification" sealed interface RtcNotificationState {
/** Some users have declined, byMe indicates if the current user is one of them. */
data class Declined(val byMe: Boolean) : RtcNotificationState
object Started : RtcNotificationState
}
class TimelineItemRtcNotificationContent(
val callIntent: CallIntent,
val state: RtcNotificationState,
) : TimelineItemEventContent {
override val type: String = EventType.RTC_NOTIFICATION
} }

View file

@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.utils.messagesummary
import android.content.Context import android.content.Context
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
@ -56,7 +57,16 @@ class DefaultMessageSummaryFormatter(
is TimelineItemFileContent -> context.getString(CommonStrings.common_file) is TimelineItemFileContent -> context.getString(CommonStrings.common_file)
is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio) is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio)
is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_unsupported_call) is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_unsupported_call)
is TimelineItemRtcNotificationContent -> context.getString(CommonStrings.common_call_started) is TimelineItemRtcNotificationContent -> when (content.state) {
is RtcNotificationState.Declined -> {
if (content.state.byMe) {
context.getString(CommonStrings.common_call_you_declined)
} else {
context.getString(CommonStrings.common_call_declined)
}
}
RtcNotificationState.Started -> context.getString(CommonStrings.common_call_started)
}
} }
// Truncate the message to a safe length to avoid crashes in Compose // Truncate the message to a safe length to avoid crashes in Compose
.toSafeLength() .toSafeLength()

View file

@ -17,6 +17,7 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser
import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aMessageEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
@ -1169,7 +1170,7 @@ class ActionListPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemRtcNotificationContent(callIntent = CallIntent.VIDEO), content = TimelineItemRtcNotificationContent(callIntent = CallIntent.VIDEO, state = RtcNotificationState.Started),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvent.ComputeForMessage( ActionListEvent.ComputeForMessage(

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.utils
import android.content.Context
import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.api.Location
import io.element.android.features.messages.impl.timeline.model.event.RtcNotificationState
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent.Mode
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.utils.messagesummary.DefaultMessageSummaryFormatter
import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.timeline.aProfileDetails
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
class DefaultMessageSummaryFormatterTest {
private val formatter = DefaultMessageSummaryFormatter(
RuntimeEnvironment.getApplication() as Context
)
@Test
@Config(qualifiers = "en")
fun `format call notification started`() {
val expected = formatter.format(
TimelineItemRtcNotificationContent(
callIntent = CallIntent.VIDEO,
state = RtcNotificationState.Started
)
)
assertThat(expected).isEqualTo("Call started")
}
@Test
@Config(qualifiers = "en")
fun `format call notification declined by me`() {
val expected = formatter.format(
TimelineItemRtcNotificationContent(
callIntent = CallIntent.VIDEO,
state = RtcNotificationState.Declined(byMe = true)
)
)
assertThat(expected).isEqualTo("You declined a call")
}
@Test
@Config(qualifiers = "en")
fun `format call notification declined`() {
val expected = formatter.format(
TimelineItemRtcNotificationContent(
callIntent = CallIntent.VIDEO,
state = RtcNotificationState.Declined(byMe = false)
)
)
assertThat(expected).isEqualTo("Call declined")
}
@Test
@Config(qualifiers = "en")
fun `format live location`() {
val expected = formatter.format(
aLocationContent(isLive = true)
)
assertThat(expected).isEqualTo("Shared live location")
}
@Test
@Config(qualifiers = "en")
fun `format static location`() {
val expected = formatter.format(
aLocationContent(isLive = false)
)
assertThat(expected).isEqualTo("Shared location")
}
}
private fun aLocationContent(isLive: Boolean) = TimelineItemLocationContent(
senderId = A_USER_ID,
senderProfile = aProfileDetails(),
description = null,
assetType = null,
mode = if (isLive) {
Mode.Live(
lastKnownLocation = Location.fromGeoUri("geo:1,5"),
isActive = true,
endsAt = "",
endTimestamp = 0,
isOwnUser = true,
)
} else {
Mode.Static(
location = Location.fromGeoUri("geo:1,5")!!,
)
}
)

View file

@ -54,6 +54,7 @@ class DefaultRoomLatestEventFormatter(
private val roomMembershipContentFormatter: RoomMembershipContentFormatter, private val roomMembershipContentFormatter: RoomMembershipContentFormatter,
private val profileChangeContentFormatter: ProfileChangeContentFormatter, private val profileChangeContentFormatter: ProfileChangeContentFormatter,
private val stateContentFormatter: StateContentFormatter, private val stateContentFormatter: StateContentFormatter,
private val rtcNotificationContentFormatter: RtcNotificationContentFormatter,
private val permalinkParser: PermalinkParser, private val permalinkParser: PermalinkParser,
) : RoomLatestEventFormatter { ) : RoomLatestEventFormatter {
override fun format( override fun format(
@ -121,7 +122,7 @@ class DefaultRoomLatestEventFormatter(
message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing)
} }
is LegacyCallInviteContent -> sp.getString(CommonStrings.common_unsupported_call) is LegacyCallInviteContent -> sp.getString(CommonStrings.common_unsupported_call)
is CallNotifyContent -> sp.getString(CommonStrings.common_call_started) is CallNotifyContent -> rtcNotificationContentFormatter.format(content, isDmRoom)
}?.take(DEFAULT_SAFE_LENGTH) }?.take(DEFAULT_SAFE_LENGTH)
} }

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.eventformatter.impl
import dev.zacsweers.metro.Inject
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
@Inject
class RtcNotificationContentFormatter(
private val matrixClient: MatrixClient,
private val sp: StringProvider,
) {
fun format(
content: CallNotifyContent,
isDm: Boolean,
): CharSequence {
return if (isDm) {
val isDeclined = content.declinedBy.isNotEmpty()
val isDeclinedByMe = content.declinedBy.any { matrixClient.isMe(it) }
if (isDeclinedByMe) {
sp.getString(CommonStrings.common_call_you_declined)
} else if (isDeclined) {
sp.getString(CommonStrings.common_call_declined)
} else {
sp.getString(CommonStrings.common_call_started)
}
} else {
sp.getString(CommonStrings.common_call_started)
}
}
}

View file

@ -74,7 +74,8 @@ class DefaultRoomLatestEventFormatterTest {
roomMembershipContentFormatter = RoomMembershipContentFormatter(fakeMatrixClient, stringProvider), roomMembershipContentFormatter = RoomMembershipContentFormatter(fakeMatrixClient, stringProvider),
profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider), profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider),
stateContentFormatter = StateContentFormatter(stringProvider), stateContentFormatter = StateContentFormatter(stringProvider),
permalinkParser = FakePermalinkParser(), rtcNotificationContentFormatter = RtcNotificationContentFormatter(fakeMatrixClient, stringProvider),
permalinkParser = FakePermalinkParser()
) )
} }

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.eventformatter.impl
import android.content.Context
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.notification.CallIntent
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import kotlin.toString
@Suppress("LargeClass")
@RunWith(RobolectricTestRunner::class)
class RtcNotificationContentFormatterTest {
private lateinit var context: Context
private lateinit var fakeMatrixClient: FakeMatrixClient
private lateinit var formatter: RtcNotificationContentFormatter
@Before
fun setup() {
context = RuntimeEnvironment.getApplication() as Context
fakeMatrixClient = FakeMatrixClient()
val stringProvider = AndroidStringProvider(context.resources)
formatter = RtcNotificationContentFormatter(
fakeMatrixClient,
stringProvider
)
}
@Test
@Config(qualifiers = "en")
fun `Should not display declined info in rooms`() {
val result = formatter.format(
CallNotifyContent(
CallIntent.VIDEO,
declinedBy = listOf(A_USER_ID_2, A_USER_ID_3)
),
false
)
val expected = "Call started"
assertThat(result.toString()).isEqualTo(expected)
}
@Test
@Config(qualifiers = "en")
fun `Declined by me variant`() {
val result = formatter.format(
CallNotifyContent(
CallIntent.VIDEO,
declinedBy = listOf(fakeMatrixClient.sessionId)
),
true
)
val expected = "You declined a call"
assertThat(result.toString()).isEqualTo(expected)
}
@Test
@Config(qualifiers = "en")
fun `Declined by other variant`() {
val result = formatter.format(
CallNotifyContent(
CallIntent.VIDEO,
declinedBy = listOf(A_USER_ID_2)
),
true
)
val expected = "Call declined"
assertThat(result.toString()).isEqualTo(expected)
}
@Test
@Config(qualifiers = "en")
fun `Call started in DM`() {
val result = formatter.format(
CallNotifyContent(
CallIntent.AUDIO,
declinedBy = listOf()
),
true
)
val expected = "Call started"
assertThat(result.toString()).isEqualTo(expected)
}
}

View file

@ -119,7 +119,8 @@ data class LiveLocationContent(
data object LegacyCallInviteContent : EventContent data object LegacyCallInviteContent : EventContent
data class CallNotifyContent( data class CallNotifyContent(
val callIntent: CallIntent val callIntent: CallIntent,
val declinedBy: List<UserId>
) : EventContent ) : EventContent
data object UnknownContent : EventContent data object UnknownContent : EventContent

View file

@ -153,7 +153,8 @@ class TimelineEventContentMapper(
CallIntent.AUDIO CallIntent.AUDIO
} else { } else {
CallIntent.VIDEO CallIntent.VIDEO
} },
declinedBy = it.declinedBy.map(::UserId)
) )
} }
} }

View file

@ -62,7 +62,7 @@ class DefaultEventItemFactoryTest {
fun `create check all null cases`() { fun `create check all null cases`() {
val factory = createEventItemFactory() val factory = createEventItemFactory()
val contents = listOf( val contents = listOf(
CallNotifyContent(callIntent = CallIntent.VIDEO), CallNotifyContent(callIntent = CallIntent.VIDEO, emptyList()),
FailedToParseMessageLikeContent("", ""), FailedToParseMessageLikeContent("", ""),
FailedToParseStateContent("", "", ""), FailedToParseStateContent("", "", ""),
LegacyCallInviteContent, LegacyCallInviteContent,

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3624fe8448ae4af2481e2023a978ffab2d69f2784b7cef41e5ae2e2dbe8fdbd5 oid sha256:5529e89e00208e38522f5206f5b8d304bd472b27071cab4e0d3c2daf3cb64db0
size 17804 size 49741

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6bd02d39619efbcaa6d96f1e75a0d14a572c43e60bad0c3f84d6a5a48b6fbda1 oid sha256:fa29aaa82f21912dd5147ffb6fdc457fd5880e8be4abde4f0177d0ab1a412fe2
size 17395 size 48245