From 5781adb163e0164465c19d43f735e998220ce147 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 8 Jun 2021 20:08:28 +0200 Subject: [PATCH 1/7] Timeline merging : introduce TimelineProxy (WIP) --- .../detail/timeline/merged/MergedTimelines.kt | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt new file mode 100644 index 0000000000..5bc5ea0803 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -0,0 +1,194 @@ +/* + * 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.merged + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +class MergedTimelines( + private val coroutineScope: CoroutineScope, + private val mainTimeline: Timeline, + private val secondaryTimelineParams: SecondaryTimelineParams) : Timeline by mainTimeline { + + data class SecondaryTimelineParams( + val timeline: Timeline, + val shouldFilterTypes: Boolean = false, + val allowedTypes: List = emptyList() + ) + + private val secondaryTimeline = secondaryTimelineParams.timeline + + private val listenersMapping = HashMap>() + private val mainTimelineEvents = ArrayList() + private val secondaryTimelineEvents = ArrayList() + private val positionsMapping = HashMap() + private val mergedEvents = ArrayList() + + private val processingSemaphore = Semaphore(1) + + private class ListenerInterceptor( + var timeline: Timeline?, + private val wrappedListener: Timeline.Listener, + private val shouldFilterTypes: Boolean, + private val allowedTypes: List, + private val onTimelineUpdate: (List) -> Unit + ) : Timeline.Listener by wrappedListener { + + override fun onTimelineUpdated(snapshot: List) { + val filteredEvents = if (shouldFilterTypes) { + snapshot.filter { + allowedTypes.contains(it.root.getClearType()) + } + } else { + snapshot + } + onTimelineUpdate(filteredEvents) + } + } + + override fun addListener(listener: Timeline.Listener): Boolean { + val mainTimelineListener = ListenerInterceptor(mainTimeline, listener, false, emptyList()) { + processTimelineUpdates(mainTimelineEvents, it) + } + val secondaryTimelineListener = ListenerInterceptor(secondaryTimeline, listener, secondaryTimelineParams.shouldFilterTypes, secondaryTimelineParams.allowedTypes) { + processTimelineUpdates(secondaryTimelineEvents, it) + } + listenersMapping[listener] = listOf(mainTimelineListener, secondaryTimelineListener) + return mainTimeline.addListener(mainTimelineListener) && secondaryTimeline.addListener(secondaryTimelineListener) + } + + override fun removeListener(listener: Timeline.Listener): Boolean { + return listenersMapping.remove(listener)?.let { + it.forEach { listener -> + listener.timeline?.removeListener(listener) + listener.timeline = null + } + true + } ?: false + } + + override fun removeAllListeners() { + mainTimeline.removeAllListeners() + secondaryTimeline.removeAllListeners() + } + + override fun start() { + mainTimeline.start() + secondaryTimeline.start() + } + + override fun dispose() { + mainTimeline.dispose() + secondaryTimeline.dispose() + } + + override fun restartWithEventId(eventId: String?) { + mainTimeline.restartWithEventId(eventId) + } + + override fun hasMoreToLoad(direction: Timeline.Direction): Boolean { + return mainTimeline.hasMoreToLoad(direction) || secondaryTimeline.hasMoreToLoad(direction) + } + + override fun paginate(direction: Timeline.Direction, count: Int) { + mainTimeline.paginate(direction, count) + secondaryTimeline.paginate(direction, count) + } + + override fun pendingEventCount(): Int { + return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount() + } + + override fun failedToDeliverEventCount(): Int { + return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount() + } + + override fun getTimelineEventAtIndex(index: Int): TimelineEvent? { + return mergedEvents.getOrNull(index) + } + + override fun getIndexOfEvent(eventId: String?): Int? { + return positionsMapping[eventId] + } + + override fun getTimelineEventWithId(eventId: String?): TimelineEvent? { + return positionsMapping[eventId]?.let { + getTimelineEventAtIndex(it) + } + } + + private fun processTimelineUpdates(eventsRef: MutableList, newData: List) { + coroutineScope.launch(Dispatchers.Default) { + processingSemaphore.withPermit { + eventsRef.apply { + clear() + addAll(newData) + } + mergeTimeline() + } + } + } + + private suspend fun mergeTimeline() { + val merged = mutableListOf() + val mainItr = mainTimelineEvents.toList().listIterator() + val secondaryItr = secondaryTimelineEvents.toList().listIterator() + var index = 0 + + while (merged.size < mainTimelineEvents.size + secondaryTimelineEvents.size) { + if (mainItr.hasNext()) { + val nextMain = mainItr.next() + if (secondaryItr.hasNext()) { + val nextSecondary = secondaryItr.next() + if (nextSecondary.root.originServerTs ?: 0 > nextMain.root.originServerTs ?: 0) { + positionsMapping[nextSecondary.eventId] = index + merged.add(nextSecondary) + mainItr.previous() + } else { + positionsMapping[nextMain.eventId] = index + merged.add(nextMain) + secondaryItr.previous() + } + } else { + positionsMapping[nextMain.eventId] = index + merged.add(nextMain) + } + } else if (secondaryItr.hasNext()) { + val nextSecondary = secondaryItr.next() + positionsMapping[nextSecondary.eventId] = index + merged.add(nextSecondary) + } + index++ + } + mergedEvents.apply { + clear() + addAll(mergedEvents) + } + withContext(Dispatchers.Main) { + listenersMapping.keys.forEach { listener -> + tryOrNull { listener.onTimelineUpdated(merged) } + } + } + } +} From 736a8a13d918061e1fd5938d71e2677a692eab1b Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 11 Jun 2021 16:34:39 +0200 Subject: [PATCH 2/7] Merging timeline: start branching it --- .../features/call/lookup/CallUserMapper.kt | 8 +++ .../home/room/detail/RoomDetailViewModel.kt | 6 +- .../timeline/factory/TimelineFactory.kt | 59 +++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt index 04177bd2b0..e55d70d79c 100644 --- a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt +++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt @@ -32,6 +32,14 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: return virtualRoomEvent?.content?.toModel()?.nativeRoomId } + fun virtualRoomForNativeRoom(roomId: String): String? { + val virtualRoomEvents = session.accountDataService().getRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)) + return virtualRoomEvents.firstOrNull { + val virtualRoomContent = it.content.toModel() + virtualRoomContent?.nativeRoomId == roomId + }?.roomId + } + suspend fun getOrCreateVirtualRoomForRoom(roomId: String, opponentUserId: String): String? { protocolsChecker.awaitCheckProtocols() if (!protocolsChecker.supportVirtualRooms) return null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a2041c0a80..2abff09b88 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -49,6 +49,7 @@ import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrate import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler +import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever @@ -119,7 +120,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val chatEffectManager: ChatEffectManager, private val directRoomHelper: DirectRoomHelper, private val jitsiService: JitsiService, - timelineSettingsFactory: TimelineSettingsFactory + private val timelineFactory: TimelineFactory, ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { @@ -127,9 +128,8 @@ class RoomDetailViewModel @AssistedInject constructor( private val eventId = initialState.eventId private val invisibleEventsObservable = BehaviorRelay.create() private val visibleEventsObservable = BehaviorRelay.create() - private val timelineSettings = timelineSettingsFactory.create() private var timelineEvents = PublishRelay.create>() - val timeline = room.createTimeline(eventId, timelineSettings) + val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) // Same lifecycle than the ViewModel (survive to screen rotation) val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt new file mode 100644 index 0000000000..c10fd5065a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt @@ -0,0 +1,59 @@ +/* + * 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.factory + +import im.vector.app.features.call.vectorCallService +import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory +import im.vector.app.features.home.room.detail.timeline.merged.MergedTimelines +import kotlinx.coroutines.CoroutineScope +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.room.Room +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import javax.inject.Inject + +private val secondaryTimelineAllowedTypes = listOf( + EventType.CALL_HANGUP, + EventType.CALL_INVITE, + EventType.CALL_REJECT, + EventType.CALL_ANSWER +) + +class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) { + + fun createTimeline(coroutineScope: CoroutineScope, room: Room, eventId: String?): Timeline { + val settings = timelineSettingsFactory.create() + if (!session.vectorCallService.protocolChecker.supportVirtualRooms) { + return room.createTimeline(eventId, settings) + } + val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(room.roomId) + return if (virtualRoomId == null) { + room.createTimeline(eventId, settings) + } else { + val virtualRoom = session.getRoom(virtualRoomId)!! + MergedTimelines( + coroutineScope, + room.createTimeline(eventId, settings), + secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams( + virtualRoom.createTimeline(null, settings), + shouldFilterTypes = true, + allowedTypes = secondaryTimelineAllowedTypes + ) + ) + } + } +} From b7dd7ef3e0c988c23b9829b5d5714cdbaea89051 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 11 Jun 2021 19:27:07 +0200 Subject: [PATCH 3/7] Timeline merging: branch virtual room (still some issues to deal with) --- .../features/call/lookup/CallUserMapper.kt | 2 ++ .../home/room/detail/RoomDetailFragment.kt | 3 +-- .../timeline/factory/CallItemFactory.kt | 6 ++++- .../timeline/factory/TimelineFactory.kt | 14 +++++------ .../detail/timeline/merged/MergedTimelines.kt | 23 +++++++++++++++---- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt index e55d70d79c..161b6d59c8 100644 --- a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt +++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt @@ -27,12 +27,14 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams class CallUserMapper(private val session: Session, private val protocolsChecker: CallProtocolsChecker) { fun nativeRoomForVirtualRoom(roomId: String): String? { + if(!protocolsChecker.supportVirtualRooms) return null val virtualRoom = session.getRoom(roomId) ?: return null val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) return virtualRoomEvent?.content?.toModel()?.nativeRoomId } fun virtualRoomForNativeRoom(roomId: String): String? { + if(!protocolsChecker.supportVirtualRooms) return null val virtualRoomEvents = session.accountDataService().getRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)) return virtualRoomEvents.firstOrNull { val virtualRoomContent = it.content.toModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 8307e93576..024ce9e6d5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1614,8 +1614,7 @@ class RoomDetailFragment @Inject constructor( override fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - val roomId = roomDetailArgs.roomId - + val roomId = roomDetailViewModel.timeline.getTimelineEventWithId(informationData.eventId)?.roomId ?: return false this.view?.hideKeyboard() MessageActionsBottomSheet diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt index 9dcc3e8182..bb6b61e8e9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.VectorEpoxyModel +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.TimelineEventController @@ -26,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHold 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.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.call.CallAnswerContent @@ -38,6 +40,7 @@ import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class CallItemFactory @Inject constructor( + private val session: Session, private val messageColorProvider: MessageColorProvider, private val messageInformationDataFactory: MessageInformationDataFactory, private val messageItemAttributesFactory: MessageItemAttributesFactory, @@ -132,7 +135,8 @@ class CallItemFactory @Inject constructor( isStillActive: Boolean, callback: TimelineEventController.Callback? ): CallTileTimelineItem? { - val userOfInterest = roomSummariesHolder.get(roomId)?.toMatrixItem() ?: return null + val correctedRoomId = session.vectorCallService.userMapper.nativeRoomForVirtualRoom(roomId) ?:roomId + val userOfInterest = roomSummariesHolder.get(correctedRoomId)?.toMatrixItem() ?: return null val attributes = messageItemAttributesFactory.create(null, informationData, callback).let { CallTileTimelineItem.Attributes( callId = callId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt index c10fd5065a..b57e39b3cf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt @@ -35,21 +35,21 @@ private val secondaryTimelineAllowedTypes = listOf( class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) { - fun createTimeline(coroutineScope: CoroutineScope, room: Room, eventId: String?): Timeline { + fun createTimeline(coroutineScope: CoroutineScope, mainRoom: Room, eventId: String?): Timeline { val settings = timelineSettingsFactory.create() if (!session.vectorCallService.protocolChecker.supportVirtualRooms) { - return room.createTimeline(eventId, settings) + return mainRoom.createTimeline(eventId, settings) } - val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(room.roomId) + val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(mainRoom.roomId) return if (virtualRoomId == null) { - room.createTimeline(eventId, settings) + mainRoom.createTimeline(eventId, settings) } else { val virtualRoom = session.getRoom(virtualRoomId)!! MergedTimelines( - coroutineScope, - room.createTimeline(eventId, settings), + coroutineScope = coroutineScope, + mainTimeline = mainRoom.createTimeline(eventId, settings), secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams( - virtualRoom.createTimeline(null, settings), + timeline = virtualRoom.createTimeline(null, settings), shouldFilterTypes = true, allowedTypes = secondaryTimelineAllowedTypes ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt index 5bc5ea0803..6058f2e34b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -155,15 +156,22 @@ class MergedTimelines( val mainItr = mainTimelineEvents.toList().listIterator() val secondaryItr = secondaryTimelineEvents.toList().listIterator() var index = 0 - + var correctedSenderInfo: SenderInfo? = mainTimelineEvents.firstOrNull()?.senderInfo + if (mainTimelineEvents.isEmpty() && mainTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS)) { + return + } + if (secondaryTimelineEvents.isEmpty() && secondaryTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS)) { + return + } while (merged.size < mainTimelineEvents.size + secondaryTimelineEvents.size) { if (mainItr.hasNext()) { val nextMain = mainItr.next() + correctedSenderInfo = nextMain.senderInfo if (secondaryItr.hasNext()) { val nextSecondary = secondaryItr.next() if (nextSecondary.root.originServerTs ?: 0 > nextMain.root.originServerTs ?: 0) { positionsMapping[nextSecondary.eventId] = index - merged.add(nextSecondary) + merged.add(nextSecondary.correctBeforeMerging(correctedSenderInfo)) mainItr.previous() } else { positionsMapping[nextMain.eventId] = index @@ -177,13 +185,13 @@ class MergedTimelines( } else if (secondaryItr.hasNext()) { val nextSecondary = secondaryItr.next() positionsMapping[nextSecondary.eventId] = index - merged.add(nextSecondary) + merged.add(nextSecondary.correctBeforeMerging(correctedSenderInfo)) } index++ } mergedEvents.apply { clear() - addAll(mergedEvents) + addAll(merged) } withContext(Dispatchers.Main) { listenersMapping.keys.forEach { listener -> @@ -191,4 +199,11 @@ class MergedTimelines( } } } + + private fun TimelineEvent.correctBeforeMerging(correctedSenderInfo: SenderInfo?): TimelineEvent { + return copy( + senderInfo = correctedSenderInfo ?: senderInfo, + readReceipts = emptyList() + ) + } } From 99d05d8db32c2fad5fc9c5fb2b15556695714768 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 11 Jun 2021 19:27:35 +0200 Subject: [PATCH 4/7] Theme: fix call tile text color --- .../src/main/res/layout/item_timeline_event_call_tile_stub.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_timeline_event_call_tile_stub.xml b/vector/src/main/res/layout/item_timeline_event_call_tile_stub.xml index ab59802145..368d0ac512 100644 --- a/vector/src/main/res/layout/item_timeline_event_call_tile_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_call_tile_stub.xml @@ -52,7 +52,7 @@ android:layout_marginTop="12dp" android:layout_marginEnd="8dp" android:layout_marginBottom="12dp" - android:textColor="?vctr_notice_secondary" + android:textColor="?vctr_content_secondary" android:textSize="13sp" tools:text="@string/video_call_in_progress" /> From 01d0d1a5ed8a6c15deb9faeb302ce08b3f72bb4f Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 14 Jun 2021 18:46:15 +0200 Subject: [PATCH 5/7] Timeline merge: wait for both timeline to be ready --- .../detail/timeline/merged/MergedTimelines.kt | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt index 6058f2e34b..480fd43037 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -26,7 +26,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import kotlin.reflect.KMutableProperty0 +/** + * This can be use to merge timeline tiles from 2 different rooms. + * Be aware it wont work properly with permalink. + */ class MergedTimelines( private val coroutineScope: CoroutineScope, private val mainTimeline: Timeline, @@ -34,10 +39,13 @@ class MergedTimelines( data class SecondaryTimelineParams( val timeline: Timeline, + val disableReadReceipts: Boolean = true, val shouldFilterTypes: Boolean = false, val allowedTypes: List = emptyList() ) + private var mainIsInit = false + private var secondaryIsInit = false private val secondaryTimeline = secondaryTimelineParams.timeline private val listenersMapping = HashMap>() @@ -70,10 +78,10 @@ class MergedTimelines( override fun addListener(listener: Timeline.Listener): Boolean { val mainTimelineListener = ListenerInterceptor(mainTimeline, listener, false, emptyList()) { - processTimelineUpdates(mainTimelineEvents, it) + processTimelineUpdates(::mainIsInit, mainTimelineEvents, it) } val secondaryTimelineListener = ListenerInterceptor(secondaryTimeline, listener, secondaryTimelineParams.shouldFilterTypes, secondaryTimelineParams.allowedTypes) { - processTimelineUpdates(secondaryTimelineEvents, it) + processTimelineUpdates(::secondaryIsInit, secondaryTimelineEvents, it) } listenersMapping[listener] = listOf(mainTimelineListener, secondaryTimelineListener) return mainTimeline.addListener(mainTimelineListener) && secondaryTimeline.addListener(secondaryTimelineListener) @@ -139,9 +147,10 @@ class MergedTimelines( } } - private fun processTimelineUpdates(eventsRef: MutableList, newData: List) { + private fun processTimelineUpdates(isInit: KMutableProperty0, eventsRef: MutableList, newData: List) { coroutineScope.launch(Dispatchers.Default) { processingSemaphore.withPermit { + isInit.set(true) eventsRef.apply { clear() addAll(newData) @@ -157,10 +166,7 @@ class MergedTimelines( val secondaryItr = secondaryTimelineEvents.toList().listIterator() var index = 0 var correctedSenderInfo: SenderInfo? = mainTimelineEvents.firstOrNull()?.senderInfo - if (mainTimelineEvents.isEmpty() && mainTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS)) { - return - } - if (secondaryTimelineEvents.isEmpty() && secondaryTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS)) { + if (!mainIsInit || !secondaryIsInit) { return } while (merged.size < mainTimelineEvents.size + secondaryTimelineEvents.size) { @@ -203,7 +209,7 @@ class MergedTimelines( private fun TimelineEvent.correctBeforeMerging(correctedSenderInfo: SenderInfo?): TimelineEvent { return copy( senderInfo = correctedSenderInfo ?: senderInfo, - readReceipts = emptyList() + readReceipts = if (secondaryTimelineParams.disableReadReceipts) emptyList() else readReceipts ) } } From 4a8a6d170b601c06909dc2e5172c10dbc1c4a3d0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 17 Jun 2021 12:09:30 +0200 Subject: [PATCH 6/7] Clean and add towncrier file --- newsfragment/3520.misc | 1 + .../app/features/call/lookup/CallUserMapper.kt | 4 ++-- .../features/home/room/detail/RoomDetailViewModel.kt | 3 +-- .../room/detail/timeline/factory/CallItemFactory.kt | 2 +- .../room/detail/timeline/merged/MergedTimelines.kt | 12 ++++++++++-- 5 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 newsfragment/3520.misc diff --git a/newsfragment/3520.misc b/newsfragment/3520.misc new file mode 100644 index 0000000000..d015c3e03d --- /dev/null +++ b/newsfragment/3520.misc @@ -0,0 +1 @@ +VoIP: Merge virtual room timeline in corresponding native room (call events only). \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt index 161b6d59c8..aa7654c405 100644 --- a/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt +++ b/vector/src/main/java/im/vector/app/features/call/lookup/CallUserMapper.kt @@ -27,14 +27,14 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams class CallUserMapper(private val session: Session, private val protocolsChecker: CallProtocolsChecker) { fun nativeRoomForVirtualRoom(roomId: String): String? { - if(!protocolsChecker.supportVirtualRooms) return null + if (!protocolsChecker.supportVirtualRooms) return null val virtualRoom = session.getRoom(roomId) ?: return null val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) return virtualRoomEvent?.content?.toModel()?.nativeRoomId } fun virtualRoomForNativeRoom(roomId: String): String? { - if(!protocolsChecker.supportVirtualRooms) return null + if (!protocolsChecker.supportVirtualRooms) return null val virtualRoomEvents = session.accountDataService().getRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)) return virtualRoomEvents.firstOrNull { val virtualRoomContent = it.content.toModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 29a206eadd..69aa1e83d3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -50,7 +50,6 @@ import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder -import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.powerlevel.PowerLevelsObservableFactory @@ -119,7 +118,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val chatEffectManager: ChatEffectManager, private val directRoomHelper: DirectRoomHelper, private val jitsiService: JitsiService, - private val timelineFactory: TimelineFactory, + timelineFactory: TimelineFactory ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt index bb6b61e8e9..9697fb6672 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt @@ -135,7 +135,7 @@ class CallItemFactory @Inject constructor( isStillActive: Boolean, callback: TimelineEventController.Callback? ): CallTileTimelineItem? { - val correctedRoomId = session.vectorCallService.userMapper.nativeRoomForVirtualRoom(roomId) ?:roomId + val correctedRoomId = session.vectorCallService.userMapper.nativeRoomForVirtualRoom(roomId) ?: roomId val userOfInterest = roomSummariesHolder.get(correctedRoomId)?.toMatrixItem() ?: return null val attributes = messageItemAttributesFactory.create(null, informationData, callback).let { CallTileTimelineItem.Attributes( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt index 480fd43037..0d5dbc5a8e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -77,10 +77,18 @@ class MergedTimelines( } override fun addListener(listener: Timeline.Listener): Boolean { - val mainTimelineListener = ListenerInterceptor(mainTimeline, listener, false, emptyList()) { + val mainTimelineListener = ListenerInterceptor( + timeline = mainTimeline, + wrappedListener = listener, + shouldFilterTypes = false, + allowedTypes = emptyList()) { processTimelineUpdates(::mainIsInit, mainTimelineEvents, it) } - val secondaryTimelineListener = ListenerInterceptor(secondaryTimeline, listener, secondaryTimelineParams.shouldFilterTypes, secondaryTimelineParams.allowedTypes) { + val secondaryTimelineListener = ListenerInterceptor( + timeline = secondaryTimeline, + wrappedListener = listener, + shouldFilterTypes = secondaryTimelineParams.shouldFilterTypes, + allowedTypes = secondaryTimelineParams.allowedTypes) { processTimelineUpdates(::secondaryIsInit, secondaryTimelineEvents, it) } listenersMapping[listener] = listOf(mainTimelineListener, secondaryTimelineListener) From 5eb0097fb28d3a001428d3e919133546710012a8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Jun 2021 14:40:38 +0200 Subject: [PATCH 7/7] Delete unused drawable to avoid conflict on develop --- .../src/main/res/drawable/rounded_rect_shape_16.xml | 11 ----------- .../src/main/res/drawable/rounded_rect_stroke_8.xml | 11 ----------- 2 files changed, 22 deletions(-) delete mode 100644 vector/src/main/res/drawable/rounded_rect_shape_16.xml delete mode 100644 vector/src/main/res/drawable/rounded_rect_stroke_8.xml diff --git a/vector/src/main/res/drawable/rounded_rect_shape_16.xml b/vector/src/main/res/drawable/rounded_rect_shape_16.xml deleted file mode 100644 index 27dabcaa74..0000000000 --- a/vector/src/main/res/drawable/rounded_rect_shape_16.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/drawable/rounded_rect_stroke_8.xml b/vector/src/main/res/drawable/rounded_rect_stroke_8.xml deleted file mode 100644 index 576c5ad622..0000000000 --- a/vector/src/main/res/drawable/rounded_rect_stroke_8.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file