Compare commits

...

10 Commits

Author SHA1 Message Date
NIkita Fedrunov
933a1cc6ee hotfixes 2022-11-16 14:56:17 +01:00
NIkita Fedrunov
13027c92e1 Merge branch 'feature/nfe/threads_read_receipts' into temp/nfe/testing_1611 2022-11-16 13:29:47 +01:00
NIkita Fedrunov
e560d7ab4a fixed compilation error 2022-11-16 13:25:10 +01:00
NIkita Fedrunov
5dd40bd43f Merge branch 'develop' into feature/nfe/threads_read_receipts 2022-11-16 13:11:15 +01:00
NIkita Fedrunov
d30381993f 4 2022-11-16 13:09:24 +01:00
NIkita Fedrunov
89020eff8d 3 2022-11-16 09:43:50 +01:00
NIkita Fedrunov
42c4912ed3 2 2022-11-15 17:01:14 +01:00
NIkita Fedrunov
c9c9e37b17 saving sync filter changed 2022-11-15 11:21:41 +01:00
NIkita Fedrunov
33e673c053 lint 2022-11-01 12:07:15 +01:00
NIkita Fedrunov
1f8ac06528 added read receipts for threads 2022-10-27 12:12:24 +02:00
44 changed files with 857 additions and 188 deletions

View File

@ -18,5 +18,6 @@ package org.matrix.android.sdk.api.session.room.model
data class ReadReceipt(
val roomMember: RoomMemberSummary,
val originServerTs: Long
val originServerTs: Long,
val threadId: String?
)

View File

@ -34,12 +34,12 @@ interface ReadService {
/**
* Force the read marker to be set on the latest event.
*/
suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH)
suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, mainTimeLineOnly: Boolean)
/**
* Set the read receipt on the event with provided eventId.
*/
suspend fun setReadReceipt(eventId: String)
suspend fun setReadReceipt(eventId: String, threadId: String)
/**
* Set the read marker on the event with provided eventId.
@ -62,7 +62,7 @@ interface ReadService {
fun getMyReadReceiptLive(): LiveData<Optional<String>>
/**
* Get the eventId where the read receipt for the provided user is.
* Get the eventId from the main timeline where the read receipt for the provided user is.
* @param userId the id of the user to look for
*
* @return the eventId where the read receipt for the provided user is attached, or null if not found
@ -74,4 +74,8 @@ interface ReadService {
* @param eventId the event
*/
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
companion object {
const val THREAD_ID_MAIN = "main"
}
}

View File

@ -16,19 +16,13 @@
package org.matrix.android.sdk.api.session.sync
import org.matrix.android.sdk.internal.session.filter.SyncFilterBuilder
interface FilterService {
enum class FilterPreset {
NoFilter,
/**
* Filter for Element, will include only known event type.
*/
ElementFilter
}
// TODO:doc
/**
* Configure the filter for the sync.
*/
fun setFilter(filterPreset: FilterPreset)
fun setSyncFilter(filterBuilder: SyncFilterBuilder)
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.api.session.sync.filter
internal data class SyncFilterParams (
val lazyLoadMembersForStateEvents: Boolean? = null,
val lazyLoadMembersForMessageEvents: Boolean? = null,
val useThreadNotifications: Boolean? = null,
val listOfSupportedEventTypes: List<String>? = null,
val listOfSupportedStateEventTypes: List<String>? = null,
)

View File

@ -59,6 +59,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@ -67,7 +68,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 42L,
schemaVersion = 43L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@ -119,5 +120,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 40) MigrateSessionTo040(realm).perform()
if (oldVersion < 41) MigrateSessionTo041(realm).perform()
if (oldVersion < 42) MigrateSessionTo042(realm).perform()
if (oldVersion < 43) MigrateSessionTo043(realm).perform()
}
}

View File

@ -133,7 +133,7 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE
val originServerTs = eventEntity.originServerTs
if (originServerTs != null) {
val timestampOfEvent = originServerTs.toDouble()
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId, threadId = eventEntity.rootThreadEventId)
// If the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()

View File

@ -22,6 +22,7 @@ import io.realm.RealmQuery
import io.realm.Sort
import org.matrix.android.sdk.api.session.events.model.UnsignedData
import org.matrix.android.sdk.api.session.events.model.isRedacted
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
import org.matrix.android.sdk.internal.database.mapper.asDomain
@ -273,8 +274,8 @@ internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm,
/**
* Find the read receipt for the current user.
*/
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? =
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId)
internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): String? =
ReadReceiptEntity.where(realm, roomId = roomId, userId = userId, threadId = threadId)
.findFirst()
?.eventId
@ -294,7 +295,7 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin
* immediately so we should not display wrong notifications
*/
internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) {
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return
val readReceipt = findMyReadReceipt(realm, roomId, currentUserId, ReadService.THREAD_ID_MAIN) ?: return
val readReceiptChunk = ChunkEntity
.findIncludingEvent(realm, readReceipt) ?: return

View File

@ -0,0 +1,45 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.mapper
import io.realm.RealmList
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity
import javax.inject.Inject
internal class FilterParamsMapper @Inject constructor() {
fun map(entity: SyncFilterParamsEntity): SyncFilterParams {
return SyncFilterParams(
useThreadNotifications = entity.useThreadNotifications,
lazyLoadMembersForMessageEvents = entity.lazyLoadMembersForMessageEvents,
lazyLoadMembersForStateEvents = entity.lazyLoadMembersForStateEvents,
listOfSupportedEventTypes = entity.listOfSupportedEventTypes?.toList(),
listOfSupportedStateEventTypes = entity.listOfSupportedStateEventTypes?.toList(),
)
}
fun map(params: SyncFilterParams): SyncFilterParamsEntity {
return SyncFilterParamsEntity(
useThreadNotifications = params.useThreadNotifications,
lazyLoadMembersForMessageEvents = params.lazyLoadMembersForMessageEvents,
lazyLoadMembersForStateEvents = params.lazyLoadMembersForStateEvents,
listOfSupportedEventTypes = params.listOfSupportedEventTypes?.toTypedArray()?.let { RealmList(*it) },
listOfSupportedStateEventTypes = params.listOfSupportedStateEventTypes?.toTypedArray()?.let { RealmList(*it) },
)
}
}

View File

@ -50,7 +50,7 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(
.mapNotNull {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
?: return@mapNotNull null
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong(), it.threadId)
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateSessionTo043(realm: DynamicRealm) : RealmMigrator(realm, 43) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("SyncFilterParamsEntity")
.addField(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_STATE_EVENTS, Boolean::class.java)
.addField(SyncFilterParamsEntityFields.LAZY_LOAD_MEMBERS_FOR_MESSAGE_EVENTS, Boolean::class.java)
.addField(SyncFilterParamsEntityFields.USE_THREAD_NOTIFICATIONS, Boolean::class.java)
}
}

