feat(media_downloader): better preview of chat messages

- set media resolver url to web api
This commit is contained in:
rhunk 2023-09-13 19:26:59 +02:00
parent bf73babf0d
commit 2e4b161eeb
3 changed files with 78 additions and 23 deletions

View File

@ -9,7 +9,7 @@ import java.io.InputStream
import java.util.Base64 import java.util.Base64
object RemoteMediaResolver { object RemoteMediaResolver {
private const val BOLT_HTTP_RESOLVER_URL = "https://aws.api.snapchat.com/bolt-http" private const val BOLT_HTTP_RESOLVER_URL = "https://web.snapchat.com/bolt-http"
const val CF_ST_CDN_D = "https://cf-st.sc-cdn.net/d/" const val CF_ST_CDN_D = "https://cf-st.sc-cdn.net/d/"
private val urlCache = mutableMapOf<String, String>() private val urlCache = mutableMapOf<String, String>()

View File

@ -32,4 +32,8 @@ abstract class Feature(
protected fun findClass(name: String): Class<*> { protected fun findClass(name: String): Class<*> {
return context.androidContext.classLoader.loadClass(name) return context.androidContext.classLoader.loadClass(name)
} }
protected fun runOnUiThread(block: () -> Unit) {
context.runOnUiThread(block)
}
} }

View File

@ -1,10 +1,18 @@
package me.rhunk.snapenhance.features.impl.downloader package me.rhunk.snapenhance.features.impl.downloader
import android.content.DialogInterface import android.annotation.SuppressLint
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.view.Gravity
import android.view.ViewGroup.MarginLayoutParams
import android.view.Window
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.bridge.DownloadCallback import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.core.database.objects.FriendInfo import me.rhunk.snapenhance.core.database.objects.FriendInfo
@ -475,6 +483,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
} }
} }
@SuppressLint("SetTextI18n")
@OptIn(ExperimentalCoroutinesApi::class)
fun downloadMessageId(messageId: Long, isPreview: Boolean = false) { fun downloadMessageId(messageId: Long, isPreview: Boolean = false) {
val messageLogger = context.feature(MessageLogger::class) val messageLogger = context.feature(MessageLogger::class)
val message = context.database.getConversationMessageFromId(messageId) ?: throw Exception("Message not found in database") val message = context.database.getConversationMessageFromId(messageId) ?: throw Exception("Message not found in database")
@ -557,35 +567,76 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
return return
} }
val downloadedMediaList = MediaDownloaderHelper.downloadMediaFromReference(urlProto) { runBlocking {
EncryptionHelper.decryptInputStream(it, contentType, messageReader, isArroyo = isArroyoMessage) val previewCoroutine = async {
} val downloadedMediaList = MediaDownloaderHelper.downloadMediaFromReference(urlProto) {
EncryptionHelper.decryptInputStream(it, contentType, messageReader, isArroyo = isArroyoMessage)
}
runCatching { val originalMedia = downloadedMediaList[SplitMediaAssetType.ORIGINAL] ?: return@async null
val originalMedia = downloadedMediaList[SplitMediaAssetType.ORIGINAL] ?: return val overlay = downloadedMediaList[SplitMediaAssetType.OVERLAY]
val overlay = downloadedMediaList[SplitMediaAssetType.OVERLAY]
var bitmap: Bitmap? = PreviewUtils.createPreview(originalMedia, isVideo = FileType.fromByteArray(originalMedia).isVideo) var bitmap: Bitmap? = PreviewUtils.createPreview(originalMedia, isVideo = FileType.fromByteArray(originalMedia).isVideo)
if (bitmap == null) { if (bitmap == null) {
context.shortToast(translations["failed_to_create_preview_toast"]) context.shortToast(translations["failed_to_create_preview_toast"])
return return@async null
} }
overlay?.let { overlay?.also {
bitmap = PreviewUtils.mergeBitmapOverlay(bitmap!!, BitmapFactory.decodeByteArray(it, 0, it.size)) bitmap = PreviewUtils.mergeBitmapOverlay(bitmap!!, BitmapFactory.decodeByteArray(it, 0, it.size))
}
bitmap
} }
with(ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)) { with(ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)) {
setView(ImageView(context).apply { val viewGroup = LinearLayout(context).apply {
setImageBitmap(bitmap) layoutParams = MarginLayoutParams(MarginLayoutParams.MATCH_PARENT, MarginLayoutParams.MATCH_PARENT)
}) gravity = Gravity.CENTER_HORIZONTAL or Gravity.CENTER_VERTICAL
setPositiveButton("Close") { dialog: DialogInterface, _: Int -> dialog.dismiss() } addView(ProgressBar(context).apply {
this@MediaDownloader.context.runOnUiThread { show()} isIndeterminate = true
})
}
setOnDismissListener {
previewCoroutine.cancel()
}
previewCoroutine.invokeOnCompletion { cause ->
runOnUiThread {
viewGroup.removeAllViews()
if (cause != null) {
viewGroup.addView(TextView(context).apply {
text = translations["failed_to_create_preview_toast"] + "\n" + cause.message
setPadding(30, 30, 30, 30)
})
return@runOnUiThread
}
viewGroup.addView(ImageView(context).apply {
setImageBitmap(previewCoroutine.getCompleted())
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
adjustViewBounds = true
})
}
}
runOnUiThread {
show().apply {
setContentView(viewGroup)
requestWindowFeature(Window.FEATURE_NO_TITLE)
window?.setLayout(
context.resources.displayMetrics.widthPixels,
context.resources.displayMetrics.heightPixels
)
}
previewCoroutine.start()
}
} }
}.onFailure {
context.shortToast(translations["failed_to_create_preview_toast"])
context.log.error("Failed to create preview", it)
} }
}.onFailure { }.onFailure {
context.longToast(translations["failed_generic_toast"]) context.longToast(translations["failed_generic_toast"])