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:
Mcommon/src/main/assets/lang/en_US.json | 12+++++++++++-
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/downloader/MediaDownloader.kt | 23+++++++++++++++++------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaContextActionMenu.kt | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
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) })