commit 0389157471118adc366f0e813c05dc2ea967291c parent 651c69d22f5bb20f036f11f06dd8512b5058e33d Author: rhunk <101876869+rhunk@users.noreply.github.com> Date: Fri, 26 May 2023 16:23:16 +0200 feat: config & notification filter Diffstat:
12 files changed, 256 insertions(+), 98 deletions(-)
diff --git a/app/src/main/assets/lang/en_US.json b/app/src/main/assets/lang/en_US.json @@ -18,7 +18,8 @@ "save_folder": "Save Folder", "prevent_read_receipts": "Prevent Read Receipts", "hide_bitmoji_presence": "Hide Bitmoji Presence", - "show_message_content": "Show Message Content", + "show_message_content_in_notifications": "Show Message Content In Notifications", + "notification_filter": "Notification Filter", "message_logger": "Message Logger", "unlimited_snap_view_time": "Unlimited Snap View Time", "auto_download_snaps": "Auto Download Snaps", @@ -49,7 +50,16 @@ "new_map_ui": "New Map UI", "use_download_manager": "Use Android Download Manager" }, - + + "option": { + "property": { + "notification_filter": { + "chat": "Chat", + "snap": "Snap", + "typing": "Typing" + } + } + }, "friend_menu_option": { "preview": "Preview", diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigAccessor.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigAccessor.kt @@ -1,58 +1,58 @@ package me.rhunk.snapenhance.config open class ConfigAccessor( - private val configMap: MutableMap<ConfigProperty, Any?> + private val configMap: MutableMap<ConfigProperty, ConfigValue<*>> = mutableMapOf() ) { fun bool(key: ConfigProperty): Boolean { - return get(key) as Boolean + return get(key).value() as Boolean } fun int(key: ConfigProperty): Int { - return get(key) as Int + return get(key).value() as Int } fun string(key: ConfigProperty): String { - return get(key) as String + return get(key).value() as String } fun double(key: ConfigProperty): Double { - return get(key) as Double + return get(key).value() as Double } fun float(key: ConfigProperty): Float { - return get(key) as Float + return get(key).value() as Float } fun long(key: ConfigProperty): Long { - return get(key) as Long + return get(key).value() as Long } fun short(key: ConfigProperty): Short { - return get(key) as Short + return get(key).value() as Short } fun byte(key: ConfigProperty): Byte { - return get(key) as Byte + return get(key).value() as Byte } fun char(key: ConfigProperty): Char { - return get(key) as Char + return get(key).value() as Char } @Suppress("UNCHECKED_CAST") - fun <T> list(key: ConfigProperty): List<T> { - return get(key) as List<T> + fun options(key: ConfigProperty): Map<String, Boolean> { + return get(key).value() as Map<String, Boolean> } - fun get(key: ConfigProperty): Any? { - return configMap[key] + fun get(key: ConfigProperty): ConfigValue<*> { + return configMap[key]!! } - fun set(key: ConfigProperty, value: Any?) { + fun set(key: ConfigProperty, value: ConfigValue<*>) { configMap[key] = value } - fun entries(): Set<Map.Entry<ConfigProperty, Any?>> { + fun entries(): Set<Map.Entry<ConfigProperty, ConfigValue<*>>> { return configMap.entries } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigProperty.kt @@ -1,174 +1,192 @@ package me.rhunk.snapenhance.config import android.os.Environment +import me.rhunk.snapenhance.config.impl.ConfigIntegerValue +import me.rhunk.snapenhance.config.impl.ConfigStateListValue +import me.rhunk.snapenhance.config.impl.ConfigStateValue +import me.rhunk.snapenhance.config.impl.ConfigStringValue import java.io.File enum class ConfigProperty( val nameKey: String, val descriptionKey: String, val category: ConfigCategory, - val defaultValue: Any + val valueContainer: ConfigValue<*> ) { SAVE_FOLDER( "property.save_folder", "description.save_folder", ConfigCategory.GENERAL, - File( + ConfigStringValue(File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath + "/Snapchat", "SnapEnhance" - ).absolutePath + ).absolutePath) ), PREVENT_READ_RECEIPTS( "property.prevent_read_receipts", "description.prevent_read_receipts", ConfigCategory.SPY, - false + ConfigStateValue(false) ), HIDE_BITMOJI_PRESENCE( "property.hide_bitmoji_presence", "description.hide_bitmoji_presence", ConfigCategory.SPY, - false + ConfigStateValue(false) ), - SHOW_MESSAGE_CONTENT( - "property.show_message_content", - "description.show_message_content", + SHOW_MESSAGE_CONTENT_IN_NOTIFICATIONS( + "property.show_message_content_in_notifications", + "description.show_message_content_in_notifications", ConfigCategory.SPY, - false + ConfigStateValue(false) ), - MESSAGE_LOGGER("property.message_logger", "description.message_logger", ConfigCategory.SPY, false), - UNLIMITED_SNAP_VIEW_TIME("property.unlimited_snap_view_time", "description.unlimited_snap_view_time", ConfigCategory.SPY, false), + NOTIFICATION_FILTER( + "property.notification_filter", + "description.notification_filter", + ConfigCategory.SPY, + ConfigStateListValue( + listOf("snap", "chat", "typing"), + mutableMapOf( + "snap" to true, + "chat" to true, + "typing" to true + ) + ) + ), + + MESSAGE_LOGGER("property.message_logger", "description.message_logger", ConfigCategory.SPY, ConfigStateValue(false)), + UNLIMITED_SNAP_VIEW_TIME("property.unlimited_snap_view_time", "description.unlimited_snap_view_time", ConfigCategory.SPY, ConfigStateValue(false)), AUTO_DOWNLOAD_SNAPS( "property.auto_download_snaps", "description.auto_download_snaps", ConfigCategory.MEDIA_DOWNLOADER, - true + ConfigStateValue(false) ), AUTO_DOWNLOAD_STORIES( "property.auto_download_stories", "description.auto_download_stories", ConfigCategory.MEDIA_DOWNLOADER, - false + ConfigStateValue(false) ), AUTO_DOWNLOAD_PUBLIC_STORIES( "property.auto_download_public_stories", "description.auto_download_public_stories", ConfigCategory.MEDIA_DOWNLOADER, - false + ConfigStateValue(false) ), AUTO_DOWNLOAD_SPOTLIGHT( "property.auto_download_spotlight", "description.auto_download_spotlight", ConfigCategory.MEDIA_DOWNLOADER, - false + ConfigStateValue(false) ), OVERLAY_MERGE( "property.overlay_merge", "description.overlay_merge", ConfigCategory.MEDIA_DOWNLOADER, - true + ConfigStateValue(false) ), DOWNLOAD_INCHAT_SNAPS( "property.download_inchat_snaps", "description.download_inchat_snaps", ConfigCategory.MEDIA_DOWNLOADER, - true + ConfigStateValue(false) ), ANTI_DOWNLOAD_BUTTON( "property.anti_auto_download_button", "description.anti_auto_download_button", ConfigCategory.MEDIA_DOWNLOADER, - false + ConfigStateValue(false) ), - DISABLE_METRICS("property.disable_metrics", "description.disable_metrics", ConfigCategory.PRIVACY, true), + DISABLE_METRICS("property.disable_metrics", "description.disable_metrics", ConfigCategory.PRIVACY, ConfigStateValue(false)), PREVENT_SCREENSHOT_NOTIFICATIONS( "property.prevent_screenshot_notifications", "description.prevent_screenshot_notifications", ConfigCategory.PRIVACY, - true + ConfigStateValue(false) ), PREVENT_STATUS_NOTIFICATIONS( "property.prevent_status_notifications", "description.prevent_status_notifications", ConfigCategory.PRIVACY, - true + ConfigStateValue(false) ), ANONYMOUS_STORY_VIEW( "property.anonymous_story_view", "description.anonymous_story_view", ConfigCategory.PRIVACY, - false + ConfigStateValue(false) ), HIDE_TYPING_NOTIFICATION( "property.hide_typing_notification", "description.hide_typing_notification", ConfigCategory.PRIVACY, - false + ConfigStateValue(false) ), - MENU_SLOT_ID("property.menu_slot_id", "description.menu_slot_id", ConfigCategory.UI, 1), + MENU_SLOT_ID("property.menu_slot_id", "description.menu_slot_id", ConfigCategory.UI, ConfigIntegerValue(1)), MESSAGE_PREVIEW_LENGTH( "property.message_preview_length", "description.message_preview_length", ConfigCategory.UI, - 20 + ConfigIntegerValue(20) ), EXTERNAL_MEDIA_AS_SNAP( "property.external_media_as_snap", "description.external_media_as_snap", ConfigCategory.EXTRAS, - false + ConfigStateValue(false) ), - AUTO_SAVE("property.auto_save", "description.auto_save", ConfigCategory.EXTRAS, false), - ANTI_AUTO_SAVE("property.anti_auto_save", "description.anti_auto_save", ConfigCategory.EXTRAS, false), - SNAPCHAT_PLUS("property.snapchat_plus", "description.snapchat_plus", ConfigCategory.EXTRAS, false), + AUTO_SAVE("property.auto_save", "description.auto_save", ConfigCategory.EXTRAS, ConfigStateValue(false)), + ANTI_AUTO_SAVE("property.anti_auto_save", "description.anti_auto_save", ConfigCategory.EXTRAS, ConfigStateValue(false)), + SNAPCHAT_PLUS("property.snapchat_plus", "description.snapchat_plus", ConfigCategory.EXTRAS, ConfigStateValue(false)), REMOVE_VOICE_RECORD_BUTTON( "property.remove_voice_record_button", "description.remove_voice_record_button", ConfigCategory.TWEAKS, - false + ConfigStateValue(false) ), REMOVE_STICKERS_BUTTON( "property.remove_stickers_button", "description.remove_stickers_button", ConfigCategory.TWEAKS, - false + ConfigStateValue(false) ), REMOVE_COGNAC_BUTTON( "property.remove_cognac_button", "description.remove_cognac_button", ConfigCategory.TWEAKS, - false + ConfigStateValue(false) ), REMOVE_CALL_BUTTONS( "property.remove_call_buttons", "description.remove_call_buttons", ConfigCategory.TWEAKS, - false + ConfigStateValue(false) ), DISABLE_SNAP_SPLITTING( "property.disable_snap_splitting", "description.disable_snap_splitting", ConfigCategory.TWEAKS, - false + ConfigStateValue(false) ), - BLOCK_ADS("property.block_ads", "description.block_ads", ConfigCategory.TWEAKS, false), + BLOCK_ADS("property.block_ads", "description.block_ads", ConfigCategory.TWEAKS, ConfigStateValue(false)), STREAK_EXPIRATION_INFO( "property.streak_expiration_info", "description.streakexpirationinfo", ConfigCategory.TWEAKS, - false + ConfigStateValue(false) ), - NEW_MAP_UI("property.new_map_ui", "description.new_map_ui", ConfigCategory.TWEAKS, false), + NEW_MAP_UI("property.new_map_ui", "description.new_map_ui", ConfigCategory.TWEAKS, ConfigStateValue(false)), USE_DOWNLOAD_MANAGER( "property.use_download_manager", "description.use_download_manager", ConfigCategory.EXPERIMENTAL, - false + ConfigStateValue(false) ); companion object { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigValue.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/ConfigValue.kt @@ -0,0 +1,7 @@ +package me.rhunk.snapenhance.config + +abstract class ConfigValue<T> { + abstract fun value(): T + abstract fun write(): String + abstract fun read(value: String) +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigIntegerValue.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigIntegerValue.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.config.impl + +import me.rhunk.snapenhance.config.ConfigValue + +class ConfigIntegerValue( + var value: Int +) : ConfigValue<Int>() { + override fun value() = value + + override fun write(): String { + return value.toString() + } + + override fun read(value: String) { + this.value = value.toInt() + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigStateListValue.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigStateListValue.kt @@ -0,0 +1,27 @@ +package me.rhunk.snapenhance.config.impl + +import me.rhunk.snapenhance.config.ConfigValue + +class ConfigStateListValue( + private val keys: List<String>, + var states: MutableMap<String, Boolean> = mutableMapOf() +) : ConfigValue<Map<String, Boolean>>() { + override fun value() = states + + fun value(key: String) = states[key] ?: false + + override fun write(): String { + return keys.joinToString("|") { "$it:${states[it]}" } + } + + override fun read(value: String) { + value.split("|").forEach { + val (key, state) = it.split(":") + states[key] = state.toBoolean() + } + } + + override fun toString(): String { + return states.filter { it.value }.keys.joinToString(", ") { it } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigStateValue.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigStateValue.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.config.impl + +import me.rhunk.snapenhance.config.ConfigValue + +class ConfigStateValue( + var value: Boolean +) : ConfigValue<Boolean>() { + override fun value() = value + + override fun write(): String { + return value.toString() + } + + override fun read(value: String) { + this.value = value.toBoolean() + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigStringValue.kt b/app/src/main/kotlin/me/rhunk/snapenhance/config/impl/ConfigStringValue.kt @@ -0,0 +1,17 @@ +package me.rhunk.snapenhance.config.impl + +import me.rhunk.snapenhance.config.ConfigValue + +class ConfigStringValue( + var value: String = "" +) : ConfigValue<String>() { + override fun value() = value + + override fun write(): String { + return value + } + + override fun read(value: String) { + this.value = value + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/extras/Notifications.kt b/app/src/main/kotlin/me/rhunk/snapenhance/features/impl/extras/Notifications.kt @@ -154,11 +154,22 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN }.clear() } + private fun shouldIgnoreNotification(type: String): Boolean { + Logger.debug("notification type: $type") + val states = context.config.options(ConfigProperty.NOTIFICATION_FILTER) + + states["snap"]?.let { if (type.endsWith("SNAP") && it) return true } + states["chat"]?.let { if (type.endsWith("CHAT") && it) return true } + states["typing"]?.let { if (type.endsWith("TYPING") && it) return true } + + return false + } + override fun init() { val fetchConversationWithMessagesCallback = context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") - Hooker.hook(notifyAsUserMethod, HookStage.BEFORE, { context.config.bool(ConfigProperty.SHOW_MESSAGE_CONTENT) }) { - val notificationData = NotificationData(it.argNullable(0), it.arg(1), it.arg(2), it.arg(3)) + Hooker.hook(notifyAsUserMethod, HookStage.BEFORE) { param -> + val notificationData = NotificationData(param.argNullable(0), param.arg(1), param.arg(2), param.arg(3)) val extras: Bundle = notificationData.notification.extras.getBundle("system_notification_extras")?: return@hook @@ -166,6 +177,13 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN val notificationType = extras.getString("notification_type") ?: return@hook val conversationId = extras.getString("conversation_id") ?: return@hook + if (shouldIgnoreNotification(notificationType)) { + param.setResult(null) + return@hook + } + + if (!context.config.bool(ConfigProperty.SHOW_MESSAGE_CONTENT_IN_NOTIFICATIONS)) return@hook + if (!notificationType.endsWith("CHAT") && !notificationType.endsWith("SNAP")) return@hook val conversationManager: Any = context.feature(Messaging::class).conversationManager @@ -181,7 +199,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN }.build() fetchConversationWithMessagesMethod.invoke(conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), callback) - it.setResult(null) + param.setResult(null) } Hooker.hook(cancelAsUserMethod, HookStage.BEFORE) { param -> 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 @@ -11,6 +11,10 @@ import android.widget.TextView import me.rhunk.snapenhance.BuildConfig import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.config.ConfigProperty +import me.rhunk.snapenhance.config.impl.ConfigIntegerValue +import me.rhunk.snapenhance.config.impl.ConfigStateListValue +import me.rhunk.snapenhance.config.impl.ConfigStateValue +import me.rhunk.snapenhance.config.impl.ConfigStringValue import me.rhunk.snapenhance.features.impl.ui.menus.AbstractMenu import me.rhunk.snapenhance.features.impl.ui.menus.ViewAppearanceHelper @@ -35,7 +39,7 @@ class SettingsMenu : AbstractMenu() { val input = EditText(viewModel.context) input.inputType = InputType.TYPE_CLASS_TEXT - input.setText(context.config.get(property).toString()) + input.setText(property.valueContainer.value().toString()) builder.setView(input) builder.setPositiveButton("OK") { _, _ -> @@ -46,34 +50,26 @@ class SettingsMenu : AbstractMenu() { builder.show() } - val resultView: View = when (property.defaultValue) { - is String -> { + val resultView: View = when (property.valueContainer) { + is ConfigStringValue -> { val textView = TextView(viewModel.context) - updateButtonText(textView, context.config.string(property)) + updateButtonText(textView, property.valueContainer.value) ViewAppearanceHelper.applyTheme(viewModel, textView) textView.setOnClickListener { textEditor { value -> - context.config.set(property, value) + property.valueContainer.value = value updateButtonText(textView, value) } } textView } - is Number -> { + is ConfigIntegerValue -> { val button = Button(viewModel.context) - updateButtonText(button, context.config.get(property).toString()) + updateButtonText(button, property.valueContainer.value.toString()) button.setOnClickListener { textEditor { value -> runCatching { - context.config.set(property, when (property.defaultValue) { - is Int -> value.toInt() - is Double -> value.toDouble() - is Float -> value.toFloat() - is Long -> value.toLong() - is Short -> value.toShort() - is Byte -> value.toByte() - else -> throw IllegalArgumentException() - }) + property.valueContainer.value = value.toInt() updateButtonText(button, value) }.onFailure { context.shortToast("Invalid value") @@ -83,16 +79,44 @@ class SettingsMenu : AbstractMenu() { ViewAppearanceHelper.applyTheme(viewModel, button) button } - is Boolean -> { + is ConfigStateValue -> { val switch = Switch(viewModel.context) switch.text = context.translation.get(property.nameKey) - switch.isChecked = context.config.bool(property) + switch.isChecked = property.valueContainer.value switch.setOnCheckedChangeListener { _, isChecked -> - context.config.set(property, isChecked) + property.valueContainer.value = isChecked } ViewAppearanceHelper.applyTheme(viewModel, switch) switch } + is ConfigStateListValue -> { + val button = Button(viewModel.context) + updateButtonText(button, property.valueContainer.toString()) + + button.setOnClickListener {_ -> + val builder = AlertDialog.Builder(viewModel.context) + builder.setTitle(context.translation.get(property.nameKey)) + + val sortedStates = property.valueContainer.states.toSortedMap() + + builder.setMultiChoiceItems( + sortedStates.toSortedMap().map { context.translation.get("option." + property.nameKey + "." +it.key) }.toTypedArray(), + sortedStates.map { it.value }.toBooleanArray() + ) { _, which, isChecked -> + sortedStates.keys.toList()[which].let { key -> + property.valueContainer.states[key] = isChecked + } + } + + builder.setPositiveButton("OK") { _, _ -> + updateButtonText(button, property.valueContainer.toString()) + } + + builder.show() + } + ViewAppearanceHelper.applyTheme(viewModel, button) + button + } else -> { TextView(viewModel.context) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ConfigManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/ConfigManager.kt @@ -10,16 +10,12 @@ import me.rhunk.snapenhance.manager.Manager import java.nio.charset.StandardCharsets class ConfigManager( - private val context: ModContext, - config: MutableMap<ConfigProperty, Any?> = mutableMapOf() -) : ConfigAccessor(config), Manager { - - private val propertyList = ConfigProperty.sortedByCategory() + private val context: ModContext +) : ConfigAccessor(), Manager { override fun init() { - //generate default config - propertyList.forEach { key -> - set(key, key.defaultValue) + ConfigProperty.sortedByCategory().forEach { key -> + set(key, key.valueContainer) } if (!context.bridgeClient.isFileExists(BridgeFileType.CONFIG)) { @@ -44,16 +40,15 @@ class ConfigManager( String(configContent, StandardCharsets.UTF_8), JsonObject::class.java ) - propertyList.forEach { key -> - val value = context.gson.fromJson(configObject.get(key.name), key.defaultValue.javaClass) ?: key.defaultValue - set(key, value) + entries().forEach { (key, value) -> + value.read(configObject.get(key.name)?.asString ?: value.write()) } } fun writeConfig() { val configObject = JsonObject() - propertyList.forEach { key -> - configObject.add(key.name, context.gson.toJsonTree(get(key))) + entries().forEach { (key, value) -> + configObject.addProperty(key.name, value.write()) } context.bridgeClient.writeFile( BridgeFileType.CONFIG, diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/manager/impl/TranslationManager.kt @@ -1,5 +1,6 @@ package me.rhunk.snapenhance.manager.impl +import com.google.gson.JsonObject import com.google.gson.JsonParser import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.ModContext @@ -22,15 +23,17 @@ class TranslationManager( return } - translations.asJsonObject.entrySet().forEach { - if (it.value.isJsonPrimitive) { - translationMap[it.key] = it.value.asString - } - if (!it.value.isJsonObject) return@forEach - it.value.asJsonObject.entrySet().forEach { entry -> - translationMap["${it.key}.${entry.key}"] = entry.value.asString + fun scanObject(jsonObject: JsonObject, prefix: String = "") { + jsonObject.entrySet().forEach { + if (it.value.isJsonPrimitive) { + translationMap["$prefix${it.key}"] = it.value.asString + } + if (!it.value.isJsonObject) return@forEach + scanObject(it.value.asJsonObject, "$prefix${it.key}.") } } + + scanObject(translations) }