Message state: allow edit and copy when failed

This commit is contained in:
ganfra 2021-03-10 18:10:03 +01:00
parent bbb5dd06a2
commit 6c8e2f1fd6
6 changed files with 115 additions and 31 deletions

View File

@ -66,11 +66,11 @@ interface RelationService {
/** /**
* Edit a text message body. Limited to "m.text" contentType * Edit a text message body. Limited to "m.text" contentType
* @param targetEventId The event to edit * @param targetEvent The event to edit
* @param newBodyText The edited body * @param newBodyText The edited body
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition * @param compatibilityBodyText The text that will appear on clients that don't support yet edition
*/ */
fun editTextMessage(targetEventId: String, fun editTextMessage(targetEvent: TimelineEvent,
msgType: String, msgType: String,
newBodyText: CharSequence, newBodyText: CharSequence,
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,

View File

@ -15,10 +15,7 @@
*/ */
package org.matrix.android.sdk.internal.crypto.tasks package org.matrix.android.sdk.internal.crypto.tasks
import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
@ -27,7 +24,6 @@ import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTa
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.session.room.send.SendResponse
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import java.lang.IllegalStateException
import javax.inject.Inject import javax.inject.Inject
internal interface SendEventTask : Task<SendEventTask.Params, String> { internal interface SendEventTask : Task<SendEventTask.Params, String> {
@ -55,9 +51,6 @@ internal class DefaultSendEventTask @Inject constructor(
val event = handleEncryption(params) val event = handleEncryption(params)
val localId = event.eventId!! val localId = event.eventId!!
if((event.content?.get("body") as? String)?.contains("Fail").orFalse()){
throw IllegalStateException()
}
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING) localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) { val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
apiCall = roomAPI.send( apiCall = roomAPI.send(

View File

@ -17,14 +17,13 @@ package org.matrix.android.sdk.internal.session.room.relation
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.relation.RelationService import org.matrix.android.sdk.api.session.room.model.relation.RelationService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
@ -39,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.configureWith
@ -47,6 +47,7 @@ import timber.log.Timber
internal class DefaultRelationService @AssistedInject constructor( internal class DefaultRelationService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
private val eventEditor: EventEditor,
private val eventSenderProcessor: EventSenderProcessor, private val eventSenderProcessor: EventSenderProcessor,
private val eventFactory: LocalEchoEventFactory, private val eventFactory: LocalEchoEventFactory,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
@ -112,32 +113,19 @@ internal class DefaultRelationService @AssistedInject constructor(
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override fun editTextMessage(targetEventId: String, override fun editTextMessage(targetEvent: TimelineEvent,
msgType: String, msgType: String,
newBodyText: CharSequence, newBodyText: CharSequence,
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,
compatibilityBodyText: String): Cancelable { compatibilityBodyText: String): Cancelable {
val event = eventFactory return eventEditor.editTextMessage(targetEvent, msgType, newBodyText, newBodyAutoMarkdown, compatibilityBodyText)
.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
.also { saveLocalEcho(it) }
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} }
override fun editReply(replyToEdit: TimelineEvent, override fun editReply(replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent, originalTimelineEvent: TimelineEvent,
newBodyText: String, newBodyText: String,
compatibilityBodyText: String): Cancelable { compatibilityBodyText: String): Cancelable {
val event = eventFactory.createReplaceTextOfReply( return eventEditor.editReply(replyToEdit, originalTimelineEvent, newBodyText, compatibilityBodyText)
roomId,
replyToEdit,
originalTimelineEvent,
newBodyText,
true,
MessageType.MSGTYPE_TEXT,
compatibilityBodyText
)
.also { saveLocalEcho(it) }
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} }
override suspend fun fetchEditHistory(eventId: String): List<Event> { override suspend fun fetchEditHistory(eventId: String): List<Event> {

View File

@ -0,0 +1,104 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.room.relation
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
import org.matrix.android.sdk.internal.database.mapper.toEntity
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import timber.log.Timber
import javax.inject.Inject
internal class EventEditor @Inject constructor(private val eventSenderProcessor: EventSenderProcessor,
private val eventFactory: LocalEchoEventFactory,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val localEchoRepository: LocalEchoRepository) {
fun editTextMessage(targetEvent: TimelineEvent,
msgType: String,
newBodyText: CharSequence,
newBodyAutoMarkdown: Boolean,
compatibilityBodyText: String): Cancelable {
val roomId = targetEvent.roomId
if (targetEvent.root.sendState.hasFailed()) {
// We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event.
val editedEvent = eventFactory.createTextEvent(roomId, msgType, newBodyText, newBodyAutoMarkdown).copy(
eventId = targetEvent.eventId
)
updateFailedEchoWithEvent(roomId, targetEvent.eventId, editedEvent)
return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} else if (targetEvent.root.sendState.isSent()) {
val event = eventFactory
.createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
.also { localEchoRepository.createLocalEcho(it) }
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} else {
// Should we throw?
Timber.w("Can't edit a sending event")
return NoOpCancellable
}
}
fun editReply(replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent,
newBodyText: String,
compatibilityBodyText: String): Cancelable {
val roomId = replyToEdit.roomId
if (replyToEdit.root.sendState.hasFailed()) {
// We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event.
val editedEvent = eventFactory.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText, false)?.copy(
eventId = replyToEdit.eventId
) ?: return NoOpCancellable
updateFailedEchoWithEvent(roomId, replyToEdit.eventId, editedEvent)
return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} else if (replyToEdit.root.sendState.isSent()) {
val event = eventFactory.createReplaceTextOfReply(
roomId,
replyToEdit,
originalTimelineEvent,
newBodyText,
true,
MessageType.MSGTYPE_TEXT,
compatibilityBodyText
)
.also { localEchoRepository.createLocalEcho(it) }
return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
} else {
// Should we throw?
Timber.w("Can't edit a sending event")
return NoOpCancellable
}
}
private fun updateFailedEchoWithEvent(roomId: String, failedEchoEventId: String, editedEvent: Event) {
val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
localEchoRepository.updateEchoAsync(failedEchoEventId) { _, entity ->
entity.content = editedEventEntity.content
entity.ageLocalTs = editedEventEntity.ageLocalTs
entity.age = editedEventEntity.age
entity.originServerTs = editedEventEntity.originServerTs
entity.sendState = editedEventEntity.sendState
}
}
}

View File

@ -828,7 +828,7 @@ class RoomDetailViewModel @AssistedInject constructor(
val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
val existingBody = messageContent?.body ?: "" val existingBody = messageContent?.body ?: ""
if (existingBody != action.text) { if (existingBody != action.text) {
room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "", room.editTextMessage(state.sendMode.timelineEvent,
messageContent?.msgType ?: MessageType.MSGTYPE_TEXT, messageContent?.msgType ?: MessageType.MSGTYPE_TEXT,
action.text, action.text,
action.autoMarkdown) action.autoMarkdown)

View File

@ -59,5 +59,4 @@ data class MessageActionState(
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
fun sendState(): SendState? = timelineEvent()?.root?.sendState fun sendState(): SendState? = timelineEvent()?.root?.sendState
} }