diff --git a/plugins/cuc/index.js b/plugins/cuc/index.js new file mode 100644 index 0000000..fc34cc5 --- /dev/null +++ b/plugins/cuc/index.js @@ -0,0 +1,126 @@ +import settingPage from "./setting"; +import { makeDefaults } from "./util"; + +import { before, after } from "@vendetta/patcher"; +import { storage } from "@vendetta/plugin"; +import { logger } from "@vendetta"; +import { React, ReactNative, FluxDispatcher, constants } from "@vendetta/metro/common" +import { find, findByProps, findByStoreName, findByName } from "@vendetta/metro"; +import { General } from "@vendetta/ui/components"; +import { semanticColors } from "@vendetta/ui"; + +const { Text } = General; + +const ThemeStore = findByStoreName("ThemeStore"); +const resolveSemanticColor = find(m => m.default?.internal?.resolveSemanticColor)?.default.internal.resolveSemanticColor + ?? find(m => m.meta?.resolveSemanticColor)?.meta.resolveSemanticColor ?? (() => {}); + +const UserStore = findByStoreName("UserStore"); +const RelationshipStore = findByStoreName("RelationshipStore"); +const GuildMemberStore = findByStoreName("GuildMemberStore"); +const TypingWrapper = findByProps("TYPING_WRAPPER_HEIGHT"); +const { DCDChatManager } = ReactNative.NativeModules; + +makeDefaults(storage, { + colors: { + hex: "#DAFAF0", + }, + switches: { + enableUsername: true, + enableReply: false, + enableType: false, + }, +}) + +function resolveColor(s) { + if(s?.colors?.hex) return ReactNative.processColor(s.colors.hex) + return 0; +} + +const patches = []; + +export default { + onLoad: () => { + patches.push( + before("updateRows", DCDChatManager, (args) => { + let rows = JSON.parse(args[1]); + + for (const row of rows) { + const { message } = row + if(!message) continue; + + const handleColor = (m) => { + return m.usernameColor = resolveColor(storage) + }; + + if(storage?.switches?.enableUsername) { + handleColor(message) + } + + if( + message?.referencedMessage?.message && + storage?.switches?.enableReply + ) { + handleColor(message?.referencedMessage?.message) + } + } + args[1] = JSON.stringify(rows); + }), + + after("default", TypingWrapper, ([{ channel }], res) => { + if (!storage?.switches?.enableType) return; + if (!res) return; + const Typing = res.props?.children; + const defaultTypingColor = resolveSemanticColor(ThemeStore.theme, semanticColors.HEADER_SECONDARY); + + const unpatchTyping = after("type", Typing, (_, res) => { + React.useEffect(() => () => { unpatchTyping() }, []); + const typingThing = res?.props?.children?.[0]?.props?.children?.[1]?.props; + + if ( + !typingThing || + !typingThing.children || + typingThing.children?.toLowerCase() === "several people are typing..." + ) return; + + const users = TypingWrapper.useTypingUserIds(channel.id).map(user => { + const member = GuildMemberStore.getMember(channel.guild_id, user); + const userobj = UserStore.getUser(user); + const name = (member?.nick || RelationshipStore.getNickname(user) || userobj.globalName || userobj.username); + const color = (storage?.colors?.hex || defaultTypingColor); + + return {displayName: name, displayColor: color}; + }); + + function userElem(user) { + return React.createElement( + Text, + { + style: { + color: user.displayColor, + fontFamily: constants.Fonts.DISPLAY_SEMIBOLD + } + }, + user.displayName + ); + }; + + if (!users || users.length < 1) return; + + typingThing.children = ( + users.length === 1 ? + [userElem(users[0]), " is typing..."] : + [ + ...users.slice(0, users.length - 1).flatMap((el, i) => [userElem(el), i < (users.length-2) ? ", " : " and "]), + userElem(users[users.length - 1]), " are typing..." + ] + ) + }); + }), + ) + }, + onUnload: () => { + patches.forEach(un => un()); + }, + settings: settingPage +} diff --git a/plugins/cuc/manifest.json b/plugins/cuc/manifest.json new file mode 100644 index 0000000..fde2b9a --- /dev/null +++ b/plugins/cuc/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "Custom Username Color 0.1", + "description": "Override Username Colors and customize them.", + "authors": [ + { + "name": "Angelica", + "id": "692632336961110087" + } + ], + "main": "index.js", + "vendetta": { + "icon": "ic_legacy_username" + } +} \ No newline at end of file diff --git a/plugins/cuc/setting.jsx b/plugins/cuc/setting.jsx new file mode 100644 index 0000000..af3da63 --- /dev/null +++ b/plugins/cuc/setting.jsx @@ -0,0 +1,93 @@ +import * as util from "./util"; + +import { ReactNative } from "@vendetta/metro/common"; +import { General, Forms } from "@vendetta/ui/components"; +import { getAssetIDByName } from "@vendetta/ui/assets"; +import { storage } from "@vendetta/plugin"; +import { useProxy } from "@vendetta/storage"; +import { find, findByName, findByProps, findByStoreName } from "@vendetta/metro"; +import { showToast } from "@vendetta/ui/toasts"; + +const { openLazy, hideActionSheet } = findByProps("openLazy", "hideActionSheet"); +const { ScrollView, View, Text, TouchableOpacity, TextInput, Pressable, Image } = General; +const { FormIcon, FormSwitchRow, FormSwitch, FormRow, FormInput, FormDivider } = Forms; +const CustomColorPickerActionSheet = findByName("CustomColorPickerActionSheet"); + +function numToHex(numericColor) { + const red = (numericColor >> 16) & 255; + const green = (numericColor >> 8) & 255; + const blue = numericColor & 255; + return `#${((1 << 24) | (red << 16) | (green << 8) | blue).toString(16).slice(1)}`; +} + +function createSwitch(id, label, sub, def, icon) { + return { id, label, sub, def, icon } +} + + +const switches = [ + createSwitch("enableUsername", "Toggle for username", null, true, null), + createSwitch("enableReply", "Toggle for replied messages", null, false, null), + createSwitch("enableType", "Toggle for typing indicator", null, false, null) + +] + +export default () => { + useProxy(storage); + + const pc = (inp) => ReactNative.processColor(inp); + + const whenPressed = () => util?.openSheet( + CustomColorPickerActionSheet, { + color: (pc(storage?.colors?.hex) || 0), + onSelect: (color) => { + const hex = numToHex(color) + storage.colors.hex = hex + // showToast(storage.colors.hex) + } + } + ); + + return ( + + +