Introduces CallEventGrouper so we can manage properly call history
This commit is contained in:
parent
d2785b69df
commit
f405532e4c
|
@ -41,6 +41,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory
|
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
|
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.CallEventGrouper
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
|
||||||
|
@ -48,7 +49,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiff
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
||||||
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.DaySeparatorItem_
|
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
|
||||||
|
@ -56,7 +56,6 @@ import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
import im.vector.app.features.media.VideoContentRenderer
|
import im.vector.app.features.media.VideoContentRenderer
|
||||||
|
@ -164,6 +163,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
|
|
||||||
// Map eventId to adapter position
|
// Map eventId to adapter position
|
||||||
private val adapterPositionMapping = HashMap<String, Int>()
|
private val adapterPositionMapping = HashMap<String, Int>()
|
||||||
|
private val callEventGroupers = HashMap<String, CallEventGrouper>()
|
||||||
|
private val receiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
||||||
private val modelCache = arrayListOf<CacheItemData?>()
|
private val modelCache = arrayListOf<CacheItemData?>()
|
||||||
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
||||||
private var inSubmitList: Boolean = false
|
private var inSubmitList: Boolean = false
|
||||||
|
@ -353,8 +354,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
if (modelCache.isEmpty()) {
|
if (modelCache.isEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val receiptsByEvents = getReadReceiptsByShownEvent()
|
preprocessReverseEvents()
|
||||||
val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvents)
|
val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvent)
|
||||||
(0 until modelCache.size).forEach { position ->
|
(0 until modelCache.size).forEach { position ->
|
||||||
val event = currentSnapshot[position]
|
val event = currentSnapshot[position]
|
||||||
val nextEvent = currentSnapshot.nextOrNull(position)
|
val nextEvent = currentSnapshot.nextOrNull(position)
|
||||||
|
@ -362,22 +363,28 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
|
val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
|
||||||
timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
|
timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
|
||||||
}
|
}
|
||||||
val params = TimelineItemFactoryParams(
|
|
||||||
event = event,
|
|
||||||
prevEvent = prevEvent,
|
|
||||||
nextEvent = nextEvent,
|
|
||||||
nextDisplayableEvent = nextDisplayableEvent,
|
|
||||||
partialState = partialState,
|
|
||||||
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
|
|
||||||
callback = callback
|
|
||||||
)
|
|
||||||
// Should be build if not cached or if model should be refreshed
|
// Should be build if not cached or if model should be refreshed
|
||||||
if (modelCache[position] == null || modelCache[position]?.isCacheable == false) {
|
if (modelCache[position] == null || modelCache[position]?.isCacheable == false) {
|
||||||
|
val callEventGrouper = if (EventType.isCallEvent(event.root.getClearType())) {
|
||||||
|
(event.root.getClearContent()?.get("call_id") as? String)?.let { callId -> callEventGroupers[callId] }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val params = TimelineItemFactoryParams(
|
||||||
|
event = event,
|
||||||
|
prevEvent = prevEvent,
|
||||||
|
nextEvent = nextEvent,
|
||||||
|
nextDisplayableEvent = nextDisplayableEvent,
|
||||||
|
partialState = partialState,
|
||||||
|
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
|
||||||
|
callback = callback,
|
||||||
|
callEventGrouper = callEventGrouper
|
||||||
|
)
|
||||||
modelCache[position] = buildCacheItem(params)
|
modelCache[position] = buildCacheItem(params)
|
||||||
}
|
}
|
||||||
val itemCachedData = modelCache[position] ?: return@forEach
|
val itemCachedData = modelCache[position] ?: return@forEach
|
||||||
// Then update with additional models if needed
|
// Then update with additional models if needed
|
||||||
modelCache[position] = itemCachedData.enrichWithModels(event, nextEvent, position, receiptsByEvents)
|
modelCache[position] = itemCachedData.enrichWithModels(event, nextEvent, position, receiptsByEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,15 +457,18 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getReadReceiptsByShownEvent(): Map<String, List<ReadReceipt>> {
|
private fun preprocessReverseEvents() {
|
||||||
val receiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
receiptsByEvent.clear()
|
||||||
if (!userPreferencesProvider.shouldShowReadReceipts()) {
|
callEventGroupers.clear()
|
||||||
return receiptsByEvent
|
|
||||||
}
|
|
||||||
var lastShownEventId: String? = null
|
|
||||||
val itr = currentSnapshot.listIterator(currentSnapshot.size)
|
val itr = currentSnapshot.listIterator(currentSnapshot.size)
|
||||||
|
var lastShownEventId: String? = null
|
||||||
while (itr.hasPrevious()) {
|
while (itr.hasPrevious()) {
|
||||||
val event = itr.previous()
|
val event = itr.previous()
|
||||||
|
if (EventType.isCallEvent(event.root.getClearType())) {
|
||||||
|
(event.root.getClearContent()?.get("call_id") as? String)?.also { callId ->
|
||||||
|
callEventGroupers.getOrPut(callId) { CallEventGrouper(session.myUserId, callId) }.add(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
val currentReadReceipts = ArrayList(event.readReceipts).filter {
|
val currentReadReceipts = ArrayList(event.readReceipts).filter {
|
||||||
it.user.userId != session.myUserId
|
it.user.userId != session.myUserId
|
||||||
}
|
}
|
||||||
|
@ -471,7 +481,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
val existingReceipts = receiptsByEvent.getOrPut(lastShownEventId) { ArrayList() }
|
val existingReceipts = receiptsByEvent.getOrPut(lastShownEventId) { ArrayList() }
|
||||||
existingReceipts.addAll(currentReadReceipts)
|
existingReceipts.addAll(currentReadReceipts)
|
||||||
}
|
}
|
||||||
return receiptsByEvent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildDaySeparatorItem(originServerTs: Long?): DaySeparatorItem {
|
private fun buildDaySeparatorItem(originServerTs: Long?): DaySeparatorItem {
|
||||||
|
|
|
@ -16,81 +16,79 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.factory
|
package im.vector.app.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
import im.vector.app.features.call.vectorCallService
|
import im.vector.app.features.call.vectorCallService
|
||||||
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.MessageColorProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
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.AvatarSizeProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
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.MessageItemAttributesFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||||
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.CallTileTimelineItem_
|
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.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.CallSignalingContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CallItemFactory @Inject constructor(
|
class CallItemFactory @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider,
|
||||||
|
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper,
|
||||||
private val messageColorProvider: MessageColorProvider,
|
private val messageColorProvider: MessageColorProvider,
|
||||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
private val roomSummariesHolder: RoomSummariesHolder,
|
private val roomSummariesHolder: RoomSummariesHolder) {
|
||||||
private val callManager: WebRtcCallManager
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
|
fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
|
||||||
val event = params.event
|
val event = params.event
|
||||||
if (event.root.eventId == null) return null
|
if (event.root.eventId == null) return null
|
||||||
|
val showHiddenEvents = userPreferencesProvider.shouldShowHiddenEvents()
|
||||||
|
val callEventGrouper = params.callEventGrouper ?: return null
|
||||||
val roomId = event.roomId
|
val roomId = event.roomId
|
||||||
val informationData = messageInformationDataFactory.create(params)
|
val informationData = messageInformationDataFactory.create(params)
|
||||||
val callSignalingContent = event.getCallSignalingContent() ?: return null
|
val callKind = if (callEventGrouper.isVideo()) CallTileTimelineItem.CallKind.VIDEO else CallTileTimelineItem.CallKind.AUDIO
|
||||||
val callId = callSignalingContent.callId ?: return null
|
val isRinging = callEventGrouper.isRinging()
|
||||||
val call = callManager.getCallById(callId)
|
|
||||||
val callKind = when {
|
|
||||||
call == null -> CallTileTimelineItem.CallKind.UNKNOWN
|
|
||||||
call.mxCall.isVideoCall -> CallTileTimelineItem.CallKind.VIDEO
|
|
||||||
else -> CallTileTimelineItem.CallKind.AUDIO
|
|
||||||
}
|
|
||||||
return when (event.root.getClearType()) {
|
return when (event.root.getClearType()) {
|
||||||
EventType.CALL_ANSWER -> {
|
EventType.CALL_ANSWER -> {
|
||||||
createCallTileTimelineItem(
|
if (isRinging || showHiddenEvents) {
|
||||||
roomId = roomId,
|
createCallTileTimelineItem(
|
||||||
callId = callId,
|
roomId = roomId,
|
||||||
callStatus = CallTileTimelineItem.CallStatus.IN_CALL,
|
callId = callEventGrouper.callId,
|
||||||
callKind = callKind,
|
callStatus = CallTileTimelineItem.CallStatus.IN_CALL,
|
||||||
callback = params.callback,
|
callKind = callKind,
|
||||||
highlight = params.isHighlighted,
|
callback = params.callback,
|
||||||
informationData = informationData,
|
highlight = params.isHighlighted,
|
||||||
isStillActive = call != null
|
informationData = informationData,
|
||||||
)
|
isStillActive = isRinging
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EventType.CALL_INVITE -> {
|
EventType.CALL_INVITE -> {
|
||||||
createCallTileTimelineItem(
|
if (isRinging || showHiddenEvents) {
|
||||||
roomId = roomId,
|
createCallTileTimelineItem(
|
||||||
callId = callId,
|
roomId = roomId,
|
||||||
callStatus = CallTileTimelineItem.CallStatus.INVITED,
|
callId = callEventGrouper.callId,
|
||||||
callKind = callKind,
|
callStatus = CallTileTimelineItem.CallStatus.INVITED,
|
||||||
callback = params.callback,
|
callKind = callKind,
|
||||||
highlight = params.isHighlighted,
|
callback = params.callback,
|
||||||
informationData = informationData,
|
highlight = params.isHighlighted,
|
||||||
isStillActive = call != null
|
informationData = informationData,
|
||||||
)
|
isStillActive = isRinging
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EventType.CALL_REJECT -> {
|
EventType.CALL_REJECT -> {
|
||||||
createCallTileTimelineItem(
|
createCallTileTimelineItem(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
callId = callId,
|
callId = callEventGrouper.callId,
|
||||||
callStatus = CallTileTimelineItem.CallStatus.REJECTED,
|
callStatus = CallTileTimelineItem.CallStatus.REJECTED,
|
||||||
callKind = callKind,
|
callKind = callKind,
|
||||||
callback = params.callback,
|
callback = params.callback,
|
||||||
|
@ -102,8 +100,8 @@ class CallItemFactory @Inject constructor(
|
||||||
EventType.CALL_HANGUP -> {
|
EventType.CALL_HANGUP -> {
|
||||||
createCallTileTimelineItem(
|
createCallTileTimelineItem(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
callId = callId,
|
callId = callEventGrouper.callId,
|
||||||
callStatus = CallTileTimelineItem.CallStatus.ENDED,
|
callStatus = if (callEventGrouper.callWasMissed()) CallTileTimelineItem.CallStatus.MISSED else CallTileTimelineItem.CallStatus.ENDED,
|
||||||
callKind = callKind,
|
callKind = callKind,
|
||||||
callback = params.callback,
|
callback = params.callback,
|
||||||
highlight = params.isHighlighted,
|
highlight = params.isHighlighted,
|
||||||
|
@ -115,16 +113,6 @@ class CallItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TimelineEvent.getCallSignalingContent(): CallSignalingContent? {
|
|
||||||
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(
|
private fun createCallTileTimelineItem(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
callId: String,
|
callId: String,
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.factory
|
package im.vector.app.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.helper.CallEventGrouper
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
data class TimelineItemFactoryParams(
|
data class TimelineItemFactoryParams(
|
||||||
|
@ -26,7 +27,8 @@ data class TimelineItemFactoryParams(
|
||||||
val nextDisplayableEvent: TimelineEvent? = null,
|
val nextDisplayableEvent: TimelineEvent? = null,
|
||||||
val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(),
|
val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(),
|
||||||
val lastSentEventIdWithoutReadReceipts: String? = null,
|
val lastSentEventIdWithoutReadReceipts: String? = null,
|
||||||
val callback: TimelineEventController.Callback? = null
|
val callback: TimelineEventController.Callback? = null,
|
||||||
|
val callEventGrouper: CallEventGrouper?= null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val highlightedEventId: String?
|
val highlightedEventId: String?
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.helper
|
||||||
|
|
||||||
|
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.CallInviteContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
class CallEventGrouper(private val myUserId: String, val callId: String) {
|
||||||
|
|
||||||
|
private val events = HashSet<TimelineEvent>()
|
||||||
|
|
||||||
|
fun add(timelineEvent: TimelineEvent) {
|
||||||
|
events.add(timelineEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVideo(): Boolean {
|
||||||
|
val invite = getInvite() ?: return false
|
||||||
|
return invite.root.getClearContent().toModel<CallInviteContent>()?.isVideo().orFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isRinging(): Boolean {
|
||||||
|
return getAnswer() == null && getHangup() == null && getReject() == null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are only events from the other side - we missed the call
|
||||||
|
*/
|
||||||
|
fun callWasMissed(): Boolean {
|
||||||
|
return events.none { it.senderInfo.userId == myUserId }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAnswer(): TimelineEvent? {
|
||||||
|
return events.firstOrNull { it.root.getClearType() == EventType.CALL_ANSWER }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInvite(): TimelineEvent? {
|
||||||
|
return events.firstOrNull { it.root.getClearType() == EventType.CALL_INVITE }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHangup(): TimelineEvent? {
|
||||||
|
return events.firstOrNull { it.root.getClearType() == EventType.CALL_HANGUP }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getReject(): TimelineEvent? {
|
||||||
|
return events.firstOrNull { it.root.getClearType() == EventType.CALL_REJECT }
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.app.features.home.room.detail.timeline.item
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
@ -72,10 +73,22 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
CallStatus.IN_CALL -> renderInCallStatus(holder)
|
CallStatus.IN_CALL -> renderInCallStatus(holder)
|
||||||
CallStatus.REJECTED -> renderRejectedStatus(holder)
|
CallStatus.REJECTED -> renderRejectedStatus(holder)
|
||||||
CallStatus.ENDED -> renderEndedStatus(holder)
|
CallStatus.ENDED -> renderEndedStatus(holder)
|
||||||
|
CallStatus.MISSED -> renderMissedStatus(holder)
|
||||||
}
|
}
|
||||||
renderSendState(holder.view, null, holder.failedToSendIndicator)
|
renderSendState(holder.view, null, holder.failedToSendIndicator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderMissedStatus(holder: Holder) {
|
||||||
|
holder.acceptRejectViewGroup.isVisible = false
|
||||||
|
holder.statusView.isVisible = true
|
||||||
|
val status = if (attributes.callKind == CallKind.VIDEO) {
|
||||||
|
holder.resources.getQuantityString(R.plurals.missed_video_call, 1)
|
||||||
|
} else {
|
||||||
|
holder.resources.getQuantityString(R.plurals.missed_audio_call, 1)
|
||||||
|
}
|
||||||
|
holder.statusView.text = status
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderEndedStatus(holder: Holder) {
|
private fun renderEndedStatus(holder: Holder) {
|
||||||
holder.acceptRejectViewGroup.isVisible = false
|
holder.acceptRejectViewGroup.isVisible = false
|
||||||
holder.statusView.isVisible = true
|
holder.statusView.isVisible = true
|
||||||
|
@ -91,7 +104,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
attributes.callback?.onTimelineItemAction(callbackAction)
|
attributes.callback?.onTimelineItemAction(callbackAction)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
holder.statusView.text = holder.view.context.getString(R.string.call_tile_other_declined, attributes.userOfInterest.getBestName())
|
holder.statusView.text = holder.resources.getString(R.string.call_tile_other_declined, attributes.userOfInterest.getBestName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +179,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
if (attributes.informationData.sentByMe) {
|
if (attributes.informationData.sentByMe) {
|
||||||
holder.statusView.setText(R.string.call_tile_you_started_call)
|
holder.statusView.setText(R.string.call_tile_you_started_call)
|
||||||
} else {
|
} else {
|
||||||
holder.statusView.text = holder.view.context.getString(R.string.call_tile_other_started_call, attributes.userOfInterest.getBestName())
|
holder.statusView.text = holder.resources.getString(R.string.call_tile_other_started_call, attributes.userOfInterest.getBestName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,6 +195,9 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
val statusView by bind<TextView>(R.id.itemCallStatusTextView)
|
val statusView by bind<TextView>(R.id.itemCallStatusTextView)
|
||||||
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
val endGuideline by bind<View>(R.id.messageEndGuideline)
|
||||||
val failedToSendIndicator by bind<ImageView>(R.id.messageFailToSendIndicator)
|
val failedToSendIndicator by bind<ImageView>(R.id.messageFailToSendIndicator)
|
||||||
|
|
||||||
|
val resources: Resources
|
||||||
|
get() = view.context.resources
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -215,6 +231,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
INVITED,
|
INVITED,
|
||||||
IN_CALL,
|
IN_CALL,
|
||||||
REJECTED,
|
REJECTED,
|
||||||
|
MISSED,
|
||||||
ENDED;
|
ENDED;
|
||||||
|
|
||||||
fun isActive() = this == INVITED || this == IN_CALL
|
fun isActive() = this == INVITED || this == IN_CALL
|
||||||
|
|
Loading…
Reference in New Issue