commit a7e5f81b430f2d2614fb7e86cb88f93fc8f4c173
parent 2fd56cdebe056eed9e93842ac0f77d0f853d9c6a
Author: Jacob Thomas <41988041+bocajthomas@users.noreply.github.com>
Date:   Wed,  8 May 2024 22:06:14 +0100

feat(core/colors): theme picker  (#997)

---------

Co-authored-by: rhunk <101876869+rhunk@users.noreply.github.com>
Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt | 26+++++++++++++++++++++++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt | 229++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
3 files changed, 223 insertions(+), 123 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -356,41 +356,59 @@ "name": "Enable App Appearance Settings", "description": "Enables the hidden App Appearance Setting\nMay not be required on newer Snapchat versions" }, - "amoled_dark_mode": { - "name": "AMOLED Dark Mode", - "description": "Enables AMOLED dark mode\nMake sure Snapchats Dark mode is enabled" - }, "customize_ui": { "name": "Colors", "description": "Customize Snapchats Colors", "properties": { - "text_color": { - "name": "Text Color", - "description": "Changes Snapchats text color\nInput hex color code" - }, - "send_and_received_text_color": { - "name": "Sent & Received Text Color", - "description": "Changes Snapchats Sent and Received text color on the friend feed\nInput a hex color code" - }, - "background_color": { - "name": "Background Color", - "description": "Changes Snapchats background color\nInput a hex color code" - }, - "background_color_surface": { - "name": "Background Surface Color", - "description": "Changes Snapchats background surface color\nInput a hex color code" - }, - "action_menu_background_color": { - "name": "Action Menu Background Color", - "description": "Changes Snapchats chat action menu background color\nInput a hex color code" + "theme_picker": { + "name": "Theme Picker", + "description": "Preset Snapchat Themes" }, - "action_menu_round_background_color": { - "name": "Action Menu Round Background Color", - "description": "Changes Snapchats chat action menu round background color\nInput a hex color code" - }, - "camera_grid_lines": { - "name": "Camera Gridlines Color", - "description": "Changes Snapchats Gridlines color on the Camera Preview \nInput a hex color code\nNote: Enable the grid on the my camera settings" + "colors": { + "name": "Custom Colors", + "description": "Customize Individual colors\nNote: Select Custom Colors on Theme Picker to use", + "properties": { + "text_color": { + "name": "Main Text Color", + "description": "Changes Snapchats main text color" + }, + "chat_chat_text_color": { + "name": "Main Friend Feed Text Color", + "description": "Changes the text color of ( New Chat / New Snap And Chats / Typing / Calling / Missed call / Speaking / New Voice Note ) on the friend feed" + }, + "pending_sending_text_color": { + "name": "Secondary Friend Feed Text Color", + "description": "Changes the text color of ( Delivered / Recevived / Sending / Opened / Tap To Chat / Hold To Replay / Replayed / Saved In Chat / Called ) on the friend feed" + }, + "snap_with_sound_text_color": { + "name": "Snaps With Sound Text Color", + "description": "Changes the text color of ( New Snap ) on the friend feed\nNote: Video Snaps Only" + }, + "snap_without_sound_text_color": { + "name": "Snaps Without Sound Text Color", + "description": "Changes the text color of ( New Snap ) on the friend feed\nNote: Video Snaps Only" + }, + "background_color": { + "name": "Background Color", + "description": "Changes Snapchats background color" + }, + "background_color_surface": { + "name": "Background Surface Color", + "description": "Changes Snapchats background surface color" + }, + "action_menu_background_color": { + "name": "Action Menu Background Color", + "description": "Changes Snapchats chat action menu background color" + }, + "action_menu_round_background_color": { + "name": "Action Menu Round Background Color", + "description": "Changes Snapchats chat action menu round background color" + }, + "camera_grid_lines": { + "name": "Camera Gridlines Color", + "description": "Changes Snapchats Gridlines color on the Camera Preview\nNote: Enable the grid on the my camera settings" + } + } } } }, @@ -1020,6 +1038,19 @@ "friend_add_source": "Show friend add source", "group": "Group notifications" }, + "theme_picker": { + "amoled_dark_mode": "AMOLED Dark Mode", + "custom": "Custom Colors", + "light_blue": "Light Blue", + "dark_blue": "Dark Blue", + "earthy_autumn": "Earthy Autumn", + "mint_chocolate": "Mint Chocolate", + "ginger_snap": "Ginger Snap", + "lemon_meringue": "Lemon Meringue", + "lava_flow": "Lava Flow", + "ocean_fog": "Ocean Fog", + "alien_landscape": "Alien Landscape" + }, "friend_feed_menu_buttons": { "auto_download": "\u2B07\uFE0F Auto Download", "auto_save": "\uD83D\uDCAC Auto Save Messages", diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt @@ -18,9 +18,13 @@ class UserInterfaceTweaks : ConfigContainer() { val amount = integer("amount", defaultValue = 1) } - inner class CustomizeUIConfig : ConfigContainer(hasGlobalState = true) { + + class ColorsConfig : ConfigContainer() { val textColor = color("text_color") - val sendAndReceivedTextColor = color("send_and_received_text_color") + val chatChatTextColor = color("chat_chat_text_color") + val pendingSendingTextColor = color("pending_sending_text_color") + val snapWithSoundTextColor = color("snap_with_sound_text_color") + val snapWithoutSoundTextColor = color("snap_without_sound_text_color") val backgroundColor = color("background_color") val backgroundColorSurface = color("background_color_surface") val actionMenuBackgroundColor = color("action_menu_background_color") @@ -28,12 +32,28 @@ class UserInterfaceTweaks : ConfigContainer() { val cameraGridLines = color("camera_grid_lines") } + inner class CustomizeUIConfig : ConfigContainer() { + val themePicker = unique("theme_picker", + "custom", + "amoled_dark_mode", + "light_blue", + "dark_blue", + "earthy_autumn", + "mint_chocolate", + "ginger_snap", + "lemon_meringue", + "lava_flow", + "ocean_fog", + "alien_landscape", + ) + val colors = container("colors", ColorsConfig()) { requireRestart() } + } + val friendFeedMenuButtons = multiple( "friend_feed_menu_buttons","conversation_info", "mark_snaps_as_seen", "mark_stories_as_seen_locally", *MessagingRuleType.entries.filter { it.showInFriendMenu }.map { it.key }.toTypedArray() ).apply { set(mutableListOf("conversation_info", MessagingRuleType.STEALTH.key)) } - val amoledDarkMode = boolean("amoled_dark_mode") { requireRestart() } val customizeUi = container("customize_ui", CustomizeUIConfig()) { addNotices(FeatureNotice.UNSTABLE); requireRestart() } val friendFeedMessagePreview = container("friend_feed_message_preview", FriendFeedMessagePreview()) { requireRestart() } val snapPreview = boolean("snap_preview") { addNotices(FeatureNotice.UNSTABLE); requireRestart() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt @@ -1,8 +1,8 @@ package me.rhunk.snapenhance.core.features.impl.ui import android.content.res.TypedArray -import android.graphics.Color import android.graphics.drawable.ColorDrawable +import android.util.TypedValue import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.util.hook.HookStage @@ -11,34 +11,32 @@ import me.rhunk.snapenhance.core.util.hook.hook import me.rhunk.snapenhance.core.util.ktx.getIdentifier class CustomizeUI: Feature("Customize UI", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { - private fun parseColor(color: String): Int? { - return color.takeIf { it.isNotEmpty() }?.let { - runCatching { Color.parseColor(color) }.getOrNull() - } + private fun getAttribute(name: String): Int { + return context.resources.getIdentifier(name, "attr") } override fun onActivityCreate() { - val isAmoledMode = context.config.userInterface.amoledDarkMode.get() - val isCustomizeUI = context.config.userInterface.customizeUi.globalState == true - - if (!isAmoledMode && !isCustomizeUI) return - - //TODO: color picker val customizeUIConfig = context.config.userInterface.customizeUi - val effectiveTextColor = customizeUIConfig.textColor.getNullable() - val effectivesendAndReceivedTextColor = customizeUIConfig.sendAndReceivedTextColor.getNullable() - val effectiveBackgroundColor = customizeUIConfig.backgroundColor.getNullable() - val effectiveBackgroundColorSurface = customizeUIConfig.backgroundColorSurface.getNullable() - val effectiveActionMenuBackgroundColor = customizeUIConfig.actionMenuBackgroundColor.getNullable() - val effectiveActionMenuRoundBackgroundColor = customizeUIConfig.actionMenuRoundBackgroundColor.getNullable() - val effectiveCameraGridLines = customizeUIConfig.cameraGridLines.getNullable() - - - val attributeCache = mutableMapOf<String, Int>() - - fun getAttribute(name: String): Int { - if (attributeCache.containsKey(name)) return attributeCache[name]!! - return context.resources.getIdentifier(name, "attr").also { attributeCache[name] = it } + val themePicker = customizeUIConfig.themePicker.getNullable() ?: return + val colorsConfig = context.config.userInterface.customizeUi.colors + + if (themePicker == "custom") { + themes.clear() + themes[themePicker] = mapOf( + "sigColorTextPrimary" to colorsConfig.textColor.getNullable(), + "sigColorChatChat" to colorsConfig.chatChatTextColor.getNullable(), + "sigColorChatPendingSending" to colorsConfig.pendingSendingTextColor.getNullable(), + "sigColorChatSnapWithSound" to colorsConfig.snapWithSoundTextColor.getNullable(), + "sigColorChatSnapWithoutSound" to colorsConfig.snapWithoutSoundTextColor.getNullable(), + "sigColorBackgroundMain" to colorsConfig.backgroundColor.getNullable(), + "sigColorBackgroundSurface" to colorsConfig.backgroundColorSurface.getNullable(), + "actionSheetBackgroundDrawable" to colorsConfig.actionMenuBackgroundColor.getNullable(), + "actionSheetRoundedBackgroundDrawable" to colorsConfig.actionMenuRoundBackgroundColor.getNullable(), + "sigExceptionColorCameraGridLines" to colorsConfig.cameraGridLines.getNullable(), + ).apply { + }.filterValues { it != null }.map { (key, value) -> + getAttribute(key) to value!! + }.toMap() } context.androidContext.theme.javaClass.getMethod("obtainStyledAttributes", IntArray::class.java).hook( @@ -52,75 +50,126 @@ class CustomizeUI: Feature("Customize UI", loadParams = FeatureLoadParams.ACTIVI } } - if (isAmoledMode) { - when (array[0]) { - getAttribute("sigColorTextPrimary") -> { - ephemeralHook("getColor", 0xFFFFFFFF.toInt()) - } - getAttribute("sigColorBackgroundMain"), - getAttribute("sigColorBackgroundSurface") -> { - ephemeralHook("getColor", 0xFF000000.toInt()) - } - getAttribute("actionSheetBackgroundDrawable"), - getAttribute("actionSheetRoundedBackgroundDrawable") -> { - ephemeralHook("getDrawable", ColorDrawable(0xFF000000.toInt())) - } - } - } - - if (isCustomizeUI) { - when (array[0]) { - getAttribute("sigColorTextPrimary") -> { - ephemeralHook("getColor", effectiveTextColor ?: return@hook) - } - - getAttribute("sigColorTextSecondary") -> { - ephemeralHook("getColor", effectiveTextColor ?: return@hook) - } - - getAttribute("sigColorBackgroundMain") -> { - ephemeralHook("getColor", effectiveBackgroundColor ?: return@hook) - } - - getAttribute("sigColorBackgroundSurface") -> { - ephemeralHook("getColor", effectiveBackgroundColorSurface ?: return@hook) - } - - getAttribute("actionSheetBackgroundDrawable") -> { - ephemeralHook("getDrawable", ColorDrawable(effectiveActionMenuBackgroundColor ?: return@hook)) - } - - getAttribute("actionSheetRoundedBackgroundDrawable") -> { - ephemeralHook("getDrawable", ColorDrawable(effectiveActionMenuRoundBackgroundColor ?: return@hook)) - } - - getAttribute("sigColorChatActivity") -> { - ephemeralHook("getColor", effectivesendAndReceivedTextColor ?: return@hook) + themes[themePicker]?.get(array[0])?.let { value -> + when (val attributeType = result.getType(0)) { + TypedValue.TYPE_INT_COLOR_ARGB8, TypedValue.TYPE_INT_COLOR_RGB8, TypedValue.TYPE_INT_COLOR_ARGB4, TypedValue.TYPE_INT_COLOR_RGB4 -> { + ephemeralHook("getColor", (value as Number).toInt()) } - - getAttribute("sigColorChatChat") -> { - ephemeralHook("getColor", effectivesendAndReceivedTextColor ?: return@hook) - } - - getAttribute("sigColorChatPendingSending") -> { - ephemeralHook("getColor", effectivesendAndReceivedTextColor ?: return@hook) - } - - getAttribute("sigColorChatSnapWithSound") -> { - ephemeralHook("getColor", effectivesendAndReceivedTextColor ?: return@hook) - } - - getAttribute("sigColorChatSnapWithoutSound") -> { - ephemeralHook("getColor", effectivesendAndReceivedTextColor ?: return@hook) - } - - getAttribute("sigExceptionColorCameraGridLines") -> { - ephemeralHook("getColor", effectiveCameraGridLines ?: return@hook) + TypedValue.TYPE_STRING -> { + val stringValue = result.getString(0) + if (stringValue?.endsWith(".xml") == true) { + ephemeralHook("getDrawable", ColorDrawable((value as Number).toInt())) + } } + else -> context.log.warn("unknown attribute type: ${attributeType.toString(16)}") } } } } -} - + private val themes by lazy { + mapOf( + "amoled_dark_mode" to mapOf( + "sigColorTextPrimary" to 0xFFFFFFFF, + "sigColorBackgroundMain" to 0xFF000000, + "sigColorBackgroundSurface" to 0xFF000000, + "actionSheetBackgroundDrawable" to 0xFF000000, + "actionSheetRoundedBackgroundDrawable" to 0xFF000000 + ), + "light_blue" to mapOf( + "sigColorTextPrimary" to 0xFF03BAFC, + "sigColorBackgroundMain" to 0xFFBDE6FF, + "sigColorBackgroundSurface" to 0xFF78DBFF, + "actionSheetBackgroundDrawable" to 0xFF78DBFF, + "sigColorChatChat" to 0xFF08D6FF, + "sigExceptionColorCameraGridLines" to 0xFF08D6FF + ), + "dark_blue" to mapOf( + "sigColorTextPrimary" to 0xFF98C2FD, + "sigColorBackgroundMain" to 0xFF192744, + "sigColorBackgroundSurface" to 0xFF192744, + "actionSheetBackgroundDrawable" to 0xFF192744, + "sigColorChatChat" to 0xFF98C2FD, + "sigExceptionColorCameraGridLines" to 0xFF192744 + ), + "earthy_autumn" to mapOf( + "sigColorTextPrimary" to 0xFFF7CAC9, + "sigColorBackgroundMain" to 0xFF800000, + "sigColorBackgroundSurface" to 0xFF800000, + "actionSheetBackgroundDrawable" to 0xFF800000, + "sigColorChatChat" to 0xFFF7CAC9, + "sigExceptionColorCameraGridLines" to 0xFF800000 + ), + "mint_chocolate" to mapOf( + "sigColorTextPrimary" to 0xFFFFFFFF, + "sigColorBackgroundMain" to 0xFF98FF98, + "sigColorBackgroundSurface" to 0xFF98FF98, + "actionSheetBackgroundDrawable" to 0xFF98FF98, + "sigColorChatChat" to 0xFFFFFFFF, + "sigColorChatPendingSending" to 0xFFFFFFFF, + "sigColorChatSnapWithSound" to 0xFFFFFFFF, + "sigColorChatSnapWithoutSound" to 0xFFFFFFFF, + "sigExceptionColorCameraGridLines" to 0xFF98FF98 + ), + "ginger_snap" to mapOf( + "sigColorTextPrimary" to 0xFFFFFFFF, + "sigColorBackgroundMain" to 0xFFC6893A, + "sigColorBackgroundSurface" to 0xFFC6893A, + "actionSheetBackgroundDrawable" to 0xFFC6893A, + "sigColorChatChat" to 0xFFFFFFFF, + "sigColorChatPendingSending" to 0xFFFFFFFF, + "sigColorChatSnapWithSound" to 0xFFFFFFFF, + "sigColorChatSnapWithoutSound" to 0xFFFFFFFF, + "sigExceptionColorCameraGridLines" to 0xFFC6893A + ), + "lemon_meringue" to mapOf( + "sigColorTextPrimary" to 0xFF000000, + "sigColorBackgroundMain" to 0xFFFCFFE7, + "sigColorBackgroundSurface" to 0xFFFCFFE7, + "actionSheetBackgroundDrawable" to 0xFFFCFFE7, + "sigColorChatChat" to 0xFF000000, + "sigColorChatPendingSending" to 0xFF000000, + "sigColorChatSnapWithSound" to 0xFF000000, + "sigColorChatSnapWithoutSound" to 0xFF000000, + "sigExceptionColorCameraGridLines" to 0xFFFCFFE7 + ), + "lava_flow" to mapOf( + "sigColorTextPrimary" to 0xFFFFCC00, + "sigColorBackgroundMain" to 0xFFC70039, + "sigColorBackgroundSurface" to 0xFFC70039, + "actionSheetBackgroundDrawable" to 0xFFC70039, + "sigColorChatChat" to 0xFFFFCC00, + "sigColorChatPendingSending" to 0xFFFFCC00, + "sigColorChatSnapWithSound" to 0xFFFFCC00, + "sigColorChatSnapWithoutSound" to 0xFFFFCC00, + "sigExceptionColorCameraGridLines" to 0xFFC70039 + ), + "ocean_fog" to mapOf( + "sigColorTextPrimary" to 0xFF333333, + "sigColorBackgroundMain" to 0xFFB0C4DE, + "sigColorBackgroundSurface" to 0xFFB0C4DE, + "actionSheetBackgroundDrawable" to 0xFFB0C4DE, + "sigColorChatChat" to 0xFF333333, + "sigColorChatPendingSending" to 0xFF333333, + "sigColorChatSnapWithSound" to 0xFF333333, + "sigColorChatSnapWithoutSound" to 0xFF333333, + "sigExceptionColorCameraGridLines" to 0xFFB0C4DE + ), + "alien_landscape" to mapOf( + "sigColorTextPrimary" to 0xFFFFFFFF, + "sigColorBackgroundMain" to 0xFF9B59B6, + "sigColorBackgroundSurface" to 0xFF9B59B6, + "actionSheetBackgroundDrawable" to 0xFF9B59B6, + "sigColorChatChat" to 0xFFFFFFFF, + "sigColorChatPendingSending" to 0xFFFFFFFF, + "sigColorChatSnapWithSound" to 0xFFFFFFFF, + "sigColorChatSnapWithoutSound" to 0xFFFFFFFF, + "sigExceptionColorCameraGridLines" to 0xFF9B59B6 + ) + ).mapValues { (_, attributes) -> + attributes.map { (key, value) -> + getAttribute(key) to value as Any + }.toMap() + }.toMutableMap() + } +}