From 21deb2551d952b64aee1f86a2160a8b9370eb420 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 12 Aug 2019 17:59:07 +0200 Subject: [PATCH] Read receipts: handle read receipts set on filtered events + let BottomSheet takes a snapshot instead of being live. --- .../session/room/timeline/TimelineService.kt | 6 +- .../session/room/timeline/TimelineSettings.kt | 39 ++++ .../database/helper/ChunkEntityHelper.kt | 2 +- .../mapper/ReadReceiptsSummaryMapper.kt | 5 +- .../database/mapper/TimelineEventMapper.kt | 13 +- .../model/ReadReceiptsSummaryEntity.kt | 6 + .../query/ReadReceiptsSummaryEntityQueries.kt | 8 + .../internal/session/room/RoomFactory.kt | 2 +- .../session/room/timeline/DefaultTimeline.kt | 178 +++++++++++++----- .../room/timeline/DefaultTimelineService.kt | 10 +- .../session/sync/ReadReceiptHandler.kt | 2 +- .../vector/riotx/core/di/ViewModelModule.kt | 6 - .../home/room/detail/RoomDetailFragment.kt | 4 +- .../home/room/detail/RoomDetailViewModel.kt | 24 +-- .../DisplayReadReceiptsBottomSheet.kt | 34 ++-- .../DisplayReadReceiptsController.kt | 49 ++--- .../DisplayReadReceiptsViewModel.kt | 63 ------- .../DisplayReadReceiptsViewState.kt | 33 ---- .../timeline/TimelineEventController.kt | 3 +- .../timeline/factory/MessageItemFactory.kt | 25 +-- .../timeline/factory/TimelineItemFactory.kt | 27 +-- .../timeline/format/NoticeEventFormatter.kt | 14 +- .../detail/timeline/item/AbsMessageItem.kt | 2 +- .../room/detail/timeline/item/NoticeItem.kt | 2 +- .../util/MessageInformationDataFactory.kt | 5 +- 25 files changed, 277 insertions(+), 285 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewModel.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewState.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt index 5a4838ad..fdf99bd2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineService.kt @@ -25,12 +25,12 @@ interface TimelineService { /** * Instantiate a [Timeline] with an optional initial eventId, to be used with permalink. - * You can filter the type you want to grab with the allowedTypes param. + * You can also configure some settings with the [settings] param. * @param eventId the optional initial eventId. - * @param allowedTypes the optional filter types + * @param settings settings to configure the timeline. * @return the instantiated timeline */ - fun createTimeline(eventId: String?, allowedTypes: List? = null): Timeline + fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline fun getTimeLineEvent(eventId: String): TimelineEvent? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt new file mode 100644 index 00000000..219c23eb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineSettings.kt @@ -0,0 +1,39 @@ +/* + * 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.matrix.android.api.session.room.timeline + +/** + * Data class holding setting values for a [Timeline] instance. + */ +data class TimelineSettings( + /** + * The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet. + */ + val initialSize: Int, + /** + * A flag to filter edit events + */ + val filterEdits: Boolean = false, + /** + * A flag to filter by types. It should be used with [allowedTypes] field + */ + val filterTypes: Boolean = false, + /** + * If [filterTypes] is true, the list of types allowed by the list. + */ + val allowedTypes: List = emptyList() +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index a541e8cf..69065f51 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -140,7 +140,7 @@ internal fun ChunkEntity.add(roomId: String, val senderId = event.senderId ?: "" val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() - ?: ReadReceiptsSummaryEntity(eventId) + ?: ReadReceiptsSummaryEntity(eventId, roomId) // Update RR for the sender of a new message with a dummy one diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/ReadReceiptsSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/ReadReceiptsSummaryMapper.kt index b7cdabfc..1f53d1b4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/ReadReceiptsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/ReadReceiptsSummaryMapper.kt @@ -28,7 +28,10 @@ import javax.inject.Inject internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) { - fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity): List { + fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List { + if (readReceiptsSummaryEntity == null) { + return emptyList() + } return Realm.getInstance(realmConfiguration).use { realm -> val readReceipts = readReceiptsSummaryEntity.readReceipts readReceipts diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt index 5290692c..42365d7e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/TimelineEventMapper.kt @@ -17,15 +17,18 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.database.model.TimelineEventEntity import javax.inject.Inject -internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper){ - - fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent { +internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) { + fun map(timelineEventEntity: TimelineEventEntity, correctedReadReceipts: List? = null): TimelineEvent { + val readReceipts = correctedReadReceipts ?: timelineEventEntity.readReceipts?.let { + readReceiptsSummaryMapper.map(it) + } return TimelineEvent( root = timelineEventEntity.root?.asDomain() ?: Event("", timelineEventEntity.eventId), @@ -35,9 +38,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS senderName = timelineEventEntity.senderName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = timelineEventEntity.senderAvatar, - readReceipts = timelineEventEntity.readReceipts?.let { - readReceiptsSummaryMapper.map(it) - } ?: emptyList() + readReceipts = readReceipts ?: emptyList() ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadReceiptsSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadReceiptsSummaryEntity.kt index e0fe970f..56e8938c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadReceiptsSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReadReceiptsSummaryEntity.kt @@ -18,14 +18,20 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmList import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey internal open class ReadReceiptsSummaryEntity( @PrimaryKey var eventId: String = "", + var roomId: String = "", var readReceipts: RealmList = RealmList() ) : RealmObject() { + @LinkingObjects("readReceipts") + val timelineEvent: RealmResults? = null + companion object } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt index d04ced11..0c3d7d8e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptsSummaryEntityQueries.kt @@ -26,3 +26,11 @@ internal fun ReadReceiptsSummaryEntity.Companion.where(realm: Realm, eventId: St return realm.where() .equalTo(ReadReceiptsSummaryEntityFields.EVENT_ID, eventId) } + +internal fun ReadReceiptsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery { + val query = realm.where() + if (roomId != null) { + query.equalTo(ReadReceiptsSummaryEntityFields.ROOM_ID, roomId) + } + return query +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index cf2627b0..e68e4282 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -65,7 +65,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, private val leaveRoomTask: LeaveRoomTask) { fun create(roomId: String): Room { - val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask, timelineEventMapper) + val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask, timelineEventMapper, readReceiptsSummaryMapper) val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 921c65ea..059b2415 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -16,25 +16,47 @@ package im.vector.matrix.android.internal.session.room.timeline +import android.util.SparseArray import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.util.CancelableBag +import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.* +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.ChunkEntityFields +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.query.* +import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity +import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields +import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields +import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates +import im.vector.matrix.android.internal.database.query.findIncludingEvent +import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.database.query.whereInRoom import im.vector.matrix.android.internal.task.TaskConstraints import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.Debouncer import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createUIHandler -import io.realm.* +import io.realm.OrderedCollectionChangeSet +import io.realm.OrderedRealmCollectionChangeListener +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.RealmQuery +import io.realm.RealmResults +import io.realm.Sort import timber.log.Timber import java.util.* import java.util.concurrent.atomic.AtomicBoolean @@ -43,10 +65,11 @@ import kotlin.collections.ArrayList import kotlin.collections.HashMap -private const val INITIAL_LOAD_SIZE = 30 private const val MIN_FETCHING_COUNT = 30 private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE +private const val EDIT_FILTER_LIKE = "{*\"m.relates_to\"*\"rel_type\":*\"m.replace\"*}" + internal class DefaultTimeline( private val roomId: String, private val initialEventId: String? = null, @@ -56,7 +79,8 @@ internal class DefaultTimeline( private val paginationTask: PaginationTask, private val cryptoService: CryptoService, private val timelineEventMapper: TimelineEventMapper, - private val allowedTypes: List? + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, + private val settings: TimelineSettings ) : Timeline { private companion object { @@ -79,6 +103,11 @@ internal class DefaultTimeline( private val debouncer = Debouncer(mainHandler) private lateinit var liveEvents: RealmResults + private lateinit var eventRelations: RealmResults + private var hiddenReadReceipts: RealmResults? = null + private val correctedReadReceiptsEventByIndex = SparseArray() + private val correctedReadReceiptsByEvent = HashMap>() + private var roomEntity: RoomEntity? = null private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN @@ -92,7 +121,6 @@ internal class DefaultTimeline( private val timelineID = UUID.randomUUID().toString() - private lateinit var eventRelations: RealmResults private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService) @@ -132,9 +160,9 @@ internal class DefaultTimeline( val eventEntity = results[index] eventEntity?.eventId?.let { eventId -> builtEventsIdMap[eventId]?.let { builtIndex -> - //Update the relation of existing event + //Update an existing event builtEvents[builtIndex]?.let { te -> - builtEvents[builtIndex] = timelineEventMapper.map(eventEntity) + builtEvents[builtIndex] = timelineEventMapper.map(eventEntity, correctedReadReceiptsByEvent[te.root.eventId]) hasChanged = true } } @@ -164,32 +192,54 @@ internal class DefaultTimeline( postSnapshot() } -// private val newSessionListener = object : NewSessionListener { -// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { -// if (roomId == this@DefaultTimeline.roomId) { -// Timber.v("New session id detected for this room") -// BACKGROUND_HANDLER.post { -// val realm = backgroundRealm.get() -// var hasChange = false -// builtEvents.forEachIndexed { index, timelineEvent -> -// if (timelineEvent.isEncrypted()) { -// val eventContent = timelineEvent.root.content.toModel() -// if (eventContent?.sessionId == sessionId -// && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { -// //we need to rebuild this event -// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { -// //builtEvents[index] = timelineEventFactory.create(it, realm) -// hasChange = true -// } -// } -// } -// } -// if (hasChange) postSnapshot() -// } -// } -// } -// -// } + private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener> { collection, changeSet -> + var hasChange = false + changeSet.deletions.forEach { + val eventId = correctedReadReceiptsEventByIndex[it] + val timelineEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, eventId).findFirst() + builtEventsIdMap[eventId]?.let { builtIndex -> + builtEvents[builtIndex]?.let { te -> + builtEvents[builtIndex] = te.copy(readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts)) + hasChange = true + } + } + } + correctedReadReceiptsEventByIndex.clear() + correctedReadReceiptsByEvent.clear() + val loadedReadReceipts = collection.where().greaterThan("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.DISPLAY_INDEX}", prevDisplayIndex).findAll() + loadedReadReceipts.forEachIndexed { index, summary -> + val timelineEvent = summary?.timelineEvent?.firstOrNull() + val displayIndex = timelineEvent?.root?.displayIndex + if (displayIndex != null) { + val firstDisplayedEvent = liveEvents.where() + .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) + .lessThan(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex) + .findFirst() + + if (firstDisplayedEvent != null) { + correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId) + correctedReadReceiptsByEvent.getOrPut(firstDisplayedEvent.eventId, { + readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts).toMutableList() + }).addAll( + readReceiptsSummaryMapper.map(summary) + ) + } + } + } + if (correctedReadReceiptsByEvent.isNotEmpty()) { + correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) -> + builtEventsIdMap[eventId]?.let { builtIndex -> + builtEvents[builtIndex]?.let { te -> + builtEvents[builtIndex] = te.copy(readReceipts = correctedReadReceipts) + hasChange = true + } + } + } + } + if (hasChange) { + postSnapshot() + } + } // Public methods ****************************************************************************** @@ -236,15 +286,23 @@ internal class DefaultTimeline( } liveEvents = buildEventQuery(realm) + .filterEventsWithSettings() .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) .findAllAsync() .also { it.addChangeListener(eventsChangeListener) } - isReady.set(true) - eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId) .findAllAsync() .also { it.addChangeListener(relationsListener) } + + hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId) + .isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT) + .isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`) + .filterReceiptsWithSettings() + .findAllAsync() + .also { it.addChangeListener(hiddenReadReceiptsListener) } + + isReady.set(true) } } } @@ -257,6 +315,7 @@ internal class DefaultTimeline( cancelableBag.cancel() roomEntity?.sendingTimelineEvents?.removeAllChangeListeners() eventRelations.removeAllChangeListeners() + hiddenReadReceipts?.removeAllChangeListeners() liveEvents.removeAllChangeListeners() backgroundRealm.getAndSet(null).also { it.close() @@ -274,7 +333,7 @@ internal class DefaultTimeline( private fun hasMoreInCache(direction: Timeline.Direction): Boolean { return Realm.getInstance(realmConfiguration).use { localRealm -> val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction) - ?: return false + ?: return false if (direction == Timeline.Direction.FORWARDS) { if (findCurrentChunk(localRealm)?.isLastForward == true) { return false @@ -331,7 +390,9 @@ internal class DefaultTimeline( val sendingEvents = ArrayList() if (hasReachedEnd(Timeline.Direction.FORWARDS)) { roomEntity?.sendingTimelineEvents - ?.filter { allowedTypes?.contains(it.root?.type) ?: false } + ?.where() + ?.filterEventsWithSettings() + ?.findAll() ?.forEach { sendingEvents.add(timelineEventMapper.map(it)) } @@ -380,7 +441,7 @@ internal class DefaultTimeline( if (initialEventId != null && shouldFetchInitialEvent) { fetchEvent(initialEventId) } else { - val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size) + val count = Math.min(settings.initialSize, liveEvents.size) if (isLive) { paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count) } else { @@ -397,9 +458,9 @@ internal class DefaultTimeline( private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { val token = getTokenLive(direction) ?: return val params = PaginationTask.Params(roomId = roomId, - from = token, - direction = direction.toPaginationDirection(), - limit = limit) + from = token, + direction = direction.toPaginationDirection(), + limit = limit) Timber.v("Should fetch $limit items $direction") cancelableBag += paginationTask @@ -465,10 +526,11 @@ internal class DefaultTimeline( nextDisplayIndex = offsetIndex + 1 } offsetResults.forEach { eventEntity -> + val timelineEvent = timelineEventMapper.map(eventEntity) if (timelineEvent.isEncrypted() - && timelineEvent.root.mxDecryptionResult == null) { + && timelineEvent.root.mxDecryptionResult == null) { timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } } @@ -500,7 +562,6 @@ internal class DefaultTimeline( .greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) } return offsetQuery - .filterAllowedTypes() .limit(count) .findAll() } @@ -559,14 +620,35 @@ internal class DefaultTimeline( } else { sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) } - .filterAllowedTypes() + .filterEventsWithSettings() .findFirst() } - private fun RealmQuery.filterAllowedTypes(): RealmQuery { - if (allowedTypes != null) { - `in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray()) + private fun RealmQuery.filterEventsWithSettings(): RealmQuery { + if (settings.filterTypes) { + `in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray()) } + if (settings.filterEdits) { + not().like(TimelineEventEntityFields.ROOT.CONTENT, EDIT_FILTER_LIKE) + } + return this + } + + /** + * We are looking for receipts related to filtered events. So, it's the opposite of [filterEventsWithSettings] method. + */ + private fun RealmQuery.filterReceiptsWithSettings(): RealmQuery { + beginGroup() + if (settings.filterTypes) { + not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray()) + } + if (settings.filterTypes && settings.filterEdits) { + or() + } + if (settings.filterEdits) { + like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", EDIT_FILTER_LIKE) + } + endGroup() return this } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt index 94fc433a..a2a80274 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineService.kt @@ -23,7 +23,9 @@ import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineService +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.internal.database.RealmLiveData +import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.TimelineEventEntity @@ -38,10 +40,11 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St private val contextOfEventTask: GetContextOfEventTask, private val cryptoService: CryptoService, private val paginationTask: PaginationTask, - private val timelineEventMapper: TimelineEventMapper + private val timelineEventMapper: TimelineEventMapper, + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper ) : TimelineService { - override fun createTimeline(eventId: String?, allowedTypes: List?): Timeline { + override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline { return DefaultTimeline(roomId, eventId, monarchy.realmConfiguration, @@ -50,7 +53,8 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St paginationTask, cryptoService, timelineEventMapper, - allowedTypes + readReceiptsSummaryMapper, + settings ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt index 5098e824..7c752e49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt @@ -62,7 +62,7 @@ internal class ReadReceiptHandler @Inject constructor() { val readReceiptSummaries = ArrayList() for ((eventId, receiptDict) in content) { val userIdsDict = receiptDict[READ_KEY] ?: continue - val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId) + val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId) for ((userId, paramsDict) in userIdsDict) { val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0 diff --git a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt index c2c86cad..b7d63e67 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt @@ -43,8 +43,6 @@ import im.vector.riotx.features.home.room.detail.RoomDetailViewModel import im.vector.riotx.features.home.room.detail.RoomDetailViewModel_AssistedFactory import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel_AssistedFactory -import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsViewModel -import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsViewModel_AssistedFactory import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsViewModel import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsViewModel_AssistedFactory import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel @@ -197,8 +195,4 @@ interface ViewModelModule { @Binds fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory - - @Binds - fun bindDisplayReadReceiptsViewModel(factory: DisplayReadReceiptsViewModel_AssistedFactory): DisplayReadReceiptsViewModel.Factory - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 544f05cd..04dd1150 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -817,8 +817,8 @@ class RoomDetailFragment : }) } - override fun onReadReceiptsClicked(informationData: MessageInformationData) { - DisplayReadReceiptsBottomSheet.newInstance(roomDetailArgs.roomId, informationData) + override fun onReadReceiptsClicked(readReceipts: List) { + DisplayReadReceiptsBottomSheet.newInstance(readReceipts) .show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS") } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 2bb7327d..2bc08bd0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -43,6 +43,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx @@ -73,12 +74,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private val roomId = initialState.roomId private val eventId = initialState.eventId private val displayedEventsObservable = BehaviorRelay.create() - private val allowedTypes = if (userPreferencesProvider.shouldShowHiddenEvents()) { - TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES + private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) { + TimelineSettings(30, false, true, TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES) } else { - TimelineDisplayableEvents.DISPLAYABLE_TYPES + TimelineSettings(30, true, true, TimelineDisplayableEvents.DISPLAYABLE_TYPES) } - private var timeline = room.createTimeline(eventId, allowedTypes) + + private var timeline = room.createTimeline(eventId, timelineSettings) // Slot to keep a pending action during permission request var pendingAction: RoomDetailActions? = null @@ -137,7 +139,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) { val tombstoneContent = action.event.getClearContent().toModel() - ?: return + ?: return val roomId = tombstoneContent.replacementRoom ?: "" val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN @@ -283,7 +285,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro //is original event a reply? val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId - ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId + ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId if (inReplyTo != null) { //TODO check if same content? room.getTimeLineEvent(inReplyTo)?.let { @@ -292,12 +294,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } else { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() val existingBody = messageContent?.body ?: "" if (existingBody != action.text) { room.editTextMessage(state.sendMode.timelineEvent.root.eventId - ?: "", messageContent?.type - ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) + ?: "", messageContent?.type + ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) } else { Timber.w("Same message content, do not send edition") } @@ -312,7 +314,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() val textMsg = messageContent?.body val finalText = legacyRiotQuoteText(textMsg, action.text) @@ -550,7 +552,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } else { // change timeline timeline.dispose() - timeline = room.createTimeline(targetEventId, allowedTypes) + timeline = room.createTimeline(targetEventId, timelineSettings) timeline.start() withState { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index 572954f1..b8c1519f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -16,8 +16,8 @@ package im.vector.riotx.features.home.room.detail.readreceipts -import android.annotation.SuppressLint import android.os.Bundle +import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -27,31 +27,31 @@ import butterknife.BindView import butterknife.ButterKnife import com.airbnb.epoxy.EpoxyRecyclerView import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.riotx.EmojiCompatFontProvider +import com.airbnb.mvrx.args import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent -import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.action.VectorBaseBottomSheetDialogFragment -import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet -import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* import javax.inject.Inject +@Parcelize +data class DisplayReadReceiptArgs( + val readReceipts: List +) : Parcelable + /** * Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp */ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { - private val viewModel: DisplayReadReceiptsViewModel by fragmentViewModel() - - @Inject lateinit var displayReadReceiptsViewModelFactory: DisplayReadReceiptsViewModel.Factory @Inject lateinit var epoxyController: DisplayReadReceiptsController @BindView(R.id.bottom_sheet_display_reactions_list) lateinit var epoxyRecyclerView: EpoxyRecyclerView + private val displayReadReceiptArgs: DisplayReadReceiptArgs by args() override fun injectWith(screenComponent: ScreenComponent) { screenComponent.inject(this) @@ -70,20 +70,18 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { LinearLayout.VERTICAL) epoxyRecyclerView.addItemDecoration(dividerItemDecoration) bottomSheetTitle.text = getString(R.string.read_receipts_list) + epoxyController.setData(displayReadReceiptArgs.readReceipts) } - - override fun invalidate() = withState(viewModel) { - epoxyController.setData(it) + override fun invalidate() { + // we are not using state for this one as it's static } companion object { - fun newInstance(roomId: String, informationData: MessageInformationData): DisplayReadReceiptsBottomSheet { + fun newInstance(readReceipts: List): DisplayReadReceiptsBottomSheet { val args = Bundle() - val parcelableArgs = TimelineEventFragmentArgs( - informationData.eventId, - roomId, - informationData + val parcelableArgs = DisplayReadReceiptArgs( + readReceipts ) args.putParcelable(MvRx.KEY_ARG, parcelableArgs) return DisplayReadReceiptsBottomSheet().apply { arguments = args } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt index 2c2f9f49..51c150be 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsController.kt @@ -17,55 +17,32 @@ package im.vector.riotx.features.home.room.detail.readreceipts import com.airbnb.epoxy.TypedEpoxyController -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.Success import im.vector.matrix.android.api.session.Session -import im.vector.riotx.R import im.vector.riotx.core.date.VectorDateFormatter -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.ui.list.genericFooterItem -import im.vector.riotx.core.ui.list.genericLoaderItem import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import javax.inject.Inject /** * Epoxy controller for read receipt event list */ class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter, - private val stringProvider: StringProvider, private val session: Session, private val avatarRender: AvatarRenderer) - : TypedEpoxyController() { + : TypedEpoxyController>() { - override fun buildModels(state: DisplayReadReceiptsViewState) { - when (state.readReceipts) { - is Incomplete -> { - genericLoaderItem { - id("loading") - } - } - is Fail -> { - genericFooterItem { - id("failure") - text(stringProvider.getString(R.string.unknown_error)) - } - } - is Success -> { - state.readReceipts()?.forEach { - val timestamp = dateFormatter.formatRelativeDateTime(it.originServerTs) - DisplayReadReceiptItem_() - .id(it.user.userId) - .userId(it.user.userId) - .avatarUrl(it.user.avatarUrl) - .name(it.user.displayName) - .avatarRenderer(avatarRender) - .timestamp(timestamp) - .addIf(session.myUserId != it.user.userId, this) - } - } + override fun buildModels(readReceipts: List) { + readReceipts.forEach { + val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp) + DisplayReadReceiptItem_() + .id(it.userId) + .userId(it.userId) + .avatarUrl(it.avatarUrl) + .name(it.displayName) + .avatarRenderer(avatarRender) + .timestamp(timestamp) + .addIf(session.myUserId != it.userId, this) } } - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewModel.kt deleted file mode 100644 index 8423ba4a..00000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewModel.kt +++ /dev/null @@ -1,63 +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.riotx.features.home.room.detail.readreceipts - -import com.airbnb.mvrx.* -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.rx.RxRoom -import im.vector.riotx.core.platform.VectorViewModel - -/** - * Used to display the list of read receipts to a given event - */ -class DisplayReadReceiptsViewModel @AssistedInject constructor(@Assisted initialState: DisplayReadReceiptsViewState, - private val session: Session -) : VectorViewModel(initialState) { - - private val roomId = initialState.roomId - private val eventId = initialState.eventId - private val room = session.getRoom(roomId) - ?: throw IllegalStateException("Shouldn't use this ViewModel without a room") - - @AssistedInject.Factory - interface Factory { - fun create(initialState: DisplayReadReceiptsViewState): DisplayReadReceiptsViewModel - } - - companion object : MvRxViewModelFactory { - - override fun create(viewModelContext: ViewModelContext, state: DisplayReadReceiptsViewState): DisplayReadReceiptsViewModel? { - val fragment: DisplayReadReceiptsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.displayReadReceiptsViewModelFactory.create(state) - } - } - - init { - observeEventAnnotationSummaries() - } - - private fun observeEventAnnotationSummaries() { - RxRoom(room) - .liveEventReadReceipts(eventId) - .execute { - copy(readReceipts = it) - } - } - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewState.kt deleted file mode 100644 index 68952b99..00000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsViewState.kt +++ /dev/null @@ -1,33 +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.riotx.features.home.room.detail.readreceipts - -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized -import im.vector.matrix.android.api.session.room.model.ReadReceipt -import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs - -data class DisplayReadReceiptsViewState( - val eventId: String, - val roomId: String, - val readReceipts: Async> = Uninitialized -) : MvRxState { - - constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) - -} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 28a3d100..3c212d61 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -38,6 +38,7 @@ import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.VideoContentRenderer import org.threeten.bp.LocalDateTime @@ -79,7 +80,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } interface ReadReceiptsCallback { - fun onReadReceiptsClicked(informationData: MessageInformationData) + fun onReadReceiptsClicked(readReceipts: List) } interface UrlClickCallback { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 71e7627d..f3a93a8d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -84,7 +84,7 @@ class MessageItemFactory @Inject constructor( private val imageContentRenderer: ImageContentRenderer, private val messageInformationDataFactory: MessageInformationDataFactory, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, - private val userPreferencesProvider: UserPreferencesProvider) { + private val noticeItemFactory: NoticeItemFactory) { fun create(event: TimelineEvent, @@ -109,27 +109,8 @@ class MessageItemFactory @Inject constructor( if (messageContent.relatesTo?.type == RelationType.REPLACE || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { - // ignore replace event, the targeted id is already edited - if (userPreferencesProvider.shouldShowHiddenEvents()) { - //These are just for debug to display hidden event, they should be filtered out in normal mode - val informationData = MessageInformationData( - eventId = event.root.eventId ?: "?", - senderId = event.root.senderId ?: "", - sendState = event.root.sendState, - time = "", - avatarUrl = event.senderAvatar(), - memberName = "", - showInformation = false - ) - return NoticeItem_() - .avatarRenderer(avatarRenderer) - .informationData(informationData) - .noticeText("{ \"type\": ${event.root.getClearType()} }") - .highlighted(highlight) - .baseCallback(callback) - } else { - return BlankItem_() - } + // This is an edit event, we should it when debugging as a notice event + return noticeItemFactory.create(event, highlight, callback) } // val all = event.root.toContent() // val ev = all.toModel() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 43197d8b..b1ae595e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -25,6 +25,7 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_ +import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory import timber.log.Timber import javax.inject.Inject @@ -33,8 +34,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val encryptedItemFactory: EncryptedItemFactory, private val noticeItemFactory: NoticeItemFactory, private val defaultItemFactory: DefaultItemFactory, - private val roomCreateItemFactory: RoomCreateItemFactory, - private val avatarRenderer: AvatarRenderer) { + private val roomCreateItemFactory: RoomCreateItemFactory) { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -53,7 +53,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> noticeItemFactory.create(event, highlight, callback) + EventType.CALL_ANSWER, + EventType.REACTION, + EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) // Crypto @@ -70,24 +72,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me // Unhandled event types (yet) EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STICKER -> defaultItemFactory.create(event, highlight) - else -> { - //These are just for debug to display hidden event, they should be filtered out in normal mode - val informationData = MessageInformationData( - eventId = event.root.eventId ?: "?", - senderId = event.root.senderId ?: "", - sendState = event.root.sendState, - time = "", - avatarUrl = event.senderAvatar(), - memberName = "", - showInformation = false - ) - NoticeItem_() - .avatarRenderer(avatarRenderer) - .informationData(informationData) - .noticeText("{ \"type\": ${event.root.getClearType()} }") - .highlighted(highlight) - .baseCallback(callback) + Timber.v("Type ${event.root.getClearType()} not handled") + null } } } catch (e: Exception) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 2b1a2633..05ce7a9c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.call.CallInviteContent -import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider @@ -42,6 +41,9 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.MESSAGE, + EventType.REACTION, + EventType.REDACTION -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") null @@ -66,6 +68,10 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin } } + private fun formatDebug(event: Event): CharSequence? { + return "{ \"type\": ${event.getClearType()} }" + } + private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (!TextUtils.isEmpty(content.name)) { @@ -90,7 +96,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { val historyVisibility = event.getClearContent().toModel()?.historyVisibility - ?: return null + ?: return null val formattedVisibility = when (historyVisibility) { RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) @@ -140,7 +146,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) else -> stringProvider.getString(R.string.notice_display_name_changed_from, - event.senderId, prevEventContent?.displayName, eventContent?.displayName) + event.senderId, prevEventContent?.displayName, eventContent?.displayName) } displayText.append(displayNameText) } @@ -167,7 +173,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin when { eventContent.thirdPartyInvite != null -> stringProvider.getString(R.string.notice_room_third_party_registered_invite, - targetDisplayName, eventContent.thirdPartyInvite?.displayName) + targetDisplayName, eventContent.thirdPartyInvite?.displayName) TextUtils.equals(event.stateKey, selfUserId) -> stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) event.stateKey.isNullOrEmpty() -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index 6f3a0a1e..a394f471 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -80,7 +80,7 @@ abstract class AbsMessageItem : BaseEventItem() { }) private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { - readReceiptsCallback?.onReadReceiptsClicked(informationData) + readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts) }) var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt index c4b5f042..51a7b0ce 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt @@ -50,7 +50,7 @@ abstract class NoticeItem : BaseEventItem() { var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { - readReceiptsCallback?.onReadReceiptsClicked(informationData) + readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts) }) override fun bind(holder: Holder) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt index a34c9874..a00dd3fa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/util/MessageInformationDataFactory.kt @@ -52,15 +52,14 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses addDaySeparator || event.senderAvatar != nextEvent?.senderAvatar || event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName() - || (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED) + || (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED) || isNextMessageReceivedMoreThanOneHourAgo val time = dateFormatter.formatMessageHour(date) val avatarUrl = event.senderAvatar val memberName = event.getDisambiguatedDisplayName() val formattedMemberName = span(memberName) { - textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId - ?: "")) + textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: "")) } return MessageInformationData(