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:
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)
+ }
}
})
}