refactor: reorganize (#15)

* refactor: organization (1/?)

open for ideas and suggestions
TODO:
    • Merge UI Hide Elements
    • Remove Longitude and Latitude from the menu and integrate into the MapActivity
    • Move New Map UI to Experimental
    • Shorten „Show Message Content in Notification“

And possibly even more

* Merge hide UI Elements

bugfix(?): merge overlay

* add: download options

* add: config listeners

* fix(ci): debug

remove prod as it's not getting built anymore

* fix(messagelogger): clear database bug

* update locale, reorganize config

* add: set precise location dialog

* config: better notifications

---------

Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
auth 2023-06-04 01:53:05 +02:00 committed by GitHub
parent cfb0ba5d65
commit 85335dafa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 483 additions and 335 deletions

View File

@ -34,8 +34,3 @@ jobs:
with: with:
name: app-armv7-release name: app-armv7-release
path: app/build/outputs/apk/armv7/debug/*.apk path: app/build/outputs/apk/armv7/debug/*.apk
- name: Upload prod
uses: actions/upload-artifact@v3.1.2
with:
name: app-prod-release
path: app/build/outputs/apk/prod/debug/*.apk

5
.gitignore vendored
View File

@ -1,10 +1,9 @@
*.iml *.iml
.gradle .gradle
/local.properties local.properties
/.idea/ /.idea/
.DS_Store .DS_Store
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties

View File

@ -1,22 +1,16 @@
{ {
"category": { "category": {
"general": "General", "spying_privacy": "Spying & Privacy",
"spying": "Spying", "media_manager": "Media Manager",
"media_download": "Media Downloader", "ui_tweaks": "UI & Tweaks",
"privacy": "Privacy", "experimental_debugging": "Experimental"
"ui": "UI",
"extras": "Extras",
"tweaks": "Tweaks",
"location_spoof": "Location Spoof",
"experimental": "Experimental",
"debugging": "Debugging"
}, },
"action": { "action": {
"clean_cache": "Clean Cache", "clean_cache": "Clean Cache",
"clear_message_logger": "Clear Message Logger", "clear_message_logger": "Clear Message Logger",
"refresh_mappings": "Refresh Mappings", "refresh_mappings": "Refresh Mappings",
"open_map": "Pick a location on the map" "open_map": "Choose location on map"
}, },
"property": { "property": {
@ -24,16 +18,14 @@
"prevent_read_receipts": "Prevent Read Receipts", "prevent_read_receipts": "Prevent Read Receipts",
"hide_bitmoji_presence": "Hide Bitmoji Presence", "hide_bitmoji_presence": "Hide Bitmoji Presence",
"show_message_content_in_notifications": "Show Message Content In Notifications", "show_message_content_in_notifications": "Show Message Content In Notifications",
"better_notifications": "Better Notifications",
"notification_blacklist": "Notification Blacklist", "notification_blacklist": "Notification Blacklist",
"message_logger": "Message Logger", "message_logger": "Message Logger",
"unlimited_snap_view_time": "Unlimited Snap View Time", "unlimited_snap_view_time": "Unlimited Snap View Time",
"auto_download_snaps": "Auto Download Snaps", "auto_download_options": "Auto Download Options",
"auto_download_stories": "Auto Download Stories",
"auto_download_public_stories": "Auto Download Public Stories",
"auto_download_spotlight": "Auto Download Spotlight",
"download_options": "Download Options", "download_options": "Download Options",
"download_inchat_snaps": "Download Inchat Snaps", "chat_download_context_menu": "Enable Chat Download Context Menu",
"anti_auto_download_button": "Anti Auto Download Button", "auto_download_blacklist": "Auto Download Blacklist",
"disable_metrics": "Disable Metrics", "disable_metrics": "Disable Metrics",
"prevent_screenshot_notifications": "Prevent Screenshot Notifications", "prevent_screenshot_notifications": "Prevent Screenshot Notifications",
"prevent_status_notifications": "Prevent Status Notifications (Save to camera roll, missed calls)", "prevent_status_notifications": "Prevent Status Notifications (Save to camera roll, missed calls)",
@ -42,7 +34,7 @@
"menu_slot_id": "Friend Menu Slot ID", "menu_slot_id": "Friend Menu Slot ID",
"message_preview_length": "Message Preview Length", "message_preview_length": "Message Preview Length",
"gallery_media_send_override": "Gallery Media Send Override", "gallery_media_send_override": "Gallery Media Send Override",
"auto_save": "Auto Save", "auto_save_messages": "Auto Save Messages",
"anti_auto_save": "Anti Auto Save Button", "anti_auto_save": "Anti Auto Save Button",
"snapchat_plus": "Snapchat Plus", "snapchat_plus": "Snapchat Plus",
"disable_snap_splitting": "Disable Snap Splitting", "disable_snap_splitting": "Disable Snap Splitting",
@ -62,11 +54,17 @@
"meo_passcode_bypass": "My Eyes Only Passcode Bypass", "meo_passcode_bypass": "My Eyes Only Passcode Bypass",
"location_spoof": "Snapmap Location Spoofer", "location_spoof": "Snapmap Location Spoofer",
"latitude_value": "Latitude", "latitude_value": "Latitude",
"longitude_value": "Longitude" "longitude_value": "Longitude",
"hide_ui_elements": "Hide UI Elements"
}, },
"option": { "option": {
"property": { "property": {
"better_notifications": {
"chat": "Show chat messages",
"snap": "Show medias",
"reply_button": "Add reply button"
},
"download_options": { "download_options": {
"format_user_folder": "Create folder for each user", "format_user_folder": "Create folder for each user",
"format_hash": "Add a unique hash to the file path", "format_hash": "Add a unique hash to the file path",
@ -74,10 +72,46 @@
"format_date_time": "Add the date and time to the file path", "format_date_time": "Add the date and time to the file path",
"merge_overlay": "Merge Snap Image Overlays" "merge_overlay": "Merge Snap Image Overlays"
}, },
"auto_download_options": {
"friend_snaps": "Friend Snaps",
"friend_stories": "Friend Stories",
"public_stories": "Public Stories",
"spotlight": "Spotlight"
},
"auto_save_messages": {
"NOTE": "Audio Note",
"CHAT": "Chat",
"EXTERNAL_MEDIA": "External Media",
"SNAP": "Snap",
"STICKER": "Sticker"
},
"notification_blacklist": { "notification_blacklist": {
"chat": "Chat", "chat": "Chat",
"snap": "Snap", "snap": "Snap",
"typing": "Typing" "typing": "Typing"
},
"gallery_media_send_override": {
"OFF": "Off",
"NOTE": "Audio Note",
"SNAP": "Snap",
"LIVE_SNAP": "Snap with audio"
},
"media_quality_level": {
"LEVEL_NONE": "Level None",
"LEVEL_1": "Level 1",
"LEVEL_2": "Level 2",
"LEVEL_3": "Level 3",
"LEVEL_4": "Level 4",
"LEVEL_5": "Level 5",
"LEVEL_6": "Level 6",
"LEVEL_7": "Level 7",
"LEVEL_MAX": "Level Max"
},
"hide_ui_elements": {
"remove_call_buttons": "Remove Call Buttons",
"remove_cognac_button": "Remove Cognac Button",
"remove_stickers_button": "Remove Stickers Button",
"remove_voice_record_button": "Remove Voice Record Button"
} }
} }
}, },
@ -94,6 +128,12 @@
"preview": "Preview" "preview": "Preview"
}, },
"chat_action_menu": {
"preview_button": "Preview",
"download_button": "Download",
"delete_logged_message_button": "Delete Logged Message"
},
"opera_context_menu": { "opera_context_menu": {
"download": "Download Media" "download": "Download Media"
}, },

View File

@ -13,7 +13,7 @@ class MessageLoggerRequest(
override fun write(bundle: Bundle) { override fun write(bundle: Bundle) {
bundle.putString("action", action!!.name) bundle.putString("action", action!!.name)
bundle.putString("conversationId", conversationId) bundle.putString("conversationId", conversationId)
bundle.putLong("messageId", messageId!!) bundle.putLong("messageId", messageId ?: 0)
bundle.putByteArray("message", message) bundle.putByteArray("message", message)
} }

View File

@ -3,13 +3,8 @@ package me.rhunk.snapenhance.config
enum class ConfigCategory( enum class ConfigCategory(
val key: String val key: String
) { ) {
GENERAL("category.general"), SPYING_PRIVACY("category.spying_privacy"),
SPYING("category.spying"), MEDIA_MANAGEMENT("category.media_manager"),
MEDIA_DOWNLOADER("category.media_download"), UI_TWEAKS("category.ui_tweaks"),
PRIVACY("category.privacy"), EXPERIMENTAL_DEBUGGING("category.experimental_debugging");
UI("category.ui"),
EXTRAS("category.extras"),
TWEAKS("category.tweaks"),
LOCATION_SPOOF("category.location_spoof"),
EXPERIMENTAL("category.experimental");
} }

View File

@ -12,30 +12,45 @@ enum class ConfigProperty(
val nameKey: String, val nameKey: String,
val descriptionKey: String, val descriptionKey: String,
val category: ConfigCategory, val category: ConfigCategory,
val valueContainer: ConfigValue<*> val valueContainer: ConfigValue<*>,
val shouldAppearInSettings: Boolean = true
) { ) {
//SPYING AND PRIVACY
MESSAGE_LOGGER("property.message_logger",
"description.message_logger",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
PREVENT_READ_RECEIPTS( PREVENT_READ_RECEIPTS(
"property.prevent_read_receipts", "property.prevent_read_receipts",
"description.prevent_read_receipts", "description.prevent_read_receipts",
ConfigCategory.SPYING, ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false) ConfigStateValue(false)
), ),
HIDE_BITMOJI_PRESENCE( HIDE_BITMOJI_PRESENCE(
"property.hide_bitmoji_presence", "property.hide_bitmoji_presence",
"description.hide_bitmoji_presence", "description.hide_bitmoji_presence",
ConfigCategory.SPYING, ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false) ConfigStateValue(false)
), ),
SHOW_MESSAGE_CONTENT_IN_NOTIFICATIONS( BETTER_NOTIFICATIONS(
"property.show_message_content_in_notifications", "property.better_notifications",
"description.show_message_content_in_notifications", "description.better_notifications",
ConfigCategory.SPYING, ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false) ConfigStateListValue(
listOf("snap", "chat", "reply_button"),
mutableMapOf(
"snap" to false,
"chat" to false,
"reply_button" to false
)
)
), ),
NOTIFICATION_BLACKLIST( NOTIFICATION_BLACKLIST(
"property.notification_blacklist", "property.notification_blacklist",
"description.notification_blacklist", "description.notification_blacklist",
ConfigCategory.SPYING, ConfigCategory.SPYING_PRIVACY,
ConfigStateListValue( ConfigStateListValue(
listOf("snap", "chat", "typing"), listOf("snap", "chat", "typing"),
mutableMapOf( mutableMapOf(
@ -45,43 +60,68 @@ enum class ConfigProperty(
) )
) )
), ),
DISABLE_METRICS("property.disable_metrics",
MESSAGE_LOGGER("property.message_logger", "description.message_logger", ConfigCategory.SPYING, ConfigStateValue(false)), "description.disable_metrics",
UNLIMITED_SNAP_VIEW_TIME("property.unlimited_snap_view_time", "description.unlimited_snap_view_time", ConfigCategory.SPYING, ConfigStateValue(false)), ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
BLOCK_ADS("property.block_ads",
"description.block_ads",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
UNLIMITED_SNAP_VIEW_TIME("property.unlimited_snap_view_time",
"description.unlimited_snap_view_time",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
PREVENT_SCREENSHOT_NOTIFICATIONS(
"property.prevent_screenshot_notifications",
"description.prevent_screenshot_notifications",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
PREVENT_STATUS_NOTIFICATIONS(
"property.prevent_status_notifications",
"description.prevent_status_notifications",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
ANONYMOUS_STORY_VIEW(
"property.anonymous_story_view",
"description.anonymous_story_view",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
HIDE_TYPING_NOTIFICATION(
"property.hide_typing_notification",
"description.hide_typing_notification",
ConfigCategory.SPYING_PRIVACY,
ConfigStateValue(false)
),
//MEDIA MANAGEMENT
SAVE_FOLDER( SAVE_FOLDER(
"property.save_folder", "description.save_folder", ConfigCategory.MEDIA_DOWNLOADER, "property.save_folder", "description.save_folder", ConfigCategory.MEDIA_MANAGEMENT,
ConfigStringValue(File( ConfigStringValue(File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath + "/Snapchat", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).absolutePath + "/Snapchat",
"SnapEnhance" "SnapEnhance"
).absolutePath) ).absolutePath)
), ),
AUTO_DOWNLOAD_SNAPS( AUTO_DOWNLOAD_OPTIONS(
"property.auto_download_snaps", "property.auto_download_options", "description.auto_download_options", ConfigCategory.MEDIA_MANAGEMENT,
"description.auto_download_snaps", ConfigStateListValue(
ConfigCategory.MEDIA_DOWNLOADER, listOf("friend_snaps", "friend_stories", "public_stories", "spotlight"),
ConfigStateValue(false) mutableMapOf(
), "friend_snaps" to false,
AUTO_DOWNLOAD_STORIES( "friend_stories" to false,
"property.auto_download_stories", "public_stories" to false,
"description.auto_download_stories", "spotlight" to false
ConfigCategory.MEDIA_DOWNLOADER, )
ConfigStateValue(false) )
),
AUTO_DOWNLOAD_PUBLIC_STORIES(
"property.auto_download_public_stories",
"description.auto_download_public_stories",
ConfigCategory.MEDIA_DOWNLOADER,
ConfigStateValue(false)
),
AUTO_DOWNLOAD_SPOTLIGHT(
"property.auto_download_spotlight",
"description.auto_download_spotlight",
ConfigCategory.MEDIA_DOWNLOADER,
ConfigStateValue(false)
), ),
DOWNLOAD_OPTIONS( DOWNLOAD_OPTIONS(
"property.download_options", "description.download_options", ConfigCategory.MEDIA_DOWNLOADER, "property.download_options", "description.download_options", ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateListValue( ConfigStateListValue(
listOf("format_user_folder", "format_hash", "format_date_time", "format_username", "merge_overlay"), listOf("format_user_folder", "format_hash", "format_date_time", "format_username", "merge_overlay"),
mutableMapOf( mutableMapOf(
@ -93,167 +133,154 @@ enum class ConfigProperty(
) )
) )
), ),
DOWNLOAD_INCHAT_SNAPS( CHAT_DOWNLOAD_CONTEXT_MENU(
"property.download_inchat_snaps", "property.chat_download_context_menu",
"description.download_inchat_snaps", "description.chat_download_context_menu",
ConfigCategory.MEDIA_DOWNLOADER, ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false) ConfigStateValue(false)
), ),
ANTI_DOWNLOAD_BUTTON( DOWNLOAD_BLACKLIST(
"property.anti_auto_download_button", "property.auto_download_blacklist",
"description.anti_auto_download_button", "description.auto_download_blacklist",
ConfigCategory.MEDIA_DOWNLOADER, ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false) ConfigStateValue(false)
), ),
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,
ConfigStateValue(false)
),
PREVENT_STATUS_NOTIFICATIONS(
"property.prevent_status_notifications",
"description.prevent_status_notifications",
ConfigCategory.PRIVACY,
ConfigStateValue(false)
),
ANONYMOUS_STORY_VIEW(
"property.anonymous_story_view",
"description.anonymous_story_view",
ConfigCategory.PRIVACY,
ConfigStateValue(false)
),
HIDE_TYPING_NOTIFICATION(
"property.hide_typing_notification",
"description.hide_typing_notification",
ConfigCategory.PRIVACY,
ConfigStateValue(false)
),
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,
ConfigIntegerValue(20)
),
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)),
DISABLE_SNAP_SPLITTING(
"property.disable_snap_splitting",
"description.disable_snap_splitting",
ConfigCategory.EXTRAS,
ConfigStateValue(false)
),
DISABLE_VIDEO_LENGTH_RESTRICTION(
"property.disable_video_length_restriction",
"description.disable_video_length_restriction",
ConfigCategory.EXTRAS,
ConfigStateValue(false)
),
OVERRIDE_MEDIA_QUALITY(
"property.override_media_quality",
"description.override_media_quality",
ConfigCategory.EXTRAS,
ConfigStateValue(false)
),
MEDIA_QUALITY_LEVEL(
"property.media_quality_level",
"description.media_quality_level",
ConfigCategory.EXTRAS,
ConfigStateSelection(
listOf("LEVEL_NONE", "LEVEL_1", "LEVEL_2", "LEVEL_3", "LEVEL_4", "LEVEL_5", "LEVEL_6", "LEVEL_7", "LEVEL_MAX"),
"LEVEL_NONE"
)
),
GALLERY_MEDIA_SEND_OVERRIDE( GALLERY_MEDIA_SEND_OVERRIDE(
"property.gallery_media_send_override", "property.gallery_media_send_override",
"description.gallery_media_send_override", "description.gallery_media_send_override",
ConfigCategory.EXTRAS, ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateSelection( ConfigStateSelection(
listOf("OFF", "NOTE", "SNAP", "LIVE_SNAP"), listOf("OFF", "NOTE", "SNAP", "LIVE_SNAP"),
"OFF" "OFF"
) )
), ),
AUTO_SAVE_MESSAGES("property.auto_save_messages",
REMOVE_VOICE_RECORD_BUTTON( "description.auto_save_messages",
"property.remove_voice_record_button", ConfigCategory.MEDIA_MANAGEMENT,
"description.remove_voice_record_button", ConfigStateListValue(
ConfigCategory.TWEAKS, listOf("CHAT", "SNAP", "NOTE", "EXTERNAL_MEDIA", "STICKER")
)
),
ANTI_AUTO_SAVE("property.anti_auto_save",
"description.anti_auto_save",
ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false) ConfigStateValue(false)
), ),
REMOVE_STICKERS_BUTTON(
"property.remove_stickers_button", OVERRIDE_MEDIA_QUALITY(
"description.remove_stickers_button", "property.override_media_quality",
ConfigCategory.TWEAKS, "description.override_media_quality",
ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false) ConfigStateValue(false)
), ),
REMOVE_COGNAC_BUTTON( MEDIA_QUALITY_LEVEL(
"property.remove_cognac_button", "property.media_quality_level",
"description.remove_cognac_button", "description.media_quality_level",
ConfigCategory.TWEAKS, ConfigCategory.MEDIA_MANAGEMENT,
ConfigStateValue(false) ConfigStateSelection(
listOf("LEVEL_NONE", "LEVEL_1", "LEVEL_2", "LEVEL_3", "LEVEL_4", "LEVEL_5", "LEVEL_6", "LEVEL_7", "LEVEL_MAX"),
"LEVEL_NONE"
)
), ),
REMOVE_CALL_BUTTONS(
"property.remove_call_buttons", //UI AND TWEAKS
"description.remove_call_buttons", HIDE_UI_ELEMENTS(
ConfigCategory.TWEAKS, "property.hide_ui_elements",
ConfigStateValue(false) "description.hide_ui_elements",
ConfigCategory.UI_TWEAKS,
ConfigStateListValue(
listOf("remove_voice_record_button", "remove_stickers_button", "remove_cognac_button", "remove_call_buttons"),
mutableMapOf(
"remove_voice_record_button" to false,
"remove_stickers_button" to false,
"remove_cognac_button" to false,
"remove_call_buttons" to false,
)
)
), ),
BLOCK_ADS("property.block_ads", "description.block_ads", ConfigCategory.TWEAKS, ConfigStateValue(false)),
STREAK_EXPIRATION_INFO( STREAK_EXPIRATION_INFO(
"property.streak_expiration_info", "property.streak_expiration_info",
"description.streakexpirationinfo", "description.streakexpirationinfo",
ConfigCategory.TWEAKS, ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
DISABLE_SNAP_SPLITTING(
"property.disable_snap_splitting",
"description.disable_snap_splitting",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
DISABLE_VIDEO_LENGTH_RESTRICTION(
"property.disable_video_length_restriction",
"description.disable_video_length_restriction",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
SNAPCHAT_PLUS("property.snapchat_plus",
"description.snapchat_plus",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false)
),
NEW_MAP_UI("property.new_map_ui",
"description.new_map_ui",
ConfigCategory.UI_TWEAKS,
ConfigStateValue(false) ConfigStateValue(false)
), ),
NEW_MAP_UI("property.new_map_ui", "description.new_map_ui", ConfigCategory.TWEAKS, ConfigStateValue(false)),
LOCATION_SPOOF( LOCATION_SPOOF(
"property.location_spoof", "property.location_spoof",
"description.location_spoof", "description.location_spoof",
ConfigCategory.LOCATION_SPOOF, ConfigCategory.UI_TWEAKS,
ConfigStateValue(false) ConfigStateValue(false)
), ),
LATITUDE( LATITUDE(
"property.latitude_value", "property.latitude_value",
"description.latitude_value", "description.latitude_value",
ConfigCategory.LOCATION_SPOOF, ConfigCategory.UI_TWEAKS,
ConfigStringValue("0.0000") ConfigStringValue("0.0000"),
shouldAppearInSettings = false
), ),
LONGITUDE( LONGITUDE(
"property.longitude_value", "property.longitude_value",
"description.longitude_value", "description.longitude_value",
ConfigCategory.LOCATION_SPOOF, ConfigCategory.UI_TWEAKS,
ConfigStringValue("0.0000") ConfigStringValue("0.0000"),
shouldAppearInSettings = false
), ),
MENU_SLOT_ID("property.menu_slot_id",
"description.menu_slot_id",
ConfigCategory.UI_TWEAKS,
ConfigIntegerValue(1)
),
MESSAGE_PREVIEW_LENGTH(
"property.message_preview_length",
"description.message_preview_length",
ConfigCategory.UI_TWEAKS,
ConfigIntegerValue(20)
),
// EXPERIMENTAL DEBUGGING
USE_DOWNLOAD_MANAGER( USE_DOWNLOAD_MANAGER(
"property.use_download_manager", "property.use_download_manager",
"description.use_download_manager", "description.use_download_manager",
ConfigCategory.EXPERIMENTAL, ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false) ConfigStateValue(false)
), ),
APP_PASSCODE( APP_PASSCODE(
"property.app_passcode", "property.app_passcode",
"description.app_passcode", "description.app_passcode",
ConfigCategory.EXPERIMENTAL, ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStringValue("") ConfigStringValue("", isHidden = true)
), ),
APP_LOCK_ON_RESUME( APP_LOCK_ON_RESUME(
"property.app_lock_on_resume", "property.app_lock_on_resume",
"description.app_lock_on_resume", "description.app_lock_on_resume",
ConfigCategory.EXPERIMENTAL, ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false) ConfigStateValue(false)
), ),
MEO_PASSCODE_BYPASS( MEO_PASSCODE_BYPASS(
"property.meo_passcode_bypass", "property.meo_passcode_bypass",
"description.meo_passcode_bypass", "description.meo_passcode_bypass",
ConfigCategory.EXPERIMENTAL, ConfigCategory.EXPERIMENTAL_DEBUGGING,
ConfigStateValue(false) ConfigStateValue(false)
); );

View File

@ -1,7 +1,22 @@
package me.rhunk.snapenhance.config package me.rhunk.snapenhance.config
abstract class ConfigValue<T> { abstract class ConfigValue<T> {
private val propertyChangeListeners = mutableListOf<(T) -> Unit>()
fun addPropertyChangeListener(listener: (T) -> Unit) = propertyChangeListeners.add(listener)
fun removePropertyChangeListener(listener: (T) -> Unit) = propertyChangeListeners.remove(listener)
abstract fun value(): T abstract fun value(): T
abstract fun write(): String abstract fun read(): String
abstract fun read(value: String) protected abstract fun write(value: String)
protected fun onValueChanged() {
propertyChangeListeners.forEach { it(value()) }
}
fun writeFrom(value: String) {
val oldValue = read()
write(value)
if (oldValue != value) onValueChanged()
}
} }

View File

@ -3,15 +3,15 @@ package me.rhunk.snapenhance.config.impl
import me.rhunk.snapenhance.config.ConfigValue import me.rhunk.snapenhance.config.ConfigValue
class ConfigIntegerValue( class ConfigIntegerValue(
var value: Int private var value: Int
) : ConfigValue<Int>() { ) : ConfigValue<Int>() {
override fun value() = value override fun value() = value
override fun write(): String { override fun read(): String {
return value.toString() return value.toString()
} }
override fun read(value: String) { override fun write(value: String) {
this.value = value.toInt() this.value = value.toInt()
} }
} }

View File

@ -4,17 +4,22 @@ import me.rhunk.snapenhance.config.ConfigValue
class ConfigStateListValue( class ConfigStateListValue(
private val keys: List<String>, private val keys: List<String>,
var states: MutableMap<String, Boolean> = mutableMapOf() private var states: MutableMap<String, Boolean> = mutableMapOf()
) : ConfigValue<Map<String, Boolean>>() { ) : ConfigValue<Map<String, Boolean>>() {
override fun value() = states override fun value() = states
fun value(key: String) = states[key] ?: false fun setKey(key: String, state: Boolean) {
states[key] = state
onValueChanged()
}
override fun write(): String { operator fun get(key: String) = states[key] ?: false
override fun read(): String {
return keys.joinToString("|") { "$it:${states[it]}" } return keys.joinToString("|") { "$it:${states[it]}" }
} }
override fun read(value: String) { override fun write(value: String) {
value.split("|").forEach { value.split("|").forEach {
val (key, state) = it.split(":") val (key, state) = it.split(":")
states[key] = state.toBoolean() states[key] = state.toBoolean()

View File

@ -4,25 +4,19 @@ import me.rhunk.snapenhance.config.ConfigValue
class ConfigStateSelection( class ConfigStateSelection(
private val keys: List<String>, private val keys: List<String>,
var state: String = "" private var state: String = ""
) : ConfigValue<String>() { ) : ConfigValue<String>() {
fun keys(): List<String> { fun keys(): List<String> {
return keys return keys
} }
override fun value(): String {
override fun value() = state
override fun read(): String {
return state return state
} }
fun value(key: String) { override fun write(value: String) {
state = key
}
override fun write(): String {
return state
}
override fun read(value: String) {
state = value state = value
} }
} }

View File

@ -3,15 +3,15 @@ package me.rhunk.snapenhance.config.impl
import me.rhunk.snapenhance.config.ConfigValue import me.rhunk.snapenhance.config.ConfigValue
class ConfigStateValue( class ConfigStateValue(
var value: Boolean private var value: Boolean
) : ConfigValue<Boolean>() { ) : ConfigValue<Boolean>() {
override fun value() = value override fun value() = value
override fun write(): String { override fun read(): String {
return value.toString() return value.toString()
} }
override fun read(value: String) { override fun write(value: String) {
this.value = value.toBoolean() this.value = value.toBoolean()
} }
} }

View File

@ -3,15 +3,18 @@ package me.rhunk.snapenhance.config.impl
import me.rhunk.snapenhance.config.ConfigValue import me.rhunk.snapenhance.config.ConfigValue
class ConfigStringValue( class ConfigStringValue(
var value: String = "" private var value: String = "",
val isHidden: Boolean = false
) : ConfigValue<String>() { ) : ConfigValue<String>() {
override fun value() = value override fun value() = value
override fun write(): String { fun hiddenValue() = if (isHidden) value.map { '*' }.joinToString("") else value
override fun read(): String {
return value return value
} }
override fun read(value: String) { override fun write(value: String) {
this.value = value this.value = value
} }
} }

View File

@ -60,7 +60,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
} }
private fun canMergeOverlay(): Boolean { private fun canMergeOverlay(): Boolean {
if (context.config.options(ConfigProperty.DOWNLOAD_OPTIONS)["overlay_merge"] == false) return false if (context.config.options(ConfigProperty.DOWNLOAD_OPTIONS)["merge_overlay"] == false) return false
return isFFmpegPresent return isFFmpegPresent
} }
@ -228,9 +228,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
forceDownload: Boolean forceDownload: Boolean
) { ) {
//messages //messages
if (paramMap.containsKey("MESSAGE_ID") && paramMap["MESSAGE_ID"]?.toString()?.takeIf { forceDownload || canAutoDownload("friend_snaps") }?.let { id ->
(forceDownload || context.config.bool(ConfigProperty.AUTO_DOWNLOAD_SNAPS))) {
val id = paramMap["MESSAGE_ID"].toString()
val messageId = id.substring(id.lastIndexOf(":") + 1).toLong() val messageId = id.substring(id.lastIndexOf(":") + 1).toLong()
val senderId: String = context.database.getConversationMessageFromId(messageId)!!.sender_id!! val senderId: String = context.database.getConversationMessageFromId(messageId)!!.sender_id!!
@ -244,23 +242,25 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
} }
//private stories //private stories
val playlistV2Group = paramMap["PLAYLIST_V2_GROUP"]?.toString()?.takeIf {
if (paramMap.containsKey("PLAYLIST_V2_GROUP")) paramMap["PLAYLIST_V2_GROUP"].toString() else null it.contains("storyUserId=") && (forceDownload || canAutoDownload("friend_stories"))
if (playlistV2Group != null && }?.let { playlistGroup ->
playlistV2Group.contains("storyUserId=") && val storyIdStartIndex = playlistGroup.indexOf("storyUserId=") + 12
(forceDownload || context.config.bool(ConfigProperty.AUTO_DOWNLOAD_STORIES)) val storyUserId = playlistGroup.substring(
) { storyIdStartIndex,
val storyIdStartIndex = playlistV2Group.indexOf("storyUserId=") + 12 playlistGroup.indexOf(",", storyIdStartIndex)
val storyUserId = playlistV2Group.substring(storyIdStartIndex, playlistV2Group.indexOf(",", storyIdStartIndex)) )
val author = context.database.getFriendInfo(if (storyUserId == "null") context.database.getMyUserId()!! else storyUserId) val author = context.database.getFriendInfo(if (storyUserId == "null") context.database.getMyUserId()!! else storyUserId)
downloadOperaMedia(mediaInfoMap, author!!.usernameForSorting!!) downloadOperaMedia(mediaInfoMap, author!!.usernameForSorting!!)
return return
} }
val snapSource = paramMap["SNAP_SOURCE"].toString() val snapSource = paramMap["SNAP_SOURCE"].toString()
//public stories //public stories
if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") && if ((snapSource == "PUBLIC_USER" || snapSource == "SAVED_STORY") &&
(forceDownload || context.config.bool(ConfigProperty.AUTO_DOWNLOAD_PUBLIC_STORIES))) { (forceDownload || canAutoDownload("public_stories"))) {
val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace( val userDisplayName = (if (paramMap.containsKey("USER_DISPLAY_NAME")) paramMap["USER_DISPLAY_NAME"].toString() else "").replace(
"[^\\x00-\\x7F]".toRegex(), "[^\\x00-\\x7F]".toRegex(),
"") "")
@ -269,7 +269,7 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
} }
//spotlight //spotlight
if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || context.config.bool(ConfigProperty.AUTO_DOWNLOAD_SPOTLIGHT))) { if (snapSource == "SINGLE_SNAP_STORY" && (forceDownload || canAutoDownload("spotlight"))) {
downloadOperaMedia(mediaInfoMap, "Spotlight") downloadOperaMedia(mediaInfoMap, "Spotlight")
return return
} }
@ -323,11 +323,9 @@ class MediaDownloader : Feature("MediaDownloader", loadParams = FeatureLoadParam
} }
} }
private fun canAutoDownload(): Boolean { private fun canAutoDownload(keyFilter: String? = null): Boolean {
return context.config.bool(ConfigProperty.AUTO_DOWNLOAD_SNAPS) || val options = context.config.options(ConfigProperty.AUTO_DOWNLOAD_OPTIONS)
context.config.bool(ConfigProperty.AUTO_DOWNLOAD_STORIES) || return options.filter { it.value }.any { keyFilter == null || it.key.contains(keyFilter, true) }
context.config.bool(ConfigProperty.AUTO_DOWNLOAD_PUBLIC_STORIES) ||
context.config.bool(ConfigProperty.AUTO_DOWNLOAD_SPOTLIGHT)
} }
override fun asyncOnActivityCreate() { override fun asyncOnActivityCreate() {

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType import me.rhunk.snapenhance.bridge.common.impl.file.BridgeFileType
import me.rhunk.snapenhance.features.BridgeFileFeature import me.rhunk.snapenhance.features.BridgeFileFeature

View File

@ -1,8 +1,7 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.MessageState import me.rhunk.snapenhance.data.MessageState
import me.rhunk.snapenhance.data.wrapper.impl.Message import me.rhunk.snapenhance.data.wrapper.impl.Message
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
@ -61,17 +60,14 @@ class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CR
private fun canSaveMessage(message: Message): Boolean { private fun canSaveMessage(message: Message): Boolean {
if (message.messageMetadata.savedBy.any { uuid -> uuid.toString() == myUserId }) return false if (message.messageMetadata.savedBy.any { uuid -> uuid.toString() == myUserId }) return false
//only save chats val contentType = message.messageContent.contentType.toString()
with(message.messageContent.contentType) {
if (this != ContentType.CHAT && return context.config.options(ConfigProperty.AUTO_SAVE_MESSAGES).filter { it.value }.any { it.key == contentType }
this != ContentType.NOTE &&
this != ContentType.STICKER &&
this != ContentType.EXTERNAL_MEDIA) return false
}
return true
} }
private fun canSave(): Boolean { private fun canSave(): Boolean {
if (context.config.options(ConfigProperty.AUTO_SAVE_MESSAGES).none { it.value }) return false
with(context.feature(Messaging::class)) { with(context.feature(Messaging::class)) {
if (lastOpenedConversationUUID == null) return@canSave false if (lastOpenedConversationUUID == null) return@canSave false
val conversation = lastOpenedConversationUUID.toString() val conversation = lastOpenedConversationUUID.toString()
@ -87,7 +83,7 @@ class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CR
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"), context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"),
"onFetchConversationWithMessagesComplete", "onFetchConversationWithMessagesComplete",
HookStage.BEFORE, HookStage.BEFORE,
{ context.config.bool(ConfigProperty.AUTO_SAVE) && canSave()} { canSave() }
) { param -> ) { param ->
val conversationId = SnapUUID(param.arg<Any>(0).getObjectField("mConversationId")!!) val conversationId = SnapUUID(param.arg<Any>(0).getObjectField("mConversationId")!!)
val messages = param.arg<List<Any>>(1).map { Message(it) } val messages = param.arg<List<Any>>(1).map { Message(it) }
@ -104,7 +100,7 @@ class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CR
context.mappings.getMappedClass("callbacks", "FetchMessageCallback"), context.mappings.getMappedClass("callbacks", "FetchMessageCallback"),
"onFetchMessageComplete", "onFetchMessageComplete",
HookStage.BEFORE, HookStage.BEFORE,
{ context.config.bool(ConfigProperty.AUTO_SAVE) && canSave()} { canSave() }
) { param -> ) { param ->
val message = Message(param.arg(0)) val message = Message(param.arg(0))
if (!canSaveMessage(message)) return@hook if (!canSaveMessage(message)) return@hook
@ -119,7 +115,7 @@ class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CR
context.mappings.getMappedClass("callbacks", "SendMessageCallback"), context.mappings.getMappedClass("callbacks", "SendMessageCallback"),
"onSuccess", "onSuccess",
HookStage.BEFORE, HookStage.BEFORE,
{ context.config.bool(ConfigProperty.AUTO_SAVE) && canSave()} { canSave() }
) { ) {
val callback = CallbackBuilder(fetchConversationWithMessagesCallbackClass).build() val callback = CallbackBuilder(fetchConversationWithMessagesCallbackClass).build()
runCatching { runCatching {

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import android.content.Intent import android.content.Intent
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
@ -17,8 +17,8 @@ class LocationSpoofer: Feature("LocationSpoof", loadParams = FeatureLoadParams.A
val longitude = bundle.getFloat("longitude") val longitude = bundle.getFloat("longitude")
with(context.config) { with(context.config) {
get(ConfigProperty.LATITUDE).read(latitude.toString()) get(ConfigProperty.LATITUDE).writeFrom(latitude.toString())
get(ConfigProperty.LONGITUDE).read(longitude.toString()) get(ConfigProperty.LONGITUDE).writeFrom(longitude.toString())
writeConfig() writeConfig()
} }
context.longToast("Location set to $latitude, $longitude") context.longToast("Location set to $latitude, $longitude")

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import android.app.Notification import android.app.Notification
import android.app.NotificationManager import android.app.NotificationManager
@ -206,7 +206,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
else it else it
}[MediaType.ORIGINAL] ?: throw Throwable("Failed to download media") }[MediaType.ORIGINAL] ?: throw Throwable("Failed to download media")
val bitmapPreview = PreviewUtils.createPreview(downloadedMedia, mediaType == MediaReferenceType.VIDEO)!! val bitmapPreview = PreviewUtils.createPreview(downloadedMedia, mediaType.name.contains("VIDEO"))!!
val notificationBuilder = XposedHelpers.newInstance( val notificationBuilder = XposedHelpers.newInstance(
Notification.Builder::class.java, Notification.Builder::class.java,
context.androidContext, context.androidContext,
@ -227,7 +227,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
} }
if (contentType == ContentType.CHAT) { if (contentType == ContentType.CHAT && context.config.options(ConfigProperty.BETTER_NOTIFICATIONS)["reply_button"] == true) {
setupNotificationActionButtons(conversationId, notificationData) setupNotificationActionButtons(conversationId, notificationData)
} }
@ -264,9 +264,8 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
return@hook return@hook
} }
if (!context.config.bool(ConfigProperty.SHOW_MESSAGE_CONTENT_IN_NOTIFICATIONS)) return@hook if (context.config.options(ConfigProperty.BETTER_NOTIFICATIONS)
.filter { it.value }.none { notificationType.endsWith(it.key.uppercase())}) return@hook
if (!notificationType.endsWith("CHAT") && !notificationType.endsWith("SNAP")) return@hook
val conversationManager: Any = context.feature(Messaging::class).conversationManager val conversationManager: Any = context.feature(Messaging::class).conversationManager
notificationDataQueue[messageId.toLong()] = notificationData notificationDataQueue[messageId.toLong()] = notificationData

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.features.impl.extras package me.rhunk.snapenhance.features.impl.tweaks
import me.rhunk.snapenhance.config.ConfigProperty import me.rhunk.snapenhance.config.ConfigProperty
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType

View File

@ -22,14 +22,15 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
val chatNoteRecordButton = resources.getIdentifier("chat_note_record_button", "id", Constants.SNAPCHAT_PACKAGE_NAME) val chatNoteRecordButton = resources.getIdentifier("chat_note_record_button", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val chatInputBarSticker = resources.getIdentifier("chat_input_bar_sticker", "id", Constants.SNAPCHAT_PACKAGE_NAME) val chatInputBarSticker = resources.getIdentifier("chat_input_bar_sticker", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val chatInputBarCognac = resources.getIdentifier("chat_input_bar_cognac", "id", Constants.SNAPCHAT_PACKAGE_NAME) val chatInputBarCognac = resources.getIdentifier("chat_input_bar_cognac", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val hiddenElements = context.config.options(ConfigProperty.HIDE_UI_ELEMENTS)
Hooker.hook(View::class.java, "setVisibility", HookStage.BEFORE) { methodParam -> Hooker.hook(View::class.java, "setVisibility", HookStage.BEFORE) { methodParam ->
val viewId = (methodParam.thisObject() as View).id val viewId = (methodParam.thisObject() as View).id
if (viewId == chatNoteRecordButton && context.config.bool(ConfigProperty.REMOVE_VOICE_RECORD_BUTTON)) { if (viewId == chatNoteRecordButton && hiddenElements["remove_voice_record_button"] == true) {
methodParam.setArg(0, View.GONE) methodParam.setArg(0, View.GONE)
} }
if (viewId == callButton1 || viewId == callButton2) { if (viewId == callButton1 || viewId == callButton2) {
if (!context.config.bool(ConfigProperty.REMOVE_CALL_BUTTONS)) return@hook if (hiddenElements["remove_call_buttons"] == false) return@hook
methodParam.setArg(0, View.GONE) methodParam.setArg(0, View.GONE)
} }
} }
@ -45,23 +46,23 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
val view: View = param.arg(0) val view: View = param.arg(0)
val viewId = view.id val viewId = view.id
if (viewId == chatNoteRecordButton && context.config.bool(ConfigProperty.REMOVE_VOICE_RECORD_BUTTON)) { if (viewId == chatNoteRecordButton && hiddenElements["remove_voice_record_button"] == true) {
view.isEnabled = false view.isEnabled = false
view.setWillNotDraw(true) view.setWillNotDraw(true)
} }
if (chatInputBarCognac == viewId && context.config.bool(ConfigProperty.REMOVE_COGNAC_BUTTON)) { if (chatInputBarCognac == viewId && hiddenElements["remove_cognac_button"] == true) {
view.visibility = View.GONE view.visibility = View.GONE
} }
if (chatInputBarSticker == viewId && context.config.bool(ConfigProperty.REMOVE_STICKERS_BUTTON)) { if (chatInputBarSticker == viewId && hiddenElements["remove_stickers_button"] == true) {
view.visibility = View.GONE view.visibility = View.GONE
} }
if (viewId == callButton1 || viewId == callButton2) { if (viewId == callButton1 || viewId == callButton2) {
if (!context.config.bool(ConfigProperty.REMOVE_CALL_BUTTONS)) return@hook if (hiddenElements["remove_call_buttons"] == false) return@hook
if (view.visibility == View.GONE) return@hook if (view.visibility == View.GONE) return@hook
} }
if (viewId == callButtonsStub) { if (viewId == callButtonsStub) {
if (!context.config.bool(ConfigProperty.REMOVE_CALL_BUTTONS)) return@hook if (hiddenElements["remove_call_buttons"] == false) return@hook
param.setResult(null) param.setResult(null)
} }
} }

View File

@ -1,10 +1,13 @@
package me.rhunk.snapenhance.features.impl.ui.menus package me.rhunk.snapenhance.features.impl.ui.menus
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent import android.view.MotionEvent
import android.widget.Button import android.widget.Button
import android.widget.EditText
import me.rhunk.snapenhance.R import me.rhunk.snapenhance.R
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.tileprovider.tilesource.TileSourceFactory
@ -20,12 +23,13 @@ class MapActivity : Activity() {
private lateinit var mapView: MapView private lateinit var mapView: MapView
@SuppressLint("MissingInflatedId", "ResourceType")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val contextBundle = intent.extras?.getBundle("location") ?: return val contextBundle = intent.extras?.getBundle("location") ?: return
val latitude = contextBundle.getDouble("latitude") val locationLatitude = contextBundle.getDouble("latitude")
val longitude = contextBundle.getDouble("longitude") val locationLongitude = contextBundle.getDouble("longitude")
Configuration.getInstance().load(applicationContext, getSharedPreferences("osmdroid", Context.MODE_PRIVATE)) Configuration.getInstance().load(applicationContext, getSharedPreferences("osmdroid", Context.MODE_PRIVATE))
@ -35,7 +39,7 @@ class MapActivity : Activity() {
mapView.setMultiTouchControls(true); mapView.setMultiTouchControls(true);
mapView.setTileSource(TileSourceFactory.MAPNIK) mapView.setTileSource(TileSourceFactory.MAPNIK)
val startPoint = GeoPoint(latitude, longitude) val startPoint = GeoPoint(locationLatitude, locationLongitude)
mapView.controller.setZoom(10.0) mapView.controller.setZoom(10.0)
mapView.controller.setCenter(startPoint) mapView.controller.setCenter(startPoint)
@ -64,6 +68,28 @@ class MapActivity : Activity() {
setResult(RESULT_OK, intent.putExtra("location", bundle)) setResult(RESULT_OK, intent.putExtra("location", bundle))
finish() finish()
} }
val setPreciseLocationButton = findViewById<Button>(R.id.set_precise_location_button)
setPreciseLocationButton.setOnClickListener {
val locationDialog = layoutInflater.inflate(R.layout.precise_location_dialog, null)
val dialogLatitude = locationDialog.findViewById<EditText>(R.id.dialog_latitude).also { it.setText(marker.position.latitude.toString()) }
val dialogLongitude = locationDialog.findViewById<EditText>(R.id.dialog_longitude).also { it.setText(marker.position.longitude.toString()) }
AlertDialog.Builder(this)
.setView(locationDialog)
.setTitle("Set a precise location")
.setPositiveButton("Set") { _, _ ->
val latitude = dialogLatitude.text.toString().toDoubleOrNull()
val longitude = dialogLongitude.text.toString().toDoubleOrNull()
if (latitude != null && longitude != null) {
val preciseLocation = GeoPoint(latitude, longitude)
mapView.controller.setCenter(preciseLocation)
marker.position = preciseLocation
mapView.invalidate()
}
}.setNegativeButton("Cancel") { _, _ -> }.show()
}
} }
override fun onDestroy() { override fun onDestroy() {

View File

@ -64,34 +64,35 @@ class ChatActionMenu : AbstractMenu() {
) )
) )
} }
if (context.config.bool(ConfigProperty.DOWNLOAD_INCHAT_SNAPS)) { if (context.config.bool(ConfigProperty.CHAT_DOWNLOAD_CONTEXT_MENU)) {
val previewButton = Button(viewGroup.context) parent.addView(Button(viewGroup.context).apply {
applyButtonTheme(parent, previewButton) applyButtonTheme(parent, this)
previewButton.text = "Preview" text = this@ChatActionMenu.context.translation.get("chat_action_menu.preview_button")
previewButton.setOnClickListener { setOnClickListener {
closeActionMenu() closeActionMenu()
context.executeAsync { context.feature(MediaDownloader::class).onMessageActionMenu(true) } this@ChatActionMenu.context.executeAsync { this@ChatActionMenu.context.feature(MediaDownloader::class).onMessageActionMenu(true) }
} }
parent.addView(previewButton) })
}
//download snap in chat parent.addView(Button(viewGroup.context).apply {
if (context.config.bool(ConfigProperty.DOWNLOAD_INCHAT_SNAPS)) { applyButtonTheme(parent, this)
val downloadButton = Button(viewGroup.context) text = this@ChatActionMenu.context.translation.get("chat_action_menu.download_button")
applyButtonTheme(parent, downloadButton) setOnClickListener {
downloadButton.text = "Download" closeActionMenu()
downloadButton.setOnClickListener { this@ChatActionMenu.context.executeAsync {
closeActionMenu() this@ChatActionMenu.context.feature(
context.executeAsync { context.feature(MediaDownloader::class).onMessageActionMenu(false) } MediaDownloader::class
} ).onMessageActionMenu(false)
parent.addView(downloadButton) }
}
})
} }
//delete logged message button //delete logged message button
if (context.config.bool(ConfigProperty.MESSAGE_LOGGER)) { if (context.config.bool(ConfigProperty.MESSAGE_LOGGER)) {
val downloadButton = Button(viewGroup.context) val downloadButton = Button(viewGroup.context)
applyButtonTheme(parent, downloadButton) applyButtonTheme(parent, downloadButton)
downloadButton.text = "Deleted logged message" downloadButton.text = context.translation.get("chat_action_menu.delete_logged_message_button")
downloadButton.setOnClickListener { downloadButton.setOnClickListener {
closeActionMenu() closeActionMenu()
context.executeAsync { context.executeAsync {

View File

@ -21,7 +21,7 @@ import me.rhunk.snapenhance.database.objects.FriendInfo
import me.rhunk.snapenhance.database.objects.UserConversationLink import me.rhunk.snapenhance.database.objects.UserConversationLink
import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.Messaging
import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
import me.rhunk.snapenhance.features.impl.extras.AntiAutoSave import me.rhunk.snapenhance.features.impl.tweaks.AntiAutoSave
import me.rhunk.snapenhance.features.impl.spying.StealthMode import me.rhunk.snapenhance.features.impl.spying.StealthMode
import me.rhunk.snapenhance.features.impl.ui.menus.AbstractMenu 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.applyTheme
@ -215,7 +215,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
run { run {
val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run
if (context.config.bool(ConfigProperty.ANTI_DOWNLOAD_BUTTON)) { if (context.config.bool(ConfigProperty.DOWNLOAD_BLACKLIST)) {
createToggleFeature(viewModel, createToggleFeature(viewModel,
viewConsumer, viewConsumer,
"friend_menu_option.anti_auto_download", "friend_menu_option.anti_auto_download",

View File

@ -35,10 +35,11 @@ class SettingsMenu : AbstractMenu() {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun createPropertyView(viewModel: View, property: ConfigProperty): View { private fun createPropertyView(viewModel: View, property: ConfigProperty): View {
val updateButtonText: (TextView, String) -> Unit = { textView, text -> val updateButtonText: (TextView, String) -> Unit = { textView, text ->
textView.text = "${context.translation.get(property.nameKey)} $text" textView.text = "${context.translation.get(property.nameKey)}${if (text.isEmpty()) "" else ": $text"}"
} }
val updateStateSelectionText: (TextView, String) -> Unit = { textView, text ->
updateButtonText(textView, text.let { if (it.isEmpty()) "(empty)" else ": $it" }) val updateLocalizedText: (TextView, String) -> Unit = { textView, value ->
updateButtonText(textView, value.let { if (it.isEmpty()) "(empty)" else context.translation.get("option." + property.nameKey + "." + it) })
} }
val textEditor: ((String) -> Unit) -> Unit = { updateValue -> val textEditor: ((String) -> Unit) -> Unit = { updateValue ->
@ -61,23 +62,29 @@ class SettingsMenu : AbstractMenu() {
val resultView: View = when (property.valueContainer) { val resultView: View = when (property.valueContainer) {
is ConfigStringValue -> { is ConfigStringValue -> {
val textView = TextView(viewModel.context) val textView = TextView(viewModel.context)
updateButtonText(textView, property.valueContainer.value) updateButtonText(textView, property.valueContainer.let {
if (it.isHidden) it.hiddenValue()
else it.value()
})
ViewAppearanceHelper.applyTheme(viewModel, textView) ViewAppearanceHelper.applyTheme(viewModel, textView)
textView.setOnClickListener { textView.setOnClickListener {
textEditor { value -> textEditor { value ->
property.valueContainer.value = value property.valueContainer.writeFrom(value)
updateButtonText(textView, value) updateButtonText(textView, property.valueContainer.let {
if (it.isHidden) it.hiddenValue()
else it.value()
})
} }
} }
textView textView
} }
is ConfigIntegerValue -> { is ConfigIntegerValue -> {
val button = Button(viewModel.context) val button = Button(viewModel.context)
updateButtonText(button, property.valueContainer.value.toString()) updateButtonText(button, property.valueContainer.value().toString())
button.setOnClickListener { button.setOnClickListener {
textEditor { value -> textEditor { value ->
runCatching { runCatching {
property.valueContainer.value = value.toInt() property.valueContainer.writeFrom(value)
updateButtonText(button, value) updateButtonText(button, value)
}.onFailure { }.onFailure {
context.shortToast("Invalid value") context.shortToast("Invalid value")
@ -90,30 +97,30 @@ class SettingsMenu : AbstractMenu() {
is ConfigStateValue -> { is ConfigStateValue -> {
val switch = Switch(viewModel.context) val switch = Switch(viewModel.context)
switch.text = context.translation.get(property.nameKey) switch.text = context.translation.get(property.nameKey)
switch.isChecked = property.valueContainer.value switch.isChecked = property.valueContainer.value()
switch.setOnCheckedChangeListener { _, isChecked -> switch.setOnCheckedChangeListener { _, isChecked ->
property.valueContainer.value = isChecked property.valueContainer.writeFrom(isChecked.toString())
} }
ViewAppearanceHelper.applyTheme(viewModel, switch) ViewAppearanceHelper.applyTheme(viewModel, switch)
switch switch
} }
is ConfigStateSelection -> { is ConfigStateSelection -> {
val button = Button(viewModel.context) val button = Button(viewModel.context)
updateStateSelectionText(button, property.valueContainer.value()) updateLocalizedText(button, property.valueContainer.value())
button.setOnClickListener {_ -> button.setOnClickListener {_ ->
val builder = AlertDialog.Builder(viewModel.context) val builder = AlertDialog.Builder(viewModel.context)
builder.setTitle(context.translation.get(property.nameKey)) builder.setTitle(context.translation.get(property.nameKey))
builder.setSingleChoiceItems( builder.setSingleChoiceItems(
property.valueContainer.keys().toTypedArray(), property.valueContainer.keys().toTypedArray().map { context.translation.get("option." + property.nameKey + "." + it) }.toTypedArray(),
property.valueContainer.keys().indexOf(property.valueContainer.value()) property.valueContainer.keys().indexOf(property.valueContainer.value())
) { _, which -> ) { _, which ->
property.valueContainer.value(property.valueContainer.keys()[which]) property.valueContainer.writeFrom(property.valueContainer.keys()[which])
} }
builder.setPositiveButton("OK") { _, _ -> builder.setPositiveButton("OK") { _, _ ->
updateStateSelectionText(button, property.valueContainer.value()) updateLocalizedText(button, property.valueContainer.value())
} }
builder.show() builder.show()
@ -123,25 +130,25 @@ class SettingsMenu : AbstractMenu() {
} }
is ConfigStateListValue -> { is ConfigStateListValue -> {
val button = Button(viewModel.context) val button = Button(viewModel.context)
updateStateSelectionText(button, property.valueContainer.toString()) updateButtonText(button, "(${property.valueContainer.value().count { it.value }})")
button.setOnClickListener {_ -> button.setOnClickListener {_ ->
val builder = AlertDialog.Builder(viewModel.context) val builder = AlertDialog.Builder(viewModel.context)
builder.setTitle(context.translation.get(property.nameKey)) builder.setTitle(context.translation.get(property.nameKey))
val sortedStates = property.valueContainer.states.toSortedMap() val sortedStates = property.valueContainer.value().toSortedMap()
builder.setMultiChoiceItems( builder.setMultiChoiceItems(
sortedStates.toSortedMap().map { context.translation.get("option." + property.nameKey + "." +it.key) }.toTypedArray(), sortedStates.toSortedMap().map { context.translation.get("option." + property.nameKey + "." +it.key) }.toTypedArray(),
sortedStates.map { it.value }.toBooleanArray() sortedStates.map { it.value }.toBooleanArray()
) { _, which, isChecked -> ) { _, which, isChecked ->
sortedStates.keys.toList()[which].let { key -> sortedStates.keys.toList()[which].let { key ->
property.valueContainer.states[key] = isChecked property.valueContainer.setKey(key, isChecked)
} }
} }
builder.setPositiveButton("OK") { _, _ -> builder.setPositiveButton("OK") { _, _ ->
updateStateSelectionText(button, property.valueContainer.toString()) updateButtonText(button, "(${property.valueContainer.value().count { it.value }})")
} }
builder.show() builder.show()
@ -191,15 +198,15 @@ class SettingsMenu : AbstractMenu() {
it.key.category it.key.category
}.forEach { (category, value) -> }.forEach { (category, value) ->
addView(createCategoryTitle(viewModel, category.key)) addView(createCategoryTitle(viewModel, category.key))
value.forEach { value.filter { it.key.shouldAppearInSettings }.forEach { (property, _) ->
addView(createPropertyView(viewModel, it.key)) addView(createPropertyView(viewModel, property))
actions.find { pair -> pair.first.dependsOnProperty == it.key }?.let { pair -> actions.find { pair -> pair.first.dependsOnProperty == property}?.let { pair ->
addView(pair.second()) addView(pair.second())
} }
} }
} }
addView(createCategoryTitle(viewModel, "category.debugging")) //addView(createCategoryTitle(viewModel, "category.debugging"))
actions.filter { it.first.dependsOnProperty == null }.forEach { actions.filter { it.first.dependsOnProperty == null }.forEach {
addView(it.second()) addView(it.second())
} }

View File

@ -41,14 +41,14 @@ class ConfigManager(
JsonObject::class.java JsonObject::class.java
) )
entries().forEach { (key, value) -> entries().forEach { (key, value) ->
value.read(configObject.get(key.name)?.asString ?: value.write()) value.writeFrom(configObject.get(key.name)?.asString ?: value.read())
} }
} }
fun writeConfig() { fun writeConfig() {
val configObject = JsonObject() val configObject = JsonObject()
entries().forEach { (key, value) -> entries().forEach { (key, value) ->
configObject.addProperty(key.name, value.write()) configObject.addProperty(key.name, value.read())
} }
context.bridgeClient.writeFile( context.bridgeClient.writeFile(
BridgeFileType.CONFIG, BridgeFileType.CONFIG,

View File

@ -10,15 +10,15 @@ import me.rhunk.snapenhance.features.impl.downloader.AntiAutoDownload
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
import me.rhunk.snapenhance.features.impl.experiments.MeoPasscodeBypass import me.rhunk.snapenhance.features.impl.experiments.MeoPasscodeBypass
import me.rhunk.snapenhance.features.impl.extras.AntiAutoSave import me.rhunk.snapenhance.features.impl.tweaks.AntiAutoSave
import me.rhunk.snapenhance.features.impl.extras.AutoSave import me.rhunk.snapenhance.features.impl.tweaks.AutoSave
import me.rhunk.snapenhance.features.impl.extras.DisableVideoLengthRestriction import me.rhunk.snapenhance.features.impl.tweaks.DisableVideoLengthRestriction
import me.rhunk.snapenhance.features.impl.extras.GalleryMediaSendOverride import me.rhunk.snapenhance.features.impl.tweaks.GalleryMediaSendOverride
import me.rhunk.snapenhance.features.impl.extras.LocationSpoofer import me.rhunk.snapenhance.features.impl.tweaks.LocationSpoofer
import me.rhunk.snapenhance.features.impl.extras.MediaQualityLevelOverride import me.rhunk.snapenhance.features.impl.tweaks.MediaQualityLevelOverride
import me.rhunk.snapenhance.features.impl.extras.Notifications import me.rhunk.snapenhance.features.impl.tweaks.Notifications
import me.rhunk.snapenhance.features.impl.extras.SnapchatPlus import me.rhunk.snapenhance.features.impl.tweaks.SnapchatPlus
import me.rhunk.snapenhance.features.impl.extras.UnlimitedSnapViewTime import me.rhunk.snapenhance.features.impl.tweaks.UnlimitedSnapViewTime
import me.rhunk.snapenhance.features.impl.privacy.DisableMetrics import me.rhunk.snapenhance.features.impl.privacy.DisableMetrics
import me.rhunk.snapenhance.features.impl.privacy.PreventMessageSending import me.rhunk.snapenhance.features.impl.privacy.PreventMessageSending
import me.rhunk.snapenhance.features.impl.spying.AnonymousStoryViewing import me.rhunk.snapenhance.features.impl.spying.AnonymousStoryViewing

View File

@ -11,15 +11,34 @@
</org.osmdroid.views.MapView> </org.osmdroid.views.MapView>
<Button <FrameLayout
android:id="@+id/apply_location_button" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_gravity="bottom|right" <Button
android:layout_marginEnd="20dp" android:id="@+id/set_precise_location_button"
android:layout_marginBottom="20dp" android:layout_width="wrap_content"
android:textSize="20sp" android:layout_height="match_parent"
android:background="@android:color/white" android:layout_gravity="left"
android:text="Apply" android:layout_marginStart="20dp"
tools:ignore="HardcodedText,RtlHardcoded" /> android:layout_marginTop="20dp"
android:padding="10dp"
android:background="@android:color/white"
android:text="Set Precise Location"
android:textSize="20sp"
tools:ignore="HardcodedText,RtlHardcoded" />
<Button
android:id="@+id/apply_location_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginTop="20dp"
android:layout_marginRight="20dp"
android:background="@android:color/white"
android:text="Apply"
android:textSize="20sp"
tools:ignore="HardcodedText,RtlHardcoded" />
</FrameLayout>
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:orientation="vertical"
tools:ignore="HardcodedText">
<EditText
android:id="@+id/dialog_latitude"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberDecimal"
android:hint="Latitude"
android:autofillHints="" />
<EditText
android:id="@+id/dialog_longitude"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberDecimal"
android:hint="Longitude"
android:autofillHints="" />
</LinearLayout>