commit 0ea7001ec50a3731e1ee1ef8915d84e5499abe47
parent 3d053155c35c27146f64880b23f5fd6d9f564b63
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Thu,  2 Nov 2023 10:25:41 +0100

refactor: resources ktx

Diffstat:
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/Constants.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AmoledDarkMode.kt | 4++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/CallStartConfirmation.kt | 5+++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/SendOverride.kt | 6+++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/FriendFeedMessagePreview.kt | 20++++++++++----------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/SnapPreview.kt | 12+++++++-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/UITweaks.kt | 38+++++++++++++++++++-------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessageExporter.kt | 25+++++++++++++++----------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/ViewAppearanceHelper.kt | 38++++++++++++++++++++++++++++----------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/AbstractMenu.kt | 4++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt | 60++++++++++++------------------------------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt | 25++++++++++++-------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/MenuViewInjector.kt | 47+++++++++++++++++++++++------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaContextActionMenu.kt | 26++++++++++++--------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/SettingsGearInjector.kt | 37+++++++++++--------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/SettingsMenu.kt | 23-----------------------
Acore/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/AndroidExt.kt | 33+++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/XposedHelperExt.kt | 2+-
18 files changed, 196 insertions(+), 211 deletions(-)

diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/Constants.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/Constants.kt @@ -1,7 +1,7 @@ package me.rhunk.snapenhance.common object Constants { - const val SNAPCHAT_PACKAGE_NAME = "com.snapchat.android" + val SNAPCHAT_PACKAGE_NAME get() = "com.snapchat.android" val ARROYO_MEDIA_CONTAINER_PROTO_PATH = intArrayOf(4, 4) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AmoledDarkMode.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AmoledDarkMode.kt @@ -3,12 +3,12 @@ package me.rhunk.snapenhance.core.features.impl.experiments import android.annotation.SuppressLint import android.content.res.TypedArray import android.graphics.drawable.ColorDrawable -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.Hooker import me.rhunk.snapenhance.core.util.hook.hook +import me.rhunk.snapenhance.core.util.ktx.getIdentifier class AmoledDarkMode : Feature("Amoled Dark Mode", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { @SuppressLint("DiscouragedApi") @@ -18,7 +18,7 @@ class AmoledDarkMode : Feature("Amoled Dark Mode", loadParams = FeatureLoadParam fun getAttribute(name: String): Int { if (attributeCache.containsKey(name)) return attributeCache[name]!! - return context.resources.getIdentifier(name, "attr", Constants.SNAPCHAT_PACKAGE_NAME).also { attributeCache[name] = it } + return context.resources.getIdentifier(name, "attr").also { attributeCache[name] = it } } context.androidContext.theme.javaClass.getMethod("obtainStyledAttributes", IntArray::class.java).hook( diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/CallStartConfirmation.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/CallStartConfirmation.kt @@ -9,6 +9,7 @@ import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper import me.rhunk.snapenhance.core.util.hook.HookAdapter import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.hook +import me.rhunk.snapenhance.core.util.ktx.getId class CallStartConfirmation : Feature("CallStartConfirmation", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { private fun hookTouchEvent(param: HookAdapter, motionEvent: MotionEvent, onConfirm: () -> Unit) { @@ -33,8 +34,8 @@ class CallStartConfirmation : Feature("CallStartConfirmation", loadParams = Feat } } - val callButton1 = context.resources.getIdentifier("friend_action_button3", "id", "com.snapchat.android") - val callButton2 = context.resources.getIdentifier("friend_action_button4", "id", "com.snapchat.android") + val callButton1 = context.resources.getId("friend_action_button3") + val callButton2 = context.resources.getId("friend_action_button4") findClass("com.snap.ui.view.stackdraw.StackDrawLayout").hook("onTouchEvent", HookStage.BEFORE) { param -> val view = param.thisObject<View>() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/SendOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/SendOverride.kt @@ -1,6 +1,5 @@ package me.rhunk.snapenhance.core.features.impl.messaging -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor import me.rhunk.snapenhance.common.util.protobuf.ProtoReader @@ -14,6 +13,7 @@ import me.rhunk.snapenhance.nativelib.NativeLib class SendOverride : Feature("Send Override", loadParams = FeatureLoadParams.INIT_SYNC) { private var isLastSnapSavable = false + private val arroyoMessageContainerPath = intArrayOf(4, 4) private val typeNames by lazy { mutableListOf( "ORIGINAL", @@ -33,8 +33,8 @@ class SendOverride : Feature("Send Override", loadParams = FeatureLoadParams.INI if (event.uri != "/messagingcoreservice.MessagingCoreService/CreateContentMessage") return@subscribe val protoEditor = ProtoEditor(event.buffer) - if (isLastSnapSavable && ProtoReader(event.buffer).containsPath(*Constants.ARROYO_MEDIA_CONTAINER_PROTO_PATH, 11)) { - protoEditor.edit(*Constants.ARROYO_MEDIA_CONTAINER_PROTO_PATH, 11, 5, 2) { + if (isLastSnapSavable && ProtoReader(event.buffer).containsPath(*arroyoMessageContainerPath, 11)) { + protoEditor.edit(*arroyoMessageContainerPath, 11, 5, 2) { remove(8) addBuffer(6, byteArrayOf()) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/FriendFeedMessagePreview.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/FriendFeedMessagePreview.kt @@ -9,7 +9,6 @@ import android.graphics.drawable.shapes.Shape import android.text.TextPaint import android.view.View import android.view.ViewGroup -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent @@ -19,20 +18,21 @@ import me.rhunk.snapenhance.core.features.impl.experiments.EndToEndEncryption import me.rhunk.snapenhance.core.ui.addForegroundDrawable import me.rhunk.snapenhance.core.ui.removeForegroundDrawable import me.rhunk.snapenhance.core.util.EvictingMap +import me.rhunk.snapenhance.core.util.ktx.getDimens +import me.rhunk.snapenhance.core.util.ktx.getId +import me.rhunk.snapenhance.core.util.ktx.getIdentifier import kotlin.math.absoluteValue @SuppressLint("DiscouragedApi") class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { private val sigColorTextPrimary by lazy { context.mainActivity!!.theme.obtainStyledAttributes( - intArrayOf(context.resources.getIdentifier("sigColorTextPrimary", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) + intArrayOf(context.resources.getIdentifier("sigColorTextPrimary", "attr")) ).getColor(0, 0) } private val friendNameCache = EvictingMap<String, String>(100) - private fun getDimens(name: String) = context.resources.getDimensionPixelSize(context.resources.getIdentifier(name, "dimen", Constants.SNAPCHAT_PACKAGE_NAME)) - override fun onActivityCreate() { val setting = context.config.userInterface.friendFeedMessagePreview if (setting.globalState != true) return @@ -40,13 +40,13 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview", loadParams val hasE2EE = context.config.experimental.e2eEncryption.globalState == true val endToEndEncryption by lazy { context.feature(EndToEndEncryption::class) } - val ffItemId = context.resources.getIdentifier("ff_item", "id", Constants.SNAPCHAT_PACKAGE_NAME) + val ffItemId = context.resources.getId("ff_item") - val secondaryTextSize = getDimens("ff_feed_cell_secondary_text_size").toFloat() - val ffSdlAvatarMargin = getDimens("ff_sdl_avatar_margin") - val ffSdlAvatarSize = getDimens("ff_sdl_avatar_size") - val ffSdlAvatarStartMargin = getDimens("ff_sdl_avatar_start_margin") - val ffSdlPrimaryTextStartMargin = getDimens("ff_sdl_primary_text_start_margin").toFloat() + val secondaryTextSize = context.resources.getDimens("ff_feed_cell_secondary_text_size").toFloat() + val ffSdlAvatarMargin = context.resources.getDimens("ff_sdl_avatar_margin") + val ffSdlAvatarSize = context.resources.getDimens("ff_sdl_avatar_size") + val ffSdlAvatarStartMargin = context.resources.getDimens("ff_sdl_avatar_start_margin") + val ffSdlPrimaryTextStartMargin = context.resources.getDimens("ff_sdl_primary_text_start_margin").toFloat() val feedEntryHeight = ffSdlAvatarSize + ffSdlAvatarMargin * 2 + ffSdlAvatarStartMargin val separatorHeight = (context.resources.displayMetrics.density * 2).toInt() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/SnapPreview.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/SnapPreview.kt @@ -1,10 +1,11 @@ package me.rhunk.snapenhance.core.features.impl.ui import android.annotation.SuppressLint -import android.graphics.* +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.Shape -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent @@ -15,6 +16,7 @@ import me.rhunk.snapenhance.core.ui.removeForegroundDrawable import me.rhunk.snapenhance.core.util.EvictingMap 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.getObjectField import me.rhunk.snapenhance.core.util.media.PreviewUtils import java.io.File @@ -44,9 +46,9 @@ class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_S @SuppressLint("DiscouragedApi") override fun onActivityCreate() { if (!isEnabled) return - val chatMediaCardHeight = context.resources.getDimensionPixelSize(context.resources.getIdentifier("chat_media_card_height", "dimen", Constants.SNAPCHAT_PACKAGE_NAME)) - val chatMediaCardSnapMargin = context.resources.getDimensionPixelSize(context.resources.getIdentifier("chat_media_card_snap_margin", "dimen", Constants.SNAPCHAT_PACKAGE_NAME)) - val chatMediaCardSnapMarginStartSdl = context.resources.getDimensionPixelSize(context.resources.getIdentifier("chat_media_card_snap_margin_start_sdl", "dimen", Constants.SNAPCHAT_PACKAGE_NAME)) + val chatMediaCardHeight = context.resources.getDimens("chat_media_card_height") + val chatMediaCardSnapMargin = context.resources.getDimens("chat_media_card_snap_margin") + val chatMediaCardSnapMarginStartSdl = context.resources.getDimens("chat_media_card_snap_margin_start_sdl") fun decodeMedia(file: File) = runCatching { bitmapCache.getOrPut(file.absolutePath) { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/UITweaks.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/UITweaks.kt @@ -7,21 +7,21 @@ import android.text.SpannableString import android.view.View import android.view.ViewGroup.MarginLayoutParams import android.widget.FrameLayout -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.core.event.events.impl.AddViewEvent import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.Hooker import me.rhunk.snapenhance.core.util.hook.hook +import me.rhunk.snapenhance.core.util.ktx.getIdentifier class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { private val identifierCache = mutableMapOf<String, Int>() @SuppressLint("DiscouragedApi") - fun getIdentifier(name: String, defType: String): Int { + fun getId(name: String, defType: String): Int { return identifierCache.getOrPut("$name:$defType") { - context.resources.getIdentifier(name, defType, Constants.SNAPCHAT_PACKAGE_NAME) + context.resources.getIdentifier(name, defType) } } @@ -45,11 +45,11 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE val displayMetrics = context.resources.displayMetrics val deviceAspectRatio = displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels.toFloat() - val callButtonsStub = getIdentifier("call_buttons_stub", "id") - val callButton1 = getIdentifier("friend_action_button3", "id") - val callButton2 = getIdentifier("friend_action_button4", "id") + val callButtonsStub = getId("call_buttons_stub", "id") + val callButton1 = getId("friend_action_button3", "id") + val callButton2 = getId("friend_action_button4", "id") - val chatNoteRecordButton = getIdentifier("chat_note_record_button", "id") + val chatNoteRecordButton = getId("chat_note_record_button", "id") View::class.java.hook("setVisibility", HookStage.BEFORE) { methodParam -> val viewId = (methodParam.thisObject() as View).id @@ -64,8 +64,8 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE { isImmersiveCamera } ) { param -> val id = param.arg<Int>(0) - if (id == getIdentifier("capri_viewfinder_default_corner_radius", "dimen") || - id == getIdentifier("ngs_hova_nav_larger_camera_button_size", "dimen")) { + if (id == getId("capri_viewfinder_default_corner_radius", "dimen") || + id == getId("ngs_hova_nav_larger_camera_button_size", "dimen")) { param.setResult(0) } } @@ -75,17 +75,17 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE val view = event.view if (hideStorySections.contains("hide_for_you")) { - if (viewId == getIdentifier("df_large_story", "id") || - viewId == getIdentifier("df_promoted_story", "id")) { + if (viewId == getId("df_large_story", "id") || + viewId == getId("df_promoted_story", "id")) { hideStorySection(event) return@subscribe } - if (viewId == getIdentifier("stories_load_progress_layout", "id")) { + if (viewId == getId("stories_load_progress_layout", "id")) { event.canceled = true } } - if (hideStorySections.contains("hide_friends") && viewId == getIdentifier("friend_card_frame", "id")) { + if (hideStorySections.contains("hide_friends") && viewId == getId("friend_card_frame", "id")) { hideStorySection(event) } @@ -103,24 +103,24 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE } } - if (hideStorySections.contains("hide_suggested") && (viewId == getIdentifier("df_small_story", "id")) + if (hideStorySections.contains("hide_suggested") && (viewId == getId("df_small_story", "id")) ) { hideStorySection(event) } - if (blockAds && viewId == getIdentifier("df_promoted_story", "id")) { + if (blockAds && viewId == getId("df_promoted_story", "id")) { hideStorySection(event) } if (isImmersiveCamera) { - if (view.id == getIdentifier("edits_container", "id")) { + if (view.id == getId("edits_container", "id")) { Hooker.hookObjectMethod(View::class.java, view, "layout", HookStage.BEFORE) { val width = it.arg(2) as Int val realHeight = (width / deviceAspectRatio).toInt() it.setArg(3, realHeight) } } - if (view.id == getIdentifier("full_screen_surface_view", "id")) { + if (view.id == getId("full_screen_surface_view", "id")) { Hooker.hookObjectMethod(View::class.java, view, "layout", HookStage.BEFORE) { it.setArg(1, 1) it.setArg(3, displayMetrics.heightPixels) @@ -130,8 +130,8 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE if ( (viewId == chatNoteRecordButton && hiddenElements.contains("hide_voice_record_button")) || - (viewId == getIdentifier("chat_input_bar_sticker", "id") && hiddenElements.contains("hide_stickers_button")) || - (viewId == getIdentifier("chat_input_bar_sharing_drawer_button", "id") && hiddenElements.contains("hide_live_location_share_button")) || + (viewId == getId("chat_input_bar_sticker", "id") && hiddenElements.contains("hide_stickers_button")) || + (viewId == getId("chat_input_bar_sharing_drawer_button", "id") && hiddenElements.contains("hide_live_location_share_button")) || (viewId == callButtonsStub && hiddenElements.contains("hide_chat_call_buttons")) ) { view.apply { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessageExporter.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/MessageExporter.kt @@ -22,7 +22,11 @@ import me.rhunk.snapenhance.core.features.impl.downloader.decoder.AttachmentType import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder import me.rhunk.snapenhance.core.wrapper.impl.Message import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID -import java.io.* +import java.io.BufferedInputStream +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream import java.text.SimpleDateFormat import java.util.Collections import java.util.Date @@ -96,7 +100,7 @@ class MessageExporter( writer.flush() } - suspend fun exportHtml(output: OutputStream) { + private suspend fun exportHtml(output: OutputStream) { val downloadMediaCacheFolder = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "SnapEnhance/cache").also { it.mkdirs() } val mediaFiles = Collections.synchronizedMap(mutableMapOf<String, Pair<FileType, File>>()) val threadPool = Executors.newFixedThreadPool(15) @@ -318,14 +322,15 @@ class MessageExporter( } suspend fun exportTo(exportFormat: ExportFormat) { - val output = FileOutputStream(outputFile) - - when (exportFormat) { - ExportFormat.HTML -> exportHtml(output) - ExportFormat.JSON -> exportJson(output) - ExportFormat.TEXT -> exportText(output) + withContext(Dispatchers.IO) { + FileOutputStream(outputFile).apply { + when (exportFormat) { + ExportFormat.HTML -> exportHtml(this) + ExportFormat.JSON -> exportJson(this) + ExportFormat.TEXT -> exportText(this) + } + close() + } } - - output.close() } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/ViewAppearanceHelper.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/ViewAppearanceHelper.kt @@ -12,11 +12,14 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.StateListDrawable import android.graphics.drawable.shapes.Shape +import android.os.SystemClock import android.view.Gravity +import android.view.MotionEvent import android.view.View import android.widget.Switch import android.widget.TextView -import me.rhunk.snapenhance.common.Constants +import me.rhunk.snapenhance.core.util.ktx.getDimens +import me.rhunk.snapenhance.core.util.ktx.getIdentifier import kotlin.random.Random fun View.applyTheme(componentWidth: Int? = null, hasRadius: Boolean = false, isAmoled: Boolean = true) { @@ -54,11 +57,28 @@ fun View.addForegroundDrawable(tag: String, drawable: Drawable) { updateForegroundDrawable() } +fun View.triggerCloseTouchEvent() { + arrayOf(MotionEvent.ACTION_DOWN, MotionEvent.ACTION_UP).forEach { + this.dispatchTouchEvent( + MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + it, 0f, 0f, 0 + ) + ) + } +} + +fun View.iterateParent(predicate: (View) -> Boolean) { + var parent = this.parent as? View ?: return + while (true) { + if (predicate(parent)) return + parent = parent.parent as? View ?: return + } +} + object ViewAppearanceHelper { - @SuppressLint("UseSwitchCompatOrMaterialCode", "RtlHardcoded", "DiscouragedApi", - "ClickableViewAccessibility" - ) private var sigColorTextPrimary: Int = 0 private var sigColorBackgroundSurface: Int = 0 @@ -81,16 +101,16 @@ object ViewAppearanceHelper { if (sigColorBackgroundSurface == 0 || sigColorTextPrimary == 0) { with(component.context.theme) { sigColorTextPrimary = obtainStyledAttributes( - intArrayOf(resources.getIdentifier("sigColorTextPrimary", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) + intArrayOf(resources.getIdentifier("sigColorTextPrimary", "attr")) ).getColor(0, 0) sigColorBackgroundSurface = obtainStyledAttributes( - intArrayOf(resources.getIdentifier("sigColorBackgroundSurface", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) + intArrayOf(resources.getIdentifier("sigColorBackgroundSurface", "attr")) ).getColor(0, 0) } } - val snapchatFontResId = resources.getIdentifier("avenir_next_medium", "font", Constants.SNAPCHAT_PACKAGE_NAME) + val snapchatFontResId = resources.getIdentifier("avenir_next_medium", "font") val scalingFactor = resources.displayMetrics.densityDpi.toDouble() / 400 with(component) { @@ -117,9 +137,7 @@ object ViewAppearanceHelper { } if (component is Switch) { - with(resources) { - component.switchMinWidth = getDimension(getIdentifier("v11_switch_min_width", "dimen", Constants.SNAPCHAT_PACKAGE_NAME)).toInt() - } + component.switchMinWidth = resources.getDimens("v11_switch_min_width") component.trackTintList = ColorStateList( arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked) ), intArrayOf( diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/AbstractMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/AbstractMenu.kt @@ -1,9 +1,13 @@ package me.rhunk.snapenhance.core.ui.menu +import android.view.View +import android.view.ViewGroup import me.rhunk.snapenhance.core.ModContext abstract class AbstractMenu { lateinit var context: ModContext + open fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) {} + open fun init() {} } \ No newline at end of file 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 @@ -2,15 +2,12 @@ package me.rhunk.snapenhance.core.ui.menu.impl import android.annotation.SuppressLint import android.content.Context -import android.os.SystemClock -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams import android.widget.Button import android.widget.LinearLayout import android.widget.TextView -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader @@ -20,6 +17,8 @@ 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.menu.AbstractMenu +import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent +import me.rhunk.snapenhance.core.util.ktx.getDimens import java.time.Instant @@ -27,35 +26,11 @@ import java.time.Instant class ChatActionMenu : AbstractMenu() { private val viewTagState = ViewTagState() - private val defaultGap by lazy { - context.androidContext.resources.getDimensionPixelSize( - context.androidContext.resources.getIdentifier( - "default_gap", - "dimen", - Constants.SNAPCHAT_PACKAGE_NAME - ) - ) - } + private val defaultGap by lazy { context.resources.getDimens("default_gap") } - private val chatActionMenuItemMargin by lazy { - context.androidContext.resources.getDimensionPixelSize( - context.androidContext.resources.getIdentifier( - "chat_action_menu_item_margin", - "dimen", - Constants.SNAPCHAT_PACKAGE_NAME - ) - ) - } + private val chatActionMenuItemMargin by lazy { context.resources.getDimens("chat_action_menu_item_margin") } - private val actionMenuItemHeight by lazy { - context.androidContext.resources.getDimensionPixelSize( - context.androidContext.resources.getIdentifier( - "action_menu_item_height", - "dimen", - Constants.SNAPCHAT_PACKAGE_NAME - ) - ) - } + private val actionMenuItemHeight by lazy { context.resources.getDimens("action_menu_item_height") } private fun createContainer(viewGroup: ViewGroup): LinearLayout { val parent = viewGroup.parent.parent as ViewGroup @@ -87,22 +62,11 @@ class ChatActionMenu : AbstractMenu() { get() = context.database.getConversationMessageFromId(context.feature(Messaging::class).lastFocusedMessageId) @SuppressLint("SetTextI18n", "DiscouragedApi", "ClickableViewAccessibility") - fun inject(viewGroup: ViewGroup) { - val parent = viewGroup.parent.parent as? ViewGroup ?: return - if (viewTagState[parent]) return + override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) { + val viewGroup = parent.parent.parent as? ViewGroup ?: return + if (viewTagState[viewGroup]) return //close the action menu using a touch event - val closeActionMenu = { - viewGroup.dispatchTouchEvent( - MotionEvent.obtain( - SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_DOWN, - 0f, - 0f, - 0 - ) - ) - } + val closeActionMenu = { parent.triggerCloseTouchEvent() } val messaging = context.feature(Messaging::class) val messageLogger = context.feature(MessageLogger::class) @@ -123,7 +87,7 @@ class ChatActionMenu : AbstractMenu() { } with(button) { - applyTheme(parent.width, true) + applyTheme(viewGroup.width, true) layoutParams = MarginLayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -168,7 +132,7 @@ class ChatActionMenu : AbstractMenu() { } if (context.isDeveloper) { - parent.addView(createContainer(viewGroup).apply { + viewGroup.addView(createContainer(viewGroup).apply { val debugText = StringBuilder() setOnClickListener { @@ -221,6 +185,6 @@ class ChatActionMenu : AbstractMenu() { }) } - parent.addView(buttonContainer) + viewGroup.addView(buttonContainer) } } 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 @@ -6,6 +6,7 @@ import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.view.View +import android.view.ViewGroup import android.widget.Button import android.widget.CompoundButton import android.widget.Switch @@ -221,7 +222,7 @@ class FriendFeedInfoMenu : AbstractMenu() { viewConsumer(switch) } - fun inject(viewModel: View, viewConsumer: ((View) -> Unit)) { + override fun inject(parent: ViewGroup, view: View, viewConsumer: ((View) -> Unit)) { val modContext = context val friendFeedMenuOptions by context.config.userInterface.friendFeedMenuButtons @@ -229,19 +230,17 @@ class FriendFeedInfoMenu : AbstractMenu() { val (conversationId, targetUser) = getCurrentConversationInfo() - val previewButton = Button(viewModel.context).apply { - text = modContext.translation["friend_menu_option.preview"] - applyTheme(viewModel.width, hasRadius = true) - setOnClickListener { - showPreview( - targetUser, - conversationId - ) - } - } - if (friendFeedMenuOptions.contains("conversation_info")) { - viewConsumer(previewButton) + viewConsumer(Button(view.context).apply { + text = modContext.translation["friend_menu_option.preview"] + applyTheme(view.width, hasRadius = true) + setOnClickListener { + showPreview( + targetUser, + conversationId + ) + } + }) } modContext.features.getRuleFeatures().forEach { ruleFeature -> diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/MenuViewInjector.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/MenuViewInjector.kt @@ -6,43 +6,42 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.core.event.events.impl.AddViewEvent import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.impl.messaging.Messaging import me.rhunk.snapenhance.core.ui.ViewTagState +import me.rhunk.snapenhance.core.ui.menu.AbstractMenu +import me.rhunk.snapenhance.core.util.ktx.getIdentifier import java.lang.reflect.Modifier +import kotlin.reflect.KClass @SuppressLint("DiscouragedApi") class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { private val viewTagState = ViewTagState() - private val friendFeedInfoMenu = FriendFeedInfoMenu() - private val operaContextActionMenu = OperaContextActionMenu() - private val chatActionMenu = ChatActionMenu() - private val settingMenu = SettingsMenu() - private val settingsGearInjector = SettingsGearInjector() - + private val menuMap = mutableMapOf<KClass<*>, AbstractMenu>() private val newChatString by lazy { - context.resources.getString(context.resources.getIdentifier("new_chat", "string", Constants.SNAPCHAT_PACKAGE_NAME)) + context.resources.getString(context.resources.getIdentifier("new_chat", "string")) } @SuppressLint("ResourceType") override fun asyncOnActivityCreate() { - friendFeedInfoMenu.context = context - operaContextActionMenu.context = context - chatActionMenu.context = context - settingMenu.context = context - settingsGearInjector.context = context + menuMap[SettingsGearInjector::class] = SettingsGearInjector() + menuMap[FriendFeedInfoMenu::class] = FriendFeedInfoMenu() + menuMap[OperaContextActionMenu::class] = OperaContextActionMenu() + menuMap[ChatActionMenu::class] = ChatActionMenu() + menuMap[SettingsMenu::class] = SettingsMenu() + + menuMap.values.forEach { it.context = context } val messaging = context.feature(Messaging::class) - 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 componentsHolder = context.resources.getIdentifier("components_holder", "id", Constants.SNAPCHAT_PACKAGE_NAME) - val feedNewChat = context.resources.getIdentifier("feed_new_chat", "id", Constants.SNAPCHAT_PACKAGE_NAME) + val actionSheetItemsContainerLayoutId = context.resources.getIdentifier("action_sheet_items_container", "id") + val actionSheetContainer = context.resources.getIdentifier("action_sheet_container", "id") + val actionMenu = context.resources.getIdentifier("action_menu", "id") + val componentsHolder = context.resources.getIdentifier("components_holder", "id") + val feedNewChat = context.resources.getIdentifier("feed_new_chat", "id") context.event.subscribe(AddViewEvent::class) { event -> val originalAddView: (View) -> Unit = { @@ -56,17 +55,17 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar val viewGroup: ViewGroup = event.parent val childView: View = event.view - operaContextActionMenu.inject(event.parent, childView) + menuMap[OperaContextActionMenu::class]!!.inject(viewGroup, childView, originalAddView) if (event.parent.id == componentsHolder && childView.id == feedNewChat) { - settingsGearInjector.inject(event.parent, childView) + menuMap[SettingsGearInjector::class]!!.inject(viewGroup, childView, originalAddView) return@subscribe } //download in chat snaps and notes from the chat action menu if (viewGroup.javaClass.name.endsWith("ActionMenuChatItemContainer")) { if (viewGroup.parent == null || viewGroup.parent.parent == null) return@subscribe - chatActionMenu.inject(viewGroup) + menuMap[ChatActionMenu::class]!!.inject(viewGroup, childView, originalAddView) return@subscribe } @@ -87,7 +86,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar val viewList = mutableListOf<View>() context.runOnUiThread { - friendFeedInfoMenu.inject(injectedLayout) { view -> + menuMap[FriendFeedInfoMenu::class]?.inject(event.parent, injectedLayout) { view -> view.layoutParams = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -124,7 +123,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar //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")) { - settingMenu.inject(viewGroup, originalAddView) + menuMap[SettingsMenu::class]!!.inject(viewGroup, childView, originalAddView) viewGroup.addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) {} override fun onViewDetachedFromWindow(v: View) { @@ -139,7 +138,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar //filter by the slot index if (viewGroup.getChildCount() != context.config.userInterface.friendFeedMenuPosition.get()) return@subscribe if (viewTagState[viewGroup]) return@subscribe - friendFeedInfoMenu.inject(viewGroup, originalAddView) + menuMap[FriendFeedInfoMenu::class]!!.inject(viewGroup, childView, originalAddView) } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaContextActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/OperaContextActionMenu.kt @@ -7,16 +7,14 @@ import android.view.ViewGroup import android.widget.Button import android.widget.LinearLayout import android.widget.ScrollView -import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.core.ui.applyTheme import me.rhunk.snapenhance.core.ui.menu.AbstractMenu +import me.rhunk.snapenhance.core.util.ktx.getId @SuppressLint("DiscouragedApi") class OperaContextActionMenu : AbstractMenu() { - private val contextCardsScrollView by lazy { - context.resources.getIdentifier("context_cards_scroll_view", "id", Constants.SNAPCHAT_PACKAGE_NAME) - } + private val contextCardsScrollView by lazy { context.resources.getId("context_cards_scroll_view") } /* LinearLayout : @@ -52,15 +50,15 @@ class OperaContextActionMenu : AbstractMenu() { } @SuppressLint("SetTextI18n") - fun inject(viewGroup: ViewGroup, childView: View) { + override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) { try { - if (viewGroup.parent !is ScrollView) return - val parent = viewGroup.parent as ScrollView - if (parent.id != contextCardsScrollView) return - if (childView !is LinearLayout) return - if (!isViewGroupButtonMenuContainer(childView as ViewGroup)) return + if (parent.parent !is ScrollView) return + val parentView = parent.parent as ScrollView + if (parentView.id != contextCardsScrollView) return + if (view !is LinearLayout) return + if (!isViewGroupButtonMenuContainer(view as ViewGroup)) return - val linearLayout = LinearLayout(childView.getContext()) + val linearLayout = LinearLayout(view.context) linearLayout.orientation = LinearLayout.VERTICAL linearLayout.gravity = Gravity.CENTER linearLayout.layoutParams = @@ -71,21 +69,21 @@ class OperaContextActionMenu : AbstractMenu() { val translation = context.translation val mediaDownloader = context.feature(MediaDownloader::class) - linearLayout.addView(Button(childView.getContext()).apply { + linearLayout.addView(Button(view.context).apply { text = translation["opera_context_menu.download"] setOnClickListener { mediaDownloader.downloadLastOperaMediaAsync() } applyTheme(isAmoled = false) }) if (context.isDeveloper) { - linearLayout.addView(Button(childView.getContext()).apply { + linearLayout.addView(Button(view.context).apply { text = "Show debug info" setOnClickListener { mediaDownloader.showLastOperaDebugMediaInfo() } applyTheme(isAmoled = false) }) } - (childView as ViewGroup).addView(linearLayout, 0) + (view as ViewGroup).addView(linearLayout, 0) } catch (e: Throwable) { context.log.error("Error while injecting OperaContextActionMenu", e) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/SettingsGearInjector.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/SettingsGearInjector.kt @@ -5,36 +5,21 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView -import me.rhunk.snapenhance.common.Constants 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.getStyledAttributes @SuppressLint("DiscouragedApi") class SettingsGearInjector : AbstractMenu() { - private val headerButtonOpaqueIconTint by lazy { - context.resources.getIdentifier("headerButtonOpaqueIconTint", "attr", Constants.SNAPCHAT_PACKAGE_NAME).let { - context.androidContext.theme.obtainStyledAttributes(intArrayOf(it)).getColorStateList(0) - } - } - - private val settingsSvg by lazy { - context.resources.getIdentifier("svg_settings_32x32", "drawable", Constants.SNAPCHAT_PACKAGE_NAME).let { - context.resources.getDrawable(it, context.androidContext.theme) - } - } - - private val ngsHovaHeaderSearchIconBackgroundMarginLeft by lazy { - context.resources.getIdentifier("ngs_hova_header_search_icon_background_margin_left", "dimen", Constants.SNAPCHAT_PACKAGE_NAME).let { - context.resources.getDimensionPixelSize(it) - } - } + override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) { + val firstView = (view as ViewGroup).getChildAt(0) - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") - fun inject(parent: ViewGroup, child: View) { - val firstView = (child as ViewGroup).getChildAt(0) + val ngsHovaHeaderSearchIconBackgroundMarginLeft = context.resources.getDimens("ngs_hova_header_search_icon_background_margin_left") - child.clipChildren = false - child.addView(FrameLayout(parent.context).apply { + view.clipChildren = false + view.addView(FrameLayout(parent.context).apply { layoutParams = FrameLayout.LayoutParams(firstView.layoutParams.width, firstView.layoutParams.height).apply { y = 0f x = -(ngsHovaHeaderSearchIconBackgroundMarginLeft + firstView.layoutParams.width).toFloat() @@ -52,7 +37,7 @@ class SettingsGearInjector : AbstractMenu() { } parent.setOnTouchListener { _, event -> - if (child.visibility == View.INVISIBLE || child.alpha == 0F) return@setOnTouchListener false + if (view.visibility == View.INVISIBLE || view.alpha == 0F) return@setOnTouchListener false val viewLocation = IntArray(2) getLocationOnScreen(viewLocation) @@ -73,8 +58,8 @@ class SettingsGearInjector : AbstractMenu() { layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 17).apply { gravity = android.view.Gravity.CENTER } - setImageDrawable(settingsSvg) - headerButtonOpaqueIconTint?.let { + setImageDrawable(context.resources.getDrawable("svg_settings_32x32", context.theme)) + context.resources.getStyledAttributes("headerButtonOpaqueIconTint", context.theme).getColorStateList(0)?.let { imageTintList = it } }) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/SettingsMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/SettingsMenu.kt @@ -1,29 +1,6 @@ package me.rhunk.snapenhance.core.ui.menu.impl -import android.annotation.SuppressLint -import android.view.View import me.rhunk.snapenhance.core.ui.menu.AbstractMenu class SettingsMenu : AbstractMenu() { - //TODO: quick settings - @SuppressLint("SetTextI18n") - @Suppress("UNUSED_PARAMETER") - fun inject(viewModel: View, addView: (View) -> Unit) { - /*val actions = context.actionManager.getActions().map { - Pair(it) { - val button = Button(viewModel.context) - button.text = context.translation[it.nameKey] - - button.setOnClickListener { _ -> - it.run() - } - ViewAppearanceHelper.applyTheme(button) - button - } - } - - actions.forEach { - addView(it.second()) - }*/ - } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/AndroidExt.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/AndroidExt.kt @@ -0,0 +1,33 @@ +package me.rhunk.snapenhance.core.util.ktx + +import android.annotation.SuppressLint +import android.content.res.Resources +import android.content.res.Resources.Theme +import android.content.res.TypedArray +import android.graphics.drawable.Drawable +import me.rhunk.snapenhance.common.Constants + + +@SuppressLint("DiscouragedApi") +fun Resources.getIdentifier(name: String, type: String): Int { + return getIdentifier(name, type, Constants.SNAPCHAT_PACKAGE_NAME) +} + +@SuppressLint("DiscouragedApi") +fun Resources.getId(name: String): Int { + return getIdentifier(name, "id", Constants.SNAPCHAT_PACKAGE_NAME) +} + +fun Resources.getDimens(name: String): Int { + return getDimensionPixelSize(getIdentifier(name, "dimen")) +} + +fun Resources.getStyledAttributes(name: String, theme: Theme): TypedArray { + return getIdentifier(name, "attr").let { + theme.obtainStyledAttributes(intArrayOf(it)) + } +} + +fun Resources.getDrawable(name: String, theme: Theme): Drawable { + return getDrawable(getIdentifier(name, "drawable"), theme) +} diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/XposedHelperExt.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/ktx/XposedHelperExt.kt @@ -20,7 +20,7 @@ fun Any.setObjectField(fieldName: String, value: Any?) { fun Any.getObjectFieldOrNull(fieldName: String): Any? { return try { getObjectField(fieldName) - } catch (e: Exception) { + } catch (t: Throwable) { null } }