View File

@ -26,6 +26,7 @@ internal open class ReadReceiptEntity(
var eventId: String = "",
var roomId: String = "",
var userId: String = "",
var threadId: String? = null,
var originServerTs: Double = 0.0
) : RealmObject() {
companion object

View File

@ -70,7 +70,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
SpaceChildSummaryEntity::class,
SpaceParentSummaryEntity::class,
UserPresenceEntity::class,
ThreadSummaryEntity::class
ThreadSummaryEntity::class,
SyncFilterParamsEntity::class,
]
)
internal class SessionRealmModule

View File

@ -0,0 +1,34 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.model
import io.realm.RealmList
import io.realm.RealmObject
/**
* This entity stores Sync Filter configuration data, provided by the client
*/
internal open class SyncFilterParamsEntity(
var lazyLoadMembersForStateEvents: Boolean? = null,
var lazyLoadMembersForMessageEvents: Boolean? = null,
var useThreadNotifications: Boolean? = null,
var listOfSupportedEventTypes: RealmList<String>? = null,
var listOfSupportedStateEventTypes: RealmList<String>? = null,
) : RealmObject() {
companion object
}

View File

@ -20,6 +20,7 @@ import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.internal.extensions.assertIsManaged
internal open class TimelineEventEntity(
@ -52,3 +53,7 @@ internal fun TimelineEventEntity.deleteOnCascade(canDeleteRoot: Boolean) {
}
deleteFromRealm()
}
internal fun TimelineEventEntity.getThreadId(): String {
return root?.rootThreadEventId ?: ReadService.THREAD_ID_MAIN
}

View File

