diff --git a/changelog.d/7424.misc b/changelog.d/7424.misc new file mode 100644 index 0000000000..6c6673eaf0 --- /dev/null +++ b/changelog.d/7424.misc @@ -0,0 +1 @@ +Gets thread notifications from sync response diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index ff4977491f..b63656dc50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -97,6 +97,14 @@ data class RoomSummary( * Number of unread and highlighted message in this room. */ val highlightCount: Int = 0, + /** + * Number of threads with unread messages in this room. + */ + val threadNotificationCount: Int = 0, + /** + * Number of threads with highlighted messages in this room. + */ + val threadHighlightCount: Int = 0, /** * True if this room has unread messages. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt index e5ac0a39b2..7eb44c1350 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSync.kt @@ -47,6 +47,11 @@ data class RoomSync( */ @Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null, + /** + * The count of threads with unread notifications (not the total # of notifications in all threads). + */ + @Json(name = "unread_thread_notifications") val unreadThreadNotifications: Map? = null, + /** * The room summary. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt new file mode 100644 index 0000000000..70524d299a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/RoomSyncUnreadThreadNotifications.kt @@ -0,0 +1,33 @@ +/* + * 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.api.session.sync.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RoomSyncUnreadThreadNotifications( + /** + * The number of threads with unread messages that match the push notification rules. + */ + @Json(name = "notification_count") val notificationCount: Int? = null, + + /** + * The number of threads with highlighted unread messages (subset of notifications). + */ + @Json(name = "highlight_count") val highlightCount: Int? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index def0f6de7a..58c015b13b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -57,6 +57,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038 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.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -65,7 +66,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 40L, + schemaVersion = 41L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -115,5 +116,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 38) MigrateSessionTo038(realm).perform() if (oldVersion < 39) MigrateSessionTo039(realm).perform() if (oldVersion < 40) MigrateSessionTo040(realm).perform() + if (oldVersion < 41) MigrateSessionTo041(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 72b0f7a043..6e9fff78e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -61,6 +61,8 @@ internal class RoomSummaryMapper @Inject constructor( otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), highlightCount = roomSummaryEntity.highlightCount, notificationCount = roomSummaryEntity.notificationCount, + threadHighlightCount = roomSummaryEntity.threadHighlightCount, + threadNotificationCount = roomSummaryEntity.threadNotificationCount, hasUnreadMessages = roomSummaryEntity.hasUnreadMessages, tags = tags, typingUsers = typingUsers, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt new file mode 100644 index 0000000000..b58d80e50a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo041.kt @@ -0,0 +1,30 @@ +/* + * 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.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo041(realm: DynamicRealm) : RealmMigrator(realm, 41) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.THREAD_HIGHLIGHT_COUNT, Int::class.java) + ?.addField(RoomSummaryEntityFields.THREAD_NOTIFICATION_COUNT, Int::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 471bec59af..650dd3c5cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -115,6 +115,16 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var threadNotificationCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var threadHighlightCount: Int = 0 + set(value) { + if (value != field) field = value + } + var readMarkerId: String? = null set(value) { if (value != field) field = value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 30010f90fd..1b4b359916 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -110,8 +110,7 @@ internal fun RealmQuery.filterEvents(filters: TimelineEvent endGroup() } if (filters.filterUseless) { - not() - .equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) + not().equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) } if (filters.filterEdits) { not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt index 676a4f6a38..77c5649709 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt @@ -28,7 +28,7 @@ internal object FilterFactory { limit = numberOfEvents, // senders = listOf(userId), // relationSenders = userId?.let { listOf(it) }, - relationTypes = listOf(RelationType.THREAD) + relationTypes = listOf(RelationType.THREAD), ) } @@ -37,7 +37,7 @@ internal object FilterFactory { limit = numberOfEvents, containsUrl = true, types = listOf(EventType.MESSAGE), - lazyLoadMembers = true + lazyLoadMembers = true, ) } @@ -55,30 +55,23 @@ internal object FilterFactory { } fun createDefaultRoomFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true - ) + return RoomEventFilter(lazyLoadMembers = true) } fun createElementRoomFilter(): RoomEventFilter { return RoomEventFilter( - lazyLoadMembers = true + lazyLoadMembers = true, // TODO Enable this for optimization // types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList() ) } private fun createElementTimelineFilter(): RoomEventFilter? { - return null // RoomEventFilter().apply { - // TODO Enable this for optimization - // types = listOfSupportedEventTypes.toMutableList() - // } + return RoomEventFilter(enableUnreadThreadNotifications = true) } private fun createElementStateFilter(): RoomEventFilter { - return RoomEventFilter( - lazyLoadMembers = true - ) + return RoomEventFilter(lazyLoadMembers = true) } // Get only managed types by Element diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt index 220c401137..4bb66a6159 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.filter import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.sync.model.RoomSync import org.matrix.android.sdk.internal.di.MoshiProvider /** @@ -74,9 +75,15 @@ internal data class RoomEventFilter( */ @Json(name = "contains_url") val containsUrl: Boolean? = null, /** - * If true, enables lazy-loading of membership events. See Lazy-loading room members for more information. Defaults to false. + * If true, enables lazy-loading of membership events. + * See Lazy-loading room members for more information. + * Defaults to false. */ - @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null + @Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null, + /** + * If true, this will opt-in for the server to return unread threads notifications in [RoomSync]. + */ + @Json(name = "unread_thread_notifications") val enableUnreadThreadNotifications: Boolean? = null, ) { fun toJSONString(): String { @@ -92,6 +99,7 @@ internal data class RoomEventFilter( rooms != null || notRooms != null || containsUrl != null || - lazyLoadMembers != null) + lazyLoadMembers != null || + enableUnreadThreadNotifications != null) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 6979d42827..7c83a4afa7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications +import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadThreadNotifications import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -91,6 +92,7 @@ internal class RoomSummaryUpdater @Inject constructor( membership: Membership? = null, roomSummary: RoomSyncSummary? = null, unreadNotifications: RoomSyncUnreadNotifications? = null, + unreadThreadNotifications: Map? = null, updateMembers: Boolean = false, inviterId: String? = null, aggregator: SyncResponsePostTreatmentAggregator? = null @@ -111,6 +113,14 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0 roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0 + roomSummaryEntity.threadHighlightCount = unreadThreadNotifications + ?.count { (it.value.highlightCount ?: 0) > 0 } + ?: 0 + + roomSummaryEntity.threadNotificationCount = unreadThreadNotifications + ?.count { (it.value.notificationCount ?: 0) > 0 } + ?: 0 + if (membership != null) { roomSummaryEntity.membership = membership } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index ea296d379d..bc1a69769d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -140,7 +140,7 @@ internal class DefaultSyncTask @Inject constructor( executeRequest(globalErrorReceiver) { syncAPI.sync( params = requestParams, - readTimeOut = readTimeOut + readTimeOut = readTimeOut, ) } } @@ -178,7 +178,7 @@ internal class DefaultSyncTask @Inject constructor( syncRequestStateTracker.setSyncRequestState( SyncRequestState.IncrementalSyncParsing( rooms = nbRooms, - toDevice = nbToDevice + toDevice = nbToDevice, ) ) syncResponseHandler.handleResponse(syncResponse, token, null) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index a2f2251b70..2825be8291 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -287,6 +287,7 @@ internal class RoomSyncHandler @Inject constructor( Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, + roomSync.unreadThreadNotifications, updateMembers = hasRoomMember, aggregator = aggregator ) @@ -372,7 +373,8 @@ internal class RoomSyncHandler @Inject constructor( roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomTypingUsersHandler.handle(realm, roomId, null) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) - roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications, aggregator = aggregator) + roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, + roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) return roomEntity } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 82ad96d645..62f3cad5aa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail import android.net.Uri import androidx.annotation.IdRes +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -408,21 +409,40 @@ class TimelineViewModel @AssistedInject constructor( */ private fun observeLocalThreadNotifications() { if (room == null) return - room.flow() - .liveLocalUnreadThreadList() - .execute { - val threadList = it.invoke() - val isUserMentioned = threadList?.firstOrNull { threadRootEvent -> - threadRootEvent.root.threadDetails?.threadNotificationState == ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE - }?.let { true } ?: false - val numberOfLocalUnreadThreads = threadList?.size ?: 0 - copy( - threadNotificationBadgeState = ThreadNotificationBadgeState( - numberOfLocalUnreadThreads = numberOfLocalUnreadThreads, - isUserMentioned = isUserMentioned - ) - ) - } + val threadNotificationsSupported = session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreadReadReceiptsAndNotifications + if (threadNotificationsSupported) { + room.getRoomSummaryLive() + .asFlow() + .onEach { + it.getOrNull()?.let { + setState { + copy( + threadNotificationBadgeState = ThreadNotificationBadgeState( + numberOfLocalUnreadThreads = it.threadNotificationCount + it.threadHighlightCount, + isUserMentioned = it.threadHighlightCount > 0, + ) + ) + } + } + } + .launchIn(viewModelScope) + } else { + room.flow() + .liveLocalUnreadThreadList() + .execute { + val threadList = it.invoke() + val isUserMentioned = threadList?.firstOrNull { threadRootEvent -> + threadRootEvent.root.threadDetails?.threadNotificationState == ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE + } != null + val numberOfLocalUnreadThreads = threadList?.size ?: 0 + copy( + threadNotificationBadgeState = ThreadNotificationBadgeState( + numberOfLocalUnreadThreads = numberOfLocalUnreadThreads, + isUserMentioned = isUserMentioned + ) + ) + } + } } override fun handle(action: RoomDetailAction) {