commit 79be5da030dd2fa582ad3c7cb0bdd57181d072dc
parent bfe367efd0405408442f34f171497e3044e3220c
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 3 Dec 2023 12:42:57 +0100
feat: prevent message list auto scroll
Diffstat:
4 files changed, 88 insertions(+), 0 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -259,6 +259,10 @@
"name": "Enhanced Friend Map Nametags",
"description": "Improves the Nametags of friends on the Snapmap"
},
+ "prevent_message_list_auto_scroll": {
+ "name": "Prevent Message List Auto Scroll",
+ "description": "Prevents the message list from scrolling to the bottom when sending/receiving a message"
+ },
"streak_expiration_info": {
"name": "Show Streak Expiration Info",
"description": "Shows a Streak Expiration timer next to the Streaks counter"
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
@@ -29,6 +29,7 @@ class UserInterfaceTweaks : ConfigContainer() {
val snapPreview = boolean("snap_preview") { addNotices(FeatureNotice.UNSTABLE); requireRestart() }
val bootstrapOverride = container("bootstrap_override", BootstrapOverride()) { requireRestart() }
val mapFriendNameTags = boolean("map_friend_nametags") { requireRestart() }
+ val preventMessageListAutoScroll = boolean("prevent_message_list_auto_scroll") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
val streakExpirationInfo = boolean("streak_expiration_info") { requireRestart() }
val hideFriendFeedEntry = boolean("hide_friend_feed_entry") { requireRestart() }
val hideStreakRestore = boolean("hide_streak_restore") { requireRestart() }
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/PreventMessageListAutoScroll.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/PreventMessageListAutoScroll.kt
@@ -0,0 +1,80 @@
+package me.rhunk.snapenhance.core.features.impl.tweaks
+
+import android.view.View
+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.util.hook.HookStage
+import me.rhunk.snapenhance.core.util.hook.hook
+import me.rhunk.snapenhance.core.wrapper.impl.Message
+import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
+
+class PreventMessageListAutoScroll : Feature("PreventMessageListAutoScroll", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
+ private var openedConversationId: String? = null
+ private val focusedMessages = mutableMapOf<View, Long>()
+ private var firstFocusedMessageId: Long? = null
+ private val delayedMessageUpdates = mutableListOf<() -> Unit>()
+
+ override fun onActivityCreate() {
+ if (!context.config.userInterface.preventMessageListAutoScroll.get()) return
+
+ context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").hook("onConversationUpdated", HookStage.BEFORE) { param ->
+ val updatedMessage = param.arg<ArrayList<*>>(2).map { Message(it) }.firstOrNull() ?: return@hook
+ if (openedConversationId != updatedMessage.messageDescriptor?.conversationId.toString()) return@hook
+
+ // cancel if the message is already in focus
+ if (focusedMessages.entries.any { entry -> entry.value == updatedMessage.messageDescriptor?.messageId && entry.key.isAttachedToWindow }) return@hook
+
+ val conversationLastMessages = context.database.getMessagesFromConversationId(
+ openedConversationId.toString(),
+ 4
+ ) ?: return@hook
+
+ if (conversationLastMessages.none {
+ focusedMessages.entries.any { entry -> entry.value == it.clientMessageId.toLong() && entry.key.isAttachedToWindow }
+ }) {
+ synchronized(delayedMessageUpdates) {
+ if (firstFocusedMessageId == null) firstFocusedMessageId = conversationLastMessages.lastOrNull()?.clientMessageId?.toLong()
+ delayedMessageUpdates.add {
+ param.invokeOriginal()
+ }
+ }
+ param.setResult(null)
+ }
+ }
+
+ context.classCache.conversationManager.apply {
+ hook("enterConversation", HookStage.BEFORE) { param ->
+ openedConversationId = SnapUUID(param.arg(0)).toString()
+ }
+ hook("exitConversation", HookStage.BEFORE) {
+ openedConversationId = null
+ firstFocusedMessageId = null
+ synchronized(focusedMessages) {
+ focusedMessages.clear()
+ }
+ synchronized(delayedMessageUpdates) {
+ delayedMessageUpdates.clear()
+ }
+ }
+ }
+
+ context.event.subscribe(BindViewEvent::class) { event ->
+ event.chatMessage { conversationId, messageId ->
+ if (conversationId != openedConversationId) return@chatMessage
+ synchronized(focusedMessages) {
+ focusedMessages[event.view] = messageId.toLong()
+ }
+
+ if (delayedMessageUpdates.isNotEmpty() && focusedMessages.entries.any { entry -> entry.value == firstFocusedMessageId && entry.key.isAttachedToWindow }) {
+ delayedMessageUpdates.apply {
+ synchronized(this) {
+ removeIf { it(); true }
+ firstFocusedMessageId = null
+ }
+ }
+ }
+ }
+ }
+ }
+}+
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt
@@ -20,6 +20,7 @@ import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
import me.rhunk.snapenhance.core.features.impl.tweaks.BypassScreenshotDetection
import me.rhunk.snapenhance.core.features.impl.tweaks.CameraTweaks
+import me.rhunk.snapenhance.core.features.impl.tweaks.PreventMessageListAutoScroll
import me.rhunk.snapenhance.core.features.impl.ui.*
import me.rhunk.snapenhance.core.logger.CoreLogger
import me.rhunk.snapenhance.core.manager.Manager
@@ -111,6 +112,7 @@ class FeatureManager(
Stories::class,
DisableComposerModules::class,
FideliusIndicator::class,
+ PreventMessageListAutoScroll::class,
)
initializeFeatures()