From 6eafa3c43d0995825f30e141298664b72bc582ad Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 17 May 2019 17:15:44 +0200 Subject: [PATCH 01/11] Undo Reaction --- .../session/room/timeline/TimelineTest.kt | 7 +- .../api/session/events/model/UnsignedData.kt | 2 +- .../matrix/android/api/session/room/Room.kt | 3 +- .../room/model/annotation/ReactionService.kt | 38 +++++ .../api/session/room/send/SendService.kt | 4 +- .../internal/session/room/DefaultRoom.kt | 11 +- .../room/EventRelationsAggregationUpdater.kt | 3 +- .../android/internal/session/room/RoomAPI.kt | 18 +++ .../internal/session/room/RoomFactory.kt | 5 + .../internal/session/room/RoomModule.kt | 7 + .../room/annotation/DefaultReactionService.kt | 133 ++++++++++++++++++ .../FindReactionEventForUndoTask.kt | 75 ++++++++++ .../SendRelationWorker.kt | 20 ++- .../session/room/prune/EventsPruner.kt | 2 +- .../session/room/prune/PruneEventWorker.kt | 105 ++++++++++---- .../session/room/send/DefaultSendService.kt | 42 +++--- .../session/room/send/RedactEventWorker.kt | 69 +++++++++ .../session/room/timeline/DefaultTimeline.kt | 18 ++- .../home/room/detail/RoomDetailActions.kt | 3 + .../home/room/detail/RoomDetailFragment.kt | 9 +- .../home/room/detail/RoomDetailViewModel.kt | 11 ++ .../timeline/action/MessageMenuViewModel.kt | 14 ++ .../timeline/factory/MessageItemFactory.kt | 14 +- .../detail/timeline/item/AbsMessageItem.kt | 8 +- .../timeline/item/RedactedMessageItem.kt | 22 +++ .../main/res/drawable/redacted_background.xml | 5 + .../res/layout/item_timeline_event_base.xml | 6 + .../item_timeline_event_redacted_stub.xml | 5 + vector/src/main/res/values/strings_riotX.xml | 3 + 29 files changed, 587 insertions(+), 75 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/FindReactionEventForUndoTask.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/{send => annotation}/SendRelationWorker.kt (71%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt create mode 100644 vector/src/main/res/drawable/redacted_background.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_redacted_stub.xml diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt index ba1095f5..7390bc4e 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt @@ -18,10 +18,12 @@ package im.vector.matrix.android.session.room.timeline import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.session.room.EventRelationExtractor -import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor +import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater +import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor @@ -55,7 +57,8 @@ internal class TimelineTest : InstrumentedTest { private fun createTimeline(initialEventId: String? = null): Timeline { val taskExecutor = TaskExecutor(testCoroutineDispatchers) - val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy) + val erau = EventRelationsAggregationUpdater(Credentials("", "", "", null, null)) + val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy, erau) val paginationTask = FakePaginationTask(tokenChunkEventPersistor) val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor) val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt index 004495b5..4a9547e3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt @@ -25,5 +25,5 @@ data class UnsignedData( @Json(name = "redacted_because") val redactedEvent: Event? = null, @Json(name = "transaction_id") val transactionId: String? = null, @Json(name = "prev_content") val prevContent: Map? = null, - @Json(name = "m.relations") val relations: AggregatedRelations? + @Json(name = "m.relations") val relations: AggregatedRelations? = null ) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 2061a296..ae890eaf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.annotation.ReactionService import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService @@ -27,7 +28,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineService /** * This interface defines methods to interact within a room. */ -interface Room : TimelineService, SendService, ReadService, MembershipService, StateService { +interface Room : TimelineService, SendService, ReadService, MembershipService, StateService , ReactionService{ /** * The roomId of this room diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt new file mode 100644 index 00000000..154401f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 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.matrix.android.api.session.room.model.annotation + +import im.vector.matrix.android.api.util.Cancelable + +interface ReactionService { + + + /** + * Sends a reaction (emoji) to the targetedEvent. + * @param reaction the reaction (preferably emoji) + * @param targetEventId the id of the event being reacted + */ + fun sendReaction(reaction: String, targetEventId: String): Cancelable + + + /** + * Undo a reaction (emoji) to the targetedEvent. + * @param reaction the reaction (preferably emoji) + * @param targetEventId the id of the event being reacted + */ + fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt index 7cd2dbf0..6852931c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.room.send import im.vector.matrix.android.api.session.content.ContentAttachmentData +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.util.Cancelable @@ -48,7 +49,6 @@ interface SendService { */ fun sendMedias(attachments: List): Cancelable - - fun sendReaction(reaction: String, targetEventId: String) : Cancelable + fun redactEvent(event: Event, reason: String?): Cancelable } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index c767e5c6..80d89659 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -22,6 +22,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.session.room.model.annotation.ReactionService import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.state.StateService @@ -39,12 +40,14 @@ internal class DefaultRoom( private val sendService: SendService, private val stateService: StateService, private val readService: ReadService, + private val reactionService: ReactionService, private val roomMembersService: MembershipService ) : Room, - TimelineService by timelineService, - SendService by sendService, - StateService by stateService, - ReadService by readService, + TimelineService by timelineService, + SendService by sendService, + StateService by stateService, + ReadService by readService, + ReactionService by reactionService, MembershipService by roomMembersService { override val roomSummary: LiveData by lazy { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index 0df08914..85e7245d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -67,13 +67,14 @@ internal class EventRelationsAggregationUpdater(private val credentials: Credent sum.key = reaction sum.firstTimestamp = event.originServerTs ?: 0 sum.count = 1 + sum.sourceEvents.add(event.eventId) sum.addedByMe = sum.addedByMe || (credentials.userId == event.sender) eventSummary.reactionsSummary.add(sum) } else { //is this a known event (is possible? pagination?) if (!sum.sourceEvents.contains(eventId)) { sum.count += 1 - sum.sourceEvents.add(eventId) + sum.sourceEvents.add(event.eventId) sum.addedByMe = sum.addedByMe || (credentials.userId == event.sender) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 23e74c74..39d9c4d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -197,4 +197,22 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") fun leave(@Path("roomId") roomId: String, @Body params: Map): Call + + /** + * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. + * This cannot be undone. + * Users may redact their own events, and any user with a power level greater than or equal to the redact power level of the room may redact events there. + * + * @param txId the transaction Id + * @param roomId the room id + * @param eventId the event to delete + * @param reason json containing reason key {"reason": "Indecent material"} + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") + fun redactEvent( + @Path("txnId") txId: String, + @Path("roomId") roomId: String, + @Path("eventId") parent_id: String, + @Body reason: Map + ): Call } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 78d8ae8c..1f5cd18d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.session.room import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService +import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask +import im.vector.matrix.android.internal.session.room.annotation.DefaultReactionService import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask @@ -45,6 +47,7 @@ internal class RoomFactory(private val monarchy: Monarchy, private val paginationTask: PaginationTask, private val contextOfEventTask: GetContextOfEventTask, private val setReadMarkersTask: SetReadMarkersTask, + private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val joinRoomTask: JoinRoomTask, private val leaveRoomTask: LeaveRoomTask) { @@ -53,6 +56,7 @@ internal class RoomFactory(private val monarchy: Monarchy, val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor()) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask) val sendService = DefaultSendService(roomId, eventFactory, monarchy) + val reactionService = DefaultReactionService(roomId, eventFactory, monarchy, findReactionEventForUndoTask, taskExecutor) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask) @@ -64,6 +68,7 @@ internal class RoomFactory(private val monarchy: Monarchy, sendService, stateService, readService, + reactionService, roomMembersService ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index a7fa3f4c..c7df624c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -17,6 +17,8 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.internal.session.DefaultSession +import im.vector.matrix.android.internal.session.room.annotation.DefaultFindReactionEventForUndoTask +import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask @@ -98,5 +100,10 @@ class RoomModule { DefaultSendStateTask(get()) as SendStateTask } + scope(DefaultSession.SCOPE) { + DefaultFindReactionEventForUndoTask(get()) as FindReactionEventForUndoTask + } + + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt new file mode 100644 index 00000000..1afbe7df --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2019 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.matrix.android.internal.session.room.annotation + +import androidx.work.* +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.annotation.ReactionService +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.database.helper.addSendingEvent +import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.RedactEventWorker +import im.vector.matrix.android.internal.session.room.send.SendEventWorker +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.CancelableWork +import im.vector.matrix.android.internal.util.WorkerParamsFactory +import im.vector.matrix.android.internal.util.tryTransactionAsync +import java.util.concurrent.TimeUnit + +private const val REACTION_WORK = "REACTION_WORK" +private const val BACKOFF_DELAY = 10_000L + +private val WORK_CONSTRAINTS = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + +internal class DefaultReactionService(private val roomId: String, + private val eventFactory: LocalEchoEventFactory, + private val monarchy: Monarchy, + private val findReactionEventForUndoTask: FindReactionEventForUndoTask, + private val taskExecutor: TaskExecutor) + : ReactionService { + + + override fun sendReaction(reaction: String, targetEventId: String): Cancelable { + val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction).also { + saveLocalEcho(it) + } + val sendRelationWork = createSendRelationWork(event) + WorkManager.getInstance() + .beginUniqueWork(buildWorkIdentifier(REACTION_WORK), ExistingWorkPolicy.APPEND, sendRelationWork) + .enqueue() + return CancelableWork(sendRelationWork.id) + } + + + private fun createSendRelationWork(event: Event): OneTimeWorkRequest { + //TODO use the new API to send relation (for now use regular send) + val sendContentWorkerParams = SendEventWorker.Params( + roomId, event) + val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) + + return OneTimeWorkRequestBuilder() + .setConstraints(WORK_CONSTRAINTS) + .setInputData(sendWorkData) + .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .build() + } + + override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ { + + val params = FindReactionEventForUndoTask.Params( + roomId, + targetEventId, + reaction, + myUserId + ) + findReactionEventForUndoTask.configureWith(params) + .enableRetry() + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: FindReactionEventForUndoTask.Result) { + data.redactEventId?.let { toRedact -> + val redactWork = createRedactEventWork(toRedact, null) + WorkManager.getInstance() + .beginUniqueWork(buildWorkIdentifier(REACTION_WORK), ExistingWorkPolicy.APPEND, redactWork) + .enqueue() + } + } + }) + .executeBy(taskExecutor) + + } + + private fun buildWorkIdentifier(identifier: String): String { + return "${roomId}_$identifier" + } + + private fun saveLocalEcho(event: Event) { + monarchy.tryTransactionAsync { realm -> + val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() + ?: return@tryTransactionAsync + val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId) + ?: return@tryTransactionAsync + + roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0) + } + } + + //TODO duplicate with send service? + private fun createRedactEventWork(eventId: String, reason: String?): OneTimeWorkRequest { + + //TODO create local echo of m.room.redaction event? + + val sendContentWorkerParams = RedactEventWorker.Params( + roomId, eventId, reason) + val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) + + return OneTimeWorkRequestBuilder() + .setConstraints(WORK_CONSTRAINTS) + .setInputData(redactWorkData) + .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .build() + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/FindReactionEventForUndoTask.kt new file mode 100644 index 00000000..441b289e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/FindReactionEventForUndoTask.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2019 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.matrix.android.internal.session.room.annotation + +import arrow.core.Try +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.task.Task +import io.realm.Realm + + +internal interface FindReactionEventForUndoTask : Task { + + data class Params( + val roomId: String, + val eventId: String, + val reaction: String, + val myUserId: String + ) + + data class Result( + val redactEventId: String? + ) + +} + +internal class DefaultFindReactionEventForUndoTask(private val monarchy: Monarchy) : FindReactionEventForUndoTask { + + override fun execute(params: FindReactionEventForUndoTask.Params): Try { + return Try { + var eventId: String? = null + monarchy.doWithRealm { realm -> + eventId = getReactionToRedact(realm, params.reaction, params.eventId, params.myUserId)?.eventId + } + FindReactionEventForUndoTask.Result(eventId) + } + } + + private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String, userId: String): EventEntity? { + val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() + if (summary != null) { + summary.reactionsSummary.where() + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) + .findFirst()?.let { + //want to find the event orignated by me! + it.sourceEvents.forEach { + //find source event + EventEntity.where(realm, it).findFirst()?.let { eventEntity -> + //is it mine? + if (eventEntity.sender == userId) { + return eventEntity + } + } + } + } + } + return null + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt similarity index 71% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendRelationWorker.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt index 96de9f0f..7ce871a0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt @@ -1,4 +1,19 @@ -package im.vector.matrix.android.internal.session.room.send +/* + * Copyright 2019 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.matrix.android.internal.session.room.annotation import android.content.Context import androidx.work.Worker @@ -11,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.annotation.ReactionInfo import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.util.WorkerParamsFactory import org.koin.standalone.inject @@ -28,7 +44,7 @@ class SendRelationWorker(context: Context, params: WorkerParameters) private val roomAPI by inject() override fun doWork(): Result { - val params = WorkerParamsFactory.fromData(inputData) + val params = WorkerParamsFactory.fromData(inputData) ?: return Result.failure() val localEvent = params.event diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt index ad17032a..d6792f30 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt @@ -36,7 +36,7 @@ internal class EventsPruner(monarchy: Monarchy) : override fun processChanges(inserted: List, updated: List, deleted: List) { val redactionEvents = inserted - .mapNotNull { it.asDomain().redacts } + .mapNotNull { it.asDomain() } val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents) val workData = WorkerParamsFactory.toData(pruneEventWorkerParams) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt index 245aa551..a65988a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt @@ -21,15 +21,24 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.database.mapper.EventMapper +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MatrixKoinComponent +import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.util.WorkerParamsFactory import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm import org.koin.standalone.inject +import timber.log.Timber internal class PruneEventWorker(context: Context, workerParameters: WorkerParameters @@ -37,57 +46,105 @@ internal class PruneEventWorker(context: Context, @JsonClass(generateAdapter = true) internal class Params( - val eventIdsToRedact: List + val redactionEvents: List ) private val monarchy by inject() override fun doWork(): Result { val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.failure() + ?: return Result.failure() val result = monarchy.tryTransactionSync { realm -> - params.eventIdsToRedact.forEach { eventId -> - pruneEvent(realm, eventId) + params.redactionEvents.forEach { event -> + pruneEvent(realm, event) } } - return result.fold({ Result.retry() }, { Result.success() }) + return result.fold({ + Result.retry() + }, { + Result.success() + }) } - private fun pruneEvent(realm: Realm, eventIdToRedact: String) { - if (eventIdToRedact.isEmpty()) { + private fun pruneEvent(realm: Realm, redactionEvent: Event) { + if (redactionEvent.redacts.isNullOrBlank()) { return } - val eventToPrune = EventEntity.where(realm, eventId = eventIdToRedact).findFirst() - ?: return + val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() + ?: return val allowedKeys = computeAllowedKeys(eventToPrune.type) if (allowedKeys.isNotEmpty()) { val prunedContent = ContentMapper.map(eventToPrune.content)?.filterKeys { key -> allowedKeys.contains(key) } eventToPrune.content = ContentMapper.map(prunedContent) + } else { + when (eventToPrune.type) { + EventType.MESSAGE -> { + Timber.d("REDACTION for message ${eventToPrune.eventId}") + val unsignedData = EventMapper.map(eventToPrune).unsignedData + ?: UnsignedData(null, null) + val modified = unsignedData.copy(redactedEvent = redactionEvent) + eventToPrune.content = ContentMapper.map(emptyMap()) + eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) + } + EventType.REACTION -> { + Timber.d("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 eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return + + val reactionkey = reactionContent.relatesTo.key + Timber.d("REMOVE reaction for key $reactionkey") + val summary = EventAnnotationsSummaryEntity.where(realm, eventThatWasReacted).findFirst() + if (summary != null) { + summary.reactionsSummary.where() + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionkey) + .findFirst()?.let { summary -> + Timber.d("Find summary for key with ${summary.sourceEvents.size} known reactions (count:${summary.count})") + Timber.d("Known reactions ${summary.sourceEvents.joinToString(",")}") + if (summary.sourceEvents.contains(eventToPrune.eventId)) { + Timber.d("REMOVE reaction for key $reactionkey") + summary.sourceEvents.remove(eventToPrune.eventId) + Timber.d("Known reactions after ${summary.sourceEvents.joinToString(",")}") + summary.count = summary.count - 1 + if (summary.count == 0) { + //delete! + summary.deleteFromRealm() + } + } else { + Timber.e("## Cannot remove summary from count, corresponding reaction ${eventToPrune.eventId} is not known") + } + } + } else { + Timber.e("## Cannot find summary for key $reactionkey") + } + } + } } } private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { - EventType.STATE_ROOM_MEMBER -> listOf("membership") - EventType.STATE_ROOM_CREATE -> listOf("creator") - EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") + EventType.STATE_ROOM_MEMBER -> listOf("membership") + EventType.STATE_ROOM_CREATE -> listOf("creator") + EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") EventType.STATE_ROOM_POWER_LEVELS -> listOf("users", - "users_default", - "events", - "events_default", - "state_default", - "ban", - "kick", - "redact", - "invite") - EventType.STATE_ROOM_ALIASES -> listOf("aliases") - EventType.STATE_CANONICAL_ALIAS -> listOf("alias") - EventType.FEEDBACK -> listOf("type", "target_event_id") - else -> emptyList() + "users_default", + "events", + "events_default", + "state_default", + "ban", + "kick", + "redact", + "invite") + EventType.STATE_ROOM_ALIASES -> listOf("aliases") + EventType.STATE_CANONICAL_ALIAS -> listOf("alias") + EventType.FEEDBACK -> listOf("type", "target_event_id") + else -> emptyList() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 916077df..28ae63b8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -16,13 +16,7 @@ package im.vector.matrix.android.internal.session.room.send -import androidx.work.BackoffPolicy -import androidx.work.Constraints -import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType -import androidx.work.OneTimeWorkRequest -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager +import androidx.work.* import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event @@ -74,16 +68,14 @@ internal class DefaultSendService(private val roomId: String, return cancelableBag } - - override fun sendReaction(reaction: String, targetEventId: String) : Cancelable { - val event = eventFactory.createReactionEvent(roomId,targetEventId,reaction).also { - saveLocalEcho(it) - } - val sendRelationWork = createSendRelationWork(event) + override fun redactEvent(event: Event, reason: String?): Cancelable { + //TODO manage local echo ? + //TODO manage media/attachements? + val redactWork = createRedactEventWork(event, reason) WorkManager.getInstance() - .beginUniqueWork(buildWorkIdentifier(SEND_WORK), ExistingWorkPolicy.APPEND, sendRelationWork) + .beginUniqueWork(buildWorkIdentifier(SEND_WORK), ExistingWorkPolicy.APPEND, redactWork) .enqueue() - return CancelableWork(sendRelationWork.id) + return CancelableWork(redactWork.id) } override fun sendMedia(attachment: ContentAttachmentData): Cancelable { @@ -105,9 +97,9 @@ internal class DefaultSendService(private val roomId: String, private fun saveLocalEcho(event: Event) { monarchy.tryTransactionAsync { realm -> val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() - ?: return@tryTransactionAsync + ?: return@tryTransactionAsync val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId) - ?: return@tryTransactionAsync + ?: return@tryTransactionAsync roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0) } @@ -128,15 +120,17 @@ internal class DefaultSendService(private val roomId: String, .build() } - private fun createSendRelationWork(event: Event): OneTimeWorkRequest { - //TODO use the new API to send relation (for now use regular send) - val sendContentWorkerParams = SendEventWorker.Params( - roomId, event) - val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) + private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest { - return OneTimeWorkRequestBuilder() + //TODO create local echo of m.room.redaction event? + + val sendContentWorkerParams = RedactEventWorker.Params( + roomId, event.eventId!!, reason) + val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams) + + return OneTimeWorkRequestBuilder() .setConstraints(WORK_CONSTRAINTS) - .setInputData(sendWorkData) + .setInputData(redactWorkData) .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) .build() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt new file mode 100644 index 00000000..e0bc740e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/RedactEventWorker.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2019 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.matrix.android.internal.session.room.send + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.internal.di.MatrixKoinComponent +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.util.WorkerParamsFactory +import org.koin.standalone.inject +import java.util.* + +internal class RedactEventWorker(context: Context, params: WorkerParameters) + : Worker(context, params), MatrixKoinComponent { + + @JsonClass(generateAdapter = true) + internal data class Params( + val roomId: String, + val eventId: String, + val reason: String? + ) + + private val roomAPI by inject() + + override fun doWork(): Result { + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.failure() + + if (params.eventId == null) { + return Result.failure() + } + val txID = UUID.randomUUID().toString() + + val result = executeRequest { + apiCall = roomAPI.redactEvent( + txID, + params.roomId, + params.eventId, + if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) + ) + } + return result.fold({ + when (it) { + is Failure.NetworkConnection -> Result.retry() + else -> Result.failure() + } + }, { + Result.success() + }) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 98a1ab30..378880f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -92,7 +92,7 @@ internal class DefaultTimeline( private lateinit var eventRelations: RealmResults - private val eventsChangeListener = OrderedRealmCollectionChangeListener> { _, changeSet -> + private val eventsChangeListener = OrderedRealmCollectionChangeListener> { results, changeSet -> if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) { handleInitialLoad() } else { @@ -122,8 +122,22 @@ internal class DefaultTimeline( buildTimelineEvents(startDisplayIndex, direction, range.length.toLong()) postSnapshot() } - } + + var hasChanged = false + changeSet.changes.forEach {index -> + val eventEntity = results[index] + eventEntity?.eventId?.let { eventId -> + builtEventsIdMap[eventId]?.let { builtIndex -> + //Update the relation of existing event + builtEvents[builtIndex]?.let { te -> + builtEvents[builtIndex] = timelineEventFactory.create(eventEntity) + hasChanged = true + } + } + } + } + if (hasChanged) postSnapshot() } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index 4461ce95..b6ebf330 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -28,7 +28,10 @@ sealed class RoomDetailActions { data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions() data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions() data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() + data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() + data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() object AcceptInvite : RoomDetailActions() object RejectInvite : RoomDetailActions() + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index 91e77a61..c9c013a8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -521,7 +521,8 @@ class RoomDetailFragment : //we should test the current real state of reaction on this event roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, informationData.eventId)) } else { - //TODO it's an undo :/ + //I need to redact a reaction + roomDetailViewModel.process(RoomDetailActions.UndoReaction(informationData.eventId,reaction)) } } @@ -546,7 +547,11 @@ class RoomDetailFragment : snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color)) snack.show() } - MessageMenuViewModel.ACTION_SHARE -> { + MessageMenuViewModel.ACTION_DELETE -> { + val eventId = actionData.data?.toString() ?: return + roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId,context?.getString(R.string.event_redacted_by_user_reason))) + } + MessageMenuViewModel.ACTION_SHARE -> { //TODO current data communication is too limited //Need to now the media type actionData.data?.toString()?.let { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 6b6ac2cc..15a5e2b5 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -80,6 +80,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, is RoomDetailActions.SendReaction -> handleSendReaction(action) is RoomDetailActions.AcceptInvite -> handleAcceptInvite() is RoomDetailActions.RejectInvite -> handleRejectInvite() + is RoomDetailActions.RedactAction -> handleRedactEvent(action) } } @@ -190,6 +191,16 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, room.sendReaction(action.reaction, action.targetEventId) } + private fun handleRedactEvent(action: RoomDetailActions.RedactAction) { + val event = room.getTimeLineEvent(action.targetEventId) ?: return + room.redactEvent(event.root, action.reason) + } + + private fun handleUndoReact(action: RoomDetailActions.UndoReaction) { + room.undoReaction(action.key, action.targetEventId, session.sessionParams.credentials.userId) + } + + private fun handleSendMedia(action: RoomDetailActions.SendMedia) { val attachments = action.mediaFiles.map { ContentAttachmentData( diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 9f523062..67e45bc7 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -73,6 +73,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel() @@ -347,6 +350,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } } + private fun buildRedactedItem(informationData: MessageInformationData): RedactedMessageItem? { + return RedactedMessageItem_() + .informationData(informationData) + } + private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence { val spannable = SpannableStringBuilder(body) MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index 6db0e0eb..ee0f6e41 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -55,11 +55,11 @@ abstract class AbsMessageItem : BaseEventItem() { var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { override fun onReacted(reactionButton: ReactionButton) { - reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString,true) + reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString, true) } override fun onUnReacted(reactionButton: ReactionButton) { - reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString,false) + reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString, false) } } @@ -123,10 +123,6 @@ abstract class AbsMessageItem : BaseEventItem() { } } - override fun unbind(holder: H) { - super.unbind(holder) - } - protected fun View.renderSendState() { isClickable = informationData.sendState.isSent() alpha = if (informationData.sendState.isSent()) 1f else 0.5f diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt new file mode 100644 index 00000000..e7132844 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt @@ -0,0 +1,22 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.item + +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotredesign.R + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base) +abstract class RedactedMessageItem : AbsMessageItem() { + + @EpoxyAttribute + override lateinit var informationData: MessageInformationData + + override fun getStubType(): Int = STUB_ID + + class Holder : AbsMessageItem.Holder() { + override fun getStubId(): Int = STUB_ID + } + + companion object { + private val STUB_ID = R.id.messageContentRedactedStub + } +} \ No newline at end of file diff --git a/vector/src/main/res/drawable/redacted_background.xml b/vector/src/main/res/drawable/redacted_background.xml new file mode 100644 index 00000000..8538e159 --- /dev/null +++ b/vector/src/main/res/drawable/redacted_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index dc2e2ed6..806e18e3 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -81,6 +81,12 @@ android:layout="@layout/item_timeline_event_file_stub" tools:ignore="MissingConstraints" /> + diff --git a/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml b/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml new file mode 100644 index 00000000..948e6ea6 --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 45131c75..8a9e11d9 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -13,4 +13,7 @@ Like Add Reaction + Event deleted by user + Event moderated by room admin + \ No newline at end of file From 3fa9d7a1d4045dbe99c60d1364ca40233df45802 Mon Sep 17 00:00:00 2001 From: Valere Date: Sat, 18 May 2019 09:54:49 +0200 Subject: [PATCH 02/11] Fix formatting --- .../home/room/detail/timeline/action/MessageMenuViewModel.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index 67e45bc7..c39da484 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -73,7 +73,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel Date: Sun, 19 May 2019 12:06:09 +0200 Subject: [PATCH 03/11] Fix / day separator flicker when adding reaction When adding a reaction, the tmp local echo force the display of a new 'day separator' at the bottom if there was no new message for this day yet (then disappears just after -flicker-) --- .../room/annotation/DefaultReactionService.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt index 1afbe7df..ddaeedfd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt @@ -21,11 +21,6 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.annotation.ReactionService import im.vector.matrix.android.api.util.Cancelable -import im.vector.matrix.android.internal.database.helper.addSendingEvent -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom -import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.RedactEventWorker import im.vector.matrix.android.internal.session.room.send.SendEventWorker @@ -33,7 +28,6 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.WorkerParamsFactory -import im.vector.matrix.android.internal.util.tryTransactionAsync import java.util.concurrent.TimeUnit private const val REACTION_WORK = "REACTION_WORK" @@ -52,9 +46,10 @@ internal class DefaultReactionService(private val roomId: String, override fun sendReaction(reaction: String, targetEventId: String): Cancelable { - val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction).also { - saveLocalEcho(it) - } + val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) +// .also { +// //saveLocalEcho(it) +// } val sendRelationWork = createSendRelationWork(event) WorkManager.getInstance() .beginUniqueWork(buildWorkIdentifier(REACTION_WORK), ExistingWorkPolicy.APPEND, sendRelationWork) @@ -104,16 +99,16 @@ internal class DefaultReactionService(private val roomId: String, return "${roomId}_$identifier" } - private fun saveLocalEcho(event: Event) { - monarchy.tryTransactionAsync { realm -> - val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() - ?: return@tryTransactionAsync - val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId) - ?: return@tryTransactionAsync - - roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0) - } - } +// private fun saveLocalEcho(event: Event) { +// monarchy.tryTransactionAsync { realm -> +// val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() +// ?: return@tryTransactionAsync +// val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId = roomId) +// ?: return@tryTransactionAsync +// +// roomEntity.addSendingEvent(event, liveChunk.forwardsStateIndex ?: 0) +// } +// } //TODO duplicate with send service? private fun createRedactEventWork(eventId: String, reason: String?): OneTimeWorkRequest { From 054d339b48c75fa6009cbbdc6efacc3ef090a5e1 Mon Sep 17 00:00:00 2001 From: Valere Date: Sun, 19 May 2019 12:07:27 +0200 Subject: [PATCH 04/11] Fix / Reaction stays highlighted when undone When undoing my reaction, the reactji stays selected as if i have done the reaction --- .../android/internal/session/SessionModule.kt | 2 +- .../internal/session/room/prune/EventsPruner.kt | 5 +++-- .../internal/session/room/prune/PruneEventWorker.kt | 13 ++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index b9889b73..1a5594f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -154,7 +154,7 @@ internal class SessionModule(private val sessionParams: SessionParams) { scope(DefaultSession.SCOPE) { val groupSummaryUpdater = GroupSummaryUpdater(get()) - val eventsPruner = EventsPruner(get()) + val eventsPruner = EventsPruner(get(), get()) val userEntityUpdater = UserEntityUpdater(get(), get(), get()) listOf(groupSummaryUpdater, eventsPruner, userEntityUpdater) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt index d6792f30..36e280bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt @@ -20,6 +20,7 @@ import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.mapper.asDomain @@ -29,7 +30,7 @@ import im.vector.matrix.android.internal.util.WorkerParamsFactory private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER" -internal class EventsPruner(monarchy: Monarchy) : +internal class EventsPruner(monarchy: Monarchy, private val credentials: Credentials) : RealmLiveEntityObserver(monarchy) { override val query = Monarchy.Query { EventEntity.where(it, type = EventType.REDACTION) } @@ -38,7 +39,7 @@ internal class EventsPruner(monarchy: Monarchy) : val redactionEvents = inserted .mapNotNull { it.asDomain() } - val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents) + val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents, credentials.userId) val workData = WorkerParamsFactory.toData(pruneEventWorkerParams) val sendWork = OneTimeWorkRequestBuilder() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt index a65988a5..08a1491e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt @@ -40,13 +40,15 @@ import io.realm.Realm import org.koin.standalone.inject import timber.log.Timber +//TODO should be a task instead of worker internal class PruneEventWorker(context: Context, workerParameters: WorkerParameters ) : Worker(context, workerParameters), MatrixKoinComponent { @JsonClass(generateAdapter = true) internal class Params( - val redactionEvents: List + val redactionEvents: List, + val userId: String ) private val monarchy by inject() @@ -57,7 +59,7 @@ internal class PruneEventWorker(context: Context, val result = monarchy.tryTransactionSync { realm -> params.redactionEvents.forEach { event -> - pruneEvent(realm, event) + pruneEvent(realm, event, params.userId) } } return result.fold({ @@ -67,7 +69,7 @@ internal class PruneEventWorker(context: Context, }) } - private fun pruneEvent(realm: Realm, redactionEvent: Event) { + private fun pruneEvent(realm: Realm, redactionEvent: Event, userId: String) { if (redactionEvent.redacts.isNullOrBlank()) { return } @@ -88,6 +90,7 @@ internal class PruneEventWorker(context: Context, val modified = unsignedData.copy(redactedEvent = redactionEvent) eventToPrune.content = ContentMapper.map(emptyMap()) eventToPrune.unsignedData = MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(modified) + } EventType.REACTION -> { Timber.d("REDACTION of reaction ${eventToPrune.eventId}") @@ -110,6 +113,10 @@ internal class PruneEventWorker(context: Context, summary.sourceEvents.remove(eventToPrune.eventId) Timber.d("Known reactions after ${summary.sourceEvents.joinToString(",")}") summary.count = summary.count - 1 + if (eventToPrune.sender == userId) { + //Was it a redact on my reaction? + summary.addedByMe = false + } if (summary.count == 0) { //delete! summary.deleteFromRealm() From 71e364b42fcb36cc9d7d73d549525badecf7022d Mon Sep 17 00:00:00 2001 From: Valere Date: Sun, 19 May 2019 12:07:40 +0200 Subject: [PATCH 05/11] Fix / Hide reactions on redacted message --- .../timeline/factory/MessageItemFactory.kt | 12 ++++++++++-- .../room/detail/timeline/item/AbsMessageItem.kt | 16 +++++++++++++--- .../detail/timeline/item/RedactedMessageItem.kt | 2 ++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 3217e6e5..b4374c0c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -85,7 +85,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, if (event.root.unsignedData?.redactedEvent != null) { //message is redacted - return buildRedactedItem(informationData) + return buildRedactedItem(informationData, callback) } val messageContent: MessageContent = event.root.content.toModel() ?: return null @@ -350,9 +350,17 @@ class MessageItemFactory(private val colorProvider: ColorProvider, } } - private fun buildRedactedItem(informationData: MessageInformationData): RedactedMessageItem? { + private fun buildRedactedItem(informationData: MessageInformationData, callback: TimelineEventController.Callback?): RedactedMessageItem? { return RedactedMessageItem_() .informationData(informationData) + .avatarClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onAvatarClicked(informationData) + })) + .memberClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onMemberNameClicked(informationData) + })) } private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index ee0f6e41..e24139b0 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -80,17 +80,23 @@ abstract class AbsMessageItem : BaseEventItem() { holder.timeView.text = informationData.time holder.memberNameView.text = informationData.memberName AvatarRenderer.render(informationData.avatarUrl, informationData.senderId, informationData.memberName?.toString(), holder.avatarImageView) + holder.view.setOnClickListener(cellClickListener) + holder.view.setOnLongClickListener(longClickListener) + holder.avatarImageView.setOnLongClickListener(longClickListener) + holder.memberNameView.setOnLongClickListener(longClickListener) } else { holder.avatarImageView.setOnClickListener(null) holder.memberNameView.setOnClickListener(null) holder.avatarImageView.visibility = View.GONE holder.memberNameView.visibility = View.GONE holder.timeView.visibility = View.GONE + holder.view.setOnClickListener(null) + holder.view.setOnLongClickListener(null) + holder.avatarImageView.setOnLongClickListener(null) + holder.memberNameView.setOnLongClickListener(null) } - holder.view.setOnClickListener(cellClickListener) - holder.view.setOnLongClickListener(longClickListener) - if (informationData.orderedReactionList.isNullOrEmpty()) { + if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) { holder.reactionWrapper?.isVisible = false } else { //inflate if needed @@ -123,6 +129,10 @@ abstract class AbsMessageItem : BaseEventItem() { } } + open fun shouldShowReactionAtBottom() : Boolean { + return true + } + protected fun View.renderSendState() { isClickable = informationData.sendState.isSent() alpha = if (informationData.sendState.isSent()) 1f else 0.5f diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt index e7132844..7331a6f3 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/RedactedMessageItem.kt @@ -12,6 +12,8 @@ abstract class RedactedMessageItem : AbsMessageItem( override fun getStubType(): Int = STUB_ID + override fun shouldShowReactionAtBottom() = false + class Holder : AbsMessageItem.Holder() { override fun getStubId(): Int = STUB_ID } From 64c307077feaab3d53f83081c2a2a8985f3ecee6 Mon Sep 17 00:00:00 2001 From: Valere Date: Sun, 19 May 2019 12:31:10 +0200 Subject: [PATCH 06/11] Refactoring / PruneWorker should be a task not a worker --- .../android/internal/session/SessionModule.kt | 2 +- .../internal/session/room/RoomModule.kt | 19 ++++------- .../session/room/prune/EventsPruner.kt | 26 +++++++------- ...{PruneEventWorker.kt => PruneEventTask.kt} | 34 +++++-------------- 4 files changed, 28 insertions(+), 53 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/{PruneEventWorker.kt => PruneEventTask.kt} (87%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 1a5594f2..6640cb2a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -154,7 +154,7 @@ internal class SessionModule(private val sessionParams: SessionParams) { scope(DefaultSession.SCOPE) { val groupSummaryUpdater = GroupSummaryUpdater(get()) - val eventsPruner = EventsPruner(get(), get()) + val eventsPruner = EventsPruner(get(), get(), get(), get()) val userEntityUpdater = UserEntityUpdater(get(), get(), get()) listOf(groupSummaryUpdater, eventsPruner, userEntityUpdater) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index c7df624c..d3abeb81 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -21,24 +21,14 @@ import im.vector.matrix.android.internal.session.room.annotation.DefaultFindReac import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask -import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask -import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask -import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask -import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask -import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask -import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask -import im.vector.matrix.android.internal.session.room.membership.leaving.DefaultLeaveRoomTask -import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask +import im.vector.matrix.android.internal.session.room.prune.PruneEventTask +import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask -import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.PaginationTask -import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor +import im.vector.matrix.android.internal.session.room.timeline.* import org.koin.dsl.module.module import retrofit2.Retrofit @@ -104,6 +94,9 @@ class RoomModule { DefaultFindReactionEventForUndoTask(get()) as FindReactionEventForUndoTask } + scope(DefaultSession.SCOPE) { + DefaultPruneEventTask(get()) as PruneEventTask + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt index 36e280bb..cb7a3818 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt @@ -16,9 +16,6 @@ package im.vector.matrix.android.internal.session.room.prune -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.EventType @@ -26,11 +23,14 @@ import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.util.WorkerParamsFactory +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith -private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER" -internal class EventsPruner(monarchy: Monarchy, private val credentials: Credentials) : +internal class EventsPruner(monarchy: Monarchy, + private val credentials: Credentials, + private val pruneEventTask: PruneEventTask, + private val taskExecutor: TaskExecutor) : RealmLiveEntityObserver(monarchy) { override val query = Monarchy.Query { EventEntity.where(it, type = EventType.REDACTION) } @@ -39,16 +39,14 @@ internal class EventsPruner(monarchy: Monarchy, private val credentials: Credent val redactionEvents = inserted .mapNotNull { it.asDomain() } - val pruneEventWorkerParams = PruneEventWorker.Params(redactionEvents, credentials.userId) - val workData = WorkerParamsFactory.toData(pruneEventWorkerParams) + val params = PruneEventTask.Params( + redactionEvents, + credentials.userId + ) - val sendWork = OneTimeWorkRequestBuilder() - .setInputData(workData) - .build() + pruneEventTask.configureWith(params) + .executeBy(taskExecutor) - WorkManager.getInstance() - .beginUniqueWork(PRUNE_EVENT_WORKER, ExistingWorkPolicy.APPEND, sendWork) - .enqueue() } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt similarity index 87% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index 08a1491e..82949b41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -13,13 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package im.vector.matrix.android.internal.session.room.prune -import android.content.Context -import androidx.work.Worker -import androidx.work.WorkerParameters -import com.squareup.moshi.JsonClass +import arrow.core.Try import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType @@ -32,41 +28,30 @@ import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryE import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.util.WorkerParamsFactory +import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm -import org.koin.standalone.inject import timber.log.Timber -//TODO should be a task instead of worker -internal class PruneEventWorker(context: Context, - workerParameters: WorkerParameters -) : Worker(context, workerParameters), MatrixKoinComponent { - @JsonClass(generateAdapter = true) - internal class Params( +internal interface PruneEventTask : Task { + + data class Params( val redactionEvents: List, val userId: String ) - private val monarchy by inject() +} - override fun doWork(): Result { - val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.failure() +internal class DefaultPruneEventTask(private val monarchy: Monarchy) : PruneEventTask { - val result = monarchy.tryTransactionSync { realm -> + override fun execute(params: PruneEventTask.Params): Try { + return monarchy.tryTransactionSync { realm -> params.redactionEvents.forEach { event -> pruneEvent(realm, event, params.userId) } } - return result.fold({ - Result.retry() - }, { - Result.success() - }) } private fun pruneEvent(realm: Realm, redactionEvent: Event, userId: String) { @@ -154,5 +139,4 @@ internal class PruneEventWorker(context: Context, else -> emptyList() } } - } \ No newline at end of file From 71e50b1bb99ccd4a575cf3fafc9d0d5d330288ba Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 May 2019 09:29:44 +0200 Subject: [PATCH 07/11] Fix / Missing inject after rebase --- .../android/internal/session/room/RoomModule.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index d3abeb81..a8c8eac4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -21,8 +21,16 @@ import im.vector.matrix.android.internal.session.room.annotation.DefaultFindReac import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask -import im.vector.matrix.android.internal.session.room.prune.PruneEventTask +import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask +import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask +import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask +import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask +import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask +import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask +import im.vector.matrix.android.internal.session.room.membership.leaving.DefaultLeaveRoomTask +import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask +import im.vector.matrix.android.internal.session.room.prune.PruneEventTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory @@ -67,7 +75,7 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) + RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } scope(DefaultSession.SCOPE) { From 44d1d063e9096a9077f8f41142cae86303d54282 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 May 2019 09:53:12 +0200 Subject: [PATCH 08/11] Fix / theme update after rebase was displaying emojis grayed out --- .../adapter_item_action_quick_reaction.xml | 5 ++++- .../res/layout/item_timeline_event_base.xml | 6 ++++-- .../item_timeline_event_redacted_stub.xml | 3 +-- .../src/main/res/layout/reaction_button.xml | 19 ++++++++++--------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml b/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml index ba39f58c..fef7b0ee 100644 --- a/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml +++ b/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml @@ -15,6 +15,7 @@ android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" + android:textColor="@color/black" android:textSize="30sp" app:autoSizeTextType="uniform" app:layout_constraintBottom_toTopOf="@id/quick_react_agree_text" @@ -34,10 +35,10 @@ android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" + android:textColor="@color/black" android:textSize="30sp" app:autoSizeTextType="uniform" app:layout_constraintBottom_toBottomOf="@id/quick_react_1_text" - app:layout_constraintEnd_toStartOf="@id/center_guideline" app:layout_constraintStart_toEndOf="@id/quick_react_1_text" app:layout_constraintTop_toTopOf="@id/quick_react_1_text" @@ -73,6 +74,7 @@ android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" + android:textColor="@color/black" android:textSize="30sp" app:autoSizeTextType="uniform" app:layout_constraintBottom_toBottomOf="@+id/quick_react_1_text" @@ -91,6 +93,7 @@ android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true" + android:textColor="@color/black" android:textSize="30sp" app:autoSizeTextType="uniform" app:layout_constraintBottom_toBottomOf="@id/quick_react_3_text" diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 806e18e3..2ec5cf04 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -85,6 +85,8 @@ android:id="@+id/messageContentRedactedStub" style="@style/TimelineContentStubLayoutParams" android:layout_height="20dp" + android:layout_marginEnd="56dp" + android:layout_marginRight="56dp" android:layout="@layout/item_timeline_event_redacted_stub" tools:ignore="MissingConstraints" /> @@ -92,16 +94,16 @@ diff --git a/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml b/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml index 948e6ea6..2f930577 100644 --- a/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_redacted_stub.xml @@ -1,5 +1,4 @@ \ No newline at end of file + android:background="@drawable/redacted_background" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/reaction_button.xml b/vector/src/main/res/layout/reaction_button.xml index 8d66d987..70b841bc 100644 --- a/vector/src/main/res/layout/reaction_button.xml +++ b/vector/src/main/res/layout/reaction_button.xml @@ -2,9 +2,9 @@ + android:layout_height="26dp" + android:clipChildren="false"> + app:layout_constraintTop_toTopOf="@+id/reactionText" /> + app:layout_constraintTop_toTopOf="@+id/reactionText" /> + tools:text="👍" /> Date: Mon, 20 May 2019 12:43:02 +0200 Subject: [PATCH 09/11] Toggle Quick React (agree/disagree like/dislike) --- .../room/model/annotation/ReactionService.kt | 8 ++ .../internal/session/room/RoomFactory.kt | 4 +- .../internal/session/room/RoomModule.kt | 8 +- .../room/annotation/DefaultReactionService.kt | 29 +++++- .../room/annotation/SendRelationWorker.kt | 8 +- .../annotation/UpdateQuickReactionTask.kt | 89 +++++++++++++++++++ .../home/room/detail/RoomDetailActions.kt | 1 + .../home/room/detail/RoomDetailFragment.kt | 59 +++++------- .../home/room/detail/RoomDetailViewModel.kt | 53 ++++++----- .../action/MessageActionsBottomSheet.kt | 9 +- .../timeline/action/QuickReactionFragment.kt | 6 +- .../timeline/action/QuickReactionViewModel.kt | 10 +++ .../detail/timeline/item/AbsMessageItem.kt | 2 +- 13 files changed, 214 insertions(+), 72 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/UpdateQuickReactionTask.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt index 154401f9..750b9c4d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt @@ -35,4 +35,12 @@ interface ReactionService { */ fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable + + /** + * Undo a reaction (emoji) to the targetedEvent. + * @param reaction the reaction (preferably emoji) + * @param targetEventId the id of the event being reacted + */ + fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 1f5cd18d..c59e3f8d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask import im.vector.matrix.android.internal.session.room.annotation.DefaultReactionService +import im.vector.matrix.android.internal.session.room.annotation.UpdateQuickReactionTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask @@ -48,6 +49,7 @@ internal class RoomFactory(private val monarchy: Monarchy, private val contextOfEventTask: GetContextOfEventTask, private val setReadMarkersTask: SetReadMarkersTask, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, + private val updateQuickReactionTask: UpdateQuickReactionTask, private val joinRoomTask: JoinRoomTask, private val leaveRoomTask: LeaveRoomTask) { @@ -56,7 +58,7 @@ internal class RoomFactory(private val monarchy: Monarchy, val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor()) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask) val sendService = DefaultSendService(roomId, eventFactory, monarchy) - val reactionService = DefaultReactionService(roomId, eventFactory, monarchy, findReactionEventForUndoTask, taskExecutor) + val reactionService = DefaultReactionService(roomId, eventFactory, findReactionEventForUndoTask, updateQuickReactionTask, taskExecutor) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index a8c8eac4..8473f45e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -18,7 +18,9 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.room.annotation.DefaultFindReactionEventForUndoTask +import im.vector.matrix.android.internal.session.room.annotation.DefaultUpdateQuickReactionTask import im.vector.matrix.android.internal.session.room.annotation.FindReactionEventForUndoTask +import im.vector.matrix.android.internal.session.room.annotation.UpdateQuickReactionTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask @@ -75,7 +77,7 @@ class RoomModule { } scope(DefaultSession.SCOPE) { - RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) + RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } scope(DefaultSession.SCOPE) { @@ -102,6 +104,10 @@ class RoomModule { DefaultFindReactionEventForUndoTask(get()) as FindReactionEventForUndoTask } + scope(DefaultSession.SCOPE) { + DefaultUpdateQuickReactionTask(get()) as UpdateQuickReactionTask + } + scope(DefaultSession.SCOPE) { DefaultPruneEventTask(get()) as PruneEventTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt index ddaeedfd..dbef5461 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/DefaultReactionService.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session.room.annotation import androidx.work.* -import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.annotation.ReactionService @@ -39,8 +38,8 @@ private val WORK_CONSTRAINTS = Constraints.Builder() internal class DefaultReactionService(private val roomId: String, private val eventFactory: LocalEchoEventFactory, - private val monarchy: Monarchy, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, + private val updateQuickReactionTask: UpdateQuickReactionTask, private val taskExecutor: TaskExecutor) : ReactionService { @@ -95,6 +94,32 @@ internal class DefaultReactionService(private val roomId: String, } + + override fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) { + + val params = UpdateQuickReactionTask.Params( + roomId, + targetEventId, + reaction, + oppositeReaction, + myUserId + ) + + updateQuickReactionTask.configureWith(params) + .dispatchTo(object : MatrixCallback { + override fun onSuccess(data: UpdateQuickReactionTask.Result) { + data.reactionToAdd?.also { sendReaction(it, targetEventId) } + data.reactionToRedact.forEach { + val redactWork = createRedactEventWork(it, null) + WorkManager.getInstance() + .beginUniqueWork(buildWorkIdentifier(REACTION_WORK), ExistingWorkPolicy.APPEND, redactWork) + .enqueue() + } + } + }) + .executeBy(taskExecutor) + } + private fun buildWorkIdentifier(identifier: String): String { return "${roomId}_$identifier" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt index 7ce871a0..e262dcaf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/SendRelationWorker.kt @@ -19,6 +19,7 @@ import android.content.Context import androidx.work.Worker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.annotation.ReactionContent @@ -66,6 +67,11 @@ class SendRelationWorker(context: Context, params: WorkerParameters) content = localEvent.content ) } - return result.fold({ Result.retry() }, { Result.success() }) + return result.fold({ + when (it) { + is Failure.NetworkConnection -> Result.retry() + else -> Result.failure() + } + }, { Result.success() }) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/UpdateQuickReactionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/UpdateQuickReactionTask.kt new file mode 100644 index 00000000..56a8545b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/annotation/UpdateQuickReactionTask.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2019 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.matrix.android.internal.session.room.annotation + +import arrow.core.Try +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields +import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.task.Task +import io.realm.Realm + + +internal interface UpdateQuickReactionTask : Task { + + data class Params( + val roomId: String, + val eventId: String, + val reaction: String, + val oppositeReaction: String, + val myUserId: String + ) + + data class Result( + val reactionToAdd: String?, + val reactionToRedact: List + ) +} + +internal class DefaultUpdateQuickReactionTask(private val monarchy: Monarchy) : UpdateQuickReactionTask { + override fun execute(params: UpdateQuickReactionTask.Params): Try { + return Try { + var res: Pair?>? = null + monarchy.doWithRealm { realm -> + res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId, params.myUserId) + } + UpdateQuickReactionTask.Result(res?.first, res?.second ?: emptyList()) + } + } + + + private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String, myUserId: String): 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) + + //Ok there is already reactions on this event, have we reacted to it + val aggregationForReaction = existingSummary.reactionsSummary.where() + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction) + .findFirst() + val aggregationForOppositeReaction = existingSummary.reactionsSummary.where() + .equalTo(ReactionAggregatedSummaryEntityFields.KEY, oppositeReaction) + .findFirst() + + if (aggregationForReaction == null || !aggregationForReaction.addedByMe) { + //i haven't yet reacted to it, so need to add it, but do I need to redact the opposite? + val toRedact = aggregationForOppositeReaction?.sourceEvents?.mapNotNull { + //find source event + val entity = EventEntity.where(realm, it).findFirst() + if (entity?.sender == myUserId) entity.eventId else null + } + return Pair(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 + val toRedact = aggregationForReaction.sourceEvents.mapNotNull { + //find source event + val entity = EventEntity.where(realm, it).findFirst() + if (entity?.sender == myUserId) entity.eventId else null + } + return Pair(null, toRedact) + } + + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt index b6ebf330..10552231 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailActions.kt @@ -30,6 +30,7 @@ sealed class RoomDetailActions { data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() + data class UpdateQuickReactAction(val targetEventId: String,val selectedReaction: String,val opposite: String) : RoomDetailActions() object AcceptInvite : RoomDetailActions() object RejectInvite : RoomDetailActions() diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index c9c013a8..72141e8a 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -54,11 +54,7 @@ import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent -import im.vector.matrix.android.api.session.room.model.message.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageFileContent -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent +import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.user.model.User import im.vector.riotredesign.R @@ -69,15 +65,7 @@ import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.VectorBaseFragment -import im.vector.riotredesign.core.utils.LiveEvent -import im.vector.riotredesign.core.utils.PERMISSIONS_FOR_TAKING_PHOTO -import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA -import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA -import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA -import im.vector.riotredesign.core.utils.checkPermissions -import im.vector.riotredesign.core.utils.copyToClipboard -import im.vector.riotredesign.core.utils.openCamera -import im.vector.riotredesign.core.utils.shareMedia +import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter @@ -199,11 +187,11 @@ class RoomDetailFragment : if (resultCode == RESULT_OK && data != null) { when (requestCode) { REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) - REACTION_SELECT_REQUEST_CODE -> { + REACTION_SELECT_REQUEST_CODE -> { val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) - ?: return + ?: return val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) - ?: return + ?: return //TODO check if already reacted with that? roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId)) } @@ -367,24 +355,24 @@ class RoomDetailFragment : private fun onSendChoiceClicked(dialogListItem: DialogListItem) { Timber.v("On send choice clicked: $dialogListItem") when (dialogListItem) { - is DialogListItem.SendFile -> { + is DialogListItem.SendFile -> { // launchFileIntent } - is DialogListItem.SendVoice -> { + is DialogListItem.SendVoice -> { //launchAudioRecorderIntent() } - is DialogListItem.SendSticker -> { + is DialogListItem.SendSticker -> { //startStickerPickerActivity() } is DialogListItem.TakePhotoVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) { // launchCamera() } - is DialogListItem.TakePhoto -> + is DialogListItem.TakePhoto -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA)) { openCamera(requireActivity(), CAMERA_VALUE_TITLE, TAKE_IMAGE_REQUEST_CODE) } - is DialogListItem.TakeVideo -> + is DialogListItem.TakeVideo -> if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA)) { // launchNativeVideoRecorder() } @@ -431,20 +419,20 @@ class RoomDetailFragment : private fun renderSendMessageResult(sendMessageResult: SendMessageResult) { when (sendMessageResult) { is SendMessageResult.MessageSent, - is SendMessageResult.SlashCommandHandled -> { + is SendMessageResult.SlashCommandHandled -> { // Clear composer composerEditText.text = null } - is SendMessageResult.SlashCommandError -> { + is SendMessageResult.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is SendMessageResult.SlashCommandUnknown -> { + is SendMessageResult.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is SendMessageResult.SlashCommandResultOk -> { + is SendMessageResult.SlashCommandResultOk -> { // Ignore } - is SendMessageResult.SlashCommandResultError -> { + is SendMessageResult.SlashCommandResultError -> { displayCommandError(sendMessageResult.throwable.localizedMessage) } is SendMessageResult.SlashCommandNotImplemented -> { @@ -522,7 +510,7 @@ class RoomDetailFragment : roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, informationData.eventId)) } else { //I need to redact a reaction - roomDetailViewModel.process(RoomDetailActions.UndoReaction(informationData.eventId,reaction)) + roomDetailViewModel.process(RoomDetailActions.UndoReaction(informationData.eventId, reaction)) } } @@ -536,11 +524,11 @@ class RoomDetailFragment : it?.getContentIfNotHandled()?.let { actionData -> when (actionData.actionId) { - MessageMenuViewModel.ACTION_ADD_REACTION -> { + MessageMenuViewModel.ACTION_ADD_REACTION -> { val eventId = actionData.data?.toString() ?: return startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE) } - MessageMenuViewModel.ACTION_COPY -> { + MessageMenuViewModel.ACTION_COPY -> { //I need info about the current selected message :/ copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false) val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) @@ -549,7 +537,7 @@ class RoomDetailFragment : } MessageMenuViewModel.ACTION_DELETE -> { val eventId = actionData.data?.toString() ?: return - roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId,context?.getString(R.string.event_redacted_by_user_reason))) + roomDetailViewModel.process(RoomDetailActions.RedactAction(eventId, context?.getString(R.string.event_redacted_by_user_reason))) } MessageMenuViewModel.ACTION_SHARE -> { //TODO current data communication is too limited @@ -594,12 +582,13 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok) { dialog, id -> dialog.cancel() } .show() } - MessageMenuViewModel.ACTION_QUICK_REACT -> { - (actionData.data as? Pair)?.let { pairData -> - roomDetailViewModel.process(RoomDetailActions.SendReaction(pairData.second, pairData.first)) + MessageMenuViewModel.ACTION_QUICK_REACT -> { + //eventId,ClickedOn,Opposite + (actionData.data as? Triple)?.let { (eventId, clickedOn, opposite) -> + roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(eventId, clickedOn, opposite)) } } - else -> { + else -> { Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show() } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 15a5e2b5..1c185add 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -72,18 +72,21 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, fun process(action: RoomDetailActions) { when (action) { - is RoomDetailActions.SendMessage -> handleSendMessage(action) - is RoomDetailActions.IsDisplayed -> handleIsDisplayed() - is RoomDetailActions.SendMedia -> handleSendMedia(action) + is RoomDetailActions.SendMessage -> handleSendMessage(action) + is RoomDetailActions.IsDisplayed -> handleIsDisplayed() + is RoomDetailActions.SendMedia -> handleSendMedia(action) is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action) - is RoomDetailActions.LoadMore -> handleLoadMore(action) - is RoomDetailActions.SendReaction -> handleSendReaction(action) - is RoomDetailActions.AcceptInvite -> handleAcceptInvite() - is RoomDetailActions.RejectInvite -> handleRejectInvite() + is RoomDetailActions.LoadMore -> handleLoadMore(action) + is RoomDetailActions.SendReaction -> handleSendReaction(action) + is RoomDetailActions.AcceptInvite -> handleAcceptInvite() + is RoomDetailActions.RejectInvite -> handleRejectInvite() is RoomDetailActions.RedactAction -> handleRedactEvent(action) + is RoomDetailActions.UndoReaction -> handleUndoReact(action) + is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action) } } + private val _sendMessageResultLiveData = MutableLiveData>() val sendMessageResultLiveData: LiveData> get() = _sendMessageResultLiveData @@ -95,63 +98,63 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, val slashCommandResult = CommandParser.parseSplashCommand(action.text) when (slashCommandResult) { - is ParsedCommand.ErrorNotACommand -> { + is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room room.sendTextMessage(action.text) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent)) } - is ParsedCommand.ErrorSyntax -> { + is ParsedCommand.ErrorSyntax -> { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))) } - is ParsedCommand.ErrorEmptySlashCommand -> { + is ParsedCommand.ErrorEmptySlashCommand -> { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/"))) } is ParsedCommand.ErrorUnknownSlashCommand -> { _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))) } - is ParsedCommand.Invite -> { + is ParsedCommand.Invite -> { handleInviteSlashCommand(slashCommandResult) } - is ParsedCommand.SetUserPowerLevel -> { + is ParsedCommand.SetUserPowerLevel -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.ClearScalarToken -> { + is ParsedCommand.ClearScalarToken -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.SetMarkdown -> { + is ParsedCommand.SetMarkdown -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.UnbanUser -> { + is ParsedCommand.UnbanUser -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.BanUser -> { + is ParsedCommand.BanUser -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.KickUser -> { + is ParsedCommand.KickUser -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.JoinRoom -> { + is ParsedCommand.JoinRoom -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.PartRoom -> { + is ParsedCommand.PartRoom -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } - is ParsedCommand.SendEmote -> { + is ParsedCommand.SendEmote -> { room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) } - is ParsedCommand.ChangeTopic -> { + is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) } - is ParsedCommand.ChangeDisplayName -> { + is ParsedCommand.ChangeDisplayName -> { // TODO _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) } @@ -201,6 +204,11 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, } + private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) { + room.updateQuickReaction(action.selectedReaction, action.opposite, action.targetEventId, session.sessionParams.credentials.userId) + } + + private fun handleSendMedia(action: RoomDetailActions.SendMedia) { val attachments = action.mediaFiles.map { ContentAttachmentData( @@ -238,6 +246,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState, room.join(object : MatrixCallback {}) } + private fun observeEventDisplayedActions() { // We are buffering scroll events for one second // and keep the most recent one to set the read receipt on. diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index c1576eef..1d50d893 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -95,13 +95,8 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { .commit() } quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener { - override fun didQuickReactWith(clikedOn: String, reactions: List, eventId: String) { - if (reactions.contains(clikedOn)) { - //it's an add - actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Pair(eventId,clikedOn)) - } else { - //it's a remove - } + override fun didQuickReactWith(clikedOn: String, opposite: String, reactions: List, eventId: String) { + actionHandlerModel.fireAction(MessageMenuViewModel.ACTION_QUICK_REACT, Triple(eventId, clikedOn, opposite)) dismiss() } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt index fdae2246..eac10a90 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt @@ -119,12 +119,14 @@ class QuickReactionFragment : BaseMvRxFragment() { } if (it.selectionResult != null) { - interactionListener?.didQuickReactWith(it.selectionResult.first, it.selectionResult.second, it.eventId) + val clikedOn = it.selectionResult.first + interactionListener?.didQuickReactWith(clikedOn, QuickReactionViewModel.getOpposite(clikedOn) + ?: "", it.selectionResult.second, it.eventId) } } interface InteractionListener { - fun didQuickReactWith(clikedOn: String, reactions: List, eventId: String) + fun didQuickReactWith(clikedOn: String, opposite: String, reactions: List, eventId: String) } companion object { diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt index 8284c16c..36a07bee 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt @@ -110,6 +110,16 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel val likePositive = "🙂" val likeNegative = "😔" + fun getOpposite(reaction: String): String? { + return when (reaction) { + agreePositive -> agreeNegative + agreeNegative -> agreePositive + likePositive -> likeNegative + likeNegative -> likePositive + else -> null + } + } + override fun initialState(viewModelContext: ViewModelContext): QuickReactionState? { // Args are accessible from the context. // val foo = vieWModelContext.args.foo diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt index e24139b0..4e219fad 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -125,7 +125,7 @@ abstract class AbsMessageItem : BaseEventItem() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && !holder.view.isInLayout) { holder.reactionFlowHelper?.requestLayout() } - + holder.reactionWrapper?.setOnLongClickListener(longClickListener) } } From 70c4b7528d73d44d82d4d030b6004ff259c98821 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 May 2019 12:49:35 +0200 Subject: [PATCH 10/11] Fix doc --- .../api/session/room/model/annotation/ReactionService.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt index 750b9c4d..ace61159 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/annotation/ReactionService.kt @@ -32,14 +32,20 @@ interface ReactionService { * Undo a reaction (emoji) to the targetedEvent. * @param reaction the reaction (preferably emoji) * @param targetEventId the id of the event being reacted + * @param myUserId used to know if a reaction event was made by the user */ fun undoReaction(reaction: String, targetEventId: String, myUserId: String)//: Cancelable /** - * Undo a reaction (emoji) to the targetedEvent. + * Update a quick reaction (toggle). + * If you have reacted with agree and then you click on disagree, this call will delete(redact) + * the disagree and add the agree + * If you click on a reaction that you already reacted with, it will undo it * @param reaction the reaction (preferably emoji) + * @param oppositeReaction the opposite reaction(preferably emoji) * @param targetEventId the id of the event being reacted + * @param myUserId used to know if a reaction event was made by the user */ fun updateQuickReaction(reaction: String, oppositeReaction: String, targetEventId: String, myUserId: String) From 2da4823e33051a126c4d3d52d034a4b62b3063be Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 20 May 2019 13:58:45 +0200 Subject: [PATCH 11/11] Fix / crash on logout cannot access deleted object from delete --- .../session/room/timeline/DefaultTimeline.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 378880f4..6743d1cb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -157,18 +157,6 @@ internal class DefaultTimeline( } } } - changeSet.deletions?.forEach { - val eventRelations = collection[it] - if (eventRelations != null) { - builtEventsIdMap[eventRelations.eventId]?.let { builtIndex -> - //Update the relation of existing event - builtEvents[builtIndex]?.let { te -> - builtEvents[builtIndex] = te.copy(annotations = null) - hasChange = true - } - } - } - } if (hasChange) postSnapshot() }