mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-12 05:07:46 +02:00
feat: in-chat snap preview
This commit is contained in:
@ -0,0 +1,88 @@
|
||||
package me.rhunk.snapenhance.core.features.impl.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.Shape
|
||||
import me.rhunk.snapenhance.common.Constants
|
||||
import me.rhunk.snapenhance.common.data.ContentType
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent
|
||||
import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.ui.addForegroundDrawable
|
||||
import me.rhunk.snapenhance.core.ui.removeForegroundDrawable
|
||||
import me.rhunk.snapenhance.core.util.EvictingMap
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.core.util.media.PreviewUtils
|
||||
import java.io.File
|
||||
|
||||
class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_SYNC or FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
private val mediaFileCache = mutableMapOf<String, File>() // mMediaId => mediaFile
|
||||
private val bitmapCache = EvictingMap<String, Bitmap>(50) // filePath => bitmap
|
||||
|
||||
private val isEnabled get() = context.config.userInterface.snapPreview.get()
|
||||
|
||||
override fun init() {
|
||||
if (!isEnabled) return
|
||||
context.mappings.getMappedClass("callbacks", "ContentCallback").hook("handleContentResult", HookStage.BEFORE) { param ->
|
||||
val contentResult = param.arg<Any>(0)
|
||||
val classMethods = contentResult::class.java.methods
|
||||
|
||||
val contentKey = classMethods.find { it.name == "getContentKey" }?.invoke(contentResult) ?: return@hook
|
||||
if (contentKey.getObjectField("mMediaContextType").toString() != "CHAT") return@hook
|
||||
|
||||
val filePath = classMethods.find { it.name == "getFilePath" }?.invoke(contentResult) ?: return@hook
|
||||
val mediaId = contentKey.getObjectField("mMediaId").toString()
|
||||
|
||||
mediaFileCache[mediaId.substringAfter("-")] = File(filePath.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
override fun onActivityCreate() {
|
||||
if (!isEnabled) return
|
||||
val chatMediaCardHeight = context.resources.getDimensionPixelSize(context.resources.getIdentifier("chat_media_card_height", "dimen", Constants.SNAPCHAT_PACKAGE_NAME))
|
||||
val chatMediaCardSnapMargin = context.resources.getDimensionPixelSize(context.resources.getIdentifier("chat_media_card_snap_margin", "dimen", Constants.SNAPCHAT_PACKAGE_NAME))
|
||||
val chatMediaCardSnapMarginStartSdl = context.resources.getDimensionPixelSize(context.resources.getIdentifier("chat_media_card_snap_margin_start_sdl", "dimen", Constants.SNAPCHAT_PACKAGE_NAME))
|
||||
|
||||
fun decodeMedia(file: File) = runCatching {
|
||||
bitmapCache.getOrPut(file.absolutePath) {
|
||||
PreviewUtils.resizeBitmap(
|
||||
PreviewUtils.createPreviewFromFile(file) ?: return@runCatching null,
|
||||
chatMediaCardHeight - chatMediaCardSnapMargin,
|
||||
chatMediaCardHeight - chatMediaCardSnapMargin
|
||||
)
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
context.event.subscribe(BindViewEvent::class) { event ->
|
||||
event.chatMessage { _, messageId ->
|
||||
event.view.removeForegroundDrawable("snapPreview")
|
||||
|
||||
val message = context.database.getConversationMessageFromId(messageId.toLong()) ?: return@chatMessage
|
||||
val messageReader = ProtoReader(message.messageContent ?: return@chatMessage)
|
||||
val contentType = ContentType.fromMessageContainer(messageReader.followPath(4, 4))
|
||||
|
||||
if (contentType != ContentType.SNAP) return@chatMessage
|
||||
|
||||
val mediaIdKey = messageReader.getString(4, 5, 1, 3, 2, 2) ?: return@chatMessage
|
||||
|
||||
event.view.addForegroundDrawable("snapPreview", ShapeDrawable(object: Shape() {
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
if (canvas.height / context.resources.displayMetrics.density > 90) return
|
||||
val bitmap = mediaFileCache[mediaIdKey]?.let { decodeMedia(it) } ?: return
|
||||
|
||||
canvas.drawBitmap(bitmap,
|
||||
canvas.width.toFloat() - bitmap.width - chatMediaCardSnapMarginStartSdl.toFloat() - chatMediaCardSnapMargin.toFloat(),
|
||||
(canvas.height - bitmap.height) / 2f,
|
||||
null
|
||||
)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -102,6 +102,7 @@ class FeatureManager(
|
||||
HideFriendFeedEntry::class,
|
||||
HideQuickAddFriendFeed::class,
|
||||
CallStartConfirmation::class,
|
||||
SnapPreview::class,
|
||||
)
|
||||
|
||||
initializeFeatures()
|
||||
|
@ -8,6 +8,7 @@ import android.media.MediaDataSource
|
||||
import android.media.MediaMetadataRetriever
|
||||
import me.rhunk.snapenhance.common.data.FileType
|
||||
import java.io.File
|
||||
import kotlin.math.max
|
||||
|
||||
object PreviewUtils {
|
||||
fun createPreview(data: ByteArray, isVideo: Boolean): Bitmap? {
|
||||
@ -52,14 +53,20 @@ object PreviewUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private fun resizeBitmap(bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap? {
|
||||
val scaleWidth = outWidth.toFloat() / bitmap.width
|
||||
val scaleHeight = outHeight.toFloat() / bitmap.height
|
||||
val matrix = Matrix()
|
||||
matrix.postScale(scaleWidth, scaleHeight)
|
||||
val resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, false)
|
||||
bitmap.recycle()
|
||||
return resizedBitmap
|
||||
fun resizeBitmap(source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
|
||||
val sourceWidth = source.getWidth()
|
||||
val sourceHeight = source.getHeight()
|
||||
val scale = max(outWidth.toFloat() / sourceWidth, outHeight.toFloat() / sourceHeight)
|
||||
|
||||
val dx = (outWidth - (scale * sourceWidth)) / 2F
|
||||
val dy = (outHeight - (scale * sourceHeight)) / 2F
|
||||
val dest = Bitmap.createBitmap(outWidth, outHeight, source.getConfig())
|
||||
val canvas = Canvas(dest)
|
||||
canvas.drawBitmap(source, Matrix().apply {
|
||||
postScale(scale, scale)
|
||||
postTranslate(dx, dy)
|
||||
}, null)
|
||||
return dest
|
||||
}
|
||||
|
||||
fun mergeBitmapOverlay(originalMedia: Bitmap, overlayLayer: Bitmap): Bitmap {
|
||||
|
Reference in New Issue
Block a user