mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 20:29:10 +03:00
Introduce live thread summaries using the enhanced /messages API from MSC 3440
Add capabilities to support local thread list to not supported servers
This commit is contained in:
parent
830c38f50b
commit
83088bbe5a
47 changed files with 1221 additions and 139 deletions
|
@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
|
@ -101,13 +102,18 @@ class FlowRoom(private val room: Room) {
|
|||
return room.getLiveRoomNotificationState().asFlow()
|
||||
}
|
||||
|
||||
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
|
||||
return room.getAllThreadSummariesLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
room.getAllThreadSummaries()
|
||||
}
|
||||
}
|
||||
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
|
||||
return room.getAllThreadsLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
room.getAllThreads()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> {
|
||||
return room.getMarkedThreadNotificationsLive().asFlow()
|
||||
.startWith(room.coroutineDispatchers.io) {
|
||||
|
|
|
@ -49,5 +49,6 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class AggregatedRelations(
|
||||
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
|
||||
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null
|
||||
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null,
|
||||
@Json(name = RelationType.IO_THREAD) val latestThread: LatestThreadUnsignedRelation? = null
|
||||
)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.events.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LatestThreadUnsignedRelation(
|
||||
override val limited: Boolean? = false,
|
||||
override val count: Int? = 0,
|
||||
@Json(name = "latest_event")
|
||||
val event: Event? = null,
|
||||
@Json(name = "current_user_participated")
|
||||
val isUserParticipating: Boolean? = false
|
||||
|
||||
) : UnsignedRelationInfo
|
|
@ -50,7 +50,11 @@ data class HomeServerCapabilities(
|
|||
* This capability describes the default and available room versions a server supports, and at what level of stability.
|
||||
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
|
||||
*/
|
||||
val roomVersions: RoomVersionCapabilities? = null
|
||||
val roomVersions: RoomVersionCapabilities? = null,
|
||||
/**
|
||||
* True if the home server support threading
|
||||
*/
|
||||
var canUseThreading: Boolean = false
|
||||
) {
|
||||
|
||||
enum class RoomCapabilitySupport {
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService
|
|||
import org.matrix.android.sdk.api.session.room.state.StateService
|
||||
import org.matrix.android.sdk.api.session.room.tags.TagsService
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
|
||||
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
||||
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
||||
|
@ -47,6 +48,7 @@ import org.matrix.android.sdk.api.util.Optional
|
|||
interface Room :
|
||||
TimelineService,
|
||||
ThreadsService,
|
||||
ThreadsLocalService,
|
||||
SendService,
|
||||
DraftService,
|
||||
ReadService,
|
||||
|
|
|
@ -163,13 +163,4 @@ interface RelationService {
|
|||
autoMarkdown: Boolean = false,
|
||||
formattedText: String? = null,
|
||||
eventReplied: TimelineEvent? = null): Cancelable?
|
||||
|
||||
/**
|
||||
* Get all the thread replies for the specified rootThreadEventId
|
||||
* The return list will contain the original root thread event and all the thread replies to that event
|
||||
* Note: We will use a large limit value in order to avoid using pagination until it would be 100% ready
|
||||
* from the backend
|
||||
* @param rootThreadEventId the root thread eventId
|
||||
*/
|
||||
suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean
|
||||
}
|
||||
|
|
|
@ -17,51 +17,43 @@
|
|||
package org.matrix.android.sdk.api.session.room.threads
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
|
||||
/**
|
||||
* This interface defines methods to interact with threads related features.
|
||||
* It's implemented at the room level within the main timeline.
|
||||
* This interface defines methods to interact with thread related features.
|
||||
* It's the dynamic threads implementation and the homeserver must return
|
||||
* a capability entry for threads. If the server do not support m.thread
|
||||
* then [ThreadsLocalService] should be used instead
|
||||
*/
|
||||
interface ThreadsService {
|
||||
|
||||
/**
|
||||
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
|
||||
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level
|
||||
*/
|
||||
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
|
||||
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>
|
||||
|
||||
/**
|
||||
* Returns a list of all the thread root TimelineEvents that exists at the room level
|
||||
* Returns a list of all the [ThreadSummary] that exists at the room level
|
||||
*/
|
||||
fun getAllThreads(): List<TimelineEvent>
|
||||
fun getAllThreadSummaries(): List<ThreadSummary>
|
||||
|
||||
/**
|
||||
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
|
||||
*/
|
||||
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>
|
||||
|
||||
/**
|
||||
* Returns a list of all the marked unread threads that exists at the room level
|
||||
*/
|
||||
fun getMarkedThreadNotifications(): List<TimelineEvent>
|
||||
|
||||
/**
|
||||
* Returns whether or not the current user is participating in the thread
|
||||
* @param rootThreadEventId the eventId of the current thread
|
||||
*/
|
||||
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
|
||||
|
||||
/**
|
||||
* Enhance the provided root thread TimelineEvent [List] by adding the latest
|
||||
* Enhance the provided ThreadSummary[List] by adding the latest
|
||||
* message edition for that thread
|
||||
* @return the enhanced [List] with edited updates
|
||||
*/
|
||||
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>
|
||||
fun enhanceWithEditions(threads: List<ThreadSummary>): List<ThreadSummary>
|
||||
|
||||
/**
|
||||
* Marks the current thread as read in local DB.
|
||||
* note: read receipts within threads are not yet supported with the API
|
||||
* @param rootThreadEventId the root eventId of the current thread
|
||||
* Fetch all thread replies for the specified thread using the /relations api
|
||||
* @param rootThreadEventId the root thread eventId
|
||||
* @param from defines the token that will fetch from that position
|
||||
* @param limit defines the number of max results the api will respond with
|
||||
*/
|
||||
suspend fun markThreadAsRead(rootThreadEventId: String)
|
||||
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
|
||||
|
||||
/**
|
||||
* Fetch all thread summaries for the current room using the enhanced /messages api
|
||||
*/
|
||||
suspend fun fetchThreadSummaries()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.room.threads.local
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
|
||||
/**
|
||||
* This interface defines methods to interact with thread related features.
|
||||
* It's the local threads implementation and assumes that the homeserver
|
||||
* do not support threads
|
||||
*/
|
||||
interface ThreadsLocalService {
|
||||
|
||||
/**
|
||||
* Returns a [LiveData] list of all the thread root TimelineEvents that exists at the room level
|
||||
*/
|
||||
fun getAllThreadsLive(): LiveData<List<TimelineEvent>>
|
||||
|
||||
/**
|
||||
* Returns a list of all the thread root TimelineEvents that exists at the room level
|
||||
*/
|
||||
fun getAllThreads(): List<TimelineEvent>
|
||||
|
||||
/**
|
||||
* Returns a [LiveData] list of all the marked unread threads that exists at the room level
|
||||
*/
|
||||
fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>>
|
||||
|
||||
/**
|
||||
* Returns a list of all the marked unread threads that exists at the room level
|
||||
*/
|
||||
fun getMarkedThreadNotifications(): List<TimelineEvent>
|
||||
|
||||
/**
|
||||
* Returns whether or not the current user is participating in the thread
|
||||
* @param rootThreadEventId the eventId of the current thread
|
||||
*/
|
||||
fun isUserParticipatingInThread(rootThreadEventId: String): Boolean
|
||||
|
||||
/**
|
||||
* Enhance the provided root thread TimelineEvent [List] by adding the latest
|
||||
* message edition for that thread
|
||||
* @return the enhanced [List] with edited updates
|
||||
*/
|
||||
fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent>
|
||||
|
||||
/**
|
||||
* Marks the current thread as read in local DB.
|
||||
* note: read receipts within threads are not yet supported with the API
|
||||
* @param rootThreadEventId the root eventId of the current thread
|
||||
*/
|
||||
suspend fun markThreadAsRead(rootThreadEventId: String)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.room.threads.model
|
||||
|
||||
data class ThreadEditions(var rootThreadEdition: String? = null,
|
||||
var latestThreadEdition: String? = null)
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.room.threads.model
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
|
||||
/**
|
||||
* The main thread Summary model, mainly used to display the thread list
|
||||
*/
|
||||
data class ThreadSummary(val roomId: String,
|
||||
val rootEvent: Event?,
|
||||
val latestEvent: Event?,
|
||||
val rootEventId: String,
|
||||
val rootThreadSenderInfo: SenderInfo,
|
||||
val latestThreadSenderInfo: SenderInfo,
|
||||
val isUserParticipating: Boolean,
|
||||
val numberOfThreads: Int,
|
||||
val threadEditions: ThreadEditions = ThreadEditions())
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.room.threads.model
|
||||
|
||||
enum class ThreadSummaryUpdateType {
|
||||
REPLACE,
|
||||
ADD
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.matrix.android.sdk.api.util
|
||||
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -178,6 +179,8 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
|
|||
|
||||
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
|
||||
|
||||
fun SenderInfo.toMatrixItemOrNull() = tryOrNull { MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl) }
|
||||
|
||||
fun SpaceChildInfo.toMatrixItem() = if (roomType == RoomType.SPACE) {
|
||||
MatrixItem.SpaceItem(childRoomId, name ?: canonicalAlias, avatarUrl)
|
||||
} else {
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
|
|||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
override fun equals(other: Any?) = other is RealmSessionStoreMigration
|
||||
override fun hashCode() = 1000
|
||||
|
||||
val schemaVersion = 25L
|
||||
val schemaVersion = 27L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
|
@ -89,5 +90,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
if (oldVersion < 24) MigrateSessionTo024(realm).perform()
|
||||
if (oldVersion < 25) MigrateSessionTo025(realm).perform()
|
||||
if (oldVersion < 26) MigrateSessionTo026(realm).perform()
|
||||
if (oldVersion < 27) MigrateSessionTo027(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
|||
return timelineEventEntity
|
||||
}
|
||||
|
||||
private fun computeIsUnique(
|
||||
fun computeIsUnique(
|
||||
realm: Realm,
|
||||
roomId: String,
|
||||
isLastForward: Boolean,
|
||||
|
|
|
@ -18,9 +18,16 @@ package org.matrix.android.sdk.internal.database.helper
|
|||
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
|
||||
internal fun RoomEntity.addIfNecessary(chunkEntity: ChunkEntity) {
|
||||
if (!chunks.contains(chunkEntity)) {
|
||||
chunks.add(chunkEntity)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RoomEntity.addIfNecessary(threadSummary: ThreadSummaryEntity) {
|
||||
if (!threadSummaries.contains(threadSummary)) {
|
||||
threadSummaries.add(threadSummary)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
|||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
||||
|
||||
private typealias ThreadSummary = Pair<Int, TimelineEventEntity>?
|
||||
private typealias Summary = Pair<Int, TimelineEventEntity>?
|
||||
|
||||
/**
|
||||
* Finds the root thread event and update it with the latest message summary along with the number
|
||||
|
@ -93,7 +93,7 @@ internal fun EventEntity.markEventAsRoot(
|
|||
* @param rootThreadEventId The root eventId that will find the number of threads
|
||||
* @return A ThreadSummary containing the counted threads and the latest event message
|
||||
*/
|
||||
internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): ThreadSummary {
|
||||
internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary {
|
||||
// Number of messages
|
||||
val messages = TimelineEventEntity
|
||||
.whereRoomId(realm, roomId = roomId)
|
||||
|
@ -124,7 +124,7 @@ internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId:
|
|||
|
||||
result ?: return null
|
||||
|
||||
return ThreadSummary(messages, result)
|
||||
return Summary(messages, result)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* 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.helper
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.createObject
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
||||
internal fun ThreadSummaryEntity.updateThreadSummary(
|
||||
rootThreadEventEntity: EventEntity,
|
||||
numberOfThreads: Int?,
|
||||
latestThreadEventEntity: EventEntity?,
|
||||
isUserParticipating: Boolean,
|
||||
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>) {
|
||||
updateThreadSummaryRootEvent(rootThreadEventEntity, roomMemberContentsByUser)
|
||||
updateThreadSummaryLatestEvent(latestThreadEventEntity, roomMemberContentsByUser)
|
||||
|
||||
// Update latest event
|
||||
// latestThreadEventEntity?.toTimelineEventEntity(roomMemberContentsByUser)?.let {
|
||||
// Timber.i("###THREADS FetchThreadSummariesTask ThreadSummaryEntity updated latest event:${it.eventId} !")
|
||||
// this.eventEntity?.threadSummaryLatestMessage = it
|
||||
// }
|
||||
|
||||
// Update number of threads
|
||||
this.isUserParticipating = isUserParticipating
|
||||
numberOfThreads?.let {
|
||||
// Update only when there is an actual value
|
||||
this.numberOfThreads = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the root thread event properties
|
||||
*/
|
||||
internal fun ThreadSummaryEntity.updateThreadSummaryRootEvent(
|
||||
rootThreadEventEntity: EventEntity,
|
||||
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>
|
||||
) {
|
||||
val roomId = rootThreadEventEntity.roomId
|
||||
val rootThreadRoomMemberContent = roomMemberContentsByUser[rootThreadEventEntity.sender ?: ""]
|
||||
this.rootThreadEventEntity = rootThreadEventEntity
|
||||
this.rootThreadSenderAvatar = rootThreadRoomMemberContent?.avatarUrl
|
||||
this.rootThreadSenderName = rootThreadRoomMemberContent?.displayName
|
||||
this.rootThreadIsUniqueDisplayName = if (rootThreadRoomMemberContent?.displayName != null) {
|
||||
computeIsUnique(realm, roomId, false, rootThreadRoomMemberContent, roomMemberContentsByUser)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the latest thread event properties
|
||||
*/
|
||||
internal fun ThreadSummaryEntity.updateThreadSummaryLatestEvent(
|
||||
latestThreadEventEntity: EventEntity?,
|
||||
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>
|
||||
) {
|
||||
val roomId = latestThreadEventEntity?.roomId ?: return
|
||||
val latestThreadRoomMemberContent = roomMemberContentsByUser[latestThreadEventEntity.sender ?: ""]
|
||||
this.latestThreadEventEntity = latestThreadEventEntity
|
||||
this.latestThreadSenderAvatar = latestThreadRoomMemberContent?.avatarUrl
|
||||
this.latestThreadSenderName = latestThreadRoomMemberContent?.displayName
|
||||
this.latestThreadIsUniqueDisplayName = if (latestThreadRoomMemberContent?.displayName != null) {
|
||||
computeIsUnique(realm, roomId, false, latestThreadRoomMemberContent, roomMemberContentsByUser)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap<String, RoomMemberContent?>): TimelineEventEntity {
|
||||
val roomId = roomId
|
||||
val eventId = eventId
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val senderId = sender ?: ""
|
||||
|
||||
val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply {
|
||||
this.localId = localId
|
||||
this.root = this@toTimelineEventEntity
|
||||
this.eventId = eventId
|
||||
this.roomId = roomId
|
||||
this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||
?.also { it.cleanUp(sender) }
|
||||
this.ownedByThreadChunk = true // To skip it from the original event flow
|
||||
val roomMemberContent = roomMemberContentsByUser[senderId]
|
||||
this.senderAvatar = roomMemberContent?.avatarUrl
|
||||
this.senderName = roomMemberContent?.displayName
|
||||
isUniqueDisplayName = if (roomMemberContent?.displayName != null) {
|
||||
computeIsUnique(realm, roomId, false, roomMemberContent, roomMemberContentsByUser)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
return timelineEventEntity
|
||||
}
|
||||
|
||||
internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||
threadSummaryType: ThreadSummaryUpdateType,
|
||||
realm: Realm,
|
||||
roomId: String,
|
||||
threadEventEntity: EventEntity? = null,
|
||||
rootThreadEvent: Event? = null,
|
||||
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>,
|
||||
roomEntity: RoomEntity,
|
||||
userId: String,
|
||||
cryptoService: CryptoService? = null
|
||||
) {
|
||||
when (threadSummaryType) {
|
||||
ThreadSummaryUpdateType.REPLACE -> {
|
||||
rootThreadEvent?.eventId ?: return
|
||||
rootThreadEvent.senderId ?: return
|
||||
|
||||
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return
|
||||
|
||||
// Something is wrong with the server return
|
||||
if (numberOfThreads <= 0) return
|
||||
|
||||
val threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEvent.eventId).also {
|
||||
Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
|
||||
}
|
||||
|
||||
val rootThreadEventEntity = createEventEntity(roomId, rootThreadEvent, realm).also {
|
||||
decryptIfNeeded(cryptoService, it, roomId)
|
||||
}
|
||||
val latestThreadEventEntity = createLatestEventEntity(roomId, rootThreadEvent, roomMemberContentsByUser, realm)?.also {
|
||||
decryptIfNeeded(cryptoService, it, roomId)
|
||||
}
|
||||
val isUserParticipating = rootThreadEvent.unsignedData.relations.latestThread.isUserParticipating == true || rootThreadEvent.senderId == userId
|
||||
roomMemberContentsByUser.addSenderState(realm, roomId, rootThreadEvent.senderId)
|
||||
threadSummary.updateThreadSummary(
|
||||
rootThreadEventEntity = rootThreadEventEntity,
|
||||
numberOfThreads = numberOfThreads,
|
||||
latestThreadEventEntity = latestThreadEventEntity,
|
||||
isUserParticipating = isUserParticipating,
|
||||
roomMemberContentsByUser = roomMemberContentsByUser
|
||||
)
|
||||
|
||||
roomEntity.addIfNecessary(threadSummary)
|
||||
}
|
||||
ThreadSummaryUpdateType.ADD -> {
|
||||
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return
|
||||
Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId")
|
||||
|
||||
val threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
|
||||
if (threadSummary != null) {
|
||||
// ThreadSummary exists so lets add the latest event
|
||||
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId exists, lets update latest thread event.")
|
||||
threadSummary.updateThreadSummaryLatestEvent(threadEventEntity, roomMemberContentsByUser)
|
||||
threadSummary.numberOfThreads++
|
||||
if (threadEventEntity.sender == userId) {
|
||||
threadSummary.isUserParticipating = true
|
||||
}
|
||||
} else {
|
||||
// ThreadSummary do not exists lets try to create one
|
||||
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId do not exists, lets try to create one")
|
||||
threadEventEntity.findRootThreadEvent()?.let { rootThreadEventEntity ->
|
||||
// Root thread event entity exists so lets create a new record
|
||||
ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).let {
|
||||
it.updateThreadSummary(
|
||||
rootThreadEventEntity = rootThreadEventEntity,
|
||||
numberOfThreads = 1,
|
||||
latestThreadEventEntity = threadEventEntity,
|
||||
isUserParticipating = threadEventEntity.sender == userId,
|
||||
roomMemberContentsByUser = roomMemberContentsByUser
|
||||
)
|
||||
roomEntity.addIfNecessary(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
|
||||
cryptoService ?: return
|
||||
val event = eventEntity.asDomain()
|
||||
if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
|
||||
try {
|
||||
Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
|
||||
// Event from sync does not have roomId, so add it to the event first
|
||||
val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
// Save decryption result, to not decrypt every time we enter the thread list
|
||||
eventEntity.setDecryptionResult(result)
|
||||
} catch (e: MXCryptoError) {
|
||||
if (e is MXCryptoError.Base) {
|
||||
event.mCryptoError = e.errorType
|
||||
event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request decryption
|
||||
*/
|
||||
private fun requestDecryption(eventDecryptor: TimelineEventDecryptor?, event: Event?) {
|
||||
eventDecryptor ?: return
|
||||
event ?: return
|
||||
if (event.isEncrypted() &&
|
||||
event.mxDecryptionResult == null && event.eventId != null) {
|
||||
Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
|
||||
|
||||
eventDecryptor.requestDecryption(TimelineEventDecryptor.DecryptionRequest(event, UUID.randomUUID().toString()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we don't have any new state on this user, get it from db
|
||||
*/
|
||||
private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roomId: String, senderId: String) {
|
||||
getOrPut(senderId) {
|
||||
CurrentStateEventEntity
|
||||
.getOrNull(realm, roomId, senderId, EventType.STATE_ROOM_MEMBER)
|
||||
?.root?.asDomain()
|
||||
?.getFixedRoomMemberContent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EventEntity for the root thread event or get an existing one
|
||||
*/
|
||||
private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
|
||||
val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
|
||||
return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EventEntity for the latest thread event or get an existing one. Also update the user room member
|
||||
* state
|
||||
*/
|
||||
private fun createLatestEventEntity(roomId: String, rootThreadEvent: Event, roomMemberContentsByUser: HashMap<String, RoomMemberContent?>, realm: Realm): EventEntity? {
|
||||
return getLatestEvent(rootThreadEvent)?.let {
|
||||
it.senderId?.let { senderId ->
|
||||
roomMemberContentsByUser.addSenderState(realm, roomId, senderId)
|
||||
}
|
||||
createEventEntity(roomId, it, realm)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned the latest event message, if any
|
||||
*/
|
||||
private fun getLatestEvent(rootThreadEvent: Event): Event? {
|
||||
return rootThreadEvent.unsignedData?.relations?.latestThread?.event
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all ThreadSummaryEntity for the specified roomId, sorted by origin server
|
||||
* note: Sorting cannot be provided by server, so we have to use that unstable property
|
||||
* @param roomId The id of the room
|
||||
*/
|
||||
internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<ThreadSummaryEntity> =
|
||||
ThreadSummaryEntity
|
||||
.where(realm, roomId = roomId)
|
||||
.sort(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.ORIGIN_SERVER_TS, Sort.DESCENDING)
|
||||
|
||||
/**
|
||||
* Enhance each [ThreadSummary] root and latest event with the equivalent decrypted text edition/replacement
|
||||
*/
|
||||
internal fun List<ThreadSummary>.enhanceWithEditions(realm: Realm, roomId: String): List<ThreadSummary> =
|
||||
this.map {
|
||||
it.addEditionIfNeeded(realm, roomId, true)
|
||||
it.addEditionIfNeeded(realm, roomId, false)
|
||||
it
|
||||
}
|
||||
|
||||
private fun ThreadSummary.addEditionIfNeeded(realm: Realm, roomId: String, enhanceRoot: Boolean) {
|
||||
val eventId = if (enhanceRoot) rootEventId else latestEvent?.eventId ?: return
|
||||
EventAnnotationsSummaryEntity
|
||||
.where(realm, roomId, eventId)
|
||||
.findFirst()
|
||||
?.editSummary
|
||||
?.editions
|
||||
?.lastOrNull()
|
||||
?.eventId
|
||||
?.let { editedEventId ->
|
||||
TimelineEventEntity.where(realm, roomId, eventId = editedEventId).findFirst()?.let { editedEvent ->
|
||||
if (enhanceRoot) {
|
||||
threadEditions.rootThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary() ?: "(edited)"
|
||||
} else {
|
||||
threadEditions.latestThreadEdition = editedEvent.root?.asDomain()?.getDecryptedTextSummary() ?: "(edited)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,8 @@ internal object HomeServerCapabilitiesMapper {
|
|||
maxUploadFileSize = entity.maxUploadFileSize,
|
||||
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
|
||||
roomVersions = mapRoomVersion(entity.roomVersionsJson)
|
||||
roomVersions = mapRoomVersion(entity.roomVersionsJson),
|
||||
canUseThreading = false // entity.canUseThreading
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ThreadSummaryMapper @Inject constructor() {
|
||||
|
||||
fun map(threadSummary: ThreadSummaryEntity): ThreadSummary {
|
||||
return ThreadSummary(
|
||||
roomId = threadSummary.room?.firstOrNull()?.roomId.orEmpty(),
|
||||
rootEvent = threadSummary.rootThreadEventEntity?.asDomain(),
|
||||
latestEvent = threadSummary.latestThreadEventEntity?.asDomain(),
|
||||
rootEventId = threadSummary.rootThreadEventId,
|
||||
rootThreadSenderInfo = SenderInfo(
|
||||
userId = threadSummary.rootThreadEventEntity?.sender ?: "",
|
||||
displayName = threadSummary.rootThreadSenderName,
|
||||
isUniqueDisplayName = threadSummary.rootThreadIsUniqueDisplayName,
|
||||
avatarUrl = threadSummary.rootThreadSenderAvatar
|
||||
),
|
||||
latestThreadSenderInfo = SenderInfo(
|
||||
userId = threadSummary.latestThreadEventEntity?.sender ?: "",
|
||||
displayName = threadSummary.latestThreadSenderName,
|
||||
isUniqueDisplayName = threadSummary.latestThreadIsUniqueDisplayName,
|
||||
avatarUrl = threadSummary.latestThreadSenderAvatar
|
||||
),
|
||||
isUserParticipating = threadSummary.isUserParticipating,
|
||||
numberOfThreads = threadSummary.numberOfThreads
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 io.realm.FieldAttribute
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
val eventEntity = realm.schema.get("EventEntity") ?: return
|
||||
val threadSummaryEntity = realm.schema.create("ThreadSummaryEntity")
|
||||
.addField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
|
||||
.addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_NAME, String::class.java)
|
||||
.addField(ThreadSummaryEntityFields.ROOT_THREAD_SENDER_AVATAR, String::class.java)
|
||||
.addField(ThreadSummaryEntityFields.ROOT_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java)
|
||||
.addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_NAME, String::class.java)
|
||||
.addField(ThreadSummaryEntityFields.LATEST_THREAD_SENDER_AVATAR, String::class.java)
|
||||
.addField(ThreadSummaryEntityFields.LATEST_THREAD_IS_UNIQUE_DISPLAY_NAME, String::class.java)
|
||||
.addField(ThreadSummaryEntityFields.NUMBER_OF_THREADS, Int::class.java)
|
||||
.addField(ThreadSummaryEntityFields.IS_USER_PARTICIPATING, Boolean::class.java)
|
||||
.addRealmObjectField(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ENTITY.`$`, eventEntity)
|
||||
.addRealmObjectField(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.`$`, eventEntity)
|
||||
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addRealmListField(RoomEntityFields.THREAD_SUMMARIES.`$`, threadSummaryEntity)
|
||||
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addRealmListField(HomeServerCapabilitiesEntityFields.CAN_USE_THREADING, Boolean::class.java)
|
||||
}
|
||||
}
|
|
@ -50,13 +50,18 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
|
|||
companion object
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRoot: Boolean) {
|
||||
internal fun ChunkEntity.deleteOnCascade(
|
||||
deleteStateEvents: Boolean,
|
||||
canDeleteRoot: Boolean) {
|
||||
assertIsManaged()
|
||||
if (deleteStateEvents) {
|
||||
stateEvents.deleteAllFromRealm()
|
||||
}
|
||||
timelineEvents.clearWith {
|
||||
val deleteRoot = canDeleteRoot && (it.root?.stateKey == null || deleteStateEvents)
|
||||
if (deleteRoot) {
|
||||
room?.firstOrNull()?.removeThreadSummaryIfNeeded(it.eventId)
|
||||
}
|
||||
it.deleteOnCascade(deleteRoot)
|
||||
}
|
||||
deleteFromRealm()
|
||||
|
|
|
@ -34,14 +34,14 @@ internal open class EventEntity(@Index var eventId: String = "",
|
|||
@Index var stateKey: String? = null,
|
||||
var originServerTs: Long? = null,
|
||||
@Index var sender: String? = null,
|
||||
// Can contain a serialized MatrixError
|
||||
// Can contain a serialized MatrixError
|
||||
var sendStateDetails: String? = null,
|
||||
var age: Long? = 0,
|
||||
var unsignedData: String? = null,
|
||||
var redacts: String? = null,
|
||||
var decryptionResultJson: String? = null,
|
||||
var ageLocalTs: Long? = null,
|
||||
// Thread related, no need to create a new Entity for performance
|
||||
// Thread related, no need to create a new Entity for performance
|
||||
@Index var isRootThread: Boolean = false,
|
||||
@Index var rootThreadEventId: String? = null,
|
||||
var numberOfThreads: Int = 0,
|
||||
|
|
|
@ -28,7 +28,8 @@ internal open class HomeServerCapabilitiesEntity(
|
|||
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||
var lastVersionIdentityServerSupported: Boolean = false,
|
||||
var defaultIdentityServerUrl: String? = null,
|
||||
var lastUpdatedTimestamp: Long = 0L
|
||||
var lastUpdatedTimestamp: Long = 0L,
|
||||
var canUseThreading: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
|
|
|
@ -20,10 +20,14 @@ import io.realm.RealmList
|
|||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.findRootOrLatest
|
||||
import org.matrix.android.sdk.internal.extensions.assertIsManaged
|
||||
|
||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||
var threadSummaries: RealmList<ThreadSummaryEntity> = RealmList(),
|
||||
var accountData: RealmList<RoomAccountDataEntity> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
|
@ -46,3 +50,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
|||
}
|
||||
companion object
|
||||
}
|
||||
internal fun RoomEntity.removeThreadSummaryIfNeeded(eventId: String) {
|
||||
assertIsManaged()
|
||||
threadSummaries.findRootOrLatest(eventId)?.let {
|
||||
threadSummaries.remove(it)
|
||||
it.deleteFromRealm()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
|
|||
|
||||
import io.realm.annotations.RealmModule
|
||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
|
||||
/**
|
||||
* Realm module for Session
|
||||
|
@ -66,6 +67,7 @@ import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntit
|
|||
RoomAccountDataEntity::class,
|
||||
SpaceChildSummaryEntity::class,
|
||||
SpaceParentSummaryEntity::class,
|
||||
UserPresenceEntity::class
|
||||
UserPresenceEntity::class,
|
||||
ThreadSummaryEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.model.threads
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.RealmResults
|
||||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.LinkingObjects
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
|
||||
internal open class ThreadSummaryEntity(@Index var rootThreadEventId: String = "",
|
||||
var rootThreadEventEntity: EventEntity? = null,
|
||||
var latestThreadEventEntity: EventEntity? = null,
|
||||
var rootThreadSenderName: String? = null,
|
||||
var latestThreadSenderName: String? = null,
|
||||
var rootThreadSenderAvatar: String? = null,
|
||||
var latestThreadSenderAvatar: String? = null,
|
||||
var rootThreadIsUniqueDisplayName: Boolean = false,
|
||||
var isUserParticipating: Boolean = false,
|
||||
var latestThreadIsUniqueDisplayName: Boolean = false,
|
||||
var numberOfThreads: Int = 0
|
||||
) : RealmObject() {
|
||||
|
||||
@LinkingObjects("threadSummaries")
|
||||
val room: RealmResults<RoomEntity>? = null
|
||||
|
||||
companion object
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.query
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||
|
||||
internal fun ThreadSummaryEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<ThreadSummaryEntity> {
|
||||
return realm.where<ThreadSummaryEntity>()
|
||||
.equalTo(ThreadSummaryEntityFields.ROOM.ROOM_ID, roomId)
|
||||
}
|
||||
|
||||
internal fun ThreadSummaryEntity.Companion.where(realm: Realm, roomId: String, rootThreadEventId: String): RealmQuery<ThreadSummaryEntity> {
|
||||
return where(realm, roomId)
|
||||
.equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
||||
}
|
||||
|
||||
internal fun ThreadSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity {
|
||||
return where(realm, roomId, rootThreadEventId).findFirst() ?: realm.createObject<ThreadSummaryEntity>().apply {
|
||||
this.rootThreadEventId = rootThreadEventId
|
||||
}
|
||||
}
|
||||
internal fun ThreadSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity? {
|
||||
return where(realm, roomId, rootThreadEventId).findFirst()
|
||||
}
|
||||
internal fun RealmList<ThreadSummaryEntity>.find(rootThreadEventId: String): ThreadSummaryEntity? {
|
||||
return this.where()
|
||||
.equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmList<ThreadSummaryEntity>.findRootOrLatest(eventId: String): ThreadSummaryEntity? {
|
||||
return this.where()
|
||||
.beginGroup()
|
||||
.equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, eventId)
|
||||
.or()
|
||||
.equalTo(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.EVENT_ID, eventId)
|
||||
.endGroup()
|
||||
.findFirst()
|
||||
}
|
|
@ -17,9 +17,21 @@
|
|||
package org.matrix.android.sdk.internal.session.filter
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||
import timber.log.Timber
|
||||
|
||||
internal object FilterFactory {
|
||||
|
||||
fun createThreadsFilter(numberOfEvents: Int, userId: String?): RoomEventFilter {
|
||||
Timber.i("$userId")
|
||||
return RoomEventFilter(
|
||||
limit = numberOfEvents,
|
||||
// senders = listOf(userId),
|
||||
// relationSenders = userId?.let { listOf(it) },
|
||||
relationTypes = listOf(RelationType.IO_THREAD)
|
||||
)
|
||||
}
|
||||
|
||||
fun createUploadsFilter(numberOfEvents: Int): RoomEventFilter {
|
||||
return RoomEventFilter(
|
||||
limit = numberOfEvents,
|
||||
|
@ -58,8 +70,8 @@ internal object FilterFactory {
|
|||
|
||||
private fun createElementTimelineFilter(): RoomEventFilter? {
|
||||
return null // RoomEventFilter().apply {
|
||||
// TODO Enable this for optimization
|
||||
// types = listOfSupportedEventTypes.toMutableList()
|
||||
// TODO Enable this for optimization
|
||||
// types = listOfSupportedEventTypes.toMutableList()
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
|
@ -52,12 +52,15 @@ data class RoomEventFilter(
|
|||
* A list of relation types which must be exist pointing to the event being filtered.
|
||||
* If this list is absent then no filtering is done on relation types.
|
||||
*/
|
||||
@Json(name = "relation_types") val relationTypes: List<String>? = null,
|
||||
// @Json(name = "relation_types") val relationTypes: List<String>? = null,
|
||||
@Json(name = "io.element.relation_types") val relationTypes: List<String>? = null, // To be replaced with the above line after the release
|
||||
/**
|
||||
* A list of senders of relations which must exist pointing to the event being filtered.
|
||||
* If this list is absent then no filtering is done on relation types.
|
||||
*/
|
||||
@Json(name = "relation_senders") val relationSenders: List<String>? = null,
|
||||
// @Json(name = "relation_senders") val relationSenders: List<String>? = null,
|
||||
@Json(name = "io.element.relation_senders") val relationSenders: List<String>? = null, // To be replaced with the above line after the release
|
||||
|
||||
/**
|
||||
* A list of room IDs to include. If this list is absent then all rooms are included.
|
||||
*/
|
||||
|
|
|
@ -65,7 +65,13 @@ internal data class Capabilities(
|
|||
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
|
||||
*/
|
||||
@Json(name = "m.room_versions")
|
||||
val roomVersions: RoomVersions? = null
|
||||
val roomVersions: RoomVersions? = null,
|
||||
/**
|
||||
* Capability to indicate if the server supports MSC3440 Threading
|
||||
* True if the user can use m.thread relation, false otherwise
|
||||
*/
|
||||
@Json(name = "m.thread")
|
||||
val threads: BooleanCapability? = null
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
@ -121,6 +121,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let {
|
||||
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
|
||||
}
|
||||
homeServerCapabilitiesEntity.canUseThreading = capabilities?.threads?.enabled.orTrue()
|
||||
}
|
||||
|
||||
if (getMediaConfigResult != null) {
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.send.SendService
|
|||
import org.matrix.android.sdk.api.session.room.state.StateService
|
||||
import org.matrix.android.sdk.api.session.room.tags.TagsService
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
|
||||
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||
import org.matrix.android.sdk.api.session.room.typing.TypingService
|
||||
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
|
||||
|
@ -56,6 +57,7 @@ internal class DefaultRoom(override val roomId: String,
|
|||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val timelineService: TimelineService,
|
||||
private val threadsService: ThreadsService,
|
||||
private val threadsLocalService: ThreadsLocalService,
|
||||
private val sendService: SendService,
|
||||
private val draftService: DraftService,
|
||||
private val stateService: StateService,
|
||||
|
@ -80,6 +82,7 @@ internal class DefaultRoom(override val roomId: String,
|
|||
Room,
|
||||
TimelineService by timelineService,
|
||||
ThreadsService by threadsService,
|
||||
ThreadsLocalService by threadsLocalService,
|
||||
SendService by sendService,
|
||||
DraftService by draftService,
|
||||
StateService by stateService,
|
||||
|
|
|
@ -197,6 +197,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
handleReaction(realm, event, roomId, isLocalEcho)
|
||||
}
|
||||
}
|
||||
// TODO is that ok??
|
||||
// else if (event.unsignedData?.relations?.annotations != null) {
|
||||
// Timber.v("###REACTION e2e Aggregation in room $roomId for event ${event.eventId}")
|
||||
// handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
|
||||
// // EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
|
||||
// // ?.let {
|
||||
// // TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findAll()
|
||||
// // ?.forEach { tet -> tet.annotations = it }
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
EventType.REDACTION -> {
|
||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||
|
@ -244,7 +254,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
}
|
||||
|
||||
// OPT OUT serer aggregation until API mature enough
|
||||
private val SHOULD_HANDLE_SERVER_AGREGGATION = false
|
||||
private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e
|
||||
|
||||
private fun handleReplace(realm: Realm,
|
||||
event: Event,
|
||||
|
@ -346,6 +356,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
|
||||
/**
|
||||
* Check if the edition is on the latest thread event, and update it accordingly
|
||||
* @param editedEvent The event that will be changed
|
||||
* @param replaceEvent The new event
|
||||
*/
|
||||
private fun handleThreadSummaryEdition(editedEvent: EventEntity?,
|
||||
replaceEvent: TimelineEventEntity?,
|
||||
|
|
|
@ -86,7 +86,7 @@ internal interface RoomAPI {
|
|||
suspend fun getRoomMessagesFrom(@Path("roomId") roomId: String,
|
||||
@Query("from") from: String,
|
||||
@Query("dir") dir: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("limit") limit: Int?,
|
||||
@Query("filter") filter: String?
|
||||
): PaginationResponse
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
|||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
|
||||
import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService
|
||||
import org.matrix.android.sdk.internal.session.room.threads.local.DefaultThreadsLocalService
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
|
||||
import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
|
||||
|
@ -52,6 +53,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
|||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||
private val timelineServiceFactory: DefaultTimelineService.Factory,
|
||||
private val threadsServiceFactory: DefaultThreadsService.Factory,
|
||||
private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
|
||||
private val sendServiceFactory: DefaultSendService.Factory,
|
||||
private val draftServiceFactory: DefaultDraftService.Factory,
|
||||
private val stateServiceFactory: DefaultStateService.Factory,
|
||||
|
@ -79,6 +81,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
|||
roomSummaryDataSource = roomSummaryDataSource,
|
||||
timelineService = timelineServiceFactory.create(roomId),
|
||||
threadsService = threadsServiceFactory.create(roomId),
|
||||
threadsLocalService = threadsLocalServiceFactory.create(roomId),
|
||||
sendService = sendServiceFactory.create(roomId),
|
||||
draftService = draftServiceFactory.create(roomId),
|
||||
stateService = stateServiceFactory.create(roomId),
|
||||
|
|
|
@ -77,7 +77,9 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultUpdateQuickR
|
|||
import org.matrix.android.sdk.internal.session.room.relation.FetchEditHistoryTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.FindReactionEventForUndoTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.UpdateQuickReactionTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadSummariesTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportContentTask
|
||||
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentTask
|
||||
|
@ -294,4 +296,7 @@ internal abstract class RoomModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindFetchThreadTimelineTask(task: DefaultFetchThreadTimelineTask): FetchThreadTimelineTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindFetchThreadSummariesTask(task: DefaultFetchThreadSummariesTask): FetchThreadSummariesTask
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
|
|||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||
|
@ -51,7 +50,6 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
@SessionDatabase private val monarchy: Monarchy
|
||||
) : RelationService {
|
||||
|
@ -205,16 +203,6 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
|
||||
}
|
||||
|
||||
override suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean {
|
||||
fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(
|
||||
roomId,
|
||||
rootThreadEventId,
|
||||
null,
|
||||
10
|
||||
))
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the event in database as a local echo.
|
||||
* SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room.
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.session.room.relation.threads
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||
import org.matrix.android.sdk.internal.database.helper.createOrUpdate
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.filter.FilterFactory
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/***
|
||||
* This class is responsible to Fetch all the thread in the current room,
|
||||
* To fetch all threads in a room, the /messages API is used with newly added filtering options.
|
||||
*/
|
||||
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, DefaultFetchThreadSummariesTask.Result> {
|
||||
data class Params(
|
||||
val roomId: String,
|
||||
val from: String = "",
|
||||
val limit: Int = 100,
|
||||
val isUserParticipating: Boolean = true
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultFetchThreadSummariesTask @Inject constructor(
|
||||
private val roomAPI: RoomAPI,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val cryptoService: DefaultCryptoService,
|
||||
@UserId private val userId: String,
|
||||
) : FetchThreadSummariesTask {
|
||||
|
||||
override suspend fun execute(params: FetchThreadSummariesTask.Params): Result {
|
||||
val filter = FilterFactory.createThreadsFilter(
|
||||
numberOfEvents = params.limit,
|
||||
userId = if (params.isUserParticipating) userId else null).toJSONString()
|
||||
|
||||
val response = executeRequest(
|
||||
globalErrorReceiver,
|
||||
canRetry = true
|
||||
) {
|
||||
roomAPI.getRoomMessagesFrom(params.roomId, params.from, PaginationDirection.BACKWARDS.value, params.limit, filter)
|
||||
}
|
||||
|
||||
Timber.i("###THREADS DefaultFetchThreadSummariesTask Fetched size:${response.events.size} nextBatch:${response.end} ")
|
||||
|
||||
return handleResponse(response, params)
|
||||
}
|
||||
|
||||
private suspend fun handleResponse(response: PaginationResponse,
|
||||
params: FetchThreadSummariesTask.Params): Result {
|
||||
val rootThreadList = response.events
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction
|
||||
|
||||
val roomMemberContentsByUser = HashMap<String, RoomMemberContent?>()
|
||||
for (rootThreadEvent in rootThreadList) {
|
||||
if (rootThreadEvent.eventId == null || rootThreadEvent.senderId == null || rootThreadEvent.type == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
ThreadSummaryEntity.createOrUpdate(
|
||||
threadSummaryType = ThreadSummaryUpdateType.REPLACE,
|
||||
realm = realm,
|
||||
roomId = params.roomId,
|
||||
rootThreadEvent = rootThreadEvent,
|
||||
roomMemberContentsByUser = roomMemberContentsByUser,
|
||||
roomEntity = roomEntity,
|
||||
userId = userId,
|
||||
cryptoService = cryptoService)
|
||||
}
|
||||
}
|
||||
return Result.SUCCESS
|
||||
}
|
||||
|
||||
enum class Result {
|
||||
SHOULD_FETCH_MORE,
|
||||
REACHED_END,
|
||||
SUCCESS
|
||||
}
|
||||
}
|
|
@ -58,7 +58,6 @@ import javax.inject.Inject
|
|||
/***
|
||||
* This class is responsible to Fetch paginated chunks of the thread timeline using the /relations API
|
||||
*
|
||||
*
|
||||
* How it works
|
||||
*
|
||||
* The problem?
|
||||
|
|
|
@ -23,25 +23,25 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
|
||||
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.helper.findAllLocalThreadNotificationsForRoomId
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.internal.database.helper.enhanceWithEditions
|
||||
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
||||
import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread
|
||||
import org.matrix.android.sdk.internal.database.helper.mapEventsWithEdition
|
||||
import org.matrix.android.sdk.internal.database.mapper.ThreadSummaryMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
|
||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||
|
||||
internal class DefaultThreadsService @AssistedInject constructor(
|
||||
@Assisted private val roomId: String,
|
||||
@UserId private val userId: String,
|
||||
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
||||
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val threadSummaryMapper: ThreadSummaryMapper
|
||||
) : ThreadsService {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -49,55 +49,40 @@ internal class DefaultThreadsService @AssistedInject constructor(
|
|||
fun create(roomId: String): DefaultThreadsService
|
||||
}
|
||||
|
||||
override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> {
|
||||
override fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{
|
||||
threadSummaryMapper.map(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMarkedThreadNotifications(): List<TimelineEvent> {
|
||||
override fun getAllThreadSummaries(): List<ThreadSummary> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ threadSummaryMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAllThreads(): List<TimelineEvent> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean {
|
||||
override fun enhanceWithEditions(threads: List<ThreadSummary>): List<ThreadSummary> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
TimelineEventEntity.isUserParticipatingInThread(
|
||||
realm = it,
|
||||
roomId = roomId,
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
senderId = userId)
|
||||
threads.enhanceWithEditions(it, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
threads.mapEventsWithEdition(it, roomId)
|
||||
}
|
||||
override suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int) {
|
||||
fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(
|
||||
roomId = roomId,
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
from = from,
|
||||
limit = limit
|
||||
))
|
||||
}
|
||||
|
||||
override suspend fun markThreadAsRead(rootThreadEventId: String) {
|
||||
monarchy.awaitTransaction {
|
||||
EventEntity.where(
|
||||
realm = it,
|
||||
eventId = rootThreadEventId).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
|
||||
}
|
||||
override suspend fun fetchThreadSummaries() {
|
||||
fetchThreadSummariesTask.execute(FetchThreadSummariesTask.Params(
|
||||
roomId = roomId
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.session.room.threads.local
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.room.threads.local.ThreadsLocalService
|
||||
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.helper.findAllLocalThreadNotificationsForRoomId
|
||||
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
||||
import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread
|
||||
import org.matrix.android.sdk.internal.database.helper.mapEventsWithEdition
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
|
||||
internal class DefaultThreadsLocalService @AssistedInject constructor(
|
||||
@Assisted private val roomId: String,
|
||||
@UserId private val userId: String,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
) : ThreadsLocalService {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(roomId: String): DefaultThreadsLocalService
|
||||
}
|
||||
|
||||
override fun getMarkedThreadNotificationsLive(): LiveData<List<TimelineEvent>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getMarkedThreadNotifications(): List<TimelineEvent> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAllThreadsLive(): LiveData<List<TimelineEvent>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAllThreads(): List<TimelineEvent> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||
{ timelineEventMapper.map(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
TimelineEventEntity.isUserParticipatingInThread(
|
||||
realm = it,
|
||||
roomId = roomId,
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
senderId = userId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mapEventsWithEdition(threads: List<TimelineEvent>): List<TimelineEvent> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||
threads.mapEventsWithEdition(it, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun markThreadAsRead(rootThreadEventId: String) {
|
||||
monarchy.awaitTransaction {
|
||||
EventEntity.where(
|
||||
realm = it,
|
||||
eventId = rootThreadEventId).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,10 +23,12 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
|||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
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.initsync.InitSyncStep
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
||||
import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync
|
||||
import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral
|
||||
import org.matrix.android.sdk.api.session.sync.model.RoomSync
|
||||
|
@ -36,6 +38,7 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
|||
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.internal.database.helper.addIfNecessary
|
||||
import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
|
||||
import org.matrix.android.sdk.internal.database.helper.createOrUpdate
|
||||
import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
|
||||
import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
|
@ -48,6 +51,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntity
|
|||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
|
||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||
import org.matrix.android.sdk.internal.database.query.find
|
||||
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
||||
|
@ -60,6 +64,7 @@ import org.matrix.android.sdk.internal.di.UserId
|
|||
import org.matrix.android.sdk.internal.extensions.clearWith
|
||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
|
||||
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
||||
import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
|
||||
import org.matrix.android.sdk.internal.session.initsync.mapWithProgress
|
||||
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
|
||||
|
@ -86,6 +91,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
@UserId private val userId: String,
|
||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||
private val timelineInput: TimelineInput,
|
||||
private val liveEventService: Lazy<StreamEventsManager>) {
|
||||
|
@ -345,7 +351,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
return roomEntity
|
||||
}
|
||||
|
||||
val customList = arrayListOf<String>()
|
||||
private fun handleTimelineEvents(realm: Realm,
|
||||
roomId: String,
|
||||
roomEntity: RoomEntity,
|
||||
|
@ -420,6 +425,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
optimizedThreadSummaryMap[it] = eventEntity
|
||||
// Add the same thread timeline event to Thread Chunk
|
||||
addToThreadChunkIfNeeded(realm, roomId, it, timelineEventAdded, roomEntity)
|
||||
// Add thread list if needed
|
||||
if(homeServerCapabilitiesService.getHomeServerCapabilities().canUseThreading) {
|
||||
ThreadSummaryEntity.createOrUpdate(
|
||||
threadSummaryType = ThreadSummaryUpdateType.ADD,
|
||||
realm = realm,
|
||||
roomId = roomId,
|
||||
threadEventEntity = eventEntity,
|
||||
roomMemberContentsByUser = roomMemberContentsByUser,
|
||||
userId = userId,
|
||||
roomEntity = roomEntity)
|
||||
}
|
||||
} ?: run {
|
||||
// This is a normal event or a root thread one
|
||||
optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity
|
||||
|
|
|
@ -32,7 +32,6 @@ import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
|||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -92,14 +91,7 @@ class ThreadsActivity : VectorBaseActivity<ActivityThreadsBinding>() {
|
|||
* This function is used to navigate to the selected thread timeline.
|
||||
* One usage of that is from the Threads Activity
|
||||
*/
|
||||
fun navigateToThreadTimeline(
|
||||
timelineEvent: TimelineEvent) {
|
||||
val roomThreadDetailArgs = ThreadTimelineArgs(
|
||||
roomId = timelineEvent.roomId,
|
||||
displayName = timelineEvent.senderInfo.displayName,
|
||||
avatarUrl = timelineEvent.senderInfo.avatarUrl,
|
||||
roomEncryptionTrustLevel = null,
|
||||
rootThreadEventId = timelineEvent.eventId)
|
||||
fun navigateToThreadTimeline(threadTimelineArgs: ThreadTimelineArgs) {
|
||||
val commonOption: (FragmentTransaction) -> Unit = {
|
||||
it.setCustomAnimations(
|
||||
R.anim.animation_slide_in_right,
|
||||
|
@ -111,8 +103,8 @@ class ThreadsActivity : VectorBaseActivity<ActivityThreadsBinding>() {
|
|||
container = views.threadsActivityFragmentContainer,
|
||||
fragmentClass = TimelineFragment::class.java,
|
||||
params = TimelineArgs(
|
||||
roomId = timelineEvent.roomId,
|
||||
threadTimelineArgs = roomThreadDetailArgs
|
||||
roomId = threadTimelineArgs.roomId,
|
||||
threadTimelineArgs = threadTimelineArgs
|
||||
),
|
||||
option = commonOption
|
||||
)
|
||||
|
|
|
@ -23,15 +23,19 @@ import im.vector.app.core.date.VectorDateFormatter
|
|||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.threads.list.model.threadListItem
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.api.util.toMatrixItemOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class ThreadListController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val dateFormatter: VectorDateFormatter
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val session: Session
|
||||
) : EpoxyController() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
@ -43,10 +47,59 @@ class ThreadListController @Inject constructor(
|
|||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
override fun buildModels() =
|
||||
when (session.getHomeServerCapabilities().canUseThreading) {
|
||||
true -> buildThreadSummaries()
|
||||
false -> buildThreadList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Building thread summaries when homeserver
|
||||
* supports threading
|
||||
*/
|
||||
private fun buildThreadSummaries() {
|
||||
val safeViewState = viewState ?: return
|
||||
val host = this
|
||||
safeViewState.threadSummaryList.invoke()
|
||||
?.filter {
|
||||
if (safeViewState.shouldFilterThreads) {
|
||||
it.isUserParticipating
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
?.forEach { threadSummary ->
|
||||
val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST)
|
||||
val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message)
|
||||
val rootThreadEdition = threadSummary.threadEditions.rootThreadEdition
|
||||
val latestThreadEdition = threadSummary.threadEditions.latestThreadEdition
|
||||
threadListItem {
|
||||
id(threadSummary.rootEvent?.eventId)
|
||||
avatarRenderer(host.avatarRenderer)
|
||||
matrixItem(threadSummary.rootThreadSenderInfo.toMatrixItem())
|
||||
title(threadSummary.rootThreadSenderInfo.displayName.orEmpty())
|
||||
date(date)
|
||||
rootMessageDeleted(threadSummary.rootEvent?.isRedacted() ?: false)
|
||||
// TODO refactor notifications that with the new thread summary
|
||||
threadNotificationState(threadSummary.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
||||
rootMessage(rootThreadEdition ?: threadSummary.rootEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage)
|
||||
lastMessage(latestThreadEdition ?: threadSummary.latestEvent?.getDecryptedTextSummary() ?: decryptionErrorMessage)
|
||||
lastMessageCounter(threadSummary.numberOfThreads.toString())
|
||||
lastMessageMatrixItem(threadSummary.latestThreadSenderInfo.toMatrixItemOrNull())
|
||||
itemClickListener {
|
||||
host.listener?.onThreadSummaryClicked(threadSummary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Building local thread list when homeserver do not
|
||||
* support threading
|
||||
*/
|
||||
private fun buildThreadList() {
|
||||
val safeViewState = viewState ?: return
|
||||
val host = this
|
||||
safeViewState.rootThreadEventList.invoke()
|
||||
?.filter {
|
||||
if (safeViewState.shouldFilterThreads) {
|
||||
|
@ -74,13 +127,14 @@ class ThreadListController @Inject constructor(
|
|||
lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString())
|
||||
lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem())
|
||||
itemClickListener {
|
||||
host.listener?.onThreadClicked(timelineEvent)
|
||||
host.listener?.onThreadListClicked(timelineEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onThreadClicked(timelineEvent: TimelineEvent)
|
||||
fun onThreadSummaryClicked(threadSummary: ThreadSummary)
|
||||
fun onThreadListClicked(timelineEvent: TimelineEvent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.app.core.platform.VectorViewModel
|
|||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
|
@ -53,11 +54,41 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
}
|
||||
|
||||
init {
|
||||
observeThreadsList()
|
||||
observeThreads()
|
||||
fetchThreadList()
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {}
|
||||
|
||||
/**
|
||||
* Observing thread list with respect to homeserver
|
||||
* capabilities
|
||||
*/
|
||||
private fun observeThreads() {
|
||||
when (session.getHomeServerCapabilities().canUseThreading) {
|
||||
true -> observeThreadSummaries()
|
||||
false -> observeThreadsList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observing thread summaries when homeserver support
|
||||
* threading
|
||||
*/
|
||||
private fun observeThreadSummaries() {
|
||||
room?.flow()
|
||||
?.liveThreadSummaries()
|
||||
?.map { room.enhanceWithEditions(it) }
|
||||
?.flowOn(room.coroutineDispatchers.io)
|
||||
?.execute { asyncThreads ->
|
||||
copy(threadSummaryList = asyncThreads)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observing thread list when homeserver do not support
|
||||
* threading
|
||||
*/
|
||||
private fun observeThreadsList() {
|
||||
room?.flow()
|
||||
?.liveThreadList()
|
||||
|
@ -74,6 +105,14 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
|
|||
}
|
||||
}
|
||||
|
||||
private fun fetchThreadList() {
|
||||
viewModelScope.launch {
|
||||
room?.fetchThreadSummaries()
|
||||
}
|
||||
}
|
||||
|
||||
fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading
|
||||
|
||||
fun applyFiltering(shouldFilterThreads: Boolean) {
|
||||
setState {
|
||||
copy(shouldFilterThreads = shouldFilterThreads)
|
||||
|
|
|
@ -20,13 +20,14 @@ import com.airbnb.mvrx.Async
|
|||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
||||
|
||||
data class ThreadListViewState(
|
||||
val threadSummaryList: Async<List<ThreadSummary>> = Uninitialized,
|
||||
val rootThreadEventList: Async<List<ThreadTimelineEvent>> = Uninitialized,
|
||||
val shouldFilterThreads: Boolean = false,
|
||||
val roomId: String
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: ThreadListArgs) : this(roomId = args.roomId)
|
||||
}
|
||||
|
|
|
@ -34,9 +34,11 @@ import im.vector.app.features.home.AvatarRenderer
|
|||
import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator
|
||||
import im.vector.app.features.home.room.threads.ThreadsActivity
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
|
||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
|
||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
|
@ -111,12 +113,30 @@ class ThreadListFragment @Inject constructor(
|
|||
views.includeThreadListToolbar.roomToolbarThreadSubtitleTextView.text = threadListArgs.displayName
|
||||
}
|
||||
|
||||
override fun onThreadClicked(timelineEvent: TimelineEvent) {
|
||||
(activity as? ThreadsActivity)?.navigateToThreadTimeline(timelineEvent)
|
||||
override fun onThreadSummaryClicked(threadSummary: ThreadSummary) {
|
||||
val roomThreadDetailArgs = ThreadTimelineArgs(
|
||||
roomId = threadSummary.roomId,
|
||||
displayName = threadSummary.rootThreadSenderInfo.displayName,
|
||||
avatarUrl = threadSummary.rootThreadSenderInfo.avatarUrl,
|
||||
roomEncryptionTrustLevel = null,
|
||||
rootThreadEventId = threadSummary.rootEventId)
|
||||
(activity as? ThreadsActivity)?.navigateToThreadTimeline(roomThreadDetailArgs)
|
||||
}
|
||||
|
||||
override fun onThreadListClicked(timelineEvent: TimelineEvent) {
|
||||
val threadTimelineArgs = ThreadTimelineArgs(
|
||||
roomId = timelineEvent.roomId,
|
||||
displayName = timelineEvent.senderInfo.displayName,
|
||||
avatarUrl = timelineEvent.senderInfo.avatarUrl,
|
||||
roomEncryptionTrustLevel = null,
|
||||
rootThreadEventId = timelineEvent.eventId)
|
||||
(activity as? ThreadsActivity)?.navigateToThreadTimeline(threadTimelineArgs)
|
||||
}
|
||||
|
||||
private fun renderEmptyStateIfNeeded(state: ThreadListViewState) {
|
||||
val show = state.rootThreadEventList.invoke().isNullOrEmpty()
|
||||
views.threadListEmptyConstraintLayout.isVisible = show
|
||||
when (threadListViewModel.canHomeserverUseThreading()) {
|
||||
true -> views.threadListEmptyConstraintLayout.isVisible = state.threadSummaryList.invoke().isNullOrEmpty()
|
||||
false -> views.threadListEmptyConstraintLayout.isVisible = state.rootThreadEventList.invoke().isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue