Add timeline item for m.call.notify events (#2986)

* Add timeline item for `m.call.notify` events

* Update screenshots

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-06-10 16:55:37 +02:00 committed by GitHub
parent 6f8de0b2c6
commit a16dff14f8
291 changed files with 369 additions and 68 deletions

View file

@ -49,6 +49,7 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter
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.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
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.TimelineItemFileContent 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.TimelineItemImageContent
@ -392,6 +393,7 @@ class MessagesPresenter @AssistedInject constructor(
is TimelineItemStateContent, is TimelineItemStateContent,
is TimelineItemEncryptedContent, is TimelineItemEncryptedContent,
is TimelineItemLegacyCallInviteContent, is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent,
is TimelineItemUnknownContent -> null is TimelineItemUnknownContent -> null
} }
val composerMode = MessageComposerMode.Reply( val composerMode = MessageComposerMode.Reply(

View file

@ -21,7 +21,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@ -32,10 +31,8 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -70,6 +67,7 @@ import io.element.android.features.messages.impl.messagecomposer.AttachmentsStat
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
import io.element.android.features.messages.impl.messagecomposer.MessageComposerView import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
import io.element.android.features.messages.impl.timeline.TimelineView import io.element.android.features.messages.impl.timeline.TimelineView
import io.element.android.features.messages.impl.timeline.components.JoinCallMenuItem
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
@ -110,7 +108,6 @@ import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import timber.log.Timber import timber.log.Timber
import kotlin.random.Random import kotlin.random.Random
import androidx.compose.material3.Button as Material3Button
@Composable @Composable
fun MessagesView( fun MessagesView(
@ -232,6 +229,7 @@ fun MessagesView(
state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent)) state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent))
}, },
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
onJoinCallClick = onJoinCallClick,
) )
}, },
snackbarHost = { snackbarHost = {
@ -324,6 +322,7 @@ private fun MessagesViewContent(
onTimestampClick: (TimelineItem.Event) -> Unit, onTimestampClick: (TimelineItem.Event) -> Unit,
onSendLocationClick: () -> Unit, onSendLocationClick: () -> Unit,
onCreatePollClick: () -> Unit, onCreatePollClick: () -> Unit,
onJoinCallClick: () -> Unit,
forceJumpToBottomVisibility: Boolean, forceJumpToBottomVisibility: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onSwipeToReply: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit,
@ -396,6 +395,7 @@ private fun MessagesViewContent(
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
onJoinCallClick = onJoinCallClick,
) )
}, },
sheetContent = { subcomposing: Boolean -> sheetContent = { subcomposing: Boolean ->
@ -478,16 +478,11 @@ private fun MessagesViewTopBar(
} }
}, },
actions = { actions = {
if (callState == RoomCallState.ONGOING) { CallMenuItem(
JoinCallMenuItem(onJoinCallClick = onJoinCallClick) isCallOngoing = callState == RoomCallState.ONGOING,
} else { onClick = onJoinCallClick,
IconButton(onClick = onJoinCallClick, enabled = callState != RoomCallState.DISABLED) { enabled = callState != RoomCallState.DISABLED
Icon( )
imageVector = CompoundIcons.VideoCallSolid(),
contentDescription = stringResource(CommonStrings.a11y_start_call),
)
}
}
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
}, },
windowInsets = WindowInsets(0.dp) windowInsets = WindowInsets(0.dp)
@ -495,29 +490,20 @@ private fun MessagesViewTopBar(
} }
@Composable @Composable
private fun JoinCallMenuItem( private fun CallMenuItem(
onJoinCallClick: () -> Unit, isCallOngoing: Boolean,
enabled: Boolean = true,
onClick: () -> Unit,
) { ) {
Material3Button( if (isCallOngoing) {
onClick = onJoinCallClick, JoinCallMenuItem(onJoinCallClick = onClick)
colors = ButtonDefaults.buttonColors( } else {
contentColor = ElementTheme.colors.bgCanvasDefault, IconButton(onClick = onClick, enabled = enabled) {
containerColor = ElementTheme.colors.iconAccentTertiary Icon(
), imageVector = CompoundIcons.VideoCallSolid(),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp), contentDescription = stringResource(CommonStrings.a11y_start_call),
modifier = Modifier.heightIn(min = 36.dp), )
) { }
Icon(
modifier = Modifier.size(20.dp),
imageVector = CompoundIcons.VideoCallSolid(),
contentDescription = null
)
Spacer(Modifier.width(8.dp))
Text(
text = stringResource(CommonStrings.action_join),
style = ElementTheme.typography.fontBodyMdMedium
)
Spacer(Modifier.width(8.dp))
} }
} }

