commit 5498f8206dff28f865f8824ad3caf6c162531a54
parent 05a495d51ffa7ed63248d65dac9273c0f83ec83d
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 21 Jan 2024 11:00:31 +0100
feat(core): long press to force download
Diffstat:
8 files changed, 88 insertions(+), 48 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -182,11 +182,11 @@
},
"opera_download_button": {
"name": "Opera Download Button",
- "description": "Adds a download button on the top right corner when viewing a Snap"
+ "description": "Adds a download button on the top right corner when viewing a Snap.\nLong press on buttons will force download"
},
- "chat_download_context_menu": {
- "name": "Chat Download Context Menu",
- "description": "Allows you to download media from a conversation by long-pressing them"
+ "download_context_menu": {
+ "name": "Download Context Menu",
+ "description": "Allows you to download/preview messages from a conversation or a story using the context menu.\nLong press on buttons will force download"
},
"ffmpeg_options": {
"name": "FFmpeg Options",
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/DownloaderConfig.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/DownloaderConfig.kt
@@ -43,7 +43,7 @@ class DownloaderConfig : ConfigContainer() {
}
val downloadProfilePictures = boolean("download_profile_pictures") { requireRestart() }
val operaDownloadButton = boolean("opera_download_button") { requireRestart() }
- val chatDownloadContextMenu = boolean("chat_download_context_menu")
+ val downloadContextMenu = boolean("download_context_menu")
val ffmpegOptions = container("ffmpeg_options", FFMpegOptions()) { addNotices(FeatureNotice.UNSTABLE) }
val logging = multiple("logging", "started", "success", "progress", "failure").apply {
set(mutableListOf("success", "progress", "failure"))
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt
@@ -94,6 +94,7 @@ class SnapEnhance {
val isMainActivityNotNull = appContext.mainActivity != null
appContext.mainActivity = this
if (isMainActivityNotNull || !appContext.mappings.isMappingsLoaded) return@hookMainActivity
+ appContext.isMainActivityPaused = false
onActivityCreate()
jetpackComposeResourceHook()
appContext.actionManager.onNewIntent(intent)
@@ -108,18 +109,13 @@ class SnapEnhance {
appContext.actionManager.onNewIntent(param.argNullable(0))
}
- var activityWasResumed = false
- //we need to reload the config when the app is resumed
- //FIXME: called twice at first launch
hookMainActivity("onResume") {
- appContext.isMainActivityPaused = false
- if (!activityWasResumed) {
- activityWasResumed = true
- return@hookMainActivity
+ if (appContext.isMainActivityPaused.also {
+ appContext.isMainActivityPaused = false
+ }) {
+ appContext.reloadConfig()
+ syncRemote()
}
-
- appContext.reloadConfig()
- syncRemote()
}
}
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
@@ -75,10 +75,11 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
mediaAuthor: String,
creationTimestamp: Long? = null,
downloadSource: MediaDownloadSource,
- friendInfo: FriendInfo? = null
+ friendInfo: FriendInfo? = null,
+ forceAllowDuplicate: Boolean = false
): DownloadManagerClient {
val generatedHash = (
- if (!context.config.downloader.allowDuplicate.get()) mediaIdentifier
+ if (!context.config.downloader.allowDuplicate.get() && !forceAllowDuplicate) mediaIdentifier
else UUID.randomUUID().toString()
).longHashCode().absoluteValue.toString(16)
@@ -135,10 +136,10 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
/*
* Download the last seen media
*/
- fun downloadLastOperaMediaAsync() {
+ fun downloadLastOperaMediaAsync(allowDuplicate: Boolean) {
if (lastSeenMapParams == null || lastSeenMediaInfoMap == null) return
context.executeAsync {
- handleOperaMedia(lastSeenMapParams!!, lastSeenMediaInfoMap!!, true)
+ handleOperaMedia(lastSeenMapParams!!, lastSeenMediaInfoMap!!, true, allowDuplicate)
}
}
@@ -162,9 +163,6 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
setNeutralButton("Copy") { _, _ ->
this@MediaDownloader.context.copyToClipboard(mediaInfoText)
}
- setPositiveButton("Download") { _, _ ->
- downloadLastOperaMediaAsync()
- }
setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() }
}.show()
}
@@ -229,7 +227,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
private fun handleOperaMedia(
paramMap: ParamMap,
mediaInfoMap: Map<SplitMediaAssetType, MediaInfo>,
- forceDownload: Boolean
+ forceDownload: Boolean,
+ forceAllowDuplicate: Boolean = false
) {
//messages
paramMap["MESSAGE_ID"]?.toString()?.takeIf { forceDownload || canAutoDownload("friend_snaps") }?.let { id ->
@@ -257,7 +256,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
mediaAuthor = authorUsername,
creationTimestamp = conversationMessage.creationTimestamp,
downloadSource = MediaDownloadSource.CHAT_MEDIA,
- friendInfo = author
+ friendInfo = author,
+ forceAllowDuplicate = forceAllowDuplicate
), mediaInfoMap)
return
@@ -301,7 +301,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
creationTimestamp = paramMap["PLAYABLE_STORY_SNAP_RECORD"]?.toString()?.substringAfter("timestamp=")
?.substringBefore(",")?.toLongOrNull(),
downloadSource = MediaDownloadSource.STORY,
- friendInfo = author
+ friendInfo = author,
+ forceAllowDuplicate = forceAllowDuplicate,
), mediaInfoMap)
return
}
@@ -331,6 +332,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
mediaAuthor = author,
downloadSource = MediaDownloadSource.PUBLIC_STORY,
creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
+ forceAllowDuplicate = forceAllowDuplicate,
), mediaInfoMap)
return
}
@@ -342,6 +344,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
downloadSource = MediaDownloadSource.SPOTLIGHT,
mediaAuthor = paramMap["CREATOR_DISPLAY_NAME"].toString(),
creationTimestamp = paramMap["SNAP_TIMESTAMP"]?.toString()?.toLongOrNull(),
+ forceAllowDuplicate = forceAllowDuplicate,
), mediaInfoMap)
return
}
@@ -435,7 +438,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
provideDownloadManagerClient(
mediaIdentifier = "${paramMap["STORY_ID"]}-${firstChapter.offset}-${lastChapter.offset}",
downloadSource = MediaDownloadSource.PUBLIC_STORY,
- mediaAuthor = storyName
+ mediaAuthor = storyName,
+ forceAllowDuplicate = forceAllowDuplicate,
).downloadDashMedia(
playlistUrl,
firstChapter.offset.plus(100),
@@ -505,7 +509,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
friendInfo: FriendInfo,
message: ConversationMessage,
authorName: String,
- attachments: List<DecodedAttachment>
+ attachments: List<DecodedAttachment>,
+ forceAllowDuplicate: Boolean = false
) {
//TODO: stickers
attachments.forEach { attachment ->
@@ -514,7 +519,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
mediaIdentifier = "${message.clientConversationId}${message.senderId}${message.serverMessageId}${attachment.mediaUniqueId}",
downloadSource = MediaDownloadSource.CHAT_MEDIA,
mediaAuthor = authorName,
- friendInfo = friendInfo
+ friendInfo = friendInfo,
+ forceAllowDuplicate = forceAllowDuplicate,
).downloadSingleMedia(
mediaData = attachment.mediaUrlKey!!,
mediaType = DownloadMediaType.PROTO_MEDIA,
@@ -531,7 +537,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
@SuppressLint("SetTextI18n")
@OptIn(ExperimentalCoroutinesApi::class)
- fun downloadMessageId(messageId: Long, isPreview: Boolean = false) {
+ fun downloadMessageId(messageId: Long, forceAllowDuplicate: Boolean = false, isPreview: Boolean = false) {
val messageLogger = context.feature(MessageLogger::class)
val message = context.database.getConversationMessageFromId(messageId) ?: throw Exception("Message not found in database")
@@ -570,7 +576,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
context.mainActivity == null // we can't show alert dialogs when it downloads from a notification, so it downloads the first one
) {
downloadMessageAttachments(friendInfo, message, authorName,
- listOf(decodedAttachments.first())
+ listOf(decodedAttachments.first()),
+ forceAllowDuplicate = forceAllowDuplicate
)
return
}
@@ -595,7 +602,9 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
setTitle(translations["select_attachments_title"])
setNegativeButton(this@MediaDownloader.context.translation["button.cancel"]) { dialog, _ -> dialog.dismiss() }
setPositiveButton(this@MediaDownloader.context.translation["button.download"]) { _, _ ->
- downloadMessageAttachments(friendInfo, message, authorName, selectedAttachments.map { decodedAttachments[it] })
+ downloadMessageAttachments(friendInfo, message, authorName, selectedAttachments.map { decodedAttachments[it] },
+ forceAllowDuplicate = forceAllowDuplicate
+ )
}
}.show()
}
@@ -698,9 +707,12 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
/**
* Called when a message is focused in chat
*/
- fun onMessageActionMenu(isPreviewMode: Boolean) {
+ fun onMessageActionMenu(isPreviewMode: Boolean, forceAllowDuplicate: Boolean = false) {
val messaging = context.feature(Messaging::class)
if (messaging.openedConversationUUID == null) return
- downloadMessageId(messaging.lastFocusedMessageId, isPreviewMode)
+
+ context.executeAsync {
+ downloadMessageId(messaging.lastFocusedMessageId, forceAllowDuplicate, isPreviewMode)
+ }
}
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt
@@ -33,6 +33,7 @@ import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.ktx.getDimens
+import me.rhunk.snapenhance.core.util.ktx.vibrateLongPress
import java.text.SimpleDateFormat
import java.util.Date
@@ -80,7 +81,7 @@ class ChatActionMenu : AbstractMenu() {
override fun init() {
runCatching {
- if (!context.config.downloader.chatDownloadContextMenu.get() && context.config.messaging.messageLogger.globalState != true && !context.isDeveloper) return
+ if (!context.config.downloader.downloadContextMenu.get() && context.config.messaging.messageLogger.globalState != true && !context.isDeveloper) return
context.androidContext.classLoader.loadClass("com.snap.messaging.chat.features.actionmenu.ActionMenuChatItemContainer")
.hook("onMeasure", HookStage.BEFORE) { param ->
param.setArg(1,
@@ -130,12 +131,14 @@ class ChatActionMenu : AbstractMenu() {
}
}
- if (context.config.downloader.chatDownloadContextMenu.get()) {
+ if (context.config.downloader.downloadContextMenu.get()) {
+ val mediaDownloader = context.feature(MediaDownloader::class)
+
injectButton(Button(viewGroup.context).apply {
text = this@ChatActionMenu.context.translation["chat_action_menu.preview_button"]
setOnClickListener {
closeActionMenu()
- this@ChatActionMenu.context.executeAsync { feature(MediaDownloader::class).onMessageActionMenu(true) }
+ mediaDownloader.onMessageActionMenu(true)
}
})
@@ -143,9 +146,13 @@ class ChatActionMenu : AbstractMenu() {
text = this@ChatActionMenu.context.translation["chat_action_menu.download_button"]
setOnClickListener {
closeActionMenu()
- this@ChatActionMenu.context.executeAsync {
- feature(MediaDownloader::class).onMessageActionMenu(false)
- }
+ mediaDownloader.onMessageActionMenu(false)
+ }
+ setOnLongClickListener {
+ closeActionMenu()
+ context.vibrateLongPress()
+ mediaDownloader.onMessageActionMenu(isPreviewMode = false, forceAllowDuplicate = true)
+ true
}
})
}
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
@@ -13,6 +13,7 @@ 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.util.ktx.vibrateLongPress
import me.rhunk.snapenhance.core.wrapper.impl.ScSize
import java.text.DateFormat
import java.util.Date
@@ -128,14 +129,22 @@ class OperaContextActionMenu : AbstractMenu() {
}
}
- linearLayout.addView(Button(view.context).apply {
- text = translation["download"]
- setOnClickListener {
- mediaDownloader.downloadLastOperaMediaAsync()
- parentView.triggerCloseTouchEvent()
- }
- applyTheme(isAmoled = false)
- })
+ if (context.config.downloader.downloadContextMenu.get()) {
+ linearLayout.addView(Button(view.context).apply {
+ text = translation["download"]
+ setOnClickListener {
+ mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = false)
+ parentView.triggerCloseTouchEvent()
+ }
+ setOnLongClickListener {
+ context.vibrateLongPress()
+ mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = true)
+ parentView.triggerCloseTouchEvent()
+ true
+ }
+ applyTheme(isAmoled = false)
+ })
+ }
if (context.isDeveloper) {
linearLayout.addView(Button(view.context).apply {
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaDownloadIconMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaDownloadIconMenu.kt
@@ -11,6 +11,7 @@ import me.rhunk.snapenhance.core.ui.children
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
import me.rhunk.snapenhance.core.util.ktx.getDimens
import me.rhunk.snapenhance.core.util.ktx.getDrawable
+import me.rhunk.snapenhance.core.util.ktx.vibrateLongPress
class OperaDownloadIconMenu : AbstractMenu() {
private val downloadSvgDrawable by lazy { context.resources.getDrawable("svg_download", context.androidContext.theme) }
@@ -21,6 +22,8 @@ class OperaDownloadIconMenu : AbstractMenu() {
override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) {
if (!context.config.downloader.operaDownloadButton.get()) return
+ val mediaDownloader = context.feature(MediaDownloader::class)
+
parent.addView(ImageView(view.context).apply {
setImageDrawable(downloadSvgDrawable)
setColorFilter(Color.WHITE)
@@ -33,7 +36,12 @@ class OperaDownloadIconMenu : AbstractMenu() {
gravity = Gravity.TOP or Gravity.END
}
setOnClickListener {
- this@OperaDownloadIconMenu.context.feature(MediaDownloader::class).downloadLastOperaMediaAsync()
+ mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = false)
+ }
+ setOnLongClickListener {
+ context.vibrateLongPress()
+ mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = true)
+ true
}
addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/AndroidExt.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/AndroidExt.kt
@@ -1,10 +1,13 @@
package me.rhunk.snapenhance.core.util.ktx
import android.annotation.SuppressLint
+import android.content.Context
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.content.res.TypedArray
import android.graphics.drawable.Drawable
+import android.os.VibrationEffect
+import android.os.Vibrator
import me.rhunk.snapenhance.common.Constants
@@ -31,3 +34,7 @@ fun Resources.getStyledAttributes(name: String, theme: Theme): TypedArray {
fun Resources.getDrawable(name: String, theme: Theme): Drawable {
return getDrawable(getIdentifier(name, "drawable"), theme)
}
+
+fun Context.vibrateLongPress() {
+ getSystemService(Vibrator::class.java).vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE))
+}+
\ No newline at end of file