CFV2 handle keyboard images and gifs.

This commit is contained in:
Alex Hart 2023-07-18 12:51:07 -03:00 committed by Nicholas
parent b8f55f982f
commit f3fb5ccc3b
5 changed files with 98 additions and 32 deletions

View file

@ -1,13 +1,9 @@
package org.thoughtcrime.securesms.conversation.drafts package org.thoughtcrime.securesms.conversation.drafts
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString 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.Maybe
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers 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.MessageRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyboard.KeyboardUtil
import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
import org.thoughtcrime.securesms.mms.GifSlide import org.thoughtcrime.securesms.mms.GifSlide
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.ImageSlide
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.mms.QuoteId 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.hasTextSlide
import org.thoughtcrime.securesms.util.requireTextSlide import org.thoughtcrime.securesms.util.requireTextSlide
import java.io.IOException import java.io.IOException
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class DraftRepository( class DraftRepository(
private val context: Context = ApplicationDependencies.getApplication(), private val context: Context = ApplicationDependencies.getApplication(),
@ -96,7 +88,7 @@ class DraftRepository(
} }
if (shareMedia != null && shareContentType != null && borderless) { 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) { if (details == null || !details.hasTransparency) {
return ShareOrDraftData.SetMedia(shareMedia, shareMediaType!!, null) to null return ShareOrDraftData.SetMedia(shareMedia, shareMediaType!!, null) to null
@ -260,26 +252,6 @@ class DraftRepository(
return ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, threadRecipient) 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?) data class DatabaseDraft(val drafts: Drafts, val updatedText: CharSequence?)
sealed interface ShareOrDraftData { sealed interface ShareOrDraftData {
@ -292,6 +264,4 @@ class DraftRepository(
data class SetQuote(val quote: ConversationMessage, val draftText: CharSequence?) : ShareOrDraftData data class SetQuote(val quote: ConversationMessage, val draftText: CharSequence?) : ShareOrDraftData
data class SetEditMessage(val messageEdit: 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)
} }

View file

@ -216,6 +216,7 @@ import org.thoughtcrime.securesms.invites.InviteActions
import org.thoughtcrime.securesms.keyboard.KeyboardPage import org.thoughtcrime.securesms.keyboard.KeyboardPage
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment
import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel 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.EmojiKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment
@ -921,6 +922,7 @@ class ConversationFragment :
initializeInlineSearch() initializeInlineSearch()
inputPanel.setListener(InputPanelListener()) inputPanel.setListener(InputPanelListener())
inputPanel.setMediaListener(InputPanelMediaListener())
viewModel viewModel
.getScheduledMessagesCount() .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 //endregion
//region Attachment + Media Keyboard //region Attachment + Media Keyboard

View file

@ -74,9 +74,11 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob
import org.thoughtcrime.securesms.keyboard.KeyboardUtil
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.messagerequests.MessageRequestState import org.thoughtcrime.securesms.messagerequests.MessageRequestState
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.mms.OutgoingMessage import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
@ -120,6 +122,15 @@ class ConversationRepository(
private val applicationContext = context.applicationContext private val applicationContext = context.applicationContext
private val oldConversationRepository = org.thoughtcrime.securesms.conversation.ConversationRepository() private val oldConversationRepository = org.thoughtcrime.securesms.conversation.ConversationRepository()
/**
* Gets image details for an image sent from the keyboard
*/
fun getKeyboardImageDetails(uri: Uri): Maybe<KeyboardUtil.ImageDetails> {
return MaybeCompat.fromCallable {
KeyboardUtil.getImageDetails(GlideApp.with(applicationContext), uri)
}.subscribeOn(Schedulers.io())
}
/** /**
* Loads the details necessary to display the conversation thread. * Loads the details necessary to display the conversation thread.
*/ */

View file

@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
import org.thoughtcrime.securesms.keyboard.KeyboardUtil
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
@ -320,6 +321,10 @@ class ConversationViewModel(
} }
} }
fun getKeyboardImageDetails(uri: Uri): Maybe<KeyboardUtil.ImageDetails> {
return repository.getKeyboardImageDetails(uri)
}
private fun MessageRecord.oldReactionRecord(): ReactionRecord? { private fun MessageRecord.oldReactionRecord(): ReactionRecord? {
return reactions.firstOrNull { it.author == Recipient.self().id } return reactions.firstOrNull { it.author == Recipient.self().id }
} }

View file

@ -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)
}