commit 752f87179fb71a5aa0440bca4159c5246a75a152
parent 15c56b705f99c27905d5719285ae7955858ac2ab
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Wed, 22 Nov 2023 22:22:15 +0100
feat(core): opera media quick info
- add creation timestamp to MediaDownloader instead of current timestamp
Diffstat:
4 files changed, 90 insertions(+), 10 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -282,6 +282,10 @@
"name": "Hide UI Components",
"description": "Select which UI components to hide"
},
+ "opera_media_quick_info": {
+ "name": "Opera Media Quick Info",
+ "description": "Shows useful information of media such as creation date in opera viewer context menu"
+ },
"old_bitmoji_selfie": {
"name": "Old Bitmoji Selfie",
"description": "Brings back the Bitmoji selfies from older Snapchat versions"
@@ -793,7 +797,13 @@
},
"opera_context_menu": {
- "download": "Download Media"
+ "download": "Download Media",
+ "sent_at": "Sent at {date}",
+ "created_at": "Created at {date}",
+ "expires_at": "Expires at {date}",
+ "media_size": "Media size: {size}",
+ "media_duration": "Media duration: {duration} ms",
+ "show_debug_info": "Show Debug Info"
},
"modal_option": {
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt
@@ -42,6 +42,7 @@ class UserInterfaceTweaks : ConfigContainer() {
"hide_chat_call_buttons",
"hide_profile_call_buttons"
) { requireRestart() }
+ val operaMediaQuickInfo = boolean("opera_media_quick_info") { requireRestart() }
val oldBitmojiSelfie = unique("old_bitmoji_selfie", "2d", "3d") { requireCleanCache() }
val disableSpotlight = boolean("disable_spotlight") { requireRestart() }
val hideSettingsGear = boolean("hide_settings_gear") { requireRestart() }
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/downloader/MediaDownloader.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/downloader/MediaDownloader.kt
@@ -72,8 +72,8 @@ class SnapChapterInfo(
@OptIn(ExperimentalEncodingApi::class)
class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleType.AUTO_DOWNLOAD, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
private var lastSeenMediaInfoMap: MutableMap<SplitMediaAssetType, MediaInfo>? = null
- private var lastSeenMapParams: ParamMap? = null
-
+ var lastSeenMapParams: ParamMap? = null
+ private set
private val translations by lazy {
context.translation.getCategory("download_processor")
}
@@ -81,6 +81,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
private fun provideDownloadManagerClient(
mediaIdentifier: String,
mediaAuthor: String,
+ creationTimestamp: Long? = null,
downloadSource: MediaDownloadSource,
friendInfo: FriendInfo? = null
): DownloadManagerClient {
@@ -93,7 +94,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
context.shortToast(translations["download_started_toast"])
}
- val outputPath = createNewFilePath(generatedHash, downloadSource, mediaAuthor)
+ val outputPath = createNewFilePath(generatedHash, downloadSource, mediaAuthor, creationTimestamp)
return DownloadManagerClient(
context = context,
@@ -133,11 +134,16 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
}
- private fun createNewFilePath(hexHash: String, downloadSource: MediaDownloadSource, mediaAuthor: String): String {
+ private fun createNewFilePath(
+ hexHash: String,
+ downloadSource: MediaDownloadSource,
+ mediaAuthor: String,
+ creationTimestamp: Long?
+ ): String {
val pathFormat by context.config.downloader.pathFormat
val sanitizedMediaAuthor = mediaAuthor.sanitizeForPath().ifEmpty { hexHash }
- val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(System.currentTimeMillis())
+ val currentDateTime = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.ENGLISH).format(creationTimestamp ?: System.currentTimeMillis())
val finalPath = StringBuilder()
@@ -299,6 +305,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
downloadOperaMedia(provideDownloadManagerClient(
mediaIdentifier = "$conversationId$senderId${conversationMessage.serverMessageId}$mediaId",
mediaAuthor = authorUsername,
+ creationTimestamp = conversationMessage.creationTimestamp,
downloadSource = MediaDownloadSource.CHAT_MEDIA,
friendInfo = author
), mediaInfoMap)
@@ -343,6 +350,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
downloadOperaMedia(provideDownloadManagerClient(
mediaIdentifier = paramMap["MEDIA_ID"].toString(),
mediaAuthor = authorName,
+ creationTimestamp = paramMap["PLAYABLE_STORY_SNAP_RECORD"]?.toString()?.substringAfter("timestamp=")
+ ?.substringBefore(",")?.toLongOrNull(),
downloadSource = MediaDownloadSource.STORY,
friendInfo = author
), mediaInfoMap)
@@ -360,6 +369,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
mediaIdentifier = paramMap["SNAP_ID"].toString(),
mediaAuthor = userDisplayName,
downloadSource = MediaDownloadSource.PUBLIC_STORY,
+ creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
), mediaInfoMap)
return
}
@@ -369,7 +379,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
downloadOperaMedia(provideDownloadManagerClient(
mediaIdentifier = paramMap["SNAP_ID"].toString(),
downloadSource = MediaDownloadSource.SPOTLIGHT,
- mediaAuthor = paramMap["TIME_STAMP"].toString()
+ mediaAuthor = paramMap["CREATOR_DISPLAY_NAME"].toString(),
+ creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
), mediaInfoMap)
return
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaContextActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaContextActionMenu.kt
@@ -7,11 +7,15 @@ import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.ScrollView
+import android.widget.TextView
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.ui.applyTheme
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent
import me.rhunk.snapenhance.core.util.ktx.getId
+import me.rhunk.snapenhance.core.wrapper.impl.ScSize
+import java.text.DateFormat
+import java.util.Date
@SuppressLint("DiscouragedApi")
class OperaContextActionMenu : AbstractMenu() {
@@ -67,11 +71,65 @@ class OperaContextActionMenu : AbstractMenu() {
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
- val translation = context.translation
+ val translation = context.translation.getCategory("opera_context_menu")
val mediaDownloader = context.feature(MediaDownloader::class)
+ val paramMap = mediaDownloader.lastSeenMapParams
+
+ if (paramMap != null && context.config.userInterface.operaMediaQuickInfo.get()) {
+ val playableStorySnapRecord = paramMap["PLAYABLE_STORY_SNAP_RECORD"]?.toString()
+ val sentTimestamp = playableStorySnapRecord?.substringAfter("timestamp=")
+ ?.substringBefore(",")?.toLongOrNull()
+ ?: paramMap["MESSAGE_ID"]?.toString()?.let { messageId ->
+ context.database.getConversationMessageFromId(
+ messageId.substring(messageId.lastIndexOf(":") + 1)
+ .toLong()
+ )?.creationTimestamp
+ }
+ ?: paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull()
+ val dateFormat = DateFormat.getDateTimeInstance()
+ val creationTimestamp = playableStorySnapRecord?.substringAfter("creationTimestamp=")
+ ?.substringBefore(",")?.toLongOrNull()
+ val expirationTimestamp = playableStorySnapRecord?.substringAfter("expirationTimestamp=")
+ ?.substringBefore(",")?.toLongOrNull()
+ ?: paramMap["SNAP_EXPIRATION_TIMESTAMP_MILLIS"]?.toString()?.toLongOrNull()
+
+ val mediaSize = paramMap["snap_size"]?.let { ScSize(it) }
+ val durationMs = paramMap["media_duration_ms"]?.toString()
+
+ val stringBuilder = StringBuilder().apply {
+ if (sentTimestamp != null) {
+ append(translation.format("sent_at", "date" to dateFormat.format(Date(sentTimestamp))))
+ append("\n")
+ }
+ if (creationTimestamp != null) {
+ append(translation.format("created_at", "date" to dateFormat.format(Date(creationTimestamp))))
+ append("\n")
+ }
+ if (expirationTimestamp != null) {
+ append(translation.format("expires_at", "date" to dateFormat.format(Date(expirationTimestamp))))
+ append("\n")
+ }
+ if (mediaSize != null) {
+ append(translation.format("media_size", "size" to "${mediaSize.first}x${mediaSize.second}"))
+ append("\n")
+ }
+ if (durationMs != null) {
+ append(translation.format("media_duration", "duration" to durationMs))
+ append("\n")
+ }
+ if (last() == '\n') deleteCharAt(length - 1)
+ }
+
+ if (stringBuilder.isNotEmpty()) {
+ linearLayout.addView(TextView(view.context).apply {
+ text = stringBuilder.toString()
+ setPadding(40, 10, 0, 0)
+ })
+ }
+ }
linearLayout.addView(Button(view.context).apply {
- text = translation["opera_context_menu.download"]
+ text = translation["download"]
setOnClickListener {
mediaDownloader.downloadLastOperaMediaAsync()
parentView.triggerCloseTouchEvent()
@@ -81,7 +139,7 @@ class OperaContextActionMenu : AbstractMenu() {
if (context.isDeveloper) {
linearLayout.addView(Button(view.context).apply {
- text = "Show debug info"
+ text = translation["show_debug_info"]
setOnClickListener { mediaDownloader.showLastOperaDebugMediaInfo() }
applyTheme(isAmoled = false)
})