feat: group chat menu

This commit is contained in:
rhunk 2023-06-11 12:25:45 +02:00
parent d18dff61d3
commit 6492cf3b48
3 changed files with 74 additions and 28 deletions

View File

@ -1,11 +1,11 @@
package me.rhunk.snapenhance.features.impl.ui.menus package me.rhunk.snapenhance.features.impl.ui.menus
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import de.robv.android.xposed.XposedBridge
import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
@ -30,6 +30,12 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
context.resources.getString(context.resources.getIdentifier("new_chat", "string", Constants.SNAPCHAT_PACKAGE_NAME)) context.resources.getString(context.resources.getIdentifier("new_chat", "string", Constants.SNAPCHAT_PACKAGE_NAME))
} }
private fun wasInjectedView(view: View): Boolean {
if (view.getTag(Constants.VIEW_INJECTED_CODE) != null) return true
view.setTag(Constants.VIEW_INJECTED_CODE, true)
return false
}
@SuppressLint("ResourceType") @SuppressLint("ResourceType")
override fun asyncOnActivityCreate() { override fun asyncOnActivityCreate() {
friendFeedInfoMenu.context = context friendFeedInfoMenu.context = context
@ -38,6 +44,9 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
settingMenu.context = context settingMenu.context = context
val actionSheetItemsContainerLayoutId = context.resources.getIdentifier("action_sheet_items_container", "id", Constants.SNAPCHAT_PACKAGE_NAME) val actionSheetItemsContainerLayoutId = context.resources.getIdentifier("action_sheet_items_container", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val actionSheetContainer = context.resources.getIdentifier("action_sheet_container", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val actionMenu = context.resources.getIdentifier("action_menu", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val addViewMethod = ViewGroup::class.java.getMethod( val addViewMethod = ViewGroup::class.java.getMethod(
"addView", "addView",
View::class.java, View::class.java,
@ -47,18 +56,12 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
Hooker.hook(addViewMethod, HookStage.BEFORE) { param -> Hooker.hook(addViewMethod, HookStage.BEFORE) { param ->
val viewGroup: ViewGroup = param.thisObject() val viewGroup: ViewGroup = param.thisObject()
val originalAddView: (View) -> Unit = { view: View -> val originalAddView: (View) -> Unit = {
XposedBridge.invokeOriginalMethod( param.invokeOriginal(arrayOf(it, -1,
addViewMethod, FrameLayout.LayoutParams(
viewGroup, ViewGroup.LayoutParams.MATCH_PARENT,
arrayOf( ViewGroup.LayoutParams.MATCH_PARENT
view, ))
-1,
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
) )
} }
@ -72,7 +75,34 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
return@hook return@hook
} }
//TODO : preview group chats //inject in group chat menus
if (viewGroup.id == actionSheetContainer && childView.id == actionMenu) {
val injectedLayout = LinearLayout(childView.context).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.BOTTOM
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
addView(childView)
}
Hooker.ephemeralHook(context.classCache.conversationManager, "fetchConversation", HookStage.AFTER) {
if (wasInjectedView(injectedLayout)) return@ephemeralHook
context.feature(Messaging::class).lastFetchConversationUserUUID = null
context.runOnUiThread {
val viewList = mutableListOf<View>()
friendFeedInfoMenu.inject(injectedLayout) { view ->
view.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, 10, 0, 10)
}
viewList.add(view)
}
viewList.reversed().forEach { injectedLayout.addView(it, 0) }
}
}
param.setArg(0, injectedLayout)
}
if (viewGroup is LinearLayout && viewGroup.id == actionSheetItemsContainerLayoutId) { if (viewGroup is LinearLayout && viewGroup.id == actionSheetItemsContainerLayoutId) {
val itemStringInterface by lazy { val itemStringInterface by lazy {
childView.javaClass.declaredFields.filter { childView.javaClass.declaredFields.filter {
@ -87,7 +117,6 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
//the 3 dot button shows a menu which contains the first item as a Plain object //the 3 dot button shows a menu which contains the first item as a Plain object
if (viewGroup.getChildCount() == 0 && itemStringInterface != null && itemStringInterface.toString().startsWith("Plain(primaryText=$newChatString")) { if (viewGroup.getChildCount() == 0 && itemStringInterface != null && itemStringInterface.toString().startsWith("Plain(primaryText=$newChatString")) {
settingMenu.inject(viewGroup, originalAddView) settingMenu.inject(viewGroup, originalAddView)
viewGroup.addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener { viewGroup.addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {} override fun onViewAttachedToWindow(v: View) {}

View File

@ -223,7 +223,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
val friendFeedMenuOptions = context.config.options(ConfigProperty.FRIEND_FEED_MENU_BUTTONS) val friendFeedMenuOptions = context.config.options(ConfigProperty.FRIEND_FEED_MENU_BUTTONS)
if (friendFeedMenuOptions.none { it.value }) return if (friendFeedMenuOptions.none { it.value }) return
val (conversationId, focusedConversationTargetUser) = getCurrentConversationId() val (conversationId, targetUser) = getCurrentConversationId()
if (!context.config.bool(ConfigProperty.ENABLE_FRIEND_FEED_MENU_BAR)) { if (!context.config.bool(ConfigProperty.ENABLE_FRIEND_FEED_MENU_BAR)) {
//preview button //preview button
@ -232,7 +232,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
ViewAppearanceHelper.applyTheme(this, viewModel.width) ViewAppearanceHelper.applyTheme(this, viewModel.width)
setOnClickListener { setOnClickListener {
showPreview( showPreview(
focusedConversationTargetUser, targetUser,
conversationId, conversationId,
context context
) )
@ -252,6 +252,14 @@ class FriendFeedInfoMenu : AbstractMenu() {
} }
} }
if (friendFeedMenuOptions["anti_auto_save"] == true) {
createToggleFeature(viewConsumer,
"friend_menu_option.anti_auto_save",
{ context.feature(AntiAutoSave::class).isConversationIgnored(conversationId) },
{ context.feature(AntiAutoSave::class).setConversationIgnored(conversationId, it) }
)
}
run { run {
val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run
if (friendFeedMenuOptions["auto_download_blacklist"] == true) { if (friendFeedMenuOptions["auto_download_blacklist"] == true) {
@ -261,18 +269,14 @@ class FriendFeedInfoMenu : AbstractMenu() {
{ context.feature(AntiAutoDownload::class).setUserIgnored(userId, it) } { context.feature(AntiAutoDownload::class).setUserIgnored(userId, it) }
) )
} }
if (friendFeedMenuOptions["anti_auto_save"] == true) {
createToggleFeature(viewConsumer,
"friend_menu_option.anti_auto_save",
{ context.feature(AntiAutoSave::class).isConversationIgnored(conversationId) },
{ context.feature(AntiAutoSave::class).setConversationIgnored(conversationId, it) }
)
}
} }
viewConsumer(stealthSwitch) if (friendFeedMenuOptions["stealth_mode"] == true) {
viewConsumer(previewButton) viewConsumer(stealthSwitch)
}
if (friendFeedMenuOptions["conversation_info"] == true) {
viewConsumer(previewButton)
}
return return
} }
@ -356,7 +360,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
//user //user
createActionButton("\uD83D\uDC64") { createActionButton("\uD83D\uDC64") {
showPreview( showPreview(
focusedConversationTargetUser, targetUser,
conversationId, conversationId,
viewModel.context viewModel.context
) )

View File

@ -92,6 +92,19 @@ object Hooker {
}.also { unhooks.addAll(it) } }.also { unhooks.addAll(it) }
} }
fun ephemeralHook(
clazz: Class<*>,
methodName: String,
stage: HookStage,
hookConsumer: (HookAdapter) -> Unit
) {
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
hook(clazz, methodName, stage) { param->
hookConsumer(param)
unhooks.forEach{ it.unhook() }
}.also { unhooks.addAll(it) }
}
fun ephemeralHookObjectMethod( fun ephemeralHookObjectMethod(
clazz: Class<*>, clazz: Class<*>,
instance: Any, instance: Any,