VoIP: add tiles for call events

This commit is contained in:
ganfra 2020-12-03 19:39:01 +01:00
parent efec711ced
commit 24de6c0101
13 changed files with 494 additions and 12 deletions

View File

@ -22,7 +22,11 @@ import android.text.style.ForegroundColorSpan
import android.text.style.UnderlineSpan
import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible
import com.google.android.material.snackbar.Snackbar
import im.vector.app.R
@ -71,6 +75,18 @@ fun TextView.setTextWithColoredPart(@StringRes fullTextRes: Int,
}
}
fun TextView.setLeftDrawable(@DrawableRes iconRes: Int, @ColorRes tintColor: Int? = null) {
val icon = if(tintColor != null){
val tint = ContextCompat.getColor(context, tintColor)
ContextCompat.getDrawable(context, iconRes)?.also {
DrawableCompat.setTint(it, tint)
}
}else {
ContextCompat.getDrawable(context, iconRes)
}
setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null)
}
/**
* Set long click listener to copy the current text of the TextView to the clipboard and show a Snackbar
*/

View File

@ -43,6 +43,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisi
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
@ -184,10 +185,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) {
positionOfReadMarker = null
adapterPositionMapping.clear()
models.forEachIndexed { index, epoxyModel ->
val callIds = mutableSetOf<String>()
val modelsIterator = models.listIterator()
modelsIterator.withIndex().forEach {
val index = it.index
val epoxyModel = it.value
if (epoxyModel is CallTileTimelineItem) {
val callId = epoxyModel.attributes.callId
if (callIds.contains(callId)) {
modelsIterator.remove()
return@forEach
}
callIds.add(callId)
}
if (epoxyModel is BaseEventItem) {
epoxyModel.getEventIds().forEach {
adapterPositionMapping[it] = index
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
}
}
}

View File

@ -0,0 +1,151 @@
/*
* Copyright 2019 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 im.vector.app.features.home.room.detail.timeline.factory
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.features.call.webrtc.WebRtcCallManager
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.helper.RoomSummaryHolder
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import org.matrix.android.sdk.api.extensions.orFalse
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.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallSignallingContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class CallItemFactory @Inject constructor(
private val messageColorProvider: MessageColorProvider,
private val messageInformationDataFactory: MessageInformationDataFactory,
private val messageItemAttributesFactory: MessageItemAttributesFactory,
private val avatarSizeProvider: AvatarSizeProvider,
private val roomSummaryHolder: RoomSummaryHolder,
private val callManager: WebRtcCallManager
) {
fun create(event: TimelineEvent,
highlight: Boolean,
callback: TimelineEventController.Callback?
): VectorEpoxyModel<*>? {
if (event.root.eventId == null) return null
val informationData = messageInformationDataFactory.create(event, null)
val callSignalingContent = event.getCallSignallingContent() ?: return null
val callId = callSignalingContent.callId ?: return null
val call = callManager.getCallById(callId)
val callKind = if (call?.mxCall?.isVideoCall.orFalse()) {
CallTileTimelineItem.CallKind.VIDEO
} else {
CallTileTimelineItem.CallKind.AUDIO
}
return when (event.root.getClearType()) {
EventType.CALL_ANSWER -> {
if (call == null) return null
createCallTileTimelineItem(
callId = callId,
callStatus = CallTileTimelineItem.CallStatus.IN_CALL,
callKind = callKind,
callback = callback,
highlight = highlight,
informationData = informationData
)
}
EventType.CALL_INVITE -> {
if (call == null) return null
createCallTileTimelineItem(
callId = callId,
callStatus = CallTileTimelineItem.CallStatus.INVITED,
callKind = callKind,
callback = callback,
highlight = highlight,
informationData = informationData
)
}
EventType.CALL_REJECT -> {
createCallTileTimelineItem(
callId = callId,
callStatus = CallTileTimelineItem.CallStatus.REJECTED,
callKind = callKind,
callback = callback,
highlight = highlight,
informationData = informationData
)
}
EventType.CALL_HANGUP -> {
createCallTileTimelineItem(
callId = callId,
callStatus = CallTileTimelineItem.CallStatus.ENDED,
callKind = callKind,
callback = callback,
highlight = highlight,
informationData = informationData
)
}
else -> null
}
}
private fun TimelineEvent.getCallSignallingContent(): CallSignallingContent? {
return when (root.getClearType()) {
EventType.CALL_INVITE -> root.getClearContent().toModel<CallInviteContent>()
EventType.CALL_HANGUP -> root.getClearContent().toModel<CallHangupContent>()
EventType.CALL_REJECT -> root.getClearContent().toModel<CallRejectContent>()
EventType.CALL_ANSWER -> root.getClearContent().toModel<CallAnswerContent>()
else -> null
}
}
private fun createCallTileTimelineItem(
callId: String,
callKind: CallTileTimelineItem.CallKind,
callStatus: CallTileTimelineItem.CallStatus,
informationData: MessageInformationData,
highlight: Boolean,
callback: TimelineEventController.Callback?
): CallTileTimelineItem? {
val userOfInterest = roomSummaryHolder.roomSummary?.toMatrixItem() ?: return null
val attributes = messageItemAttributesFactory.create(null, informationData, callback).let {
CallTileTimelineItem.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
)
}
return CallTileTimelineItem_()
.attributes(attributes)
.highlighted(highlight)
.leftGuideline(avatarSizeProvider.leftGuideline)
}
}

View File

@ -34,6 +34,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
private val roomCreateItemFactory: RoomCreateItemFactory,
private val roomSummaryHolder: RoomSummaryHolder,
private val verificationConclusionItemFactory: VerificationItemFactory,
private val callItemFactory: CallItemFactory,
private val userPreferencesProvider: UserPreferencesProvider) {
fun create(event: TimelineEvent,
@ -45,7 +46,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
val computedModel = try {
when (event.root.getClearType()) {
EventType.STICKER,
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, callback)
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, highlight, callback)
// State and call
EventType.STATE_ROOM_TOMBSTONE,
EventType.STATE_ROOM_NAME,
@ -60,17 +61,19 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_ROOM_GUEST_ACCESS,
EventType.STATE_ROOM_WIDGET_LEGACY,
EventType.STATE_ROOM_WIDGET,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.STATE_ROOM_POWER_LEVELS,
EventType.REACTION,
EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback)
EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback)
// State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
// Calls
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_REJECT,
EventType.CALL_ANSWER -> callItemFactory.create(event, highlight, callback)
// Crypto
EventType.ENCRYPTED -> {
EventType.ENCRYPTED -> {
if (event.root.isRedacted()) {
// Redacted event, let the MessageItemFactory handle it
messageItemFactory.create(event, nextEvent, highlight, callback)
@ -84,7 +87,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_MAC,
EventType.CALL_CANDIDATES -> {
EventType.CALL_CANDIDATES -> {
// TODO These are not filtered out by timeline when encrypted
// For now manually ignore
if (userPreferencesProvider.shouldShowHiddenEvents()) {

View File

@ -38,6 +38,7 @@ object TimelineDisplayableEvents {
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.CALL_REJECT,
EventType.ENCRYPTED,
EventType.STATE_ROOM_ENCRYPTION,
EventType.STATE_ROOM_GUEST_ACCESS,

View File

@ -0,0 +1,157 @@
/*
* Copyright 2019 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 im.vector.app.features.home.room.detail.timeline.item
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible
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.extensions.setLeftDrawable
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.setTextWithColoredPart
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 org.matrix.android.sdk.api.util.MatrixItem
import timber.log.Timber
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Holder>() {
override val baseAttributes: AbsBaseMessageItem.Attributes
get() = attributes
@EpoxyAttribute
lateinit var attributes: Attributes
override fun getViewType() = 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)
holder.callKindView.setText(attributes.callKind.title)
holder.callKindView.setLeftDrawable(attributes.callKind.icon)
if (attributes.callStatus == CallStatus.INVITED && !attributes.informationData.sentByMe) {
holder.acceptRejectViewGroup.isVisible = true
holder.acceptView.setOnClickListener {
Timber.v("On accept call: $attributes.callId ")
}
holder.rejectView.setLeftDrawable(R.drawable.ic_call_hangup, R.color.riotx_notice)
holder.rejectView.setOnClickListener {
Timber.v("On reject call: $attributes.callId")
}
holder.statusView.isVisible = false
when (attributes.callKind) {
CallKind.CONFERENCE -> {
holder.rejectView.setText(R.string.ignore)
holder.acceptView.setText(R.string.join)
holder.acceptView.setLeftDrawable(R.drawable.ic_call_audio_small, R.color.riotx_accent)
}
CallKind.AUDIO -> {
holder.rejectView.setText(R.string.call_notification_reject)
holder.acceptView.setText(R.string.call_notification_answer)
holder.acceptView.setLeftDrawable(R.drawable.ic_call_audio_small, R.color.riotx_accent)
}
CallKind.VIDEO -> {
holder.rejectView.setText(R.string.call_notification_reject)
holder.acceptView.setText(R.string.call_notification_answer)
holder.acceptView.setLeftDrawable(R.drawable.ic_call_video_small, R.color.riotx_accent)
}
}
} else {
holder.acceptRejectViewGroup.isVisible = false
holder.statusView.isVisible = true
}
holder.statusView.setCallStatus(attributes)
renderSendState(holder.view, null, holder.failedToSendIndicator)
}
private fun TextView.setCallStatus(attributes: Attributes) {
when (attributes.callStatus) {
CallStatus.INVITED -> if (attributes.informationData.sentByMe) {
setText(R.string.call_tile_you_started_call)
}
CallStatus.IN_CALL -> setText(R.string.call_tile_in_call)
CallStatus.REJECTED -> if (attributes.informationData.sentByMe) {
setTextWithColoredPart(R.string.call_tile_you_declined, R.string.call_tile_call_back)
} else {
text = context.getString(R.string.call_tile_other_declined, attributes.userOfInterest.getBestName())
}
CallStatus.ENDED -> setText(R.string.call_tile_ended)
}
}
class Holder : AbsBaseMessageItem.Holder(STUB_ID) {
val acceptView by bind<Button>(R.id.itemCallAcceptView)
val rejectView by bind<Button>(R.id.itemCallRejectView)
val acceptRejectViewGroup by bind<ViewGroup>(R.id.itemCallAcceptRejectViewGroup)
val callKindView by bind<TextView>(R.id.itemCallKindTextView)
val creatorAvatarView by bind<ImageView>(R.id.itemCallCreatorAvatar)
val creatorNameView by bind<TextView>(R.id.itemCallCreatorNameTextView)
val statusView by bind<TextView>(R.id.itemCallStatusTextView)
val endGuideline by bind<View>(R.id.messageEndGuideline)
val failedToSendIndicator by bind<ImageView>(R.id.messageFailToSendIndicator)
}
companion object {
private const val STUB_ID = R.id.messageCallStub
}
data class Attributes(
val callId: String,
val callKind: CallKind,
val callStatus: CallStatus,
val userOfInterest: MatrixItem,
override val informationData: MessageInformationData,
override val avatarRenderer: AvatarRenderer,
override val messageColorProvider: MessageColorProvider,
override val itemLongClickListener: View.OnLongClickListener? = null,
override val itemClickListener: View.OnClickListener? = null,
override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null,
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
) : AbsBaseMessageItem.Attributes
enum class CallKind(@DrawableRes val icon: Int, @StringRes val title: Int) {
VIDEO(R.drawable.ic_call_video_small, R.string.action_video_call),
AUDIO(R.drawable.ic_call_audio_small, R.string.action_voice_call),
CONFERENCE(R.drawable.ic_call_conference_small, R.string.conference_call_in_progress)
}
enum class CallStatus {
INVITED,
IN_CALL,
REJECTED,
ENDED
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:pathData="M4.3514,9.6408C5.1121,10.4621 6.9433,11.8842 7.4424,12.176C7.4719,12.1933 7.5057,12.2134 7.5435,12.2358C8.3051,12.6886 10.6916,14.1072 12.4304,12.7796C13.7775,11.751 13.3395,10.5939 12.886,10.25C12.5756,10.0085 11.661,9.3429 10.8005,8.7434C9.9555,8.1548 9.4846,8.6264 9.1662,8.9453C9.1603,8.9512 9.1545,8.957 9.1488,8.9627L8.5082,9.6034C8.345,9.7665 8.0968,9.707 7.8591,9.5203C7.0062,8.8707 6.3788,8.2439 6.0649,7.93L6.0623,7.9273C5.7484,7.6135 5.1293,6.9938 4.4798,6.1409C4.2931,5.9032 4.2335,5.655 4.3967,5.4919L5.0373,4.8512C5.0431,4.8455 5.0489,4.8397 5.0547,4.8338C5.3736,4.5154 5.8453,4.0445 5.2566,3.1995C4.6571,2.339 3.9915,1.4244 3.7501,1.114C3.4061,0.6606 2.249,0.2226 1.2205,1.5697C-0.1072,3.3084 1.3115,5.6949 1.7642,6.4565C1.7867,6.4943 1.8068,6.5281 1.824,6.5576C2.1159,7.0567 3.5301,8.8801 4.3514,9.6408Z"
android:fillColor="#737D8C"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M8,16C12.4183,16 16,12.4183 16,8C16,3.5817 12.4183,0 8,0C3.5817,0 0,3.5817 0,8C0,12.4183 3.5817,16 8,16ZM5.3333,8.3333C6.4379,8.3333 7.3333,7.3633 7.3333,6.1667C7.3333,4.97 6.4379,4 5.3333,4C4.2288,4 3.3333,4.97 3.3333,6.1667C3.3333,7.3633 4.2288,8.3333 5.3333,8.3333ZM11.5043,9.1296C12.472,9.1296 13.2564,8.2798 13.2564,7.2315C13.2564,6.1832 12.472,5.3333 11.5043,5.3333C10.5366,5.3333 9.7522,6.1832 9.7522,7.2315C9.7522,8.2798 10.5366,9.1296 11.5043,9.1296ZM6.1045,9.4089C7.5698,9.7298 8.6666,11.0353 8.6666,12.5969L8.6666,14.7587H4.6666L1.7144,11.6667C2.3548,10.2875 3.7345,9.3333 5.3333,9.3333C5.5971,9.3333 5.855,9.3593 6.1045,9.4089ZM9.5501,10.611C9.8385,11.2121 10,11.8856 10,12.5969L10,14.7587H11.5043L14.4675,11.6667C13.8465,10.6685 12.7515,10.0057 11.5043,10.0057C10.7807,10.0057 10.1084,10.2288 9.5501,10.611Z"
android:fillColor="#737D8C"
android:fillType="evenOdd"/>
<path
android:pathData="M8,14.6667C11.6819,14.6667 14.6667,11.6819 14.6667,8C14.6667,4.3181 11.6819,1.3333 8,1.3333C4.3181,1.3333 1.3333,4.3181 1.3333,8C1.3333,11.6819 4.3181,14.6667 8,14.6667ZM8,16C12.4183,16 16,12.4183 16,8C16,3.5817 12.4183,0 8,0C3.5817,0 0,3.5817 0,8C0,12.4183 3.5817,16 8,16Z"
android:fillColor="#737D8C"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="12dp"
android:viewportWidth="16"
android:viewportHeight="12">
<path
android:pathData="M0,3.6666C0,2.0098 1.3432,0.6666 3,0.6666H8.3333C9.9902,0.6666 11.3333,2.0098 11.3333,3.6666V8.3333C11.3333,9.9901 9.9902,11.3333 8.3333,11.3333H3C1.3431,11.3333 0,9.9902 0,8.3333V3.6666Z"
android:fillColor="#737D8C"/>
<path
android:pathData="M12.6666,3.9999L14.3753,2.633C15.03,2.1092 16,2.5754 16,3.4139V8.586C16,9.4245 15.03,9.8906 14.3753,9.3668L12.6666,7.9999V3.9999Z"
android:fillColor="#737D8C"/>
</vector>

View File

@ -40,6 +40,12 @@
android:background="@drawable/rounded_rect_shape_8"
android:padding="8dp">
<ViewStub
android:id="@+id/messageCallStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_call_tile_stub"
tools:visibility="visible" />
<ViewStub
android:id="@+id/messageVerificationRequestStub"
style="@style/TimelineContentStubBaseParams"
@ -50,7 +56,7 @@
android:id="@+id/messageVerificationDoneStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_status_tile_stub"
tools:visibility="visible" />
tools:visibility="gone" />
</FrameLayout>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<ImageView
android:id="@+id/itemCallCreatorAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/itemCallCreatorNameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:drawablePadding="6dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/itemCallKindTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="4dp"
android:drawablePadding="4dp"
android:layout_marginBottom="12dp"
android:gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="12sp"
tools:text="@string/action_video_call" />
<TextView
android:id="@+id/itemCallStatusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="12dp"
android:textColor="?attr/vctr_notice_secondary"
android:textSize="13sp"
tools:text="@string/video_call_in_progress" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/itemCallAcceptRejectViewGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/itemCallAcceptView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:minWidth="120dp"
style="@style/VectorButtonStylePositive"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/itemCallRejectView"
app:layout_constraintTop_toTopOf="@id/itemCallRejectView" />
<Button
android:id="@+id/itemCallRejectView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginEnd="4dp"
android:minWidth="120dp"
style="@style/VectorButtonStyleDestructive"
app:layout_constraintEnd_toStartOf="@+id/itemCallAcceptView"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -2747,4 +2747,11 @@
<string name="warning_unsaved_change_discard">Discard changes</string>
<string name="matrix_to_card_title">Matrix Link</string>
<string name="call_tile_you_started_call">You started a call</string>
<string name="call_tile_in_call">You\'re currently in this call</string>
<string name="call_tile_you_declined">You declined this call %1$s</string>
<string name="call_tile_other_declined">%1$s declined this call</string>
<string name="call_tile_ended">This call has ended</string>
<string name="call_tile_call_back">Call back</string>
</resources>

View File

@ -135,11 +135,13 @@
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textColor">@color/button_destructive_text_color_selector</item>
<item name="drawableTint">@color/riotx_notice</item>
</style>
<style name="VectorButtonStylePositive" parent="VectorButtonStyleDestructive">
<item name="backgroundTint">@color/button_positive_background_selector</item>
<item name="android:textColor">@color/button_positive_text_color_selector</item>
<item name="drawableTint">@color/riotx_accent</item>
</style>
<style name="VectorButtonStyleInlineBot" parent="VectorButtonStyleDestructive">