View file

@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction 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.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
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.TimelineItemPollContent 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.TimelineItemRedactedContent
@ -86,6 +87,13 @@ class ActionListPresenter @Inject constructor(
val canRedact = timelineItem.isMine && userCanRedactOwn || !timelineItem.isMine && userCanRedactOther val canRedact = timelineItem.isMine && userCanRedactOwn || !timelineItem.isMine && userCanRedactOther
val actions = val actions =
when (timelineItem.content) { when (timelineItem.content) {
is TimelineItemCallNotifyContent -> {
if (isDeveloperModeEnabled) {
listOf(TimelineItemAction.ViewSource)
} else {
emptyList()
}
}
is TimelineItemRedactedContent -> { is TimelineItemRedactedContent -> {
if (isDeveloperModeEnabled) { if (isDeveloperModeEnabled) {
listOf(TimelineItemAction.ViewSource) listOf(TimelineItemAction.ViewSource)

View file

@ -59,6 +59,7 @@ import io.element.android.features.messages.impl.sender.SenderName
import io.element.android.features.messages.impl.sender.SenderNameMode import io.element.android.features.messages.impl.sender.SenderNameMode
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.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
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.TimelineItemFileContent 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.TimelineItemImageContent
@ -265,6 +266,9 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
is TimelineItemLegacyCallInviteContent -> { is TimelineItemLegacyCallInviteContent -> {
content = { ContentForBody(textContent) } content = { ContentForBody(textContent) }
} }
is TimelineItemCallNotifyContent -> {
content = { ContentForBody(stringResource(CommonStrings.common_call_started)) }
}
} }
Row(modifier = modifier) { Row(modifier = modifier) {
icon() icon()

View file

@ -38,6 +38,7 @@ import io.element.android.features.poll.api.actions.EndPollAction
import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.features.poll.api.actions.SendPollResponseAction
import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.features.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
@ -85,6 +86,7 @@ class TimelinePresenter @AssistedInject constructor(
val lastReadReceiptId = rememberSaveable { mutableStateOf<EventId?>(null) } val lastReadReceiptId = rememberSaveable { mutableStateOf<EventId?>(null) }
val timelineItems by timelineItemsFactory.collectItemsAsState() val timelineItems by timelineItemsFactory.collectItemsAsState()
val roomInfo by room.roomInfoFlow.collectAsState(initial = null)
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
@ -196,6 +198,7 @@ class TimelinePresenter @AssistedInject constructor(
isDm = room.isDm, isDm = room.isDm,
userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToSendReaction = userHasPermissionToSendReaction, userHasPermissionToSendReaction = userHasPermissionToSendReaction,
isCallOngoing = roomInfo?.hasRoomCall.orFalse(),
) )
} }
} }

View file

@ -51,4 +51,5 @@ data class TimelineRoomInfo(
val name: String?, val name: String?,
val userHasPermissionToSendMessage: Boolean, val userHasPermissionToSendMessage: Boolean,
val userHasPermissionToSendReaction: Boolean, val userHasPermissionToSendReaction: Boolean,
val isCallOngoing: Boolean,
) )

View file

@ -232,4 +232,5 @@ internal fun aTimelineRoomInfo(
name = name, name = name,
userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToSendReaction = true, userHasPermissionToSendReaction = true,
isCallOngoing = false,
) )

View file

@ -89,6 +89,7 @@ fun TimelineView(
onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit, onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit,
onMoreReactionsClick: (TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit,
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
onJoinCallClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
forceJumpToBottomVisibility: Boolean = false forceJumpToBottomVisibility: Boolean = false
) { ) {
@ -150,6 +151,7 @@ fun TimelineView(
onTimestampClick = onTimestampClick, onTimestampClick = onTimestampClick,
eventSink = state.eventSink, eventSink = state.eventSink,
onSwipeToReply = onSwipeToReply, onSwipeToReply = onSwipeToReply,
onJoinCallClick = onJoinCallClick,
) )
} }
} }
@ -305,6 +307,7 @@ internal fun TimelineViewPreview(
onReactionLongClick = { _, _ -> }, onReactionLongClick = { _, _ -> },
onMoreReactionsClick = {}, onMoreReactionsClick = {},
onReadReceiptClick = {}, onReadReceiptClick = {},
onJoinCallClick = {},
forceJumpToBottomVisibility = true, forceJumpToBottomVisibility = true,
) )
} }

View file

