Merge pull request #2940 from vector-im/feature/bma/various_fixies

Rework event edition management
This commit is contained in:
Benoit Marty 2021-03-04 18:03:45 +01:00 committed by GitHub
commit e5656e264a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 317 additions and 281 deletions

View File

@ -30,7 +30,7 @@ Test:
- -
Other changes: Other changes:
- - Rework edition of event management
Changes in Element 1.1.0 (2021-02-19) Changes in Element 1.1.0 (2021-02-19)
=================================================== ===================================================

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/design_default_color_primary">
<TextView
android:id="@+id/testPage"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="80sp"
android:textStyle="bold" />
</RelativeLayout>

View File

@ -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
}

View File

@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
data class EditAggregatedSummary( 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) // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
val sourceEvents: List<String>, val sourceEvents: List<String>,
val localEchos: List<String>, val localEchos: List<String>,

View File

@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.model
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass 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. * Contains an aggregated summary info of the references.

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.room.model.relation package org.matrix.android.sdk.api.session.room.model.relation
import androidx.lifecycle.LiveData 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.events.model.Event
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -92,8 +91,11 @@ interface RelationService {
/** /**
* Get the edit history of the given event * 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<List<Event>>) suspend fun fetchEditHistory(eventId: String): List<Event>
/** /**
* Reply to an event in the timeline (must be in same room) * Reply to an event in the timeline (must be in same room)

View File

@ -123,8 +123,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
return if (root.getClearType() == EventType.STICKER) { return if (root.getClearType() == EventType.STICKER) {
root.getClearContent().toModel<MessageStickerContent>() root.getClearContent().toModel<MessageStickerContent>()
} else { } else {
annotations?.editSummary?.aggregatedContent?.toModel() (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
?: root.getClearContent().toModel()
} }
} }

View File

@ -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
}

View File

@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration 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.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
@ -30,7 +32,7 @@ import javax.inject.Inject
class RealmSessionStoreMigration @Inject constructor() : RealmMigration { class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
companion object { 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) { 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 <= 4) migrateTo5(realm)
if (oldVersion <= 5) migrateTo6(realm) if (oldVersion <= 5) migrateTo6(realm)
if (oldVersion <= 6) migrateTo7(realm) if (oldVersion <= 6) migrateTo7(realm)
if (oldVersion <= 7) migrateTo8(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -122,4 +125,28 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
} }
?.removeField("areAllMembersLoaded") ?.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)
}
} }

View File

@ -97,7 +97,8 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
this.root = eventEntity this.root = eventEntity
this.eventId = eventId this.eventId = eventId
this.roomId = roomId 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.readReceipts = readReceiptsSummaryEntity
this.displayIndex = displayIndex this.displayIndex = displayIndex
val roomMemberContent = roomMemberContentsByUser[senderId] val roomMemberContent = roomMemberContentsByUser[senderId]

View File

@ -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.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary 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.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.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 { internal object EventAnnotationsSummaryMapper {
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary { fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
@ -40,14 +36,18 @@ internal object EventAnnotationsSummaryMapper {
it.sourceLocalEcho.toList() it.sourceLocalEcho.toList()
) )
}, },
editSummary = annotationsSummary.editSummary?.let { editSummary = annotationsSummary.editSummary
EditAggregatedSummary( ?.let {
ContentMapper.map(it.aggregatedContent), val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp } ?: return@let null
it.sourceEvents.toList(), EditAggregatedSummary(
it.sourceLocalEchoEvents.toList(), latestContent = ContentMapper.map(latestEdition.content),
it.lastEditTs 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 = annotationsSummary.referencesSummaryEntity?.let {
ReferencesAggregatedSummary( ReferencesAggregatedSummary(
it.eventId, 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<String>().apply { addAll(it.sourceEvents) },
RealmList<String>().apply { addAll(it.localEchos) },
it.lastEditTs
)
}
eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary.let {
RealmList<ReactionAggregatedSummaryEntity>().apply {
addAll(it.map {
ReactionAggregatedSummaryEntity(
it.key,
it.count,
it.addedByMe,
it.firstTimestamp,
RealmList<String>().apply { addAll(it.sourceEvents) },
RealmList<String>().apply { addAll(it.localEchoEvents) }
)
})
}
}
eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let {
ReferencesAggregatedSummaryEntity(
it.eventId,
ContentMapper.map(it.content),
RealmList<String>().apply { addAll(it.sourceEvents) },
RealmList<String>().apply { addAll(it.localEchos) }
)
}
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
PollResponseAggregatedSummaryEntityMapper.map(it)
}
return eventAnnotationsSummaryEntity
}
} }
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary { internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {

View File

@ -17,17 +17,24 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject 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( internal open class EditAggregatedSummaryEntity(
var aggregatedContent: String? = null, // The list of the editions used to build the summary (might be out of sync if chunked received from message chunk)
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) var editions: RealmList<EditionOfEvent> = RealmList()
var sourceEvents: RealmList<String> = RealmList(),
var sourceLocalEchoEvents: RealmList<String> = RealmList(),
var lastEditTs: Long = 0
) : RealmObject() { ) : RealmObject() {
companion object 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()

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import timber.log.Timber
internal open class EventAnnotationsSummaryEntity( internal open class EventAnnotationsSummaryEntity(
@PrimaryKey @PrimaryKey
@ -29,6 +30,21 @@ internal open class EventAnnotationsSummaryEntity(
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
) : RealmObject() { ) : 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 companion object
} }

View File

@ -43,6 +43,7 @@ import io.realm.annotations.RealmModule
EventAnnotationsSummaryEntity::class, EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class, ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class,
EditionOfEvent::class,
PollResponseAggregatedSummaryEntity::class, PollResponseAggregatedSummaryEntity::class,
ReferencesAggregatedSummaryEntity::class, ReferencesAggregatedSummaryEntity::class,
PushRulesEntity::class, PushRulesEntity::class,

View File

@ -23,18 +23,10 @@ import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> { internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
val query = realm.where<EventAnnotationsSummaryEntity>() return realm.where<EventAnnotationsSummaryEntity>()
query.equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) .equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
return query .equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId)
}
internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery<EventAnnotationsSummaryEntity> {
val query = realm.where<EventAnnotationsSummaryEntity>()
if (roomId != null) {
query.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
}
return query
} }
internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity { 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 { internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId).apply { this.roomId = roomId } ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
} }

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.room package org.matrix.android.sdk.internal.session.room
import io.realm.Realm 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.AggregatedAnnotation
import org.matrix.android.sdk.api.session.events.model.Event 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.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.message.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent 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.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.ContentMapper
import org.matrix.android.sdk.internal.database.mapper.EventMapper 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.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.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.EventEntity 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.EventInsertType
@ -50,33 +53,6 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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) internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String)
: EventInsertLiveProcessor { : EventInsertLiveProcessor {
@ -118,13 +94,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
EventAnnotationsSummaryEntity.where(realm, event.eventId EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
?: "").findFirst()?.let { ?.let {
TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findFirst()
?: "").findFirst()?.let { tet -> ?.let { tet -> tet.annotations = it }
tet.annotations = it }
}
}
} }
val content: MessageContent? = event.content.toModel() 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 // OPT OUT serer aggregation until API mature enough
private val SHOULD_HANDLE_SERVER_AGREGGATION = false 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 eventId = event.eventId ?: return
val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return
val newContent = content.newContent ?: 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 // ok, this is a replace
val existing = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId) val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId)
// we have it // we have it
val existingSummary = existing.editSummary val existingSummary = eventAnnotationsSummaryEntity.editSummary
if (existingSummary == null) { if (existingSummary == null) {
Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)") Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)")
// create the edit summary // create the edit summary
val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java) eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
editSummary.aggregatedContent = ContentMapper.map(newContent) .also { editSummary ->
if (isLocalEcho) { editSummary.editions.add(
editSummary.lastEditTs = 0 EditionOfEvent(
editSummary.sourceLocalEchoEvents.add(eventId) senderId = event.senderId ?: "",
} else { eventId = event.eventId,
editSummary.lastEditTs = event.originServerTs ?: 0 content = ContentMapper.map(newContent),
editSummary.sourceEvents.add(eventId) timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0,
} isLocalEcho = isLocalEcho
)
existing.editSummary = editSummary )
}
} else { } else {
if (existingSummary.sourceEvents.contains(eventId)) { if (existingSummary.editions.any { it.eventId == eventId }) {
// ignore this event, we already know it (??) // ignore this event, we already know it (??)
Timber.v("###REPLACE ignoring event for summary, it's known $eventId") Timber.v("###REPLACE ignoring event for summary, it's known $eventId")
return return
} }
val txId = event.unsignedData?.transactionId val txId = event.unsignedData?.transactionId
// is it a remote echo? // 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 // ok it has already been managed
Timber.v("###REPLACE Receiving remote echo of edit (edit already done)") Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
existingSummary.sourceLocalEchoEvents.remove(txId) existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
existingSummary.sourceEvents.add(event.eventId) it.eventId = event.eventId
} else if ( it.timestamp = event.originServerTs ?: System.currentTimeMillis()
isLocalEcho // do not rely on ts for local echo, take it it.isLocalEcho = false
|| 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)
} }
} else { } else {
// ignore this event for the summary (back paginate) Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
if (!isLocalEcho) { existingSummary.editions.add(
existingSummary.sourceEvents.add(eventId) EditionOfEvent(
} senderId = event.senderId ?: "",
Timber.v("###REPLACE ignoring event for summary, it's to old $eventId") 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 val eventTimestamp = event.originServerTs ?: return
// ok, this is a poll response // ok, this is a poll response
var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst() var existing = EventAnnotationsSummaryEntity.where(realm, roomId, targetEventId).findFirst()
if (existing == null) { if (existing == null) {
Timber.v("## POLL creating new relation summary for $targetEventId") Timber.v("## POLL creating new relation summary for $targetEventId")
existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId) existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId)
@ -370,7 +359,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
aggregation.chunk?.forEach { aggregation.chunk?.forEach {
if (it.type == EventType.REACTION) { if (it.type == EventType.REACTION) {
val eventId = event.eventId ?: "" val eventId = event.eventId ?: ""
val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
if (existing == null) { if (existing == null) {
val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId) val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) 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) { private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) {
Timber.d("Handle redaction of m.replace") 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) { if (eventSummary == null) {
Timber.w("Redaction of a replace targeting an unknown event $relatedEventId") Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
return return
} }
val sourceEvents = eventSummary.editSummary?.sourceEvents val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull { it.eventId == redacted.eventId }
val sourceToDiscard = sourceEvents?.indexOf(redacted.eventId)
if (sourceToDiscard == null) { if (sourceToDiscard == null) {
Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard") Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard")
return return
} }
// Need to remove this event from the redaction list and compute new aggregation state // Need to remove this event from the edition list
sourceEvents.removeAt(sourceToDiscard) sourceToDiscard.deleteFromRealm()
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<MessageContent>()?.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
}
}
} }
fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) { private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
Timber.v("REDACTION of reaction ${eventToPrune.eventId}") Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
// delete a reaction, need to update the annotation summary if any // delete a reaction, need to update the annotation summary if any
val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return
?: return
val eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return val eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return
val reactionKey = reactionContent.relatesTo.key val reactionKey = reactionContent.relatesTo.key
Timber.v("REMOVE reaction for key $reactionKey") 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) { if (summary != null) {
summary.reactionsSummary.where() summary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey) .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey)

View File

@ -140,13 +140,8 @@ internal class DefaultRelationService @AssistedInject constructor(
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} }
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) { override suspend fun fetchEditHistory(eventId: String): List<Event> {
val params = FetchEditHistoryTask.Params(roomId, eventId) return fetchEditHistoryTask.execute(FetchEditHistoryTask.Params(roomId, eventId))
fetchEditHistoryTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? { 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? { override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
return monarchy.fetchCopyMap( return monarchy.fetchCopyMap(
{ EventAnnotationsSummaryEntity.where(it, eventId).findFirst() }, { EventAnnotationsSummaryEntity.where(it, roomId, eventId).findFirst() },
{ entity, _ -> { entity, _ ->
entity.asDomain() entity.asDomain()
} }
@ -168,7 +163,7 @@ internal class DefaultRelationService @AssistedInject constructor(
override fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> { override fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> {
val liveData = monarchy.findAllMappedWithChanges( val liveData = monarchy.findAllMappedWithChanges(
{ EventAnnotationsSummaryEntity.where(it, eventId) }, { EventAnnotationsSummaryEntity.where(it, roomId, eventId) },
{ it.asDomain() } { it.asDomain() }
) )
return Transformations.map(liveData) { results -> return Transformations.map(liveData) { results ->

View File

@ -49,8 +49,11 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
) )
} }
val events = response.chunks.toMutableList() // Filter out edition form other users, and redacted editions
response.originalEvent?.let { events.add(it) } val originalSenderId = response.originalEvent?.senderId
return events val events = response.chunks
.filter { it.senderId == originalSenderId }
.filter { !it.isRedacted() }
return events + listOfNotNull(response.originalEvent)
} }
} }

View File

@ -45,16 +45,16 @@ internal class DefaultFindReactionEventForUndoTask @Inject constructor(
override suspend fun execute(params: FindReactionEventForUndoTask.Params): FindReactionEventForUndoTask.Result { override suspend fun execute(params: FindReactionEventForUndoTask.Params): FindReactionEventForUndoTask.Result {
val eventId = Realm.getInstance(monarchy.realmConfiguration).use { realm -> val eventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
getReactionToRedact(realm, params.reaction, params.eventId)?.eventId getReactionToRedact(realm, params)?.eventId
} }
return FindReactionEventForUndoTask.Result(eventId) return FindReactionEventForUndoTask.Result(eventId)
} }
private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String): EventEntity? { private fun getReactionToRedact(realm: Realm, params: FindReactionEventForUndoTask.Params): EventEntity? {
val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() ?: return null val summary = EventAnnotationsSummaryEntity.where(realm, params.roomId, params.eventId).findFirst() ?: return null
val rase = summary.reactionsSummary.where() val rase = summary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.reaction)
.findFirst() ?: return null .findFirst() ?: return null
// want to find the event originated by me! // want to find the event originated by me!

View File

@ -47,22 +47,22 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(@SessionDataba
override suspend fun execute(params: UpdateQuickReactionTask.Params): UpdateQuickReactionTask.Result { override suspend fun execute(params: UpdateQuickReactionTask.Params): UpdateQuickReactionTask.Result {
var res: Pair<String?, List<String>?>? = null var res: Pair<String?, List<String>?>? = null
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId) res = updateQuickReaction(realm, params)
} }
return UpdateQuickReactionTask.Result(res?.first, res?.second.orEmpty()) return UpdateQuickReactionTask.Result(res?.first, res?.second.orEmpty())
} }
private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String): Pair<String?, List<String>?> { private fun updateQuickReaction(realm: Realm, params: UpdateQuickReactionTask.Params): Pair<String?, List<String>?> {
// the emoji reaction has been selected, we need to check if we have reacted it or not // the emoji reaction has been selected, we need to check if we have reacted it or not
val existingSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() val existingSummary = EventAnnotationsSummaryEntity.where(realm, params.roomId, params.eventId).findFirst()
?: return Pair(reaction, null) ?: return Pair(params.reaction, null)
// Ok there is already reactions on this event, have we reacted to it // Ok there is already reactions on this event, have we reacted to it
val aggregationForReaction = existingSummary.reactionsSummary.where() val aggregationForReaction = existingSummary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.reaction)
.findFirst() .findFirst()
val aggregationForOppositeReaction = existingSummary.reactionsSummary.where() val aggregationForOppositeReaction = existingSummary.reactionsSummary.where()
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, oppositeReaction) .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.oppositeReaction)
.findFirst() .findFirst()
if (aggregationForReaction == null || !aggregationForReaction.addedByMe) { if (aggregationForReaction == null || !aggregationForReaction.addedByMe) {
@ -72,7 +72,7 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(@SessionDataba
val entity = EventEntity.where(realm, it).findFirst() val entity = EventEntity.where(realm, it).findFirst()
if (entity?.sender == userId) entity.eventId else null if (entity?.sender == userId) entity.eventId else null
} }
return Pair(reaction, toRedact) return Pair(params.reaction, toRedact)
} else { } else {
// I already added it, so i need to undo it (like a toggle) // I already added it, so i need to undo it (like a toggle)
// find all m.redaction coming from me to readact them // find all m.redaction coming from me to readact them

View File

@ -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.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary 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.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.MessageType
import org.matrix.android.sdk.api.session.room.model.message.OptionItem import org.matrix.android.sdk.api.session.room.model.message.OptionItem
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl 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.send.UserDraft
import org.matrix.android.sdk.api.session.room.timeline.Timeline 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.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.getRelationContent
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
import org.matrix.android.sdk.api.session.widgets.model.Widget 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()) room.editReply(state.sendMode.timelineEvent, it, action.text.toString())
} }
} else { } else {
val messageContent: MessageContent? = val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
val existingBody = messageContent?.body ?: "" val existingBody = messageContent?.body ?: ""
if (existingBody != action.text) { if (existingBody != action.text) {
room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "", room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "",
@ -842,9 +840,7 @@ class RoomDetailViewModel @AssistedInject constructor(
popDraft() popDraft()
} }
is SendMode.QUOTE -> { is SendMode.QUOTE -> {
val messageContent: MessageContent? = val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
val textMsg = messageContent?.body val textMsg = messageContent?.body
val finalText = legacyRiotQuoteText(textMsg, action.text.toString()) val finalText = legacyRiotQuoteText(textMsg, action.text.toString())

View File

@ -229,8 +229,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
} }
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> { private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() val messageContent = timelineEvent.getLastMessageContent()
?: timelineEvent.root.getClearContent().toModel()
val msgType = messageContent?.msgType val msgType = messageContent?.msgType
return arrayListOf<EventSharedAction>().apply { return arrayListOf<EventSharedAction>().apply {

View File

@ -15,42 +15,28 @@
*/ */
package im.vector.app.features.home.room.detail.timeline.edithistory 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.Fail
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.api.session.events.model.isReply
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
data class ViewEditHistoryViewState(
val eventId: String,
val roomId: String,
val isOriginalAReply: Boolean = false,
val editList: Async<List<Event>> = Uninitialized)
: MvRxState {
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
}
class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
initialState: ViewEditHistoryViewState, initialState: ViewEditHistoryViewState,
val session: Session, val session: Session,
@ -82,48 +68,48 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
private fun loadHistory() { private fun loadHistory() {
setState { copy(editList = Loading()) } setState { copy(editList = Loading()) }
room.fetchEditHistory(eventId, object : MatrixCallback<List<Event>> {
override fun onFailure(failure: Throwable) { viewModelScope.launch {
val data = try {
room.fetchEditHistory(eventId)
} catch (failure: Throwable) {
setState { setState {
copy(editList = Fail(failure)) copy(editList = Fail(failure))
} }
return@launch
} }
override fun onSuccess(data: List<Event>) { var originalIsReply = false
var originalIsReply = false
val events = data.map { event -> data.forEach { event ->
val timelineID = event.roomId + UUID.randomUUID().toString() val timelineID = event.roomId + UUID.randomUUID().toString()
event.also { // We need to check encryption
// We need to check encryption if (event.isEncrypted() && event.mxDecryptionResult == null) {
if (it.isEncrypted() && it.mxDecryptionResult == null) { // for now decrypt sync
// for now decrypt sync try {
try { val result = session.cryptoService().decryptEvent(event, timelineID)
val result = session.cryptoService().decryptEvent(it, timelineID) event.mxDecryptionResult = OlmDecryptionResult(
it.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent,
payload = result.clearEvent, senderKey = result.senderCurve25519Key,
senderKey = result.senderCurve25519Key, keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain )
) } catch (e: MXCryptoError) {
} catch (e: MXCryptoError) { Timber.w("Failed to decrypt event in history")
Timber.w("Failed to decrypt event in history")
}
}
if (event.eventId == it.eventId) {
originalIsReply = it.isReply()
}
} }
} }
setState {
copy( if (event.eventId == eventId) {
editList = Success(events), originalIsReply = event.isReply()
isOriginalAReply = originalIsReply
)
} }
} }
}) setState {
copy(
editList = Success(data),
isOriginalAReply = originalIsReply
)
}
}
} }
override fun handle(action: EmptyAction) { override fun handle(action: EmptyAction) {

View File

@ -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<List<Event>> = Uninitialized)
: MvRxState {
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
}

View File

@ -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.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 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.Session
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf 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.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent 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.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.internal.session.room.VerificationState
import javax.inject.Inject import javax.inject.Inject
/** /**

View File

@ -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.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
import im.vector.app.features.settings.VectorPreferences 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.extensions.orFalse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType 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.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited 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.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.session.room.VerificationState
import javax.inject.Inject import javax.inject.Inject
/** /**

View File

@ -18,9 +18,9 @@ package im.vector.app.features.home.room.detail.timeline.item
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize 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.session.room.send.SendState
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.room.VerificationState
@Parcelize @Parcelize
data class MessageInformationData( data class MessageInformationData(

View File

@ -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.RoomDetailAction
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController 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.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.internal.session.room.VerificationState
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestItem.Holder>() { abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestItem.Holder>() {

View File

@ -48,6 +48,7 @@ class HomeserverSettingsController @Inject constructor(
data ?: return data ?: return
buildHeader(data) buildHeader(data)
buildCapabilities(data)
when (val federationVersion = data.federationVersion) { when (val federationVersion = data.federationVersion) {
is Loading, is Loading,
is Uninitialized -> is Uninitialized ->
@ -63,7 +64,6 @@ class HomeserverSettingsController @Inject constructor(
is Success -> is Success ->
buildFederationVersion(federationVersion()) buildFederationVersion(federationVersion())
} }
buildCapabilities(data)
} }
private fun buildHeader(state: HomeServerSettingsViewState) { private fun buildHeader(state: HomeServerSettingsViewState) {