mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Read receipts: handle read receipts set on filtered events + let BottomSheet takes a snapshot instead of being live.
This commit is contained in:
parent
70639f180c
commit
21deb2551d
25 changed files with 277 additions and 285 deletions
|
@ -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<String>? = null): Timeline
|
||||
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
|
||||
|
||||
|
||||
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
||||
|
|
|
@ -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<String> = emptyList()
|
||||
)
|
|
@ -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
|
||||
|
||||
|
|
|
@ -28,7 +28,10 @@ import javax.inject.Inject
|
|||
|
||||
internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
|
||||
|
||||
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity): List<ReadReceipt> {
|
||||
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> {
|
||||
if (readReceiptsSummaryEntity == null) {
|
||||
return emptyList()
|
||||
}
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
||||
readReceipts
|
||||
|
|
|
@ -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<ReadReceipt>? = 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()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ReadReceiptEntity> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
@LinkingObjects("readReceipts")
|
||||
val timelineEvent: RealmResults<TimelineEventEntity>? = null
|
||||
|
||||
companion object
|
||||
|
||||
}
|
|
@ -26,3 +26,11 @@ internal fun ReadReceiptsSummaryEntity.Companion.where(realm: Realm, eventId: St
|
|||
return realm.where<ReadReceiptsSummaryEntity>()
|
||||
.equalTo(ReadReceiptsSummaryEntityFields.EVENT_ID, eventId)
|
||||
}
|
||||
|
||||
internal fun ReadReceiptsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery<ReadReceiptsSummaryEntity> {
|
||||
val query = realm.where<ReadReceiptsSummaryEntity>()
|
||||
if (roomId != null) {
|
||||
query.equalTo(ReadReceiptsSummaryEntityFields.ROOM_ID, roomId)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<String>?
|
||||
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<TimelineEventEntity>
|
||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||
private var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>? = null
|
||||
private val correctedReadReceiptsEventByIndex = SparseArray<String>()
|
||||
private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
||||
|
||||
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<EventAnnotationsSummaryEntity>
|
||||
|
||||
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<EncryptedEventContent>()
|
||||
// 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<RealmResults<ReadReceiptsSummaryEntity>> { 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<TimelineEvent>()
|
||||
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<TimelineEventEntity>.filterAllowedTypes(): RealmQuery<TimelineEventEntity> {
|
||||
if (allowedTypes != null) {
|
||||
`in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray())
|
||||
private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> {
|
||||
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<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>?): 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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ internal class ReadReceiptHandler @Inject constructor() {
|
|||
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>()
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -817,8 +817,8 @@ class RoomDetailFragment :
|
|||
})
|
||||
}
|
||||
|
||||
override fun onReadReceiptsClicked(informationData: MessageInformationData) {
|
||||
DisplayReadReceiptsBottomSheet.newInstance(roomDetailArgs.roomId, informationData)
|
||||
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {
|
||||
DisplayReadReceiptsBottomSheet.newInstance(readReceipts)
|
||||
.show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS")
|
||||
}
|
||||
|
||||
|
|
|
@ -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<RoomDetailActions.EventDisplayed>()
|
||||
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<RoomTombstoneContent>()
|
||||
?: 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<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.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 {
|
||||
|
|
|
@ -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<ReadReceiptData>
|
||||
) : 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<ReadReceiptData>): 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 }
|
||||
|
|
|
@ -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<DisplayReadReceiptsViewState>() {
|
||||
: TypedEpoxyController<List<ReadReceiptData>>() {
|
||||
|
||||
|
||||
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<ReadReceiptData>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<DisplayReadReceiptsViewState>(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<DisplayReadReceiptsViewModel, DisplayReadReceiptsViewState> {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<List<ReadReceipt>> = Uninitialized
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||
|
||||
}
|
|
@ -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<ReadReceiptData>)
|
||||
}
|
||||
|
||||
interface UrlClickCallback {
|
||||
|
|
|
@ -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<EncryptedEventContent>()?.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<Event>()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<RoomNameContent>() ?: 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<RoomHistoryVisibilityContent>()?.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() ->
|
||||
|
|
|
@ -80,7 +80,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||
})
|
||||
|
||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData)
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||
})
|
||||
|
||||
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
||||
|
|
|
@ -50,7 +50,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||
|
||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData)
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||
})
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue