diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt new file mode 100644 index 000000000..2a205b775 --- /dev/null +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/ConversationItemPreviewer.kt @@ -0,0 +1,170 @@ +package org.thoughtcrime.securesms.conversation + +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.signal.core.util.ThreadUtil +import org.thoughtcrime.securesms.attachments.PointerAttachment +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.database.model.StoryType +import org.thoughtcrime.securesms.mms.IncomingMediaMessage +import org.thoughtcrime.securesms.mms.OutgoingMediaMessage +import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage +import org.thoughtcrime.securesms.profiles.ProfileName +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.releasechannel.ReleaseChannel +import org.thoughtcrime.securesms.testing.SignalActivityRule +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer +import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId +import java.util.Optional + +/** + * Helper test for rendering conversation items for preview. + */ +@RunWith(AndroidJUnit4::class) +@Ignore("For testing/previewing manually, no assertions") +class ConversationItemPreviewer { + + @get:Rule + val harness = SignalActivityRule(othersCount = 10) + + @Test + fun testShowLongName() { + val other: Recipient = Recipient.resolved(harness.others.first()) + + SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("Seef", "$$$")) + + insertFailedMediaMessage(other = other, attachmentCount = 1) + insertFailedMediaMessage(other = other, attachmentCount = 2) + insertFailedMediaMessage(other = other, body = "Test", attachmentCount = 1) +// insertFailedOutgoingMediaMessage(other = other, body = "Test", attachmentCount = 1) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) +// insertMediaMessage(other = other) + + val scenario: ActivityScenario = harness.launchActivity { putExtra("recipient_id", other.id.serialize()) } + scenario.onActivity { + } + + // Uncomment to make dialog stay on screen, otherwise will show/dismiss immediately +// ThreadUtil.sleep(45000) + } + + private fun insertMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) { + val attachments: List = (0 until attachmentCount).map { + attachment() + } + + val message = IncomingMediaMessage( + from = other.id, + body = body, + sentTimeMillis = System.currentTimeMillis(), + serverTimeMillis = System.currentTimeMillis(), + receivedTimeMillis = System.currentTimeMillis(), + attachments = PointerAttachment.forPointers(Optional.of(attachments)), + ) + + SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get() + + ThreadUtil.sleep(1) + } + + private fun insertFailedMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) { + val attachments: List = (0 until attachmentCount).map { + attachment() + } + + val message = IncomingMediaMessage( + from = other.id, + body = body, + sentTimeMillis = System.currentTimeMillis(), + serverTimeMillis = System.currentTimeMillis(), + receivedTimeMillis = System.currentTimeMillis(), + attachments = PointerAttachment.forPointers(Optional.of(attachments)), + ) + + val insert = SignalDatabase.mms.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get() + + SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment -> +// if (index != 1) { + SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert.messageId) +// } else { +// SignalDatabase.attachments.setTransferState(insert.messageId, attachment, TRANSFER_PROGRESS_STARTED) +// } + } + + ThreadUtil.sleep(1) + } + + private fun insertFailedOutgoingMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) { + val attachments: List = (0 until attachmentCount).map { + attachment() + } + + val message = OutgoingMediaMessage( + other, + body, + PointerAttachment.forPointers(Optional.of(attachments)), + System.currentTimeMillis(), + -1, + 0, + false, + ThreadDatabase.DistributionTypes.DEFAULT, + StoryType.NONE, + null, + false, + null, + emptyList(), + emptyList(), + emptyList(), + emptySet(), + emptySet(), + null + ) + + val insert = SignalDatabase.mms.insertMessageOutbox( + OutgoingSecureMediaMessage(message), + SignalDatabase.threads.getOrCreateThreadIdFor(other), + false, + null + ) + + SignalDatabase.attachments.getAttachmentsForMessage(insert).forEachIndexed { index, attachment -> + SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert) + } + + ThreadUtil.sleep(1) + } + + private fun attachment(): SignalServiceAttachmentPointer { + return SignalServiceAttachmentPointer( + ReleaseChannel.CDN_NUMBER, + SignalServiceAttachmentRemoteId.from(""), + "image/webp", + null, + Optional.empty(), + Optional.empty(), + 1024, + 1024, + Optional.empty(), + Optional.of("/not-there.jpg"), + false, + false, + false, + Optional.empty(), + Optional.empty(), + System.currentTimeMillis() + ) + } +} diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt index 4caa81939..8af9677d2 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/testing/SignalActivityRule.kt @@ -110,6 +110,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew()) SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true)) SignalDatabase.recipients.setProfileSharing(recipientId, true) + SignalDatabase.recipients.markRegistered(recipientId, aci) ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey) others += recipientId } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java index afc4d691b..cf99c5c91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java @@ -119,7 +119,12 @@ public abstract class Attachment { public boolean isInProgress() { return transferState != AttachmentDatabase.TRANSFER_PROGRESS_DONE && - transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED; + transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED && + transferState != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE; + } + + public boolean isPermanentlyFailed() { + return transferState == AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE; } public long getSize() { 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 fe9521c0b..6f00df6c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -18,6 +18,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; import androidx.annotation.UiThread; +import androidx.appcompat.widget.AppCompatImageView; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.load.engine.DiskCacheStrategy; @@ -34,9 +35,11 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequest; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener; +import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.stories.StoryTextPostModel; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Util; @@ -62,10 +65,12 @@ public class ThumbnailView extends FrameLayout { private static final int MIN_HEIGHT = 2; private static final int MAX_HEIGHT = 3; - private ImageView image; - private ImageView blurhash; - private View playOverlay; - private View captionIcon; + private final ImageView image; + private final ImageView blurhash; + private final View playOverlay; + private final View captionIcon; + private final AppCompatImageView errorImage; + private OnClickListener parentClickListener; private final int[] dimens = new int[2]; @@ -97,6 +102,7 @@ public class ThumbnailView extends FrameLayout { this.blurhash = findViewById(R.id.thumbnail_blurhash); this.playOverlay = findViewById(R.id.play_overlay); this.captionIcon = findViewById(R.id.thumbnail_caption_icon); + this.errorImage = findViewById(R.id.thumbnail_error); super.setOnClickListener(new ThumbnailClickDispatcher()); @@ -302,6 +308,34 @@ public class ThumbnailView extends FrameLayout { boolean showControls, boolean isPreview, int naturalWidth, int naturalHeight) { + if (slide.asAttachment().isPermanentlyFailed()) { + this.slide = slide; + + transferControls.ifPresent(c -> c.setVisibility(View.GONE)); + playOverlay.setVisibility(View.GONE); + + glideRequests.clear(blurhash); + blurhash.setImageDrawable(null); + + glideRequests.clear(image); + image.setImageDrawable(null); + + int errorImageResource; + if (slide instanceof ImageSlide) { + errorImageResource = R.drawable.ic_photo_slash_outline_24; + } else if (slide instanceof VideoSlide) { + errorImageResource = R.drawable.ic_video_slash_outline_24; + } else { + errorImageResource = R.drawable.ic_error_outline_24; + } + errorImage.setImageResource(errorImageResource); + errorImage.setVisibility(View.VISIBLE); + + return new SettableFuture<>(true); + } else { + errorImage.setVisibility(View.GONE); + } + if (showControls) { getTransferControls().setSlide(slide); getTransferControls().setDownloadClickListener(new DownloadClickDispatcher()); @@ -532,11 +566,13 @@ public class ThumbnailView extends FrameLayout { private class ThumbnailClickDispatcher implements View.OnClickListener { @Override public void onClick(View view) { - if (thumbnailClickListener != null && - slide != null && - slide.asAttachment().getUri() != null && - slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) - { + boolean validThumbnail = slide != null && + slide.asAttachment().getUri() != null && + slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE; + + boolean permanentFailure = slide != null && slide.asAttachment().isPermanentlyFailed(); + + if (thumbnailClickListener != null && (validThumbnail || permanentFailure)) { thumbnailClickListener.onClick(view, slide); } else if (parentClickListener != null) { parentClickListener.onClick(view); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java index a86ebf037..ef922616c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java @@ -183,15 +183,20 @@ public final class TransferControlView extends FrameLayout { } private int getTransferState(@NonNull List slides) { - int transferState = AttachmentDatabase.TRANSFER_PROGRESS_DONE; + int transferState = AttachmentDatabase.TRANSFER_PROGRESS_DONE; + boolean allFailed = true; + for (Slide slide : slides) { - if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING && transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { - transferState = slide.getTransferState(); - } else { - transferState = Math.max(transferState, slide.getTransferState()); + if (slide.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE) { + allFailed = false; + if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_PENDING && transferState == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { + transferState = slide.getTransferState(); + } else { + transferState = Math.max(transferState, slide.getTransferState()); + } } } - return transferState; + return allFailed ? AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE : transferState; } private String getDownloadText(@NonNull List slides) { 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 c7079f6b2..df9218c4d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -64,6 +64,7 @@ import androidx.lifecycle.LifecycleOwner; import com.annimon.stream.Stream; import com.google.android.exoplayer2.MediaItem; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.common.collect.Sets; import org.signal.core.util.DimensionUnit; @@ -2348,6 +2349,25 @@ public final class ConversationItem extends RelativeLayout implements BindableCo Log.w(TAG, "No activity existed to view the media."); Toast.makeText(context, R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show(); } + } else if (slide.asAttachment().isPermanentlyFailed()) { + String failedMessage; + + if (slide instanceof ImageSlide) { + failedMessage = messageRecord.isOutgoing() ? context.getString(R.string.ConversationItem_cant_download_image_you_will_need_to_send_it_again) + : context.getString(R.string.ConversationItem_cant_download_image_s_will_need_to_send_it_again, messageRecord.getIndividualRecipient().getShortDisplayName(context)); + } else if (slide instanceof VideoSlide) { + failedMessage = messageRecord.isOutgoing() ? context.getString(R.string.ConversationItem_cant_download_video_you_will_need_to_send_it_again) + : context.getString(R.string.ConversationItem_cant_download_video_s_will_need_to_send_it_again, messageRecord.getIndividualRecipient().getShortDisplayName(context)); + } else { + failedMessage = messageRecord.isOutgoing() ? context.getString(R.string.ConversationItem_cant_download_message_you_will_need_to_send_it_again) + : context.getString(R.string.ConversationItem_cant_download_message_s_will_need_to_send_it_again, messageRecord.getIndividualRecipient().getShortDisplayName(context)); + } + + new MaterialAlertDialogBuilder(getContext()) + .setMessage(failedMessage) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(android.R.string.cancel, null) + .show(); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 2d965f407..e2f00f665 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -127,10 +127,11 @@ public class AttachmentDatabase extends Database { private static final String DIRECTORY = "parts"; - public static final int TRANSFER_PROGRESS_DONE = 0; - public static final int TRANSFER_PROGRESS_STARTED = 1; - public static final int TRANSFER_PROGRESS_PENDING = 2; - public static final int TRANSFER_PROGRESS_FAILED = 3; + public static final int TRANSFER_PROGRESS_DONE = 0; + public static final int TRANSFER_PROGRESS_STARTED = 1; + public static final int TRANSFER_PROGRESS_PENDING = 2; + public static final int TRANSFER_PROGRESS_FAILED = 3; + public static final int TRANSFER_PROGRESS_PERMANENT_FAILURE = 4; public static final long PREUPLOAD_MESSAGE_ID = -8675309; @@ -233,6 +234,17 @@ public class AttachmentDatabase extends Database { ContentValues values = new ContentValues(); values.put(TRANSFER_STATE, TRANSFER_PROGRESS_FAILED); + database.update(TABLE_NAME, values, PART_ID_WHERE + " AND " + TRANSFER_STATE + " < " + TRANSFER_PROGRESS_PERMANENT_FAILURE, attachmentId.toStrings()); + notifyConversationListeners(SignalDatabase.mms().getThreadIdForMessage(mmsId)); + } + + public void setTransferProgressPermanentFailure(AttachmentId attachmentId, long mmsId) + throws MmsException + { + SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(TRANSFER_STATE, TRANSFER_PROGRESS_PERMANENT_FAILURE); + database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()); notifyConversationListeners(SignalDatabase.mms().getThreadIdForMessage(mmsId)); } 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 f81465fec..9a8ddd45e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -8,6 +8,7 @@ import androidx.annotation.VisibleForTesting; import org.greenrobot.eventbus.EventBus; import org.signal.core.util.Hex; import org.signal.core.util.logging.Log; +import org.signal.libsignal.protocol.InvalidMacException; import org.signal.libsignal.protocol.InvalidMessageException; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.AttachmentId; @@ -107,7 +108,8 @@ public final class AttachmentDownloadJob extends BaseJob { final AttachmentDatabase database = SignalDatabase.attachments(); final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId); final DatabaseAttachment attachment = database.getAttachment(attachmentId); - final boolean pending = attachment != null && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE; + final boolean pending = attachment != null && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_DONE + && attachment.getTransferState() != AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE; if (pending && (manual || AttachmentUtil.isAutoDownloadPermitted(context, attachment))) { Log.i(TAG, "onAdded() Marking attachment progress as 'started'"); @@ -136,6 +138,11 @@ public final class AttachmentDownloadJob extends BaseJob { return; } + if (attachment.isPermanentlyFailed()) { + Log.w(TAG, "Attachment was marked as a permanent failure. Refusing to download."); + return; + } + if (!attachment.isInProgress()) { Log.w(TAG, "Attachment was already downloaded."); return; @@ -194,9 +201,17 @@ public final class AttachmentDownloadJob extends BaseJob { } else { throw new IOException("Failed to delete temp download file following range exception"); } - } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException | MissingConfigurationException e) { + } catch (InvalidPartException | NonSuccessfulResponseCodeException | MmsException | MissingConfigurationException e) { Log.w(TAG, "Experienced exception while trying to download an attachment.", e); markFailed(messageId, attachmentId); + } catch (InvalidMessageException e) { + Log.w(TAG, "Experienced an InvalidMessageException while trying to download an attachment.", e); + if (e.getCause() instanceof InvalidMacException) { + Log.w(TAG, "Detected an invalid mac. Treating as a permanent failure."); + markPermanentlyFailed(messageId, attachmentId); + } else { + markFailed(messageId, attachmentId); + } } } @@ -262,6 +277,15 @@ public final class AttachmentDownloadJob extends BaseJob { } } + private void markPermanentlyFailed(long messageId, AttachmentId attachmentId) { + try { + AttachmentDatabase database = SignalDatabase.attachments(); + database.setTransferProgressPermanentFailure(attachmentId, messageId); + } catch (MmsException e) { + Log.w(TAG, e); + } + } + @VisibleForTesting static class InvalidPartException extends Exception { InvalidPartException(String s) {super(s);} InvalidPartException(Exception e) {super(e);} diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt index f1749b912..f9af7118b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/StorySlateView.kt @@ -8,12 +8,12 @@ import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView -import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.mms.GlideApp +import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.visible /** @@ -24,10 +24,6 @@ class StorySlateView @JvmOverloads constructor( attrs: AttributeSet? = null ) : FrameLayout(context, attrs) { - companion object { - private val TAG = Log.tag(StorySlateView::class.java) - } - var callback: Callback? = null var state: State = State.HIDDEN @@ -43,10 +39,10 @@ class StorySlateView @JvmOverloads constructor( private val loadingSpinner: View = findViewById(R.id.loading_spinner) private val errorCircle: View = findViewById(R.id.error_circle) private val errorBackground: View = findViewById(R.id.stories_error_background) - private val unavailableText: View = findViewById(R.id.unavailable) + private val unavailableText: TextView = findViewById(R.id.unavailable) private val errorText: TextView = findViewById(R.id.error_text) - fun moveToState(state: State, postId: Long) { + fun moveToState(state: State, postId: Long, sender: Recipient? = null) { if (this.state == state && this.postId == postId) { return } @@ -61,7 +57,7 @@ class StorySlateView @JvmOverloads constructor( State.LOADING -> moveToProgressState(State.LOADING) State.ERROR -> moveToErrorState() State.RETRY -> moveToProgressState(State.RETRY) - State.NOT_FOUND -> moveToNotFoundState() + State.NOT_FOUND, State.FAILED -> moveToNotFoundState(state, sender) State.HIDDEN -> moveToHiddenState() } @@ -106,8 +102,8 @@ class StorySlateView @JvmOverloads constructor( } } - private fun moveToNotFoundState() { - state = State.NOT_FOUND + private fun moveToNotFoundState(state: State, sender: Recipient?) { + this.state = state visible = true background.visible = true loadingSpinner.visible = false @@ -115,6 +111,12 @@ class StorySlateView @JvmOverloads constructor( errorBackground.visible = false unavailableText.visible = true errorText.visible = false + + if (state == State.FAILED && sender != null) { + unavailableText.text = context.getString(R.string.StorySlateView__cant_download_story_s_will_need_to_share_it_again, sender.getShortDisplayName(context)) + } else { + unavailableText.setText(R.string.StorySlateView__this_story_is_no_longer_available) + } } private fun moveToHiddenState() { @@ -155,7 +157,8 @@ class StorySlateView @JvmOverloads constructor( ERROR(1, true), RETRY(2, true), NOT_FOUND(3, false), - HIDDEN(4, false); + HIDDEN(4, false), + FAILED(5, false); companion object { fun fromCode(code: Int): State { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index 1504f1264..1635a2d26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -736,6 +736,11 @@ class StoryViewerPageFragment : sharedViewModel.setContentIsReady() viewModel.setIsDisplayingSlate(true) } + AttachmentDatabase.TRANSFER_PROGRESS_PERMANENT_FAILURE -> { + storySlate.moveToState(StorySlateView.State.FAILED, post.id, post.sender) + sharedViewModel.setContentIsReady() + viewModel.setIsDisplayingSlate(true) + } } } diff --git a/app/src/main/res/drawable/ic_photo_slash_outline_24.xml b/app/src/main/res/drawable/ic_photo_slash_outline_24.xml new file mode 100644 index 000000000..2cdfa06ec --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_slash_outline_24.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_video_slash_outline_24.xml b/app/src/main/res/drawable/ic_video_slash_outline_24.xml new file mode 100644 index 000000000..57c2b87ce --- /dev/null +++ b/app/src/main/res/drawable/ic_video_slash_outline_24.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/layout/thumbnail_view.xml b/app/src/main/res/layout/thumbnail_view.xml index df9bca9af..a5f00dae0 100644 --- a/app/src/main/res/layout/thumbnail_view.xml +++ b/app/src/main/res/layout/thumbnail_view.xml @@ -1,9 +1,9 @@ - + tools:parentTag="android.widget.FrameLayout" + tools:viewBindingIgnore="true"> + android:scaleType="fitCenter" /> + android:scaleType="fitCenter" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c0eb945a..3fc3bb257 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -297,6 +297,18 @@   Pending This message was deleted. You deleted this message. + + Can\'t download message. %1$s will need to send it again. + + Can\'t download image. %1$s will need to send it again. + + Can\'t download video. %1$s will need to send it again. + + Can\'t download message. You will need to send it again. + + Can\'t download image. You will need to send it again. + + Can\'t download video. You will need to send it again. Add attachment @@ -5093,6 +5105,8 @@ Sending reply… This story is no longer available. + + Can\'t download story. %1$s will need to share it again. No Internet Connection