diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 152a018e78..6ae42de90c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -132,4 +132,9 @@ interface SendService { * Resend all failed messages one by one (and keep order) */ fun resendAllFailedMessages() + + /** + * Cancel all failed messages + */ + fun cancelAllFailedMessages() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index a12962b51f..c5b8b42b3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -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, compressBeforeSending: Boolean, roomIds: Set): Cancelable { diff --git a/vector/src/main/java/im/vector/app/core/ui/views/FailedMessagesWarningView.kt b/vector/src/main/java/im/vector/app/core/ui/views/FailedMessagesWarningView.kt new file mode 100644 index 0000000000..f9518552a3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/FailedMessagesWarningView.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index efc495a379..ecbd0b0d30 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -106,4 +106,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class DoNotShowPreviewUrlFor(val eventId: String, val url: String) : RoomDetailAction() data class ComposerFocusChange(val focused: Boolean) : RoomDetailAction() + + // Failed messages + object RemoveAllFailedMessages : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 8087a0072f..1df583187e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -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.KnownCallsViewHolder 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.NotificationAreaView 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.reactions.EmojiReactionPickerActivity 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.VectorSettingsActivity import im.vector.app.features.share.SharedData @@ -326,6 +326,7 @@ class RoomDetailFragment @Inject constructor( setupJumpToBottomView() setupConfBannerView() setupEmojiPopup() + setupFailedMessagesWarningView() views.roomToolbarContentView.debouncedClicks { 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) { 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) true } - R.id.resend_all -> { - roomDetailViewModel.handle(RoomDetailAction.ResendAll) - true - } R.id.open_matrix_apps -> { roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations) true @@ -1172,6 +1188,7 @@ class RoomDetailFragment @Inject constructor( val summary = state.asyncRoomSummary() renderToolbar(summary, state.typingMessage) views.activeConferenceView.render(state) + views.failedMessagesWarningView.render(state.hasFailedSending) val inviter = state.asyncInviter() if (summary?.membership == Membership.JOIN) { views.jumpToBottomView.count = summary.notificationCount diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index f293eadf50..cf6862a28d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -322,6 +322,8 @@ class RoomDetailViewModel @AssistedInject constructor( ) } is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) + RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() + RoomDetailAction.ResendAll -> handleResendAll() }.exhaustive } @@ -660,10 +662,8 @@ class RoomDetailViewModel @AssistedInject constructor( return@withState false } when (itemId) { - R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.timeline_setting -> true R.id.invite -> state.canInvite - R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.open_matrix_apps -> true R.id.voice_call, R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() @@ -1223,6 +1223,10 @@ class RoomDetailViewModel @AssistedInject constructor( room.resendAllFailedMessages() } + private fun handleRemoveAllFailedMessages() { + room.cancelAllFailedMessages() + } + private fun observeEventDisplayedActions() { // We are buffering scroll events for one second // and keep the most recent one to set the read receipt on. @@ -1437,7 +1441,7 @@ class RoomDetailViewModel @AssistedInject constructor( roomSummariesHolder.set(summary) setState { val typingMessage = typingHelper.getTypingMessage(summary.typingUsers) - copy(typingMessage = typingMessage) + copy(typingMessage = typingMessage, hasFailedSending = summary.hasFailedSending) } if (summary.membership == Membership.INVITE) { summary.inviterId?.let { inviterId -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 8c2b3ffe98..965733c424 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -75,7 +75,8 @@ data class RoomDetailViewState( val canInvite: Boolean = true, val isAllowedToManageWidgets: Boolean = false, val isAllowedToStartWebRTCCall: Boolean = true, - val showDialerOption: Boolean = false + val showDialerOption: Boolean = false, + val hasFailedSending: Boolean = false ) : MvRxState { constructor(args: RoomDetailArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index 9dc777676a..b215fa5dd5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -28,7 +28,6 @@ import com.airbnb.epoxy.EpoxyModelClass 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.ContentUploadStateTrackerBinder -import org.matrix.android.sdk.api.session.room.send.SendState @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageFileItem : AbsMessageItem() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index e0085105d2..2c0d1fcfbd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.item import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.ProgressBar import androidx.core.view.ViewCompat import androidx.core.view.isVisible 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.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder 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) abstract class MessageImageVideoItem : AbsMessageItem() { diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index df36d7e225..a6909a2893 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -86,7 +86,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="top" - app:constraint_referenced_ids="composerLayout,notificationAreaView" /> + app:constraint_referenced_ids="composerLayout,notificationAreaView, failedMessagesWarningView" /> + + + app:constraint_referenced_ids="composerLayout,notificationAreaView, failedMessagesWarningView" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index 1f4e2736b1..e3208c4cc4 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -52,22 +52,6 @@ app:actionLayout="@layout/custom_action_item_layout_badge" app:showAsAction="ifRoom" /> - - - - Edit Reply - "Retry" + Retry "Join a room to start using the app." "Sent you an invitation" Invited by %s @@ -3243,5 +3243,9 @@ Sending Sent Failed + Delete all failed messages Do you want to cancel sending message? + Messages failed to send + Delete unsent messages + Are you sure you want to delete all unsent messages in this room?