Signal-Android/app/src/main/java/org/thoughtcrime/securesms/stories/StoryTextPostModel.kt

175 wiersze
7.0 KiB
Kotlin
Czysty Zwykły widok Historia

package org.thoughtcrime.securesms.stories
import android.graphics.Bitmap
2022-03-25 12:14:12 +00:00
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Parcel
import android.os.Parcelable
2022-08-01 16:13:45 +00:00
import android.view.ContextThemeWrapper
import android.view.View
import androidx.core.graphics.scale
import androidx.core.view.drawToBitmap
import com.bumptech.glide.load.Key
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.ResourceDecoder
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.resource.SimpleResource
import org.signal.core.util.concurrent.safeBlockingGet
2023-02-26 18:45:24 +00:00
import org.signal.core.util.readParcelableCompat
2022-08-01 16:13:45 +00:00
import org.thoughtcrime.securesms.R
2022-03-25 12:14:12 +00:00
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.database.SignalDatabase
2022-12-31 18:43:12 +00:00
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.StoryTextPost
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.fonts.TextFont
import org.thoughtcrime.securesms.fonts.TextToScript
import org.thoughtcrime.securesms.fonts.TypefaceCache
2022-08-01 16:13:45 +00:00
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.ParcelUtil
import java.io.IOException
import java.security.MessageDigest
/**
* Glide model to render a StoryTextPost as a bitmap
*/
data class StoryTextPostModel(
private val storyTextPost: StoryTextPost,
private val storySentAtMillis: Long,
private val storyAuthor: RecipientId,
private val bodyRanges: BodyRangeList?
) : Key, Parcelable {
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(storyTextPost.toByteArray())
messageDigest.update(storySentAtMillis.toString().toByteArray())
messageDigest.update(storyAuthor.serialize().toByteArray())
messageDigest.update(bodyRanges?.toByteArray() ?: ByteArray(0))
}
val text: String = storyTextPost.body
2022-03-25 12:14:12 +00:00
fun getPlaceholder(): Drawable {
return if (storyTextPost.hasBackground()) {
ChatColors.forChatColor(ChatColors.Id.NotSet, storyTextPost.background).chatBubbleMask
} else {
ColorDrawable(Color.TRANSPARENT)
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
ParcelUtil.writeByteArray(parcel, storyTextPost.toByteArray())
parcel.writeLong(storySentAtMillis)
parcel.writeParcelable(storyAuthor, flags)
ParcelUtil.writeByteArray(parcel, bodyRanges?.toByteArray())
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<StoryTextPostModel> {
override fun createFromParcel(parcel: Parcel): StoryTextPostModel {
return StoryTextPostModel(
storyTextPost = StoryTextPost.parseFrom(ParcelUtil.readByteArray(parcel)),
storySentAtMillis = parcel.readLong(),
2023-02-26 18:45:24 +00:00
storyAuthor = parcel.readParcelableCompat(RecipientId::class.java)!!,
bodyRanges = ParcelUtil.readByteArray(parcel)?.let { BodyRangeList.parseFrom(it) }
)
}
override fun newArray(size: Int): Array<StoryTextPostModel?> {
return arrayOfNulls(size)
}
fun parseFrom(messageRecord: MessageRecord): StoryTextPostModel {
return parseFrom(
body = messageRecord.body,
storySentAtMillis = messageRecord.timestamp,
storyAuthor = if (messageRecord.isOutgoing) Recipient.self().id else messageRecord.individualRecipient.id,
bodyRanges = messageRecord.messageRanges
)
}
@JvmStatic
@Throws(IOException::class)
fun parseFrom(body: String, storySentAtMillis: Long, storyAuthor: RecipientId, bodyRanges: BodyRangeList?): StoryTextPostModel {
return StoryTextPostModel(
storyTextPost = StoryTextPost.parseFrom(Base64.decode(body)),
storySentAtMillis = storySentAtMillis,
storyAuthor = storyAuthor,
bodyRanges = bodyRanges
)
}
}
class Decoder : ResourceDecoder<StoryTextPostModel, Bitmap> {
companion object {
private const val RENDER_HW_AR = 16f / 9f
}
override fun handles(source: StoryTextPostModel, options: Options): Boolean = true
override fun decode(source: StoryTextPostModel, width: Int, height: Int, options: Options): Resource<Bitmap> {
2022-12-31 18:43:12 +00:00
val message = SignalDatabase.messages.getMessageFor(source.storySentAtMillis, source.storyAuthor).run {
if (this is MediaMmsMessageRecord) {
this.withAttachments(ApplicationDependencies.getApplication(), SignalDatabase.attachments.getAttachmentsForMessage(this.id))
} else {
this
}
}
2022-08-01 16:13:45 +00:00
val view = StoryTextPostView(ContextThemeWrapper(ApplicationDependencies.getApplication(), R.style.TextSecure_DarkNoActionBar))
val typeface = TypefaceCache.get(
ApplicationDependencies.getApplication(),
TextFont.fromStyle(source.storyTextPost.style),
TextToScript.guessScript(source.storyTextPost.body)
).safeBlockingGet()
val displayWidth: Int = ApplicationDependencies.getApplication().resources.displayMetrics.widthPixels
val arHeight: Int = (RENDER_HW_AR * displayWidth).toInt()
2022-08-01 16:13:45 +00:00
val linkPreview = (message as? MmsMessageRecord)?.linkPreviews?.firstOrNull()
val useLargeThumbnail = source.text.isBlank()
view.setTypeface(typeface)
view.bindFromStoryTextPost(source.storyTextPost, source.bodyRanges)
2022-08-01 16:13:45 +00:00
view.bindLinkPreview(linkPreview, useLargeThumbnail, loadThumbnail = false)
view.postAdjustLinkPreviewTranslationY()
view.invalidate()
view.measure(View.MeasureSpec.makeMeasureSpec(displayWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(arHeight, View.MeasureSpec.EXACTLY))
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
val drawable = if (linkPreview != null && linkPreview.thumbnail.isPresent) {
GlideApp
.with(view)
.load(DecryptableStreamUriLoader.DecryptableUri(linkPreview.thumbnail.get().uri!!))
.centerCrop()
.submit(view.getLinkPreviewThumbnailWidth(useLargeThumbnail), view.getLinkPreviewThumbnailHeight(useLargeThumbnail))
.get()
} else {
null
}
view.setLinkPreviewDrawable(drawable, useLargeThumbnail)
view.invalidate()
view.measure(View.MeasureSpec.makeMeasureSpec(displayWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(arHeight, View.MeasureSpec.EXACTLY))
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
val bitmap = view.drawToBitmap().scale(width, height)
return SimpleResource(bitmap)
}
}
}