Retry and delete options added for all failed messages.

This commit is contained in:
Onuray Sahin 2021-03-01 17:27:52 +03:00 committed by Benoit Marty
parent a3d45bbba8
commit f5f16fd330
13 changed files with 185 additions and 31 deletions

View file

@ -132,4 +132,9 @@ interface SendService {
* Resend all failed messages one by one (and keep order) * Resend all failed messages one by one (and keep order)
*/ */
fun resendAllFailedMessages() fun resendAllFailedMessages()
/**
* Cancel all failed messages
*/
fun cancelAllFailedMessages()
} }

View file

@ -232,6 +232,14 @@ internal class DefaultSendService @AssistedInject constructor(
} }
} }
override fun cancelAllFailedMessages() {
taskExecutor.executorScope.launch {
localEchoRepository.getAllFailedEventsToResend(roomId).forEach { event ->
cancelSend(event.eventId)
}
}
}
override fun sendMedia(attachment: ContentAttachmentData, override fun sendMedia(attachment: ContentAttachmentData,
compressBeforeSending: Boolean, compressBeforeSending: Boolean,
roomIds: Set<String>): Cancelable { roomIds: Set<String>): Cancelable {

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.ui.views
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.databinding.ViewFailedMessagesWarningBinding
class FailedMessagesWarningView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
interface Callback {
fun onDeleteAllClicked()
fun onRetryClicked()
}
var callback: Callback? = null
private lateinit var views: ViewFailedMessagesWarningBinding
init {
setupViews()
}
private fun setupViews() {
inflate(context, R.layout.view_failed_messages_warning, this)
views = ViewFailedMessagesWarningBinding.bind(this)
views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() }
views.failedMessagesRetryButton.setOnClickListener { callback?.onRetryClicked() }
}
fun render(hasFailedMessages: Boolean) {
isVisible = hasFailedMessages
}
}

View file

@ -106,4 +106,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class DoNotShowPreviewUrlFor(val eventId: String, val url: String) : RoomDetailAction() data class DoNotShowPreviewUrlFor(val eventId: String, val url: String) : RoomDetailAction()
data class ComposerFocusChange(val focused: Boolean) : RoomDetailAction() data class ComposerFocusChange(val focused: Boolean) : RoomDetailAction()
// Failed messages
object RemoveAllFailedMessages : RoomDetailAction()
} }

View file

