Support cancel sending and resend event with attachments

Avoid auto retry for medium and big files
This commit is contained in:
Valere 2020-08-07 16:54:39 +02:00
parent 8b8855d2d5
commit a888e1e80e
6 changed files with 95 additions and 6 deletions

View File

@ -115,7 +115,7 @@ dependencies {
def coroutines_version = "1.3.8"
def markwon_version = '3.1.0'
def daggerVersion = '2.25.4'
def work_version = '2.3.3'
def work_version = '2.4.0'
def retrofit_version = '2.6.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2020 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 im.vector.matrix.android.internal.session.SessionScope
import javax.inject.Inject
/**
* We cannot use work manager cancellation mechanism because cancelling a work will just ignore
* any follow up send that was already queued.
* We use this class to track cancel requests, the workers will look for this to check for cancellation request
* and just ignore the work request and continue by returning success.
*
* Known limitation, for now requests are not persisted
*/
@SessionScope
class CancelSendTracker @Inject constructor() {
data class Request(
val localId: String,
val roomId: String
)
private val cancellingRequests = ArrayList<Request>()
fun markLocalEchoForCancel(eventId: String, roomId: String) {
synchronized(cancellingRequests) {
cancellingRequests.add(Request(eventId, roomId))
}
}
fun isCancelRequestedFor(eventId: String?, roomId: String?): Boolean {
val index = synchronized(cancellingRequests) {
cancellingRequests.indexOfFirst { it.localId == eventId && it.roomId == roomId }
}
return index != -1
}
fun markCancelled(eventId: String, roomId: String) {
synchronized(cancellingRequests) {
val index = cancellingRequests.indexOfFirst { it.localId == eventId && it.roomId == roomId }
if (index != -1) {
cancellingRequests.removeAt(index)
}
}
}
}

View File

@ -55,6 +55,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class ResendMessage(val eventId: String) : RoomDetailAction()
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
data class CancelSend(val eventId: String) : RoomDetailAction()
data class ReplyToOptions(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction()

View File

@ -1617,6 +1617,9 @@ class RoomDetailFragment @Inject constructor(
is EventSharedAction.Remove -> {
roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
}
is EventSharedAction.Cancel -> {
roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId))
}
is EventSharedAction.ReportContentSpam -> {
roomDetailViewModel.handle(RoomDetailAction.ReportContent(
action.eventId, action.senderId, "This message is spam", spam = true))

View File

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail
import android.net.Uri
import androidx.annotation.IdRes
import androidx.lifecycle.viewModelScope
import androidx.work.WorkManager
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
@ -282,6 +283,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action)
}.exhaustive
}
@ -1051,9 +1053,9 @@ class RoomDetailViewModel @AssistedInject constructor(
return
}
when {
it.root.isTextMessage() -> room.resendTextMessage(it)
it.root.isImageMessage() -> room.resendMediaMessage(it)
else -> {
it.root.isTextMessage() -> room.resendTextMessage(it)
it.root.isAttachmentMessage() -> room.resendMediaMessage(it)
else -> {
// TODO
}
}
@ -1072,6 +1074,18 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
private fun handleCancel(action: RoomDetailAction.CancelSend) {
val targetEventId = action.eventId
room.getTimeLineEvent(targetEventId)?.let {
// State must be UNDELIVERED or Failed
if (!it.root.sendState.isSending()) {
Timber.e("Cannot resend message, it is not failed, Cancel first")
return
}
room.cancelSend(action.eventId)
}
}
private fun handleClearSendQueue() {
room.clearSendingQueue()
}

View File

@ -230,6 +230,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
add(EventSharedAction.Resend(eventId))
}
add(EventSharedAction.Remove(eventId))
if (vectorPreferences.developerMode()) {
add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent()))
if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult != null) {
val decryptedContent = timelineEvent.root.toClearContentStringWithIndent()
?: stringProvider.getString(R.string.encryption_information_decryption_error)
add(EventSharedAction.ViewDecryptedSource(decryptedContent))
}
}
} else if (timelineEvent.root.sendState.isSending()) {
// TODO is uploading attachment?
if (canCancel(timelineEvent)) {
@ -321,7 +329,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
private fun canCancel(@Suppress("UNUSED_PARAMETER") event: TimelineEvent): Boolean {
return false
return true
}
private fun canReply(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
@ -365,7 +373,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
}
private fun canRetry(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
return event.root.sendState.hasFailed() && event.root.isTextMessage() && actionPermissions.canSendMessage
return event.root.sendState.hasFailed()
&& actionPermissions.canSendMessage
&& (event.root.isAttachmentMessage() || event.root.isTextMessage())
}
private fun canViewReactions(event: TimelineEvent): Boolean {