From 5022d81d9a2a24b99270d27094ebd4e96ca7b7f9 Mon Sep 17 00:00:00 2001 From: Nicholas Tinsley Date: Fri, 5 Jan 2024 13:36:31 -0500 Subject: [PATCH] Allow canceling media attachment send. --- .../components/AlbumThumbnailView.java | 28 ++++---- .../components/BorderlessImageView.java | 5 +- .../components/ConversationItemFooter.java | 6 +- .../components/ConversationItemThumbnail.kt | 12 ++-- .../ConversationItemThumbnailState.kt | 16 ++--- .../LinkPreviewViewThumbnailState.kt | 2 +- .../securesms/components/ThumbnailView.java | 34 +++++----- .../transfercontrols/TransferControlView.kt | 64 +++++++++---------- .../TransferControlViewState.kt | 4 +- .../transfercontrols/TransferProgressView.kt | 9 +-- .../conversation/ConversationItem.java | 34 +++------- .../AttachmentCancelClickListener.kt | 43 +++++++++++++ .../clicklisteners/ResendClickListener.kt | 28 ++++++++ .../jobs/AttachmentCompressionJob.java | 49 +++++++++++++- .../securesms/jobs/AttachmentDownloadJob.java | 30 +++++++-- .../securesms/jobs/AttachmentUploadJob.kt | 46 ++++++++++++- .../mediasend/MediaUploadRepository.java | 1 + 17 files changed, 278 insertions(+), 133 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/AttachmentCancelClickListener.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/ResendClickListener.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java index d514d11336..9ceeaf091c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java @@ -30,9 +30,9 @@ import java.util.List; public class AlbumThumbnailView extends FrameLayout { private @Nullable SlideClickListener thumbnailClickListener; - private @Nullable SlidesClickedListener downloadClickListener; - private @Nullable SlidesClickedListener cancelDownloadClickListener; - private @Nullable SlideClickListener playVideoClickListener; + private @Nullable SlidesClickedListener startTransferClickListener; + private @Nullable SlidesClickedListener cancelTransferClickListener; + private @Nullable SlideClickListener playVideoClickListener; private int currentSizeClass; @@ -72,16 +72,16 @@ public class AlbumThumbnailView extends FrameLayout { if (showControls) { transferControlsStub.get().setShowSecondaryText(true); - transferControlsStub.get().setDownloadClickListener( + transferControlsStub.get().setTransferClickListener( v -> { - if (downloadClickListener != null) { - downloadClickListener.onClick(v, slides); + if (startTransferClickListener != null) { + startTransferClickListener.onClick(v, slides); } }); transferControlsStub.get().setCancelClickListener( v -> { - if (cancelDownloadClickListener != null) { - cancelDownloadClickListener.onClick(v, slides); + if (cancelTransferClickListener != null) { + cancelTransferClickListener.onClick(v, slides); } }); transferControlsStub.get().setSlides(slides); @@ -117,12 +117,12 @@ public class AlbumThumbnailView extends FrameLayout { thumbnailClickListener = listener; } - public void setDownloadClickListener(SlidesClickedListener listener) { - this.downloadClickListener = listener; + public void setStartTransferClickListener(SlidesClickedListener listener) { + this.startTransferClickListener = listener; } - public void setCancelDownloadClickListener(SlidesClickedListener listener) { - this.cancelDownloadClickListener = listener; + public void setCancelTransferClickListener(SlidesClickedListener listener) { + this.cancelTransferClickListener = listener; } public void setPlayVideoClickListener(SlideClickListener listener) { @@ -288,8 +288,8 @@ public class AlbumThumbnailView extends FrameLayout { ThumbnailView cell = findViewById(id); cell.showSecondaryText(false); cell.setThumbnailClickListener(defaultThumbnailClickListener); - cell.setDownloadClickListener(downloadClickListener); - cell.setCancelDownloadClickListener(cancelDownloadClickListener); + cell.setStartTransferClickListener(startTransferClickListener); + cell.setCancelTransferClickListener(cancelTransferClickListener); if (MediaUtil.isInstantVideoSupported(slide)) { cell.setPlayVideoClickListener(playVideoClickListener); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java index 2366c4ef15..4ced566fb6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/BorderlessImageView.java @@ -9,9 +9,6 @@ import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.load.resource.bitmap.CenterInside; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; @@ -74,6 +71,6 @@ public class BorderlessImageView extends FrameLayout { } public void setDownloadClickListener(@NonNull SlidesClickedListener listener) { - image.setDownloadClickListener(listener); + image.setStartTransferClickListener(listener); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index 63a2bce264..1c2dc80c43 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -31,8 +31,8 @@ import org.thoughtcrime.securesms.animation.AnimationCompleteListener; import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode; import org.thoughtcrime.securesms.conversation.v2.computed.FormattedDate; import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.recipients.Recipient; @@ -302,7 +302,9 @@ public class ConversationItemFooter extends ConstraintLayout { private void presentDate(@NonNull MessageRecord messageRecord, @NonNull Locale locale, @NonNull ConversationItemDisplayMode displayMode) { dateView.forceLayout(); - if (messageRecord.isFailed()) { + if (messageRecord.isMediaPending()) { + dateView.setText(null); + } else if (messageRecord.isFailed()) { int errorMsg; if (messageRecord.hasFailedWithNetworkFailures()) { errorMsg = R.string.ConversationItem_error_network_not_delivered; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt index 9cecb89ac6..804259c76f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt @@ -251,10 +251,10 @@ class ConversationItemThumbnail @JvmOverloads constructor( state.applyState(thumbnail, album) } - fun setDownloadClickListener(listener: SlidesClickedListener?) { + fun setStartTransferClickListener(listener: SlidesClickedListener?) { state = state.copy( - thumbnailViewState = state.thumbnailViewState.copy(downloadClickListener = listener), - albumViewState = state.albumViewState.copy(downloadClickListener = listener) + thumbnailViewState = state.thumbnailViewState.copy(startTransferClickListener = listener), + albumViewState = state.albumViewState.copy(startTransferClickListener = listener) ) state.applyState(thumbnail, album) @@ -269,10 +269,10 @@ class ConversationItemThumbnail @JvmOverloads constructor( state.applyState(thumbnail, album) } - fun setCancelDownloadClickListener(listener: SlidesClickedListener?) { + fun setCancelTransferClickListener(listener: SlidesClickedListener?) { state = state.copy( - thumbnailViewState = state.thumbnailViewState.copy(cancelDownloadClickListener = listener), - albumViewState = state.albumViewState.copy(cancelDownloadClickListener = listener) + thumbnailViewState = state.thumbnailViewState.copy(cancelTransferClickListener = listener), + albumViewState = state.albumViewState.copy(cancelTransferClickListener = listener) ) state.applyState(thumbnail, album) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt index 77681e4c15..4f8efc6366 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt @@ -34,9 +34,9 @@ data class ConversationItemThumbnailState( @IgnoredOnParcel private val clickListener: SlideClickListener? = null, @IgnoredOnParcel - private val downloadClickListener: SlidesClickedListener? = null, + private val startTransferClickListener: SlidesClickedListener? = null, @IgnoredOnParcel - private val cancelDownloadClickListener: SlidesClickedListener? = null, + private val cancelTransferClickListener: SlidesClickedListener? = null, @IgnoredOnParcel private val playVideoClickListener: SlideClickListener? = null, @IgnoredOnParcel @@ -63,8 +63,8 @@ data class ConversationItemThumbnailState( thumbnailView.get().isClickable = clickable thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) thumbnailView.get().setThumbnailClickListener(clickListener) - thumbnailView.get().setDownloadClickListener(downloadClickListener) - thumbnailView.get().setCancelDownloadClickListener(cancelDownloadClickListener) + thumbnailView.get().setStartTransferClickListener(startTransferClickListener) + thumbnailView.get().setCancelTransferClickListener(cancelTransferClickListener) thumbnailView.get().setPlayVideoClickListener(playVideoClickListener) thumbnailView.get().setOnLongClickListener(longClickListener) thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight) @@ -78,9 +78,9 @@ data class ConversationItemThumbnailState( @IgnoredOnParcel private val clickListener: SlideClickListener? = null, @IgnoredOnParcel - private val downloadClickListener: SlidesClickedListener? = null, + private val startTransferClickListener: SlidesClickedListener? = null, @IgnoredOnParcel - private val cancelDownloadClickListener: SlidesClickedListener? = null, + private val cancelTransferClickListener: SlidesClickedListener? = null, @IgnoredOnParcel private val playVideoClickListener: SlideClickListener? = null, @IgnoredOnParcel @@ -103,8 +103,8 @@ data class ConversationItemThumbnailState( albumView.get().isClickable = clickable albumView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) albumView.get().setThumbnailClickListener(clickListener) - albumView.get().setDownloadClickListener(downloadClickListener) - albumView.get().setCancelDownloadClickListener(cancelDownloadClickListener) + albumView.get().setStartTransferClickListener(startTransferClickListener) + albumView.get().setCancelTransferClickListener(cancelTransferClickListener) albumView.get().setPlayVideoClickListener(playVideoClickListener) albumView.get().setOnLongClickListener(longClickListener) albumView.get().setCellBackgroundColor(cellBackgroundColor) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt index 6f18e4e975..b41dc76e49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewViewThumbnailState.kt @@ -22,7 +22,7 @@ data class LinkPreviewViewThumbnailState( fun applyState(thumbnail: Stub) { if (thumbnail.resolved()) { thumbnail.get().setCorners(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) - thumbnail.get().setDownloadClickListener(downloadListener) + thumbnail.get().setStartTransferClickListener(downloadListener) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java index 4cf5a55330..a464453108 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -86,12 +86,12 @@ public class ThumbnailView extends FrameLayout { private final CornerMask cornerMask; - private final Stub transferControlViewStub; - private SlideClickListener thumbnailClickListener = null; - private SlidesClickedListener downloadClickListener = null; - private SlidesClickedListener cancelDownloadClickListener = null; - private SlideClickListener playVideoClickListener = null; - private Slide slide = null; + private final Stub transferControlViewStub; + private SlideClickListener thumbnailClickListener = null; + private SlidesClickedListener startTransferClickListener = null; + private SlidesClickedListener cancelTransferClickListener = null; + private SlideClickListener playVideoClickListener = null; + private Slide slide = null; public ThumbnailView(Context context) { @@ -363,7 +363,7 @@ public class ThumbnailView extends FrameLayout { } if (showControls) { - transferControlViewStub.get().setDownloadClickListener(new DownloadClickDispatcher()); + transferControlViewStub.get().setTransferClickListener(new DownloadClickDispatcher()); transferControlViewStub.get().setCancelClickListener(new CancelClickDispatcher()); if (MediaUtil.isInstantVideoSupported(slide)) { transferControlViewStub.get().setInstantPlaybackClickListener(new InstantVideoClickDispatcher()); @@ -516,12 +516,12 @@ public class ThumbnailView extends FrameLayout { this.thumbnailClickListener = listener; } - public void setDownloadClickListener(SlidesClickedListener listener) { - this.downloadClickListener = listener; + public void setStartTransferClickListener(SlidesClickedListener listener) { + this.startTransferClickListener = listener; } - public void setCancelDownloadClickListener(SlidesClickedListener listener) { - this.cancelDownloadClickListener = listener; + public void setCancelTransferClickListener(SlidesClickedListener listener) { + this.cancelTransferClickListener = listener; } public void setPlayVideoClickListener(SlideClickListener listener) { @@ -667,10 +667,10 @@ public class ThumbnailView extends FrameLayout { @Override public void onClick(View view) { Log.i(TAG, "onClick() for download button"); - if (downloadClickListener != null && slide != null) { - downloadClickListener.onClick(view, Collections.singletonList(slide)); + if (startTransferClickListener != null && slide != null) { + startTransferClickListener.onClick(view, Collections.singletonList(slide)); } else { - Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + slide + " downloadClickListener: " + downloadClickListener); + Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + slide + " downloadClickListener: " + startTransferClickListener); } } } @@ -679,10 +679,10 @@ public class ThumbnailView extends FrameLayout { @Override public void onClick(View view) { Log.i(TAG, "onClick() for cancel button"); - if (cancelDownloadClickListener != null && slide != null) { - cancelDownloadClickListener.onClick(view, Collections.singletonList(slide)); + if (cancelTransferClickListener != null && slide != null) { + cancelTransferClickListener.onClick(view, Collections.singletonList(slide)); } else { - Log.w(TAG, "Received a cancel button click, but unable to execute it. slide: " + slide + " cancelDownloadClickListener: " + cancelDownloadClickListener); + Log.w(TAG, "Received a cancel button click, but unable to execute it. slide: " + slide + " cancelDownloadClickListener: " + cancelTransferClickListener); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt index f76872907c..64e9d0ba0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt @@ -215,7 +215,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att } private fun displayPendingGallery(currentState: TransferControlViewState) { - binding.primaryProgressView.startClickListener = currentState.downloadClickedListener + binding.primaryProgressView.startClickListener = currentState.startTransferClickListener applyFocusableAndClickable( currentState, listOf(binding.primaryProgressView, binding.primaryDetailsText, binding.primaryBackground), @@ -228,8 +228,8 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att secondaryDetailsText = currentState.showSecondaryText ) - binding.primaryDetailsText.setOnClickListener(currentState.downloadClickedListener) - binding.primaryBackground.setOnClickListener(currentState.downloadClickedListener) + binding.primaryDetailsText.setOnClickListener(currentState.startTransferClickListener) + binding.primaryBackground.setOnClickListener(currentState.startTransferClickListener) binding.primaryDetailsText.translationX = if (ViewUtil.isLtr(this)) { ViewUtil.dpToPx(-PRIMARY_TEXT_OFFSET_DP).toFloat() @@ -244,7 +244,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att } private fun displayPendingGalleryWithPlayable(currentState: TransferControlViewState) { - binding.secondaryProgressView.startClickListener = currentState.downloadClickedListener + binding.secondaryProgressView.startClickListener = currentState.startTransferClickListener super.setClickable(false) binding.secondaryProgressView.isClickable = currentState.showSecondaryText binding.secondaryProgressView.isFocusable = currentState.showSecondaryText @@ -269,7 +269,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att } private fun displayPendingSingleItem(currentState: TransferControlViewState) { - binding.primaryProgressView.startClickListener = currentState.downloadClickedListener + binding.primaryProgressView.startClickListener = currentState.startTransferClickListener applyFocusableAndClickable(currentState, listOf(binding.primaryProgressView), listOf(binding.secondaryProgressView, binding.playVideoButton)) binding.primaryProgressView.setStopped(false) showAllViews( @@ -284,9 +284,9 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att } private fun displayPendingPlayableVideo(currentState: TransferControlViewState) { - binding.secondaryProgressView.startClickListener = currentState.downloadClickedListener - binding.secondaryDetailsText.setOnClickListener(currentState.downloadClickedListener) - binding.secondaryBackground.setOnClickListener(currentState.downloadClickedListener) + binding.secondaryProgressView.startClickListener = currentState.startTransferClickListener + binding.secondaryDetailsText.setOnClickListener(currentState.startTransferClickListener) + binding.secondaryBackground.setOnClickListener(currentState.startTransferClickListener) binding.playVideoButton.setOnClickListener(currentState.instantPlaybackClickListener) applyFocusableAndClickable( currentState, @@ -320,17 +320,17 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att val progress = calculateProgress(currentState) if (progress == 0f) { - binding.secondaryProgressView.setUploading(progress) + binding.secondaryProgressView.setProgress(progress) } else { - binding.secondaryProgressView.cancelClickListener = currentState.cancelDownloadClickedListener - binding.secondaryProgressView.setDownloading(progress) + binding.secondaryProgressView.cancelClickListener = currentState.cancelTransferClickedListener + binding.secondaryProgressView.setProgress(progress) } binding.secondaryDetailsText.translationX = 0f binding.secondaryDetailsText.text = deriveSecondaryDetailsText(currentState) } private fun displayDownloadingSingleItem(currentState: TransferControlViewState) { - binding.primaryProgressView.cancelClickListener = currentState.cancelDownloadClickedListener + binding.primaryProgressView.cancelClickListener = currentState.cancelTransferClickedListener applyFocusableAndClickable(currentState, listOf(binding.primaryProgressView), listOf(binding.secondaryProgressView, binding.playVideoButton)) showAllViews( playVideoButton = false, @@ -341,16 +341,16 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att val progress = calculateProgress(currentState) if (progress == 0f) { - binding.primaryProgressView.setUploading(progress) + binding.primaryProgressView.setProgress(progress) } else { - binding.primaryProgressView.setDownloading(progress) + binding.primaryProgressView.setProgress(progress) } binding.secondaryDetailsText.translationX = 0f binding.secondaryDetailsText.text = deriveSecondaryDetailsText(currentState) } private fun displayDownloadingPlayableVideo(currentState: TransferControlViewState) { - binding.secondaryProgressView.cancelClickListener = currentState.cancelDownloadClickedListener + binding.secondaryProgressView.cancelClickListener = currentState.cancelTransferClickedListener applyFocusableAndClickable(currentState, listOf(binding.secondaryProgressView, binding.playVideoButton), listOf(binding.primaryProgressView)) showAllViews( primaryDetailsText = false, @@ -362,16 +362,16 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att val progress = calculateProgress(currentState) if (progress == 0f) { - binding.secondaryProgressView.setUploading(progress) + binding.secondaryProgressView.setProgress(progress) } else { - binding.secondaryProgressView.setDownloading(progress) + binding.secondaryProgressView.setProgress(progress) } binding.secondaryDetailsText.translationX = 0f binding.secondaryDetailsText.text = deriveSecondaryDetailsText(currentState) } private fun displayUploadingSingleItem(currentState: TransferControlViewState) { - binding.secondaryProgressView.startClickListener = currentState.downloadClickedListener + binding.secondaryProgressView.cancelClickListener = currentState.cancelTransferClickedListener applyFocusableAndClickable(currentState, listOf(binding.secondaryProgressView), listOf(binding.primaryProgressView, binding.playVideoButton)) showAllViews( playVideoButton = false, @@ -381,18 +381,14 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att ) val progress = calculateProgress(currentState) - binding.secondaryProgressView.setUploading(progress) + binding.secondaryProgressView.setProgress(progress) binding.secondaryDetailsText.translationX = 0f binding.secondaryDetailsText.text = deriveSecondaryDetailsText(currentState) } private fun displayUploadingGallery(currentState: TransferControlViewState) { - if (currentState.downloadClickedListener == null) { - Log.w(TAG, "No click listener set for retry!") - } - - binding.secondaryProgressView.startClickListener = currentState.downloadClickedListener + binding.secondaryProgressView.cancelClickListener = currentState.cancelTransferClickedListener applyFocusableAndClickable(currentState, listOf(binding.secondaryProgressView), listOf(binding.primaryProgressView, binding.playVideoButton)) showAllViews( playVideoButton = false, @@ -401,18 +397,18 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att ) val progress = calculateProgress(currentState) - binding.secondaryProgressView.setUploading(progress) + binding.secondaryProgressView.setProgress(progress) binding.secondaryDetailsText.translationX = 0f binding.secondaryDetailsText.text = deriveSecondaryDetailsText(currentState) } private fun displayRetry(currentState: TransferControlViewState, isUploading: Boolean) { - if (currentState.downloadClickedListener == null) { + if (currentState.startTransferClickListener == null) { Log.w(TAG, "No click listener set for retry!") } - binding.secondaryProgressView.startClickListener = currentState.downloadClickedListener + binding.secondaryProgressView.startClickListener = currentState.startTransferClickListener applyFocusableAndClickable( currentState, listOf(binding.secondaryProgressView, binding.secondaryDetailsText, binding.secondaryBackground), @@ -429,8 +425,8 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att } else { ViewUtil.dpToPx(RETRY_SECONDARY_TEXT_OFFSET_DP).toFloat() } - binding.secondaryBackground.setOnClickListener(currentState.downloadClickedListener) - binding.secondaryDetailsText.setOnClickListener(currentState.downloadClickedListener) + binding.secondaryBackground.setOnClickListener(currentState.startTransferClickListener) + binding.secondaryDetailsText.setOnClickListener(currentState.startTransferClickListener) binding.secondaryProgressView.setStopped(isUploading) binding.secondaryDetailsText.text = resources.getString(R.string.NetworkFailure__retry) } @@ -594,11 +590,11 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att return true } - fun setDownloadClickListener(listener: OnClickListener) { - verboseLog("downloadClickListener update") + fun setTransferClickListener(listener: OnClickListener) { + verboseLog("transferClickListener update") updateState { it.copy( - downloadClickedListener = listener + startTransferClickListener = listener ) } } @@ -607,7 +603,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att verboseLog("cancelClickListener update") updateState { it.copy( - cancelDownloadClickedListener = listener + cancelTransferClickedListener = listener ) } } @@ -660,7 +656,7 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att @SuppressLint("SetTextI18n") private fun deriveSecondaryDetailsText(currentState: TransferControlViewState): String { - return if (isCompressing(currentState)) { + return if (currentState.networkProgress.sumCompleted() == 0L || isCompressing(currentState)) { return context.getString(R.string.TransferControlView__processing) } else { val progressMiB = currentState.networkProgress.sumCompleted() / MEBIBYTE diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState.kt index 2a1d41b6ab..95159c570a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlViewState.kt @@ -14,8 +14,8 @@ data class TransferControlViewState( val isFocusable: Boolean = true, val isClickable: Boolean = true, val slides: List = emptyList(), - val downloadClickedListener: View.OnClickListener? = null, - val cancelDownloadClickedListener: View.OnClickListener? = null, + val startTransferClickListener: View.OnClickListener? = null, + val cancelTransferClickedListener: View.OnClickListener? = null, val instantPlaybackClickListener: View.OnClickListener? = null, val showSecondaryText: Boolean = true, val networkProgress: Map = HashMap(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt index 657f6cc346..182779489d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt @@ -97,7 +97,7 @@ class TransferProgressView @JvmOverloads constructor( } } - fun setDownloading(progress: Float) { + fun setProgress(progress: Float) { currentState = State.IN_PROGRESS_CANCELABLE if (cancelClickListener == null) { Log.i(TAG, "Illegal click listener attached.") @@ -108,13 +108,6 @@ class TransferProgressView @JvmOverloads constructor( invalidate() } - fun setUploading(progress: Float) { - currentState = State.IN_PROGRESS_NON_CANCELABLE - setOnClickListener { Log.d(TAG, "Not allowed to click an upload.") } - progressPercent = progress - invalidate() - } - fun setStopped(isUpload: Boolean) { val newState = if (isUpload) State.READY_TO_UPLOAD else State.READY_TO_DOWNLOAD currentState = newState diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index c19fdf921b..c7f5a5833e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -95,6 +95,8 @@ import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; import org.thoughtcrime.securesms.components.mention.MentionAnnotation; import org.thoughtcrime.securesms.contactshare.Contact; +import org.thoughtcrime.securesms.conversation.clicklisteners.AttachmentCancelClickListener; +import org.thoughtcrime.securesms.conversation.clicklisteners.ResendClickListener; import org.thoughtcrime.securesms.conversation.colors.Colorizer; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart; @@ -1083,7 +1085,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo boolean messageRequestAccepted, boolean allowedToPlayInline) { - boolean showControls = !messageRecord.isFailed() && !MessageRecordUtil.isScheduled(messageRecord); + boolean showControls = messageRecord.isMediaPending() || (!messageRecord.isFailed() && !MessageRecordUtil.isScheduled(messageRecord)); ViewUtil.setTopMargin(bodyText, readDimen(R.dimen.message_bubble_top_padding)); @@ -1174,8 +1176,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo mediaThumbnailStub.require().setMaximumThumbnailHeight(readDimen(R.dimen.media_bubble_max_height)); mediaThumbnailStub.require().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(linkPreview.getThumbnail().get())), showControls, false); mediaThumbnailStub.require().setThumbnailClickListener(new LinkPreviewThumbnailClickListener()); - mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.require().setCancelDownloadClickListener(attachmentCancelClickListener); + mediaThumbnailStub.require().setStartTransferClickListener(downloadClickListener); + mediaThumbnailStub.require().setCancelTransferClickListener(attachmentCancelClickListener); mediaThumbnailStub.require().setPlayVideoClickListener(playVideoClickListener); mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener); @@ -1312,8 +1314,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo : R.dimen.media_bubble_max_height)); mediaThumbnailStub.require().setThumbnailClickListener(new ThumbnailClickListener()); - mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.require().setCancelDownloadClickListener(attachmentCancelClickListener); + mediaThumbnailStub.require().setCancelTransferClickListener(attachmentCancelClickListener); mediaThumbnailStub.require().setPlayVideoClickListener(playVideoClickListener); mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener); mediaThumbnailStub.require().setOnClickListener(passthroughClickListener); @@ -1324,8 +1325,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo false); if (!messageRecord.isOutgoing()) { mediaThumbnailStub.require().setConversationColor(getDefaultBubbleColor(hasWallpaper)); + mediaThumbnailStub.require().setStartTransferClickListener(downloadClickListener); } else { mediaThumbnailStub.require().setConversationColor(Color.TRANSPARENT); + mediaThumbnailStub.require().setStartTransferClickListener(new ResendClickListener(messageRecord)); } mediaThumbnailStub.require().setBorderless(false); @@ -1566,7 +1569,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private void setStatusIcons(MessageRecord messageRecord, boolean hasWallpaper) { bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0); - if (messageRecord.isFailed()) { + if (!messageRecord.isMediaPending() && messageRecord.isFailed()) { alertView.setFailed(); } else if (messageRecord.isPendingInsecureSmsFallback()) { alertView.setPendingApproval(); @@ -2445,25 +2448,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo } } - private class AttachmentCancelClickListener implements SlidesClickedListener { - @Override - public void onClick(View v, List slides) { - Log.i(TAG, "onClick() for attachment cancellation"); - final JobManager jobManager = ApplicationDependencies.getJobManager(); - if (messageRecord.isMmsNotification()) { - Log.i(TAG, "Canceling MMS attachments download"); - jobManager.cancel("mms-operation"); - } else { - Log.i(TAG, "Canceling push attachment downloads for " + slides.size() + " items"); - - for (Slide slide : slides) { - final String queue = AttachmentDownloadJob.constructQueueString(((DatabaseAttachment) slide.asAttachment()).attachmentId); - jobManager.cancelAllInQueue(queue); - } - } - } - } - private class PlayVideoClickListener implements SlideClickListener { private static final float MINIMUM_DOWNLOADED_THRESHOLD = 0.05f; private View parentView; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/AttachmentCancelClickListener.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/AttachmentCancelClickListener.kt new file mode 100644 index 0000000000..4cbda4fdd4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/AttachmentCancelClickListener.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ +package org.thoughtcrime.securesms.conversation.clicklisteners + +import android.view.View +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.attachments.DatabaseAttachment +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobs.AttachmentCompressionJob +import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob +import org.thoughtcrime.securesms.jobs.AttachmentUploadJob +import org.thoughtcrime.securesms.mms.Slide +import org.thoughtcrime.securesms.mms.SlidesClickedListener + +internal class AttachmentCancelClickListener : SlidesClickedListener { + override fun onClick(v: View, slides: List) { + Log.i(TAG, "Canceling compression/upload/download jobs for ${slides.size} items") + val jobManager = ApplicationDependencies.getJobManager() + var cancelCount = 0 + for (slide in slides) { + val attachmentId = (slide.asAttachment() as DatabaseAttachment).attachmentId + val jobsToCancel = jobManager.find { + when (it.factoryKey) { + AttachmentDownloadJob.KEY -> AttachmentDownloadJob.jobSpecMatchesAttachmentId(it, attachmentId) + AttachmentCompressionJob.KEY -> AttachmentCompressionJob.jobSpecMatchesAttachmentId(it, attachmentId) + AttachmentUploadJob.KEY -> AttachmentUploadJob.jobSpecMatchesAttachmentId(it, attachmentId) + else -> false + } + } + jobsToCancel.forEach { + jobManager.cancel(it.id) + cancelCount++ + } + } + Log.i(TAG, "Canceled $cancelCount jobs.") + } + + companion object { + private val TAG = Log.tag(AttachmentCancelClickListener::class.java) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/ResendClickListener.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/ResendClickListener.kt new file mode 100644 index 0000000000..60dede9140 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/clicklisteners/ResendClickListener.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.conversation.clicklisteners + +import android.view.View +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.mms.Slide +import org.thoughtcrime.securesms.mms.SlidesClickedListener +import org.thoughtcrime.securesms.sms.MessageSender + +class ResendClickListener(private val messageRecord: MessageRecord) : SlidesClickedListener { + override fun onClick(v: View?, slides: MutableList?) { + if (v == null) { + Log.w(TAG, "Could not resend message, view was null!") + return + } + + MessageSender.resend(v.context, messageRecord) + } + + companion object { + private val TAG = Log.tag(ResendClickListener::class.java) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java index 2d5e47e231..bf8fa8f5e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java @@ -7,7 +7,6 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; - import androidx.media3.common.MimeTypes; import org.greenrobot.eventbus.EventBus; @@ -23,9 +22,10 @@ import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream; import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.events.PartProgressEvent; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.MediaStream; @@ -124,6 +124,21 @@ public final class AttachmentCompressionJob extends BaseJob { return true; } + @Override + public void onAdded() { + Log.i(TAG, "onAdded() " + attachmentId.toString()); + + final AttachmentTable database = SignalDatabase.attachments(); + final DatabaseAttachment attachment = database.getAttachment(attachmentId); + final boolean pending = attachment != null && attachment.transferState != AttachmentTable.TRANSFER_PROGRESS_DONE + && attachment.transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE; + + if (pending) { + Log.i(TAG, "onAdded() Marking attachment progress as 'started'"); + database.setTransferState(attachment.mmsId, attachmentId, AttachmentTable.TRANSFER_PROGRESS_STARTED); + } + } + @Override public void onRun() throws Exception { Log.d(TAG, "Running for: " + attachmentId); @@ -147,7 +162,20 @@ public final class AttachmentCompressionJob extends BaseJob { } @Override - public void onFailure() { } + public void onFailure() { + AttachmentTable database = SignalDatabase.attachments(); + DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId); + if (databaseAttachment == null) { + Log.i(TAG, "Could not find attachment in DB for compression job upon failure."); + return; + } + + try { + database.setTransferProgressFailed(attachmentId, databaseAttachment.mmsId); + } catch (MmsException e) { + Log.w(TAG, "Error marking attachment as failed upon failed compression.", e); + } + } @Override protected boolean onShouldRetry(@NonNull Exception exception) { @@ -352,6 +380,21 @@ public final class AttachmentCompressionJob extends BaseJob { result.getHeight()); } + public static boolean jobSpecMatchesAttachmentId(@NonNull JobSpec jobSpec, @NonNull AttachmentId attachmentId) { + if (!KEY.equals(jobSpec.getFactoryKey())) { + return false; + } + + final byte[] serializedData = jobSpec.getSerializedData(); + if (serializedData == null) { + return false; + } + + JsonJobData data = JsonJobData.deserialize(serializedData); + final AttachmentId parsed = new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)); + return attachmentId.equals(parsed); + } + public static final class Factory implements Job.Factory { @Override public @NonNull AttachmentCompressionJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index bc7c37e6b6..d6821b5e62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobLogger; import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.notifications.v2.ConversationId; import org.thoughtcrime.securesms.releasechannel.ReleaseChannel; @@ -63,10 +64,10 @@ public final class AttachmentDownloadJob extends BaseJob { private static final String TAG = Log.tag(AttachmentDownloadJob.class); - private static final String KEY_MESSAGE_ID = "message_id"; - private static final String KEY_PART_ROW_ID = "part_row_id"; - private static final String KEY_PAR_UNIQUE_ID = "part_unique_id"; - private static final String KEY_MANUAL = "part_manual"; + private static final String KEY_MESSAGE_ID = "message_id"; + private static final String KEY_ROW_ID = "part_row_id"; + private static final String KEY_UNIQUE_ID = "part_unique_id"; + private static final String KEY_MANUAL = "part_manual"; private final long messageId; private final long partRowId; @@ -97,8 +98,8 @@ public final class AttachmentDownloadJob extends BaseJob { @Override public @Nullable byte[] serialize() { return new JsonJobData.Builder().putLong(KEY_MESSAGE_ID, messageId) - .putLong(KEY_PART_ROW_ID, partRowId) - .putLong(KEY_PAR_UNIQUE_ID, partUniqueId) + .putLong(KEY_ROW_ID, partRowId) + .putLong(KEY_UNIQUE_ID, partUniqueId) .putBoolean(KEY_MANUAL, manual) .serialize(); } @@ -318,6 +319,21 @@ public final class AttachmentDownloadJob extends BaseJob { } } + public static boolean jobSpecMatchesAttachmentId(@NonNull JobSpec jobSpec, @NonNull AttachmentId attachmentId) { + if (!KEY.equals(jobSpec.getFactoryKey())) { + return false; + } + + final byte[] serializedData = jobSpec.getSerializedData(); + if (serializedData == null) { + return false; + } + + JsonJobData data = JsonJobData.deserialize(serializedData); + final AttachmentId parsed = new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)); + return attachmentId.equals(parsed); + } + @VisibleForTesting static class InvalidPartException extends Exception { InvalidPartException(String s) {super(s);} @@ -331,7 +347,7 @@ public final class AttachmentDownloadJob extends BaseJob { return new AttachmentDownloadJob(parameters, data.getLong(KEY_MESSAGE_ID), - new AttachmentId(data.getLong(KEY_PART_ROW_ID), data.getLong(KEY_PAR_UNIQUE_ID)), + new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), data.getBoolean(KEY_MANUAL)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt index 48438f351d..11246d88dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.kt @@ -17,12 +17,15 @@ import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.blurhash.BlurHashEncoder +import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.events.PartProgressEvent import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec import org.thoughtcrime.securesms.jobs.protos.AttachmentUploadJobData +import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.net.NotPushRegisteredException import org.thoughtcrime.securesms.recipients.Recipient @@ -71,6 +74,17 @@ class AttachmentUploadJob private constructor( val maxPaddedSize = AttachmentCipherStreamUtil.getPlaintextLength(maxCipherTextSize) return PaddingInputStream.getMaxUnpaddedSize(maxPaddedSize) } + + @JvmStatic + fun jobSpecMatchesAttachmentId(jobSpec: JobSpec, attachmentId: AttachmentId): Boolean { + if (KEY != jobSpec.factoryKey) { + return false + } + val serializedData = jobSpec.serializedData ?: return false + val data = AttachmentUploadJobData.ADAPTER.decode(serializedData) + val parsed = AttachmentId(data.attachmentRowId, data.attachmentUniqueId) + return attachmentId == parsed + } } constructor(attachmentId: AttachmentId) : this( @@ -95,6 +109,25 @@ class AttachmentUploadJob private constructor( override fun shouldTrace(): Boolean = true + override fun onAdded() { + Log.i(TAG, "onAdded() $attachmentId") + + val database = SignalDatabase.attachments + val attachment = database.getAttachment(attachmentId) + + if (attachment == null) { + Log.w(TAG, "Could not fetch attachment from database.") + return + } + + val pending = attachment.transferState != AttachmentTable.TRANSFER_PROGRESS_DONE && attachment.transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE + + if (pending) { + Log.i(TAG, "onAdded() Marking attachment progress as 'started'") + database.setTransferState(attachment.mmsId, attachmentId, AttachmentTable.TRANSFER_PROGRESS_STARTED) + } + } + @Throws(Exception::class) public override fun onRun() { if (!Recipient.self().isRegistered) { @@ -152,8 +185,17 @@ class AttachmentUploadJob private constructor( } override fun onFailure() { - if (isCanceled) { - SignalDatabase.attachments.deleteAttachment(attachmentId) + val database = SignalDatabase.attachments + val databaseAttachment = database.getAttachment(attachmentId) + if (databaseAttachment == null) { + Log.i(TAG, "Could not find attachment in DB for upload job upon failure/cancellation.") + return + } + + try { + database.setTransferProgressFailed(attachmentId, databaseAttachment.mmsId) + } catch (e: MmsException) { + Log.w(TAG, "Error marking attachment as failed upon failed/canceled upload.", e) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaUploadRepository.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaUploadRepository.java index 45983941f8..7dd440a3aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaUploadRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaUploadRepository.java @@ -161,6 +161,7 @@ public class MediaUploadRepository { if (result != null) { Stream.of(result.getJobIds()).forEach(jobManager::cancel); uploadResults.remove(media); + SignalDatabase.attachments().deleteAttachment(result.getAttachmentId()); } }