commit 68c61a0b73f70f2c508d4dbc28eaf02ff3c46c46
parent 785fb49aa98f3e53ee11061d4667eef9bfdc1c8b
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Wed, 17 Apr 2024 23:10:22 +0200
feat(experimental): auto open snaps
Diffstat:
4 files changed, 138 insertions(+), 0 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -199,6 +199,14 @@
"whitelist": "Unsaveable Messages"
}
},
+ "auto_open_snaps": {
+ "name": "Auto Open Snaps",
+ "description": "Automatically opens Snaps when receiving them",
+ "options": {
+ "blacklist": "Exclude from Auto Open Snaps",
+ "whitelist": "Auto Open Snaps"
+ }
+ },
"hide_friend_feed": {
"name": "Hide from Friend Feed"
},
@@ -945,6 +953,7 @@
"auto_download": "\u2B07\uFE0F Auto Download",
"auto_save": "\uD83D\uDCAC Auto Save Messages",
"unsaveable_messages": "\u2B07\uFE0F Unsaveable Messages",
+ "auto_open_snaps": "\uD83D\uDCF7 Auto Open Snaps",
"stealth": "\uD83D\uDC7B Stealth Mode",
"mark_snaps_as_seen": "\uD83D\uDC40 Mark Snaps as seen",
"mark_stories_as_seen_locally": "\uD83D\uDC40 Mark Stories as seen locally",
@@ -1357,6 +1366,11 @@
"incoming_secret_message": "Your friend just accepted your public key. Click below to accept the secret."
},
+ "auto_open_snaps": {
+ "title": "Auto Open Snaps",
+ "notification_content": "{count} Snaps opened"
+ },
+
"suspend_location_updates": {
"switch_text": "Suspend Location Updates"
},
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt
@@ -45,6 +45,7 @@ enum class MessagingRuleType(
STEALTH("stealth", true),
AUTO_DOWNLOAD("auto_download", true),
AUTO_SAVE("auto_save", true, defaultValue = "blacklist"),
+ AUTO_OPEN_SNAPS("auto_open_snaps", true, configNotices = arrayOf(FeatureNotice.BAN_RISK, FeatureNotice.UNSTABLE), defaultValue = null),
UNSAVEABLE_MESSAGES("unsaveable_messages", true, configNotices = arrayOf(FeatureNotice.REQUIRE_NATIVE_HOOKS), defaultValue = null),
HIDE_FRIEND_FEED("hide_friend_feed", false, showInFriendMenu = false),
E2E_ENCRYPTION("e2e_encryption", false),
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt
@@ -128,6 +128,7 @@ class FeatureManager(
BetterLocation(),
MediaFilePicker(),
HideActiveMusic(),
+ AutoOpenSnaps(),
)
initializeFeatures()
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AutoOpenSnaps.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AutoOpenSnaps.kt
@@ -0,0 +1,121 @@
+package me.rhunk.snapenhance.core.features.impl.experiments
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+import me.rhunk.snapenhance.common.data.ContentType
+import me.rhunk.snapenhance.common.data.MessageUpdate
+import me.rhunk.snapenhance.common.data.MessagingRuleType
+import me.rhunk.snapenhance.core.event.events.impl.BuildMessageEvent
+import me.rhunk.snapenhance.core.features.FeatureLoadParams
+import me.rhunk.snapenhance.core.features.MessagingRuleFeature
+import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlin.random.Random
+
+class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.AUTO_OPEN_SNAPS, loadParams = FeatureLoadParams.INIT_SYNC) {
+ private val snapQueue = MutableSharedFlow<Pair<String, Long>>()
+ private var snapQueueSize = AtomicInteger(0)
+ private val openedSnaps = mutableListOf<Long>()
+
+ private val notificationManager by lazy {
+ context.androidContext.getSystemService(NotificationManager::class.java)
+ }
+
+ private val notificationId by lazy { Random.nextInt() }
+
+ private val channelId by lazy {
+ "auto_open_snaps".also {
+ notificationManager.createNotificationChannel(
+ NotificationChannel(it, context.translation["auto_open_snaps.title"], NotificationManager.IMPORTANCE_LOW)
+ )
+ }
+ }
+
+ private fun sendStatusNotification(count: Int) {
+ notificationManager.notify(
+ notificationId,
+ Notification.Builder(context.androidContext, channelId)
+ .setContentTitle(context.translation["auto_open_snaps.title"])
+ .setContentText(context.translation.format("auto_open_snaps.notification_content", "count" to count.toString()))
+ .setSmallIcon(android.R.drawable.ic_menu_view)
+ .setProgress(0, 0, true)
+ .build().apply {
+ flags = flags or Notification.FLAG_ONLY_ALERT_ONCE
+ }
+ )
+ }
+
+ override fun init() {
+ if (getRuleState() == null) return
+ val messaging = context.feature(Messaging::class)
+
+ context.coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ snapQueue.collect { (conversationId, messageId) ->
+ snapQueueSize.addAndGet(-1)
+ delay(Random.nextLong(50, 100))
+ var result: String?
+
+ for (i in 0..5) {
+ while (context.isMainActivityPaused || messaging.conversationManager == null) {
+ delay(2000)
+ }
+
+ result = suspendCoroutine { continuation ->
+ runCatching {
+ messaging.conversationManager?.updateMessage(conversationId, messageId, MessageUpdate.READ) { result ->
+ continuation.resume(result)
+ }
+ }.getOrNull() ?: continuation.resume("ConversationManager is null")
+ }
+
+ if (result != null && result != "DUPLICATEREQUEST") {
+ context.log.warn("Failed to mark snap as read, retrying in 3 second")
+ delay(3000)
+ continue
+ }
+ break
+ }
+
+ if (snapQueueSize.get() <= 5) {
+ notificationManager.cancel(notificationId)
+ synchronized(openedSnaps) {
+ openedSnaps.clear()
+ }
+ } else {
+ sendStatusNotification(openedSnaps.size)
+ }
+ }
+ }
+
+ context.event.subscribe(BuildMessageEvent::class, priority = 103) { event ->
+ val conversationId = event.message.messageDescriptor?.conversationId?.toString() ?: return@subscribe
+ val clientMessageId = event.message.messageDescriptor?.messageId ?: return@subscribe
+
+ if (
+ event.message.messageContent?.contentType != ContentType.SNAP ||
+ event.message.messageMetadata?.openedBy?.any { it.toString() == context.database.myUserId } == true
+ ) {
+ return@subscribe
+ }
+
+ context.coroutineScope.launch {
+ if (!canUseRule(conversationId)) return@launch
+ synchronized(openedSnaps) {
+ if (openedSnaps.contains(clientMessageId)) {
+ return@launch
+ }
+ openedSnaps.add(clientMessageId)
+ }
+ snapQueueSize.addAndGet(1)
+ snapQueue.emit(conversationId to clientMessageId)
+ }
+ }
+ }
+}+
\ No newline at end of file