@ -94,6 +94,7 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.KnownCallsViewHolder
import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.ActiveConferenceView
import im.vector.app.core.ui.views.FailedMessagesWarningView
import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.JumpToReadMarkerView
import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.ui.views.NotificationAreaView
import im.vector.app.core.utils.Debouncer import im.vector.app.core.utils.Debouncer
@ -157,7 +158,6 @@ import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.reactions.EmojiReactionPickerActivity import im.vector.app.features.reactions.EmojiReactionPickerActivity
import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.roomprofile.alias.RoomAliasAction
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData import im.vector.app.features.share.SharedData
@ -326,6 +326,7 @@ class RoomDetailFragment @Inject constructor(
setupJumpToBottomView() setupJumpToBottomView()
setupConfBannerView() setupConfBannerView()
setupEmojiPopup() setupEmojiPopup()
setupFailedMessagesWarningView()
views.roomToolbarContentView.debouncedClicks { views.roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
@ -558,6 +559,25 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private fun setupFailedMessagesWarningView() {
views.failedMessagesWarningView.callback = object : FailedMessagesWarningView.Callback {
override fun onDeleteAllClicked() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.event_status_delete_all_failed_dialog_title)
.setMessage(getString(R.string.event_status_delete_all_failed_dialog_message))
.setNegativeButton(R.string.no, null)
.setPositiveButton(R.string.yes) { _, _ ->
roomDetailViewModel.handle(RoomDetailAction.RemoveAllFailedMessages)
}
.show()
}
override fun onRetryClicked() {
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
}
}
}
private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) { private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) {
navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo)) navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo))
} }
@ -777,10 +797,6 @@ class RoomDetailFragment @Inject constructor(
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
true true
} }
R.id.resend_all -> {
roomDetailViewModel.handle(RoomDetailAction.ResendAll)
true
}
R.id.open_matrix_apps -> { R.id.open_matrix_apps -> {
roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations) roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations)
true true
@ -1172,6 +1188,7 @@ class RoomDetailFragment @Inject constructor(
val summary = state.asyncRoomSummary() val summary = state.asyncRoomSummary()
renderToolbar(summary, state.typingMessage) renderToolbar(summary, state.typingMessage)
views.activeConferenceView.render(state) views.activeConferenceView.render(state)
views.failedMessagesWarningView.render(state.hasFailedSending)
val inviter = state.asyncInviter() val inviter = state.asyncInviter()
if (summary?.membership == Membership.JOIN) { if (summary?.membership == Membership.JOIN) {
views.jumpToBottomView.count = summary.notificationCount views.jumpToBottomView.count = summary.notificationCount

View file

@ -322,6 +322,8 @@ class RoomDetailViewModel @AssistedInject constructor(
) )
} }
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages()
RoomDetailAction.ResendAll -> handleResendAll()
}.exhaustive }.exhaustive
} }
@ -660,10 +662,8 @@ class RoomDetailViewModel @AssistedInject constructor(
return@withState false return@withState false
} }
when (itemId) { when (itemId) {
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.timeline_setting -> true R.id.timeline_setting -> true
R.id.invite -> state.canInvite R.id.invite -> state.canInvite
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.open_matrix_apps -> true R.id.open_matrix_apps -> true
R.id.voice_call, R.id.voice_call,
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
@ -1223,6 +1223,10 @@ class RoomDetailViewModel @AssistedInject constructor(
room.resendAllFailedMessages() room.resendAllFailedMessages()
} }
private fun handleRemoveAllFailedMessages() {
room.cancelAllFailedMessages()
}
private fun observeEventDisplayedActions() { private fun observeEventDisplayedActions() {
// We are buffering scroll events for one second // We are buffering scroll events for one second
// and keep the most recent one to set the read receipt on. // and keep the most recent one to set the read receipt on.
@ -1437,7 +1441,7 @@ class RoomDetailViewModel @AssistedInject constructor(
roomSummariesHolder.set(summary) roomSummariesHolder.set(summary)
setState { setState {
val typingMessage = typingHelper.getTypingMessage(summary.typingUsers) val typingMessage = typingHelper.getTypingMessage(summary.typingUsers)
copy(typingMessage = typingMessage) copy(typingMessage = typingMessage, hasFailedSending = summary.hasFailedSending)
} }
if (summary.membership == Membership.INVITE) { if (summary.membership == Membership.INVITE) {
summary.inviterId?.let { inviterId -> summary.inviterId?.let { inviterId ->

View file

@ -75,7 +75,8 @@ data class RoomDetailViewState(
val canInvite: Boolean = true, val canInvite: Boolean = true,
val isAllowedToManageWidgets: Boolean = false, val isAllowedToManageWidgets: Boolean = false,
val isAllowedToStartWebRTCCall: Boolean = true, val isAllowedToStartWebRTCCall: Boolean = true,
val showDialerOption: Boolean = false val showDialerOption: Boolean = false,
val hasFailedSending: Boolean = false
) : MvRxState { ) : MvRxState {
constructor(args: RoomDetailArgs) : this( constructor(args: RoomDetailArgs) : this(

View file

@ -28,7 +28,6 @@ import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import org.matrix.android.sdk.api.session.room.send.SendState
@EpoxyModelClass(layout = R.layout.item_timeline_event_base) @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() { abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {

View file

@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.item
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
@ -29,7 +28,6 @@ import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import org.matrix.android.sdk.api.session.room.send.SendState
@EpoxyModelClass(layout = R.layout.item_timeline_event_base) @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Holder>() { abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Holder>() {

View file

@ -86,7 +86,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="top" app:barrierDirection="top"
app:constraint_referenced_ids="composerLayout,notificationAreaView" /> app:constraint_referenced_ids="composerLayout,notificationAreaView, failedMessagesWarningView" />
<im.vector.app.features.sync.widget.SyncStateView <im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView" android:id="@+id/syncStateView"
@ -159,6 +159,16 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<im.vector.app.core.ui.views.FailedMessagesWarningView
android:id="@+id/failedMessagesWarningView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/composerLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
<im.vector.app.features.home.room.detail.composer.TextComposerView <im.vector.app.features.home.room.detail.composer.TextComposerView
android:id="@+id/composerLayout" android:id="@+id/composerLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -186,7 +196,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:barrierDirection="top" app:barrierDirection="top"
app:constraint_referenced_ids="composerLayout,notificationAreaView" /> app:constraint_referenced_ids="composerLayout,notificationAreaView, failedMessagesWarningView" />
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/activeCallPiPWrap" android:id="@+id/activeCallPiPWrap"

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<View
android:id="@+id/failedMessagesWarningDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/vctr_list_divider_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/failedMessageWarningImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/event_status_a11y_failed"
android:src="@drawable/ic_sending_message_failed"
app:layout_constraintBottom_toBottomOf="@id/failedMessagesRetryButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/failedMessagesRetryButton" />
<TextView
android:id="@+id/failedMessagesWarningTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/event_status_failed_messages_warning"
android:textColor="?riotx_text_primary"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/failedMessageWarningImageView"
app:layout_constraintStart_toEndOf="@id/failedMessageWarningImageView"
app:layout_constraintTop_toTopOf="@id/failedMessageWarningImageView" />
<ImageButton
android:id="@+id/failedMessagesDeleteAllButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/event_status_a11y_delete_all"
android:src="@drawable/ic_delete_unsent_messages"
app:layout_constraintBottom_toBottomOf="@id/failedMessagesRetryButton"
app:layout_constraintEnd_toStartOf="@id/failedMessagesRetryButton"
app:layout_constraintTop_toTopOf="@id/failedMessagesRetryButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/failedMessagesRetryButton"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"
android:text="@string/global_retry"
android:textSize="14sp"
app:icon="@drawable/ic_retry_sending_messages"
app:iconTint="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/failedMessagesWarningDivider" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -52,22 +52,6 @@
app:actionLayout="@layout/custom_action_item_layout_badge" app:actionLayout="@layout/custom_action_item_layout_badge"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/resend_all"
android:icon="@drawable/ic_refresh_cw"
android:title="@string/room_prompt_resend"
android:visible="false"
app:showAsAction="never"
tools:visible="true" />
<item
android:id="@+id/clear_all"
android:icon="@drawable/ic_trash"
android:title="@string/room_prompt_cancel"
android:visible="false"
app:showAsAction="never"
tools:visible="true" />
<item <item
android:id="@+id/dev_tools" android:id="@+id/dev_tools"
android:icon="@drawable/ic_settings_root_general" android:icon="@drawable/ic_settings_root_general"

View file

@ -2051,7 +2051,7 @@
<string name="edit">Edit</string> <string name="edit">Edit</string>
<string name="reply">Reply</string> <string name="reply">Reply</string>
<string name="global_retry">"Retry"</string> <string name="global_retry">Retry</string>
<string name="room_list_empty">"Join a room to start using the app."</string> <string name="room_list_empty">"Join a room to start using the app."</string>
<string name="send_you_invite">"Sent you an invitation"</string> <string name="send_you_invite">"Sent you an invitation"</string>
<string name="invited_by">Invited by %s</string> <string name="invited_by">Invited by %s</string>
@ -3243,5 +3243,9 @@
<string name="event_status_a11y_sending">Sending</string> <string name="event_status_a11y_sending">Sending</string>
<string name="event_status_a11y_sent">Sent</string> <string name="event_status_a11y_sent">Sent</string>
<string name="event_status_a11y_failed">Failed</string> <string name="event_status_a11y_failed">Failed</string>
<string name="event_status_a11y_delete_all">Delete all failed messages</string>
<string name="event_status_cancel_sending_dialog_message">Do you want to cancel sending message?</string> <string name="event_status_cancel_sending_dialog_message">Do you want to cancel sending message?</string>
<string name="event_status_failed_messages_warning">Messages failed to send</string>
<string name="event_status_delete_all_failed_dialog_title">Delete unsent messages</string>
<string name="event_status_delete_all_failed_dialog_message">Are you sure you want to delete all unsent messages in this room?</string>
</resources> </resources>