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 3dd7797a9c..a08bc09f11 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 @@ -110,6 +110,7 @@ import org.thoughtcrime.securesms.components.mention.MentionAnnotation import org.thoughtcrime.securesms.components.menu.ActionItem import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager +import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalFragment import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity @@ -199,19 +200,21 @@ import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory.create import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult -import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository import org.thoughtcrime.securesms.messagerequests.MessageRequestState import org.thoughtcrime.securesms.mms.AttachmentManager import org.thoughtcrime.securesms.mms.AudioSlide +import org.thoughtcrime.securesms.mms.GifSlide import org.thoughtcrime.securesms.mms.GlideApp +import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.SlideFactory import org.thoughtcrime.securesms.mms.StickerSlide +import org.thoughtcrime.securesms.mms.VideoSlide import org.thoughtcrime.securesms.notifications.v2.ConversationId import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity import org.thoughtcrime.securesms.permissions.Permissions @@ -229,6 +232,7 @@ import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity import org.thoughtcrime.securesms.revealable.ViewOnceUtil import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet +import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity @@ -240,6 +244,7 @@ import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.ContextUtil import org.thoughtcrime.securesms.util.Debouncer import org.thoughtcrime.securesms.util.DeleteDialog +import org.thoughtcrime.securesms.util.Dialogs import org.thoughtcrime.securesms.util.DrawableUtil import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.FullscreenHelper @@ -1190,7 +1195,9 @@ class ConversationFragment : slideDeck: SlideDeck? = if (attachmentManager.isAttachmentPresent) attachmentManager.buildSlideDeck() else null, contacts: List = emptyList(), clearCompose: Boolean = true, - linkPreviews: List = linkPreviewViewModel.onSend() + linkPreviews: List = linkPreviewViewModel.onSend(), + preUploadResults: List = emptyList(), + afterSendComplete: () -> Unit = {} ) { val metricId = viewModel.recipientSnapshot?.let { if (it.isGroup == true) SignalLocalMetrics.GroupMessageSend.start() else SignalLocalMetrics.IndividualMessageSend.start() } @@ -1204,7 +1211,8 @@ class ConversationFragment : mentions = mentions, bodyRanges = bodyRanges, contacts = contacts, - linkPreviews = linkPreviews + linkPreviews = linkPreviews, + preUploadResults = preUploadResults ) disposables += send @@ -1222,7 +1230,10 @@ class ConversationFragment : is RecipientFormattingException -> toast(R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation, Toast.LENGTH_LONG) } }, - onComplete = this::onSendComplete + onComplete = { + onSendComplete() + afterSendComplete() + } ) } @@ -2501,7 +2512,70 @@ class ConversationFragment : } override fun onMediaSend(result: MediaSendActivityResult) { - // TODO [cfv2] media send + val recipientSnapshot = viewModel.recipientSnapshot + if (result.recipientId != recipientSnapshot?.id) { + Log.w(TAG, "Result's recipientId did not match ours! Result: " + result.recipientId + ", Ours: " + recipientSnapshot?.id) + toast(R.string.ConversationActivity_error_sending_media) + return + } + + if (result.isPushPreUpload) { + sendPreUploadMediaMessage(result) + return + } + + val slides: List = result.nonUploadedMedia.mapNotNull { + when { + MediaUtil.isVideoType(it.mimeType) -> VideoSlide(requireContext(), it.uri, it.size, it.isVideoGif, it.width, it.height, it.caption.orNull(), it.transformProperties.orNull()) + MediaUtil.isGif(it.mimeType) -> GifSlide(requireContext(), it.uri, it.size, it.width, it.height, it.isBorderless, it.caption.orNull()) + MediaUtil.isImageType(it.mimeType) -> ImageSlide(requireContext(), it.uri, it.mimeType, it.size, it.width, it.height, it.isBorderless, it.caption.orNull(), null, it.transformProperties.orNull()) + else -> { + Log.w(TAG, "Asked to send an unexpected mimeType: '${it.mimeType}'. Skipping.") + null + } + } + } + + sendMessage( + body = result.body, + mentions = result.mentions, + bodyRanges = result.bodyRanges, + messageToEdit = null, + quote = if (result.isViewOnce) null else inputPanel.quote.orNull(), + scheduledDate = result.scheduledTime, + slideDeck = SlideDeck().apply { slides.forEach { addSlide(it) } }, + contacts = emptyList(), + clearCompose = true, + linkPreviews = emptyList() + ) { + viewModel.deleteSlideData(slides) + } + } + + private fun sendPreUploadMediaMessage(result: MediaSendActivityResult) { + if (ExpiredBuildReminder.isEligible()) { + /* TODO [cfv2] -- Show expired dialog */ + return + } + + if (SignalStore.uiHints().hasNotSeenTextFormattingAlert() && result.bodyRanges != null && result.bodyRanges.rangesCount > 0) { + Dialogs.showFormattedTextDialog(requireContext()) { sendPreUploadMediaMessage(result) } + return + } + + sendMessage( + body = result.body, + mentions = result.mentions, + bodyRanges = result.bodyRanges, + messageToEdit = null, + quote = if (result.isViewOnce) null else inputPanel.quote.orNull(), + scheduledDate = result.scheduledTime, + slideDeck = null, + contacts = emptyList(), + clearCompose = true, + linkPreviews = emptyList(), + preUploadResults = result.preUploadResults + ) } } @@ -2951,7 +3025,7 @@ class ConversationFragment : AttachmentKeyboardButton.PAYMENT -> AttachmentManager.selectPayment(this@ConversationFragment, viewModel.recipientSnapshot!!) } } else if (media != null) { - startActivityForResult(MediaSelectionActivity.editor(requireActivity(), sendButton.selectedSendType, listOf(media), viewModel.recipientSnapshot!!.id, composeText.textTrimmed), 12) + conversationActivityResultContracts.launchMediaEditor(listOf(media), viewModel.recipientSnapshot!!.id, composeText.textTrimmed) } container.hideInput() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index e2f63a6ee2..e3a188af18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -79,6 +79,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.OutgoingMessage import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.QuoteModel +import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil import org.thoughtcrime.securesms.providers.BlobProvider @@ -87,6 +88,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFormattingException import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.search.MessageResult import org.thoughtcrime.securesms.sms.MessageSender +import org.thoughtcrime.securesms.sms.MessageSender.PreUploadResult import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.DrawableUtil import org.thoughtcrime.securesms.util.MediaUtil @@ -196,10 +198,11 @@ class ConversationRepository( mentions: List, bodyRanges: BodyRangeList?, contacts: List, - linkPreviews: List + linkPreviews: List, + preUploadResults: List ): Completable { val sendCompletable = Completable.create { emitter -> - if (body.isEmpty() && slideDeck?.containsMediaSlide() != true) { + if (body.isEmpty() && slideDeck?.containsMediaSlide() != true && preUploadResults.isEmpty()) { emitter.onError(InvalidMessageException("Message is empty!")) return@create } @@ -225,14 +228,25 @@ class ConversationRepository( linkPreviews = linkPreviews ) - MessageSender.send( - ApplicationDependencies.getApplication(), - message, - threadId, - MessageSender.SendType.SIGNAL, - metricId - ) { - emitter.onComplete() + if (preUploadResults.isEmpty()) { + MessageSender.send( + ApplicationDependencies.getApplication(), + message, + threadId, + MessageSender.SendType.SIGNAL, + metricId + ) { + emitter.onComplete() + } + } else { + MessageSender.sendPushWithPreUploadedMedia( + ApplicationDependencies.getApplication(), + message, + preUploadResults, + threadId + ) { + emitter.onComplete() + } } } @@ -526,6 +540,17 @@ class ConversationRepository( return oldConversationRepository.resolveMessageToEdit(conversationMessage) } + fun deleteSlideData(slides: List) { + SignalExecutors.BOUNDED_IO.execute { + slides + .mapNotNull(Slide::getUri) + .filter(BlobProvider::isAuthority) + .forEach { + BlobProvider.getInstance().delete(applicationContext, it) + } + } + } + /** * Glide target for a contact photo which expects an error drawable, and publishes * the result to the given emitter. diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 624f8ac61b..944a5dd19f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -51,10 +51,12 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository import org.thoughtcrime.securesms.messagerequests.MessageRequestState import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.QuoteModel +import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.search.MessageResult +import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.hasGiftBadge import org.thoughtcrime.securesms.util.rx.RxStore @@ -299,7 +301,8 @@ class ConversationViewModel( mentions: List, bodyRanges: BodyRangeList?, contacts: List, - linkPreviews: List + linkPreviews: List, + preUploadResults: List ): Completable { return repository.sendMessage( threadId = threadId, @@ -313,7 +316,8 @@ class ConversationViewModel( mentions = mentions, bodyRanges = bodyRanges, contacts = contacts, - linkPreviews = linkPreviews + linkPreviews = linkPreviews, + preUploadResults = preUploadResults ).observeOn(AndroidSchedulers.mainThread()) } @@ -354,4 +358,8 @@ class ConversationViewModel( fun resolveMessageToEdit(conversationMessage: ConversationMessage): Single { return repository.resolveMessageToEdit(conversationMessage) } + + fun deleteSlideData(slides: List) { + repository.deleteSlideData(slides) + } }