From 719ae72270bb79af057b49c8e313d50cdd3a3c4b Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 31 Oct 2024 15:29:28 -0300 Subject: [PATCH] Add MediaNoLongerAvailable wiring. --- .../v2/items/V2ConversationItemShapeTest.kt | 1 + .../test/InternalConversationTestFragment.kt | 4 + .../securesms/BindableConversationItem.java | 1 + .../securesms/attachments/Attachment.kt | 8 ++ .../conversation/ConversationItem.java | 6 + .../conversation/v2/ConversationFragment.kt | 9 ++ .../v2/MediaNoLongerAvailableBottomSheet.kt | 124 ++++++++++++++++++ .../messagedetails/MessageDetailsFragment.kt | 4 + app/src/main/res/values/strings.xml | 10 ++ 9 files changed, 167 insertions(+) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MediaNoLongerAvailableBottomSheet.kt diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt index 72e99eb08b..af89201882 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt @@ -334,5 +334,6 @@ class V2ConversationItemShapeTest { override fun onItemDoubleClick(item: MultiselectPart) = Unit override fun onPaymentTombstoneClicked() = Unit + override fun onDisplayMediaNoLongerAvailableSheet() = Unit } } diff --git a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt index 04339ca04e..25307917c8 100644 --- a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt +++ b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt @@ -312,6 +312,10 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } + override fun onDisplayMediaNoLongerAvailableSheet() { + Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() + } + override fun onShowSafetyTips(forGroup: Boolean) { Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index b1b78674ad..35010a86dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -134,5 +134,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, void onMessageRequestAcceptOptionsClicked(); void onItemDoubleClick(MultiselectPart multiselectPart); void onPaymentTombstoneClicked(); + void onDisplayMediaNoLongerAvailableSheet(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt index 27a54c5c07..38c8db6410 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.kt @@ -16,6 +16,8 @@ import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.util.ParcelUtil import org.whispersystems.signalservice.api.util.UuidUtil import java.util.UUID +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds /** * Note: We have to use our own Parcelable implementation because we need to do custom stuff to preserve @@ -146,6 +148,12 @@ abstract class Attachment( val isPermanentlyFailed: Boolean get() = transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE + /** + * Denotes whether the media for the given attachment is no longer available for download. + */ + val isMediaNoLongerAvailableForDownload: Boolean + get() = isPermanentlyFailed && uploadTimestamp.milliseconds > 30.days + val isSticker: Boolean get() = stickerLocator != null 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 959a4a15b8..788e4085e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -80,6 +80,7 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.badges.BadgeImageView; import org.thoughtcrime.securesms.badges.gifts.GiftMessageView; import org.thoughtcrime.securesms.badges.gifts.OpenableGift; +import org.thoughtcrime.securesms.billing.upgrade.UpgradeToStartMediaBackupSheet; import org.thoughtcrime.securesms.calls.links.CallLinks; import org.thoughtcrime.securesms.components.AlertView; import org.thoughtcrime.securesms.components.AudioView; @@ -2607,6 +2608,11 @@ 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().isMediaNoLongerAvailableForDownload() && !SignalStore.backup().backsUpMedia()) { + Log.i(TAG, "Clicked unavailable media, attempting to display sheet."); + if (eventListener != null) { + eventListener.onDisplayMediaNoLongerAvailableSheet(); + } } else if (slide.asAttachment().isPermanentlyFailed()) { String failedMessage; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 294231b6e6..e0bb12f99f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -112,6 +112,7 @@ import org.thoughtcrime.securesms.badges.gifts.OpenableGift import org.thoughtcrime.securesms.badges.gifts.OpenableGiftItemDecoration import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet +import org.thoughtcrime.securesms.billing.upgrade.UpgradeToStartMediaBackupSheet import org.thoughtcrime.securesms.calls.YouAreAlreadyInACallSnackbar import org.thoughtcrime.securesms.components.AnimatingToggle import org.thoughtcrime.securesms.components.ComposeText @@ -2895,6 +2896,14 @@ class ConversationFragment : this@ConversationFragment.showPaymentTombstoneLearnMoreDialog() } + override fun onDisplayMediaNoLongerAvailableSheet() { + if (SignalStore.backup.areBackupsEnabled) { + UpgradeToStartMediaBackupSheet().show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + } else { + MediaNoLongerAvailableBottomSheet().show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + } + } + override fun onMessageWithErrorClicked(messageRecord: MessageRecord) { val recipientId = viewModel.recipientSnapshot?.id ?: return if (messageRecord.isIdentityMismatchFailure) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MediaNoLongerAvailableBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MediaNoLongerAvailableBottomSheet.kt new file mode 100644 index 0000000000..eb79c3ed4d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MediaNoLongerAvailableBottomSheet.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.conversation.v2 + +import android.os.Bundle +import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.signal.core.ui.BottomSheets +import org.signal.core.ui.Buttons +import org.signal.core.ui.Previews +import org.signal.core.ui.SignalPreview +import org.signal.core.ui.horizontalGutters +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.backup.v2.MessageBackupTier +import org.thoughtcrime.securesms.components.settings.app.subscription.MessageBackupsCheckoutLauncher.createBackupsCheckoutLauncher +import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment + +/** + * Bottom sheet displayed when the user taps media that is not available for download, + * over 30 days old, and they do not currently have a subscription. + */ +class MediaNoLongerAvailableBottomSheet : ComposeBottomSheetDialogFragment() { + + private lateinit var checkoutLauncher: ActivityResultLauncher + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + checkoutLauncher = createBackupsCheckoutLauncher { + dismissAllowingStateLoss() + } + } + + @Composable + override fun SheetContent() { + MediaNoLongerAvailableBottomSheetContent( + onContinueClick = { + checkoutLauncher.launch(MessageBackupTier.PAID) + }, + onNotNowClick = { dismissAllowingStateLoss() } + ) + } +} + +@Composable +private fun MediaNoLongerAvailableBottomSheetContent( + onContinueClick: () -> Unit = {}, + onNotNowClick: () -> Unit = {} +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .horizontalGutters() + ) { + BottomSheets.Handle() + + Image( + painter = painterResource(R.drawable.image_signal_backups_media), + contentDescription = null, + modifier = Modifier.padding(vertical = 16.dp) + ) + + Text( + text = stringResource(R.string.MediaNoLongerAvailableSheet__this_media_is_no_longer_available), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 10.dp) + ) + + Text( + text = stringResource(R.string.MediaNoLongerAvailableSheet__to_start_backing_up_all_your_media), + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 92.dp) + ) + + Buttons.LargeTonal( + onClick = onContinueClick, + modifier = Modifier + .padding(bottom = 22.dp) + .defaultMinSize(minWidth = 220.dp) + ) { + Text( + text = stringResource(R.string.MediaNoLongerAvailableSheet__continue) + ) + } + + TextButton( + onClick = onNotNowClick, + modifier = Modifier + .padding(bottom = 32.dp) + .defaultMinSize(minWidth = 220.dp) + ) { + Text( + text = stringResource(R.string.MediaNoLongerAvailableSheet__not_now) + ) + } + } +} + +@SignalPreview +@Composable +private fun MediaNoLongerAvailableBottomSheetContentPreview() { + Previews.BottomSheetPreview { + MediaNoLongerAvailableBottomSheetContent() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt index e31573daae..f9b0cad872 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt @@ -382,6 +382,10 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter Log.w(TAG, "Not yet implemented!", Exception()) } + override fun onDisplayMediaNoLongerAvailableSheet() { + Log.w(TAG, "Not yet implemented!", Exception()) + } + interface Callback { fun onMessageDetailsFragmentDismissed() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80eb18b3a1..9895e6e147 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7565,6 +7565,16 @@ Subscribe for %1$s/month + + + This media is no longer available + + To start backing up all your media, enable Signal Backups and choose the paid tier. + + Continue + + Not now + Deleting is now synced across all of your devices