Read marker: start working on it (no UI)
This commit is contained in:
parent
8ca829d538
commit
d8f449388c
|
@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
|
||||||
vector.debugPrivateData=false
|
vector.debugPrivateData=false
|
||||||
vector.httpLogLevel=NONE
|
vector.httpLogLevel=HEADERS
|
||||||
|
|
||||||
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
||||||
#vector.debugPrivateData=true
|
#vector.debugPrivateData=true
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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.read
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class FullyReadContent(
|
||||||
|
@Json(name = "event_id") val eventId: String
|
||||||
|
)
|
|
@ -42,5 +42,10 @@ interface ReadService {
|
||||||
|
|
||||||
fun isEventRead(eventId: String): Boolean
|
fun isEventRead(eventId: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a nullable read marker for the room.
|
||||||
|
*/
|
||||||
|
fun getReadMarkerLive(): LiveData<String?>
|
||||||
|
|
||||||
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||||
}
|
}
|
|
@ -39,7 +39,8 @@ data class TimelineEvent(
|
||||||
val isUniqueDisplayName: Boolean,
|
val isUniqueDisplayName: Boolean,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val annotations: EventAnnotationsSummary? = null,
|
val annotations: EventAnnotationsSummary? = null,
|
||||||
val readReceipts: List<ReadReceipt> = emptyList()
|
val readReceipts: List<ReadReceipt> = emptyList(),
|
||||||
|
val hasReadMarker: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val metadata = HashMap<String, Any>()
|
val metadata = HashMap<String, Any>()
|
||||||
|
|
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
@ -157,7 +158,6 @@ internal fun ChunkEntity.add(roomId: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val eventEntity = TimelineEventEntity(localId).also {
|
val eventEntity = TimelineEventEntity(localId).also {
|
||||||
it.root = event.toEntity(roomId).apply {
|
it.root = event.toEntity(roomId).apply {
|
||||||
this.stateIndex = currentStateIndex
|
this.stateIndex = currentStateIndex
|
||||||
|
@ -169,6 +169,7 @@ internal fun ChunkEntity.add(roomId: String,
|
||||||
it.roomId = roomId
|
it.roomId = roomId
|
||||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||||
it.readReceipts = readReceiptsSummaryEntity
|
it.readReceipts = readReceiptsSummaryEntity
|
||||||
|
it.readMarker = ReadMarkerEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()
|
||||||
}
|
}
|
||||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||||
timelineEvents.add(position, eventEntity)
|
timelineEvents.add(position, eventEntity)
|
||||||
|
|
|
@ -26,8 +26,8 @@ import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryMapper @Inject constructor(
|
internal class RoomSummaryMapper @Inject constructor(
|
||||||
val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
val timelineEventMapper: TimelineEventMapper
|
private val timelineEventMapper: TimelineEventMapper
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||||
|
|
|
@ -45,7 +45,8 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
|
||||||
senderAvatar = timelineEventEntity.senderAvatar,
|
senderAvatar = timelineEventEntity.senderAvatar,
|
||||||
readReceipts = readReceipts?.sortedByDescending {
|
readReceipts = readReceipts?.sortedByDescending {
|
||||||
it.originServerTs
|
it.originServerTs
|
||||||
} ?: emptyList()
|
} ?: emptyList(),
|
||||||
|
hasReadMarker = timelineEventEntity.readMarker?.eventId?.isEmpty() == false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.annotations.LinkingObjects
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
|
internal open class ReadMarkerEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
var roomId: String = "",
|
||||||
|
var eventId: String = ""
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
@LinkingObjects("readMarker")
|
||||||
|
val timelineEvent: RealmResults<TimelineEventEntity>? = null
|
||||||
|
|
||||||
|
companion object
|
||||||
|
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ import io.realm.annotations.RealmModule
|
||||||
PushConditionEntity::class,
|
PushConditionEntity::class,
|
||||||
PusherEntity::class,
|
PusherEntity::class,
|
||||||
PusherDataEntity::class,
|
PusherDataEntity::class,
|
||||||
ReadReceiptsSummaryEntity::class
|
ReadReceiptsSummaryEntity::class,
|
||||||
|
ReadMarkerEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -31,7 +31,8 @@ internal open class TimelineEventEntity(var localId: Long = 0,
|
||||||
var isUniqueDisplayName: Boolean = false,
|
var isUniqueDisplayName: Boolean = false,
|
||||||
var senderAvatar: String? = null,
|
var senderAvatar: String? = null,
|
||||||
var senderMembershipEvent: EventEntity? = null,
|
var senderMembershipEvent: EventEntity? = null,
|
||||||
var readReceipts: ReadReceiptsSummaryEntity? = null
|
var readReceipts: ReadReceiptsSummaryEntity? = null,
|
||||||
|
var readMarker: ReadMarkerEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
@LinkingObjects("timelineEvents")
|
@LinkingObjects("timelineEvents")
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.database.query
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntityFields
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String, eventId: String? = null): RealmQuery<ReadMarkerEntity> {
|
||||||
|
val query = realm.where<ReadMarkerEntity>()
|
||||||
|
.equalTo(ReadMarkerEntityFields.ROOM_ID, roomId)
|
||||||
|
if (eventId != null) {
|
||||||
|
query.equalTo(ReadMarkerEntityFields.EVENT_ID, eventId)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ReadMarkerEntity.Companion.getOrCreate(realm: Realm, roomId: String): ReadMarkerEntity {
|
||||||
|
return where(realm, roomId).findFirst()
|
||||||
|
?: realm.createObject(ReadMarkerEntity::class.java, roomId)
|
||||||
|
}
|
|
@ -28,8 +28,10 @@ import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
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.ReadReceiptsSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
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.where
|
||||||
|
@ -93,6 +95,15 @@ internal class DefaultReadService @AssistedInject constructor(@Assisted private
|
||||||
return isEventRead
|
return isEventRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getReadMarkerLive(): LiveData<String?> {
|
||||||
|
val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
||||||
|
ReadMarkerEntity.where(realm, roomId)
|
||||||
|
}
|
||||||
|
return Transformations.map(liveRealmData) { results ->
|
||||||
|
results.firstOrNull()?.eventId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
|
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
|
||||||
val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
||||||
ReadReceiptsSummaryEntity.where(realm, eventId)
|
ReadReceiptsSummaryEntity.where(realm, eventId)
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.read
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
@ -57,6 +58,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
||||||
val fullyReadEventId: String?
|
val fullyReadEventId: String?
|
||||||
val readReceiptEventId: String?
|
val readReceiptEventId: String?
|
||||||
|
|
||||||
|
Timber.v("Execute set read marker with params: $params")
|
||||||
if (params.markAllAsRead) {
|
if (params.markAllAsRead) {
|
||||||
val latestSyncedEventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
val latestSyncedEventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
TimelineEventEntity.latestEvent(realm, roomId = params.roomId, includesSending = false)?.eventId
|
TimelineEventEntity.latestEvent(realm, roomId = params.roomId, includesSending = false)?.eventId
|
||||||
|
@ -68,7 +70,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
||||||
readReceiptEventId = params.readReceiptEventId
|
readReceiptEventId = params.readReceiptEventId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullyReadEventId != null) {
|
if (fullyReadEventId != null && isReadMarkerMoreRecent(params.roomId, fullyReadEventId)) {
|
||||||
if (LocalEchoEventFactory.isLocalEchoId(fullyReadEventId)) {
|
if (LocalEchoEventFactory.isLocalEchoId(fullyReadEventId)) {
|
||||||
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
|
Timber.w("Can't set read marker for local event ${params.fullyReadEventId}")
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,7 +78,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readReceiptEventId != null
|
if (readReceiptEventId != null
|
||||||
&& !isEventRead(params.roomId, readReceiptEventId)) {
|
&& !isEventRead(params.roomId, readReceiptEventId)) {
|
||||||
|
|
||||||
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
|
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
|
||||||
Timber.w("Can't set read receipt for local event ${params.fullyReadEventId}")
|
Timber.w("Can't set read receipt for local event ${params.fullyReadEventId}")
|
||||||
|
@ -93,12 +95,23 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isReadMarkerMoreRecent(roomId: String, fullyReadEventId: String): Boolean {
|
||||||
|
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
|
val readMarkerEntity = ReadMarkerEntity.where(realm, roomId = roomId).findFirst()
|
||||||
|
val readMarkerEvent = readMarkerEntity?.timelineEvent?.firstOrNull()
|
||||||
|
val eventToCheck = TimelineEventEntity.where(realm, eventId = fullyReadEventId).findFirst()
|
||||||
|
val readReceiptIndex = readMarkerEvent?.root?.displayIndex ?: Int.MAX_VALUE
|
||||||
|
val eventToCheckIndex = eventToCheck?.root?.displayIndex ?: Int.MIN_VALUE
|
||||||
|
eventToCheckIndex > readReceiptIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
||||||
monarchy.writeAsync { realm ->
|
monarchy.writeAsync { realm ->
|
||||||
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == eventId
|
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == eventId
|
||||||
if (isLatestReceived) {
|
if (isLatestReceived) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: return@writeAsync
|
?: return@writeAsync
|
||||||
roomSummary.notificationCount = 0
|
roomSummary.notificationCount = 0
|
||||||
roomSummary.highlightCount = 0
|
roomSummary.highlightCount = 0
|
||||||
}
|
}
|
||||||
|
@ -106,19 +119,17 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEventRead(roomId: String, eventId: String): Boolean {
|
private fun isEventRead(roomId: String, eventId: String): Boolean {
|
||||||
var isEventRead = false
|
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||||
monarchy.doWithRealm {
|
val readReceipt = ReadReceiptEntity.where(realm, roomId, credentials.userId).findFirst()
|
||||||
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
?: return false
|
||||||
?: return@doWithRealm
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
?: return false
|
||||||
?: return@doWithRealm
|
|
||||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||||
?: Int.MAX_VALUE
|
?: Int.MAX_VALUE
|
||||||
isEventRead = eventToCheckIndex <= readReceiptIndex
|
eventToCheckIndex <= readReceiptIndex
|
||||||
}
|
}
|
||||||
return isEventRead
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -32,6 +32,7 @@ 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.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.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
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.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
|
@ -47,10 +48,12 @@ 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.ObjectChangeSet
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.OrderedCollectionChangeSet
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
import io.realm.OrderedRealmCollectionChangeListener
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.RealmObjectChangeListener
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
|
@ -101,6 +104,7 @@ internal class DefaultTimeline(
|
||||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||||
|
|
||||||
private var roomEntity: RoomEntity? = null
|
private var roomEntity: RoomEntity? = null
|
||||||
|
private var readMarkerEntity: ReadMarkerEntity? = null
|
||||||
|
|
||||||
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
||||||
private var nextDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
private var nextDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
||||||
|
@ -149,13 +153,9 @@ internal class DefaultTimeline(
|
||||||
changeSet.changes.forEach { index ->
|
changeSet.changes.forEach { index ->
|
||||||
val eventEntity = results[index]
|
val eventEntity = results[index]
|
||||||
eventEntity?.eventId?.let { eventId ->
|
eventEntity?.eventId?.let { eventId ->
|
||||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
hasChanged = rebuildEvent(eventId) {
|
||||||
//Update an existing event
|
buildTimelineEvent(eventEntity)
|
||||||
builtEvents[builtIndex]?.let { te ->
|
} || hasChanged
|
||||||
builtEvents[builtIndex] = buildTimelineEvent(eventEntity)
|
|
||||||
hasChanged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasChanged) postSnapshot()
|
if (hasChanged) postSnapshot()
|
||||||
|
@ -163,27 +163,44 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val relationsListener = OrderedRealmCollectionChangeListener<RealmResults<EventAnnotationsSummaryEntity>> { collection, changeSet ->
|
private val relationsListener = OrderedRealmCollectionChangeListener<RealmResults<EventAnnotationsSummaryEntity>> { collection, changeSet ->
|
||||||
|
|
||||||
var hasChange = false
|
var hasChange = false
|
||||||
|
|
||||||
(changeSet.insertions + changeSet.changes).forEach {
|
(changeSet.insertions + changeSet.changes).forEach {
|
||||||
val eventRelations = collection[it]
|
val eventRelations = collection[it]
|
||||||
if (eventRelations != null) {
|
if (eventRelations != null) {
|
||||||
builtEventsIdMap[eventRelations.eventId]?.let { builtIndex ->
|
hasChange = rebuildEvent(eventRelations.eventId) { te ->
|
||||||
//Update the relation of existing event
|
te.copy(annotations = eventRelations.asDomain())
|
||||||
builtEvents[builtIndex]?.let { te ->
|
} || hasChange
|
||||||
builtEvents[builtIndex] = te.copy(annotations = eventRelations.asDomain())
|
}
|
||||||
hasChange = true
|
}
|
||||||
|
if (hasChange) postSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val readMarkerListener = RealmObjectChangeListener { readMarkerEntity: ReadMarkerEntity, _: ObjectChangeSet? ->
|
||||||
|
val isEventHidden = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, readMarkerEntity.eventId).findFirst() == null
|
||||||
|
var hasChange = false
|
||||||
|
if (isEventHidden) {
|
||||||
|
val hiddenEvent = readMarkerEntity.timelineEvent?.firstOrNull() ?: return@RealmObjectChangeListener
|
||||||
|
val displayIndex = hiddenEvent.root?.displayIndex
|
||||||
|
if (displayIndex != null) {
|
||||||
|
// Then we are looking for the first displayable event after the hidden one
|
||||||
|
val firstDisplayedEvent = liveEvents.where()
|
||||||
|
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
|
||||||
|
.findFirst()
|
||||||
|
|
||||||
|
// If we find one, we should rebuild this one with marker
|
||||||
|
if (firstDisplayedEvent != null) {
|
||||||
|
hasChange = rebuildEvent(firstDisplayedEvent.eventId) {
|
||||||
|
it.copy(hasReadMarker = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasChange)
|
if (hasChange) postSnapshot()
|
||||||
postSnapshot()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Public methods ******************************************************************************
|
// Public methods ******************************************************************************
|
||||||
|
|
||||||
override fun paginate(direction: Timeline.Direction, count: Int) {
|
override fun paginate(direction: Timeline.Direction, count: Int) {
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
|
@ -237,6 +254,10 @@ internal class DefaultTimeline(
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
.also { it.addChangeListener(relationsListener) }
|
.also { it.addChangeListener(relationsListener) }
|
||||||
|
|
||||||
|
readMarkerEntity = ReadMarkerEntity.where(realm, roomId = roomId)
|
||||||
|
.findFirstAsync()
|
||||||
|
.also { it.addChangeListener(readMarkerListener) }
|
||||||
|
|
||||||
if (settings.buildReadReceipts) {
|
if (settings.buildReadReceipts) {
|
||||||
hiddenReadReceipts.start(realm, liveEvents, this)
|
hiddenReadReceipts.start(realm, liveEvents, this)
|
||||||
}
|
}
|
||||||
|
@ -255,6 +276,7 @@ internal class DefaultTimeline(
|
||||||
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
||||||
eventRelations.removeAllChangeListeners()
|
eventRelations.removeAllChangeListeners()
|
||||||
liveEvents.removeAllChangeListeners()
|
liveEvents.removeAllChangeListeners()
|
||||||
|
readMarkerEntity?.removeAllChangeListeners()
|
||||||
if (settings.buildReadReceipts) {
|
if (settings.buildReadReceipts) {
|
||||||
hiddenReadReceipts.dispose()
|
hiddenReadReceipts.dispose()
|
||||||
}
|
}
|
||||||
|
@ -272,20 +294,26 @@ internal class DefaultTimeline(
|
||||||
// TimelineHiddenReadReceipts.Delegate
|
// TimelineHiddenReadReceipts.Delegate
|
||||||
|
|
||||||
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
||||||
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
return rebuildEvent(eventId) { te ->
|
||||||
//Update the relation of existing event
|
te.copy(readReceipts = readReceipts)
|
||||||
builtEvents[builtIndex]?.let { te ->
|
}
|
||||||
builtEvents[builtIndex] = te.copy(readReceipts = readReceipts)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} ?: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReadReceiptsUpdated() {
|
override fun onReadReceiptsUpdated() {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
|
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean {
|
||||||
|
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
|
//Update the relation of existing event
|
||||||
|
builtEvents[builtIndex]?.let { te ->
|
||||||
|
builtEvents[builtIndex] = builder(te)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
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 ->
|
||||||
|
@ -571,7 +599,7 @@ internal class DefaultTimeline(
|
||||||
debouncer.debounce("post_snapshot", runnable, 50)
|
debouncer.debounce("post_snapshot", runnable, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension methods ***************************************************************************
|
// Extension methods ***************************************************************************
|
||||||
|
|
||||||
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
||||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.internal.session.sync
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.read.FullyReadContent
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadMarkerEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import io.realm.Realm
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class RoomFullyReadHandler @Inject constructor() {
|
||||||
|
|
||||||
|
fun handle(realm: Realm, roomId: String, content: FullyReadContent?) {
|
||||||
|
if (content == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Timber.v("Handle for roomId: $roomId eventId: ${content.eventId}")
|
||||||
|
val readMarkerEntity = ReadMarkerEntity.getOrCreate(realm, roomId).apply {
|
||||||
|
eventId = content.eventId
|
||||||
|
}
|
||||||
|
// Remove the old marker if any
|
||||||
|
readMarkerEntity.timelineEvent?.firstOrNull()?.readMarker = null
|
||||||
|
// Attach to timelineEvent if known
|
||||||
|
val timelineEventEntity = TimelineEventEntity.where(realm, eventId = content.eventId).findFirst()
|
||||||
|
timelineEventEntity?.readMarker = readMarkerEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,8 +23,13 @@ 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.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
|
||||||
|
import im.vector.matrix.android.api.session.room.read.FullyReadContent
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.CryptoManager
|
||||||
import im.vector.matrix.android.internal.database.helper.*
|
import im.vector.matrix.android.internal.database.helper.add
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||||
|
import im.vector.matrix.android.internal.database.helper.addStateEvent
|
||||||
|
import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||||
|
import im.vector.matrix.android.internal.database.helper.updateSenderDataFor
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
@ -37,7 +42,11 @@ import im.vector.matrix.android.internal.session.notification.DefaultPushRuleSer
|
||||||
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
|
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
import im.vector.matrix.android.internal.session.sync.model.*
|
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomSync
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncAccountData
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral
|
||||||
|
import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse
|
||||||
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
import im.vector.matrix.android.internal.session.user.UserEntityFactory
|
||||||
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
|
||||||
|
@ -50,6 +59,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||||
private val readReceiptHandler: ReadReceiptHandler,
|
private val readReceiptHandler: ReadReceiptHandler,
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||||
private val roomTagHandler: RoomTagHandler,
|
private val roomTagHandler: RoomTagHandler,
|
||||||
|
private val roomFullyReadHandler: RoomFullyReadHandler,
|
||||||
private val cryptoManager: CryptoManager,
|
private val cryptoManager: CryptoManager,
|
||||||
private val tokenStore: SyncTokenStore,
|
private val tokenStore: SyncTokenStore,
|
||||||
private val pushRuleService: DefaultPushRuleService,
|
private val pushRuleService: DefaultPushRuleService,
|
||||||
|
@ -247,11 +257,16 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
||||||
accountData.events
|
for (event in accountData.events) {
|
||||||
.asSequence()
|
val eventType = event.getClearType()
|
||||||
.filter { it.getClearType() == EventType.TAG }
|
if (eventType == EventType.TAG) {
|
||||||
.map { it.content.toModel<RoomTagContent>() }
|
val content = event.getClearContent().toModel<RoomTagContent>()
|
||||||
.forEach { roomTagHandler.handle(realm, roomId, it) }
|
roomTagHandler.handle(realm, roomId, content)
|
||||||
|
} else if (eventType == EventType.FULLY_READ) {
|
||||||
|
val content = event.getClearContent().toModel<FullyReadContent>()
|
||||||
|
roomFullyReadHandler.handle(realm, roomId, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -626,9 +626,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
.buffer(1, TimeUnit.SECONDS)
|
.buffer(1, TimeUnit.SECONDS)
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.subscribeBy(onNext = { actions ->
|
.subscribeBy(onNext = { actions ->
|
||||||
|
val readMarkerVisible = actions.find { it.event.hasReadMarker } != null
|
||||||
val mostRecentEvent = actions.maxBy { it.event.displayIndex }
|
val mostRecentEvent = actions.maxBy { it.event.displayIndex }
|
||||||
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
|
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
|
||||||
room.setReadReceipt(eventId, callback = object : MatrixCallback<Unit> {})
|
room.setReadReceipt(eventId, callback = object : MatrixCallback<Unit> {})
|
||||||
|
if (readMarkerVisible) {
|
||||||
|
room.setReadMarker(eventId, callback = object : MatrixCallback<Unit> {})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disposeOnClear()
|
.disposeOnClear()
|
||||||
|
|
|
@ -129,6 +129,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||||
holder.memberNameView.setOnLongClickListener(null)
|
holder.memberNameView.setOnLongClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holder.readMarkerView.isVisible = informationData.displayReadMarker
|
||||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||||
|
|
||||||
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
||||||
|
@ -182,6 +183,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
val timeView by bind<TextView>(R.id.messageTimeView)
|
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||||
|
val readMarkerView by bind<View>(R.id.readMarkerView)
|
||||||
var reactionWrapper: ViewGroup? = null
|
var reactionWrapper: ViewGroup? = null
|
||||||
var reactionFlowHelper: Flow? = null
|
var reactionFlowHelper: Flow? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,8 @@ data class MessageInformationData(
|
||||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||||
val hasBeenEdited: Boolean = false,
|
val hasBeenEdited: Boolean = false,
|
||||||
val hasPendingEdits: Boolean = false,
|
val hasPendingEdits: Boolean = false,
|
||||||
val readReceipts: List<ReadReceiptData> = emptyList()
|
val readReceipts: List<ReadReceiptData> = emptyList(),
|
||||||
|
val displayReadMarker: Boolean = false
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -65,6 +66,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||||
)
|
)
|
||||||
holder.view.setOnLongClickListener(longClickListener)
|
holder.view.setOnLongClickListener(longClickListener)
|
||||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||||
|
holder.readMarkerView.isVisible = informationData.displayReadMarker
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getViewType() = STUB_ID
|
override fun getViewType() = STUB_ID
|
||||||
|
@ -73,6 +75,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||||
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
||||||
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
||||||
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||||
|
val readMarkerView by bind<View>(R.id.readMarkerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -62,6 +62,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: ""))
|
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val displayReadMarker = event.hasReadMarker && event.readReceipts.find { it.user.userId == session.myUserId } == null
|
||||||
|
|
||||||
return MessageInformationData(
|
return MessageInformationData(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
senderId = event.root.senderId ?: "",
|
senderId = event.root.senderId ?: "",
|
||||||
|
@ -85,7 +87,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
.map {
|
.map {
|
||||||
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
|
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
|
||||||
}
|
}
|
||||||
.toList()
|
.toList(),
|
||||||
|
displayReadMarker = displayReadMarker
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -122,7 +122,6 @@
|
||||||
|
|
||||||
</ViewStub>
|
</ViewStub>
|
||||||
|
|
||||||
|
|
||||||
<im.vector.riotx.core.ui.views.ReadReceiptsView
|
<im.vector.riotx.core.ui.views.ReadReceiptsView
|
||||||
android:id="@+id/readReceiptsView"
|
android:id="@+id/readReceiptsView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -132,5 +131,14 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/readMarkerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:background="@android:color/holo_green_light"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -61,5 +61,15 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/readMarkerView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:background="@android:color/holo_green_light"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue