Revert "feat: detach Sidebar patches as they"
This reverts commit ecd97c7018.
This commit is contained in:
parent
6ca9423849
commit
a2a20ab1a1
11 changed files with 827 additions and 2 deletions
178
chatboxavatar/index.tsx
Normal file
178
chatboxavatar/index.tsx
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
import { after } from "@api/patcher";
|
||||||
|
import { findByNameLazy, findByProps, findByPropsLazy, findByStoreName, findByTypeDisplayName } from "@metro";
|
||||||
|
import { ReactNative } from "@metro/common";
|
||||||
|
import { definePlugin } from "@plugins";
|
||||||
|
import { Contributors } from "@rain/Developers";
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import ChatboxAvatarSettings from "./settings";
|
||||||
|
import { useChatboxAvatarSettings } from "./storage";
|
||||||
|
|
||||||
|
const Flux = findByProps("useStateFromStores");
|
||||||
|
const ChatInputActions = findByTypeDisplayName("ChatInputActions");
|
||||||
|
const ChatInputSendButton = findByTypeDisplayName("ChatInputSendButton");
|
||||||
|
let hasText = false;
|
||||||
|
let sendBtnRef: { setHasText?: (hasText: boolean) => void } | undefined;
|
||||||
|
const { Pressable, View, Animated } = ReactNative;
|
||||||
|
|
||||||
|
const avatarCollapse = new Animated.Value(0);
|
||||||
|
|
||||||
|
const Avatar = findByPropsLazy("default", "AvatarSizes", "getStatusSize")?.default;
|
||||||
|
|
||||||
|
const UserStore = findByStoreName("UserStore");
|
||||||
|
const SelectedChannelStore = findByStoreName("SelectedChannelStore");
|
||||||
|
const ChannelStore = findByStoreName("ChannelStore");
|
||||||
|
const SelfPresenceStore = findByStoreName("SelfPresenceStore");
|
||||||
|
const showUserProfileActionSheet = findByNameLazy("showUserProfileActionSheet");
|
||||||
|
const showYouAccountActionSheetByProp = findByPropsLazy("showYouAccountActionSheet");
|
||||||
|
|
||||||
|
function AvatarAction() {
|
||||||
|
const [textState, setTextState] = React.useState(false);
|
||||||
|
const self = Flux?.useStateFromStores?.([UserStore], () => UserStore?.getCurrentUser?.());
|
||||||
|
const status = Flux?.useStateFromStores?.([SelfPresenceStore], () => SelfPresenceStore?.getStatus?.());
|
||||||
|
const settings = useChatboxAvatarSettings();
|
||||||
|
const channelId = Flux?.useStateFromStores?.([SelectedChannelStore], () => SelectedChannelStore?.getCurrentlySelectedChannelId?.());
|
||||||
|
const channel = Flux?.useStateFromStores?.([ChannelStore], () => ChannelStore?.getChannel?.(channelId), [channelId]);
|
||||||
|
|
||||||
|
const animated = useRef(avatarCollapse).current;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setTextState(hasText);
|
||||||
|
}, 100);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const shouldCollapse = settings.collapseWhileTyping && textState;
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.timing(animated, {
|
||||||
|
toValue: shouldCollapse ? 1 : 0,
|
||||||
|
duration: 200,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
}, [textState, settings.collapseWhileTyping, animated]);
|
||||||
|
|
||||||
|
if (!self) return null;
|
||||||
|
|
||||||
|
const openAccountSheet = () => {
|
||||||
|
const fn = showYouAccountActionSheetByProp?.showYouAccountActionSheet;
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
try {
|
||||||
|
fn(true, true);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showUserProfileActionSheet?.({ userId: self.id, channelId: channel?.id ?? channelId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePress = () => {
|
||||||
|
switch (settings.pressAction) {
|
||||||
|
case "profile":
|
||||||
|
showUserProfileActionSheet?.({ userId: self.id, channelId: channel?.id ?? channelId });
|
||||||
|
break;
|
||||||
|
case "server":
|
||||||
|
openAccountSheet();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLongPress = () => {
|
||||||
|
switch (settings.longPressAction) {
|
||||||
|
case "profile":
|
||||||
|
showUserProfileActionSheet?.({ userId: self.id, channelId: channel?.id ?? channelId });
|
||||||
|
break;
|
||||||
|
case "server":
|
||||||
|
openAccountSheet();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={{
|
||||||
|
height: 40,
|
||||||
|
width: animated.interpolate({ inputRange: [0, 1], outputRange: [40, 0] }),
|
||||||
|
marginHorizontal: animated.interpolate({ inputRange: [0, 1], outputRange: [4, 0] }),
|
||||||
|
flexShrink: 0,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflow: settings.collapseWhileTyping ? "hidden" : "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pressable
|
||||||
|
onPress={handlePress}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
>
|
||||||
|
{Avatar && (
|
||||||
|
<Avatar
|
||||||
|
user={self}
|
||||||
|
guildId={channel?.guild_id}
|
||||||
|
status={settings.showStatusCutout ? status : undefined}
|
||||||
|
avatarDecoration={self?.avatarDecoration}
|
||||||
|
animate={!settings.collapseWhileTyping || !textState}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpatches: (() => void)[] = [];
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ChatboxAvatar",
|
||||||
|
description: "Adds your avatar to the chatbox.",
|
||||||
|
author: [Contributors.LampDelivery],
|
||||||
|
id: "chatboxavatar",
|
||||||
|
version: "v1.0.0",
|
||||||
|
requiresRestart: true,
|
||||||
|
settings: ChatboxAvatarSettings,
|
||||||
|
async start() {
|
||||||
|
if (!ChatInputActions?.type || !ChatInputSendButton?.type) return;
|
||||||
|
unpatches.push(
|
||||||
|
after("render", ChatInputSendButton.type, (args, ret) => {
|
||||||
|
setImmediate(() => {
|
||||||
|
setImmediate(() => {
|
||||||
|
if (args?.[1]?.current) {
|
||||||
|
sendBtnRef = args[1].current;
|
||||||
|
const origSetHasText = sendBtnRef?.setHasText;
|
||||||
|
unpatches.push(() => { if (sendBtnRef && origSetHasText) sendBtnRef.setHasText = origSetHasText; });
|
||||||
|
if (sendBtnRef) {
|
||||||
|
sendBtnRef.setHasText = (hasText_: boolean) => {
|
||||||
|
hasText = hasText_;
|
||||||
|
if (origSetHasText) return origSetHasText.call(sendBtnRef, hasText_);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
async eagerStart() {
|
||||||
|
if (!ChatInputActions?.type || !ChatInputSendButton?.type) return;
|
||||||
|
unpatches.push(
|
||||||
|
after("render", ChatInputActions.type, (args, ret) => {
|
||||||
|
return React.createElement(
|
||||||
|
View,
|
||||||
|
{ style: { flexDirection: "row", alignItems: "center" } },
|
||||||
|
ret,
|
||||||
|
React.createElement(AvatarAction)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
for (const unpatch of unpatches) unpatch?.();
|
||||||
|
unpatches.length = 0;
|
||||||
|
},
|
||||||
|
});
|
||||||
58
chatboxavatar/settings.tsx
Normal file
58
chatboxavatar/settings.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { Stack, TableRadioGroup, TableRadioRow } from "@metro/common/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { ChatboxAvatarSettings, useChatboxAvatarSettings } from "./storage";
|
||||||
|
export default function ChatboxAvatarSettings() {
|
||||||
|
const settings = useChatboxAvatarSettings();
|
||||||
|
const [, forceUpdate] = React.useReducer(x => ~x, 0);
|
||||||
|
const setPressAction = (value: string) => {
|
||||||
|
useChatboxAvatarSettings.getState().updateSettings?.({ pressAction: value as ChatboxAvatarSettings["pressAction"] });
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
const setLongPressAction = (value: string) => {
|
||||||
|
useChatboxAvatarSettings.getState().updateSettings?.({ longPressAction: value as ChatboxAvatarSettings["longPressAction"] });
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Stack style={{ padding: 16 }} spacing={16}>
|
||||||
|
<TableRadioGroup
|
||||||
|
value={settings.pressAction}
|
||||||
|
onChange={setPressAction}
|
||||||
|
title="Avatar Press Action"
|
||||||
|
>
|
||||||
|
<TableRadioRow value="profile" label="Open Profile" />
|
||||||
|
<TableRadioRow value="server" label="Open Status Picker" />
|
||||||
|
</TableRadioGroup>
|
||||||
|
<TableRadioGroup
|
||||||
|
value={settings.longPressAction}
|
||||||
|
onChange={setLongPressAction}
|
||||||
|
title="Avatar Long-Press Action"
|
||||||
|
>
|
||||||
|
<TableRadioRow value="profile" label="Open Profile" />
|
||||||
|
<TableRadioRow value="server" label="Open Status Picker" />
|
||||||
|
</TableRadioGroup>
|
||||||
|
<TableRadioGroup
|
||||||
|
value={settings.showStatusCutout ? "true" : "false"}
|
||||||
|
onChange={v => {
|
||||||
|
useChatboxAvatarSettings.getState().updateSettings?.({ showStatusCutout: v === "true" });
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
title="Status Icon"
|
||||||
|
>
|
||||||
|
<TableRadioRow value="true" label="Show" />
|
||||||
|
<TableRadioRow value="false" label="Hide" />
|
||||||
|
</TableRadioGroup>
|
||||||
|
<TableRadioGroup
|
||||||
|
value={settings.collapseWhileTyping ? "true" : "false"}
|
||||||
|
onChange={v => {
|
||||||
|
useChatboxAvatarSettings.getState().updateSettings?.({ collapseWhileTyping: v === "true" });
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
title="Collapse While Typing"
|
||||||
|
>
|
||||||
|
<TableRadioRow value="true" label="Collapse avatar while typing" />
|
||||||
|
<TableRadioRow value="false" label="Always show avatar" />
|
||||||
|
</TableRadioGroup>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
chatboxavatar/storage.ts
Normal file
22
chatboxavatar/storage.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { createPluginStore } from "@api/storage";
|
||||||
|
|
||||||
|
export interface ChatboxAvatarSettings {
|
||||||
|
replaceButton: "gallery" | "apps" | "gift";
|
||||||
|
pressAction: "profile" | "server";
|
||||||
|
longPressAction: "profile" | "server";
|
||||||
|
serverId?: string;
|
||||||
|
showStatusCutout?: boolean;
|
||||||
|
collapseWhileTyping?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useStore: useChatboxAvatarSettings,
|
||||||
|
settings: chatboxAvatarSettings,
|
||||||
|
} = createPluginStore<ChatboxAvatarSettings>("chatboxavatar", {
|
||||||
|
replaceButton: "gallery",
|
||||||
|
pressAction: "profile",
|
||||||
|
longPressAction: "server",
|
||||||
|
serverId: "",
|
||||||
|
showStatusCutout: false,
|
||||||
|
collapseWhileTyping: false,
|
||||||
|
});
|
||||||
85
eslint.config.mjs
Normal file
85
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tseslintPlugin from "@typescript-eslint/eslint-plugin";
|
||||||
|
import tseslintParser from "@typescript-eslint/parser";
|
||||||
|
import reactPlugin from "eslint-plugin-react";
|
||||||
|
import globals from "globals";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
parser: tseslintParser,
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.browser,
|
||||||
|
NodeJS: "readonly",
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": tseslintPlugin,
|
||||||
|
react: reactPlugin,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...tseslintPlugin.configs.recommended.rules,
|
||||||
|
...reactPlugin.configs.recommended.rules,
|
||||||
|
indent: ["error", 4, { SwitchCase: 1 }],
|
||||||
|
quotes: ["error", "double"],
|
||||||
|
semi: ["error", "always"],
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/triple-slash-reference": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"eol-last": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.{js,jsx}"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.browser,
|
||||||
|
NodeJS: "readonly",
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
react: reactPlugin,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactPlugin.configs.recommended.rules,
|
||||||
|
indent: ["error", 4, { SwitchCase: 1 }],
|
||||||
|
quotes: ["error", "double"],
|
||||||
|
semi: ["error", "always"],
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"eol-last": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ["dist/**", "node_modules/**"],
|
||||||
|
},
|
||||||
|
];
|
||||||
18
plugins/ChatboxAvatar/manifest.json
Normal file
18
plugins/ChatboxAvatar/manifest.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "Chatbox Avatar",
|
||||||
|
"description": "Quickly view your profile from the chatbox.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Cynosphere",
|
||||||
|
"id": "150745989836308480"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kmmiio99o",
|
||||||
|
"id": "879393496627306587"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"main": "src/index.tsx",
|
||||||
|
"vendetta": {
|
||||||
|
"icon": "UserSquareIcon"
|
||||||
|
}
|
||||||
|
}
|
||||||
165
plugins/ChatboxAvatar/src/index.tsx
Normal file
165
plugins/ChatboxAvatar/src/index.tsx
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { after } from "@vendetta/patcher";
|
||||||
|
import { findByName, findByProps, findByStoreName, findByDisplayName } from "@vendetta/metro";
|
||||||
|
import { ReactNative } from "@vendetta/metro/common";
|
||||||
|
import { plugin } from "@vendetta";
|
||||||
|
import React, { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import ChatboxAvatarSettings from "./settings";
|
||||||
|
|
||||||
|
const Flux = findByProps("useStateFromStores");
|
||||||
|
const ChatInputActions = findByDisplayName("ChatInputActions");
|
||||||
|
const ChatInputSendButton = findByDisplayName("ChatInputSendButton");
|
||||||
|
let hasText = false;
|
||||||
|
let sendBtnRef: { setHasText?: (hasText: boolean) => void } | undefined;
|
||||||
|
const { Pressable, View, Animated } = ReactNative;
|
||||||
|
|
||||||
|
const avatarCollapse = new Animated.Value(0);
|
||||||
|
|
||||||
|
const Avatar = findByProps("default", "AvatarSizes", "getStatusSize")?.default;
|
||||||
|
|
||||||
|
const UserStore = findByStoreName("UserStore");
|
||||||
|
const SelectedChannelStore = findByStoreName("SelectedChannelStore");
|
||||||
|
const ChannelStore = findByStoreName("ChannelStore");
|
||||||
|
const SelfPresenceStore = findByStoreName("SelfPresenceStore");
|
||||||
|
const showUserProfileActionSheet = findByName("showUserProfileActionSheet");
|
||||||
|
const showYouAccountActionSheetByProp = findByProps("showYouAccountActionSheet");
|
||||||
|
|
||||||
|
function AvatarAction() {
|
||||||
|
const [textState, setTextState] = React.useState(false);
|
||||||
|
const self = Flux?.useStateFromStores?.([UserStore], () => UserStore?.getCurrentUser?.());
|
||||||
|
const status = Flux?.useStateFromStores?.([SelfPresenceStore], () => SelfPresenceStore?.getStatus?.());
|
||||||
|
const settings = plugin.storage;
|
||||||
|
const channelId = Flux?.useStateFromStores?.([SelectedChannelStore], () => SelectedChannelStore?.getCurrentlySelectedChannelId?.());
|
||||||
|
const channel = Flux?.useStateFromStores?.([ChannelStore], () => ChannelStore?.getChannel?.(channelId), [channelId]);
|
||||||
|
|
||||||
|
const animated = useRef(avatarCollapse).current;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setTextState(hasText);
|
||||||
|
}, 100);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const shouldCollapse = settings.collapseWhileTyping && textState;
|
||||||
|
Animated.parallel([
|
||||||
|
Animated.timing(animated, {
|
||||||
|
toValue: shouldCollapse ? 1 : 0,
|
||||||
|
duration: 200,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}),
|
||||||
|
]).start();
|
||||||
|
}, [textState, settings.collapseWhileTyping, animated]);
|
||||||
|
|
||||||
|
if (!self) return null;
|
||||||
|
|
||||||
|
const openAccountSheet = () => {
|
||||||
|
const fn = showYouAccountActionSheetByProp?.showYouAccountActionSheet;
|
||||||
|
if (typeof fn === "function") {
|
||||||
|
try {
|
||||||
|
fn(true, true);
|
||||||
|
return;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
showUserProfileActionSheet?.({ userId: self.id, channelId: channel?.id ?? channelId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePress = () => {
|
||||||
|
switch (settings.pressAction) {
|
||||||
|
case "profile":
|
||||||
|
showUserProfileActionSheet?.({ userId: self.id, channelId: channel?.id ?? channelId });
|
||||||
|
break;
|
||||||
|
case "server":
|
||||||
|
openAccountSheet();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLongPress = () => {
|
||||||
|
switch (settings.longPressAction) {
|
||||||
|
case "profile":
|
||||||
|
showUserProfileActionSheet?.({ userId: self.id, channelId: channel?.id ?? channelId });
|
||||||
|
break;
|
||||||
|
case "server":
|
||||||
|
openAccountSheet();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={{
|
||||||
|
height: 40,
|
||||||
|
width: animated.interpolate({ inputRange: [0, 1], outputRange: [40, 0] }),
|
||||||
|
marginHorizontal: animated.interpolate({ inputRange: [0, 1], outputRange: [4, 0] }),
|
||||||
|
flexShrink: 0,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflow: settings.collapseWhileTyping ? "hidden" : "visible",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Pressable
|
||||||
|
onPress={handlePress}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
>
|
||||||
|
{Avatar && (
|
||||||
|
<Avatar
|
||||||
|
user={self}
|
||||||
|
guildId={channel?.guild_id}
|
||||||
|
status={settings.showStatusCutout ? status : undefined}
|
||||||
|
avatarDecoration={self?.avatarDecoration}
|
||||||
|
animate={!settings.collapseWhileTyping || !textState}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpatches: (() => void)[] = [];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
onLoad() {
|
||||||
|
if (!ChatInputActions?.prototype?.render || !ChatInputSendButton?.prototype?.render) return;
|
||||||
|
unpatches.push(
|
||||||
|
after("render", ChatInputActions.prototype, (args, ret) => {
|
||||||
|
return React.createElement(
|
||||||
|
View,
|
||||||
|
{ style: { flexDirection: "row", alignItems: "center" } },
|
||||||
|
ret,
|
||||||
|
React.createElement(AvatarAction)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
unpatches.push(
|
||||||
|
after("render", ChatInputSendButton.prototype, (args, ret) => {
|
||||||
|
setImmediate(() => {
|
||||||
|
setImmediate(() => {
|
||||||
|
if (args?.[1]?.current) {
|
||||||
|
sendBtnRef = args[1].current;
|
||||||
|
const origSetHasText = sendBtnRef?.setHasText;
|
||||||
|
unpatches.push(() => { if (sendBtnRef && origSetHasText) sendBtnRef.setHasText = origSetHasText; });
|
||||||
|
if (sendBtnRef) {
|
||||||
|
sendBtnRef.setHasText = (hasText_: boolean) => {
|
||||||
|
hasText = hasText_;
|
||||||
|
if (origSetHasText) return origSetHasText.call(sendBtnRef, hasText_);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
for (const unpatch of unpatches) unpatch?.();
|
||||||
|
unpatches.length = 0;
|
||||||
|
},
|
||||||
|
settings: ChatboxAvatarSettings,
|
||||||
|
};
|
||||||
101
plugins/ChatboxAvatar/src/settings.tsx
Normal file
101
plugins/ChatboxAvatar/src/settings.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { plugin } from "@vendetta";
|
||||||
|
import { useProxy } from "@vendetta/storage";
|
||||||
|
import { React, ReactNative } from "@vendetta/metro/common";
|
||||||
|
import { findByProps } from "@vendetta/metro";
|
||||||
|
|
||||||
|
const { ScrollView } = ReactNative;
|
||||||
|
const { TableRowGroup, TableCheckboxRow, Stack } = findByProps(
|
||||||
|
"TableCheckboxRow", "TableRowGroup", "Stack"
|
||||||
|
);
|
||||||
|
|
||||||
|
plugin.storage.pressAction ??= "profile";
|
||||||
|
plugin.storage.longPressAction ??= "server";
|
||||||
|
plugin.storage.showStatusCutout ??= false;
|
||||||
|
plugin.storage.collapseWhileTyping ??= false;
|
||||||
|
|
||||||
|
export default function ChatboxAvatarSettings() {
|
||||||
|
useProxy(plugin.storage);
|
||||||
|
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
|
||||||
|
<Stack spacing={8}>
|
||||||
|
<TableRowGroup title="Avatar Press Action">
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Open Profile"
|
||||||
|
checked={plugin.storage.pressAction === "profile"}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.pressAction = "profile";
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Open Status Picker"
|
||||||
|
checked={plugin.storage.pressAction === "server"}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.pressAction = "server";
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableRowGroup>
|
||||||
|
|
||||||
|
<TableRowGroup title="Avatar Long-Press Action">
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Open Profile"
|
||||||
|
checked={plugin.storage.longPressAction === "profile"}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.longPressAction = "profile";
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Open Status Picker"
|
||||||
|
checked={plugin.storage.longPressAction === "server"}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.longPressAction = "server";
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableRowGroup>
|
||||||
|
|
||||||
|
<TableRowGroup title="Status Icon">
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Show"
|
||||||
|
checked={!!plugin.storage.showStatusCutout}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.showStatusCutout = true;
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Hide"
|
||||||
|
checked={!plugin.storage.showStatusCutout}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.showStatusCutout = false;
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableRowGroup>
|
||||||
|
|
||||||
|
<TableRowGroup title="Collapse While Typing">
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Collapse avatar while typing"
|
||||||
|
checked={!!plugin.storage.collapseWhileTyping}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.collapseWhileTyping = true;
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TableCheckboxRow
|
||||||
|
label="Always show avatar"
|
||||||
|
checked={!plugin.storage.collapseWhileTyping}
|
||||||
|
onPress={() => {
|
||||||
|
plugin.storage.collapseWhileTyping = false;
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableRowGroup>
|
||||||
|
</Stack>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
friendInviteRevokeCommand,
|
friendInviteRevokeCommand,
|
||||||
} from "./src/commands/friendinvites";
|
} from "./src/commands/friendinvites";
|
||||||
import settings from "./settings/settings";
|
import settings from "./settings/settings";
|
||||||
|
import patchSidebar from "./sidebar";
|
||||||
|
|
||||||
if (!storage.factSettings) {
|
if (!storage.factSettings) {
|
||||||
storage.factSettings = {
|
storage.factSettings = {
|
||||||
|
|
@ -76,6 +77,10 @@ if (!storage.hiddenSettings) {
|
||||||
konochanBypassNsfw: false,
|
konochanBypassNsfw: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// sidebar enabled setting
|
||||||
|
if (storage.sidebarEnabled === undefined) {
|
||||||
|
storage.sidebarEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
const commandMap = {
|
const commandMap = {
|
||||||
catfact: catFactCommand,
|
catfact: catFactCommand,
|
||||||
|
|
@ -102,11 +107,13 @@ const commandMap = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let commands: Array<() => void> = [];
|
let commands: Array<() => void> = [];
|
||||||
|
let sidebarUnpatch: (() => void) | undefined;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
onLoad: () => {
|
onLoad: () => {
|
||||||
console.log("[Commands Plugin] Loading...");
|
console.log("[Commands Plugin] Loading...");
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
// Register commands
|
// Register commands
|
||||||
for (const [key, command] of Object.entries(commandMap)) {
|
for (const [key, command] of Object.entries(commandMap)) {
|
||||||
if (storage.enabledCommands[key]) {
|
if (storage.enabledCommands[key]) {
|
||||||
|
|
@ -120,11 +127,35 @@ export default {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
// Patch sidebar if enabled
|
||||||
|
if (storage.sidebarEnabled !== false) {
|
||||||
|
try {
|
||||||
|
sidebarUnpatch = patchSidebar();
|
||||||
|
console.log("[Commands Plugin] Sidebar patched successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Commands Plugin] Failed to patch sidebar:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register commands
|
||||||
|
for (const [key, command] of Object.entries(commandMap)) {
|
||||||
|
if (storage.enabledCommands[key]) {
|
||||||
|
try {
|
||||||
|
commands.push(registerCommand(command as any));
|
||||||
|
console.log(`[Commands Plugin] Registered command: ${key}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[Commands Plugin] Failed to register command ${key}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUnload: () => {
|
onUnload: () => {
|
||||||
console.log("[Commands Plugin] Unloading...");
|
console.log("[Commands Plugin] Unloading...");
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
// Unregister commands
|
// Unregister commands
|
||||||
commands.forEach((unregister) => {
|
commands.forEach((unregister) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -136,4 +167,28 @@ export default {
|
||||||
commands = [];
|
commands = [];
|
||||||
},
|
},
|
||||||
settings,
|
settings,
|
||||||
|
=======
|
||||||
|
// Unregister commands
|
||||||
|
commands.forEach((unregister) => {
|
||||||
|
try {
|
||||||
|
unregister();
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors during cleanup
|
||||||
|
}
|
||||||
|
});
|
||||||
|
commands = [];
|
||||||
|
|
||||||
|
// Unpatch sidebar
|
||||||
|
if (sidebarUnpatch) {
|
||||||
|
try {
|
||||||
|
sidebarUnpatch();
|
||||||
|
sidebarUnpatch = undefined;
|
||||||
|
console.log("[Commands Plugin] Sidebar unpatched");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Commands Plugin] Failed to unpatch sidebar:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings,
|
||||||
|
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,8 @@ storage.hiddenSettings ??= {
|
||||||
konochanBypassNsfw: false,
|
konochanBypassNsfw: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sidebar setting
|
// Sidebar setting (enabled by default)
|
||||||
storage.sidebarEnabled ??= false;
|
storage.sidebarEnabled ??= true;
|
||||||
|
|
||||||
// MAIN SETTINGS COMPONENT
|
// MAIN SETTINGS COMPONENT
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { UserStore } from "./modules";
|
||||||
import { serviceFactory } from "./services/ServiceFactory";
|
import { serviceFactory } from "./services/ServiceFactory";
|
||||||
|
|
||||||
import Settings from "./ui/pages/Settings";
|
import Settings from "./ui/pages/Settings";
|
||||||
|
import patchSidebar from "./sidebar";
|
||||||
|
|
||||||
export const pluginState = {
|
export const pluginState = {
|
||||||
pluginStopped: false,
|
pluginStopped: false,
|
||||||
|
|
@ -21,6 +22,8 @@ export const pluginState = {
|
||||||
lastTrackUrl?: string;
|
lastTrackUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let sidebarUnpatch: (() => void) | undefined;
|
||||||
|
|
||||||
// Initialize default settings
|
// Initialize default settings
|
||||||
const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS;
|
const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS;
|
||||||
Object.keys(defaultSettings).forEach((key) => {
|
Object.keys(defaultSettings).forEach((key) => {
|
||||||
|
|
@ -28,6 +31,8 @@ Object.keys(defaultSettings).forEach((key) => {
|
||||||
plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings];
|
plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
plugin.storage.addToSidebar ??= false;
|
||||||
|
|
||||||
export const currentSettings = new Proxy(plugin.storage, {
|
export const currentSettings = new Proxy(plugin.storage, {
|
||||||
get(target, prop: string) {
|
get(target, prop: string) {
|
||||||
return target[prop];
|
return target[prop];
|
||||||
|
|
@ -119,13 +124,87 @@ export default {
|
||||||
console.log("[Multi-Scrobbler] Loading...");
|
console.log("[Multi-Scrobbler] Loading...");
|
||||||
pluginState.pluginStopped = false;
|
pluginState.pluginStopped = false;
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
validateAndInitialize();
|
validateAndInitialize();
|
||||||
},
|
},
|
||||||
|
=======
|
||||||
|
// Patch sidebar if enabled
|
||||||
|
if (currentSettings.addToSidebar !== false) {
|
||||||
|
try {
|
||||||
|
sidebarUnpatch = patchSidebar();
|
||||||
|
console.log("[Multi-Scrobbler] Sidebar patched successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Multi-Scrobbler] Failed to patch sidebar:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateAndInitialize();
|
||||||
|
},
|
||||||
|
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||||
|
|
||||||
onUnload() {
|
onUnload() {
|
||||||
console.log("[Multi-Scrobbler] Unloading...");
|
console.log("[Multi-Scrobbler] Unloading...");
|
||||||
pluginState.pluginStopped = true;
|
pluginState.pluginStopped = true;
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
// Unpatch sidebar
|
||||||
|
if (sidebarUnpatch) {
|
||||||
|
try {
|
||||||
|
sidebarUnpatch();
|
||||||
|
sidebarUnpatch = undefined;
|
||||||
|
console.log("[Multi-Scrobbler] Sidebar unpatched");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Multi-Scrobbler] Failed to unpatch sidebar:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop();
|
||||||
|
},
|
||||||
|
|
||||||
|
async onSettingsUpdate(newSettings: any) {
|
||||||
|
const oldService = currentSettings.service;
|
||||||
|
const newService = newSettings.service;
|
||||||
|
const oldSidebar = currentSettings.addToSidebar;
|
||||||
|
const newSidebar = newSettings.addToSidebar;
|
||||||
|
|
||||||
|
Object.assign(currentSettings, newSettings);
|
||||||
|
|
||||||
|
// Check if sidebar setting changed
|
||||||
|
if (oldSidebar !== newSidebar) {
|
||||||
|
if (newSidebar) {
|
||||||
|
try {
|
||||||
|
sidebarUnpatch = patchSidebar();
|
||||||
|
console.log("[Multi-Scrobbler] Sidebar enabled");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Multi-Scrobbler] Failed to enable sidebar:", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sidebarUnpatch) {
|
||||||
|
try {
|
||||||
|
sidebarUnpatch();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[Multi-Scrobbler] Failed to unpatch sidebar:", e);
|
||||||
|
}
|
||||||
|
sidebarUnpatch = undefined;
|
||||||
|
console.log("[Multi-Scrobbler] Sidebar disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldService !== newService && newService) {
|
||||||
|
console.log(`[Multi-Scrobbler] Service changed from ${oldService || "none"} to ${newService}`);
|
||||||
|
try {
|
||||||
|
await switchService(newService);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[Multi-Scrobbler] Failed to switch service:", e);
|
||||||
|
}
|
||||||
|
} else if (!pluginState.pluginStopped && currentSettings.service) {
|
||||||
|
tryInitialize();
|
||||||
|
} else if (!currentSettings.service) {
|
||||||
|
console.log("[Multi-Scrobbler] Service unselected, stopping plugin...");
|
||||||
|
try {
|
||||||
|
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||||
stop();
|
stop();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -133,6 +212,7 @@ export default {
|
||||||
const oldService = currentSettings.service;
|
const oldService = currentSettings.service;
|
||||||
const newService = newSettings.service;
|
const newService = newSettings.service;
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
Object.assign(currentSettings, newSettings);
|
Object.assign(currentSettings, newSettings);
|
||||||
|
|
||||||
if (oldService !== newService && newService) {
|
if (oldService !== newService && newService) {
|
||||||
|
|
@ -163,3 +243,7 @@ export default {
|
||||||
|
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
};
|
};
|
||||||
|
=======
|
||||||
|
settings: Settings,
|
||||||
|
};
|
||||||
|
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,7 @@ export default function Settings() {
|
||||||
/>
|
/>
|
||||||
</TableRowGroup>
|
</TableRowGroup>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
{/* Plugin Configuration */}
|
{/* Plugin Configuration */}
|
||||||
<TableRowGroup title="Plugin Configuration">
|
<TableRowGroup title="Plugin Configuration">
|
||||||
<TableRow
|
<TableRow
|
||||||
|
|
@ -286,6 +287,64 @@ export default function Settings() {
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
</TableRowGroup>
|
</TableRowGroup>
|
||||||
|
=======
|
||||||
|
{/* Plugin Configuration */}
|
||||||
|
<TableRowGroup title="Plugin Configuration">
|
||||||
|
<TableRow
|
||||||
|
label="Display Settings"
|
||||||
|
subLabel="Customize app name and update interval"
|
||||||
|
trailing={<TableRow.Arrow />}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push("VendettaCustomPage", {
|
||||||
|
title: "Display Settings",
|
||||||
|
render: DisplaySettingsPage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label="RPC Customization"
|
||||||
|
subLabel="Customize Discord rich presence display options"
|
||||||
|
trailing={<TableRow.Arrow />}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push("VendettaCustomPage", {
|
||||||
|
title: "RPC Customization",
|
||||||
|
render: RPCCustomizationSettingsPage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label="Ignore List"
|
||||||
|
subLabel="Configure apps that should hide your status"
|
||||||
|
trailing={<TableRow.Arrow />}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push("VendettaCustomPage", {
|
||||||
|
title: "Ignore List Settings",
|
||||||
|
render: IgnoreListSettingsPage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TableRow
|
||||||
|
label="Logging Settings"
|
||||||
|
subLabel="Configure logging and debugging options"
|
||||||
|
trailing={<TableRow.Arrow />}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push("VendettaCustomPage", {
|
||||||
|
title: "Logging Settings",
|
||||||
|
render: LoggingSettingsPage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TableSwitchRow
|
||||||
|
label="Add to Sidebar"
|
||||||
|
subLabel="Show plugin in Discord settings"
|
||||||
|
value={getStorage("addToSidebar", false)}
|
||||||
|
onValueChange={(value: boolean) => {
|
||||||
|
setStorage("addToSidebar", value);
|
||||||
|
forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableRowGroup>
|
||||||
|
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||||
|
|
||||||
{/* About */}
|
{/* About */}
|
||||||
<TableRowGroup title="About">
|
<TableRowGroup title="About">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue