CFV2 handle keyboard images and gifs.
This commit is contained in:
parent
b8f55f982f
commit
f3fb5ccc3b
5 changed files with 98 additions and 32 deletions
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue