commit 248100b8b9c9a373e7575be4a0166d6a003099ea
parent 3f02023580470e7b71ff76f6d7aba70dd2a5232a
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 12 Jun 2024 16:04:27 +0200

feat(core): mark as seen button

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 8++++++++
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 | 6+++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/MenuViewInjector.kt | 4++--
Dcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaDownloadIconMenu.kt | 62--------------------------------------------------------------
Acore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaViewerIcons.kt | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 162 insertions(+), 67 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -613,6 +613,14 @@ "name": "Auto Mark as Read", "description": "Automatically marks messages/snaps as read even when Stealth Mode is enabled" }, + "mark_snap_as_seen_button": { + "name": "Mark Snap as Seen Button", + "description": "Adds a button to mark a Snap as seen when viewing it.\nThis will work even when Stealth Mode is enabled" + }, + "skip_when_marking_as_seen": { + "name": "Skip When Marking as Seen", + "description": "Automatically skips to the next Snap when marking a Snap as seen.\nUse in combination with Mark Snap as Seen Button" + }, "loop_media_playback": { "name": "Loop Media Playback", "description": "Loops media playback when viewing Snaps / Stories" 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 @@ -59,6 +59,8 @@ class MessagingTweaks : ConfigContainer() { val hideTypingNotifications = boolean("hide_typing_notifications") val unlimitedSnapViewTime = boolean("unlimited_snap_view_time") val autoMarkAsRead = multiple("auto_mark_as_read", "snap_reply", "conversation_read") { requireRestart() } + val markSnapAsSeenButton = boolean("mark_snap_as_seen_button") { requireRestart() } + val skipWhenMarkingAsSeen = boolean("skip_when_marking_as_seen") { 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 @@ -34,10 +34,10 @@ class AutoMarkAsRead : Feature("Auto Mark As Read", loadParams = FeatureLoadPara } } - private suspend fun markSnapAsSeen(conversationId: String, clientMessageId: Long) { - suspendCoroutine { continuation -> + suspend fun markSnapAsSeen(conversationId: String, clientMessageId: Long): String? { + return suspendCoroutine { continuation -> context.feature(Messaging::class).conversationManager?.updateMessage(conversationId, clientMessageId, MessageUpdate.READ) { - continuation.resume(Unit) + continuation.resume(it) if (it != null && it != "DUPLICATEREQUEST") { context.log.error("Error marking message as read $it") } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/MenuViewInjector.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/MenuViewInjector.kt @@ -23,7 +23,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar arrayOf( NewChatActionMenu(), OperaContextActionMenu(), - OperaDownloadIconMenu(), + OperaViewerIcons(), SettingsGearInjector(), FriendFeedInfoMenu(), ChatActionMenu(), @@ -84,7 +84,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar } if (childView.id == contextMenuButtonIconView) { - menuMap[OperaDownloadIconMenu::class]!!.inject(viewGroup, childView, originalAddView) + menuMap[OperaViewerIcons::class]!!.inject(viewGroup, childView, originalAddView) } if (event.parent.id == componentsHolder && childView.id == feedNewChat) { 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 @@ -1,61 +0,0 @@ -package me.rhunk.snapenhance.core.ui.menu.impl - -import android.graphics.Color -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader -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) } - private val actionMenuIconSize by lazy { context.resources.getDimens("action_menu_icon_size") } - private val actionMenuIconMargin by lazy { context.resources.getDimens("action_menu_icon_margin") } - private val actionMenuIconMarginTop by lazy { context.resources.getDimens("action_menu_icon_margin_top") } - - 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) - layoutParams = FrameLayout.LayoutParams( - actionMenuIconSize, - actionMenuIconSize - ).apply { - setMargins(0, actionMenuIconMarginTop * 2 + actionMenuIconSize, 0, 0) - marginEnd = actionMenuIconMargin - gravity = Gravity.TOP or Gravity.END - } - setOnClickListener { - mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = false) - } - setOnLongClickListener { - context.vibrateLongPress() - mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = true) - true - } - addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - v.visibility = View.VISIBLE - (parent.parent as? ViewGroup)?.children()?.forEach { child -> - if (child !is ViewGroup) return@forEach - child.children().forEach { - if (it::class.java.name.endsWith("PreviewToolbar")) v.visibility = View.GONE - } - } - } - - override fun onViewDetachedFromWindow(v: View) {} - }) - }, 0) - } -}- \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaViewerIcons.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaViewerIcons.kt @@ -0,0 +1,146 @@ +package me.rhunk.snapenhance.core.ui.menu.impl + +import android.graphics.Color +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader +import me.rhunk.snapenhance.core.features.impl.messaging.AutoMarkAsRead +import me.rhunk.snapenhance.core.ui.children +import me.rhunk.snapenhance.core.ui.iterateParent +import me.rhunk.snapenhance.core.ui.menu.AbstractMenu +import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent +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 OperaViewerIcons : AbstractMenu() { + private val downloadSvgDrawable by lazy { context.resources.getDrawable("svg_download", context.androidContext.theme) } + private val eyeSvgDrawable by lazy { context.resources.getDrawable("svg_eye_24x24", context.androidContext.theme) } + private val actionMenuIconSize by lazy { context.resources.getDimens("action_menu_icon_size") } + private val actionMenuIconMargin by lazy { context.resources.getDimens("action_menu_icon_margin") } + private val actionMenuIconMarginTop by lazy { context.resources.getDimens("action_menu_icon_margin_top") } + + override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) { + val mediaDownloader = context.feature(MediaDownloader::class) + + if (context.config.downloader.operaDownloadButton.get()) { + parent.addView(LinearLayout(view.context).apply { + orientation = LinearLayout.VERTICAL + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, actionMenuIconMarginTop * 2 + actionMenuIconSize, 0, 0) + marginEnd = actionMenuIconMargin + gravity = Gravity.TOP or Gravity.END + } + addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + v.visibility = View.VISIBLE + (parent.parent as? ViewGroup)?.children()?.forEach { child -> + if (child !is ViewGroup) return@forEach + child.children().forEach { + if (it::class.java.name.endsWith("PreviewToolbar")) v.visibility = View.GONE + } + } + } + + override fun onViewDetachedFromWindow(v: View) {} + }) + + addView(ImageView(view.context).apply { + setImageDrawable(downloadSvgDrawable) + setColorFilter(Color.WHITE) + layoutParams = LinearLayout.LayoutParams( + actionMenuIconSize, + actionMenuIconSize + ).apply { + setMargins(0, 0, 0, actionMenuIconMargin * 2) + } + setOnClickListener { + mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = false) + } + setOnLongClickListener { + context.vibrateLongPress() + mediaDownloader.downloadLastOperaMediaAsync(allowDuplicate = true) + true + } + }) + }, 0) + + } + + if (context.config.messaging.markSnapAsSeenButton.get()) { + fun getMessageId(): Pair<String, String>? { + return mediaDownloader.lastSeenMapParams?.get("MESSAGE_ID") + ?.toString() + ?.split(":") + ?.takeIf { it.size == 3 } + ?.let { return it[0] to it[2] } + } + + parent.addView(ImageView(view.context).apply { + setImageDrawable(eyeSvgDrawable) + setColorFilter(Color.WHITE) + addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + v.visibility = View.GONE + this@OperaViewerIcons.context.coroutineScope.launch(Dispatchers.Main) { + delay(300) + v.visibility = if (getMessageId() != null) View.VISIBLE else View.GONE + } + } + override fun onViewDetachedFromWindow(v: View) {} + }) + layoutParams = FrameLayout.LayoutParams( + actionMenuIconSize, + actionMenuIconSize + ).apply { + setMargins(0, 0, 0, actionMenuIconMarginTop * 2 + (80 * context.resources.displayMetrics.density).toInt()) + marginEnd = actionMenuIconMarginTop * 2 + marginStart = actionMenuIconMarginTop * 2 + gravity = Gravity.BOTTOM or Gravity.END + } + setOnClickListener { + this@OperaViewerIcons.context.apply { + coroutineScope.launch { + val (conversationId, clientMessageId) = getMessageId() ?: return@launch + val result = feature(AutoMarkAsRead::class).markSnapAsSeen(conversationId, clientMessageId.toLong()) + if (result == "DUPLICATEREQUEST") return@launch + if (result == null) { + if (config.messaging.skipWhenMarkingAsSeen.get()) { + withContext(Dispatchers.Main) { + parent.iterateParent { + it.triggerCloseTouchEvent() + false + } + } + } + inAppOverlay.showStatusToast( + Icons.Default.Info, + translation["mark_as_seen.seen_toast"], + durationMs = 800 + ) + } else { + inAppOverlay.showStatusToast( + Icons.Default.Info, + "Failed to mark as seen: $result", + ) + } + } + } + } + }, 0) + } + } +}+ \ No newline at end of file