commit 785fb49aa98f3e53ee11061d4667eef9bfdc1c8b
parent 6e7aa7c498754f86e18119066066a3089efc8751
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 17 Apr 2024 17:43:35 +0200

feat(auto_mark_as_read): snap reply

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 6+++++-
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/AutoMarkAsRead.kt | 79++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Notifications.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt | 60+++++-------------------------------------------------------
5 files changed, 88 insertions(+), 61 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -490,7 +490,7 @@ }, "auto_mark_as_read": { "name": "Auto Mark as Read", - "description": "Automatically marks messages as read when sending a message to a conversation, even when Stealth Mode is enabled" + "description": "Automatically marks messages/snaps as read even when Stealth Mode is enabled" }, "loop_media_playback": { "name": "Loop Media Playback", @@ -1092,6 +1092,10 @@ "location_indicator": "Adds a \uD83D\uDCCD icon to snaps when they have been sent with location enabled", "ovf_editor_indicator": "Indicates if a snap has been sent using OVF Editor", "director_mode_indicator": "Adds a \u270F\uFE0F icon to snaps when they have been sent using Director Mode, which can be used to send gallery images as snaps" + }, + "auto_mark_as_read": { + "conversation_read": "Mark conversation as read when sending a message", + "snap_reply": "Mark snaps as read when replying to them" } } }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt @@ -55,7 +55,7 @@ class MessagingTweaks : ConfigContainer() { val hideBitmojiPresence = boolean("hide_bitmoji_presence") val hideTypingNotifications = boolean("hide_typing_notifications") val unlimitedSnapViewTime = boolean("unlimited_snap_view_time") - val autoMarkAsRead = boolean("auto_mark_as_read") { requireRestart() } + val autoMarkAsRead = multiple("auto_mark_as_read", "snap_reply", "conversation_read") { requireRestart() } val loopMediaPlayback = boolean("loop_media_playback") { requireRestart() } val disableReplayInFF = boolean("disable_replay_in_ff") val halfSwipeNotifier = container("half_swipe_notifier", HalfSwipeNotifierConfig()) { requireRestart()} diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/AutoMarkAsRead.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/AutoMarkAsRead.kt @@ -1,12 +1,26 @@ package me.rhunk.snapenhance.core.features.impl.messaging +import android.widget.ProgressBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.WarningAmber +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.rhunk.snapenhance.common.data.ContentType +import me.rhunk.snapenhance.common.data.MessageUpdate import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.impl.spying.StealthMode +import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper +import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlin.random.Random class AutoMarkAsRead : Feature("Auto Mark As Read", loadParams = FeatureLoadParams.INIT_SYNC) { - val isEnabled by lazy { context.config.messaging.autoMarkAsRead.get() } + val canMarkConversationAsRead by lazy { context.config.messaging.autoMarkAsRead.get().contains("conversation_read") } fun markConversationsAsRead(conversationIds: List<String>) { conversationIds.forEach { conversationId -> @@ -20,12 +34,71 @@ class AutoMarkAsRead : Feature("Auto Mark As Read", loadParams = FeatureLoadPara } } + private suspend fun markSnapAsSeen(conversationId: String, clientMessageId: Long) { + suspendCoroutine { continuation -> + context.feature(Messaging::class).conversationManager?.updateMessage(conversationId, clientMessageId, MessageUpdate.READ) { + continuation.resume(Unit) + if (it != null && it != "DUPLICATEREQUEST") { + context.log.error("Error marking message as read $it") + } + } + } + } + + fun markSnapsAsSeen(conversationId: String) { + val messaging = context.feature(Messaging::class) + val messageIds = messaging.getFeedCachedMessageIds(conversationId)?.takeIf { it.isNotEmpty() } ?: run { + context.inAppOverlay.showStatusToast( + Icons.Default.WarningAmber, + context.translation["mark_as_seen.no_unseen_snaps_toast"] + ) + return + } + + var job: Job? = null + val dialog = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity) + .setTitle("Processing...") + .setView(ProgressBar(context.mainActivity).apply { + setPadding(10, 10, 10, 10) + }) + .setOnDismissListener { job?.cancel() } + .show() + + context.coroutineScope.launch(Dispatchers.IO) { + messageIds.forEach { messageId -> + markSnapAsSeen(conversationId, messageId) + delay(Random.nextLong(20, 60)) + context.runOnUiThread { + dialog.setTitle("Processing... (${messageIds.indexOf(messageId) + 1}/${messageIds.size})") + } + } + }.also { job = it }.invokeOnCompletion { + context.runOnUiThread { + dialog.dismiss() + } + } + } + override fun init() { - if (!isEnabled) return + val config by context.config.messaging.autoMarkAsRead + if (config.isEmpty()) return context.event.subscribe(SendMessageWithContentEvent::class) { event -> event.addCallbackResult("onSuccess") { - markConversationsAsRead(event.destinations.conversations?.map { it.toString() } ?: return@addCallbackResult) + if (canMarkConversationAsRead) { + markConversationsAsRead(event.destinations.conversations?.map { it.toString() } ?: return@addCallbackResult) + } + + if (config.contains("snap_reply")) { + val quotedMessageId = event.messageContent.instanceNonNull().getObjectFieldOrNull("mQuotedMessageId") as? Long ?: return@addCallbackResult + val message = context.database.getConversationMessageFromId(quotedMessageId) ?: return@addCallbackResult + + if (message.contentType == ContentType.SNAP.id) { + context.coroutineScope.launch { + markSnapAsSeen(event.destinations.conversations?.firstOrNull()?.toString() ?: return@launch, quotedMessageId) + } + } + } } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Notifications.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Notifications.kt @@ -201,7 +201,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN }, onSuccess = { context.coroutineScope.launch(coroutineDispatcher) { appendNotificationText("${myUser.displayName ?: myUser.mutableUsername}: $input") - context.feature(AutoMarkAsRead::class).takeIf { it.isEnabled }?.markConversationsAsRead(listOf(conversationId)) + context.feature(AutoMarkAsRead::class).takeIf { it.canMarkConversationAsRead }?.markConversationsAsRead(listOf(conversationId)) } }) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt @@ -8,30 +8,21 @@ import android.graphics.drawable.Drawable import android.view.View import android.view.ViewGroup import android.widget.Button -import android.widget.CompoundButton import android.widget.LinearLayout -import android.widget.ProgressBar import android.widget.Switch import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CheckCircleOutline import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.NotInterested -import androidx.compose.material.icons.filled.WarningAmber import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.FriendLinkType -import me.rhunk.snapenhance.common.data.MessageUpdate import me.rhunk.snapenhance.common.database.impl.ConversationMessage import me.rhunk.snapenhance.common.database.impl.FriendInfo -import me.rhunk.snapenhance.common.database.impl.UserConversationLink import me.rhunk.snapenhance.common.scripting.ui.EnumScriptInterface import me.rhunk.snapenhance.common.scripting.ui.InterfaceManager import me.rhunk.snapenhance.common.scripting.ui.ScriptInterface @@ -39,6 +30,7 @@ import me.rhunk.snapenhance.common.ui.createComposeView import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie import me.rhunk.snapenhance.core.features.impl.experiments.EndToEndEncryption +import me.rhunk.snapenhance.core.features.impl.messaging.AutoMarkAsRead import me.rhunk.snapenhance.core.features.impl.messaging.Messaging import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper @@ -53,9 +45,6 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine -import kotlin.random.Random class FriendFeedInfoMenu : AbstractMenu() { private fun getImageDrawable(url: String): Drawable { @@ -131,47 +120,6 @@ class FriendFeedInfoMenu : AbstractMenu() { } } - private fun markAsSeen(conversationId: String) { - val messaging = context.feature(Messaging::class) - val messageIds = messaging.getFeedCachedMessageIds(conversationId)?.takeIf { it.isNotEmpty() } ?: run { - context.inAppOverlay.showStatusToast( - Icons.Default.WarningAmber, - context.translation["mark_as_seen.no_unseen_snaps_toast"] - ) - return - } - - var job: Job? = null - val dialog = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity) - .setTitle("Processing...") - .setView(ProgressBar(context.mainActivity).apply { - setPadding(10, 10, 10, 10) - }) - .setOnDismissListener { job?.cancel() } - .show() - - context.coroutineScope.launch(Dispatchers.IO) { - messageIds.forEach { messageId -> - suspendCoroutine { continuation -> - messaging.conversationManager?.updateMessage(conversationId, messageId, MessageUpdate.READ) { - continuation.resume(Unit) - if (it != null && it != "DUPLICATEREQUEST") { - context.log.error("Error marking message as read $it") - } - } - } - delay(Random.nextLong(20, 60)) - context.runOnUiThread { - dialog.setTitle("Processing... (${messageIds.indexOf(messageId) + 1}/${messageIds.size})") - } - } - }.also { job = it }.invokeOnCompletion { - context.runOnUiThread { - dialog.dismiss() - } - } - } - private fun showPreview(userId: String?, conversationId: String) { //query message val messageLogger = context.feature(MessageLogger::class) @@ -324,8 +272,10 @@ class FriendFeedInfoMenu : AbstractMenu() { isSoundEffectsEnabled = false applyTheme(view.width, hasRadius = true) setOnClickListener { - this@FriendFeedInfoMenu.context.mainActivity?.triggerRootCloseTouchEvent() - markAsSeen(conversationId) + this@FriendFeedInfoMenu.context.apply { + mainActivity?.triggerRootCloseTouchEvent() + feature(AutoMarkAsRead::class).markSnapsAsSeen(conversationId) + } } }) }