mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 09:56:00 +03:00
Merge pull request #8941 from element-hq/feature/bma/elementCall
Element call incoming call
This commit is contained in:
commit
ea170fc2af
15 changed files with 319 additions and 7 deletions
1
changelog.d/8938.misc
Normal file
1
changelog.d/8938.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Indicate when calls are unsupported in the timeline/notifications
|
|
@ -2939,6 +2939,9 @@
|
|||
|
||||
<string name="call_slide_to_end_conference">Slide to end the call</string>
|
||||
|
||||
<string name="call_unsupported">Unsupported call</string>
|
||||
<string name="call_unsupported_matrix_rtc_call">Unsupported call. The new Element X app is needed to join this call.</string>
|
||||
|
||||
<string name="re_authentication_activity_title">Re-Authentication Needed</string>
|
||||
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
|
||||
<string name="re_authentication_default_confirm_text">${app_name} requires you to enter your credentials to perform this action.</string>
|
||||
|
|
|
@ -499,7 +499,11 @@ fun Event.getPollContent(): MessagePollContent? {
|
|||
}
|
||||
|
||||
fun Event.supportsNotification() =
|
||||
this.getClearType() in EventType.MESSAGE + EventType.POLL_START.values + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values
|
||||
this.getClearType() in EventType.MESSAGE +
|
||||
EventType.POLL_START.values +
|
||||
EventType.POLL_END.values +
|
||||
EventType.STATE_ROOM_BEACON_INFO.values +
|
||||
EventType.ELEMENT_CALL_NOTIFY.values
|
||||
|
||||
fun Event.isContentReportable() =
|
||||
this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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<String>? = null,
|
||||
)
|
||||
|
||||
fun ElementCallNotifyContent.isUserMentioned(userId: String): Boolean {
|
||||
return mentions?.room == true ||
|
||||
mentions?.userIds?.contains(userId) == true
|
||||
}
|
|
@ -61,6 +61,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
|||
in EventType.POLL_START.values,
|
||||
in EventType.POLL_END.values,
|
||||
in EventType.STATE_ROOM_BEACON_INFO.values,
|
||||
in EventType.ELEMENT_CALL_NOTIFY.values,
|
||||
EventType.MESSAGE,
|
||||
EventType.REDACTION,
|
||||
EventType.ENCRYPTED,
|
||||
|
|
|
@ -214,6 +214,9 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||
in EventType.POLL_END.values -> {
|
||||
stringProvider.getString(CommonStrings.message_reply_to_ended_poll_preview)
|
||||
}
|
||||
in EventType.ELEMENT_CALL_NOTIFY.values -> {
|
||||
stringProvider.getString(CommonStrings.call_unsupported)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -142,6 +142,9 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
in EventType.STATE_ROOM_BEACON_INFO.values -> {
|
||||
simpleFormat(senderName, stringProvider.getString(CommonStrings.sent_live_location), appendAuthor)
|
||||
}
|
||||
in EventType.ELEMENT_CALL_NOTIFY.values -> {
|
||||
simpleFormat(senderName, stringProvider.getString(CommonStrings.call_unsupported), appendAuthor)
|
||||
}
|
||||
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
|
||||
formatVoiceBroadcastEvent(timelineEvent.root, isDm, senderName)
|
||||
}
|
||||
|
@ -243,6 +246,9 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
in EventType.STATE_ROOM_BEACON_INFO.values -> {
|
||||
stringProvider.getString(CommonStrings.sent_live_location)
|
||||
}
|
||||
in EventType.ELEMENT_CALL_NOTIFY.values -> {
|
||||
stringProvider.getString(CommonStrings.call_unsupported)
|
||||
}
|
||||
else -> {
|
||||
span {
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ object TimelineDisplayableEvents {
|
|||
) +
|
||||
EventType.POLL_START.values +
|
||||
EventType.POLL_END.values +
|
||||
EventType.ELEMENT_CALL_NOTIFY.values +
|
||||
EventType.STATE_ROOM_BEACON_INFO.values +
|
||||
EventType.BEACON_LOCATION_DATA.values
|
||||
}
|
||||
|
|
|
@ -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<ElementCallTileTimelineItem.Holder>(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<RelativeLayout.LayoutParams> {
|
||||
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<ImageView>(R.id.itemCallCreatorAvatar)
|
||||
val creatorNameView by bind<TextView>(R.id.itemCallCreatorNameTextView)
|
||||
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
||||
val failedToSendIndicator by bind<ImageView>(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,
|
||||
}
|
||||
}
|
|
@ -34,7 +34,9 @@ import org.matrix.android.sdk.api.session.getUserOrDefault
|
|||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.ElementCallNotifyContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.isUserMentioned
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId
|
||||
|
@ -149,9 +151,11 @@ class NotifiableEventResolver @Inject constructor(
|
|||
)
|
||||
} else {
|
||||
event.attemptToDecryptIfNeeded(session)
|
||||
// only convert encrypted messages to NotifiableMessageEvents
|
||||
// For incoming Element Call, check that the user is mentioned
|
||||
val isIncomingElementCall = event.root.getClearType() in EventType.ELEMENT_CALL_NOTIFY.values &&
|
||||
event.root.getClearContent()?.toModel<ElementCallNotifyContent>()?.isUserMentioned(session.myUserId) == true
|
||||
when {
|
||||
event.root.supportsNotification() -> {
|
||||
isIncomingElementCall || event.root.supportsNotification() -> {
|
||||
val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
|
||||
val roomName = room.roomSummary()?.displayName ?: ""
|
||||
val senderDisplayName = event.senderInfo.disambiguatedDisplayName
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/messageVerificationDoneStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout="@layout/item_timeline_event_status_tile_stub"
|
||||
tools:layout_marginTop="450dp"
|
||||
tools:layout_marginTop="360dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/messageWidgetStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout="@layout/item_timeline_event_widget_stub"
|
||||
tools:layout_marginTop="280dp"
|
||||
tools:layout_marginTop="440dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/messageElementCallStub"
|
||||
style="@style/TimelineContentStubBaseParams"
|
||||
android:layout="@layout/item_timeline_event_element_call_tile_stub"
|
||||
tools:layout_marginTop="520dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemCallCreatorAvatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="@sample/user_round_avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemCallCreatorNameTextView"
|
||||
style="@style/Widget.Vector.TextView.Subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:gravity="center"
|
||||
android:textColor="?vctr_content_primary"
|
||||
android:textStyle="bold"
|
||||
tools:text="@sample/users.json/data/displayName" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/itemCallStatusTextView"
|
||||
style="@style/Widget.Vector.TextView.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="start"
|
||||
android:maxLines="3"
|
||||
android:text="@string/call_unsupported_matrix_rtc_call"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
app:drawableStartCompat="@drawable/ic_call_audio_small" />
|
||||
|
||||
</LinearLayout>
|
Loading…
Reference in a new issue