From f3fb5ccc3baf3eb4b97baf9074a91892a9399bed Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Tue, 18 Jul 2023 12:51:07 -0300 Subject: [PATCH] CFV2 handle keyboard images and gifs. --- .../conversation/drafts/DraftRepository.kt | 34 +-------------- .../conversation/v2/ConversationFragment.kt | 38 +++++++++++++++++ .../conversation/v2/ConversationRepository.kt | 11 +++++ .../conversation/v2/ConversationViewModel.kt | 5 +++ .../securesms/keyboard/KeyboardUtil.kt | 42 +++++++++++++++++++ 5 files changed, 98 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt index dc121893d1..d64ba8b5bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt @@ -1,13 +1,9 @@ package org.thoughtcrime.securesms.conversation.drafts import android.content.Context -import android.graphics.Bitmap -import android.graphics.Color import android.net.Uri import android.text.Spannable import android.text.SpannableString -import androidx.annotation.WorkerThread -import com.bumptech.glide.load.engine.DiskCacheStrategy import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -34,11 +30,10 @@ import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.keyboard.KeyboardUtil import org.thoughtcrime.securesms.mediasend.Media -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri import org.thoughtcrime.securesms.mms.GifSlide import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.QuoteId @@ -53,10 +48,7 @@ import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor import org.thoughtcrime.securesms.util.hasTextSlide import org.thoughtcrime.securesms.util.requireTextSlide import java.io.IOException -import java.util.concurrent.ExecutionException import java.util.concurrent.Executor -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException class DraftRepository( private val context: Context = ApplicationDependencies.getApplication(), @@ -96,7 +88,7 @@ class DraftRepository( } if (shareMedia != null && shareContentType != null && borderless) { - val details = getKeyboardImageDetails(GlideApp.with(context), shareMedia) + val details = KeyboardUtil.getImageDetails(GlideApp.with(context), shareMedia) if (details == null || !details.hasTransparency) { return ShareOrDraftData.SetMedia(shareMedia, shareMediaType!!, null) to null @@ -260,26 +252,6 @@ class DraftRepository( return ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, threadRecipient) } - @WorkerThread - private fun getKeyboardImageDetails(glideRequests: GlideRequests, uri: Uri): KeyboardImageDetails? { - return try { - val bitmap: Bitmap = glideRequests.asBitmap() - .load(DecryptableUri(uri)) - .skipMemoryCache(true) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .submit() - .get(1000, TimeUnit.MILLISECONDS) - val topLeft = bitmap.getPixel(0, 0) - KeyboardImageDetails(bitmap.width, bitmap.height, Color.alpha(topLeft) < 255) - } catch (e: InterruptedException) { - null - } catch (e: ExecutionException) { - null - } catch (e: TimeoutException) { - null - } - } - data class DatabaseDraft(val drafts: Drafts, val updatedText: CharSequence?) sealed interface ShareOrDraftData { @@ -292,6 +264,4 @@ class DraftRepository( data class SetQuote(val quote: ConversationMessage, val draftText: CharSequence?) : ShareOrDraftData data class SetEditMessage(val messageEdit: ConversationMessage, val draftText: CharSequence?) : ShareOrDraftData } - - data class KeyboardImageDetails(val width: Int, val height: Int, val hasTransparency: Boolean) } 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 b2efec88e5..71e182dc77 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 @@ -216,6 +216,7 @@ import org.thoughtcrime.securesms.invites.InviteActions import org.thoughtcrime.securesms.keyboard.KeyboardPage import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel +import org.thoughtcrime.securesms.keyboard.KeyboardUtil import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment @@ -921,6 +922,7 @@ class ConversationFragment : initializeInlineSearch() inputPanel.setListener(InputPanelListener()) + inputPanel.setMediaListener(InputPanelMediaListener()) viewModel .getScheduledMessagesCount() @@ -3598,6 +3600,42 @@ class ConversationFragment : } } + private inner class InputPanelMediaListener : InputPanel.MediaListener { + override fun onMediaSelected(uri: Uri, contentType: String?) { + if (MediaUtil.isGif(contentType) || MediaUtil.isImageType(contentType)) { + disposables += viewModel.getKeyboardImageDetails(uri) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy( + onSuccess = { + sendKeyboardImage(uri, contentType!!, it) + }, + onComplete = { + sendKeyboardImage(uri, contentType!!, null) + } + ) + } else if (MediaUtil.isVideoType(contentType)) { + setMedia(uri, SlideFactory.MediaType.VIDEO) + } else { + setMedia(uri, SlideFactory.MediaType.AUDIO) + } + } + + private fun sendKeyboardImage(uri: Uri, contentType: String, keyboardImageDetails: KeyboardUtil.ImageDetails?) { + if (keyboardImageDetails == null || !keyboardImageDetails.hasTransparency) { + setMedia(uri, requireNotNull(SlideFactory.MediaType.from(contentType))) + return + } + + val slide: Slide = when { + MediaUtil.isGif(contentType) -> GifSlide(requireContext(), uri, 0, keyboardImageDetails.width, keyboardImageDetails.height, true, null) + MediaUtil.isImageType(contentType) -> ImageSlide(requireContext(), uri, contentType, 0, keyboardImageDetails.width, keyboardImageDetails.height, true, null, null) + else -> null + } ?: error("Only images are supported!") + + sendMessageWithoutComposeInput(slide) + } + } + //endregion //region Attachment + Media Keyboard 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 e139f4f9c4..33545065e9 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 @@ -74,9 +74,11 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob +import org.thoughtcrime.securesms.keyboard.KeyboardUtil import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.messagerequests.MessageRequestState +import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.OutgoingMessage import org.thoughtcrime.securesms.mms.PartAuthority @@ -120,6 +122,15 @@ class ConversationRepository( private val applicationContext = context.applicationContext private val oldConversationRepository = org.thoughtcrime.securesms.conversation.ConversationRepository() + /** + * Gets image details for an image sent from the keyboard + */ + fun getKeyboardImageDetails(uri: Uri): Maybe { + return MaybeCompat.fromCallable { + KeyboardUtil.getImageDetails(GlideApp.with(applicationContext), uri) + }.subscribeOn(Schedulers.io()) + } + /** * Loads the details necessary to display the conversation thread. */ 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 13bd684083..52e74933b7 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 @@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.database.model.StoryViewState import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.RetrieveProfileJob +import org.thoughtcrime.securesms.keyboard.KeyboardUtil import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository @@ -320,6 +321,10 @@ class ConversationViewModel( } } + fun getKeyboardImageDetails(uri: Uri): Maybe { + return repository.getKeyboardImageDetails(uri) + } + private fun MessageRecord.oldReactionRecord(): ReactionRecord? { return reactions.firstOrNull { it.author == Recipient.self().id } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt new file mode 100644 index 0000000000..97eb0db718 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/KeyboardUtil.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.keyboard + +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import androidx.annotation.WorkerThread +import com.bumptech.glide.load.engine.DiskCacheStrategy +import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader +import org.thoughtcrime.securesms.mms.GlideRequests +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +object KeyboardUtil { + + @WorkerThread + fun getImageDetails(glideRequests: GlideRequests, uri: Uri): ImageDetails? { + return try { + val bitmap: Bitmap = glideRequests.asBitmap() + .load(DecryptableStreamUriLoader.DecryptableUri(uri)) + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .submit() + .get(1000, TimeUnit.MILLISECONDS) + val topLeft = bitmap.getPixel(0, 0) + ImageDetails(bitmap.width, bitmap.height, Color.alpha(topLeft) < 255) + } catch (e: InterruptedException) { + null + } catch (e: ExecutionException) { + null + } catch (e: TimeoutException) { + null + } + } + + data class ImageDetails(val width: Int, val height: Int, val hasTransparency: Boolean) +}