diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 30e6ef673e..f7f1b8488f 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -2937,6 +2937,8 @@
Slide to end the call
+ Unsupported call. The new Element X app is needed to join this call.
+
Re-Authentication Needed
${app_name} requires you to enter your credentials to perform this action.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
index 9228f76db2..78bbc7cd6b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
@@ -87,6 +87,9 @@ object EventType {
// This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces"
+ // Element Call
+ val ELEMENT_CALL_NOTIFY = StableUnstableId(stable = "m.call.notify", unstable = "org.matrix.msc4075.call.notify")
+
// Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ElementCallNotifyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ElementCallNotifyContent.kt
new file mode 100644
index 0000000000..50bef28fdc
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ElementCallNotifyContent.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.room.model.message
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class ElementCallNotifyContent(
+ @Json(name = "application") val application: String? = null,
+ @Json(name = "call_id") val callId: String? = null,
+ @Json(name = "m.mentions") val mentions: Mentions? = null,
+ @Json(name = "notify_type") val notifyType: String? = null,
+)
+
+@JsonClass(generateAdapter = true)
+data class Mentions(
+ @Json(name = "room") val room: Boolean? = null,
+ @Json(name = "user_ids") val userIds: List? = null,
+)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ElementCallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ElementCallItemFactory.kt
new file mode 100644
index 0000000000..2c0b49eeab
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ElementCallItemFactory.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * Please see LICENSE in the repository root for full details.
+ */
+package im.vector.app.features.home.room.detail.timeline.factory
+
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.core.resources.UserPreferencesProvider
+import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
+import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
+import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
+import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
+import im.vector.app.features.home.room.detail.timeline.item.ElementCallTileTimelineItem
+import im.vector.app.features.home.room.detail.timeline.item.ElementCallTileTimelineItem_
+import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.message.ElementCallNotifyContent
+import org.matrix.android.sdk.api.util.toMatrixItem
+import javax.inject.Inject
+
+class ElementCallItemFactory @Inject constructor(
+ private val session: Session,
+ private val userPreferencesProvider: UserPreferencesProvider,
+ private val messageColorProvider: MessageColorProvider,
+ private val messageInformationDataFactory: MessageInformationDataFactory,
+ private val messageItemAttributesFactory: MessageItemAttributesFactory,
+ private val avatarSizeProvider: AvatarSizeProvider,
+ private val noticeItemFactory: NoticeItemFactory
+) {
+
+ fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
+ val event = params.event
+ if (event.root.eventId == null) return null
+ val showHiddenEvents = userPreferencesProvider.shouldShowHiddenEvents()
+ val roomSummary = params.partialState.roomSummary ?: return null
+ val informationData = messageInformationDataFactory.create(params)
+ val callItem = when (event.root.getClearType()) {
+ in EventType.ELEMENT_CALL_NOTIFY.values -> {
+ val notifyContent: ElementCallNotifyContent = event.root.content.toModel() ?: return null
+ createElementCallTileTimelineItem(
+ roomSummary = roomSummary,
+ callId = notifyContent.callId.orEmpty(),
+ callStatus = ElementCallTileTimelineItem.CallStatus.INVITED,
+ callKind = ElementCallTileTimelineItem.CallKind.VIDEO,
+ callback = params.callback,
+ highlight = params.isHighlighted,
+ informationData = informationData,
+ reactionsSummaryEvents = params.reactionsSummaryEvents
+ )
+ }
+ else -> null
+ }
+ return if (callItem == null && showHiddenEvents) {
+ // Fallback to notice item for showing hidden events
+ noticeItemFactory.create(params)
+ } else {
+ callItem
+ }
+ }
+
+ private fun createElementCallTileTimelineItem(
+ roomSummary: RoomSummary,
+ callId: String,
+ callKind: ElementCallTileTimelineItem.CallKind,
+ callStatus: ElementCallTileTimelineItem.CallStatus,
+ informationData: MessageInformationData,
+ highlight: Boolean,
+ callback: TimelineEventController.Callback?,
+ reactionsSummaryEvents: ReactionsSummaryEvents?
+ ): ElementCallTileTimelineItem? {
+ val userOfInterest = roomSummary.toMatrixItem()
+ val attributes = messageItemAttributesFactory.create(null, informationData, callback, reactionsSummaryEvents).let {
+ ElementCallTileTimelineItem.Attributes(
+ callId = callId,
+ callKind = callKind,
+ callStatus = callStatus,
+ informationData = informationData,
+ avatarRenderer = it.avatarRenderer,
+ messageColorProvider = messageColorProvider,
+ itemClickListener = it.itemClickListener,
+ itemLongClickListener = it.itemLongClickListener,
+ reactionPillCallback = it.reactionPillCallback,
+ readReceiptsCallback = it.readReceiptsCallback,
+ userOfInterest = userOfInterest,
+ callback = callback,
+ reactionsSummaryEvents = reactionsSummaryEvents
+ )
+ }
+ return ElementCallTileTimelineItem_()
+ .attributes(attributes)
+ .highlighted(highlight)
+ .leftGuideline(avatarSizeProvider.leftGuideline)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index ce3694c637..51a08b07f6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -32,6 +32,7 @@ class TimelineItemFactory @Inject constructor(
private val widgetItemFactory: WidgetItemFactory,
private val verificationConclusionItemFactory: VerificationItemFactory,
private val callItemFactory: CallItemFactory,
+ private val elementCallItemFactory: ElementCallItemFactory,
private val decryptionFailureTracker: DecryptionFailureTracker,
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper,
private val session: Session,
@@ -119,6 +120,8 @@ class TimelineItemFactory @Inject constructor(
noticeItemFactory.create(params)
}
}
+ // Element Call
+ in EventType.ELEMENT_CALL_NOTIFY.values -> elementCallItemFactory.create(params)
// Calls
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ElementCallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ElementCallTileTimelineItem.kt
new file mode 100644
index 0000000000..33d5e33661
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ElementCallTileTimelineItem.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2024 New Vector Ltd.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * Please see LICENSE in the repository root for full details.
+ */
+package im.vector.app.features.home.room.detail.timeline.item
+
+import android.content.res.Resources
+import android.view.View
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.core.view.updateLayoutParams
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.ClickListener
+import im.vector.app.features.displayname.getBestName
+import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
+import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.lib.strings.CommonStrings
+import org.matrix.android.sdk.api.util.MatrixItem
+
+@EpoxyModelClass
+abstract class ElementCallTileTimelineItem : AbsBaseMessageItem(R.layout.item_timeline_event_base_state) {
+
+ override val baseAttributes: AbsBaseMessageItem.Attributes
+ get() = attributes
+
+ override fun isCacheable() = false
+
+ @EpoxyAttribute
+ lateinit var attributes: Attributes
+
+ override fun getViewStubId() = STUB_ID
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.endGuideline.updateLayoutParams {
+ this.marginEnd = leftGuideline
+ }
+ holder.creatorNameView.text = attributes.userOfInterest.getBestName()
+ attributes.avatarRenderer.render(attributes.userOfInterest, holder.creatorAvatarView)
+ renderSendState(holder.view, null, holder.failedToSendIndicator)
+ }
+
+ class Holder : AbsBaseMessageItem.Holder(STUB_ID) {
+ val creatorAvatarView by bind(R.id.itemCallCreatorAvatar)
+ val creatorNameView by bind(R.id.itemCallCreatorNameTextView)
+ val endGuideline by bind(R.id.messageEndGuideline)
+ val failedToSendIndicator by bind(R.id.messageFailToSendIndicator)
+
+ val resources: Resources
+ get() = view.context.resources
+ }
+
+ companion object {
+ private val STUB_ID = R.id.messageElementCallStub
+ }
+
+ data class Attributes(
+ val callId: String,
+ val callKind: CallKind,
+ val callStatus: CallStatus,
+ val userOfInterest: MatrixItem,
+ val callback: TimelineEventController.Callback? = null,
+ override val informationData: MessageInformationData,
+ override val avatarRenderer: AvatarRenderer,
+ override val messageColorProvider: MessageColorProvider,
+ override val itemLongClickListener: View.OnLongClickListener? = null,
+ override val itemClickListener: ClickListener? = null,
+ override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
+ override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
+ override val reactionsSummaryEvents: ReactionsSummaryEvents? = null
+ ) : AbsBaseMessageItem.Attributes
+
+ enum class CallKind(@DrawableRes val icon: Int, @StringRes val title: Int) {
+ VIDEO(R.drawable.ic_call_video_small, CommonStrings.action_video_call),
+ }
+
+ enum class CallStatus {
+ INVITED,
+ }
+}
diff --git a/vector/src/main/res/layout/item_timeline_event_base_state.xml b/vector/src/main/res/layout/item_timeline_event_base_state.xml
index 927abbfd16..f94cb8801b 100644
--- a/vector/src/main/res/layout/item_timeline_event_base_state.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base_state.xml
@@ -52,21 +52,28 @@
android:id="@+id/messageVerificationRequestStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_verification_stub"
- tools:layout_marginTop="250dp"
+ tools:layout_marginTop="200dp"
tools:visibility="visible" />
+
+
@@ -122,4 +129,4 @@
-
\ No newline at end of file
+
diff --git a/vector/src/main/res/layout/item_timeline_event_element_call_tile_stub.xml b/vector/src/main/res/layout/item_timeline_event_element_call_tile_stub.xml
new file mode 100644
index 0000000000..5f0a924ff9
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_element_call_tile_stub.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+