diff --git a/README.md b/README.md index 32dec684d..a8c8aaec1 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,7 @@ Thanks to everyone involved including the [third-party libraries](https://github - [TheVisual](https://github.com/TheVisual) - [CanerKaraca23](https://github.com/CanerKaraca23) - [bocajthomas](https://github.com/bocajthomas) + ## Donate - LTC: LbBnT9GxgnFhwy891EdDKqGmpn7XtduBdE - BCH: qpu57a05kqljjadvpgjc6t894apprvth9slvlj4vpj diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt index de011c4fe..d53c8dc33 100644 --- a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt +++ b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt @@ -2,6 +2,7 @@ package me.rhunk.snapenhance.ui.util import android.content.Context import android.view.MotionEvent +import android.widget.Toast import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -175,6 +176,7 @@ class AlertDialogs( @Composable fun KeyboardInputDialog(property: PropertyPair<*>, dismiss: () -> Unit = {}) { val focusRequester = remember { FocusRequester() } + val context = LocalContext.current DefaultDialogCard { var fieldValue by remember { @@ -215,7 +217,7 @@ class AlertDialogs( } Button(onClick = { if (fieldValue.text.isNotEmpty() && property.key.params.inputCheck?.invoke(fieldValue.text) == false) { - dismiss() + Toast.makeText(context, "Invalid input! Make sure you entered a valid value.", Toast.LENGTH_SHORT).show() //TODO: i18n return@Button } diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json index a07524b54..8fc96376f 100644 --- a/common/src/main/assets/lang/en_US.json +++ b/common/src/main/assets/lang/en_US.json @@ -358,9 +358,35 @@ "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_colour": { + "name": "Text Color", + "description": "Changes Snapchats text color\nInput hex color code" + }, + "chat_colour": { + "name": "Sent & Received Color", + "description": "Changes Snapchats Sent and Received text color on the friend feed\nInput a hex color code" + }, + "background_colour": { + "name": "Background Color", + "description": "Changes Snapchats background color\nInput a hex color code" + }, + "background_colour_surface": { + "name": "Background Surface Color", + "description": "Changes Snapchats background surface color\nInput a hex color code" + }, + "action_menu_background_colour": { + "name": "Action Menu Background Color", + "description": "Changes Snapchats chat action menu background color\nInput a hex color code" + }, + "action_menu_round_background_colour": { + "name": "Action Menu Round Background Color", + "description": "Changes Snapchats chat action menu round background color\nInput a hex color code" + } + } }, "friend_feed_message_preview": { "name": "Friend Feed Message Preview", 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 index b173f1933..2b8e0b392 100644 --- 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 @@ -1,5 +1,6 @@ package me.rhunk.snapenhance.common.config.impl +import android.graphics.Color import me.rhunk.snapenhance.common.config.ConfigContainer import me.rhunk.snapenhance.common.config.FeatureNotice import me.rhunk.snapenhance.common.data.MessagingRuleType @@ -18,12 +19,24 @@ class UserInterfaceTweaks : ConfigContainer() { val amount = integer("amount", defaultValue = 1) } + inner class CustomizeUIConfig : ConfigContainer(hasGlobalState = true) { + private val checkInputColor = { value: String -> + value.isEmpty() || runCatching { Color.parseColor(value) }.isSuccess + } + val textColour = string("text_colour") { inputCheck = checkInputColor } + val backgroundColour = string("background_colour") { inputCheck = checkInputColor } + val backgroundColourSurface = string("background_colour_surface") { inputCheck = checkInputColor } + val actionMenuBackgroundColour = string("action_menu_background_colour") { inputCheck = checkInputColor } + val actionMenuRoundBackgroundColour = string("action_menu_round_background_colour") { inputCheck = checkInputColor } + val chatColour = string("chat_colour") { inputCheck = checkInputColor } + } + 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") { addNotices(FeatureNotice.UNSTABLE); 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() } val bootstrapOverride = container("bootstrap_override", BootstrapOverride()) { requireRestart() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt index bd9ba2467..6410e792b 100644 --- a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt +++ b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt @@ -84,7 +84,6 @@ class FeatureManager( AppLock(), CameraTweaks(), InfiniteStoryBoost(), - AmoledDarkMode(), PinConversations(), DeviceSpooferHook(), ClientBootstrapOverride(), @@ -120,6 +119,7 @@ class FeatureManager( AccountSwitcher(), RemoveGroupsLockedStatus(), BypassMessageActionRestrictions(), + CustomizeUI(), BetterLocation(), MediaFilePicker(), HideActiveMusic(), @@ -128,7 +128,6 @@ class FeatureManager( ComposerHooks(), DisableCustomTabs(), ) - initializeFeatures() } @@ -190,4 +189,4 @@ class FeatureManager( context.log.verbose("feature manager onActivityCreate took $it ms") } } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AmoledDarkMode.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AmoledDarkMode.kt deleted file mode 100644 index 7d117faf2..000000000 --- a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AmoledDarkMode.kt +++ /dev/null @@ -1,50 +0,0 @@ -package me.rhunk.snapenhance.core.features.impl.experiments - -import android.annotation.SuppressLint -import android.content.res.TypedArray -import android.graphics.drawable.ColorDrawable -import me.rhunk.snapenhance.core.features.Feature -import me.rhunk.snapenhance.core.features.FeatureLoadParams -import me.rhunk.snapenhance.core.util.hook.HookStage -import me.rhunk.snapenhance.core.util.hook.Hooker -import me.rhunk.snapenhance.core.util.hook.hook -import me.rhunk.snapenhance.core.util.ktx.getIdentifier - -class AmoledDarkMode : Feature("Amoled Dark Mode", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { - @SuppressLint("DiscouragedApi") - override fun onActivityCreate() { - if (!context.config.userInterface.amoledDarkMode.get()) return - val attributeCache = mutableMapOf() - - fun getAttribute(name: String): Int { - if (attributeCache.containsKey(name)) return attributeCache[name]!! - return context.resources.getIdentifier(name, "attr").also { attributeCache[name] = it } - } - - context.androidContext.theme.javaClass.getMethod("obtainStyledAttributes", IntArray::class.java).hook( - HookStage.AFTER) { param -> - val array = param.arg(0) - val result = param.getResult() as TypedArray - - fun ephemeralHook(methodName: String, content: Any) { - Hooker.ephemeralHookObjectMethod(result::class.java, result, methodName, HookStage.BEFORE) { - it.setResult(content) - } - } - - 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())) - } - } - } - } -} \ No newline at end of file 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 new file mode 100644 index 000000000..35088db23 --- /dev/null +++ b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/CustomizeUI.kt @@ -0,0 +1,111 @@ +package me.rhunk.snapenhance.core.features.impl.ui + +import android.content.res.TypedArray +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import me.rhunk.snapenhance.core.features.Feature +import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.util.hook.HookStage +import me.rhunk.snapenhance.core.util.hook.Hooker +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() + } + } + + 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 effectiveTextColour by lazy { parseColor(customizeUIConfig.textColour.get()) } + val effectiveBackgroundColour by lazy { parseColor(customizeUIConfig.backgroundColour.get()) } + val effectiveBackgroundColourSurface by lazy { parseColor(customizeUIConfig.backgroundColourSurface.get()) } + val effectiveActionMenuBackgroundColour by lazy { parseColor(customizeUIConfig.actionMenuBackgroundColour.get()) } + val effectiveActionMenuRoundBackgroundColour by lazy { parseColor(customizeUIConfig.actionMenuRoundBackgroundColour.get()) } + val effectiveChatColour by lazy { parseColor(customizeUIConfig.chatColour.get()) } + + val attributeCache = mutableMapOf() + + fun getAttribute(name: String): Int { + if (attributeCache.containsKey(name)) return attributeCache[name]!! + return context.resources.getIdentifier(name, "attr").also { attributeCache[name] = it } + } + + context.androidContext.theme.javaClass.getMethod("obtainStyledAttributes", IntArray::class.java).hook( + HookStage.AFTER) { param -> + val array = param.arg(0) + val result = param.getResult() as TypedArray + + fun ephemeralHook(methodName: String, content: Any) { + Hooker.ephemeralHookObjectMethod(result::class.java, result, methodName, HookStage.BEFORE) { + it.setResult(content) + } + } + + 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", effectiveTextColour ?: return@hook) + } + + getAttribute("sigColorBackgroundMain") -> { + ephemeralHook("getColor", effectiveBackgroundColour ?: return@hook) + } + + getAttribute("sigColorBackgroundSurface") -> { + ephemeralHook("getColor", effectiveBackgroundColourSurface ?: return@hook) + } + + getAttribute("actionSheetBackgroundDrawable") -> { + ephemeralHook("getDrawable", ColorDrawable(effectiveActionMenuBackgroundColour ?: return@hook)) + } + + getAttribute("actionSheetRoundedBackgroundDrawable") -> { + ephemeralHook("getDrawable", ColorDrawable(effectiveActionMenuRoundBackgroundColour ?: return@hook)) + } + getAttribute("sigColorChatActivity") -> { + ephemeralHook("getColor", effectiveChatColour ?: return@hook) + } + getAttribute("sigColorChatChat") -> { + ephemeralHook("getColor", effectiveChatColour ?: return@hook) + } + getAttribute("sigColorChatPendingSending") -> { + ephemeralHook("getColor", effectiveChatColour ?: return@hook) + } + getAttribute("sigColorChatSnapWithSound") -> { + ephemeralHook("getColor", effectiveChatColour ?: return@hook) + } + getAttribute("sigColorChatSnapWithoutSound") -> { + ephemeralHook("getColor", effectiveChatColour ?: return@hook) + } + } + } + } + } +} + +