diff --git a/app/libs/snapmod.jar b/app/libs/snapmod.jar index 3bdce52..cc18dd8 100644 Binary files a/app/libs/snapmod.jar and b/app/libs/snapmod.jar differ diff --git a/app/src/main/java/xyz/rodit/snapmod/DelegateProxy.kt b/app/src/main/java/xyz/rodit/snapmod/DelegateProxy.kt new file mode 100644 index 0000000..2ee4467 --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/DelegateProxy.kt @@ -0,0 +1,22 @@ +package xyz.rodit.snapmod + +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy + +typealias DelegateFunction = (Any, Array) -> Any? + +class DelegateProxy(private val delegate: DelegateFunction) : InvocationHandler { + + override fun invoke(target: Any, method: Method, args: Array?): Any? { + return delegate(target, args ?: emptyArray()) + } +} + +fun Class<*>.createDelegate(classLoader: ClassLoader, delegate: DelegateFunction): Any { + return Proxy.newProxyInstance( + classLoader, + arrayOf(this), + DelegateProxy(delegate) + ) +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt b/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt index 75b05be..f36accc 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt @@ -1,6 +1,7 @@ package xyz.rodit.snapmod.features import xyz.rodit.snapmod.features.chatmenu.ChatMenuModifier +import xyz.rodit.snapmod.features.chatmenu.new.NewChatMenuModifier import xyz.rodit.snapmod.features.conversations.* import xyz.rodit.snapmod.features.friendsfeed.FeedModifier import xyz.rodit.snapmod.features.info.AdditionalFriendInfo @@ -22,6 +23,7 @@ class FeatureManager(context: FeatureContext) : Contextual(context) { fun load() { // Chat context menu add(::ChatMenuModifier) + add(::NewChatMenuModifier) // Friends feed add(::FeedModifier) diff --git a/app/src/main/java/xyz/rodit/snapmod/features/callbacks/CallbackManager.kt b/app/src/main/java/xyz/rodit/snapmod/features/callbacks/CallbackManager.kt index 9d20843..096dee4 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/callbacks/CallbackManager.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/callbacks/CallbackManager.kt @@ -3,6 +3,8 @@ package xyz.rodit.snapmod.features.callbacks import de.robv.android.xposed.XC_MethodHook import xyz.rodit.dexsearch.client.xposed.MethodRef import xyz.rodit.snapmod.isDummyProxy +import xyz.rodit.snapmod.mappings.DefaultFetchConversationCallback +import xyz.rodit.snapmod.mappings.DefaultFetchMessageCallback import xyz.rodit.snapmod.util.before import kotlin.reflect.KClass @@ -28,4 +30,16 @@ class CallbackManager { fun on(type: KClass<*>, method: MethodRef, callback: HookedCallback) { callbacks.computeIfAbsent("${type.simpleName}:${method.name}") { mutableListOf() }.add(callback) } + + init { + hook( + DefaultFetchConversationCallback::class, + DefaultFetchConversationCallback.onFetchConversationWithMessagesComplete + ) { DefaultFetchConversationCallback.wrap(it).dummy } + + hook( + DefaultFetchMessageCallback::class, + DefaultFetchMessageCallback.onFetchMessageComplete + ) { DefaultFetchMessageCallback.wrap(it).dummy } + } } diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ChatMenuModifier.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ChatMenuModifier.kt index b984e53..81f2f86 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ChatMenuModifier.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ChatMenuModifier.kt @@ -19,7 +19,7 @@ const val PIN_STRING_NAME = "action_menu_pin_conversation" class ChatMenuModifier(context: FeatureContext) : Feature(context) { - private val plugins: MutableMap = HashMap() + private val plugins = mutableMapOf() override fun init() { registerPlugin(PreviewOption(context)) diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ExportOption.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ExportOption.kt index 3be9a64..39d6585 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ExportOption.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/ExportOption.kt @@ -1,13 +1,8 @@ package xyz.rodit.snapmod.features.chatmenu -import android.content.Intent -import androidx.core.content.FileProvider import xyz.rodit.snapmod.CustomResources.string.menu_option_export import xyz.rodit.snapmod.features.FeatureContext -import xyz.rodit.snapmod.mappings.SelectFriendsByUserIds -import java.io.File -import java.text.SimpleDateFormat -import java.util.* +import xyz.rodit.snapmod.features.chatmenu.shared.export class ExportOption(context: FeatureContext): ButtonOption(context, "export_chat", menu_option_export) { @@ -17,41 +12,6 @@ class ExportOption(context: FeatureContext): override fun handleEvent(data: String?) { if (data == null) return - val (messages, senders) = context.arroyo.getAllMessages(data) - val friendData = - context.instances.friendsRepository.selectFriendsByUserIds(senders.toList()) - val senderMap = friendData.map(SelectFriendsByUserIds::wrap).associateBy { u -> u.userId } - - val dateFormat = SimpleDateFormat("dd/MM/yyyy, HH:mm:ss", Locale.getDefault()) - - val temp = File.createTempFile( - "Snapchat Export ", - ".txt", - File(context.appContext.filesDir, "file_manager/media") - ) - temp.deleteOnExit() - temp.bufferedWriter().use { - messages.forEach { m -> - val username = senderMap[m.senderId]?.displayName ?: "Unknown" - val dateTime = dateFormat.format(m.timestamp) - it.append(dateTime) - .append(" - ") - .append(username) - .append(": ") - .appendLine(m.content) - } - } - - val intent = Intent(Intent.ACTION_SEND) - .setType("text/plain") - .putExtra( - Intent.EXTRA_STREAM, - FileProvider.getUriForFile( - context.appContext, - "com.snapchat.android.media.fileprovider", - temp - ) - ) - context.activity?.startActivity(Intent.createChooser(intent, "Export Chat")) + export(context, data) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/PreviewOption.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/PreviewOption.kt index b758fda..f8df45a 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/PreviewOption.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/PreviewOption.kt @@ -1,14 +1,8 @@ package xyz.rodit.snapmod.features.chatmenu -import android.app.AlertDialog -import de.robv.android.xposed.XC_MethodHook import xyz.rodit.snapmod.CustomResources.string.menu_option_preview -import xyz.rodit.snapmod.createDummyProxy import xyz.rodit.snapmod.features.FeatureContext -import xyz.rodit.snapmod.mappings.* -import xyz.rodit.snapmod.util.toSnapUUID -import xyz.rodit.snapmod.util.toUUIDString -import java.lang.Integer.min +import xyz.rodit.snapmod.features.chatmenu.shared.previewChat class PreviewOption(context: FeatureContext) : ButtonOption(context, "preview", menu_option_preview) { @@ -18,79 +12,6 @@ class PreviewOption(context: FeatureContext) : override fun handleEvent(data: String?) { if (data == null) return - val uuid = data.toSnapUUID() - val proxy = - ConversationDummyInterface.wrap( - ConversationDummyInterface.getMappedClass().createDummyProxy(context.classLoader) - ) - - context.callbacks.on( - DefaultFetchConversationCallback::class, - DefaultFetchConversationCallback.onFetchConversationWithMessagesComplete, - this::displayPreview - ) - - context.instances.conversationManager.fetchConversationWithMessages( - uuid, - DefaultFetchConversationCallback(proxy, uuid, false) - ) - } - - override fun performHooks() { - context.callbacks.hook( - DefaultFetchConversationCallback::class, - DefaultFetchConversationCallback.onFetchConversationWithMessagesComplete - ) { DefaultFetchConversationCallback.wrap(it).dummy } - } - - private fun displayPreview(param: XC_MethodHook.MethodHookParam): Boolean { - val conversation = Conversation.wrap(param.args[0]) - - val userIds = conversation.participants.map(Participant::wrap) - .map { p -> (p.participantId.id as ByteArray).toUUIDString() } - val friendData = context.instances.friendsRepository.selectFriendsByUserIds(userIds) - val userMap = friendData.map(SelectFriendsByUserIds::wrap).associateBy { u -> u.userId } - - val messageList = param.args[1] as List<*> - val previewText = StringBuilder() - if (messageList.isEmpty()) previewText.append("No messages available.") - else { - val numMessages = - min(context.config.getInt("preview_messages_count", 5), messageList.size) - previewText.append("Last ").append(numMessages).append(" messages:") - messageList.takeLast(numMessages) - .map(Message::wrap).forEach { m -> - run { - val uuidString = m.senderId.toUUIDString() - val displayName = userMap[uuidString]?.displayName ?: "Unknown" - previewText.append('\n').append(displayName).append(": ") - if (m.messageContent.contentType.instance == ContentType.CHAT().instance) { - val chatMessage = - NanoMessageContent.parse(m.messageContent.content).chatMessageContent.content - previewText.append(chatMessage) - } else { - previewText.append(m.messageContent.contentType.instance) - } - } - } - } - - userMap.values.find { f -> f.streakExpiration ?: 0L > 0L }?.let { f -> - val hourDiff = - (f.streakExpiration - System.currentTimeMillis()).toDouble() / 3600000.0 - previewText.append("\n\nStreak Expires in ") - .append(String.format("%.1f", hourDiff)) - .append(" hours") - } - - context.activity?.runOnUiThread { - AlertDialog.Builder(context.activity) - .setTitle(if (conversation.title.isNullOrBlank()) "Chat Preview" else conversation.title) - .setMessage(previewText) - .setPositiveButton("Ok") { _, _ -> } - .show() - } - - return true + previewChat(context, data) } } \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/MenuPlugin.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/MenuPlugin.kt new file mode 100644 index 0000000..c02ddfa --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/MenuPlugin.kt @@ -0,0 +1,12 @@ +package xyz.rodit.snapmod.features.chatmenu.new + +abstract class MenuPlugin { + + abstract fun shouldCreate(): Boolean + + abstract fun createModel(key: String): Any + + open fun performHooks() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/NewChatMenuModifier.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/NewChatMenuModifier.kt new file mode 100644 index 0000000..9c7e795 --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/NewChatMenuModifier.kt @@ -0,0 +1,72 @@ +package xyz.rodit.snapmod.features.chatmenu.new + +import xyz.rodit.snapmod.createDelegate +import xyz.rodit.snapmod.features.Feature +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.features.chatmenu.shared.export +import xyz.rodit.snapmod.features.chatmenu.shared.previewChat +import xyz.rodit.snapmod.mappings.* +import xyz.rodit.snapmod.util.before + +class NewChatMenuModifier(context: FeatureContext) : Feature(context) { + + private val plugins = mutableListOf() + + override fun init() { + registerPlain("Export") { export(context, it) } + registerPlain("Preview") { previewChat(context, it) } + + registerSwitch("pinning", "Pin Conversation") { it.pinned } + registerSwitch("stealth", "Stealth Mode") { it.stealth } + registerSwitch("auto_save", "Auto-Save Messages") { it.autoSave } + registerSwitch("auto_download", "Auto-Download Snaps") { it.autoDownload } + } + + private fun registerPlugin(plugin: MenuPlugin) { + plugins.add(plugin) + } + + private fun registerPlain(text: String, click: ClickHandler) { + registerPlugin(PlainOption(context, text, click)) + } + + private fun registerSwitch(name: String, text: String, manager: Manager) { + registerPlugin(SwitchOption(context, name, text, manager)) + } + + override fun performHooks() { + // Force new chat action menu + ProfileActionSheetChooser.choose.before { + it.args[0] = context.config.getBoolean("enable_new_chat_menu", true) + } + + // Add subsection + ProfileActionSheetCreator.apply.before { + if (it.args[0] !is List<*>) return@before + + val newItems = (it.args[0] as List<*>).toMutableList() + val creator = ProfileActionSheetCreator.wrap(it.thisObject) + val nestedContext = NestedActionMenuContext.wrap(creator.nestedContext) + val actionContext = ActionMenuContext.wrap(creator.actionMenuContext) + val key = actionContext.feedInfo.key + + val subOptions = plugins.filter(MenuPlugin::shouldCreate).map { p -> + p.createModel(key) + } + val clickProxy = + Func0.getMappedClass().createDelegate(context.classLoader) { _, _ -> + NestedActionMenuContext.display( + nestedContext, + "SnapMod", + subOptions + ) + null + } + val snapModSettings = + ActionClickableCaret("SnapMod Settings", null, Func0.wrap(clickProxy)).instance + newItems.add(snapModSettings) + + it.args[0] = newItems + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/PlainOption.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/PlainOption.kt new file mode 100644 index 0000000..8651593 --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/PlainOption.kt @@ -0,0 +1,24 @@ +package xyz.rodit.snapmod.features.chatmenu.new + +import xyz.rodit.snapmod.createDelegate +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.mappings.ActionPlain +import xyz.rodit.snapmod.mappings.Func0 + +typealias ClickHandler = (key: String) -> Unit + +class PlainOption( + private val context: FeatureContext, + private val text: String, + private val click: ClickHandler +) : MenuPlugin() { + + override fun shouldCreate() = true + + override fun createModel(key: String): Any = ActionPlain( + text, + Func0.wrap(Func0.getMappedClass().createDelegate(context.classLoader) { _, _ -> + click(key) + }) + ).instance +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/SwitchOption.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/SwitchOption.kt new file mode 100644 index 0000000..82e04af --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/new/SwitchOption.kt @@ -0,0 +1,32 @@ +package xyz.rodit.snapmod.features.chatmenu.new + +import xyz.rodit.snapmod.createDelegate +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.mappings.ActionSwitch +import xyz.rodit.snapmod.mappings.Func1 +import xyz.rodit.snapmod.util.ConversationManager +import xyz.rodit.snapmod.util.getList + +typealias Manager = (FeatureContext) -> ConversationManager + +class SwitchOption( + private val context: FeatureContext, + private val name: String, + private val text: String, + private val manager: Manager +) : MenuPlugin() { + + override fun shouldCreate() = !context.config.getList("hidden_chat_options").contains(name) + + override fun createModel(key: String): Any = ActionSwitch( + text, + manager(context).isEnabled(key), + Func1.wrap(Func1.getMappedClass().createDelegate(context.classLoader) { _, _ -> true }), + Func1.wrap(Func1.getMappedClass().createDelegate(context.classLoader) { _, _ -> + manager(context).toggle(key) + true + }), + null, + 0 + ).instance +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/shared/ExportChat.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/shared/ExportChat.kt new file mode 100644 index 0000000..7e28037 --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/shared/ExportChat.kt @@ -0,0 +1,48 @@ +package xyz.rodit.snapmod.features.chatmenu.shared + +import android.content.Intent +import androidx.core.content.FileProvider +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.mappings.SelectFriendsByUserIds +import java.io.File +import java.text.SimpleDateFormat +import java.util.* + +fun export(context: FeatureContext, key: String) { + val (messages, senders) = context.arroyo.getAllMessages(key) + val friendData = + context.instances.friendsRepository.selectFriendsByUserIds(senders.toList()) + val senderMap = friendData.map(SelectFriendsByUserIds::wrap).associateBy { u -> u.userId } + + val dateFormat = SimpleDateFormat("dd/MM/yyyy, HH:mm:ss", Locale.getDefault()) + + val temp = File.createTempFile( + "Snapchat Export ", + ".txt", + File(context.appContext.filesDir, "file_manager/media") + ) + temp.deleteOnExit() + temp.bufferedWriter().use { + messages.forEach { m -> + val username = senderMap[m.senderId]?.displayName ?: "Unknown" + val dateTime = dateFormat.format(m.timestamp) + it.append(dateTime) + .append(" - ") + .append(username) + .append(": ") + .appendLine(m.content) + } + } + + val intent = Intent(Intent.ACTION_SEND) + .setType("text/plain") + .putExtra( + Intent.EXTRA_STREAM, + FileProvider.getUriForFile( + context.appContext, + "com.snapchat.android.media.fileprovider", + temp + ) + ) + context.activity?.startActivity(Intent.createChooser(intent, "Export Chat")) +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/shared/PreviewChat.kt b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/shared/PreviewChat.kt new file mode 100644 index 0000000..46c11fd --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/chatmenu/shared/PreviewChat.kt @@ -0,0 +1,78 @@ +package xyz.rodit.snapmod.features.chatmenu.shared + +import android.app.AlertDialog +import de.robv.android.xposed.XC_MethodHook +import xyz.rodit.snapmod.createDummyProxy +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.mappings.* +import xyz.rodit.snapmod.util.toSnapUUID +import xyz.rodit.snapmod.util.toUUIDString + +fun previewChat(context: FeatureContext, key: String) { + val uuid = key.toSnapUUID() + val proxy = + ConversationDummyInterface.wrap( + ConversationDummyInterface.getMappedClass().createDummyProxy(context.classLoader) + ) + + context.callbacks.on( + DefaultFetchConversationCallback::class, + DefaultFetchConversationCallback.onFetchConversationWithMessagesComplete + ) { displayPreview(context, it) } + + context.instances.conversationManager.fetchConversationWithMessages( + uuid, + DefaultFetchConversationCallback(proxy, uuid, false) + ) +} + +private fun displayPreview(context: FeatureContext, param: XC_MethodHook.MethodHookParam): Boolean { + val conversation = Conversation.wrap(param.args[0]) + + val userIds = conversation.participants.map(Participant::wrap) + .map { p -> (p.participantId.id as ByteArray).toUUIDString() } + val friendData = context.instances.friendsRepository.selectFriendsByUserIds(userIds) + val userMap = friendData.map(SelectFriendsByUserIds::wrap).associateBy { u -> u.userId } + + val messageList = param.args[1] as List<*> + val previewText = StringBuilder() + if (messageList.isEmpty()) previewText.append("No messages available.") + else { + val numMessages = + Integer.min(context.config.getInt("preview_messages_count", 5), messageList.size) + previewText.append("Last ").append(numMessages).append(" messages:") + messageList.takeLast(numMessages) + .map(Message::wrap).forEach { m -> + run { + val uuidString = m.senderId.toUUIDString() + val displayName = userMap[uuidString]?.displayName ?: "Unknown" + previewText.append('\n').append(displayName).append(": ") + if (m.messageContent.contentType.instance == ContentType.CHAT().instance) { + val chatMessage = + NanoMessageContent.parse(m.messageContent.content).chatMessageContent.content + previewText.append(chatMessage) + } else { + previewText.append(m.messageContent.contentType.instance) + } + } + } + } + + userMap.values.find { f -> f.streakExpiration ?: 0L > 0L }?.let { f -> + val hourDiff = + (f.streakExpiration - System.currentTimeMillis()).toDouble() / 3600000.0 + previewText.append("\n\nStreak Expires in ") + .append(String.format("%.1f", hourDiff)) + .append(" hours") + } + + context.activity?.runOnUiThread { + AlertDialog.Builder(context.activity) + .setTitle(if (conversation.title.isNullOrBlank()) "Chat Preview" else conversation.title) + .setMessage(previewText) + .setPositiveButton("Ok") { _, _ -> } + .show() + } + + return true +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/saving/StoriesSaving.kt b/app/src/main/java/xyz/rodit/snapmod/features/saving/StoriesSaving.kt index bd1aee0..5139c8f 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/saving/StoriesSaving.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/saving/StoriesSaving.kt @@ -1,13 +1,14 @@ package xyz.rodit.snapmod.features.saving +import xyz.rodit.snapmod.createDelegate import xyz.rodit.snapmod.features.Feature import xyz.rodit.snapmod.features.FeatureContext -import xyz.rodit.snapmod.mappings.* +import xyz.rodit.snapmod.mappings.ContextActionMenuModel +import xyz.rodit.snapmod.mappings.Func1 +import xyz.rodit.snapmod.mappings.OperaContextAction +import xyz.rodit.snapmod.mappings.ParamsMap import xyz.rodit.snapmod.util.PathManager import xyz.rodit.snapmod.util.after -import java.lang.reflect.InvocationHandler -import java.lang.reflect.Method -import java.lang.reflect.Proxy class StoriesSaving(context: FeatureContext) : Feature(context) { @@ -17,31 +18,14 @@ class StoriesSaving(context: FeatureContext) : Feature(context) { val model = ContextActionMenuModel.wrap(it.thisObject) if (model.action.instance !== OperaContextAction.SAVE().instance) return@after - val clickProxy = Proxy.newProxyInstance( - context.classLoader, - arrayOf(ContextClickHandler.getMappedClass()), - StoryDownloadProxy(context) - ) - model.onClick = ContextClickHandler.wrap(clickProxy) - } - - context.callbacks.hook( - DefaultFetchMessageCallback::class, - DefaultFetchMessageCallback.onFetchMessageComplete - ) { DefaultFetchMessageCallback.wrap(it).dummy } - } - - private class StoryDownloadProxy(private val context: FeatureContext) : InvocationHandler { - - override fun invoke(thisObject: Any, method: Method, args: Array?): Any? { - if (method.name != ContextClickHandler.invoke.dexName) return null - - val map = ParamsMap.wrap(args!![0]) - getMediaInfo(context, map) { - downloadOperaMedia(context, PathManager.DOWNLOAD_STORY, it) + val clickProxy = Func1.getMappedClass().createDelegate(context.classLoader) { _, args -> + val map = ParamsMap.wrap(args[0]) + getMediaInfo(context, map) { + downloadOperaMedia(context, PathManager.DOWNLOAD_STORY, it) + } + null } - - return null + model.onClick = Func1.wrap(clickProxy) } } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7ea8a6c..c3691bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -152,4 +152,7 @@ Auto-Download Snaps Automatically downloads snaps when viewed. + + Enable New Chat Menu + Enables the new chat context menu (recommended). \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index e3cf605..c0a84dc 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -164,6 +164,13 @@ app:summary="@string/enable_new_voice_notes_description" app:iconSpaceReserved="false" /> + + (...) { @@ -1840,4 +1840,127 @@ class CameraRollVideoLengthChecker { static boolean isOver60Seconds(!CameraRollMediaBase) { .const 60000; } +} + +[certain, obfuscated] +enum ProfileActionSheetDisplay { + + [late] static this .fields { ONLY_HEADER, NO_HEADER, DEFAULT } +} + +[certain, discard] +class ProfileActionSheetProvider { + + void $(...) { + .string "ProfileActionSheetProvider"; + } +} + +[certain] +class ProfileActionSheetCreator { + + Object nestedContext; + Object actionMenuContext; + + * apply(*) { + .type !ProfileActionSheetProvider; + .field !ProfileActionSheetDisplay->!ONLY_HEADER; + .field !ProfileActionSheetDisplay->!NO_HEADER; + } +} + +[certain, obfuscated] +enum ActionSheetType { + + [late] static this .fields { PROFILE, PROFILE_ACTION_MENU } +} + +[certain] +class ActionMenuContext { + + !ActionSheetType sheetType; + !FriendInfo friendInfo; + !BasicFeedInfo feedInfo; + !ProfileActionSheetDisplay display; +} + +[certain] +class ActionClickableCaret { + + String primaryText; + Drawable drawable; + #Func0 onClick; + + void $(...) + + String $toString() { + .string "ClickableCaret(primaryText="; + .string ", drawable="; + } +} + +[certain] +class ActionSwitch { + + String primaryText; + boolean isSwitchedOn; + !Func1 onToggleRequested; + !Func1 onToggle; + + void $(...) + + String $toString() { + .string "Switch(primaryText="; + .string ", isSwitchedOn="; + } +} + +[certain] +class ActionPlain { + + String primaryText; + !Func0 onClick; + + void $(...) + + String $toString() { + .string "Plain(primaryText="; + .string ", onClick="; + } +} + +[certain] +class NestedActionMenuContext { + + Context context; + !CompositeConfigurationProvider config; + + static void display(this, String, List) +} + +[certain] +class ProfileActionSheetChooser { + + * choose(*) { + .field !ProfileActionSheetDisplay->!ONLY_HEADER; + .field !ProfileActionSheetDisplay->!DEFAULT; + } +} + +[late] +interface Func0 { + + * invoke() +} + +[late] +interface Func1 { + + * invoke(*) +} + +[late] +interface Func2 { + + * invoke(*, *) } \ No newline at end of file