@ -0,0 +1,61 @@
/*
* 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.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
internal fun JoinCallMenuItem(
onJoinCallClick: () -> Unit,
) {
Button(
onClick = onJoinCallClick,
colors = ButtonDefaults.buttonColors(
contentColor = ElementTheme.colors.bgCanvasDefault,
containerColor = ElementTheme.colors.iconAccentTertiary
),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
modifier = Modifier.heightIn(min = 36.dp),
) {
Icon(
modifier = Modifier.size(20.dp),
imageVector = CompoundIcons.VideoCallSolid(),
contentDescription = null
)
Spacer(Modifier.width(8.dp))
Text(
text = stringResource(CommonStrings.action_join),
style = ElementTheme.typography.fontBodyMdMedium
)
Spacer(Modifier.width(8.dp))
}
}

View file

@ -0,0 +1,127 @@
/*
* 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.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
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.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun TimelineItemCallNotifyView(
event: TimelineItem.Event,
isCallOngoing: Boolean,
onLongClick: (TimelineItem.Event) -> Unit,
onJoinCallClick: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier
.fillMaxWidth()
.border(1.dp, ElementTheme.colors.borderInteractiveSecondary, RoundedCornerShape(8.dp))
.combinedClickable(enabled = true, onClick = {}, onLongClick = { onLongClick(event) })
.padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Avatar(avatarData = event.senderAvatar)
Column(modifier = Modifier.weight(1f)) {
Text(
text = event.safeSenderName,
style = ElementTheme.typography.fontBodyLgMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.size(20.sp.toDp()),
imageVector = 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,
)
}
}
if (isCallOngoing) {
JoinCallMenuItem(onJoinCallClick)
} else {
Text(
text = event.sentTime,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@PreviewsDayNight
@Composable
internal fun TimelineItemCallNotifyViewPreview() {
ElementPreview {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
TimelineItemCallNotifyView(
event = aTimelineItemEvent(content = TimelineItemCallNotifyContent()),
isCallOngoing = true,
onLongClick = {},
onJoinCallClick = {},
)
TimelineItemCallNotifyView(
event = aTimelineItemEvent(content = TimelineItemCallNotifyContent()),
isCallOngoing = false,
onLongClick = {},
onJoinCallClick = {},
)
}
}
}

View file

@ -140,6 +140,7 @@ private fun TimelineItemGroupedEventsRowContent(
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
eventSink = eventSink, eventSink = eventSink,
onSwipeToReply = {}, onSwipeToReply = {},
onJoinCallClick = {},
) )
} }
} }

View file

@ -30,6 +30,7 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.TimelineEvents 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.TimelineRoomInfo
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.TimelineItemCallNotifyContent
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.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.libraries.designsystem.text.toPx import io.element.android.libraries.designsystem.text.toPx
@ -55,6 +56,7 @@ internal fun TimelineItemRow(
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
onTimestampClick: (TimelineItem.Event) -> Unit, onTimestampClick: (TimelineItem.Event) -> Unit,
onSwipeToReply: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit,
onJoinCallClick: () -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@ -78,37 +80,49 @@ internal fun TimelineItemRow(
) )
} }
is TimelineItem.Event -> { is TimelineItem.Event -> {
if (timelineItem.content is TimelineItemStateContent || timelineItem.content is TimelineItemLegacyCallInviteContent) { when (timelineItem.content) {
TimelineItemStateEventRow( is TimelineItemStateContent, is TimelineItemLegacyCallInviteContent -> {
event = timelineItem, TimelineItemStateEventRow(
renderReadReceipts = renderReadReceipts, event = timelineItem,
isLastOutgoingMessage = isLastOutgoingMessage, renderReadReceipts = renderReadReceipts,
isHighlighted = timelineItem.isEvent(focusedEventId), isLastOutgoingMessage = isLastOutgoingMessage,
onClick = { onClick(timelineItem) }, isHighlighted = timelineItem.isEvent(focusedEventId),
onReadReceiptsClick = onReadReceiptClick, onClick = { onClick(timelineItem) },
onLongClick = { onLongClick(timelineItem) }, onReadReceiptsClick = onReadReceiptClick,
eventSink = eventSink, onLongClick = { onLongClick(timelineItem) },
) eventSink = eventSink,
} else { )
TimelineItemEventRow( }
event = timelineItem, is TimelineItemCallNotifyContent -> {
timelineRoomInfo = timelineRoomInfo, TimelineItemCallNotifyView(
renderReadReceipts = renderReadReceipts, modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
isLastOutgoingMessage = isLastOutgoingMessage, event = timelineItem,
isHighlighted = timelineItem.isEvent(focusedEventId), isCallOngoing = timelineRoomInfo.isCallOngoing,
onClick = { onClick(timelineItem) }, onLongClick = onLongClick,
onLongClick = { onLongClick(timelineItem) }, onJoinCallClick = onJoinCallClick,
onUserDataClick = onUserDataClick, )
onLinkClick = onLinkClick, }
inReplyToClick = inReplyToClick, else -> {
onReactionClick = onReactionClick, TimelineItemEventRow(
onReactionLongClick = onReactionLongClick, event = timelineItem,
onMoreReactionsClick = onMoreReactionsClick, timelineRoomInfo = timelineRoomInfo,
onReadReceiptClick = onReadReceiptClick, renderReadReceipts = renderReadReceipts,
onTimestampClick = onTimestampClick, isLastOutgoingMessage = isLastOutgoingMessage,
onSwipeToReply = { onSwipeToReply(timelineItem) }, isHighlighted = timelineItem.isEvent(focusedEventId),
eventSink = eventSink, onClick = { onClick(timelineItem) },
) onLongClick = { onLongClick(timelineItem) },
onUserDataClick = onUserDataClick,
onLinkClick = onLinkClick,
inReplyToClick = inReplyToClick,
onReactionClick = onReactionClick,
onReactionLongClick = onReactionLongClick,
onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick,
onTimestampClick = onTimestampClick,
onSwipeToReply = { onSwipeToReply(timelineItem) },
eventSink = eventSink,
)
}
} }
} }
is TimelineItem.GroupedEvents -> { is TimelineItem.GroupedEvents -> {

View file

@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.components.layout.Cont
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.rememberPresenter import io.element.android.features.messages.impl.timeline.di.rememberPresenter
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.TimelineItemCallNotifyContent
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
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
@ -118,5 +119,6 @@ fun TimelineItemEventContentView(
modifier = modifier modifier = modifier
) )
} }
is TimelineItemCallNotifyContent -> error("This shouldn't be rendered as the content of a bubble")
} }
} }

View file

@ -16,9 +16,11 @@
package io.element.android.features.messages.impl.timeline.factories.event package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
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.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem 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.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
@ -67,6 +69,7 @@ class TimelineItemContentFactory @Inject constructor(
is StickerContent -> stickerFactory.create(itemContent) is StickerContent -> stickerFactory.create(itemContent)
is PollContent -> pollFactory.create(eventTimelineItem, itemContent) is PollContent -> pollFactory.create(eventTimelineItem, itemContent)
is UnableToDecryptContent -> utdFactory.create(itemContent) is UnableToDecryptContent -> utdFactory.create(itemContent)
is CallNotifyContent -> TimelineItemCallNotifyContent()
is UnknownContent -> TimelineItemUnknownContent is UnknownContent -> TimelineItemUnknownContent
} }
} }

View file

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.groups
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.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
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.TimelineItemFileContent 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.TimelineItemImageContent
@ -34,6 +35,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent 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.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInviteContent
@ -66,7 +68,8 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean {
is TimelineItemVoiceContent, is TimelineItemVoiceContent,
TimelineItemRedactedContent, TimelineItemRedactedContent,
TimelineItemUnknownContent, TimelineItemUnknownContent,
is TimelineItemLegacyCallInviteContent -> false is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent -> false
is TimelineItemProfileChangeContent, is TimelineItemProfileChangeContent,
is TimelineItemRoomMembershipContent, is TimelineItemRoomMembershipContent,
is TimelineItemStateEventContent -> true is TimelineItemStateEventContent -> true
@ -93,6 +96,7 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
is RoomMembershipContent, is RoomMembershipContent,
UnknownContent, UnknownContent,
is LegacyCallInviteContent, is LegacyCallInviteContent,
CallNotifyContent,
is StateContent -> false is StateContent -> false
} }
} }

View file

@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent 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.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
@ -133,5 +134,6 @@ internal fun InReplyToDetails.Ready.metadata(): InReplyToMetadata? = when (event
is StateContent, is StateContent,
UnknownContent, UnknownContent,
is LegacyCallInviteContent, is LegacyCallInviteContent,
is CallNotifyContent,
null -> null null -> null
} }

View file

@ -0,0 +1,21 @@
/*
* 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
class TimelineItemCallNotifyContent : TimelineItemEventContent {
override val type: String = "m.call.notify"
}

View file

@ -42,6 +42,7 @@ fun TimelineItemEventContent.canBeRepliedTo(): Boolean =
when (this) { when (this) {
is TimelineItemRedactedContent, is TimelineItemRedactedContent,
is TimelineItemLegacyCallInviteContent, is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent,
is TimelineItemStateContent -> false is TimelineItemStateContent -> false
else -> true else -> true
} }
@ -65,6 +66,7 @@ fun TimelineItemEventContent.canReact(): Boolean =
is TimelineItemStateContent, is TimelineItemStateContent,
is TimelineItemRedactedContent, is TimelineItemRedactedContent,
is TimelineItemLegacyCallInviteContent, is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent,
TimelineItemUnknownContent -> false TimelineItemUnknownContent -> false
} }

View file

@ -20,6 +20,7 @@ import android.content.Context
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
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.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
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.TimelineItemFileContent 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.TimelineItemImageContent
@ -65,6 +66,7 @@ class DefaultMessageSummaryFormatter @Inject constructor(
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_call_invite) is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_call_invite)
is TimelineItemCallNotifyContent -> context.getString(CommonStrings.common_call_started)
}.take(MAX_SAFE_LENGTH) }.take(MAX_SAFE_LENGTH)
} }
} }

View file

@ -23,6 +23,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
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.event.TimelineItemCallNotifyContent
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.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
@ -759,6 +760,39 @@ class ActionListPresenterTest {
) )
} }
} }
@Test
fun `present - compute for call notify`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
isMine = true,
content = TimelineItemCallNotifyContent(),
)
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
)
)
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
displayEmojiReactions = false,
actions = persistentListOf(
TimelineItemAction.ViewSource
)
)
)
}
}
} }
private fun createActionListPresenter(isDeveloperModeEnabled: Boolean): ActionListPresenter { private fun createActionListPresenter(isDeveloperModeEnabled: Boolean): ActionListPresenter {

View file

@ -29,6 +29,7 @@ import io.element.android.features.messages.impl.typing.aTypingNotificationState
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
@ -111,6 +112,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit = EnsureNeverCalledWithTwoParams(), onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit = EnsureNeverCalledWithTwoParams(),
onMoreReactionsClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onMoreReactionsClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
onReadReceiptClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), onReadReceiptClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
onJoinCallClick: () -> Unit = EnsureNeverCalled(),
forceJumpToBottomVisibility: Boolean = false, forceJumpToBottomVisibility: Boolean = false,
) { ) {
setContent { setContent {
@ -127,6 +129,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
onReactionLongClick = onReactionLongClick, onReactionLongClick = onReactionLongClick,
onMoreReactionsClick = onMoreReactionsClick, onMoreReactionsClick = onMoreReactionsClick,
onReadReceiptClick = onReadReceiptClick, onReadReceiptClick = onReadReceiptClick,
onJoinCallClick = onJoinCallClick,
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
) )
} }

View file

@ -27,6 +27,7 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.eventformatter.impl.mode.RenderingMode import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem 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.FailedToParseMessageLikeContent
@ -111,6 +112,8 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisambiguatedDisplayName, isDmRoom) prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisambiguatedDisplayName, isDmRoom)
} }
is LegacyCallInviteContent -> sp.getString(CommonStrings.common_call_invite) is LegacyCallInviteContent -> sp.getString(CommonStrings.common_call_invite)
is CallNotifyContent -> sp.getString(CommonStrings.common_call_started)
else -> null
}?.take(MAX_SAFE_LENGTH) }?.take(MAX_SAFE_LENGTH)
} }

View file

@ -21,6 +21,7 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
import io.element.android.libraries.eventformatter.impl.mode.RenderingMode import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem 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.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
@ -63,6 +64,9 @@ class DefaultTimelineEventFormatter @Inject constructor(
is LegacyCallInviteContent -> { is LegacyCallInviteContent -> {
sp.getString(CommonStrings.common_call_invite) sp.getString(CommonStrings.common_call_invite)
} }
is CallNotifyContent -> {
sp.getString(CommonStrings.common_call_started)
}
RedactedContent, RedactedContent,
is StickerContent, is StickerContent,
is PollContent, is PollContent,

View file

@ -103,4 +103,6 @@ data class FailedToParseStateContent(
data object LegacyCallInviteContent : EventContent data object LegacyCallInviteContent : EventContent
data object CallNotifyContent : EventContent
data object UnknownContent : EventContent data object UnknownContent : EventContent

View file

@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.impl.timeline.item.event package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent 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.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
@ -125,6 +126,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
) )
} }
is TimelineItemContentKind.CallInvite -> LegacyCallInviteContent is TimelineItemContentKind.CallInvite -> LegacyCallInviteContent
is TimelineItemContentKind.CallNotify -> CallNotifyContent
else -> UnknownContent else -> UnknownContent
} }
} }

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