@ -23,12 +23,14 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.getThreadId
internal fun isEventRead(
realmConfiguration: RealmConfiguration,
userId: String?,
roomId: String?,
eventId: String?
eventId: String?,
shouldCheckIfReadInEventsThread: Boolean
): Boolean {
if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) {
return false
@ -45,7 +47,8 @@ internal fun isEventRead(
eventToCheck.root?.sender == userId -> true
// If new event exists and the latest event is from ourselves we can infer the event is read
latestEventIsFromSelf(realm, roomId, userId) -> true
eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId) -> true
eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId, null) -> true
(shouldCheckIfReadInEventsThread && eventToCheck.isBeforeLatestReadReceipt(realm, roomId, userId, eventToCheck.getThreadId())) -> true
else -> false
}
}
@ -54,11 +57,12 @@ internal fun isEventRead(
private fun latestEventIsFromSelf(realm: Realm, roomId: String, userId: String) = TimelineEventEntity.latestEvent(realm, roomId, true)
?.root?.sender == userId
private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String): Boolean {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()?.let { readReceipt ->
private fun TimelineEventEntity.isBeforeLatestReadReceipt(realm: Realm, roomId: String, userId: String, threadId: String?): Boolean {
val isMoreRecent = ReadReceiptEntity.where(realm, roomId, userId, threadId).findFirst()?.let { readReceipt ->
val readReceiptEvent = TimelineEventEntity.where(realm, roomId, readReceipt.eventId).findFirst()
readReceiptEvent?.isMoreRecentThan(this)
} ?: false
return isMoreRecent
}
/**

View File

@ -20,12 +20,26 @@ import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.createObject
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntityFields
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String, threadId: String?): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, threadId))
}
internal fun ReadReceiptEntity.Companion.forMainTimelineWhere(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, ReadService.THREAD_ID_MAIN))
.or()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId, null))
}
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId))
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
}
internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: String): RealmQuery<ReadReceiptEntity> {
@ -38,23 +52,31 @@ internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: Strin
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
}
internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
internal fun ReadReceiptEntity.Companion.createUnmanaged(
roomId: String,
eventId: String,
userId: String,
threadId: String?,
originServerTs: Double
): ReadReceiptEntity {
return ReadReceiptEntity().apply {
this.primaryKey = "${roomId}_$userId"
this.primaryKey = buildPrimaryKey(roomId, userId, threadId)
this.eventId = eventId
this.roomId = roomId
this.userId = userId
this.threadId = threadId
this.originServerTs = originServerTs
}
}
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId))
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String, threadId: String?): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId, threadId).findFirst()
?: realm.createObject<ReadReceiptEntity>(buildPrimaryKey(roomId, userId, threadId))
.apply {
this.roomId = roomId
this.userId = userId
this.threadId = threadId
}
}
private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId"
private fun buildPrimaryKey(roomId: String, userId: String, threadId: String?) = "${roomId}_${userId}_${threadId ?: "null"}"

View File

@ -19,44 +19,37 @@ package org.matrix.android.sdk.internal.session.filter
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
import org.matrix.android.sdk.internal.database.mapper.FilterParamsMapper
import org.matrix.android.sdk.internal.database.model.FilterEntity
import org.matrix.android.sdk.internal.database.model.FilterEntityFields
import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.util.awaitTransaction
import javax.inject.Inject
internal class DefaultFilterRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : FilterRepository {
internal class DefaultFilterRepository @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
) : FilterRepository {
override suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
val filterEntity = FilterEntity.get(realm)
// Filter has changed, or no filter Id yet
filterEntity == null ||
filterEntity.filterBodyJson != filter.toJSONString() ||
filterEntity.filterId.isBlank()
}.also { hasChanged ->
if (hasChanged) {
// Filter is new or has changed, store it and reset the filter Id.
// This has to be done outside of the Realm.use(), because awaitTransaction change the current thread
monarchy.awaitTransaction { realm ->
// We manage only one filter for now
val filterJson = filter.toJSONString()
val roomEventFilterJson = roomEventFilter.toJSONString()
override suspend fun storeSyncFilter(filter: Filter, roomEventFilter: RoomEventFilter) {
monarchy.awaitTransaction { realm ->
// We manage only one filter for now
val filterJson = filter.toJSONString()
val roomEventFilterJson = roomEventFilter.toJSONString()
val filterEntity = FilterEntity.getOrCreate(realm)
val filterEntity = FilterEntity.getOrCreate(realm)
filterEntity.filterBodyJson = filterJson
filterEntity.roomEventFilterJson = roomEventFilterJson
// Reset filterId
filterEntity.filterId = ""
}
}
filterEntity.filterBodyJson = filterJson
filterEntity.roomEventFilterJson = roomEventFilterJson
// Reset filterId
filterEntity.filterId = ""
}
}
override suspend fun storeFilterId(filter: Filter, filterId: String) {
override suspend fun storeSyncFilterId(filter: Filter, filterId: String) {
monarchy.awaitTransaction {
// We manage only one filter for now
val filterJson = filter.toJSONString()
@ -69,15 +62,19 @@ internal class DefaultFilterRepository @Inject constructor(@SessionDatabase priv
}
}
override suspend fun getFilter(): String {
override suspend fun getStoredSyncFilterBody(): String {
return monarchy.awaitTransaction {
val filter = FilterEntity.getOrCreate(it)
if (filter.filterId.isBlank()) {
// Use the Json format
filter.filterBodyJson
FilterEntity.getOrCreate(it).filterBodyJson
}
}
override suspend fun getStoredSyncFilterId(): String? {
return monarchy.awaitTransaction {
val id = FilterEntity.get(it)?.filterId
if (id.isNullOrBlank()) {
null
} else {
// Use FilterId
filter.filterId
id
}
}
}
@ -87,4 +84,18 @@ internal class DefaultFilterRepository @Inject constructor(@SessionDatabase priv
FilterEntity.getOrCreate(it).roomEventFilterJson
}
}
override suspend fun getStoredParams(): SyncFilterParams? {
return monarchy.awaitTransaction { realm ->
realm.where<SyncFilterParamsEntity>().findFirst()?.let {
FilterParamsMapper().map(it)
}
}
}
override suspend fun storeFilterParams(params: SyncFilterParams) {
return monarchy.awaitTransaction { realm ->
realm.insertOrUpdate(FilterParamsMapper().map(params))
}
}
}

View File

@ -16,20 +16,33 @@
package org.matrix.android.sdk.internal.session.filter
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import javax.inject.Inject
internal class DefaultFilterService @Inject constructor(
private val saveFilterTask: SaveFilterTask,
private val taskExecutor: TaskExecutor
private val taskExecutor: TaskExecutor,
private val filterRepository: FilterRepository,
private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
) : FilterService {
// TODO Pass a list of support events instead
override fun setFilter(filterPreset: FilterService.FilterPreset) {
override fun setSyncFilter(filterBuilder: SyncFilterBuilder) {
taskExecutor.executorScope.launch {
filterRepository.storeFilterParams(filterBuilder.extractParams())
}
val filter = filterBuilder.build(homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities())
saveFilterTask
.configureWith(SaveFilterTask.Params(filterPreset))
.configureWith(
SaveFilterTask.Params(
filter = filter
)
)
.executeBy(taskExecutor)
}
}

View File

@ -45,46 +45,7 @@ internal object FilterFactory {
return FilterUtil.enableLazyLoading(Filter(), true)
}
fun createElementFilter(): Filter {
return Filter(
room = RoomFilter(
timeline = createElementTimelineFilter(),
state = createElementStateFilter()
)
)
}
fun createDefaultRoomFilter(): RoomEventFilter {
return RoomEventFilter(lazyLoadMembers = true)
}
fun createElementRoomFilter(): RoomEventFilter {
return RoomEventFilter(
lazyLoadMembers = true,
// TODO Enable this for optimization
// types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList()
)
}
private fun createElementTimelineFilter(): RoomEventFilter? {
// we need to check if homeserver supports thread notifications before setting this param
// return RoomEventFilter(enableUnreadThreadNotifications = true)
return null
}
private fun createElementStateFilter(): RoomEventFilter {
return RoomEventFilter(lazyLoadMembers = true)
}
// Get only managed types by Element
private val listOfSupportedEventTypes = listOf(
// TODO Complete the list
EventType.MESSAGE
)
// Get only managed types by Element
private val listOfSupportedStateEventTypes = listOf(
// TODO Complete the list
EventType.STATE_ROOM_MEMBER
)
}

View File

@ -44,4 +44,7 @@ internal abstract class FilterModule {
@Binds
abstract fun bindSaveFilterTask(task: DefaultSaveFilterTask): SaveFilterTask
@Binds
abstract fun bindGetCurrentFilterTask(task: DefaultGetCurrentFilterTask): GetCurrentFilterTask
}

View File

@ -16,25 +16,46 @@
package org.matrix.android.sdk.internal.session.filter
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
/**
* Repository for request filters
*/
internal interface FilterRepository {
/**
* Return true if the filterBody has changed, or need to be sent to the server.
* Stores sync filter and room filter
* @return true if the filterBody has changed, or need to be sent to the server.
*/
suspend fun storeFilter(filter: Filter, roomEventFilter: RoomEventFilter): Boolean
suspend fun storeSyncFilter(filter: Filter, roomEventFilter: RoomEventFilter)
/**
* Set the filterId of this filter.
* Set the filterId of sync filter.
*/
suspend fun storeFilterId(filter: Filter, filterId: String)
suspend fun storeSyncFilterId(filter: Filter, filterId: String)
/**
* Return filter json or filter id.
* Returns stored sync filter's JSON body if it exists
*/
suspend fun getFilter(): String
suspend fun getStoredSyncFilterBody(): String?
/**
* Returns stored sync filter's ID if it exists
*/
suspend fun getStoredSyncFilterId(): String?
/**
* Return the room filter.
*/
suspend fun getRoomFilter(): String
/**
* Returns filter params stored in local storage if it exists
*/
suspend fun getStoredParams(): SyncFilterParams?
/**
* Stores filter params to local storage
*/
suspend fun storeFilterParams(params: SyncFilterParams)
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.session.filter
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetCurrentFilterTask : Task<Unit, String>
internal class DefaultGetCurrentFilterTask @Inject constructor(
private val filterRepository: FilterRepository,
private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val saveFilterTask: SaveFilterTask
) : GetCurrentFilterTask {
override suspend fun execute(params: Unit): String {
// val storedFilterId = filterRepository.getStoredSyncFilterId()
val storedFilterBody = filterRepository.getStoredSyncFilterBody()
val homeServerCapabilities = homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities()
val currentFilter = SyncFilterBuilder()
.with(filterRepository.getStoredParams())
.build(homeServerCapabilities)
val currentFilterBody = currentFilter.toJSONString()
return when (storedFilterBody) {
currentFilterBody -> storedFilterBody
else -> saveFilter(currentFilter)
}
}
private suspend fun saveFilter(filter: Filter) = saveFilterTask
.execute(
SaveFilterTask.Params(
filter = filter
)
)
}

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.session.filter
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
@ -26,10 +25,10 @@ import javax.inject.Inject
/**
* Save a filter, in db and if any changes, upload to the server.
*/
internal interface SaveFilterTask : Task<SaveFilterTask.Params, Unit> {
internal interface SaveFilterTask : Task<SaveFilterTask.Params, String> {
data class Params(
val filterPreset: FilterService.FilterPreset
val filter: Filter
)
}
@ -37,33 +36,17 @@ internal class DefaultSaveFilterTask @Inject constructor(
@UserId private val userId: String,
private val filterAPI: FilterApi,
private val filterRepository: FilterRepository,
private val globalErrorReceiver: GlobalErrorReceiver
private val globalErrorReceiver: GlobalErrorReceiver,
) : SaveFilterTask {
override suspend fun execute(params: SaveFilterTask.Params) {
val filterBody = when (params.filterPreset) {
FilterService.FilterPreset.ElementFilter -> {
FilterFactory.createElementFilter()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultFilter()
}
}
val roomFilter = when (params.filterPreset) {
FilterService.FilterPreset.ElementFilter -> {
FilterFactory.createElementRoomFilter()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultRoomFilter()
}
}
val updated = filterRepository.storeFilter(filterBody, roomFilter)
if (updated) {
val filterResponse = executeRequest(globalErrorReceiver) {
// TODO auto retry
filterAPI.uploadFilter(userId, filterBody)
}
filterRepository.storeFilterId(filterBody, filterResponse.filterId)
override suspend fun execute(params: SaveFilterTask.Params): String {
val filter = params.filter
val filterResponse = executeRequest(globalErrorReceiver) {
// TODO auto retry
filterAPI.uploadFilter(userId, filter)
}
filterRepository.storeSyncFilter(filter, filter.room?.timeline ?: FilterFactory.createDefaultRoomFilter())
filterRepository.storeSyncFilterId(filter, filterResponse.filterId)
return filterResponse.filterId
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.session.filter
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
class SyncFilterBuilder {
private var lazyLoadMembersForStateEvents: Boolean? = true
private var lazyLoadMembersForMessageEvents: Boolean? = true
private var useThreadNotifications: Boolean? = false
private var listOfSupportedEventTypes: List<String>? = null
private var listOfSupportedStateEventTypes: List<String>? = null
fun lazyLoadMembersForStateEvents(lazyLoadMembersForStateEvents: Boolean) = apply { this.lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents }
fun lazyLoadMembersForMessageEvents(lazyLoadMembersForMessageEvents: Boolean) =
apply { this.lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents }
fun useThreadNotifications(useThreadNotifications: Boolean) =
apply { this.useThreadNotifications = useThreadNotifications }
fun listOfSupportedStateEventTypes(listOfSupportedStateEventTypes: List<String>) =
apply { this.listOfSupportedStateEventTypes = listOfSupportedStateEventTypes }
fun listOfSupportedTimelineEventTypes(listOfSupportedEventTypes: List<String>) =
apply { this.listOfSupportedEventTypes = listOfSupportedEventTypes }
internal fun with(currentFilterParams: SyncFilterParams?) =
apply {
currentFilterParams?.let {
useThreadNotifications = currentFilterParams.useThreadNotifications
lazyLoadMembersForMessageEvents = currentFilterParams.lazyLoadMembersForMessageEvents
lazyLoadMembersForStateEvents = currentFilterParams.lazyLoadMembersForStateEvents
listOfSupportedEventTypes = currentFilterParams.listOfSupportedEventTypes?.toList()
listOfSupportedStateEventTypes = currentFilterParams.listOfSupportedStateEventTypes?.toList()
}
}
internal fun extractParams(): SyncFilterParams {
return SyncFilterParams(
useThreadNotifications = useThreadNotifications,
lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents,
lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents,
listOfSupportedEventTypes = listOfSupportedEventTypes,
listOfSupportedStateEventTypes = listOfSupportedStateEventTypes,
)
}
internal fun build(homeServerCapabilities: HomeServerCapabilities): Filter {
return Filter(
room = buildRoomFilter(homeServerCapabilities)
)
}
private fun buildRoomFilter(homeServerCapabilities: HomeServerCapabilities): RoomFilter {
return RoomFilter(
timeline = buildTimelineFilter(homeServerCapabilities),
state = buildStateFilter()
)
}
private fun buildTimelineFilter(homeServerCapabilities: HomeServerCapabilities): RoomEventFilter? {
val resolvedUseThreadNotifications = if (homeServerCapabilities.canUseThreadReadReceiptsAndNotifications) {
useThreadNotifications
} else {
null
}
return RoomEventFilter(
enableUnreadThreadNotifications = resolvedUseThreadNotifications,
lazyLoadMembers = lazyLoadMembersForMessageEvents
).orNullIfEmpty()
}
private fun buildStateFilter(): RoomEventFilter? =
RoomEventFilter(
lazyLoadMembers = lazyLoadMembersForStateEvents,
types = listOfSupportedStateEventTypes
).orNullIfEmpty()
private fun RoomEventFilter.orNullIfEmpty(): RoomEventFilter? {
return if (hasData()) {
this
} else {
null
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SyncFilterBuilder
if (lazyLoadMembersForStateEvents != other.lazyLoadMembersForStateEvents) return false
if (lazyLoadMembersForMessageEvents != other.lazyLoadMembersForMessageEvents) return false
if (useThreadNotifications != other.useThreadNotifications) return false
if (listOfSupportedEventTypes != other.listOfSupportedEventTypes) return false
if (listOfSupportedStateEventTypes != other.listOfSupportedStateEventTypes) return false
return true
}
override fun hashCode(): Int {
var result = lazyLoadMembersForStateEvents?.hashCode() ?: 0
result = 31 * result + (lazyLoadMembersForMessageEvents?.hashCode() ?: 0)
result = 31 * result + (useThreadNotifications?.hashCode() ?: 0)
result = 31 * result + (listOfSupportedEventTypes?.hashCode() ?: 0)
result = 31 * result + (listOfSupportedStateEventTypes?.hashCode() ?: 0)
return result
}
}

View File

@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMembersRespon
import org.matrix.android.sdk.internal.session.room.membership.admin.UserIdAndReason
import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBody
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
import org.matrix.android.sdk.internal.session.room.read.ReadBody
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
import org.matrix.android.sdk.internal.session.room.send.SendResponse
@ -173,7 +174,7 @@ internal interface RoomAPI {
@Path("roomId") roomId: String,
@Path("receiptType") receiptType: String,
@Path("eventId") eventId: String,
@Body body: JsonDict = emptyMap()
@Body body: ReadBody
)
/**

View File

@ -22,6 +22,7 @@ import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.util.Optional
@ -30,6 +31,7 @@ import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
import org.matrix.android.sdk.internal.database.query.forMainTimelineWhere
import org.matrix.android.sdk.internal.database.query.isEventRead
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
@ -40,7 +42,8 @@ internal class DefaultReadService @AssistedInject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val setReadMarkersTask: SetReadMarkersTask,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
@UserId private val userId: String
@UserId private val userId: String,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
) : ReadService {
@AssistedFactory
@ -48,17 +51,28 @@ internal class DefaultReadService @AssistedInject constructor(
fun create(roomId: String): DefaultReadService
}
override suspend fun markAsRead(params: ReadService.MarkAsReadParams) {
override suspend fun markAsRead(params: ReadService.MarkAsReadParams, mainTimeLineOnly: Boolean) {
val readReceiptThreadId = if (homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications) {
if (mainTimeLineOnly) ReadService.THREAD_ID_MAIN else null
} else {
null
}
val taskParams = SetReadMarkersTask.Params(
roomId = roomId,
forceReadMarker = params.forceReadMarker(),
forceReadReceipt = params.forceReadReceipt()
forceReadReceipt = params.forceReadReceipt(),
readReceiptThreadId = readReceiptThreadId
)
setReadMarkersTask.execute(taskParams)
}
override suspend fun setReadReceipt(eventId: String) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId)
override suspend fun setReadReceipt(eventId: String, threadId: String) {
val readReceiptThreadId = if (homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications) {
threadId
} else {
null
}
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId, readReceiptThreadId = readReceiptThreadId)
setReadMarkersTask.execute(params)
}
@ -68,7 +82,8 @@ internal class DefaultReadService @AssistedInject constructor(
}
override fun isEventRead(eventId: String): Boolean {
return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId)
val shouldCheckIfReadInEventsThread = homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications
return isEventRead(monarchy.realmConfiguration, userId, roomId, eventId, shouldCheckIfReadInEventsThread)
}
override fun getReadMarkerLive(): LiveData<Optional<String>> {
@ -94,10 +109,11 @@ internal class DefaultReadService @AssistedInject constructor(
override fun getUserReadReceipt(userId: String): String? {
var eventId: String? = null
monarchy.doWithRealm {
eventId = ReadReceiptEntity.where(it, roomId = roomId, userId = userId)
eventId = ReadReceiptEntity.forMainTimelineWhere(it, roomId = roomId, userId = userId)
.findFirst()
?.eventId
}
return eventId
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.session.room.read
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class ReadBody(
@Json(name = "thread_id") val threadId: String?,
)

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.read
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.isEventRead
@ -45,8 +46,9 @@ internal interface SetReadMarkersTask : Task<SetReadMarkersTask.Params, Unit> {
val roomId: String,
val fullyReadEventId: String? = null,
val readReceiptEventId: String? = null,
val readReceiptThreadId: String? = null,
val forceReadReceipt: Boolean = false,
val forceReadMarker: Boolean = false
val forceReadMarker: Boolean = false,
)
}
@ -61,12 +63,14 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver,
private val clock: Clock,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params) {
val markers = mutableMapOf<String, String>()
Timber.v("Execute set read marker with params: $params")
val latestSyncedEventId = latestSyncedEventId(params.roomId)
val readReceiptThreadId = params.readReceiptThreadId
val fullyReadEventId = if (params.forceReadMarker) {
latestSyncedEventId
} else {
@ -77,6 +81,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
} else {
params.readReceiptEventId
}
if (fullyReadEventId != null && !isReadMarkerMoreRecent(monarchy.realmConfiguration, params.roomId, fullyReadEventId)) {
if (LocalEcho.isLocalEchoId(fullyReadEventId)) {
Timber.w("Can't set read marker for local event $fullyReadEventId")
@ -84,8 +89,12 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
markers[READ_MARKER] = fullyReadEventId
}
}
val shouldCheckIfReadInEventsThread = readReceiptThreadId != null &&
homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications
if (readReceiptEventId != null &&
!isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId)) {
!isEventRead(monarchy.realmConfiguration, userId, params.roomId, readReceiptEventId, shouldCheckIfReadInEventsThread)) {
if (LocalEcho.isLocalEchoId(readReceiptEventId)) {
Timber.w("Can't set read receipt for local event $readReceiptEventId")
} else {
@ -95,7 +104,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
val shouldUpdateRoomSummary = readReceiptEventId != null && readReceiptEventId == latestSyncedEventId
if (markers.isNotEmpty() || shouldUpdateRoomSummary) {
updateDatabase(params.roomId, markers, shouldUpdateRoomSummary)
updateDatabase(params.roomId, readReceiptThreadId, markers, shouldUpdateRoomSummary)
}
if (markers.isNotEmpty()) {
executeRequest(
@ -104,7 +113,8 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
) {
if (markers[READ_MARKER] == null) {
if (readReceiptEventId != null) {
roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId)
val readBody = ReadBody(threadId = params.readReceiptThreadId)
roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId, readBody)
}
} else {
// "m.fully_read" value is mandatory to make this call
@ -119,7 +129,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
}
private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
private suspend fun updateDatabase(roomId: String, readReceiptThreadId: String?, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
monarchy.awaitTransaction { realm ->
val readMarkerId = markers[READ_MARKER]
val readReceiptId = markers[READ_RECEIPT]
@ -127,7 +137,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
}
if (readReceiptId != null) {
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, readReceiptThreadId, clock.epochMillis())
readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null)
}
if (shouldUpdateRoomSummary) {

View File

@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@ -75,7 +76,8 @@ internal class RoomSummaryUpdater @Inject constructor(
private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor,
private val crossSigningService: DefaultCrossSigningService,
private val roomAccountDataDataSource: RoomAccountDataDataSource
private val roomAccountDataDataSource: RoomAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
) {
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
@ -151,9 +153,11 @@ internal class RoomSummaryUpdater @Inject constructor(
latestPreviewableEvent.attemptToDecrypt()
}
val shouldCheckIfReadInEventsThread = homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 ||
// avoid this call if we are sure there are unread events
latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId) } ?: false
latestPreviewableEvent?.let { !isEventRead(realm.configuration, userId, roomId, it.eventId, shouldCheckIfReadInEventsThread) } ?: false
roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId))
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)

View File

@ -411,7 +411,6 @@ internal class DefaultTimeline(
private fun ensureReadReceiptAreLoaded(realm: Realm) {
readReceiptHandler.getContentFromInitSync(roomId)
?.also {
Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId")
}
?.let { readReceiptContent ->
realm.executeTransactionAsync {

View File

@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.network.toFailure
import org.matrix.android.sdk.internal.session.SessionListeners
import org.matrix.android.sdk.internal.session.dispatchTo
import org.matrix.android.sdk.internal.session.filter.FilterRepository
import org.matrix.android.sdk.internal.session.filter.GetCurrentFilterTask
import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
import org.matrix.android.sdk.internal.session.user.UserStore
@ -64,11 +65,9 @@ internal interface SyncTask : Task<SyncTask.Params, SyncResponse> {
internal class DefaultSyncTask @Inject constructor(
private val syncAPI: SyncAPI,
@UserId private val userId: String,
private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler,
private val syncRequestStateTracker: SyncRequestStateTracker,
private val syncTokenStore: SyncTokenStore,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
private val userStore: UserStore,
private val session: Session,
private val sessionListeners: SessionListeners,
@ -79,6 +78,8 @@ internal class DefaultSyncTask @Inject constructor(
private val syncResponseParser: InitialSyncResponseParser,
private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
private val clock: Clock,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
private val getCurrentFilterTask: GetCurrentFilterTask
) : SyncTask {
private val workingDir = File(fileDirectory, "is")
@ -100,8 +101,13 @@ internal class DefaultSyncTask @Inject constructor(
requestParams["since"] = token
timeout = params.timeout
}
// Maybe refresh the homeserver capabilities data we know
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
val filter = getCurrentFilterTask.execute(Unit)
requestParams["timeout"] = timeout.toString()
requestParams["filter"] = filterRepository.getFilter()
requestParams["filter"] = filter
params.presence?.let { requestParams["set_presence"] = it.value }
val isInitialSync = token == null
@ -115,8 +121,6 @@ internal class DefaultSyncTask @Inject constructor(
)
syncRequestStateTracker.startRoot(InitialSyncStep.ImportingAccount, 100)
}
// Maybe refresh the homeserver capabilities data we know
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)

View File

@ -33,10 +33,11 @@ import javax.inject.Inject
// value : dict key $UserId
// value dict key ts
// dict value ts value
internal typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, Double>>>>
internal typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, Any>>>>
private const val READ_KEY = "m.read"
private const val TIMESTAMP_KEY = "ts"
private const val THREAD_ID_KEY = "thread_id"
internal class ReadReceiptHandler @Inject constructor(
private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
@ -47,14 +48,19 @@ internal class ReadReceiptHandler @Inject constructor(
fun createContent(
userId: String,
eventId: String,
threadId: String?,
currentTimeMillis: Long
): ReadReceiptContent {
val userReadReceipt = mutableMapOf<String, Any>(
TIMESTAMP_KEY to currentTimeMillis.toDouble(),
)
threadId?.let {
userReadReceipt.put(THREAD_ID_KEY, threadId)
}
return mapOf(
eventId to mapOf(
READ_KEY to mapOf(
userId to mapOf(
TIMESTAMP_KEY to currentTimeMillis.toDouble()
)
userId to userReadReceipt
)
)
)
@ -98,8 +104,9 @@ internal class ReadReceiptHandler @Inject constructor(
val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId)
for ((userId, paramsDict) in userIdsDict) {
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, ts)
val ts = paramsDict[TIMESTAMP_KEY] as? Double ?: 0.0
val threadId = paramsDict[THREAD_ID_KEY] as String?
val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, threadId, ts)
readReceiptsSummary.readReceipts.add(receiptEntity)
}
readReceiptSummaries.add(readReceiptsSummary)
@ -115,7 +122,6 @@ internal class ReadReceiptHandler @Inject constructor(
) {
// First check if we have data from init sync to handle
getContentFromInitSync(roomId)?.let {
Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId")
doIncrementalSyncStrategy(realm, roomId, it)
aggregator?.ephemeralFilesToDelete?.add(roomId)
}
@ -132,8 +138,9 @@ internal class ReadReceiptHandler @Inject constructor(
}
for ((userId, paramsDict) in userIdsDict) {
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId)
val ts = paramsDict[TIMESTAMP_KEY] as? Double ?: 0.0
val threadId = paramsDict[THREAD_ID_KEY] as String?
val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId, threadId)
// ensure new ts is superior to the previous one
if (ts > receiptEntity.originServerTs) {
ReadReceiptsSummaryEntity.where(realm, receiptEntity.eventId).findFirst()?.also {

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.sync
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity
import org.matrix.android.sdk.internal.session.filter.DefaultGetCurrentFilterTask
import org.matrix.android.sdk.internal.session.filter.SyncFilterBuilder
import org.matrix.android.sdk.test.fakes.FakeFilterRepository
import org.matrix.android.sdk.test.fakes.FakeHomeServerCapabilitiesDataSource
import org.matrix.android.sdk.test.fakes.FakeSaveFilterTask
private const val A_FILTER_ID = "filter-id"
private val A_HOMESERVER_CAPABILITIES = HomeServerCapabilities()
private val A_SYNC_FILTER_PARAMS = SyncFilterParamsEntity(
lazyLoadMembersForMessageEvents = true,
lazyLoadMembersForStateEvents = true,
useThreadNotifications = true
)
@ExperimentalCoroutinesApi
class DefaultGetCurrentFilterTaskTest {
private val filterRepository = FakeFilterRepository()
private val homeServerCapabilitiesDataSource = FakeHomeServerCapabilitiesDataSource()
private val saveFilterTask = FakeSaveFilterTask()
private val getCurrentFilterTask = DefaultGetCurrentFilterTask(
filterRepository = filterRepository,
homeServerCapabilitiesDataSource = homeServerCapabilitiesDataSource.instance,
saveFilterTask = saveFilterTask
)
//TODO: name
@Test
fun `given no filter is stored, when execute, then executes task to save new filter`() = runTest {
filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS)
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES)
filterRepository.givenFilterStored(null, null)
getCurrentFilterTask.execute(Unit)
val filter = SyncFilterBuilder()
.with(A_SYNC_FILTER_PARAMS)
.build(A_HOMESERVER_CAPABILITIES)
saveFilterTask.verifyExecution(filter)
}
@Test
fun `given filter is stored and didn't change, when execute, then returns stored filter id`() = runTest {
filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS)
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES)
val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES)
filterRepository.givenFilterStored(A_FILTER_ID, filter.toJSONString())
val result = getCurrentFilterTask.execute(Unit)
result shouldBeEqualTo A_FILTER_ID
}
@Test
fun `given filter is set and home server capabilities has changed, when execute, then executes task to save new filter`() = runTest {
filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS)
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES)
val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES)
filterRepository.givenFilterStored(A_FILTER_ID, filter.toJSONString())
val newHomeServerCapabilities = HomeServerCapabilities(canUseThreadReadReceiptsAndNotifications = true)
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(newHomeServerCapabilities)
val newFilter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(newHomeServerCapabilities)
getCurrentFilterTask.execute(Unit)
saveFilterTask.verifyExecution(newFilter)
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes
import io.mockk.coEvery
import io.mockk.mockk
import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity
import org.matrix.android.sdk.internal.session.filter.FilterRepository
internal class FakeFilterRepository: FilterRepository by mockk() {
fun givenFilterStored(filterId: String?, filterBody: String?) {
coEvery { getStoredSyncFilterId() } returns filterId
coEvery { getStoredSyncFilterBody() } returns filterBody
}
fun givenFilterParamsAreStored(filterParamsEntity: SyncFilterParamsEntity?) {
coEvery { getStoredParams() } returns filterParamsEntity
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
internal class FakeHomeServerCapabilitiesDataSource {
val instance = mockk<HomeServerCapabilitiesDataSource>()
fun givenHomeServerCapabilities(homeServerCapabilities: HomeServerCapabilities) {
every { instance.getHomeServerCapabilities() } returns homeServerCapabilities
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.test.fakes
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.slot
import org.amshove.kluent.shouldBeEqualTo
import org.matrix.android.sdk.internal.session.filter.Filter
import org.matrix.android.sdk.internal.session.filter.SaveFilterTask
import java.util.UUID
internal class FakeSaveFilterTask : SaveFilterTask by mockk() {
init {
coEvery { execute(any()) } returns UUID.randomUUID().toString()
}
fun verifyExecution(filter: Filter) {
val slot = slot<SaveFilterTask.Params>()
coVerify { execute(capture(slot)) }
val params = slot.captured
params.filter shouldBeEqualTo filter
}
}

View File

@ -23,8 +23,8 @@ import im.vector.app.core.notification.EnableNotificationsSettingUpdater
import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.sync.SyncUtils
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.sync.FilterService
import timber.log.Timber
import javax.inject.Inject
@ -39,7 +39,7 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
suspend fun execute(session: Session, startSyncing: Boolean = true) {
Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing")
session.open()
session.filterService().setFilter(FilterService.FilterPreset.ElementFilter)
session.filterService().setSyncFilter(SyncUtils.getSyncFilterBuilder())
if (startSyncing) {
session.startSyncing(context)
}

View File

@ -217,7 +217,7 @@ class TimelineViewModel @AssistedInject constructor(
observePowerLevel()
setupPreviewUrlObservers()
viewModelScope.launch(Dispatchers.IO) {
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, mainTimeLineOnly = true) }
}
// Inform the SDK that the room is displayed
viewModelScope.launch(Dispatchers.IO) {
@ -1103,7 +1103,8 @@ class TimelineViewModel @AssistedInject constructor(
}
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
session.coroutineScope.launch {
tryOrNull { room.readService().setReadReceipt(eventId) }
val threadId = initialState.rootThreadEventId ?: ReadService.THREAD_ID_MAIN
tryOrNull { room.readService().setReadReceipt(eventId, threadId = threadId) }
}
}
}
@ -1121,7 +1122,7 @@ class TimelineViewModel @AssistedInject constructor(
if (room == null) return
setState { copy(unreadState = UnreadState.HasNoUnread) }
viewModelScope.launch {
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.BOTH) }
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.BOTH, mainTimeLineOnly = true) }
}
}

View File

@ -74,6 +74,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import timber.log.Timber
@ -514,8 +515,7 @@ class TimelineEventController @Inject constructor(
readReceiptsItem = readReceiptsItemFactory.create(
event.eventId,
readReceipts,
callback,
partialState.isFromThreadTimeline()
callback
),
formattedDayModel = formattedDayModel,
mergedHeaderModel = mergedHeaderModel
@ -558,7 +558,7 @@ class TimelineEventController @Inject constructor(
val event = itr.previous()
timelineEventsGroups.addOrIgnore(event)
val currentReadReceipts = ArrayList(event.readReceipts).filter {
it.roomMember.userId != session.myUserId
it.roomMember.userId != session.myUserId && it.isVisibleInThisThread()
}
if (timelineEventVisibilityHelper.shouldShowEvent(
timelineEvent = event,
@ -576,6 +576,14 @@ class TimelineEventController @Inject constructor(
}
}
private fun ReadReceipt.isVisibleInThisThread(): Boolean {
return if (partialState.isFromThreadTimeline()) {
this.threadId == partialState.rootThreadEventId
} else {
this.threadId == null || this.threadId == ReadService.THREAD_ID_MAIN
}
}
private fun buildDaySeparatorItem(originServerTs: Long?): DaySeparatorItem {
val formattedDay = dateFormatter.format(originServerTs, DateFormatKind.TIMELINE_DAY_DIVIDER)
return DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)

View File

@ -30,7 +30,6 @@ class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: Av
eventId: String,
readReceipts: List<ReadReceipt>,
callback: TimelineEventController.Callback?,
isFromThreadTimeLine: Boolean
): ReadReceiptsItem? {
if (readReceipts.isEmpty()) {
return null
@ -45,7 +44,7 @@ class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: Av
.eventId(eventId)
.readReceipts(readReceiptsData)
.avatarRenderer(avatarRenderer)
.shouldHideReadReceipts(isFromThreadTimeLine)
.shouldHideReadReceipts(false)
.clickListener {
callback?.onReadReceiptsClicked(readReceiptsData)
}

View File

@ -109,7 +109,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val room = session.getRoom(roomId)
if (room != null) {
session.coroutineScope.launch {
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, mainTimeLineOnly = false) }
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.sync
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.session.filter.SyncFilterBuilder
object SyncUtils {
// Get only managed types by Element
private val listOfSupportedTimelineEventTypes = listOf(
// TODO Complete the list
EventType.MESSAGE
)
// Get only managed types by Element
private val listOfSupportedStateEventTypes = listOf(
// TODO Complete the list
EventType.STATE_ROOM_MEMBER
)
fun getSyncFilterBuilder(): SyncFilterBuilder {
return SyncFilterBuilder()
.useThreadNotifications(true)
.lazyLoadMembersForStateEvents(true)
.lazyLoadMembersForMessageEvents(true)
// .listOfSupportedStateEventTypes(listOfSupportedStateEventTypes)
// .listOfSupportedTimelineEventTypes(listOfSupportedTimelineEventTypes)
}
}

View File

@ -18,6 +18,7 @@ package im.vector.app.core.session
import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.session.clientinfo.UpdateMatrixClientInfoUseCase
import im.vector.app.features.sync.SyncUtils
import im.vector.app.test.fakes.FakeContext
import im.vector.app.test.fakes.FakeEnableNotificationsSettingUpdater
import im.vector.app.test.fakes.FakeSession
@ -36,7 +37,6 @@ import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.matrix.android.sdk.api.session.sync.FilterService
class ConfigureAndStartSessionUseCaseTest {
@ -78,7 +78,7 @@ class ConfigureAndStartSessionUseCaseTest {
// Then
verify { fakeSession.startSyncing(fakeContext.instance) }
fakeSession.fakeFilterService.verifySetFilter(FilterService.FilterPreset.ElementFilter)
fakeSession.fakeFilterService.verifySetSyncFilter(SyncUtils.getSyncFilterBuilder())
fakeSession.fakePushersService.verifyRefreshPushers()
fakeWebRtcCallManager.verifyCheckForProtocolsSupportIfNeeded()
coVerify { fakeUpdateMatrixClientInfoUseCase.execute(fakeSession) }
@ -98,7 +98,7 @@ class ConfigureAndStartSessionUseCaseTest {
// Then
verify { fakeSession.startSyncing(fakeContext.instance) }
fakeSession.fakeFilterService.verifySetFilter(FilterService.FilterPreset.ElementFilter)
fakeSession.fakeFilterService.verifySetSyncFilter(SyncUtils.getSyncFilterBuilder())
fakeSession.fakePushersService.verifyRefreshPushers()
fakeWebRtcCallManager.verifyCheckForProtocolsSupportIfNeeded()
coVerify(inverse = true) { fakeUpdateMatrixClientInfoUseCase.execute(fakeSession) }
@ -118,7 +118,7 @@ class ConfigureAndStartSessionUseCaseTest {
// Then
verify(inverse = true) { fakeSession.startSyncing(fakeContext.instance) }
fakeSession.fakeFilterService.verifySetFilter(FilterService.FilterPreset.ElementFilter)
fakeSession.fakeFilterService.verifySetSyncFilter(SyncUtils.getSyncFilterBuilder())
fakeSession.fakePushersService.verifyRefreshPushers()
fakeWebRtcCallManager.verifyCheckForProtocolsSupportIfNeeded()
coVerify { fakeUpdateMatrixClientInfoUseCase.execute(fakeSession) }

View File

@ -22,14 +22,15 @@ import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.internal.session.filter.SyncFilterBuilder
class FakeFilterService : FilterService by mockk() {
fun givenSetFilterSucceeds() {
every { setFilter(any()) } just runs
every { setSyncFilter(any()) } just runs
}
fun verifySetFilter(filterPreset: FilterService.FilterPreset) {
verify { setFilter(filterPreset) }
fun verifySetSyncFilter(filterBuilder: SyncFilterBuilder) {
verify { setSyncFilter(filterBuilder) }
}
}