commit 72cfd7a8bce2cd3c52764af99e8377be7cca77ec
parent 7619cc0b8eb752bbd5e51f9a70c184ee9bd1933b
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Mon, 4 Mar 2024 00:03:43 +0100
feat(ui/new_chat_action_menu): debug view
Diffstat:
3 files changed, 157 insertions(+), 150 deletions(-)
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
@@ -40,6 +40,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
val componentsHolder = context.resources.getIdentifier("components_holder", "id")
val feedNewChat = context.resources.getIdentifier("feed_new_chat", "id")
val contextMenuButtonIconView = context.resources.getIdentifier("context_menu_button_icon_view", "id")
+ val chatActionMenu = context.resources.getIdentifier("chat_action_menu", "id")
context.event.subscribe(AddViewEvent::class) { event ->
val originalAddView: (View) -> Unit = {
@@ -75,6 +76,16 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
return@subscribe
}
+ if (viewGroup !is LinearLayout && childView.id == chatActionMenu && context.config.experimental.newChatActionMenu.get() && context.isDeveloper) {
+ event.view = LinearLayout(childView.context).apply {
+ orientation = LinearLayout.VERTICAL
+ addView(
+ (menuMap[NewChatActionMenu::class]!! as NewChatActionMenu).createDebugInfoView(childView.context)
+ )
+ addView(event.view)
+ }
+ }
+
if (childView.javaClass.name.endsWith("ChatActionMenuComponent") && context.config.experimental.newChatActionMenu.get()) {
(menuMap[NewChatActionMenu::class]!! as NewChatActionMenu).handle(event)
return@subscribe
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt
@@ -1,56 +1,28 @@
package me.rhunk.snapenhance.core.ui.menu.impl
-import android.annotation.SuppressLint
-import android.content.Context
-import android.text.format.Formatter
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Button
import android.widget.LinearLayout
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import me.rhunk.snapenhance.common.data.ContentType
-import me.rhunk.snapenhance.common.ui.createComposeView
-import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
-import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
-import me.rhunk.snapenhance.common.util.snap.RemoteMediaResolver
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
-import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
import me.rhunk.snapenhance.core.features.impl.experiments.ConvertMessageLocally
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
import me.rhunk.snapenhance.core.ui.ViewTagState
import me.rhunk.snapenhance.core.ui.applyTheme
-import me.rhunk.snapenhance.core.ui.debugEditText
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.ktx.getDimens
import me.rhunk.snapenhance.core.util.ktx.vibrateLongPress
-import java.text.SimpleDateFormat
-import java.util.Date
-import kotlin.io.encoding.Base64
-import kotlin.io.encoding.ExperimentalEncodingApi
-@SuppressLint("DiscouragedApi")
class ChatActionMenu : AbstractMenu() {
private val viewTagState = ViewTagState()
-
private val defaultGap by lazy { context.resources.getDimens("default_gap") }
-
private val chatActionMenuItemMargin by lazy { context.resources.getDimens("chat_action_menu_item_margin") }
-
private val actionMenuItemHeight by lazy { context.resources.getDimens("action_menu_item_height") }
private fun createContainer(viewGroup: ViewGroup): LinearLayout {
@@ -68,29 +40,13 @@ class ChatActionMenu : AbstractMenu() {
}
}
- private fun debugAlertDialog(context: Context, title: String, text: String) {
- this@ChatActionMenu.context.runOnUiThread {
- ViewAppearanceHelper.newAlertDialogBuilder(context).apply {
- setTitle(title)
- setView(debugEditText(context, text))
- setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
- setNegativeButton("Copy") { _, _ ->
- context.copyToClipboard(text, title)
- }
- }.show()
- }
- }
-
- private val lastFocusedMessage
- get() = context.database.getConversationMessageFromId(context.feature(Messaging::class).lastFocusedMessageId)
-
override fun init() {
runCatching {
if (!context.config.downloader.downloadContextMenu.get() && context.config.messaging.messageLogger.globalState != true && !context.isDeveloper) return
context.androidContext.classLoader.loadClass("com.snap.messaging.chat.features.actionmenu.ActionMenuChatItemContainer")
.hook("onMeasure", HookStage.BEFORE) { param ->
param.setArg(1,
- View.MeasureSpec.makeMeasureSpec((context.resources.displayMetrics.heightPixels * 0.35).toInt(), View.MeasureSpec.AT_MOST)
+ View.MeasureSpec.makeMeasureSpec((context.resources.displayMetrics.heightPixels * 0.25).toInt(), View.MeasureSpec.AT_MOST)
)
}
}.onFailure {
@@ -98,8 +54,6 @@ class ChatActionMenu : AbstractMenu() {
}
}
- @OptIn(ExperimentalLayoutApi::class, ExperimentalEncodingApi::class)
- @SuppressLint("SetTextI18n", "DiscouragedApi", "ClickableViewAccessibility")
override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) {
val viewGroup = parent.parent.parent as? ViewGroup ?: return
if (viewTagState[viewGroup]) return
@@ -202,108 +156,6 @@ class ChatActionMenu : AbstractMenu() {
})
}
- if (context.isDeveloper) {
- val composeDebugView = createComposeView(viewGroup.context) {
- FlowRow(
- modifier = Modifier.fillMaxWidth().padding(5.dp),
- horizontalArrangement = Arrangement.spacedBy(3.dp)
- ) {
- Button(onClick = {
- val arroyoMessage = lastFocusedMessage ?: return@Button
- debugAlertDialog(viewGroup.context,
- "Message Info",
- StringBuilder().apply {
- runCatching {
- append("conversation_id: ${arroyoMessage.clientConversationId}\n")
- append("sender_id: ${arroyoMessage.senderId}\n")
- append("client_id: ${arroyoMessage.clientMessageId}, server_id: ${arroyoMessage.serverMessageId}\n")
- append("content_type: ${ContentType.fromId(arroyoMessage.contentType)} (${arroyoMessage.contentType})\n")
- append("parsed_content_type: ${
- ContentType.fromMessageContainer(
- ProtoReader(arroyoMessage.messageContent!!).followPath(4, 4)
- ).let { "$it (${it?.id})" }}\n")
- append("creation_timestamp: ${
- SimpleDateFormat.getDateTimeInstance().format(
- Date(arroyoMessage.creationTimestamp)
- )} (${arroyoMessage.creationTimestamp})\n")
- append("read_timestamp: ${SimpleDateFormat.getDateTimeInstance().format(
- Date(arroyoMessage.readTimestamp)
- )} (${arroyoMessage.readTimestamp})\n")
- append("ml_deleted: ${messageLogger.isMessageDeleted(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong())}, ")
- append("ml_stored: ${messageLogger.getMessageObject(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong()) != null}\n")
- }
- }.toString()
- )
- }) {
- Text("Info")
- }
- Button(onClick = {
- val arroyoMessage = lastFocusedMessage ?: return@Button
- messaging.conversationManager?.fetchMessage(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong(), onSuccess = { message ->
- val decodedAttachments = MessageDecoder.decode(message.messageContent!!)
-
- debugAlertDialog(
- viewGroup.context,
- "Media References",
- decodedAttachments.mapIndexed { index, attachment ->
- StringBuilder().apply {
- append("---- media $index ----\n")
- append("resolveProto: ${attachment.mediaUrlKey}\n")
- append("type: ${attachment.type}\n")
- attachment.attachmentInfo?.apply {
- encryption?.let {
- append("encryption:\n - key: ${it.key}\n - iv: ${it.iv}\n")
- }
- resolution?.let {
- append("resolution: ${it.first}x${it.second}\n")
- }
- duration?.let {
- append("duration: $it\n")
- }
- }
- runCatching {
- val mediaHeaders = RemoteMediaResolver.getMediaHeaders(Base64.UrlSafe.decode(attachment.mediaUrlKey ?: return@runCatching))
- append("content-type: ${mediaHeaders["content-type"]}\n")
- append("content-length: ${Formatter.formatShortFileSize(context.androidContext, mediaHeaders["content-length"]?.toLongOrNull() ?: 0)}\n")
- append("creation-date: ${mediaHeaders["last-modified"]}\n")
- }
- }.toString()
- }.joinToString("\n\n")
- )
- })
- }) {
- Text("Refs")
- }
- Button(onClick = {
- val message = lastFocusedMessage ?: return@Button
- debugAlertDialog(
- viewGroup.context,
- "Arroyo proto",
- message.messageContent?.let { ProtoReader(it) }?.toString() ?: "empty"
- )
- }) {
- Text("Arroyo proto")
- }
- Button(onClick = {
- val arroyoMessage = lastFocusedMessage ?: return@Button
- messaging.conversationManager?.fetchMessage(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong(), onSuccess = { message ->
- debugAlertDialog(
- viewGroup.context,
- "Message proto",
- message.messageContent?.content?.let { ProtoReader(it) }?.toString() ?: "empty"
- )
- }, onError = {
- this@ChatActionMenu.context.shortToast("Failed to fetch message: $it")
- })
- }) {
- Text("Message proto")
- }
- }
- }
- viewGroup.addView(createContainer(viewGroup).apply {
- addView(composeDebugView)
- })
- }
viewGroup.addView(buttonContainer)
}
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/NewChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/NewChatActionMenu.kt
@@ -1,5 +1,7 @@
package me.rhunk.snapenhance.core.ui.menu.impl
+import android.content.Context
+import android.text.format.Formatter
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ScrollView
@@ -12,6 +14,8 @@ import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.RemoveRedEye
import androidx.compose.material.icons.outlined.Image
import androidx.compose.material.icons.rounded.BookmarkRemove
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -21,21 +25,160 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
+import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.common.ui.createComposeView
+import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
+import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
+import me.rhunk.snapenhance.common.util.snap.RemoteMediaResolver
import me.rhunk.snapenhance.core.event.events.impl.AddViewEvent
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
+import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
import me.rhunk.snapenhance.core.features.impl.experiments.ConvertMessageLocally
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
+import me.rhunk.snapenhance.core.ui.debugEditText
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.isDarkTheme
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.util.ktx.vibrateLongPress
+import java.text.SimpleDateFormat
+import java.util.Date
+import kotlin.io.encoding.Base64
+import kotlin.io.encoding.ExperimentalEncodingApi
class NewChatActionMenu : AbstractMenu() {
+ private fun debugAlertDialog(context: Context, title: String, text: String) {
+ this@NewChatActionMenu.context.runOnUiThread {
+ ViewAppearanceHelper.newAlertDialogBuilder(context).apply {
+ setTitle(title)
+ setView(debugEditText(context, text))
+ setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
+ setNegativeButton("Copy") { _, _ ->
+ context.copyToClipboard(text, title)
+ }
+ }.show()
+ }
+ }
+
+ private val lastFocusedMessage
+ get() = context.database.getConversationMessageFromId(context.feature(Messaging::class).lastFocusedMessageId)
+
+ @OptIn(ExperimentalLayoutApi::class, ExperimentalEncodingApi::class)
+ fun createDebugInfoView(context: Context): ComposeView {
+ val messageLogger = this@NewChatActionMenu.context.feature(MessageLogger::class)
+ val messaging = this@NewChatActionMenu.context.feature(Messaging::class)
+
+ return createComposeView(context) {
+ Card(
+ modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 0.dp)
+ ) {
+ FlowRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(5.dp),
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ ) {
+ Button(onClick = {
+ val arroyoMessage = lastFocusedMessage ?: return@Button
+ debugAlertDialog(context,
+ "Message Info",
+ StringBuilder().apply {
+ runCatching {
+ append("conversation_id: ${arroyoMessage.clientConversationId}\n")
+ append("sender_id: ${arroyoMessage.senderId}\n")
+ append("client_id: ${arroyoMessage.clientMessageId}, server_id: ${arroyoMessage.serverMessageId}\n")
+ append("content_type: ${ContentType.fromId(arroyoMessage.contentType)} (${arroyoMessage.contentType})\n")
+ append("parsed_content_type: ${
+ ContentType.fromMessageContainer(
+ ProtoReader(arroyoMessage.messageContent!!).followPath(4, 4)
+ ).let { "$it (${it?.id})" }}\n")
+ append("creation_timestamp: ${
+ SimpleDateFormat.getDateTimeInstance().format(
+ Date(arroyoMessage.creationTimestamp)
+ )} (${arroyoMessage.creationTimestamp})\n")
+ append("read_timestamp: ${
+ SimpleDateFormat.getDateTimeInstance().format(
+ Date(arroyoMessage.readTimestamp)
+ )} (${arroyoMessage.readTimestamp})\n")
+ append("ml_deleted: ${messageLogger.isMessageDeleted(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong())}, ")
+ append("ml_stored: ${messageLogger.getMessageObject(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong()) != null}\n")
+ }
+ }.toString()
+ )
+ }) {
+ Text("Info")
+ }
+ Button(onClick = {
+ val arroyoMessage = lastFocusedMessage ?: return@Button
+ messaging.conversationManager?.fetchMessage(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong(), onSuccess = { message ->
+ val decodedAttachments = MessageDecoder.decode(message.messageContent!!)
+ debugAlertDialog(
+ context,
+ "Media References",
+ decodedAttachments.mapIndexed { index, attachment ->
+ StringBuilder().apply {
+ append("---- media $index ----\n")
+ append("resolveProto: ${attachment.mediaUrlKey}\n")
+ append("type: ${attachment.type}\n")
+ attachment.attachmentInfo?.apply {
+ encryption?.let {
+ append("encryption:\n - key: ${it.key}\n - iv: ${it.iv}\n")
+ }
+ resolution?.let {
+ append("resolution: ${it.first}x${it.second}\n")
+ }
+ duration?.let {
+ append("duration: $it\n")
+ }
+ }
+ runCatching {
+ val mediaHeaders = RemoteMediaResolver.getMediaHeaders(
+ Base64.UrlSafe.decode(attachment.mediaUrlKey ?: return@runCatching))
+ append("content-type: ${mediaHeaders["content-type"]}\n")
+ append("content-length: ${Formatter.formatShortFileSize(context, mediaHeaders["content-length"]?.toLongOrNull() ?: 0)}\n")
+ append("creation-date: ${mediaHeaders["last-modified"]}\n")
+ }
+ }.toString()
+ }.joinToString("\n\n")
+ )
+ })
+ }) {
+ Text("Refs")
+ }
+ Button(onClick = {
+ val message = lastFocusedMessage ?: return@Button
+ debugAlertDialog(
+ context,
+ "Arroyo proto",
+ message.messageContent?.let { ProtoReader(it) }?.toString() ?: "empty"
+ )
+ }) {
+ Text("Arroyo")
+ }
+ Button(onClick = {
+ val arroyoMessage = lastFocusedMessage ?: return@Button
+ messaging.conversationManager?.fetchMessage(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong(), onSuccess = { message ->
+ debugAlertDialog(
+ context,
+ "Message proto",
+ message.messageContent?.content?.let { ProtoReader(it) }?.toString() ?: "empty"
+ )
+ }, onError = {
+ this@NewChatActionMenu.context.shortToast("Failed to fetch message: $it")
+ })
+ }) {
+ Text("Message")
+ }
+ }
+ }
+ }
+ }
+
fun handle(event: AddViewEvent) {
if (event.parent is LinearLayout) return
val closeActionMenu = { event.parent.iterateParent {
@@ -149,9 +292,10 @@ class NewChatActionMenu : AbstractMenu() {
addView(composeView)
composeView.post {
(event.parent.layoutParams as ViewGroup.MarginLayoutParams).apply {
- setObjectField("a", null) // remove drag callback
if (height < composeView.measuredHeight) {
height += composeView.measuredHeight
+ } else {
+ setObjectField("a", null) // remove drag callback
}
}
event.parent.requestLayout()