mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +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.
|
* 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 eventId the optional initial eventId.
|
||||||
* @param allowedTypes the optional filter types
|
* @param settings settings to configure the timeline.
|
||||||
* @return the instantiated 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?
|
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 senderId = event.senderId ?: ""
|
||||||
|
|
||||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
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
|
// 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) {
|
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 ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
||||||
readReceipts
|
readReceipts
|
||||||
|
|
|
@ -17,15 +17,18 @@
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
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.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.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper){
|
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
||||||
|
|
||||||
fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
|
|
||||||
|
|
||||||
|
fun map(timelineEventEntity: TimelineEventEntity, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
|
||||||
|
val readReceipts = correctedReadReceipts ?: timelineEventEntity.readReceipts?.let {
|
||||||
|
readReceiptsSummaryMapper.map(it)
|
||||||
|
}
|
||||||
return TimelineEvent(
|
return TimelineEvent(
|
||||||
root = timelineEventEntity.root?.asDomain()
|
root = timelineEventEntity.root?.asDomain()
|
||||||
?: Event("", timelineEventEntity.eventId),
|
?: Event("", timelineEventEntity.eventId),
|
||||||
|
@ -35,9 +38,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
|
||||||
senderName = timelineEventEntity.senderName,
|
senderName = timelineEventEntity.senderName,
|
||||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||||
senderAvatar = timelineEventEntity.senderAvatar,
|
senderAvatar = timelineEventEntity.senderAvatar,
|
||||||
readReceipts = timelineEventEntity.readReceipts?.let {
|
readReceipts = readReceipts ?: emptyList()
|
||||||
readReceiptsSummaryMapper.map(it)
|
|
||||||
} ?: emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,20 @@ package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.annotations.LinkingObjects
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class ReadReceiptsSummaryEntity(
|
internal open class ReadReceiptsSummaryEntity(
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
var eventId: String = "",
|
var eventId: String = "",
|
||||||
|
var roomId: String = "",
|
||||||
var readReceipts: RealmList<ReadReceiptEntity> = RealmList()
|
var readReceipts: RealmList<ReadReceiptEntity> = RealmList()
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
@LinkingObjects("readReceipts")
|
||||||
|
val timelineEvent: RealmResults<TimelineEventEntity>? = null
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
||||||
}
|
}
|
|
@ -26,3 +26,11 @@ internal fun ReadReceiptsSummaryEntity.Companion.where(realm: Realm, eventId: St
|
||||||
return realm.where<ReadReceiptsSummaryEntity>()
|
return realm.where<ReadReceiptsSummaryEntity>()
|
||||||
.equalTo(ReadReceiptsSummaryEntityFields.EVENT_ID, eventId)
|
.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) {
|
private val leaveRoomTask: LeaveRoomTask) {
|
||||||
|
|
||||||
fun create(roomId: String): Room {
|
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 sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
||||||
val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask)
|
val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask)
|
||||||
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
||||||
|
|
|
@ -16,25 +16,47 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.timeline
|
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.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
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.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.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
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.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
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.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
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.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.TaskConstraints
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.Debouncer
|
import im.vector.matrix.android.internal.util.Debouncer
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import im.vector.matrix.android.internal.util.createUIHandler
|
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 timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
@ -43,10 +65,11 @@ import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
|
||||||
private const val INITIAL_LOAD_SIZE = 30
|
|
||||||
private const val MIN_FETCHING_COUNT = 30
|
private const val MIN_FETCHING_COUNT = 30
|
||||||
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
|
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(
|
internal class DefaultTimeline(
|
||||||
private val roomId: String,
|
private val roomId: String,
|
||||||
private val initialEventId: String? = null,
|
private val initialEventId: String? = null,
|
||||||
|
@ -56,7 +79,8 @@ internal class DefaultTimeline(
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val allowedTypes: List<String>?
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||||
|
private val settings: TimelineSettings
|
||||||
) : Timeline {
|
) : Timeline {
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
@ -79,6 +103,11 @@ internal class DefaultTimeline(
|
||||||
private val debouncer = Debouncer(mainHandler)
|
private val debouncer = Debouncer(mainHandler)
|
||||||
|
|
||||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
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 roomEntity: RoomEntity? = null
|
||||||
|
|
||||||
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
||||||
|
@ -92,7 +121,6 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
private val timelineID = UUID.randomUUID().toString()
|
private val timelineID = UUID.randomUUID().toString()
|
||||||
|
|
||||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
|
||||||
|
|
||||||
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
||||||
|
|
||||||
|
@ -132,9 +160,9 @@ internal class DefaultTimeline(
|
||||||
val eventEntity = results[index]
|
val eventEntity = results[index]
|
||||||
eventEntity?.eventId?.let { eventId ->
|
eventEntity?.eventId?.let { eventId ->
|
||||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
//Update the relation of existing event
|
//Update an existing event
|
||||||
builtEvents[builtIndex]?.let { te ->
|
builtEvents[builtIndex]?.let { te ->
|
||||||
builtEvents[builtIndex] = timelineEventMapper.map(eventEntity)
|
builtEvents[builtIndex] = timelineEventMapper.map(eventEntity, correctedReadReceiptsByEvent[te.root.eventId])
|
||||||
hasChanged = true
|
hasChanged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,32 +192,54 @@ internal class DefaultTimeline(
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// private val newSessionListener = object : NewSessionListener {
|
private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
|
||||||
// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
var hasChange = false
|
||||||
// if (roomId == this@DefaultTimeline.roomId) {
|
changeSet.deletions.forEach {
|
||||||
// Timber.v("New session id detected for this room")
|
val eventId = correctedReadReceiptsEventByIndex[it]
|
||||||
// BACKGROUND_HANDLER.post {
|
val timelineEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, eventId).findFirst()
|
||||||
// val realm = backgroundRealm.get()
|
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
// var hasChange = false
|
builtEvents[builtIndex]?.let { te ->
|
||||||
// builtEvents.forEachIndexed { index, timelineEvent ->
|
builtEvents[builtIndex] = te.copy(readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts))
|
||||||
// if (timelineEvent.isEncrypted()) {
|
hasChange = true
|
||||||
// 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
|
correctedReadReceiptsEventByIndex.clear()
|
||||||
// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
|
correctedReadReceiptsByEvent.clear()
|
||||||
// //builtEvents[index] = timelineEventFactory.create(it, realm)
|
val loadedReadReceipts = collection.where().greaterThan("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.DISPLAY_INDEX}", prevDisplayIndex).findAll()
|
||||||
// hasChange = true
|
loadedReadReceipts.forEachIndexed { index, summary ->
|
||||||
// }
|
val timelineEvent = summary?.timelineEvent?.firstOrNull()
|
||||||
// }
|
val displayIndex = timelineEvent?.root?.displayIndex
|
||||||
// }
|
if (displayIndex != null) {
|
||||||
// }
|
val firstDisplayedEvent = liveEvents.where()
|
||||||
// if (hasChange) postSnapshot()
|
.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 ******************************************************************************
|
// Public methods ******************************************************************************
|
||||||
|
|
||||||
|
@ -236,15 +286,23 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
|
|
||||||
liveEvents = buildEventQuery(realm)
|
liveEvents = buildEventQuery(realm)
|
||||||
|
.filterEventsWithSettings()
|
||||||
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
.also { it.addChangeListener(eventsChangeListener) }
|
.also { it.addChangeListener(eventsChangeListener) }
|
||||||
|
|
||||||
isReady.set(true)
|
|
||||||
|
|
||||||
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
.also { it.addChangeListener(relationsListener) }
|
.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()
|
cancelableBag.cancel()
|
||||||
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
||||||
eventRelations.removeAllChangeListeners()
|
eventRelations.removeAllChangeListeners()
|
||||||
|
hiddenReadReceipts?.removeAllChangeListeners()
|
||||||
liveEvents.removeAllChangeListeners()
|
liveEvents.removeAllChangeListeners()
|
||||||
backgroundRealm.getAndSet(null).also {
|
backgroundRealm.getAndSet(null).also {
|
||||||
it.close()
|
it.close()
|
||||||
|
@ -274,7 +333,7 @@ internal class DefaultTimeline(
|
||||||
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
||||||
return Realm.getInstance(realmConfiguration).use { localRealm ->
|
return Realm.getInstance(realmConfiguration).use { localRealm ->
|
||||||
val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction)
|
val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction)
|
||||||
?: return false
|
?: return false
|
||||||
if (direction == Timeline.Direction.FORWARDS) {
|
if (direction == Timeline.Direction.FORWARDS) {
|
||||||
if (findCurrentChunk(localRealm)?.isLastForward == true) {
|
if (findCurrentChunk(localRealm)?.isLastForward == true) {
|
||||||
return false
|
return false
|
||||||
|
@ -331,7 +390,9 @@ internal class DefaultTimeline(
|
||||||
val sendingEvents = ArrayList<TimelineEvent>()
|
val sendingEvents = ArrayList<TimelineEvent>()
|
||||||
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
||||||
roomEntity?.sendingTimelineEvents
|
roomEntity?.sendingTimelineEvents
|
||||||
?.filter { allowedTypes?.contains(it.root?.type) ?: false }
|
?.where()
|
||||||
|
?.filterEventsWithSettings()
|
||||||
|
?.findAll()
|
||||||
?.forEach {
|
?.forEach {
|
||||||
sendingEvents.add(timelineEventMapper.map(it))
|
sendingEvents.add(timelineEventMapper.map(it))
|
||||||
}
|
}
|
||||||
|
@ -380,7 +441,7 @@ internal class DefaultTimeline(
|
||||||
if (initialEventId != null && shouldFetchInitialEvent) {
|
if (initialEventId != null && shouldFetchInitialEvent) {
|
||||||
fetchEvent(initialEventId)
|
fetchEvent(initialEventId)
|
||||||
} else {
|
} else {
|
||||||
val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size)
|
val count = Math.min(settings.initialSize, liveEvents.size)
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
||||||
} else {
|
} else {
|
||||||
|
@ -397,9 +458,9 @@ internal class DefaultTimeline(
|
||||||
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
||||||
val token = getTokenLive(direction) ?: return
|
val token = getTokenLive(direction) ?: return
|
||||||
val params = PaginationTask.Params(roomId = roomId,
|
val params = PaginationTask.Params(roomId = roomId,
|
||||||
from = token,
|
from = token,
|
||||||
direction = direction.toPaginationDirection(),
|
direction = direction.toPaginationDirection(),
|
||||||
limit = limit)
|
limit = limit)
|
||||||
|
|
||||||
Timber.v("Should fetch $limit items $direction")
|
Timber.v("Should fetch $limit items $direction")
|
||||||
cancelableBag += paginationTask
|
cancelableBag += paginationTask
|
||||||
|
@ -465,10 +526,11 @@ internal class DefaultTimeline(
|
||||||
nextDisplayIndex = offsetIndex + 1
|
nextDisplayIndex = offsetIndex + 1
|
||||||
}
|
}
|
||||||
offsetResults.forEach { eventEntity ->
|
offsetResults.forEach { eventEntity ->
|
||||||
|
|
||||||
val timelineEvent = timelineEventMapper.map(eventEntity)
|
val timelineEvent = timelineEventMapper.map(eventEntity)
|
||||||
|
|
||||||
if (timelineEvent.isEncrypted()
|
if (timelineEvent.isEncrypted()
|
||||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||||
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
|
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +562,6 @@ internal class DefaultTimeline(
|
||||||
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
||||||
}
|
}
|
||||||
return offsetQuery
|
return offsetQuery
|
||||||
.filterAllowedTypes()
|
|
||||||
.limit(count)
|
.limit(count)
|
||||||
.findAll()
|
.findAll()
|
||||||
}
|
}
|
||||||
|
@ -559,14 +620,35 @@ internal class DefaultTimeline(
|
||||||
} else {
|
} else {
|
||||||
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
}
|
}
|
||||||
.filterAllowedTypes()
|
.filterEventsWithSettings()
|
||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmQuery<TimelineEventEntity>.filterAllowedTypes(): RealmQuery<TimelineEventEntity> {
|
private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> {
|
||||||
if (allowedTypes != null) {
|
if (settings.filterTypes) {
|
||||||
`in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray())
|
`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
|
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.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
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.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.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.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
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 contextOfEventTask: GetContextOfEventTask,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||||
) : TimelineService {
|
) : TimelineService {
|
||||||
|
|
||||||
override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
|
override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
|
||||||
return DefaultTimeline(roomId,
|
return DefaultTimeline(roomId,
|
||||||
eventId,
|
eventId,
|
||||||
monarchy.realmConfiguration,
|
monarchy.realmConfiguration,
|
||||||
|
@ -50,7 +53,8 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
||||||
paginationTask,
|
paginationTask,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
timelineEventMapper,
|
timelineEventMapper,
|
||||||
allowedTypes
|
readReceiptsSummaryMapper,
|
||||||
|
settings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ internal class ReadReceiptHandler @Inject constructor() {
|
||||||
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>()
|
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>()
|
||||||
for ((eventId, receiptDict) in content) {
|
for ((eventId, receiptDict) in content) {
|
||||||
val userIdsDict = receiptDict[READ_KEY] ?: continue
|
val userIdsDict = receiptDict[READ_KEY] ?: continue
|
||||||
val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId)
|
val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId)
|
||||||
|
|
||||||
for ((userId, paramsDict) in userIdsDict) {
|
for ((userId, paramsDict) in userIdsDict) {
|
||||||
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
|
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.RoomDetailViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
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.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
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsViewModel_AssistedFactory
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel
|
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel
|
||||||
|
@ -197,8 +195,4 @@ interface ViewModelModule {
|
||||||
@Binds
|
@Binds
|
||||||
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
|
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) {
|
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {
|
||||||
DisplayReadReceiptsBottomSheet.newInstance(roomDetailArgs.roomId, informationData)
|
DisplayReadReceiptsBottomSheet.newInstance(readReceipts)
|
||||||
.show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS")
|
.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.message.getFileUrl
|
||||||
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
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.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.attachments.toElementToDecrypt
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
|
@ -73,12 +74,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
private val roomId = initialState.roomId
|
private val roomId = initialState.roomId
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
||||||
private val allowedTypes = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
||||||
TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES
|
TimelineSettings(30, false, true, TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES)
|
||||||
} else {
|
} 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
|
// Slot to keep a pending action during permission request
|
||||||
var pendingAction: RoomDetailActions? = null
|
var pendingAction: RoomDetailActions? = null
|
||||||
|
@ -137,7 +139,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
|
|
||||||
private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) {
|
private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) {
|
||||||
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
|
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val roomId = tombstoneContent.replacementRoom ?: ""
|
val roomId = tombstoneContent.replacementRoom ?: ""
|
||||||
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
|
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?
|
//is original event a reply?
|
||||||
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
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) {
|
if (inReplyTo != null) {
|
||||||
//TODO check if same content?
|
//TODO check if same content?
|
||||||
room.getTimeLineEvent(inReplyTo)?.let {
|
room.getTimeLineEvent(inReplyTo)?.let {
|
||||||
|
@ -292,12 +294,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
} else {
|
} else {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val existingBody = messageContent?.body ?: ""
|
val existingBody = messageContent?.body ?: ""
|
||||||
if (existingBody != action.text) {
|
if (existingBody != action.text) {
|
||||||
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
||||||
?: "", messageContent?.type
|
?: "", messageContent?.type
|
||||||
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
||||||
} else {
|
} else {
|
||||||
Timber.w("Same message content, do not send edition")
|
Timber.w("Same message content, do not send edition")
|
||||||
}
|
}
|
||||||
|
@ -312,7 +314,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
is SendMode.QUOTE -> {
|
is SendMode.QUOTE -> {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
val textMsg = messageContent?.body
|
val textMsg = messageContent?.body
|
||||||
|
|
||||||
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
||||||
|
@ -550,7 +552,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
} else {
|
} else {
|
||||||
// change timeline
|
// change timeline
|
||||||
timeline.dispose()
|
timeline.dispose()
|
||||||
timeline = room.createTimeline(targetEventId, allowedTypes)
|
timeline = room.createTimeline(targetEventId, timelineSettings)
|
||||||
timeline.start()
|
timeline.start()
|
||||||
|
|
||||||
withState {
|
withState {
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.readreceipts
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -27,31 +27,31 @@ import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
import com.airbnb.mvrx.MvRx
|
import com.airbnb.mvrx.MvRx
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.withState
|
|
||||||
import im.vector.riotx.EmojiCompatFontProvider
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
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.VectorBaseBottomSheetDialogFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.*
|
import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.*
|
||||||
import javax.inject.Inject
|
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
|
* Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp
|
||||||
*/
|
*/
|
||||||
class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
private val viewModel: DisplayReadReceiptsViewModel by fragmentViewModel()
|
|
||||||
|
|
||||||
@Inject lateinit var displayReadReceiptsViewModelFactory: DisplayReadReceiptsViewModel.Factory
|
|
||||||
@Inject lateinit var epoxyController: DisplayReadReceiptsController
|
@Inject lateinit var epoxyController: DisplayReadReceiptsController
|
||||||
|
|
||||||
@BindView(R.id.bottom_sheet_display_reactions_list)
|
@BindView(R.id.bottom_sheet_display_reactions_list)
|
||||||
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||||
|
|
||||||
|
private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(screenComponent: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
screenComponent.inject(this)
|
||||||
|
@ -70,20 +70,18 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
LinearLayout.VERTICAL)
|
LinearLayout.VERTICAL)
|
||||||
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
|
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
|
||||||
bottomSheetTitle.text = getString(R.string.read_receipts_list)
|
bottomSheetTitle.text = getString(R.string.read_receipts_list)
|
||||||
|
epoxyController.setData(displayReadReceiptArgs.readReceipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun invalidate() {
|
||||||
override fun invalidate() = withState(viewModel) {
|
// we are not using state for this one as it's static
|
||||||
epoxyController.setData(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(roomId: String, informationData: MessageInformationData): DisplayReadReceiptsBottomSheet {
|
fun newInstance(readReceipts: List<ReadReceiptData>): DisplayReadReceiptsBottomSheet {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
val parcelableArgs = TimelineEventFragmentArgs(
|
val parcelableArgs = DisplayReadReceiptArgs(
|
||||||
informationData.eventId,
|
readReceipts
|
||||||
roomId,
|
|
||||||
informationData
|
|
||||||
)
|
)
|
||||||
args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
|
args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
|
||||||
return DisplayReadReceiptsBottomSheet().apply { arguments = args }
|
return DisplayReadReceiptsBottomSheet().apply { arguments = args }
|
||||||
|
|
|
@ -17,55 +17,32 @@
|
||||||
package im.vector.riotx.features.home.room.detail.readreceipts
|
package im.vector.riotx.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
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.matrix.android.api.session.Session
|
||||||
import im.vector.riotx.R
|
|
||||||
import im.vector.riotx.core.date.VectorDateFormatter
|
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.AvatarRenderer
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Epoxy controller for read receipt event list
|
* Epoxy controller for read receipt event list
|
||||||
*/
|
*/
|
||||||
class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||||
private val stringProvider: StringProvider,
|
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val avatarRender: AvatarRenderer)
|
private val avatarRender: AvatarRenderer)
|
||||||
: TypedEpoxyController<DisplayReadReceiptsViewState>() {
|
: TypedEpoxyController<List<ReadReceiptData>>() {
|
||||||
|
|
||||||
|
|
||||||
override fun buildModels(state: DisplayReadReceiptsViewState) {
|
override fun buildModels(readReceipts: List<ReadReceiptData>) {
|
||||||
when (state.readReceipts) {
|
readReceipts.forEach {
|
||||||
is Incomplete -> {
|
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
||||||
genericLoaderItem {
|
DisplayReadReceiptItem_()
|
||||||
id("loading")
|
.id(it.userId)
|
||||||
}
|
.userId(it.userId)
|
||||||
}
|
.avatarUrl(it.avatarUrl)
|
||||||
is Fail -> {
|
.name(it.displayName)
|
||||||
genericFooterItem {
|
.avatarRenderer(avatarRender)
|
||||||
id("failure")
|
.timestamp(timestamp)
|
||||||
text(stringProvider.getString(R.string.unknown_error))
|
.addIf(session.myUserId != it.userId, this)
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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.DaySeparatorItem_
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem
|
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.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.ImageContentRenderer
|
||||||
import im.vector.riotx.features.media.VideoContentRenderer
|
import im.vector.riotx.features.media.VideoContentRenderer
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
|
@ -79,7 +80,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReadReceiptsCallback {
|
interface ReadReceiptsCallback {
|
||||||
fun onReadReceiptsClicked(informationData: MessageInformationData)
|
fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UrlClickCallback {
|
interface UrlClickCallback {
|
||||||
|
|
|
@ -84,7 +84,7 @@ class MessageItemFactory @Inject constructor(
|
||||||
private val imageContentRenderer: ImageContentRenderer,
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||||
private val userPreferencesProvider: UserPreferencesProvider) {
|
private val noticeItemFactory: NoticeItemFactory) {
|
||||||
|
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
|
@ -109,27 +109,8 @@ class MessageItemFactory @Inject constructor(
|
||||||
if (messageContent.relatesTo?.type == RelationType.REPLACE
|
if (messageContent.relatesTo?.type == RelationType.REPLACE
|
||||||
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|
||||||
) {
|
) {
|
||||||
// ignore replace event, the targeted id is already edited
|
// This is an edit event, we should it when debugging as a notice event
|
||||||
if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
return noticeItemFactory.create(event, highlight, callback)
|
||||||
//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_()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// val all = event.root.toContent()
|
// val all = event.root.toContent()
|
||||||
// val ev = all.toModel<Event>()
|
// 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.helper.senderAvatar
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
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.item.NoticeItem_
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -33,8 +34,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
private val encryptedItemFactory: EncryptedItemFactory,
|
private val encryptedItemFactory: EncryptedItemFactory,
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val defaultItemFactory: DefaultItemFactory,
|
private val defaultItemFactory: DefaultItemFactory,
|
||||||
private val roomCreateItemFactory: RoomCreateItemFactory,
|
private val roomCreateItemFactory: RoomCreateItemFactory) {
|
||||||
private val avatarRenderer: AvatarRenderer) {
|
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
|
@ -53,7 +53,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
EventType.STATE_HISTORY_VISIBILITY,
|
EventType.STATE_HISTORY_VISIBILITY,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
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
|
// State room create
|
||||||
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
|
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
|
||||||
// Crypto
|
// Crypto
|
||||||
|
@ -70,24 +72,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
// Unhandled event types (yet)
|
// Unhandled event types (yet)
|
||||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
||||||
EventType.STICKER -> defaultItemFactory.create(event, highlight)
|
EventType.STICKER -> defaultItemFactory.create(event, highlight)
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
//These are just for debug to display hidden event, they should be filtered out in normal mode
|
Timber.v("Type ${event.root.getClearType()} not handled")
|
||||||
val informationData = MessageInformationData(
|
null
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.*
|
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.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.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
@ -42,6 +41,9 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||||
|
EventType.MESSAGE,
|
||||||
|
EventType.REACTION,
|
||||||
|
EventType.REDACTION -> formatDebug(timelineEvent.root)
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("Type $type not handled by this formatter")
|
Timber.v("Type $type not handled by this formatter")
|
||||||
null
|
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? {
|
private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
|
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
|
||||||
return if (!TextUtils.isEmpty(content.name)) {
|
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? {
|
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
||||||
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val formattedVisibility = when (historyVisibility) {
|
val formattedVisibility = when (historyVisibility) {
|
||||||
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
|
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)
|
stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
|
||||||
else ->
|
else ->
|
||||||
stringProvider.getString(R.string.notice_display_name_changed_from,
|
stringProvider.getString(R.string.notice_display_name_changed_from,
|
||||||
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
|
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
|
||||||
}
|
}
|
||||||
displayText.append(displayNameText)
|
displayText.append(displayNameText)
|
||||||
}
|
}
|
||||||
|
@ -167,7 +173,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
|
||||||
when {
|
when {
|
||||||
eventContent.thirdPartyInvite != null ->
|
eventContent.thirdPartyInvite != null ->
|
||||||
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
|
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
|
||||||
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
|
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
|
||||||
TextUtils.equals(event.stateKey, selfUserId) ->
|
TextUtils.equals(event.stateKey, selfUserId) ->
|
||||||
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
|
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
|
||||||
event.stateKey.isNullOrEmpty() ->
|
event.stateKey.isNullOrEmpty() ->
|
||||||
|
|
|
@ -80,7 +80,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||||
})
|
})
|
||||||
|
|
||||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
readReceiptsCallback?.onReadReceiptsClicked(informationData)
|
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||||
})
|
})
|
||||||
|
|
||||||
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
||||||
|
|
|
@ -50,7 +50,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||||
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||||
|
|
||||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||||
readReceiptsCallback?.onReadReceiptsClicked(informationData)
|
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||||
})
|
})
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
|
|
@ -52,15 +52,14 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
addDaySeparator
|
addDaySeparator
|
||||||
|| event.senderAvatar != nextEvent?.senderAvatar
|
|| event.senderAvatar != nextEvent?.senderAvatar
|
||||||
|| event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName()
|
|| 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
|
|| isNextMessageReceivedMoreThanOneHourAgo
|
||||||
|
|
||||||
val time = dateFormatter.formatMessageHour(date)
|
val time = dateFormatter.formatMessageHour(date)
|
||||||
val avatarUrl = event.senderAvatar
|
val avatarUrl = event.senderAvatar
|
||||||
val memberName = event.getDisambiguatedDisplayName()
|
val memberName = event.getDisambiguatedDisplayName()
|
||||||
val formattedMemberName = span(memberName) {
|
val formattedMemberName = span(memberName) {
|
||||||
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId
|
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: ""))
|
||||||
?: ""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageInformationData(
|
return MessageInformationData(
|
||||||
|
|
Loading…
Reference in a new issue