From 5781adb163e0164465c19d43f735e998220ce147 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 8 Jun 2021 20:08:28 +0200 Subject: [PATCH 01/89] 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 02/89] 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 03/89] 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 04/89] 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 05/89] 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 6e83f0510e34610f9c3735a34d186f2d919ca5a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jun 2021 06:24:33 +0000 Subject: [PATCH 06/89] Bump realm-gradle-plugin from 10.5.0 to 10.6.0 Bumps [realm-gradle-plugin](https://github.com/realm/realm-java) from 10.5.0 to 10.6.0. - [Release notes](https://github.com/realm/realm-java/releases) - [Changelog](https://github.com/realm/realm-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/realm/realm-java/compare/v10.5.0...v10.6.0) --- updated-dependencies: - dependency-name: io.realm:realm-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 60352e4a97..11faf3320e 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -9,7 +9,7 @@ buildscript { mavenCentral() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.5.0" + classpath "io.realm:realm-gradle-plugin:10.6.0" } } From dd74b8a7558bcf2f966ff2ed69706a7b91fc62cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jun 2021 06:25:21 +0000 Subject: [PATCH 07/89] Bump libphonenumber from 8.12.24 to 8.12.25 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.24 to 8.12.25. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.12.24...v8.12.25) --- updated-dependencies: - dependency-name: com.googlecode.libphonenumber:libphonenumber dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 60352e4a97..7cd0bba0d8 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -169,7 +169,7 @@ dependencies { implementation 'com.otaliastudios:transcoder:0.10.3' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.25' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' diff --git a/vector/build.gradle b/vector/build.gradle index dcc91ce87d..2eafb3fa4e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -354,7 +354,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.25' // rx implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' From 5e32e9800051ed49836fcc1eb6cb2d5018a22b20 Mon Sep 17 00:00:00 2001 From: gradle-update-robot Date: Wed, 16 Jun 2021 00:09:41 +0000 Subject: [PATCH 08/89] Update Gradle Wrapper from 7.0.2 to 7.1. Signed-off-by: gradle-update-robot --- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1e2fd2c75..283500479a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionSha256Sum=a9e356a21595348b6f04b024ed0b08ac8aea6b2ac37e6c0ef58e51549cd7b9cb +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c81..744e882ed5 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) From e277deece55925940121865272f4a0a3ac1335c4 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 16 Jun 2021 17:35:00 +0200 Subject: [PATCH 09/89] Add error feedback when joining space rooms --- newsfragment/3207.bugfix | 1 + .../home/room/list/RoomSummaryItemFactory.kt | 16 ++++++++++++++-- .../home/room/list/SpaceChildInfoItem.kt | 5 +++++ .../spaces/explore/SpaceDirectoryController.kt | 14 ++++++++++++-- .../spaces/explore/SpaceDirectoryViewModel.kt | 9 +++++---- .../src/main/res/layout/item_suggested_room.xml | 15 +++++++++++++-- vector/src/main/res/values/strings.xml | 1 + 7 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 newsfragment/3207.bugfix diff --git a/newsfragment/3207.bugfix b/newsfragment/3207.bugfix new file mode 100644 index 0000000000..162485907b --- /dev/null +++ b/newsfragment/3207.bugfix @@ -0,0 +1 @@ +Space Explore Rooms no feedback on failed to join \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index f0380b80be..17d96f210f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -17,11 +17,13 @@ package im.vector.app.features.home.room.list import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter @@ -37,7 +39,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor private val dateFormatter: VectorDateFormatter, private val stringProvider: StringProvider, private val typingHelper: TypingHelper, - private val avatarRenderer: AvatarRenderer) { + private val avatarRenderer: AvatarRenderer, + private val errorFormatter: ErrorFormatter) { fun create(roomSummary: RoomSummary, roomChangeMembershipStates: Map, @@ -55,12 +58,21 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor fun createSuggestion(spaceChildInfo: SpaceChildInfo, suggestedRoomJoiningStates: Map>, listener: RoomListListener?): VectorEpoxyModel<*> { + val error = (suggestedRoomJoiningStates[spaceChildInfo.childRoomId] as? Fail)?.error return SpaceChildInfoItem_() .id("sug_${spaceChildInfo.childRoomId}") .matrixItem(spaceChildInfo.toMatrixItem()) .avatarRenderer(avatarRenderer) .topic(spaceChildInfo.topic) - .buttonLabel(stringProvider.getString(R.string.join)) + .errorLabel( + error?.let { + stringProvider.getString(R.string.error_failed_to_join_room, errorFormatter.toHumanReadable(it)) + } + ) + .buttonLabel( + if (error != null) stringProvider.getString(R.string.global_retry) + else stringProvider.getString(R.string.join) + ) .loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading) .memberCount(spaceChildInfo.activeMemberCount ?: 0) .buttonClickListener { listener?.onJoinSuggestedRoom(spaceChildInfo) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt index 61fcd1ba86..99cbd45294 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceChildInfoItem.kt @@ -33,6 +33,7 @@ import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.themes.ThemeUtils import me.gujun.android.span.image @@ -52,6 +53,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( @EpoxyAttribute var loading: Boolean = false @EpoxyAttribute var buttonLabel: String? = null + @EpoxyAttribute var errorLabel: String? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null @@ -97,6 +99,8 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( holder.joinButton.isVisible = true } + holder.errorTextView.setTextOrHide(errorLabel) + holder.joinButton.onClick { // local echo holder.joinButton.isEnabled = false @@ -120,5 +124,6 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel( val descriptionText by bind(R.id.suggestedRoomDescription) val avatarImageView by bind(R.id.roomAvatarImageView) val rootView by bind(R.id.itemRoomLayout) + val errorTextView by bind(R.id.inlineErrorText) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index 34bb50b871..1e67da7a4d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -35,6 +35,7 @@ import im.vector.app.features.home.room.list.spaceChildInfoItem import me.gujun.android.span.span import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError.Companion.M_UNRECOGNIZED +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.util.toMatrixItem @@ -127,6 +128,7 @@ class SpaceDirectoryController @Inject constructor( val isSpace = info.roomType == RoomType.SPACE val isJoined = data?.joinedRoomsIds?.contains(info.childRoomId) == true val isLoading = data?.changeMembershipStates?.get(info.childRoomId)?.isInProgress() ?: false + val error = (data?.changeMembershipStates?.get(info.childRoomId) as? ChangeMembershipState.FailedJoining)?.throwable // if it's known use that matrixItem because it would have a better computed name val matrixItem = data?.knownRoomSummaries?.find { it.roomId == info.childRoomId }?.toMatrixItem() ?: info.toMatrixItem() @@ -135,11 +137,19 @@ class SpaceDirectoryController @Inject constructor( matrixItem(matrixItem) avatarRenderer(host.avatarRenderer) topic(info.topic) + errorLabel( + error?.let { + host.stringProvider.getString(R.string.error_failed_to_join_room, host.errorFormatter.toHumanReadable(it)) + } + ) memberCount(info.activeMemberCount ?: 0) loading(isLoading) buttonLabel( - if (isJoined) host.stringProvider.getString(R.string.action_open) - else host.stringProvider.getString(R.string.join) + when { + error != null -> host.stringProvider.getString(R.string.global_retry) + isJoined -> host.stringProvider.getString(R.string.action_open) + else -> host.stringProvider.getString(R.string.join) + } ) apply { if (isSpace) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 3d3e1dac65..de31f43322 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -188,20 +188,21 @@ class SpaceDirectoryViewModel @AssistedInject constructor( private fun handleJoinOrOpen(spaceChildInfo: SpaceChildInfo) = withState { state -> val isSpace = spaceChildInfo.roomType == RoomType.SPACE - if (state.joinedRoomsIds.contains(spaceChildInfo.childRoomId)) { + val childId = spaceChildInfo.childRoomId + if (state.joinedRoomsIds.contains(childId)) { if (isSpace) { handle(SpaceDirectoryViewAction.ExploreSubSpace(spaceChildInfo)) } else { - _viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(spaceChildInfo.childRoomId)) + _viewEvents.post(SpaceDirectoryViewEvents.NavigateToRoom(childId)) } } else { // join viewModelScope.launch { try { if (isSpace) { - session.spaceService().joinSpace(spaceChildInfo.childRoomId, null, spaceChildInfo.viaServers) + session.spaceService().joinSpace(childId, null, spaceChildInfo.viaServers) } else { - session.joinRoom(spaceChildInfo.childRoomId, null, spaceChildInfo.viaServers) + session.joinRoom(childId, null, spaceChildInfo.viaServers) } } catch (failure: Throwable) { Timber.e(failure, "## Space: Failed to join room or subspace") diff --git a/vector/src/main/res/layout/item_suggested_room.xml b/vector/src/main/res/layout/item_suggested_room.xml index 28803daef1..bc8dd03542 100644 --- a/vector/src/main/res/layout/item_suggested_room.xml +++ b/vector/src/main/res/layout/item_suggested_room.xml @@ -80,7 +80,7 @@ android:layout_marginEnd="8dp" android:maxWidth="@dimen/button_max_width" android:text="@string/join" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/inlineErrorText" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -102,6 +102,17 @@ app:barrierDirection="bottom" app:constraint_referenced_ids="roomAvatarBottomSpace" /> + + + app:layout_constraintTop_toBottomOf="@+id/inlineErrorText" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 621ecb7aee..f792038c07 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3403,4 +3403,5 @@ "Teammate spaces aren’t quite ready but you can still give them a try" "At the moment people might not be able to join any private rooms you make.\n\nWe’ll be improving this as part of the beta, but just wanted to let you know." + Sorry, an error occurred while trying to join: %s From 7c19c9f0b7064ad9cb4ec52ebf834b18e428aecf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Jun 2021 23:08:30 +0000 Subject: [PATCH 10/89] Bump fragment-ktx from 1.3.4 to 1.3.5 Bumps fragment-ktx from 1.3.4 to 1.3.5. --- updated-dependencies: - dependency-name: androidx.fragment:fragment-ktx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- multipicker/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/multipicker/build.gradle b/multipicker/build.gradle index ecccc76ad8..a993c452b0 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -43,7 +43,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.3.0' - implementation "androidx.fragment:fragment-ktx:1.3.4" + implementation "androidx.fragment:fragment-ktx:1.3.5" implementation 'androidx.exifinterface:exifinterface:1.3.2' // Log diff --git a/vector/build.gradle b/vector/build.gradle index 21f62647c0..9d1eec2ad7 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -300,7 +300,7 @@ android { dependencies { def epoxy_version = '4.6.2' - def fragment_version = '1.3.4' + def fragment_version = '1.3.5' def arrow_version = "0.8.2" def markwon_version = '4.1.2' def big_image_viewer_version = '1.8.0' From 2a675bf8d1166ad9b005c54352be9a14b86a3762 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 17 Jun 2021 09:27:31 +0200 Subject: [PATCH 11/89] Code review --- vector/src/main/res/layout/item_suggested_room.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_suggested_room.xml b/vector/src/main/res/layout/item_suggested_room.xml index bc8dd03542..011aea8dae 100644 --- a/vector/src/main/res/layout/item_suggested_room.xml +++ b/vector/src/main/res/layout/item_suggested_room.xml @@ -104,11 +104,12 @@ From 4a8a6d170b601c06909dc2e5172c10dbc1c4a3d0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 17 Jun 2021 12:09:30 +0200 Subject: [PATCH 12/89] 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 646f00f3fcbdc9d06d149d772c41e1b14fee1dc1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 17 Jun 2021 20:49:08 +0200 Subject: [PATCH 13/89] First test for auto accept invite [WIP] --- .../java/im/vector/app/AppStateHandler.kt | 61 ++++++++++++++++--- .../features/call/lookup/CallUserMapper.kt | 4 -- .../app/features/home/HomeDetailViewModel.kt | 17 +----- .../home/UnreadMessagesSharedViewModel.kt | 11 +--- .../room/list/GroupRoomListSectionBuilder.kt | 19 ------ .../room/list/SpaceRoomListSectionBuilder.kt | 37 ----------- .../NotificationDrawerManager.kt | 4 +- .../features/spaces/SpacesListViewModel.kt | 5 +- 8 files changed, 59 insertions(+), 99 deletions(-) diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index c5eac7e3e0..eb60a66436 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -24,13 +24,20 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.session.coroutineScope import im.vector.app.features.ui.UiStateRepository +import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.group.model.GroupSummary +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.rx.rx +import timber.log.Timber +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -49,19 +56,22 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr // TODO Keep this class for now, will maybe be used fro Space @Singleton class AppStateHandler @Inject constructor( - sessionDataSource: ActiveSessionDataSource, + private val sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, private val activeSessionHolder: ActiveSessionHolder ) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() - private val selectedSpaceDataSource = BehaviorDataSource>(Option.empty()) val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? = selectedSpaceDataSource.currentValue?.orNull() + init { + observeActiveSession() + } + fun setCurrentSpace(spaceId: String?, session: Session? = null) { val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace @@ -92,12 +102,13 @@ class AppStateHandler @Inject constructor( } } - init { + private fun observeActiveSession(){ sessionDataSource.observe() .distinctUntilChanged() .subscribe { - // sessionDataSource could already return a session while acitveSession holder still returns null + // sessionDataSource could already return a session while activeSession holder still returns null it.orNull()?.let { session -> + observeInvitesForAutoAccept(session) if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session) } else { @@ -109,6 +120,41 @@ class AppStateHandler @Inject constructor( } } + private fun observeInvitesForAutoAccept(session: Session?) { + if (session == null) return + val roomQueryParams = roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + } + val rxSession = session.rx() + Observable + .combineLatest( + rxSession.liveRoomSummaries(roomQueryParams).debounce(1, TimeUnit.SECONDS), + rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), + { invitedRooms, membershipsChanged -> + val roomIdsToJoin = mutableListOf() + for (room in invitedRooms) { + val roomMembershipChanged = membershipsChanged[room.roomId] ?: ChangeMembershipState.Unknown + if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { + roomIdsToJoin.add(room.roomId) + } + } + roomIdsToJoin + } + ) + .doOnNext { roomIdsToJoin -> + session.coroutineScope.launch { + for (roomId in roomIdsToJoin) { + Timber.v("Auto accept invite for room: $roomId") + tryOrNull { session.joinRoom(roomId) } + } + } + } + .subscribe() + .also { + compositeDisposable.add(it) + } + } + fun safeActiveSpaceId(): String? { return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId } @@ -117,16 +163,11 @@ class AppStateHandler @Inject constructor( return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId } - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun entersForeground() { - } - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { - compositeDisposable.clear() val session = activeSessionHolder.getSafeActiveSession() ?: return when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { - is RoomGroupingMethod.BySpace -> { + is RoomGroupingMethod.BySpace -> { uiStateRepository.storeGroupingMethod(true, session.sessionId) uiStateRepository.storeSelectedSpace(currentMethod.spaceSummary?.roomId, session.sessionId) } 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..243f1ab6fb 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 @@ -57,10 +57,6 @@ class CallUserMapper(private val session: Session, private val protocolsChecker: // will make sure we know where how to map calls and also allow us know not to display // it in the future. invitedRoom.markVirtual(nativeRoomId) - // also auto-join the virtual room if we have a matching native room - // (possibly we should only join if we've also joined the native room, then we'd also have - // to make sure we joined virtual rooms on joining a native one) - session.joinRoom(invitedRoomId) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index b6210ae019..d636259512 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -204,21 +204,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } is RoomGroupingMethod.BySpace -> { val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId - val dmInvites = session.getRoomSummaries( - roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None - } - ).size - - val roomsInvite = session.getRoomSummaries( - roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) - } - ).size + val dmInvites = 0 + val roomsInvite = 0 val dmRooms = session.getNotificationCountForRooms( roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 9b506b6ed7..44a2f8cf09 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -92,12 +92,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) } ) - val invites = session.getRoomSummaries( - roomSummaryQueryParams { - this.memberships = listOf(Membership.INVITE) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) - } - ).size + val invites = 0 copy( homeSpaceUnread = RoomAggregateNotificationCount( counts.notificationCount + invites, @@ -129,9 +124,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia is RoomGroupingMethod.BySpace -> { val selectedSpace = appStateHandler.safeActiveSpaceId() - val inviteCount = session.getRoomSummaries( - roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - ).size + val inviteCount = 0 val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt index 22b0eb091c..62b0612d4b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt @@ -115,16 +115,6 @@ class GroupRoomListSectionBuilder( private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList, actualGroupId: String?) { - addSection( - sections, - activeSpaceAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - it.activeGroupId = actualGroupId - } addSection( sections, @@ -180,15 +170,6 @@ class GroupRoomListSectionBuilder( activeSpaceAwareQueries: MutableList, actualGroupId: String? ) { - addSection(sections, - activeSpaceAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM - it.activeGroupId = actualGroupId - } addSection( sections, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index 266adf6b0c..e1b205eca2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -88,21 +88,6 @@ class SpaceRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { - addSection( - sections = sections, - activeSpaceUpdaters = activeSpaceAwareQueries, - nameRes = R.string.invitations_header, - notifyOfLocalEcho = true, - spaceFilterStrategy = if (onlyOrphansInHome) { - RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL - } else { - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL - }, - countRoomAsNotif = true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ALL - } addSection( sections = sections, @@ -136,18 +121,6 @@ class SpaceRoomListSectionBuilder( } private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { - addSection( - sections = sections, - activeSpaceUpdaters = activeSpaceAwareQueries, - nameRes = R.string.invitations_header, - notifyOfLocalEcho = true, - spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, - countRoomAsNotif = true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - } - addSection( sections, activeSpaceAwareQueries, @@ -253,16 +226,6 @@ class SpaceRoomListSectionBuilder( } private fun buildDmSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { - addSection(sections = sections, - activeSpaceUpdaters = activeSpaceAwareQueries, - nameRes = R.string.invitations_header, - notifyOfLocalEcho = true, - spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, - countRoomAsNotif = true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM - } addSection(sections, activeSpaceAwareQueries, diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 7ac9b28b9a..cf49b5ce0f 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -253,7 +253,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context roomEvents.add(event) } } - is InviteNotifiableEvent -> invitationEvents.add(event) + is InviteNotifiableEvent -> { + //invitationEvents.add(event) + } is SimpleNotifiableEvent -> simpleEvents.add(event) else -> Timber.w("Type not handled") } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index d7d7af4ca5..c77a0f36c9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -119,10 +119,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) .subscribe { - val inviteCount = session.getRoomSummaries( - roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - ).size - + val inviteCount = 0 val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) From f6ac57ec9359229e452f7f87252221dc6a549114 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 21:11:06 +0000 Subject: [PATCH 14/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index db7f940e38..3d5fada807 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2876,4 +2876,9 @@ Enviar vídeo com o tamanho original Enviar vídeos com o tamanho original + No momento pessoas podem não ser capaz de se juntar a quaisquer salas privadas que você fizer. +\n +\nNós vamos melhorar isto como parte da beta, mas só queríamos deixar você saber. + Espaços de colegas de trabalho não estão bem prontos mas você ainda pode dar-lhes uma tentativa + Continuar Mesmo Assim \ No newline at end of file From 51119ceec4b990caf8d812a2f1e1f4edb3903f54 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 21:36:51 +0000 Subject: [PATCH 15/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 3d5fada807..6a86e58d11 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -429,7 +429,7 @@ Falha para verificar endereço de email: assegure-se que clicou no link no email Sua senha tem sido resettada. \n -\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para reativar notificações, faça re-login em cada dispositivo. +\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para re-ativar notificações, re-faça login em cada dispositivo. URL deve começar com http[s]:// Incapaz de fazer login: Erro de rede From 861d652d88a3331ba2093dd98fa85486289a85f0 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 21:45:05 +0000 Subject: [PATCH 16/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 6a86e58d11..0f684d145c 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1995,7 +1995,7 @@ Eles correspondem Eles não correspondem Verifique esta(e) usuária(o) ao confirmar que os seguintes emoji únicos aparecem na tela dela(e), na mesma ordem. - Para máxima segurança, use um outro meio de comunicação confiado ou faça isto em pessoa. + Para segurança ótima, use um outro meio de comunicação confiado ou faça isto em pessoa. Procure pelo escudo verde para assegurar que um/uma usuário(a) é confiado. Confie em todos(as) os/as usuários(as) numa sala para assegurar que a sala é segura. Não seguro Um dos seguintes pode estar comprometido: From 1ea48f89b4bf44bf5e6492de18f7d15b66b6fb43 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 21:56:35 +0000 Subject: [PATCH 17/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 0f684d145c..458d8243b7 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1358,7 +1358,7 @@ Desbanir usuária(o) Desbanir usuária(o) vai permitir-lhe se juntar à sala de novo. Confirme sua senha - Você não pode fazer isto de ${app_name} celular + Você não pode fazer isto desde ${app_name} mobile Autenticação é requerida O app não precisa de se conectar ao ServidorCasa no background, isto deveria reduzir uso de bateria Modo Sinc no Background From de0b745eb980883002430af04cfa2e2e412ebba3 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 22:10:33 +0000 Subject: [PATCH 18/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 458d8243b7..cf45cbf5ee 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1602,7 +1602,7 @@ Esperando por parceira(o) confirmar… Verificada! Você tem confirmado esta sessão com sucesso. - Mensagens seguras com esta(e) usuária(o) estão encriptadas ponta-a-ponta e não são capazes de ser lidas por terceiros. + Mensagens seguras com esta(e) usuária(o) são encriptadas ponta-a-ponta e não são capazes de ser lidas por terceiros. Entendido Nada aparecendo\? Não todos os clientes suportam verificação interativa ainda. Use verificação legado. Usar verificação legado. From e6e7f82eb625b54f62fd07bcd02f40230614197a Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 22:36:15 +0000 Subject: [PATCH 19/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index cf45cbf5ee..ce235e3b8a 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1943,7 +1943,7 @@ Este não é um identificador de usuária(o) válido. Formato esperado: \'@usuarix:servidorcasa.org\' Incapaz de encontrar um servidorcasa válido. Por favor cheque seu identificador Vista por - Você fez signout + Você está com signout feito Pode ser devido a várias razões: \n \n• Você tem mudado sua senha numa outra sessão. @@ -1952,7 +1952,7 @@ \n \n• O/a administrador(a) de seu servidor tem invalidado seu acesso por razão de segurança. Fazer signin de novo - Você fez signout + Você está com signout feito Fazer signin A/o admin de seu servidorcasa (%1$s) fez seu signout de sua conta %2$s (%3$s). Faça signin para recuperar chaves de encriptação armazenadas exclusivamente neste dispositivo. Você precisa delas para ler todas suas mensagens seguras em qualquer dispositivo. @@ -2361,7 +2361,7 @@ Você não pode acessar esta mensagem porque o/a enviador(a) propositalmente não enviou as chaves Esperando por histórico de encriptação Riot agora é Element! - Nós estamos animados em anunciar que nós mudamos de nome! Seu app está atualizado e o signin está feito a sua conta. + Nós estamos animados em anunciar que nós mudamos de nome! Seu app está atualizado e você está com signin feito a sua conta. ENTENDI SABER MAIS Salvar chave de recuperação em From 7a12d29d3977e04fcc96288652f7bc61fbe672ef Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 22:37:07 +0000 Subject: [PATCH 20/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index ce235e3b8a..617ceb8fb8 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2361,7 +2361,7 @@ Você não pode acessar esta mensagem porque o/a enviador(a) propositalmente não enviou as chaves Esperando por histórico de encriptação Riot agora é Element! - Nós estamos animados em anunciar que nós mudamos de nome! Seu app está atualizado e você está com signin feito a sua conta. + Nós estamos animados em anunciar que nós temos mudado de nome! Seu app está atualizado e você está com signin feito a sua conta. ENTENDI SABER MAIS Salvar chave de recuperação em From e826900bd3c3d0de122295551907d72581594b60 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 22:38:38 +0000 Subject: [PATCH 21/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 617ceb8fb8..e85ef28212 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1637,7 +1637,7 @@ Convidada(o) por %s Você está em dia! Você não tem mais nenhuma mensagem não-lida - Boas-vindas! + Boas vindas a casa! Fique em dia com suas mensagens não-lidas aqui Conversas Suas conversas de mensagem direta vai ser exibidas aqui. Toque no + à direita fundo para começar algumas. @@ -2790,7 +2790,7 @@ Convidar por nome de usuária(o) Convidar por email Convidar pessoas - Boas-vindas a %1$s, %2$s. + Boas vindas a %1$s, %2$s. Você está convidada(o) Aviso requer suporte de servidor e versão de sala experimental %s convida você @@ -2810,7 +2810,7 @@ Procurando por alguém que não está em %s\? Espaço Experimental - Sala Restringida. Espaços são uma nova forma de agrupar salas e pessoas. - Boas-vindas a Espaços! + Boas vindas a Espaços! Adicionar salas Adicionar salas e espaços existentes Você é admin deste espaço, assegure-se que você tem transferido direito de admin a um outro membro antes de sair. From 08af370600090cd5c3663df676eebf96b15133bb Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 22:50:45 +0000 Subject: [PATCH 22/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index e85ef28212..191b9846ad 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2510,7 +2510,7 @@ Rotar e recortar Configurações de sala Tópico - Tópico da sala (opcional) + Tópico de sala (opcional) Nome de sala Enviar histórico de requisições de compartilhamento de chaves Mais nenhum resultado From a1bfe099adb9ffb702113e60254ec27d8cc1f2e6 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 22:56:50 +0000 Subject: [PATCH 23/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 191b9846ad..f5a9635a48 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1077,7 +1077,7 @@ Privacidade de Notificação ${app_name} pode rodar no background para gerenciar suas notificações seguramente e privadamente. Isto pode afetar uso de bateria. Conceder permissão - Escolha um outra opção + Escolher uma outra opção Enviar dados de analítica ${app_name} coleta analítica anônima para nos permitir melhorar o aplicativo. Por favor ative analítica para nos ajudar a melhorar ${app_name}. @@ -1846,7 +1846,7 @@ Junte-se a milhões de graça no maior servidor público Hospedagem premium para organizações Saiba mais - Outros + Outro Configurações personalizadas & avançadas Continuar Conectar-se a %1$s @@ -2010,7 +2010,7 @@ Arquivo Sticker Esperando… - %s cancelado + %s cancelou Você cancelou %s aceitou Você aceitou From d4c8c645d1fa1148645b14be677252190317f69e Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 17 Jun 2021 23:04:15 +0000 Subject: [PATCH 24/89] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2490 of 2490 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index f5a9635a48..eaec973570 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2519,7 +2519,7 @@ Mostrar avançadas Esconder avançadas Link Matrix - %s para deixar pessoas sabendo do que esta sala se trata. + %s para deixar pessoas saberem do que esta sala se trata. Por favor proveja um endereço de sala Recente QR code não scannado! From 22f3f612d7501222ccbf71cd6a8ae70603f91382 Mon Sep 17 00:00:00 2001 From: Erik Huizinga Date: Fri, 18 Jun 2021 12:26:30 +0200 Subject: [PATCH 25/89] Apply Google Services Gradle plugin more robustly Fixes #3527 --- vector/build.gradle | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 21f62647c0..10d593994e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -246,6 +246,8 @@ android { productFlavors { gplay { + apply plugin: 'com.google.gms.google-services' + dimension "store" isDefault = true versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" @@ -506,7 +508,3 @@ dependencies { exclude group: 'org.jetbrains.kotlin' } } - -if (getGradle().getStartParameter().getTaskRequests().toString().contains("Gplay")) { - apply plugin: 'com.google.gms.google-services' -} From cc5260b31138476bc49fca67936e312106297b06 Mon Sep 17 00:00:00 2001 From: Erik Huizinga Date: Fri, 18 Jun 2021 17:23:10 +0200 Subject: [PATCH 26/89] Disable non-Gplay flavor *GoogleServices* tasks --- vector/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index 10d593994e..62afed1b0a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -247,6 +247,9 @@ android { productFlavors { gplay { apply plugin: 'com.google.gms.google-services' + afterEvaluate { + tasks.matching { it.name.contains("GoogleServices") && !it.name.contains("Gplay") }*.enabled = false + } dimension "store" isDefault = true From 6b10406622e3cbc1022538664e70cc81f50ee9ed Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 18 Jun 2021 17:24:51 +0200 Subject: [PATCH 27/89] AutoAcceptInvite: refact and hide behind flag --- .../sdk/api/session/room/RoomService.kt | 6 ++ .../sdk/internal/session/SessionModule.kt | 3 + .../session/room/DefaultRoomService.kt | 4 + .../RoomChangeMembershipStateDataSource.kt | 3 +- .../room/membership/joining/JoinRoomTask.kt | 4 + .../java/im/vector/app/AppStateHandler.kt | 50 +--------- .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../im/vector/app/core/di/VectorComponent.kt | 3 + .../im/vector/app/core/di/VectorModule.kt | 5 + .../app/features/home/HomeDetailViewModel.kt | 25 ++++- .../home/UnreadMessagesSharedViewModel.kt | 25 ++++- .../room/list/GroupRoomListSectionBuilder.kt | 52 +++++++--- .../home/room/list/RoomListViewModel.kt | 6 +- .../room/list/RoomListViewModelFactory.kt | 15 +-- .../room/list/SpaceRoomListSectionBuilder.kt | 55 ++++++++++- .../app/features/invite/AutoAcceptInvites.kt | 29 ++++++ .../app/features/invite/InvitesAcceptor.kt | 97 +++++++++++++++++++ .../NotificationDrawerManager.kt | 11 ++- .../features/spaces/SpacesListViewModel.kt | 12 ++- 19 files changed, 325 insertions(+), 82 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt create mode 100644 vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 871c5378a6..b7377df1b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -125,6 +125,12 @@ interface RoomService { */ suspend fun deleteRoomAlias(roomAlias: String) + /** + * Return the current local changes membership for the given room. + * see [getChangeMembershipsLive] for more details. + */ + fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState + /** * Return a live data of all local changes membership that happened since the session has been opened. * It allows you to track this in your client to known what is currently being processed by the SDK. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index e6da21315b..c750cb1f73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -386,4 +386,7 @@ internal abstract class SessionModule { @Binds abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor + + + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index d9fe1288e2..632ea4c450 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor( deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) } + override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState { + return roomChangeMembershipStateDataSource.getState(roomIdOrAlias) + } + override fun getChangeMembershipsLive(): LiveData> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt index b9c547d4fb..35d8cb08af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.session.SessionScope +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** @@ -30,7 +31,7 @@ import javax.inject.Inject internal class RoomChangeMembershipStateDataSource @Inject constructor() { private val mutableLiveStates = MutableLiveData>(emptyMap()) - private val states = HashMap() + private val states = ConcurrentHashMap() /** * This will update local states to be synced with the server. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 33776e4f6e..562b25683b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor( ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { + val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias) + if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) { + return + } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { executeRequest(globalErrorReceiver) { diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index eb60a66436..3822bd6c08 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -22,22 +22,16 @@ import androidx.lifecycle.OnLifecycleEvent import arrow.core.Option import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource +import im.vector.app.features.invite.InvitesAcceptor import im.vector.app.features.session.coroutineScope import im.vector.app.features.ui.UiStateRepository -import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState -import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.rx -import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -58,7 +52,8 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, - private val activeSessionHolder: ActiveSessionHolder + private val activeSessionHolder: ActiveSessionHolder, + private val invitesAcceptor: InvitesAcceptor ) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() @@ -102,13 +97,13 @@ class AppStateHandler @Inject constructor( } } - private fun observeActiveSession(){ + private fun observeActiveSession() { sessionDataSource.observe() .distinctUntilChanged() .subscribe { // sessionDataSource could already return a session while activeSession holder still returns null it.orNull()?.let { session -> - observeInvitesForAutoAccept(session) + invitesAcceptor.onSessionActive(session) if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session) } else { @@ -120,41 +115,6 @@ class AppStateHandler @Inject constructor( } } - private fun observeInvitesForAutoAccept(session: Session?) { - if (session == null) return - val roomQueryParams = roomSummaryQueryParams { - this.memberships = listOf(Membership.INVITE) - } - val rxSession = session.rx() - Observable - .combineLatest( - rxSession.liveRoomSummaries(roomQueryParams).debounce(1, TimeUnit.SECONDS), - rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), - { invitedRooms, membershipsChanged -> - val roomIdsToJoin = mutableListOf() - for (room in invitedRooms) { - val roomMembershipChanged = membershipsChanged[room.roomId] ?: ChangeMembershipState.Unknown - if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { - roomIdsToJoin.add(room.roomId) - } - } - roomIdsToJoin - } - ) - .doOnNext { roomIdsToJoin -> - session.coroutineScope.launch { - for (roomId in roomIdsToJoin) { - Timber.v("Auto accept invite for room: $roomId") - tryOrNull { session.joinRoom(roomId) } - } - } - } - .subscribe() - .also { - compositeDisposable.add(it) - } - } - fun safeActiveSpaceId(): String? { return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId } diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 3c11bfcd13..38edb771bb 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.home.room.list.RoomListModule import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.link.LinkHandlerActivity @@ -122,6 +123,7 @@ interface ScreenComponent { fun errorFormatter(): ErrorFormatter fun uiStateRepository(): UiStateRepository fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog + fun autoAcceptInvites(): AutoAcceptInvites /* ========================================================================================== * Activities diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index e5a47e872c..4a3379cb5a 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorPr import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.VectorHtmlCompressor +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotifiableEventResolver @@ -160,6 +161,8 @@ interface VectorComponent { fun pinLocker(): PinLocker + fun autoAcceptInvites(): AutoAcceptInvites + fun webRtcCallManager(): WebRtcCallManager fun roomSummaryHolder(): RoomSummariesHolder diff --git a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt index 77cad0ae73..006a2f5aa0 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt @@ -25,6 +25,8 @@ import dagger.Module import dagger.Provides import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter +import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.navigation.DefaultNavigator import im.vector.app.features.navigation.Navigator import im.vector.app.features.pin.PinCodeStore @@ -105,4 +107,7 @@ abstract class VectorModule { @Binds abstract fun bindPinCodeStore(store: SharedPrefPinCodeStore): PinCodeStore + + @Binds + abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index d636259512..f282a3137c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -31,6 +31,7 @@ import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.createdirect.DirectRoomHelper +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.ui.UiStateRepository import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers @@ -56,7 +57,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val uiStateRepository: UiStateRepository, private val callManager: WebRtcCallManager, private val directRoomHelper: DirectRoomHelper, - private val appStateHandler: AppStateHandler) + private val appStateHandler: AppStateHandler, +private val autoAcceptInvites: AutoAcceptInvites) : VectorViewModel(initialState), CallProtocolsChecker.Listener { @@ -204,8 +206,25 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } is RoomGroupingMethod.BySpace -> { val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId - val dmInvites = 0 - val roomsInvite = 0 + var dmInvites = 0 + var roomsInvite = 0 + if(!autoAcceptInvites.hideInvites) { + dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None + } + ).size + + roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) + } + ).size + } val dmRooms = session.getNotificationCountForRooms( roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 44a2f8cf09..e9e2447b39 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import io.reactivex.Observable import io.reactivex.schedulers.Schedulers @@ -54,7 +55,8 @@ data class CountInfo( class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState, session: Session, private val vectorPreferences: VectorPreferences, - appStateHandler: AppStateHandler) + appStateHandler: AppStateHandler, + private val autoAcceptInvites: AutoAcceptInvites) : VectorViewModel(initialState) { @AssistedFactory @@ -92,7 +94,17 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) } ) - val invites = 0 + val invites = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + } + ).size + } + copy( homeSpaceUnread = RoomAggregateNotificationCount( counts.notificationCount + invites, @@ -124,8 +136,13 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia is RoomGroupingMethod.BySpace -> { val selectedSpace = appStateHandler.safeActiveSpaceId() - val inviteCount = 0 - + val inviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt index 62b0612d4b..a23251510f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt @@ -22,6 +22,7 @@ import im.vector.app.R import im.vector.app.RoomGroupingMethod import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.invite.AutoAcceptInvites import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope @@ -38,6 +39,7 @@ class GroupRoomListSectionBuilder( val stringProvider: StringProvider, val viewModelScope: CoroutineScope, val appStateHandler: AppStateHandler, + private val autoAcceptInvites: AutoAcceptInvites, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { @@ -48,15 +50,15 @@ class GroupRoomListSectionBuilder( val actualGroupId = appStateHandler.safeActiveGroupId() when (mode) { - RoomListDisplayMode.PEOPLE -> { + RoomListDisplayMode.PEOPLE -> { // 3 sections Invites / Fav / Dms buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId) } - RoomListDisplayMode.ROOMS -> { + RoomListDisplayMode.ROOMS -> { // 5 sections invites / Fav / Rooms / Low Priority / Server notice buildRoomsSections(sections, activeGroupAwareQueries, actualGroupId) } - RoomListDisplayMode.FILTERED -> { + RoomListDisplayMode.FILTERED -> { // Used when searching for rooms withQueryParams( { @@ -73,17 +75,18 @@ class GroupRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { - addSection( - sections, - activeGroupAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ALL - it.activeGroupId = actualGroupId + if (!autoAcceptInvites.hideInvites) { + addSection( + sections, + activeGroupAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + it.activeGroupId = actualGroupId + } } - addSection( sections, activeGroupAwareQueries, @@ -115,6 +118,18 @@ class GroupRoomListSectionBuilder( private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList, actualGroupId: String?) { + if (!autoAcceptInvites.hideInvites) { + addSection( + sections, + activeSpaceAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.activeGroupId = actualGroupId + } + } addSection( sections, @@ -170,6 +185,17 @@ class GroupRoomListSectionBuilder( activeSpaceAwareQueries: MutableList, actualGroupId: String? ) { + if (!autoAcceptInvites.hideInvites) { + addSection(sections, + activeSpaceAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + it.activeGroupId = actualGroupId + } + } addSection( sections, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index fbb8faebb0..c5f166ea5b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -49,7 +50,8 @@ class RoomListViewModel @Inject constructor( private val session: Session, private val stringProvider: StringProvider, private val appStateHandler: AppStateHandler, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { interface Factory { @@ -126,6 +128,7 @@ class RoomListViewModel @Inject constructor( appStateHandler, viewModelScope, suggestedRoomJoiningState, + autoAcceptInvites, { it.disposeOnClear() }, @@ -140,6 +143,7 @@ class RoomListViewModel @Inject constructor( stringProvider, viewModelScope, appStateHandler, + autoAcceptInvites, { it.disposeOnClear() }, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt index a30c175f41..6b269356c7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list import im.vector.app.AppStateHandler import im.vector.app.core.resources.StringProvider +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -26,16 +27,18 @@ import javax.inject.Provider class RoomListViewModelFactory @Inject constructor(private val session: Provider, private val appStateHandler: AppStateHandler, private val stringProvider: StringProvider, - private val vectorPreferences: VectorPreferences) + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites) : RoomListViewModel.Factory { override fun create(initialState: RoomListViewState): RoomListViewModel { return RoomListViewModel( - initialState, - session.get(), - stringProvider, - appStateHandler, - vectorPreferences + initialState = initialState, + session = session.get(), + stringProvider = stringProvider, + appStateHandler = appStateHandler, + vectorPreferences = vectorPreferences, + autoAcceptInvites = autoAcceptInvites ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index e1b205eca2..7d3816af26 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -26,6 +26,7 @@ import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.space import io.reactivex.Observable import io.reactivex.disposables.Disposable @@ -50,6 +51,7 @@ class SpaceRoomListSectionBuilder( val appStateHandler: AppStateHandler, val viewModelScope: CoroutineScope, private val suggestedRoomJoiningState: LiveData>>, + private val autoAcceptInvites: AutoAcceptInvites, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit, val onlyOrphansInHome: Boolean = false @@ -66,13 +68,13 @@ class SpaceRoomListSectionBuilder( val sections = mutableListOf() val activeSpaceAwareQueries = mutableListOf() when (mode) { - RoomListDisplayMode.PEOPLE -> { + RoomListDisplayMode.PEOPLE -> { buildDmSections(sections, activeSpaceAwareQueries) } - RoomListDisplayMode.ROOMS -> { + RoomListDisplayMode.ROOMS -> { buildRoomsSections(sections, activeSpaceAwareQueries) } - RoomListDisplayMode.FILTERED -> { + RoomListDisplayMode.FILTERED -> { withQueryParams( { it.memberships = Membership.activeMemberships() @@ -88,6 +90,23 @@ class SpaceRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { + if (!autoAcceptInvites.hideInvites) { + addSection( + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + }, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + } + } addSection( sections = sections, @@ -121,6 +140,20 @@ class SpaceRoomListSectionBuilder( } private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { + if (!autoAcceptInvites.hideInvites) { + addSection( + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + } + addSection( sections, activeSpaceAwareQueries, @@ -226,6 +259,18 @@ class SpaceRoomListSectionBuilder( } private fun buildDmSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { + if (!autoAcceptInvites.hideInvites) { + addSection(sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + } addSection(sections, activeSpaceAwareQueries, @@ -350,7 +395,7 @@ class SpaceRoomListSectionBuilder( activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) ) } - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { if (currentSpace == null) { copy( activeSpaceFilter = ActiveSpaceFilter.None @@ -361,7 +406,7 @@ class SpaceRoomListSectionBuilder( ) } } - RoomListViewModel.SpaceFilterStrategy.NONE -> this + RoomListViewModel.SpaceFilterStrategy.NONE -> this } } } diff --git a/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt new file mode 100644 index 0000000000..4027ba23d5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt @@ -0,0 +1,29 @@ +/* + * 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.invite + +import javax.inject.Inject + +interface AutoAcceptInvites { + val isEnabled: Boolean + val hideInvites: Boolean +} + +class CompileTimeAutoAcceptInvites @Inject constructor() : AutoAcceptInvites { + override val isEnabled = true + override val hideInvites = false +} diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt new file mode 100644 index 0000000000..f084eb9bb4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -0,0 +1,97 @@ +/* + * 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.invite + +import im.vector.app.features.session.coroutineScope +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.rx.rx +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This class is responsible for auto accepting invites. + * It's listening to invites and membershipChanges so it can retry automatically if needed. + * This mechanism will be on only if AutoAcceptInvites.isEnabled is true. + */ +@Singleton +class InvitesAcceptor @Inject constructor(private val autoAcceptInvites: AutoAcceptInvites) : Session.Listener { + + private val disposables = HashMap() + private val semaphore = Semaphore(1) + + fun onSessionActive(session: Session) { + if (!autoAcceptInvites.isEnabled) { + return + } + if (disposables.containsKey(session.sessionId)) { + return + } + session.addListener(this) + val roomQueryParams = roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + } + val rxSession = session.rx() + Observable + .combineLatest( + rxSession.liveRoomSummaries(roomQueryParams), + rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), + { invitedRooms, _ -> invitedRooms.map { it.roomId } } + ) + .filter { it.isNotEmpty() } + .subscribe { invitedRoomIds -> + session.coroutineScope.launch { + semaphore.withPermit { + Timber.v("Invited roomIds: $invitedRoomIds") + for (roomId in invitedRoomIds) { + async { session.joinRoomSafely(roomId) }.start() + } + } + } + } + .also { + disposables[session.sessionId] = it + } + } + + private suspend fun Session.joinRoomSafely(roomId: String) { + val roomMembershipChanged = getChangeMemberships(roomId) + if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { + try { + Timber.v("Try auto join room: $roomId") + joinRoom(roomId) + } catch (failure: Throwable) { + Timber.v("Failed auto join room: $roomId") + } + } + } + + override fun onSessionStopped(session: Session) { + session.removeListener(this) + disposables.remove(session.sessionId)?.dispose() + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index cf49b5ce0f..c051a866b5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -27,6 +27,7 @@ import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.FirstThrottler +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -50,7 +51,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context private val activeSessionDataSource: ActiveSessionDataSource, private val iconLoader: IconLoader, private val bitmapLoader: BitmapLoader, - private val outdatedDetector: OutdatedEventDetector?) { + private val outdatedDetector: OutdatedEventDetector?, + private val autoAcceptInvites: AutoAcceptInvites) { private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY) private var backgroundHandler: Handler @@ -254,7 +256,12 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } } is InviteNotifiableEvent -> { - //invitationEvents.add(event) + if(autoAcceptInvites.hideInvites){ + // Forget this event + eventIterator.remove() + }else { + invitationEvents.add(event) + } } is SimpleNotifiableEvent -> simpleEvents.add(event) else -> Timber.w("Type not handled") diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index c77a0f36c9..93205364f2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -28,6 +28,7 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import im.vector.app.group import im.vector.app.space @@ -54,7 +55,8 @@ import java.util.concurrent.TimeUnit class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, private val appStateHandler: AppStateHandler, private val session: Session, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { @AssistedFactory @@ -119,7 +121,13 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) .subscribe { - val inviteCount = 0 + val inviteCount = if(autoAcceptInvites.hideInvites){ + 0 + }else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) From c551cf305827e70722cbe98a0755244d3fa707b8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 18 Jun 2021 17:30:32 +0200 Subject: [PATCH 28/89] Clean and add towncrier --- .../org/matrix/android/sdk/internal/session/SessionModule.kt | 3 --- newsfragment/3531.feature | 1 + .../java/im/vector/app/features/home/HomeDetailViewModel.kt | 2 +- .../java/im/vector/app/features/invite/AutoAcceptInvites.kt | 4 ++-- .../app/features/notifications/NotificationDrawerManager.kt | 4 ++-- .../java/im/vector/app/features/spaces/SpacesListViewModel.kt | 4 ++-- 6 files changed, 8 insertions(+), 10 deletions(-) create mode 100644 newsfragment/3531.feature diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index c750cb1f73..e6da21315b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -386,7 +386,4 @@ internal abstract class SessionModule { @Binds abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor - - - } diff --git a/newsfragment/3531.feature b/newsfragment/3531.feature new file mode 100644 index 0000000000..e8b63584e3 --- /dev/null +++ b/newsfragment/3531.feature @@ -0,0 +1 @@ +Introduces AutoAcceptInvites which can be enabled at compile time. \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index f282a3137c..3614b345c6 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -208,7 +208,7 @@ private val autoAcceptInvites: AutoAcceptInvites) val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId var dmInvites = 0 var roomsInvite = 0 - if(!autoAcceptInvites.hideInvites) { + if (!autoAcceptInvites.hideInvites) { dmInvites = session.getRoomSummaries( roomSummaryQueryParams { memberships = listOf(Membership.INVITE) diff --git a/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt index 4027ba23d5..4f39ee0925 100644 --- a/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt +++ b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt @@ -24,6 +24,6 @@ interface AutoAcceptInvites { } class CompileTimeAutoAcceptInvites @Inject constructor() : AutoAcceptInvites { - override val isEnabled = true - override val hideInvites = false + override val isEnabled = false + override val hideInvites = isEnabled } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index c051a866b5..37ed1e654a 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -256,10 +256,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } } is InviteNotifiableEvent -> { - if(autoAcceptInvites.hideInvites){ + if (autoAcceptInvites.hideInvites) { // Forget this event eventIterator.remove() - }else { + } else { invitationEvents.add(event) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 93205364f2..ca1dc54cf5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -121,9 +121,9 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) .subscribe { - val inviteCount = if(autoAcceptInvites.hideInvites){ + val inviteCount = if (autoAcceptInvites.hideInvites) { 0 - }else { + } else { session.getRoomSummaries( roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } ).size From 4ce71c8487861ceaec33dd2fe385a9959d9ffbe7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Jun 2021 21:52:53 +0200 Subject: [PATCH 29/89] Create a style for password edit --- .../res/layout/activity_debug_text_view.xml | 16 ++++++++++++++++ .../src/main/res/values/styles_edit_text.xml | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/library/ui-styles/src/debug/res/layout/activity_debug_text_view.xml b/library/ui-styles/src/debug/res/layout/activity_debug_text_view.xml index 0c16f889ec..6ca83c5a8a 100644 --- a/library/ui-styles/src/debug/res/layout/activity_debug_text_view.xml +++ b/library/ui-styles/src/debug/res/layout/activity_debug_text_view.xml @@ -4,6 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:padding="16dp" tools:ignore="HardcodedText"> + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml index d23c11c96c..cc1041a2ce 100644 --- a/library/ui-styles/src/main/res/values/styles_edit_text.xml +++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml @@ -4,6 +4,11 @@ + @@ -27,7 +27,7 @@ @@ -35,7 +35,7 @@ @@ -49,7 +49,7 @@ @@ -62,7 +62,7 @@ @@ -70,14 +70,14 @@ From 121fd9a19dbe20421123bd5cc7ebd1e851b7776e Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 22 Jun 2021 16:29:19 +0200 Subject: [PATCH 46/89] Jump to unread: implement new design --- .../main/res/values/styles_jump_to_unread.xml | 27 +++++++++ .../src/main/res/values/theme_dark.xml | 1 + .../src/main/res/values/theme_light.xml | 1 + .../app/core/ui/views/JumpToReadMarkerView.kt | 56 ------------------- .../home/room/detail/RoomDetailFragment.kt | 17 +++--- .../main/res/drawable/ic_jump_to_unread.xml | 10 ++++ .../main/res/layout/fragment_room_detail.xml | 36 +++++------- .../res/layout/view_jump_to_read_marker.xml | 41 -------------- vector/src/main/res/values/strings.xml | 2 +- 9 files changed, 63 insertions(+), 128 deletions(-) create mode 100644 library/ui-styles/src/main/res/values/styles_jump_to_unread.xml delete mode 100644 vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt create mode 100644 vector/src/main/res/drawable/ic_jump_to_unread.xml delete mode 100644 vector/src/main/res/layout/view_jump_to_read_marker.xml diff --git a/library/ui-styles/src/main/res/values/styles_jump_to_unread.xml b/library/ui-styles/src/main/res/values/styles_jump_to_unread.xml new file mode 100644 index 0000000000..d0561425ff --- /dev/null +++ b/library/ui-styles/src/main/res/values/styles_jump_to_unread.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index b9965439ff..69f1b4d5cc 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -86,6 +86,7 @@ @style/Widget.Vector.SnackBar.Button @style/Widget.Vector.SnackBar.TextView @style/Widget.Vector.ActionMode + @style/Widget.Vector.JumpToUnread.Dark @style/Theme.Vector.BottomSheetDialog.Dark diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index 531f590b6c..db379028f3 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -86,6 +86,7 @@ @style/Widget.Vector.SnackBar.Button @style/Widget.Vector.SnackBar.TextView @style/Widget.Vector.ActionMode + @style/Widget.Vector.JumpToUnread.Light @style/Theme.Vector.BottomSheetDialog.Light diff --git a/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt b/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt deleted file mode 100644 index 28e23c0e50..0000000000 --- a/vector/src/main/java/im/vector/app/core/ui/views/JumpToReadMarkerView.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.core.ui.views - -import android.content.Context -import android.util.AttributeSet -import android.view.View -import android.widget.RelativeLayout -import androidx.core.content.ContextCompat -import im.vector.app.R -import im.vector.app.databinding.ViewJumpToReadMarkerBinding - -class JumpToReadMarkerView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RelativeLayout(context, attrs, defStyleAttr) { - - interface Callback { - fun onJumpToReadMarkerClicked() - fun onClearReadMarkerClicked() - } - - var callback: Callback? = null - - init { - setupView() - } - - private fun setupView() { - inflate(context, R.layout.view_jump_to_read_marker, this) - val views = ViewJumpToReadMarkerBinding.bind(this) - setBackgroundColor(ContextCompat.getColor(context, R.color.notification_accent_color)) - views.jumpToReadMarkerLabelView.setOnClickListener { - callback?.onJumpToReadMarkerClicked() - } - views.closeJumpToReadMarkerView.setOnClickListener { - visibility = View.INVISIBLE - callback?.onClearReadMarkerClicked() - } - } -} 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..46a47f9be6 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 @@ -1,3 +1,4 @@ + /* * Copyright 2019 New Vector Ltd * @@ -94,7 +95,6 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.FailedMessagesWarningView -import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.utils.Debouncer @@ -239,7 +239,6 @@ class RoomDetailFragment @Inject constructor( VectorBaseFragment(), TimelineEventController.Callback, VectorInviteView.Callback, - JumpToReadMarkerView.Callback, AttachmentTypeSelectorView.Callback, AttachmentsHelper.Callback, GalleryOrCameraDialogHelper.Listener, @@ -725,7 +724,13 @@ class RoomDetailFragment @Inject constructor( } private fun setupJumpToReadMarkerView() { - views.jumpToReadMarkerView.callback = this + views.jumpToReadMarkerView.setOnClickListener { + onJumpToReadMarkerClicked() + } + views.jumpToReadMarkerView.setOnCloseIconClickListener { + views.jumpToReadMarkerView.isVisible = false + onClearReadMarkerClicked() + } } private fun setupActiveCallView() { @@ -1959,9 +1964,7 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.RejectInvite) } -// JumpToReadMarkerView.Callback - - override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) { + private fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) { views.jumpToReadMarkerView.isVisible = false if (it.unreadState is UnreadState.HasUnread) { roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false)) @@ -1971,7 +1974,7 @@ class RoomDetailFragment @Inject constructor( } } - override fun onClearReadMarkerClicked() { + private fun onClearReadMarkerClicked() { roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead) } diff --git a/vector/src/main/res/drawable/ic_jump_to_unread.xml b/vector/src/main/res/drawable/ic_jump_to_unread.xml new file mode 100644 index 0000000000..2c5b8b90c1 --- /dev/null +++ b/vector/src/main/res/drawable/ic_jump_to_unread.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 19d85d5393..de0f95f583 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -127,32 +127,22 @@ app:layout_constraintTop_toBottomOf="@id/activeConferenceView" tools:listitem="@layout/item_timeline_event_base" /> - + app:layout_constraintTop_toBottomOf="@id/activeConferenceView" + tools:visibility="visible" /> - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/jumpToReadMarkerView"> - - - - - - - diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 54356db664..5a95a96e5c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -783,7 +783,7 @@ List members Open header Syncing… - Jump to first unread message. + Jump to unread You have been invited to join this room by %s From 13ec7a500a4468ff4bf38beb17b6cf43ffaa65a4 Mon Sep 17 00:00:00 2001 From: Ridhubharan Date: Tue, 22 Jun 2021 17:31:49 +0000 Subject: [PATCH 47/89] Added translation using Weblate (Tamil) --- vector/src/main/res/values-ta/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 vector/src/main/res/values-ta/strings.xml diff --git a/vector/src/main/res/values-ta/strings.xml b/vector/src/main/res/values-ta/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/vector/src/main/res/values-ta/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From a148d21cdbe38fd0bb64635aae1b05eea29fae8a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Jun 2021 12:36:43 +0200 Subject: [PATCH 48/89] Update doc --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbff8652cf..5151a618f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ Supported filename extensions are: - ``.bugfix``: Signifying a bug fix. - ``.doc``: Signifying a documentation improvement. - ``.removal``: Signifying a deprecation or removal of public API. Can be used to notifying about API change in the Matrix SDK -- ``.misc``: A ticket has been closed, but it is not of interest to users. Note that in this case, the content of the file will not be output, but just the issue/PR number. +- ``.misc``: Any other changes. See https://github.com/twisted/towncrier#news-fragments if you need more details. From d0a7265975bde45bfee13514a9929c997350a0fa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Jun 2021 13:01:48 +0200 Subject: [PATCH 49/89] Reorder buttons --- vector/src/debug/res/layout/activity_debug_menu.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index 60cfc1e826..a83f61266a 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -114,6 +114,12 @@ android:text="Vector" /> +