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,
|
||||
} from "./src/commands/friendinvites";
|
||||
import settings from "./settings/settings";
|
||||
import patchSidebar from "./sidebar";
|
||||
|
||||
if (!storage.factSettings) {
|
||||
storage.factSettings = {
|
||||
|
|
@ -76,6 +77,10 @@ if (!storage.hiddenSettings) {
|
|||
konochanBypassNsfw: false,
|
||||
};
|
||||
}
|
||||
// sidebar enabled setting
|
||||
if (storage.sidebarEnabled === undefined) {
|
||||
storage.sidebarEnabled = true;
|
||||
}
|
||||
|
||||
const commandMap = {
|
||||
catfact: catFactCommand,
|
||||
|
|
@ -102,11 +107,13 @@ const commandMap = {
|
|||
};
|
||||
|
||||
let commands: Array<() => void> = [];
|
||||
let sidebarUnpatch: (() => void) | undefined;
|
||||
|
||||
export default {
|
||||
onLoad: () => {
|
||||
console.log("[Commands Plugin] Loading...");
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Register commands
|
||||
for (const [key, command] of Object.entries(commandMap)) {
|
||||
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: () => {
|
||||
console.log("[Commands Plugin] Unloading...");
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Unregister commands
|
||||
commands.forEach((unregister) => {
|
||||
try {
|
||||
|
|
@ -136,4 +167,28 @@ export default {
|
|||
commands = [];
|
||||
},
|
||||
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,
|
||||
};
|
||||
|
||||
// Sidebar setting
|
||||
storage.sidebarEnabled ??= false;
|
||||
// Sidebar setting (enabled by default)
|
||||
storage.sidebarEnabled ??= true;
|
||||
|
||||
// MAIN SETTINGS COMPONENT
|
||||
export default function Settings() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { UserStore } from "./modules";
|
|||
import { serviceFactory } from "./services/ServiceFactory";
|
||||
|
||||
import Settings from "./ui/pages/Settings";
|
||||
import patchSidebar from "./sidebar";
|
||||
|
||||
export const pluginState = {
|
||||
pluginStopped: false,
|
||||
|
|
@ -21,6 +22,8 @@ export const pluginState = {
|
|||
lastTrackUrl?: string;
|
||||
};
|
||||
|
||||
let sidebarUnpatch: (() => void) | undefined;
|
||||
|
||||
// Initialize default settings
|
||||
const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS;
|
||||
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.addToSidebar ??= false;
|
||||
|
||||
export const currentSettings = new Proxy(plugin.storage, {
|
||||
get(target, prop: string) {
|
||||
return target[prop];
|
||||
|
|
@ -119,22 +124,97 @@ export default {
|
|||
console.log("[Multi-Scrobbler] Loading...");
|
||||
pluginState.pluginStopped = false;
|
||||
|
||||
<<<<<<< HEAD
|
||||
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() {
|
||||
console.log("[Multi-Scrobbler] Unloading...");
|
||||
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();
|
||||
},
|
||||
|
||||
async onSettingsUpdate(newSettings: any) {
|
||||
const oldService = currentSettings.service;
|
||||
const newService = newSettings.service;
|
||||
|
||||
<<<<<<< HEAD
|
||||
Object.assign(currentSettings, newSettings);
|
||||
|
||||
if (oldService !== newService && newService) {
|
||||
console.log(`[Multi-Scrobbler] Service changed from ${oldService || "none"} to ${newService}`);
|
||||
try {
|
||||
|
|
@ -163,3 +243,7 @@ export default {
|
|||
|
||||
settings: Settings,
|
||||
};
|
||||
=======
|
||||
settings: Settings,
|
||||
};
|
||||
>>>>>>> parent of ecd97c7 (feat: detach Sidebar patches as they)
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ export default function Settings() {
|
|||
/>
|
||||
</TableRowGroup>
|
||||
|
||||
<<<<<<< HEAD
|
||||
{/* Plugin Configuration */}
|
||||
<TableRowGroup title="Plugin Configuration">
|
||||
<TableRow
|
||||
|
|
@ -286,6 +287,64 @@ export default function Settings() {
|
|||
disabled={true}
|
||||
/>
|
||||
</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 */}
|
||||
<TableRowGroup title="About">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue