diff --git a/CHANGES.md b/CHANGES.md index 43563f71aa..ea78fcaf7d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,7 +30,7 @@ Test: - Other changes: - - + - Rework edition of event management Changes in Element 1.1.0 (2021-02-19) =================================================== diff --git a/attachment-viewer/src/main/res/layout/view_image_attachment.xml b/attachment-viewer/src/main/res/layout/view_image_attachment.xml deleted file mode 100644 index 3518a4472d..0000000000 --- a/attachment-viewer/src/main/res/layout/view_image_attachment.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt new file mode 100644 index 0000000000..54276a6b51 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 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.crypto + +enum class VerificationState { + REQUEST, + WAITING, + CANCELED_BY_ME, + CANCELED_BY_OTHER, + DONE +} + +fun VerificationState.isCanceled(): Boolean { + return this == VerificationState.CANCELED_BY_ME || this == VerificationState.CANCELED_BY_OTHER +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt index 10fb81dc7f..67bab626cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model import org.matrix.android.sdk.api.session.events.model.Content data class EditAggregatedSummary( - val aggregatedContent: Content? = null, + val latestContent: Content? = null, // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) val sourceEvents: List, val localEchos: List, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt index 0947c96bb0..664d042e18 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.session.room.VerificationState +import org.matrix.android.sdk.api.crypto.VerificationState /** * Contains an aggregated summary info of the references. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 49aa95924c..2c0e378efb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.room.model.relation import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -92,8 +91,11 @@ interface RelationService { /** * Get the edit history of the given event + * The return list will contain the original event and all the editions of this event, done by the + * same sender, sorted in the reverse order (so the original event is the latest element, and the + * latest edition is the first element of the list) */ - fun fetchEditHistory(eventId: String, callback: MatrixCallback>) + suspend fun fetchEditHistory(eventId: String): List /** * Reply to an event in the timeline (must be in same room) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 53f0e5a8d3..7010a80233 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -123,8 +123,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { return if (root.getClearType() == EventType.STICKER) { root.getClearContent().toModel() } else { - annotations?.editSummary?.aggregatedContent?.toModel() - ?: root.getClearContent().toModel() + (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt new file mode 100644 index 0000000000..0617f32c24 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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.crypto.verification + +import org.matrix.android.sdk.api.crypto.VerificationState +import org.matrix.android.sdk.api.crypto.isCanceled + +// State transition with control +internal fun VerificationState?.toState(newState: VerificationState): VerificationState { + // Cancel is always prioritary ? + // Eg id i found that mac or keys mismatch and send a cancel and the other send a done, i have to + // consider as canceled + if (newState.isCanceled()) { + return newState + } + // never move out of cancel + if (this?.isCanceled() == true) { + return this + } + return newState +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 57002b5a60..c7fe7ab447 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm import io.realm.RealmMigration +import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.EditionOfEventFields import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields @@ -30,7 +32,7 @@ import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 7L + const val SESSION_STORE_SCHEMA_VERSION = 8L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -43,6 +45,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 4) migrateTo5(realm) if (oldVersion <= 5) migrateTo6(realm) if (oldVersion <= 6) migrateTo7(realm) + if (oldVersion <= 7) migrateTo8(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -122,4 +125,28 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { } ?.removeField("areAllMembersLoaded") } + + private fun migrateTo8(realm: DynamicRealm) { + Timber.d("Step 7 -> 8") + + val editionOfEventSchema = realm.schema.create("EditionOfEvent") + .apply { + // setEmbedded does not return `this`... + isEmbedded = true + } + .addField(EditionOfEventFields.CONTENT, String::class.java) + .addField(EditionOfEventFields.EVENT_ID, String::class.java) + .setRequired(EditionOfEventFields.EVENT_ID, true) + .addField(EditionOfEventFields.SENDER_ID, String::class.java) + .setRequired(EditionOfEventFields.SENDER_ID, true) + .addField(EditionOfEventFields.TIMESTAMP, Long::class.java) + .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java) + + realm.schema.get("EditAggregatedSummaryEntity") + ?.removeField("aggregatedContent") + ?.removeField("sourceEvents") + ?.removeField("lastEditTs") + ?.removeField("sourceLocalEchoEvents") + ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index b4935cfdcc..e262b40419 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -97,7 +97,8 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String, this.root = eventEntity this.eventId = eventId this.roomId = roomId - this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() + ?.also { it.cleanUp(eventEntity.sender) } this.readReceipts = readReceiptsSummaryEntity this.displayIndex = displayIndex val roomMemberContent = roomMemberContentsByUser[senderId] diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt index 9ed2664068..4a26b4c4bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -20,11 +20,7 @@ import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary -import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity -import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity -import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity -import io.realm.RealmList internal object EventAnnotationsSummaryMapper { fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary { @@ -40,14 +36,18 @@ internal object EventAnnotationsSummaryMapper { it.sourceLocalEcho.toList() ) }, - editSummary = annotationsSummary.editSummary?.let { - EditAggregatedSummary( - ContentMapper.map(it.aggregatedContent), - it.sourceEvents.toList(), - it.sourceLocalEchoEvents.toList(), - it.lastEditTs - ) - }, + editSummary = annotationsSummary.editSummary + ?.let { + val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp } ?: return@let null + EditAggregatedSummary( + latestContent = ContentMapper.map(latestEdition.content), + sourceEvents = it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho } + .map { editionOfEvent -> editionOfEvent.eventId }, + localEchos = it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho } + .map { editionOfEvent -> editionOfEvent.eventId }, + lastEditTs = latestEdition.timestamp + ) + }, referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let { ReferencesAggregatedSummary( it.eventId, @@ -62,46 +62,6 @@ internal object EventAnnotationsSummaryMapper { ) } - - fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity { - val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity() - eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId - eventAnnotationsSummaryEntity.roomId = roomId - eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let { - EditAggregatedSummaryEntity( - ContentMapper.map(it.aggregatedContent), - RealmList().apply { addAll(it.sourceEvents) }, - RealmList().apply { addAll(it.localEchos) }, - it.lastEditTs - ) - } - eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary.let { - RealmList().apply { - addAll(it.map { - ReactionAggregatedSummaryEntity( - it.key, - it.count, - it.addedByMe, - it.firstTimestamp, - RealmList().apply { addAll(it.sourceEvents) }, - RealmList().apply { addAll(it.localEchoEvents) } - ) - }) - } - } - eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let { - ReferencesAggregatedSummaryEntity( - it.eventId, - ContentMapper.map(it.content), - RealmList().apply { addAll(it.sourceEvents) }, - RealmList().apply { addAll(it.localEchos) } - ) - } - eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let { - PollResponseAggregatedSummaryEntityMapper.map(it) - } - return eventAnnotationsSummaryEntity - } } internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt index 604afc1ab1..0ed927a6b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt @@ -17,17 +17,24 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmList import io.realm.RealmObject +import io.realm.annotations.RealmClass /** - * Keep the latest state of edition of a message + * Keep all the editions of a message */ internal open class EditAggregatedSummaryEntity( - var aggregatedContent: String? = null, - // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) - var sourceEvents: RealmList = RealmList(), - var sourceLocalEchoEvents: RealmList = RealmList(), - var lastEditTs: Long = 0 + // The list of the editions used to build the summary (might be out of sync if chunked received from message chunk) + var editions: RealmList = RealmList() ) : RealmObject() { companion object } + +@RealmClass(embedded = true) +internal open class EditionOfEvent( + var senderId: String = "", + var eventId: String = "", + var content: String? = null, + var timestamp: Long = 0, + var isLocalEcho: Boolean = false +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt index 33f26d439f..3e88130420 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import timber.log.Timber internal open class EventAnnotationsSummaryEntity( @PrimaryKey @@ -29,6 +30,21 @@ internal open class EventAnnotationsSummaryEntity( var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null ) : RealmObject() { + /** + * Cleanup undesired editions, done by users different from the originalEventSender + */ + fun cleanUp(originalEventSenderId: String?) { + originalEventSenderId ?: return + + editSummary?.editions?.filter { + it.senderId != originalEventSenderId + } + ?.forEach { + Timber.w("Deleting an edition from ${it.senderId} of event sent by $originalEventSenderId") + it.deleteFromRealm() + } + } + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index bca2c42c9e..6e6096cf8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -43,6 +43,7 @@ import io.realm.annotations.RealmModule EventAnnotationsSummaryEntity::class, ReactionAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class, + EditionOfEvent::class, PollResponseAggregatedSummaryEntity::class, ReferencesAggregatedSummaryEntity::class, PushRulesEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt index 9a298b7e79..c3cae3d268 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt @@ -23,18 +23,10 @@ import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where -internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { - val query = realm.where() - query.equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) - return query -} - -internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery { - val query = realm.where() - if (roomId != null) { - query.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) - } - return query +internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery { + return realm.where() + .equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) + .equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) } internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity { @@ -49,6 +41,6 @@ internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId } internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity { - return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() - ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId).apply { this.roomId = roomId } + return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() + ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index d090ba5296..60440c6359 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room import io.realm.Realm +import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -31,9 +32,11 @@ import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponse import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent +import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.EditionOfEvent 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 @@ -50,33 +53,6 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import timber.log.Timber import javax.inject.Inject -enum class VerificationState { - REQUEST, - WAITING, - CANCELED_BY_ME, - CANCELED_BY_OTHER, - DONE -} - -fun VerificationState.isCanceled(): Boolean { - return this == VerificationState.CANCELED_BY_ME || this == VerificationState.CANCELED_BY_OTHER -} - -// State transition with control -private fun VerificationState?.toState(newState: VerificationState): VerificationState { - // Cancel is always prioritary ? - // Eg id i found that mac or keys mismatch and send a cancel and the other send a done, i have to - // consider as canceled - if (newState.isCanceled()) { - return newState - } - // never move out of cancel - if (this?.isCanceled() == true) { - return this - } - return newState -} - internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String) : EventInsertLiveProcessor { @@ -118,13 +94,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) - EventAnnotationsSummaryEntity.where(realm, event.eventId - ?: "").findFirst()?.let { - TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId - ?: "").findFirst()?.let { tet -> - tet.annotations = it - } - } + EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() + ?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findFirst() + ?.let { tet -> tet.annotations = it } + } } val content: MessageContent? = event.content.toModel() @@ -216,63 +190,78 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr // OPT OUT serer aggregation until API mature enough private val SHOULD_HANDLE_SERVER_AGREGGATION = false - private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean, relatedEventId: String? = null) { + private fun handleReplace(realm: Realm, + event: Event, + content: MessageContent, + roomId: String, + isLocalEcho: Boolean, + relatedEventId: String? = null) { val eventId = event.eventId ?: return val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return val newContent = content.newContent ?: return + + // Check that the sender is the same + val editedEvent = EventEntity.where(realm, targetEventId).findFirst() + if (editedEvent == null) { + // We do not know yet about the edited event + } else if (editedEvent.sender != event.senderId) { + // Edited by someone else, ignore + Timber.w("Ignore edition by someone else") + return + } + // ok, this is a replace - val existing = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId) + val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId) // we have it - val existingSummary = existing.editSummary + val existingSummary = eventAnnotationsSummaryEntity.editSummary if (existingSummary == null) { Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)") // create the edit summary - val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java) - editSummary.aggregatedContent = ContentMapper.map(newContent) - if (isLocalEcho) { - editSummary.lastEditTs = 0 - editSummary.sourceLocalEchoEvents.add(eventId) - } else { - editSummary.lastEditTs = event.originServerTs ?: 0 - editSummary.sourceEvents.add(eventId) - } - - existing.editSummary = editSummary + eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java) + .also { editSummary -> + editSummary.editions.add( + EditionOfEvent( + senderId = event.senderId ?: "", + eventId = event.eventId, + content = ContentMapper.map(newContent), + timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0, + isLocalEcho = isLocalEcho + ) + ) + } } else { - if (existingSummary.sourceEvents.contains(eventId)) { + if (existingSummary.editions.any { it.eventId == eventId }) { // ignore this event, we already know it (??) Timber.v("###REPLACE ignoring event for summary, it's known $eventId") return } val txId = event.unsignedData?.transactionId // is it a remote echo? - if (!isLocalEcho && existingSummary.sourceLocalEchoEvents.contains(txId)) { + if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) { // ok it has already been managed Timber.v("###REPLACE Receiving remote echo of edit (edit already done)") - existingSummary.sourceLocalEchoEvents.remove(txId) - existingSummary.sourceEvents.add(event.eventId) - } else if ( - isLocalEcho // do not rely on ts for local echo, take it - || event.originServerTs ?: 0 >= existingSummary.lastEditTs - ) { - Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)") - if (!isLocalEcho) { - // Do not take local echo originServerTs here, could mess up ordering (keep old ts) - existingSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis() - } - existingSummary.aggregatedContent = ContentMapper.map(newContent) - if (isLocalEcho) { - existingSummary.sourceLocalEchoEvents.add(eventId) - } else { - existingSummary.sourceEvents.add(eventId) + existingSummary.editions.firstOrNull { it.eventId == txId }?.let { + it.eventId = event.eventId + it.timestamp = event.originServerTs ?: System.currentTimeMillis() + it.isLocalEcho = false } } else { - // ignore this event for the summary (back paginate) - if (!isLocalEcho) { - existingSummary.sourceEvents.add(eventId) - } - Timber.v("###REPLACE ignoring event for summary, it's to old $eventId") + Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)") + existingSummary.editions.add( + EditionOfEvent( + senderId = event.senderId ?: "", + eventId = event.eventId, + content = ContentMapper.map(newContent), + timestamp = if (isLocalEcho) { + System.currentTimeMillis() + } else { + // Do not take local echo originServerTs here, could mess up ordering (keep old ts) + event.originServerTs ?: System.currentTimeMillis() + }, + isLocalEcho = isLocalEcho + ) + ) } } } @@ -290,7 +279,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr val eventTimestamp = event.originServerTs ?: return // ok, this is a poll response - var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst() + var existing = EventAnnotationsSummaryEntity.where(realm, roomId, targetEventId).findFirst() if (existing == null) { Timber.v("## POLL creating new relation summary for $targetEventId") existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId) @@ -370,7 +359,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr aggregation.chunk?.forEach { if (it.type == EventType.REACTION) { val eventId = event.eventId ?: "" - val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst() if (existing == null) { val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId) val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) @@ -454,46 +443,29 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr */ private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) { Timber.d("Handle redaction of m.replace") - val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventId).findFirst() + val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst() if (eventSummary == null) { Timber.w("Redaction of a replace targeting an unknown event $relatedEventId") return } - val sourceEvents = eventSummary.editSummary?.sourceEvents - val sourceToDiscard = sourceEvents?.indexOf(redacted.eventId) + val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull { it.eventId == redacted.eventId } if (sourceToDiscard == null) { Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard") return } - // Need to remove this event from the redaction list and compute new aggregation state - sourceEvents.removeAt(sourceToDiscard) - val previousEdit = sourceEvents.mapNotNull { EventEntity.where(realm, it).findFirst() }.sortedBy { it.originServerTs }.lastOrNull() - if (previousEdit == null) { - // revert to original - eventSummary.editSummary?.deleteFromRealm() - } else { - // I have the last event - ContentMapper.map(previousEdit.content)?.toModel()?.newContent?.let { newContent -> - eventSummary.editSummary?.lastEditTs = previousEdit.originServerTs - ?: System.currentTimeMillis() - eventSummary.editSummary?.aggregatedContent = ContentMapper.map(newContent) - } ?: run { - Timber.e("Failed to udate edited summary") - // TODO how to reccover that - } - } + // Need to remove this event from the edition list + sourceToDiscard.deleteFromRealm() } - fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) { + private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) { Timber.v("REDACTION of reaction ${eventToPrune.eventId}") // delete a reaction, need to update the annotation summary if any - val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() - ?: return + val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return val eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return val reactionKey = reactionContent.relatesTo.key Timber.v("REMOVE reaction for key $reactionKey") - val summary = EventAnnotationsSummaryEntity.where(realm, eventThatWasReacted).findFirst() + val summary = EventAnnotationsSummaryEntity.where(realm, eventToPrune.roomId, eventThatWasReacted).findFirst() if (summary != null) { summary.reactionsSummary.where() .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index b7caf62865..da0cf45946 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -140,13 +140,8 @@ internal class DefaultRelationService @AssistedInject constructor( return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } - override fun fetchEditHistory(eventId: String, callback: MatrixCallback>) { - val params = FetchEditHistoryTask.Params(roomId, eventId) - fetchEditHistoryTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun fetchEditHistory(eventId: String): List { + return fetchEditHistoryTask.execute(FetchEditHistoryTask.Params(roomId, eventId)) } override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? { @@ -159,7 +154,7 @@ internal class DefaultRelationService @AssistedInject constructor( override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? { return monarchy.fetchCopyMap( - { EventAnnotationsSummaryEntity.where(it, eventId).findFirst() }, + { EventAnnotationsSummaryEntity.where(it, roomId, eventId).findFirst() }, { entity, _ -> entity.asDomain() } @@ -168,7 +163,7 @@ internal class DefaultRelationService @AssistedInject constructor( override fun getEventAnnotationsSummaryLive(eventId: String): LiveData> { val liveData = monarchy.findAllMappedWithChanges( - { EventAnnotationsSummaryEntity.where(it, eventId) }, + { EventAnnotationsSummaryEntity.where(it, roomId, eventId) }, { it.asDomain() } ) return Transformations.map(liveData) { results -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt index 854585ca29..f9fd5f9348 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt @@ -49,8 +49,11 @@ internal class DefaultFetchEditHistoryTask @Inject constructor( ) } - val events = response.chunks.toMutableList() - response.originalEvent?.let { events.add(it) } - return events + // Filter out edition form other users, and redacted editions + val originalSenderId = response.originalEvent?.senderId + val events = response.chunks + .filter { it.senderId == originalSenderId } + .filter { !it.isRedacted() } + return events + listOfNotNull(response.originalEvent) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt index fa6db2ee37..863ae4f5ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt @@ -45,16 +45,16 @@ internal class DefaultFindReactionEventForUndoTask @Inject constructor( override suspend fun execute(params: FindReactionEventForUndoTask.Params): FindReactionEventForUndoTask.Result { val eventId = Realm.getInstance(monarchy.realmConfiguration).use { realm -> - getReactionToRedact(realm, params.reaction, params.eventId)?.eventId + getReactionToRedact(realm, params)?.eventId } return FindReactionEventForUndoTask.Result(eventId) } - private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String): EventEntity? { - val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() ?: return null + private fun getReactionToRedact(realm: Realm, params: FindReactionEventForUndoTask.Params): EventEntity? { + val summary = EventAnnotationsSummaryEntity.where(realm, params.roomId, params.eventId).findFirst() ?: return null val rase = summary.reactionsSummary.where() - .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.reaction) .findFirst() ?: return null // want to find the event originated by me! diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt index 1f68a700ad..32d6c5aa7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt @@ -47,22 +47,22 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(@SessionDataba override suspend fun execute(params: UpdateQuickReactionTask.Params): UpdateQuickReactionTask.Result { var res: Pair?>? = null monarchy.doWithRealm { realm -> - res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId) + res = updateQuickReaction(realm, params) } return UpdateQuickReactionTask.Result(res?.first, res?.second.orEmpty()) } - private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String): Pair?> { + private fun updateQuickReaction(realm: Realm, params: UpdateQuickReactionTask.Params): Pair?> { // the emoji reaction has been selected, we need to check if we have reacted it or not - val existingSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() - ?: return Pair(reaction, null) + val existingSummary = EventAnnotationsSummaryEntity.where(realm, params.roomId, params.eventId).findFirst() + ?: return Pair(params.reaction, null) // Ok there is already reactions on this event, have we reacted to it val aggregationForReaction = existingSummary.reactionsSummary.where() - .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.reaction) .findFirst() val aggregationForOppositeReaction = existingSummary.reactionsSummary.where() - .equalTo(ReactionAggregatedSummaryEntityFields.KEY, oppositeReaction) + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.oppositeReaction) .findFirst() if (aggregationForReaction == null || !aggregationForReaction.addedByMe) { @@ -72,7 +72,7 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(@SessionDataba val entity = EventEntity.where(realm, it).findFirst() if (entity?.sender == userId) entity.eventId else null } - return Pair(reaction, toRedact) + return Pair(params.reaction, toRedact) } else { // I already added it, so i need to undo it (like a toggle) // find all m.redaction coming from me to readact them diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 935b4ca2f8..f293eadf50 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -85,7 +85,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent 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.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.OptionItem import org.matrix.android.sdk.api.session.room.model.message.getFileUrl @@ -95,6 +94,7 @@ import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -825,9 +825,7 @@ class RoomDetailViewModel @AssistedInject constructor( room.editReply(state.sendMode.timelineEvent, it, action.text.toString()) } } else { - val messageContent: MessageContent? = - state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val existingBody = messageContent?.body ?: "" if (existingBody != action.text) { room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "", @@ -842,9 +840,7 @@ class RoomDetailViewModel @AssistedInject constructor( popDraft() } is SendMode.QUOTE -> { - val messageContent: MessageContent? = - state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val textMsg = messageContent?.body val finalText = legacyRiotQuoteText(textMsg, action.text.toString()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 1697d9250e..363899c4f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -229,8 +229,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List { - val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: timelineEvent.root.getClearContent().toModel() + val messageContent = timelineEvent.getLastMessageContent() val msgType = messageContent?.msgType return arrayListOf().apply { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt index fff1c8a0ff..af814f5856 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt @@ -15,42 +15,28 @@ */ package im.vector.app.features.home.room.detail.timeline.edithistory -import com.airbnb.mvrx.Async +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session 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.isReply import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import timber.log.Timber import java.util.UUID -data class ViewEditHistoryViewState( - val eventId: String, - val roomId: String, - val isOriginalAReply: Boolean = false, - val editList: Async> = Uninitialized) - : MvRxState { - - constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) -} - class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted initialState: ViewEditHistoryViewState, val session: Session, @@ -82,48 +68,48 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted private fun loadHistory() { setState { copy(editList = Loading()) } - room.fetchEditHistory(eventId, object : MatrixCallback> { - override fun onFailure(failure: Throwable) { + + viewModelScope.launch { + val data = try { + room.fetchEditHistory(eventId) + } catch (failure: Throwable) { setState { copy(editList = Fail(failure)) } + return@launch } - override fun onSuccess(data: List) { - var originalIsReply = false + var originalIsReply = false - val events = data.map { event -> - val timelineID = event.roomId + UUID.randomUUID().toString() - event.also { - // We need to check encryption - if (it.isEncrypted() && it.mxDecryptionResult == null) { - // for now decrypt sync - try { - val result = session.cryptoService().decryptEvent(it, timelineID) - it.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.w("Failed to decrypt event in history") - } - } - - if (event.eventId == it.eventId) { - originalIsReply = it.isReply() - } + data.forEach { event -> + val timelineID = event.roomId + UUID.randomUUID().toString() + // We need to check encryption + if (event.isEncrypted() && event.mxDecryptionResult == null) { + // for now decrypt sync + try { + val result = session.cryptoService().decryptEvent(event, timelineID) + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.w("Failed to decrypt event in history") } } - setState { - copy( - editList = Success(events), - isOriginalAReply = originalIsReply - ) + + if (event.eventId == eventId) { + originalIsReply = event.isReply() } } - }) + setState { + copy( + editList = Success(data), + isOriginalAReply = originalIsReply + ) + } + } } override fun handle(action: EmptyAction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt new file mode 100644 index 0000000000..62f08eef7f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.edithistory + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs +import org.matrix.android.sdk.api.session.events.model.Event + +data class ViewEditHistoryViewState( + val eventId: String, + val roomId: String, + val isOriginalAReply: Boolean = false, + val editList: Async> = Uninitialized) + : MvRxState { + + constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index 0b623d78f1..eb539d2b8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_ +import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf @@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.internal.session.room.VerificationState import javax.inject.Inject /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 802c177197..951a4d3fa0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.settings.VectorPreferences +import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -37,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent -import org.matrix.android.sdk.internal.session.room.VerificationState import javax.inject.Inject /** diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 38575f0cc9..48bd4db94c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -18,9 +18,9 @@ package im.vector.app.features.home.room.detail.timeline.item import android.os.Parcelable import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.internal.session.room.VerificationState @Parcelize data class MessageInformationData( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt index c7a279979b..9ec1d825df 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -35,8 +35,8 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import org.matrix.android.sdk.api.crypto.VerificationState import org.matrix.android.sdk.api.session.crypto.verification.VerificationService -import org.matrix.android.sdk.internal.session.room.VerificationState @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) abstract class VerificationRequestItem : AbsBaseMessageItem() { diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt index e90f711edc..514311315d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt @@ -48,6 +48,7 @@ class HomeserverSettingsController @Inject constructor( data ?: return buildHeader(data) + buildCapabilities(data) when (val federationVersion = data.federationVersion) { is Loading, is Uninitialized -> @@ -63,7 +64,6 @@ class HomeserverSettingsController @Inject constructor( is Success -> buildFederationVersion(federationVersion()) } - buildCapabilities(data) } private fun buildHeader(state: HomeServerSettingsViewState) {