commit ab8c4ddb665961961bd6629ace762f0bd67e8675 parent bbfc0a835074a44f770ca85a9a3857e8b5a1f4c7 Author: auth <64337177+authorisation@users.noreply.github.com> Date: Sun, 11 Jun 2023 01:10:24 +0200 feat: ff menu bar (#59) fix: amoled dark mode Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com> Diffstat:
9 files changed, 378 insertions(+), 178 deletions(-)
diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json @@ -69,7 +69,10 @@ "picture_resolution": "Override Picture Resolution", "force_highest_frame_rate": "Force Highest Frame Rate", "force_camera_source_encoding": "Force Camera Source Encoding", - "amoled_dark_mode": "AMOLED Dark Mode" + "amoled_dark_mode": "AMOLED Dark Mode", + "enable_friend_feed_menu_bar": "Enable New Friend Feed Menu Bar", + "friend_feed_menu_buttons": "Friend Feed Menu Buttons", + "friend_feed_menu_buttons_position": "Friend Feed Buttons Position Index" }, "option": { @@ -79,6 +82,12 @@ "snap": "Show medias", "reply_button": "Add reply button" }, + "friend_feed_menu_buttons": { + "auto_download_blacklist": "\u2B07\uFE0F Auto Download Blacklist", + "anti_auto_save": "\uD83D\uDCAC Anti Auto Save Messages", + "stealth_mode": "\uD83D\uDC7B Stealth Mode", + "conversation_info": "\uD83D\uDC64 Conversation Info" + }, "download_options": { "format_user_folder": "Create folder for each user", "format_hash": "Add a unique hash to the file path", @@ -140,7 +149,7 @@ "friend_menu_option": { "preview": "Preview", "stealth_mode": "Stealth Mode", - "anti_auto_download": "Anti Auto Download", + "auto_download_blacklist": "Auto Download Blacklist", "anti_auto_save": "Anti Auto Save" }, diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt @@ -141,12 +141,6 @@ enum class ConfigProperty( ConfigCategory.MEDIA_MANAGEMENT, ConfigStateValue(false) ), - DOWNLOAD_BLACKLIST( - "property.auto_download_blacklist", - "description.auto_download_blacklist", - ConfigCategory.MEDIA_MANAGEMENT, - ConfigStateValue(false) - ), GALLERY_MEDIA_SEND_OVERRIDE( "property.gallery_media_send_override", "description.gallery_media_send_override", @@ -163,11 +157,6 @@ enum class ConfigProperty( listOf("CHAT", "SNAP", "NOTE", "EXTERNAL_MEDIA", "STICKER") ) ), - ANTI_AUTO_SAVE("property.anti_auto_save", - "description.anti_auto_save", - ConfigCategory.MEDIA_MANAGEMENT, - ConfigStateValue(false) - ), FORCE_MEDIA_SOURCE_QUALITY( "property.force_media_source_quality", @@ -177,6 +166,31 @@ enum class ConfigProperty( ), //UI AND TWEAKS + ENABLE_FRIEND_FEED_MENU_BAR( + "property.enable_friend_feed_menu_bar", + "description.enable_friend_feed_menu_bar", + ConfigCategory.UI_TWEAKS, + ConfigStateValue(false) + ), + FRIEND_FEED_MENU_BUTTONS( + "property.friend_feed_menu_buttons", + "description.friend_feed_menu_buttons", + ConfigCategory.UI_TWEAKS, + ConfigStateListValue( + listOf("auto_download_blacklist", "anti_auto_save", "stealth_mode", "conversation_info"), + mutableMapOf( + "auto_download_blacklist" to false, + "anti_auto_save" to false, + "stealth_mode" to true, + "conversation_info" to true + ) + ) + ), + FRIEND_FEED_MENU_POSITION("property.friend_feed_menu_buttons_position", + "description.friend_feed_menu_buttons_position", + ConfigCategory.UI_TWEAKS, + ConfigIntegerValue(1) + ), CAMERA_DISABLE( "property.disable_camera", "description.disable_camera", diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/data/wrapper/impl/FriendActionButton.kt b/app/src/main/kotlin/me/rhunk/snapenhance/data/wrapper/impl/FriendActionButton.kt @@ -0,0 +1,38 @@ +package me.rhunk.snapenhance.data.wrapper.impl + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import me.rhunk.snapenhance.SnapEnhance +import me.rhunk.snapenhance.data.wrapper.AbstractWrapper + +class FriendActionButton( + obj: View +) : AbstractWrapper(obj) { + private val iconDrawableContainer by lazy { + instanceNonNull().javaClass.declaredFields.first { it.type != Int::class.javaPrimitiveType }[instanceNonNull()] + } + + private val setIconDrawableMethod by lazy { + iconDrawableContainer.javaClass.declaredMethods.first { + it.parameterTypes.size == 1 && + it.parameterTypes[0] == Drawable::class.java && + it.name != "invalidateDrawable" && + it.returnType == Void::class.javaPrimitiveType + } + } + + fun setIconDrawable(drawable: Drawable) { + setIconDrawableMethod.invoke(iconDrawableContainer, drawable) + } + + companion object { + fun new(context: Context): FriendActionButton { + val instance = SnapEnhance.classLoader.loadClass("com.snap.profile.shared.view.FriendActionButton") + .getConstructor(Context::class.java, AttributeSet::class.java) + .newInstance(context, null) as View + return FriendActionButton(instance) + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/AmoledDarkMode.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/AmoledDarkMode.kt @@ -36,7 +36,8 @@ class AmoledDarkMode : Feature("Amoled Dark Mode", loadParams = FeatureLoadParam getAttribute("sigColorTextPrimary") -> { ephemeralHook("getColor", 0xFFFFFFFF.toInt()) } - getAttribute("sigColorBackgroundMain") -> { + getAttribute("sigColorBackgroundMain"), + getAttribute("sigColorBackgroundSurface") -> { ephemeralHook("getColor", 0xFF000000.toInt()) } getAttribute("actionSheetBackgroundDrawable"), diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/ViewAppearanceHelper.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/ViewAppearanceHelper.kt @@ -3,8 +3,11 @@ package me.rhunk.snapenhance.features.impl.ui.menus import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.StateListDrawable import android.view.Gravity -import android.view.MotionEvent import android.view.View import android.widget.Switch import android.widget.TextView @@ -14,65 +17,77 @@ object ViewAppearanceHelper { @SuppressLint("UseSwitchCompatOrMaterialCode", "RtlHardcoded", "DiscouragedApi", "ClickableViewAccessibility" ) - fun applyTheme(viewModel: View, view: TextView) { - val sigColorTextPrimary = viewModel.context.theme.obtainStyledAttributes( - intArrayOf(viewModel.resources.getIdentifier("sigColorTextPrimary", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) - ).getColor(0, 0) + private var sigColorTextPrimary: Int = 0 + private var sigColorBackgroundSurface: Int = 0 - val sigColorBackgroundMain = viewModel.context.theme.obtainStyledAttributes( - intArrayOf(viewModel.resources.getIdentifier("sigColorBackgroundMain", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) - ).getColor(0, 0) + private fun createRoundedBackground(color: Int, hasRadius: Boolean): Drawable { + if (!hasRadius) return ColorDrawable(color) + //FIXME: hardcoded radius + return ShapeDrawable().apply { + paint.color = color + shape = android.graphics.drawable.shapes.RoundRectShape( + floatArrayOf(20f, 20f, 20f, 20f, 20f, 20f, 20f, 20f), + null, + null + ) + } + } - val snapchatFontResId = view.context.resources.getIdentifier("avenir_next_medium", "font", "com.snapchat.android") - //remove the shadow - view.setBackgroundColor(sigColorBackgroundMain) - view.setTextColor(sigColorTextPrimary) - view.setShadowLayer(0F, 0F, 0F, 0) - view.outlineProvider = null - view.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL - view.width = viewModel.width + @SuppressLint("DiscouragedApi") + fun applyTheme(component: View, componentWidth: Int? = null, hasRadius: Boolean = false) { + val resources = component.context.resources + if (sigColorBackgroundSurface == 0 || sigColorTextPrimary == 0) { + with(component.context.theme) { + sigColorTextPrimary = obtainStyledAttributes( + intArrayOf(resources.getIdentifier("sigColorTextPrimary", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) + ).getColor(0, 0) - //DPI Calculator - val scalingFactor = view.context.resources.displayMetrics.densityDpi.toDouble() / 400 - view.height = (150 * scalingFactor).toInt() - view.setPadding((40 * scalingFactor).toInt(), 0, (40 * scalingFactor).toInt(), 0) - view.isAllCaps = false - view.textSize = 16f - view.typeface = view.context.resources.getFont(snapchatFontResId) - - //FIXME: wrong color, shouldn't be that much noticeable though - view.setOnTouchListener { _, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - view.setBackgroundColor(0x5395026) - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - view.setBackgroundColor(sigColorBackgroundMain) - } + sigColorBackgroundSurface = obtainStyledAttributes( + intArrayOf(resources.getIdentifier("sigColorBackgroundSurface", "attr", Constants.SNAPCHAT_PACKAGE_NAME)) + ).getColor(0, 0) } - false } - - if (view is Switch) { - with(viewModel.resources) { - view.switchMinWidth = getDimension(getIdentifier("v11_switch_min_width", "dimen", Constants.SNAPCHAT_PACKAGE_NAME)).toInt() + + val snapchatFontResId = resources.getIdentifier("avenir_next_medium", "font", "com.snapchat.android") + val scalingFactor = resources.displayMetrics.densityDpi.toDouble() / 400 + + with(component) { + if (this is TextView) { + setTextColor(sigColorTextPrimary) + setShadowLayer(0F, 0F, 0F, 0) + gravity = Gravity.CENTER_VERTICAL + componentWidth?.let { width = it} + height = (150 * scalingFactor).toInt() + isAllCaps = false + textSize = 16f + typeface = resources.getFont(snapchatFontResId) + outlineProvider = null + setPadding((40 * scalingFactor).toInt(), 0, (40 * scalingFactor).toInt(), 0) + } + background = StateListDrawable().apply { + addState(intArrayOf(), createRoundedBackground(color = sigColorBackgroundSurface, hasRadius)) + addState(intArrayOf(android.R.attr.state_pressed), createRoundedBackground(color = 0x5395026, hasRadius)) + } + } + + if (component is Switch) { + with(resources) { + component.switchMinWidth = getDimension(getIdentifier("v11_switch_min_width", "dimen", Constants.SNAPCHAT_PACKAGE_NAME)).toInt() } - val colorStateList = ColorStateList( + component.trackTintList = ColorStateList( arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked) ), intArrayOf( Color.parseColor("#1d1d1d"), Color.parseColor("#26bd49") ) ) - val thumbStateList = ColorStateList( + component.thumbTintList = ColorStateList( arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked) ), intArrayOf( Color.parseColor("#F5F5F5"), Color.parseColor("#26bd49") ) ) - view.trackTintList = colorStateList - view.thumbTintList = thumbStateList } } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/ChatActionMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/ChatActionMenu.kt @@ -1,11 +1,7 @@ package me.rhunk.snapenhance.features.impl.ui.menus.impl import android.annotation.SuppressLint -import android.content.res.Resources -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.os.SystemClock -import android.util.TypedValue import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -15,8 +11,8 @@ import me.rhunk.snapenhance.Constants.VIEW_INJECTED_CODE import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader -import me.rhunk.snapenhance.features.impl.spying.MessageLogger import me.rhunk.snapenhance.features.impl.ui.menus.AbstractMenu +import me.rhunk.snapenhance.features.impl.ui.menus.ViewAppearanceHelper class ChatActionMenu : AbstractMenu() { @@ -26,27 +22,6 @@ class ChatActionMenu : AbstractMenu() { return false } - private fun applyButtonTheme(parent: View, button: Button) { - button.background = ColorDrawable(Color.WHITE) - button.setTextColor(Color.BLACK) - button.transformationMethod = null - val margin = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 20f, - Resources.getSystem().displayMetrics - ).toInt() - val params = MarginLayoutParams(parent.layoutParams) - params.setMargins(margin, 5, margin, 5) - params.marginEnd = margin - params.marginStart = margin - button.layoutParams = params - button.height = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 50f, - Resources.getSystem().displayMetrics - ).toInt() - } - @SuppressLint("SetTextI18n") fun inject(viewGroup: ViewGroup) { val parent = viewGroup.parent.parent as ViewGroup @@ -64,9 +39,23 @@ class ChatActionMenu : AbstractMenu() { ) ) } + + val injectButton = { button: Button -> + ViewAppearanceHelper.applyTheme(button, parent.width, hasRadius = true) + + with(button) { + layoutParams = MarginLayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(40, 0, 40, 15) + } + parent.addView(this) + } + } + if (context.config.bool(ConfigProperty.CHAT_DOWNLOAD_CONTEXT_MENU)) { - parent.addView(Button(viewGroup.context).apply { - applyButtonTheme(parent, this) + injectButton(Button(viewGroup.context).apply { text = this@ChatActionMenu.context.translation.get("chat_action_menu.preview_button") setOnClickListener { closeActionMenu() @@ -74,8 +63,7 @@ class ChatActionMenu : AbstractMenu() { } }) - parent.addView(Button(viewGroup.context).apply { - applyButtonTheme(parent, this) + injectButton(Button(viewGroup.context).apply { text = this@ChatActionMenu.context.translation.get("chat_action_menu.download_button") setOnClickListener { closeActionMenu() @@ -90,18 +78,17 @@ class ChatActionMenu : AbstractMenu() { //delete logged message button if (context.config.bool(ConfigProperty.MESSAGE_LOGGER)) { - val downloadButton = Button(viewGroup.context) - applyButtonTheme(parent, downloadButton) - downloadButton.text = context.translation.get("chat_action_menu.delete_logged_message_button") - downloadButton.setOnClickListener { - closeActionMenu() - context.executeAsync { - with(context.feature(Messaging::class)) { - context.feature(MessageLogger::class).deleteMessage(lastOpenedConversationUUID.toString(), lastFocusedMessageId) + injectButton(Button(viewGroup.context).apply { + text = this@ChatActionMenu.context.translation.get("chat_action_menu.delete_logged_message_button") + setOnClickListener { + closeActionMenu() + this@ChatActionMenu.context.executeAsync { + with(this@ChatActionMenu.context.feature(Messaging::class)) { + context.feature(me.rhunk.snapenhance.features.impl.spying.MessageLogger::class).deleteMessage(lastOpenedConversationUUID.toString(), lastFocusedMessageId) + } } } - } - parent.addView(downloadButton) + }) } } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/FriendFeedInfoMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/FriendFeedInfoMenu.kt @@ -5,26 +5,35 @@ import android.app.AlertDialog import android.content.Context import android.content.DialogInterface import android.content.res.Resources +import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.view.Gravity +import android.view.MotionEvent import android.view.View +import android.view.ViewGroup import android.widget.Button import android.widget.CompoundButton +import android.widget.LinearLayout import android.widget.Switch import android.widget.Toast import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.data.ContentType +import me.rhunk.snapenhance.data.wrapper.impl.FriendActionButton import me.rhunk.snapenhance.database.objects.ConversationMessage import me.rhunk.snapenhance.database.objects.FriendInfo import me.rhunk.snapenhance.database.objects.UserConversationLink import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload -import me.rhunk.snapenhance.features.impl.tweaks.AntiAutoSave import me.rhunk.snapenhance.features.impl.spying.StealthMode +import me.rhunk.snapenhance.features.impl.tweaks.AntiAutoSave import me.rhunk.snapenhance.features.impl.ui.menus.AbstractMenu -import me.rhunk.snapenhance.features.impl.ui.menus.ViewAppearanceHelper.applyTheme +import me.rhunk.snapenhance.features.impl.ui.menus.ViewAppearanceHelper import java.net.HttpURLConnection import java.net.URL import java.text.DateFormat @@ -164,77 +173,203 @@ class FriendFeedInfoMenu : AbstractMenu() { builder.show() } - private fun createToggleFeature(viewModel: View, viewConsumer: ((View) -> Unit), text: String, isChecked: () -> Boolean, toggle: (Boolean) -> Unit) { - val switch = Switch(viewModel.context) + private fun createEmojiDrawable(text: String, width: Int, height: Int, textSize: Float, disabled: Boolean = false): Drawable { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val paint = Paint() + paint.textSize = textSize + paint.color = Color.BLACK + paint.textAlign = Paint.Align.CENTER + canvas.drawText(text, width / 2f, height.toFloat() - paint.descent(), paint) + if (disabled) { + paint.color = Color.RED + paint.strokeWidth = 5f + canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), paint) + } + return BitmapDrawable(context.resources, bitmap) + } + + private fun getCurrentConversationId(): Pair<String, String?> { + val messaging = context.feature(Messaging::class) + var focusedConversationTargetUser: String? = null + val conversationId = if (messaging.lastFetchConversationUUID == null) { + focusedConversationTargetUser = messaging.lastFetchConversationUserUUID.toString() + val conversation: UserConversationLink = context.database.getDMConversationIdFromUserId(focusedConversationTargetUser) ?: throw IllegalStateException("No conversation found") + conversation.client_conversation_id!!.trim().lowercase() + } else { + messaging.lastFetchConversationUUID.toString() + } + + return Pair(conversationId, focusedConversationTargetUser) + } + + private fun createToggleFeature(viewConsumer: ((View) -> Unit), text: String, isChecked: () -> Boolean, toggle: (Boolean) -> Unit) { + val switch = Switch(context.androidContext) switch.text = context.translation.get(text) switch.isChecked = isChecked() - applyTheme(viewModel, switch) + ViewAppearanceHelper.applyTheme(switch) switch.setOnCheckedChangeListener { _: CompoundButton?, checked: Boolean -> toggle(checked) } viewConsumer(switch) } - @SuppressLint("SetTextI18n", "UseSwitchCompatOrMaterialCode", "DefaultLocale") + fun injectOldButtons(viewModel: View, viewConsumer: ((View) -> Unit)) { + + } + + @SuppressLint("SetTextI18n", "UseSwitchCompatOrMaterialCode", "DefaultLocale", "InflateParams", + "DiscouragedApi", "ClickableViewAccessibility" + ) fun inject(viewModel: View, viewConsumer: ((View) -> Unit)) { - val messaging = context.feature(Messaging::class) - var focusedConversationTargetUser: String? = null - val conversationId: String - if (messaging.lastFetchConversationUserUUID != null) { - focusedConversationTargetUser = messaging.lastFetchConversationUserUUID.toString() - val conversation: UserConversationLink = context.database.getDMConversationIdFromUserId(focusedConversationTargetUser) ?: return - conversationId = conversation.client_conversation_id!!.trim().lowercase() - } else { - conversationId = messaging.lastFetchConversationUUID.toString() - } - - //preview button - val previewButton = Button(viewModel.context) - previewButton.text = context.translation.get("friend_menu_option.preview") - applyTheme(viewModel, previewButton) - val finalFocusedConversationTargetUser = focusedConversationTargetUser - previewButton.setOnClickListener { - showPreview( - finalFocusedConversationTargetUser, - conversationId, - previewButton.context - ) - } - - //stealth switch - val stealthSwitch = Switch(viewModel.context) - stealthSwitch.text = context.translation.get("friend_menu_option.stealth_mode") - stealthSwitch.isChecked = context.feature(StealthMode::class).isStealth(conversationId) - applyTheme(viewModel, stealthSwitch) - stealthSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - context.feature(StealthMode::class).setStealth( - conversationId, - isChecked - ) - } - - run { - val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run - if (context.config.bool(ConfigProperty.DOWNLOAD_BLACKLIST)) { - createToggleFeature(viewModel, - viewConsumer, - "friend_menu_option.anti_auto_download", - { context.feature(AntiAutoDownload::class).isUserIgnored(userId) }, - { context.feature(AntiAutoDownload::class).setUserIgnored(userId, it) } + val modContext = context + + val friendFeedMenuOptions = context.config.options(ConfigProperty.FRIEND_FEED_MENU_BUTTONS) + if (friendFeedMenuOptions.none { it.value }) return + + val (conversationId, focusedConversationTargetUser) = getCurrentConversationId() + + if (!context.config.bool(ConfigProperty.ENABLE_FRIEND_FEED_MENU_BAR)) { + //preview button + val previewButton = Button(viewModel.context).apply { + text = modContext.translation.get("friend_menu_option.preview") + ViewAppearanceHelper.applyTheme(this, viewModel.width) + setOnClickListener { + showPreview( + focusedConversationTargetUser, + conversationId, + context + ) + } + } + + //stealth switch + val stealthSwitch = Switch(viewModel.context).apply { + text = modContext.translation.get("friend_menu_option.stealth_mode") + isChecked = modContext.feature(StealthMode::class).isStealth(conversationId) + ViewAppearanceHelper.applyTheme(this) + setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + modContext.feature(StealthMode::class).setStealth( + conversationId, + isChecked + ) + } + } + + run { + val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run + if (friendFeedMenuOptions["auto_download_blacklist"] == true) { + createToggleFeature(viewConsumer, + "friend_menu_option.auto_download_blacklist", + { context.feature(AntiAutoDownload::class).isUserIgnored(userId) }, + { 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) + viewConsumer(previewButton) + return + } + + val menuButtonBar = LinearLayout(viewModel.context).apply { + layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER + } + + fun createActionButton(icon: String, isDisabled: Boolean? = null, onClick: (Boolean) -> Unit) { + //FIXME: hardcoded values + menuButtonBar.addView(LinearLayout(viewModel.context).apply { + layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f) + gravity = Gravity.CENTER + setPadding(0, 20, 0, 20) + isClickable = true + scaleX = 1.1f + scaleY = 1.1f + + var isLineThrough = isDisabled ?: false + FriendActionButton.new(viewModel.context).apply { + fun setLineThrough(value: Boolean) { + setIconDrawable(createEmojiDrawable(icon, 60, 60, 50f, if (isDisabled == null) false else value)) + } + setLineThrough(isLineThrough) + (instanceNonNull() as View).apply { + layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + setMargins(0, 60, 0, 60) + } + setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_UP) { + isLineThrough = !isLineThrough + onClick(isLineThrough) + setLineThrough(isLineThrough) + } + false + } + } + + }.also { addView(it.instanceNonNull() as View) } + + }) + } + + if (friendFeedMenuOptions["auto_download_blacklist"] == true) { + run { + val userId = + context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId + ?: return@run + createActionButton( + "\u2B07\uFE0F", + isDisabled = !context.feature(AntiAutoDownload::class).isUserIgnored(userId) + ) { + context.feature(AntiAutoDownload::class).setUserIgnored(userId, !it) + } + } + } + + if (friendFeedMenuOptions["anti_auto_save"] == true) { + //diskette + createActionButton("\uD83D\uDCAC", + isDisabled = !context.feature(AntiAutoSave::class) + .isConversationIgnored(conversationId) + ) { + context.feature(AntiAutoSave::class).setConversationIgnored(conversationId, !it) + } + } + + + if (friendFeedMenuOptions["stealth_mode"] == true) { + //eyes + createActionButton( + "\uD83D\uDC7B", + isDisabled = !context.feature(StealthMode::class).isStealth(conversationId) + ) { isChecked -> + context.feature(StealthMode::class).setStealth( + conversationId, + !isChecked ) } + } - if (context.config.bool(ConfigProperty.ANTI_AUTO_SAVE)) { - createToggleFeature(viewModel, - viewConsumer, - "friend_menu_option.anti_auto_save", - { context.feature(AntiAutoSave::class).isConversationIgnored(conversationId) }, - { context.feature(AntiAutoSave::class).setConversationIgnored(conversationId, it) } + if (friendFeedMenuOptions["conversation_info"] == true) { + //user + createActionButton("\uD83D\uDC64") { + showPreview( + focusedConversationTargetUser, + conversationId, + viewModel.context ) } } - viewConsumer(stealthSwitch) - viewConsumer(previewButton) + viewConsumer(menuButtonBar) } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/OperaContextActionMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/OperaContextActionMenu.kt @@ -72,7 +72,7 @@ class OperaContextActionMenu : AbstractMenu() { val button = Button(childView.getContext()) button.text = context.translation.get("opera_context_menu.download") button.setOnClickListener { context.feature(MediaDownloader::class).downloadLastOperaMediaAsync() } - applyTheme(linearLayout, button) + applyTheme(button) linearLayout.addView(button) (childView as ViewGroup).addView(linearLayout, 0) } catch (e: Throwable) { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/ui/menus/impl/SettingsMenu.kt @@ -24,10 +24,10 @@ import me.rhunk.snapenhance.features.impl.ui.menus.ViewAppearanceHelper class SettingsMenu : AbstractMenu() { @SuppressLint("ClickableViewAccessibility") - private fun createCategoryTitle(viewModel: View, key: String): TextView { - val categoryText = TextView(viewModel.context) + private fun createCategoryTitle(key: String): TextView { + val categoryText = TextView(context.androidContext) categoryText.text = context.translation.get(key) - ViewAppearanceHelper.applyTheme(viewModel, categoryText) + ViewAppearanceHelper.applyTheme(categoryText) categoryText.textSize = 20f categoryText.typeface = categoryText.typeface?.let { Typeface.create(it, Typeface.BOLD) } categoryText.setOnTouchListener { _, _ -> true } @@ -35,7 +35,7 @@ class SettingsMenu : AbstractMenu() { } @SuppressLint("SetTextI18n") - private fun createPropertyView(viewModel: View, property: ConfigProperty): View { + private fun createPropertyView(property: ConfigProperty): View { val propertyName = context.translation.get(property.nameKey) val updateButtonText: (TextView, String) -> Unit = { textView, text -> textView.text = "$propertyName${if (text.isEmpty()) "" else ": $text"}" @@ -57,10 +57,10 @@ class SettingsMenu : AbstractMenu() { } val textEditor: ((String) -> Unit) -> Unit = { updateValue -> - val builder = AlertDialog.Builder(viewModel.context) + val builder = AlertDialog.Builder(context.mainActivity!!) builder.setTitle(propertyName) - val input = EditText(viewModel.context) + val input = EditText(context.androidContext) input.inputType = InputType.TYPE_CLASS_TEXT input.setText(property.valueContainer.value().toString()) @@ -75,12 +75,12 @@ class SettingsMenu : AbstractMenu() { val resultView: View = when (property.valueContainer) { is ConfigStringValue -> { - val textView = TextView(viewModel.context) + val textView = TextView(context.androidContext) updateButtonText(textView, property.valueContainer.let { if (it.isHidden) it.hiddenValue() else it.value() }) - ViewAppearanceHelper.applyTheme(viewModel, textView) + ViewAppearanceHelper.applyTheme(textView) textView.setOnClickListener { textEditor { value -> property.valueContainer.writeFrom(value) @@ -93,7 +93,7 @@ class SettingsMenu : AbstractMenu() { textView } is ConfigIntegerValue -> { - val button = Button(viewModel.context) + val button = Button(context.androidContext) updateButtonText(button, property.valueContainer.value().toString()) button.setOnClickListener { textEditor { value -> @@ -105,25 +105,25 @@ class SettingsMenu : AbstractMenu() { } } } - ViewAppearanceHelper.applyTheme(viewModel, button) + ViewAppearanceHelper.applyTheme(button) button } is ConfigStateValue -> { - val switch = Switch(viewModel.context) + val switch = Switch(context.androidContext) switch.text = propertyName switch.isChecked = property.valueContainer.value() switch.setOnCheckedChangeListener { _, isChecked -> property.valueContainer.writeFrom(isChecked.toString()) } - ViewAppearanceHelper.applyTheme(viewModel, switch) + ViewAppearanceHelper.applyTheme(switch) switch } is ConfigStateSelection -> { - val button = Button(viewModel.context) + val button = Button(context.androidContext) updateLocalizedText(button, property.valueContainer.value()) button.setOnClickListener {_ -> - val builder = AlertDialog.Builder(viewModel.context) + val builder = AlertDialog.Builder(context.mainActivity!!) builder.setTitle(propertyName) builder.setSingleChoiceItems( @@ -142,15 +142,15 @@ class SettingsMenu : AbstractMenu() { builder.show() } - ViewAppearanceHelper.applyTheme(viewModel, button) + ViewAppearanceHelper.applyTheme(button) button } is ConfigStateListValue -> { - val button = Button(viewModel.context) + val button = Button(context.androidContext) updateButtonText(button, "(${property.valueContainer.value().count { it.value }})") button.setOnClickListener {_ -> - val builder = AlertDialog.Builder(viewModel.context) + val builder = AlertDialog.Builder(context.mainActivity!!) builder.setTitle(propertyName) val sortedStates = property.valueContainer.value().toSortedMap() @@ -173,11 +173,11 @@ class SettingsMenu : AbstractMenu() { builder.show() } - ViewAppearanceHelper.applyTheme(viewModel, button) + ViewAppearanceHelper.applyTheme(button) button } else -> { - TextView(viewModel.context) + TextView(context.androidContext) } } return resultView @@ -203,7 +203,7 @@ class SettingsMenu : AbstractMenu() { } val titleText = TextView(viewModel.context) titleText.text = versionTextBuilder.toString() - ViewAppearanceHelper.applyTheme(viewModel, titleText) + ViewAppearanceHelper.applyTheme(titleText) titleText.textSize = 18f titleText.minHeight = 80 * versionTextBuilder.chars().filter { ch: Int -> ch == '\n'.code } .count().coerceAtLeast(2).toInt() @@ -216,7 +216,7 @@ class SettingsMenu : AbstractMenu() { button.setOnClickListener { _ -> it.run() } - ViewAppearanceHelper.applyTheme(viewModel, button) + ViewAppearanceHelper.applyTheme(button) button } } @@ -224,9 +224,9 @@ class SettingsMenu : AbstractMenu() { context.config.entries().groupBy { it.key.category }.forEach { (category, value) -> - addView(createCategoryTitle(viewModel, category.key)) + addView(createCategoryTitle(category.key)) value.filter { it.key.shouldAppearInSettings }.forEach { (property, _) -> - addView(createPropertyView(viewModel, property)) + addView(createPropertyView(property)) actions.find { pair -> pair.first.dependsOnProperty == property}?.let { pair -> addView(pair.second()) }