rewrite(Commands): rewrite whole settings page using proper tablerow

instead of BetterTableRow
This commit is contained in:
kmmiio99o 2026-07-19 17:17:13 +02:00
parent bba550dbc3
commit 786caf8c86
No known key found for this signature in database
GPG key ID: DCC0431E10FADBF1
18 changed files with 800 additions and 1311 deletions

View file

@ -1,71 +0,0 @@
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { rawColors, semanticColors } from "@vendetta/ui";
export default function BetterTableRowGroup({
title,
icon,
children,
padding = false,
}: React.PropsWithChildren<{
title?: string;
icon?: number;
padding?: boolean;
}>) {
const styles = stylesheet.createThemedStyleSheet({
main: {
backgroundColor: rawColors.PLUM_18,
borderColor: semanticColors.CARD_BACKGROUND_DEFAULT,
borderWidth: 1,
borderRadius: 16,
overflow: "hidden",
flex: 1,
maxWidth: "100%",
},
titleContainer: {
marginBottom: 8,
marginHorizontal: 16,
marginTop: 8,
gap: 4,
flexDirection: "row",
alignItems: "center",
},
icon: {
width: 16,
height: 16,
marginTop: 1.5,
tintColor: semanticColors.TEXT_MUTED,
},
titleText: {
fontSize: 14,
fontWeight: "600",
color: semanticColors.TEXT_MUTED,
},
wrapper: {
marginHorizontal: 16,
marginTop: 16,
maxWidth: "100%",
},
});
return (
<RN.View style={styles.wrapper}>
{title && (
<RN.View style={styles.titleContainer}>
{icon && (
<RN.Image style={styles.icon} source={icon} resizeMode="cover" />
)}
<RN.Text style={styles.titleText}>{title.toUpperCase()}</RN.Text>
</RN.View>
)}
<RN.View style={styles.main}>
{padding ? (
<RN.View style={{ paddingHorizontal: 16, paddingVertical: 16 }}>
{children}
</RN.View>
) : (
children
)}
</RN.View>
</RN.View>
);
}

View file

@ -1,10 +1,19 @@
import { storage } from "@vendetta/plugin";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { React, ReactNative as RN } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { showToast } from "@vendetta/ui/toasts";
import { findByProps } from "@vendetta/metro";
import Text from "../../../../nexxstuff/components/Text";
import { findByProps, findByStoreName } from "@vendetta/metro";
const TextStyleSheet = findByProps("TextStyleSheet").TextStyleSheet;
const ThemeStore = findByStoreName("ThemeStore");
const colorModule = findByProps("colors", "unsafe_rawColors");
const colorResolver = colorModule?.internal ?? colorModule?.meta;
function resolveSemanticColor(color: any, theme?: string) {
const t = theme ?? ThemeStore?.theme;
return (color && colorResolver?.resolveSemanticColor(t, color)) || "#000000";
}
export default function Header() {
const [clickCounter, setClickCounter] = React.useState(0);
@ -21,15 +30,32 @@ export default function Header() {
}
}, []);
// Get current user from Discord's user store
const users = findByProps("getUser", "getCurrentUser");
const currentUser = users?.getCurrentUser?.();
// Get current user from Discord's user store with error handling
let currentUser = null;
try {
const users = findByProps("getUser", "getCurrentUser");
currentUser = users?.getCurrentUser?.();
} catch (e) {
console.warn("[Commands Header] Failed to get current user:", e);
}
// Get avatar URL utility
const { getUserAvatarURL } = findByProps("getUserAvatarURL") || {};
const { getDefaultAvatarURL } = findByProps("getDefaultAvatarURL") || {};
// Get avatar URL utilities with error handling
let getUserAvatarURL = null;
let getDefaultAvatarURL = null;
try {
const userAvatarModule = findByProps("getUserAvatarURL");
getUserAvatarURL = userAvatarModule?.getUserAvatarURL;
} catch (e) {
console.warn("[Commands Header] Failed to get getUserAvatarURL:", e);
}
try {
const defaultAvatarModule = findByProps("getDefaultAvatarURL");
getDefaultAvatarURL = defaultAvatarModule?.getDefaultAvatarURL;
} catch (e) {
console.warn("[Commands Header] Failed to get getDefaultAvatarURL:", e);
}
const styles = stylesheet.createThemedStyleSheet({
const styles = RN.StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
@ -56,12 +82,11 @@ export default function Header() {
width: 96,
height: 96,
borderRadius: 24,
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
borderWidth: 2,
borderColor: semanticColors.BACKGROUND_TERTIARY,
borderColor: semanticColors.MOBILE_CHATINPUT_BORDER_DEFAULT,
},
avatar: {
width: 92,
@ -125,25 +150,30 @@ export default function Header() {
// Try to get the user's custom avatar first
if (getUserAvatarURL && currentUser.avatar) {
// Use animated parameter for GIF avatars
url = getUserAvatarURL(currentUser, hasAnimatedAvatar);
animated = hasAnimatedAvatar;
try {
// Use animated parameter for GIF avatars
url = getUserAvatarURL(currentUser, hasAnimatedAvatar);
animated = hasAnimatedAvatar;
} catch (e) {
console.warn("[Commands Header] Error getting custom avatar:", e);
url = null;
}
}
// Fall back to default avatar
else if (getDefaultAvatarURL && typeof getDefaultAvatarURL === "function") {
if (!url && getDefaultAvatarURL && typeof getDefaultAvatarURL === "function") {
try {
url = getDefaultAvatarURL(currentUser);
} catch (error) {
console.error("Error getting default avatar URL:", error);
console.warn("[Commands Header] Error getting default avatar URL:", error);
url = null;
}
}
// Last resort: Construct Discord CDN URL
else if (currentUser.avatar) {
if (!url && currentUser.avatar) {
const isGif = currentUser.avatar.startsWith("a_");
url = `https://cdn.discordapp.com/avatars/${currentUser.id}/${currentUser.avatar}.${isGif ? "gif" : "png"}?size=256`;
animated = isGif;
} else {
} else if (!url) {
// Default Discord avatar based on discriminator or id
const defaultAvatarIndex = currentUser.discriminator
? parseInt(currentUser.discriminator) % 5
@ -250,12 +280,12 @@ export default function Header() {
</RN.Pressable>
<RN.View style={styles.textContainer}>
<Text variant="display-md" color="TEXT_DEFAULT" align="left">
<RN.Text style={[TextStyleSheet["display-md"], { color: resolveSemanticColor(semanticColors["TEXT_DEFAULT"]) }]}>
Commands
</Text>
<Text variant="text-md/bold" color="TEXT_MUTED" align="left">
</RN.Text>
<RN.Text style={[TextStyleSheet["text-md/bold"], { color: resolveSemanticColor(semanticColors["TEXT_MUTED"]) }]}>
A collection of useful commands for Vendetta like clients
</Text>
</RN.Text>
</RN.View>
</RN.View>
</RN.View>

View file

@ -0,0 +1,21 @@
import { findByProps } from "@vendetta/metro";
export const { ScrollView } = findByProps("ScrollView");
export const {
TableRowGroup,
TableSwitchRow,
TableCheckboxRow,
TableRadioGroup,
TableRadioRow,
Stack,
TableRow,
} = findByProps(
"TableSwitchRow",
"TableCheckboxRow",
"TableRadioGroup",
"TableRadioRow",
"TableRowGroup",
"Stack",
"TableRow",
);
export const { TextInput } = findByProps("TextInput");

View file

@ -1,12 +0,0 @@
export { default as Header } from "./components/Header";
export { default as BetterTableRowGroup } from "./components/BetterTableRowGroup";
export { default as FactsSettingsPage } from "./pages/FactsSettingsPage";
export { default as ListSettingsPage } from "./pages/ListSettingsPage";
export { default as ImageSettingsPage } from "./pages/ImageSettingsPage";
export { default as GaryAPIPage } from "./pages/GaryAPIPage";
export { default as SpotifySettingsPage } from "./pages/SpotifySettingsPage";
export { default as OtherSettingsPage } from "./pages/OtherSettingsPage";
export { default as AliucordPage } from "./pages/AliucordPage";
export { default as HiddenSettingsPage } from "./pages/HiddenSettingsPage";
export { default as CreditsPage } from "./pages/CreditsPage";
export { default as NekosLifeCategoriesPage } from "./pages/NekosLifeCategoriesPage";

View file

@ -1,76 +1,61 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { React } from "@vendetta/metro/common";
import { NavigationNative } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
import {
ScrollView,
Stack,
TableRowGroup,
TableRow,
TableSwitchRow,
} from "../components/TableComponents";
import NekosLifeCategoriesPage from "./NekosLifeCategoriesPage";
const { FormSwitchRow, FormRow } = Forms;
export default function AliucordPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const navigation = NavigationNative.useNavigation();
return (
<RN.ScrollView
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="IP Commands"
icon={getAssetIDByName("GlobeEarthIcon")}
>
<FormSwitchRow
label="/ip"
subLabel="Get your current IP address"
leading={<FormRow.Icon source={getAssetIDByName("GlobeEarthIcon")} />}
value={storage.enabledCommands.ip}
onValueChange={(v) => {
storage.enabledCommands.ip = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="IP Commands">
<TableSwitchRow
label="/ip"
subLabel="Get your current IP address"
value={storage.enabledCommands.ip}
onValueChange={(v) => {
storage.enabledCommands.ip = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="NekosLife Commands"
icon={getAssetIDByName("ImageIcon")}
>
<FormSwitchRow
label="/nekoslife"
subLabel="Get images/gifs from nekos.life"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={storage.enabledCommands.nekoslife}
onValueChange={(v) => {
storage.enabledCommands.nekoslife = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormRow
label="View Categories"
subLabel="See all 16 available categories"
leading={<FormRow.Icon source={getAssetIDByName("BookOpenIcon")} />}
trailing={<FormRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "NekosLife Categories",
render: NekosLifeCategoriesPage,
})
}
/>
</BetterTableRowGroup>
<RN.View style={{ height: 32 }} />
</RN.ScrollView>
<TableRowGroup title="NekosLife Commands">
<TableSwitchRow
label="/nekoslife"
subLabel="Get images/gifs from nekos.life"
value={storage.enabledCommands.nekoslife}
onValueChange={(v) => {
storage.enabledCommands.nekoslife = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableRow
label="View Categories"
subLabel="See all 16 available categories"
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "NekosLife Categories",
render: NekosLifeCategoriesPage,
})
}
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,7 +1,10 @@
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { rawColors, semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
import { ReactNative as RN } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableRow,
} from "../components/TableComponents";
type Credit = {
command: string;
@ -10,231 +13,103 @@ type Credit = {
github: string;
};
const credits: Credit[] = [
{
command: "Facts Commands",
author: "jdev082",
avatar: "https://github.com/jdev082.png",
github: "https://github.com/jdev082",
},
{
command: "List Commands",
author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
github: "https://github.com/Kitosight",
},
{
command: "PetPet",
author: "wolfieeee",
avatar: "https://github.com/WolfPlugs.png",
github: "https://github.com/WolfPlugs",
},
{
command: "KonoChan Commands",
author: "btmc727 & Rico040",
avatar: "https://github.com/OTKUSteyler.png",
github: "https://github.com/OTKUSteyler",
},
{
command: "FirstMessage Command",
author: "sapphire",
avatar: "https://github.com/aeongdesu.png",
github: "https://github.com/aeongdesu",
},
{
command: "Sysinfo Command",
author: "mugman",
avatar: "https://github.com/mugman174.png",
github: "https://github.com/mugman174",
},
{
command: "Spotify Commands",
author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
github: "https://github.com/Kitosight",
},
{
command: "Gary Command",
author: "Zach Orange",
avatar: "https://github.com/IAmGaryTheCat.png",
github: "https://github.com/IAmGaryTheCat",
},
{
command: "IP & NekosLife Commands",
author: "scruzism",
avatar: "https://github.com/scrazzz.png",
github: "https://github.com/scrazzz",
},
{
command: "FriendInvites",
author: "nikosszzz",
avatar: "https://github.com/nikosszzz.png",
github: "https://github.com/nikosszzz",
},
];
export default function CreditsPage() {
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
scrollContent: {
paddingVertical: 16,
paddingHorizontal: 12,
alignItems: "stretch",
},
groupWrapper: {
marginHorizontal: 16,
marginTop: 16,
maxWidth: "100%",
},
groupMain: {
backgroundColor: rawColors.PLUM_18,
borderColor: semanticColors.CARD_BACKGROUND_DEFAULT,
borderWidth: 1,
borderRadius: 16,
overflow: "hidden",
flex: 1,
maxWidth: "100%",
},
rippleContainer: {
borderRadius: 16,
overflow: "hidden",
},
cardPressable: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 12,
},
avatarContainer: {
width: 56,
height: 56,
borderRadius: 12,
overflow: "hidden",
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
justifyContent: "center",
alignItems: "center",
marginRight: 12,
},
avatar: {
width: 56,
height: 56,
borderRadius: 12,
resizeMode: "cover",
},
textContainer: {
flex: 1,
justifyContent: "center",
},
commandText: {
fontSize: 16,
fontWeight: "700",
color: semanticColors.MOBILE_TEXT_HEADING_PRIMARY,
marginBottom: 2,
},
authorText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginBottom: 6,
},
linkText: {
fontSize: 13,
color: semanticColors.LINK,
fontWeight: "600",
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginBottom: 20,
textAlign: "center",
lineHeight: 20,
},
versionText: {
fontSize: 15,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
fontWeight: "600",
lineHeight: 22,
},
});
const credits: Credit[] = [
{
command: "Facts Commands",
author: "jdev082",
avatar: "https://github.com/jdev082.png",
github: "https://github.com/jdev082",
},
{
command: "List Commands",
author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
github: "https://github.com/Kitosight",
},
{
command: "PetPet",
author: "wolfieeee",
avatar: "https://github.com/WolfPlugs.png",
github: "https://github.com/WolfPlugs",
},
{
command: "KonoChan Commands",
author: "btmc727 & Rico040",
avatar: "https://github.com/OTKUSteyler.png",
github: "https://github.com/OTKUSteyler",
},
{
command: "FirstMessage Command",
author: "sapphire",
avatar: "https://github.com/aeongdesu.png",
github: "https://github.com/aeongdesu",
},
{
command: "Sysinfo Command",
author: "mugman",
avatar: "https://github.com/mugman174.png",
github: "https://github.com/mugman174",
},
{
command: "Spotify Commands",
author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
github: "https://github.com/Kitosight",
},
{
command: "Gary Command",
author: "Zach Orange",
avatar: "https://github.com/IAmGaryTheCat.png",
github: "https://github.com/IAmGaryTheCat",
},
{
command: "IP & NekosLife Commands",
author: "scruzism",
avatar: "https://github.com/scrazzz.png",
github: "https://github.com/scrazzz",
},
{
command: "FriendInvites",
author: "nikosszzz",
avatar: "https://github.com/nikosszzz.png",
github: "https://github.com/nikosszzz",
},
];
const handleProfilePress = (githubUrl: string) => {
RN.Linking.openURL(githubUrl);
};
// Resolve asset IDs (some assets may be numeric IDs or URIs depending on bundling)
const heartIconRaw = getAssetIDByName("HeartIcon");
const infoIconRaw = getAssetIDByName("ic_information_24px");
const heartIcon =
typeof heartIconRaw === "number" ? (heartIconRaw as number) : undefined;
const infoIcon =
typeof infoIconRaw === "number" ? (infoIconRaw as number) : undefined;
return (
<RN.ScrollView
style={styles.container}
contentContainerStyle={styles.scrollContent}
>
<BetterTableRowGroup
title="Plugin Authors"
icon={heartIcon}
padding={true}
>
<RN.Text style={styles.infoText}>
Thanks to these developers for creating such great plugins!
{"\n"}Tap any developer to visit their GitHub profile.
</RN.Text>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Plugin Authors">
{credits.map((credit) => (
<TableRow
key={credit.github}
label={credit.command}
subLabel={`by ${credit.author}`}
icon={
<RN.Image
source={{ uri: credit.avatar }}
style={{
width: 48,
height: 48,
borderRadius: 8,
}}
resizeMode="cover"
/>
}
trailing={<TableRow.Arrow />}
onPress={() => RN.Linking.openURL(credit.github)}
/>
))}
</TableRowGroup>
{credits.map((credit) => (
<RN.View key={credit.github} style={styles.groupWrapper}>
<RN.View style={styles.groupMain}>
<RN.View style={styles.rippleContainer}>
<RN.Pressable
style={styles.cardPressable}
onPress={() => handleProfilePress(credit.github)}
android_ripple={{
color: semanticColors.ANDROID_RIPPLE,
borderless: false,
radius: 200,
}}
accessibilityRole="button"
>
<RN.View style={styles.avatarContainer}>
<RN.Image
source={{ uri: credit.avatar }}
style={styles.avatar}
/>
</RN.View>
<RN.View style={styles.textContainer}>
<RN.Text numberOfLines={1} style={styles.commandText}>
{credit.command}
</RN.Text>
<RN.Text numberOfLines={1} style={styles.authorText}>
by {credit.author}
</RN.Text>
</RN.View>
</RN.Pressable>
</RN.View>
</RN.View>
</RN.View>
))}
<BetterTableRowGroup title="About" icon={infoIcon} padding={true}>
<RN.Text style={styles.versionText}>
Commands Plugin Collection{"\n"}
Version 1.3.0
</RN.Text>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="About">
<TableRow
label="Commands Plugin Collection"
subLabel="Version 1.3.0"
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,90 +1,72 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { React } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableSwitchRow,
} from "../components/TableComponents";
export default function FactsSettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
<RN.ScrollView
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="Fact Display Settings"
icon={getAssetIDByName("SettingsIcon")}
>
<FormSwitchRow
label="Send as Reply"
subLabel="Send facts as a reply to the command message"
leading={
<FormRow.Icon source={getAssetIDByName("ArrowAngleLeftUpIcon")} />
}
value={storage.factSettings.sendAsReply}
onValueChange={(v) => {
storage.factSettings.sendAsReply = v;
}}
/>
<FormSwitchRow
label="Include Source Citation"
subLabel="Include the source of facts when available"
leading={<FormRow.Icon source={getAssetIDByName("LinkIcon")} />}
value={storage.factSettings.includeCitation}
onValueChange={(v) => {
storage.factSettings.includeCitation = v;
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Fact Display Settings">
<TableSwitchRow
label="Send as Reply"
subLabel="Send facts as a reply to the command message"
value={storage.factSettings.sendAsReply}
onValueChange={(v) => {
storage.factSettings.sendAsReply = v;
}}
/>
<TableSwitchRow
label="Include Source Citation"
subLabel="Include the source of facts when available"
value={storage.factSettings.includeCitation}
onValueChange={(v) => {
storage.factSettings.includeCitation = v;
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="Available Fact Commands"
icon={getAssetIDByName("BookmarkIcon")}
>
<FormSwitchRow
label="/catfact"
subLabel="Get random cat facts"
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
value={storage.enabledCommands.catfact}
onValueChange={(v) => {
storage.enabledCommands.catfact = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/dogfact"
subLabel="Get random dog facts"
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
value={storage.enabledCommands.dogfact}
onValueChange={(v) => {
storage.enabledCommands.dogfact = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/useless"
subLabel="Get random useless facts"
leading={<FormRow.Icon source={getAssetIDByName("BookCheckIcon")} />}
value={storage.enabledCommands.useless}
onValueChange={(v) => {
storage.enabledCommands.useless = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="Available Fact Commands">
<TableSwitchRow
label="/catfact"
subLabel="Get random cat facts"
value={storage.enabledCommands.catfact}
onValueChange={(v) => {
storage.enabledCommands.catfact = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/dogfact"
subLabel="Get random dog facts"
value={storage.enabledCommands.dogfact}
onValueChange={(v) => {
storage.enabledCommands.dogfact = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/useless"
subLabel="Get random useless facts"
value={storage.enabledCommands.useless}
onValueChange={(v) => {
storage.enabledCommands.useless = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,146 +1,66 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { React } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableSwitchRow,
TableRadioGroup,
TableRadioRow,
} from "../components/TableComponents";
export default function GaryAPIPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
marginBottom: 16,
textAlign: "center",
},
currentText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
fontWeight: "600",
},
});
const currentSource = storage.garySettings.imageSource;
return (
<RN.ScrollView
style={styles.container}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="Gary Command Settings"
icon={getAssetIDByName("SettingsIcon")}
>
<FormSwitchRow
label="/gary"
subLabel="Send random Gary images to channel"
leading={<FormRow.Icon source={getAssetIDByName("AttachmentIcon")} />}
value={storage.enabledCommands.gary}
onValueChange={(v) => {
storage.enabledCommands.gary = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Gary Command Settings">
<TableSwitchRow
label="/gary"
subLabel="Send random Gary images to channel"
value={storage.enabledCommands.gary}
onValueChange={(v) => {
storage.enabledCommands.gary = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="Image Source Selection"
icon={getAssetIDByName("DownloadIcon")}
padding={true}
>
<RN.Text style={styles.infoText}>
Choose which API the /gary command should use to fetch images.
</RN.Text>
</BetterTableRowGroup>
<BetterTableRowGroup
title="API Options"
icon={getAssetIDByName("CloudIcon")}
>
<FormSwitchRow
label="Gary API"
subLabel="Original Gary the cat images from api.garythe.cat"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "gary"}
onValueChange={(v) => {
if (v) {
storage.garySettings.imageSource = "gary";
<TableRowGroup title="Image Source Selection">
<TableRadioGroup
value={storage.garySettings.imageSource}
onChange={(v) => {
storage.garySettings.imageSource = v;
forceUpdate();
}
}}
/>
<FormSwitchRow
label="Cat API"
subLabel="Random cat pictures from thecatapi.com"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "catapi"}
onValueChange={(v) => {
if (v) {
storage.garySettings.imageSource = "catapi";
forceUpdate();
}
}}
/>
<FormSwitchRow
label="Minker API"
subLabel="Minky images from minky.materii.dev"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "minker"}
onValueChange={(v) => {
if (v) {
storage.garySettings.imageSource = "minker";
forceUpdate();
}
}}
/>
<FormSwitchRow
label="Goober API"
subLabel="Goober images from api.garythe.cat/goober"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={currentSource === "goober"}
onValueChange={(v) => {
if (v) {
storage.garySettings.imageSource = "goober";
forceUpdate();
}
}}
/>
</BetterTableRowGroup>
<BetterTableRowGroup
title="Current Selection"
icon={getAssetIDByName("CheckmarkIcon")}
padding={true}
>
<RN.Text style={styles.currentText}>
Currently using:{" "}
{currentSource === "gary"
? "Gary API"
: currentSource === "catapi"
? "Cat API"
: currentSource === "minker"
? "Minker API"
: currentSource === "goober"
? "Goober API"
: "Gary API"}
</RN.Text>
</BetterTableRowGroup>
</RN.ScrollView>
}}
>
<TableRadioRow
value="gary"
label="Gary API"
subLabel="Original Gary the cat images from api.garythe.cat"
/>
<TableRadioRow
value="catapi"
label="Cat API"
subLabel="Random cat pictures from thecatapi.com"
/>
<TableRadioRow
value="minker"
label="Minker API"
subLabel="Minky images from minky.materii.dev"
/>
<TableRadioRow
value="goober"
label="Goober API"
subLabel="Goober images from api.garythe.cat/goober"
/>
</TableRadioGroup>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,152 +1,107 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import { React } from "@vendetta/metro/common";
import { alerts } from "@vendetta/ui";
import { showToast } from "@vendetta/ui/toasts";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { getAssetIDByName } from "@vendetta/ui/assets";
import {
ScrollView,
Stack,
TableRowGroup,
TableRow,
TableSwitchRow,
} from "../components/TableComponents";
export default function HiddenSettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
warningText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
fontWeight: "600",
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
marginTop: 8,
},
});
return (
<RN.ScrollView
style={styles.container}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="Warning"
icon={getAssetIDByName("WarningIcon")}
padding={true}
>
<RN.Text style={styles.warningText}>
These are hidden commands that may contain mature content or
experimental features.
</RN.Text>
<RN.Text style={styles.infoText}>
Use at your own discretion. Commands in this section are disabled by
default.
</RN.Text>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Warning">
<TableRow
label="Hidden Commands Warning"
subLabel="These are hidden commands that may contain mature content or experimental features. Use at your own discretion. Commands in this section are disabled by default."
/>
</TableRowGroup>
<BetterTableRowGroup
title="Hidden Commands"
icon={getAssetIDByName("EyeIcon")}
>
<FormSwitchRow
label="/lovefemboys"
subLabel="Get random femboy images from r/femboys (NSFW content available)"
leading={<FormRow.Icon source={getAssetIDByName("HeartIcon")} />}
value={storage.enabledCommands.lovefemboys}
onValueChange={(v) => {
storage.enabledCommands.lovefemboys = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<BetterTableRowGroup
title="NSFW Bypass Options"
icon={getAssetIDByName("WarningIcon")}
>
<FormSwitchRow
label="KonoChan NSFW Bypass"
subLabel="Allow NSFW KonoChan content in non-NSFW channels (USE WITH CAUTION)"
leading={<FormRow.Icon source={getAssetIDByName("ShieldIcon")} />}
value={storage.hiddenSettings.konochanBypassNsfw}
onValueChange={(v) => {
if (v) {
alerts.showConfirmationAlert({
title: "⚠️ NSFW Bypass Warning",
content:
"Enabling this allows NSFW content from KonoChan to be sent in any channel, including non-NSFW channels. This could violate server rules or Discord ToS. Use responsibly!",
confirmText: "I Understand",
cancelText: "Cancel",
onConfirm: () => {
storage.hiddenSettings.konochanBypassNsfw = true;
forceUpdate();
},
onCancel: () => {},
});
} else {
storage.hiddenSettings.konochanBypassNsfw = false;
<TableRowGroup title="Hidden Commands">
<TableSwitchRow
label="/lovefemboys"
subLabel="Get random femboy images from r/femboys (NSFW content available)"
value={storage.enabledCommands.lovefemboys}
onValueChange={(v) => {
storage.enabledCommands.lovefemboys = v;
storage.pendingRestart = true;
forceUpdate();
}
}}
/>
</BetterTableRowGroup>
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="Hidden Settings Control"
icon={getAssetIDByName("SettingsIcon")}
>
<FormSwitchRow
label="Keep Hidden Settings Visible"
subLabel="Keep this section visible even when navigating away"
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
value={storage.hiddenSettings.visible}
onValueChange={(v) => {
storage.hiddenSettings.visible = v;
}}
/>
<FormRow
label="Reset Hidden Settings"
subLabel="Hide this section and disable all hidden commands"
leading={<FormRow.Icon source={getAssetIDByName("TrashIcon")} />}
onPress={() => {
alerts.showConfirmationAlert({
title: "Reset Hidden Settings",
content:
"This will hide the hidden settings section and disable all hidden commands. Are you sure?",
confirmText: "Reset",
onConfirm: () => {
storage.hiddenSettings.enabled = false;
storage.hiddenSettings.visible = false;
<TableRowGroup title="NSFW Bypass Options">
<TableSwitchRow
label="KonoChan NSFW Bypass"
subLabel="Allow NSFW KonoChan content in non-NSFW channels (USE WITH CAUTION)"
value={storage.hiddenSettings.konochanBypassNsfw}
onValueChange={(v) => {
if (v) {
alerts.showConfirmationAlert({
title: "NSFW Bypass Warning",
content:
"Enabling this allows NSFW content from KonoChan to be sent in any channel, including non-NSFW channels. This could violate server rules or Discord ToS. Use responsibly!",
confirmText: "I Understand",
cancelText: "Cancel",
onConfirm: () => {
storage.hiddenSettings.konochanBypassNsfw = true;
forceUpdate();
},
onCancel: () => {},
});
} else {
storage.hiddenSettings.konochanBypassNsfw = false;
storage.enabledCommands.lovefemboys = false;
forceUpdate();
}
}}
/>
</TableRowGroup>
storage.pendingRestart = true;
showToast(
"Hidden settings reset",
getAssetIDByName("CheckmarkIcon"),
);
},
cancelText: "Cancel",
});
}}
/>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="Hidden Settings Control">
<TableSwitchRow
label="Keep Hidden Settings Visible"
subLabel="Keep this section visible even when navigating away"
value={storage.hiddenSettings.visible}
onValueChange={(v) => {
storage.hiddenSettings.visible = v;
}}
/>
<TableRow
label="Reset Hidden Settings"
subLabel="Hide this section and disable all hidden commands"
onPress={() => {
alerts.showConfirmationAlert({
title: "Reset Hidden Settings",
content:
"This will hide the hidden settings section and disable all hidden commands. Are you sure?",
confirmText: "Reset",
onConfirm: () => {
storage.hiddenSettings.enabled = false;
storage.hiddenSettings.visible = false;
storage.hiddenSettings.konochanBypassNsfw = false;
storage.enabledCommands.lovefemboys = false;
storage.pendingRestart = true;
showToast(
"Hidden settings reset",
getAssetIDByName("CheckmarkIcon"),
);
},
cancelText: "Cancel",
});
}}
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,72 +1,56 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { React } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableSwitchRow,
} from "../components/TableComponents";
export default function ImageSettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
<RN.ScrollView
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="Image Commands"
icon={getAssetIDByName("ImageIcon")}
>
<FormSwitchRow
label="/petpet"
subLabel="Create pet-pet GIF of a user"
leading={
<FormRow.Icon source={getAssetIDByName("HandRequestSpeakIcon")} />
}
value={storage.enabledCommands.petpet}
onValueChange={(v) => {
storage.enabledCommands.petpet = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Image Commands">
<TableSwitchRow
label="/petpet"
subLabel="Create pet-pet GIF of a user"
value={storage.enabledCommands.petpet}
onValueChange={(v) => {
storage.enabledCommands.petpet = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="KonoChan Commands"
icon={getAssetIDByName("ImageIcon")}
>
<FormSwitchRow
label="/konoself"
subLabel="Get random image from KonoChan (private)"
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
value={storage.enabledCommands.konoself}
onValueChange={(v) => {
storage.enabledCommands.konoself = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/konosend"
subLabel="Send random image from KonoChan to channel"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
value={storage.enabledCommands.konosend}
onValueChange={(v) => {
storage.enabledCommands.konosend = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="KonoChan Commands">
<TableSwitchRow
label="/konoself"
subLabel="Get random image from KonoChan (private)"
value={storage.enabledCommands.konoself}
onValueChange={(v) => {
storage.enabledCommands.konoself = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/konosend"
subLabel="Send random image from KonoChan to channel"
value={storage.enabledCommands.konosend}
onValueChange={(v) => {
storage.enabledCommands.konosend = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,85 +1,62 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { React } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableSwitchRow,
} from "../components/TableComponents";
export default function ListSettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
<RN.ScrollView
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="List Display Settings"
icon={getAssetIDByName("SettingsIcon")}
>
<FormSwitchRow
label="Always Send Detailed Plugin List"
subLabel="Always use detailed mode when listing plugins"
leading={
<FormRow.Icon source={getAssetIDByName("PuzzlePieceIcon")} />
}
value={storage.listSettings.pluginListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.pluginListAlwaysDetailed = v;
}}
/>
<FormSwitchRow
label="Always Send Detailed Theme List"
subLabel="Always use detailed mode when listing themes"
leading={
<FormRow.Icon source={getAssetIDByName("PaintPaletteIcon")} />
}
value={storage.listSettings.themeListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.themeListAlwaysDetailed = v;
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="List Display Settings">
<TableSwitchRow
label="Always Send Detailed Plugin List"
subLabel="Always use detailed mode when listing plugins"
value={storage.listSettings.pluginListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.pluginListAlwaysDetailed = v;
}}
/>
<TableSwitchRow
label="Always Send Detailed Theme List"
subLabel="Always use detailed mode when listing themes"
value={storage.listSettings.themeListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.themeListAlwaysDetailed = v;
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="Available List Commands"
icon={getAssetIDByName("ListViewIcon")}
>
<FormSwitchRow
label="/plugin-list"
subLabel="List all installed plugins"
leading={
<FormRow.Icon source={getAssetIDByName("PuzzlePieceIcon")} />
}
value={storage.enabledCommands.pluginList}
onValueChange={(v) => {
storage.enabledCommands.pluginList = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/theme-list"
subLabel="List all installed themes"
leading={
<FormRow.Icon source={getAssetIDByName("PaintPaletteIcon")} />
}
value={storage.enabledCommands.themeList}
onValueChange={(v) => {
storage.enabledCommands.themeList = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="Available List Commands">
<TableSwitchRow
label="/plugin-list"
subLabel="List all installed plugins"
value={storage.enabledCommands.pluginList}
onValueChange={(v) => {
storage.enabledCommands.pluginList = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/theme-list"
subLabel="List all installed themes"
value={storage.enabledCommands.themeList}
onValueChange={(v) => {
storage.enabledCommands.themeList = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,22 +1,19 @@
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { React, ReactNative as RN } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
import {
ScrollView,
Stack,
TableRowGroup,
TableRow,
} from "../components/TableComponents";
export default function NekosLifeCategoriesPage() {
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
const styles = RN.StyleSheet.create({
tableHeader: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 16,
backgroundColor: semanticColors.BACKGROUND_MODIFIER_ACCENT,
borderBottomWidth: 2,
borderBottomColor: semanticColors.BORDER_STRONG,
},
headerText: {
fontSize: 14,
@ -40,8 +37,6 @@ export default function NekosLifeCategoriesPage() {
alignItems: "center",
paddingVertical: 12,
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: semanticColors.BORDER_FAINT,
},
categoryName: {
fontSize: 16,
@ -71,15 +66,8 @@ export default function NekosLifeCategoriesPage() {
fontWeight: "600",
color: semanticColors.TEXT_NORMAL,
},
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
},
});
// nekos.life categories
const sfwCategories = [
{ name: "Avatar", value: "avatar" },
{ name: "Classic", value: "classic" },
@ -99,63 +87,46 @@ export default function NekosLifeCategoriesPage() {
{ name: "Woof", value: "woof" },
];
// Sort categories alphabetically
const sortedCategories = sfwCategories.sort((a, b) =>
a.name.localeCompare(b.name),
);
return (
<RN.ScrollView
style={styles.container}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="📊 Category Statistics"
icon={getAssetIDByName("ChartIcon")}
padding={true}
>
<RN.Text style={styles.infoText}>
Total SFW Categories: {sfwCategories.length}
{"\n\n"}
Use these category names with the /nekoslife command.{"\n"}
All categories are Safe For Work (SFW).
</RN.Text>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Category Statistics">
<TableRow
label={`${sfwCategories.length} SFW Categories`}
subLabel="Use these category names with the /nekoslife command. All categories are Safe For Work (SFW)."
/>
</TableRowGroup>
<BetterTableRowGroup
title="📋 SFW Categories"
icon={getAssetIDByName("ListViewIcon")}
>
{/* Table Header */}
<RN.View style={styles.tableHeader}>
<RN.Text style={[styles.headerText, styles.nameHeader]}>
Category Name
</RN.Text>
<RN.Text style={[styles.headerText, styles.codeHeader]}>
Command Code
</RN.Text>
<RN.Text style={[styles.headerText, styles.badgeHeader]}>
Type
</RN.Text>
</RN.View>
<TableRowGroup title="SFW Categories">
<RN.View style={styles.tableHeader}>
<RN.Text style={[styles.headerText, styles.nameHeader]}>
Category Name
</RN.Text>
<RN.Text style={[styles.headerText, styles.codeHeader]}>
Command Code
</RN.Text>
<RN.Text style={[styles.headerText, styles.badgeHeader]}>
Type
</RN.Text>
</RN.View>
{/* Table Rows */}
{sortedCategories.map((category, index) => (
<RN.View key={index} style={styles.categoryItem}>
<RN.Text style={styles.categoryName}>{category.name}</RN.Text>
<RN.Text style={styles.categoryValue}>{category.value}</RN.Text>
<RN.View style={styles.sfwBadgeContainer}>
<RN.View style={styles.sfwBadge}>
<RN.Text style={styles.badgeText}>SFW</RN.Text>
{sortedCategories.map((category, index) => (
<RN.View key={index} style={styles.categoryItem}>
<RN.Text style={styles.categoryName}>{category.name}</RN.Text>
<RN.Text style={styles.categoryValue}>{category.value}</RN.Text>
<RN.View style={styles.sfwBadgeContainer}>
<RN.View style={styles.sfwBadge}>
<RN.Text style={styles.badgeText}>SFW</RN.Text>
</RN.View>
</RN.View>
</RN.View>
</RN.View>
))}
</BetterTableRowGroup>
</RN.ScrollView>
))}
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,95 +1,79 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { React } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableSwitchRow,
} from "../components/TableComponents";
export default function OtherSettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
<RN.ScrollView
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="Message Commands"
icon={getAssetIDByName("ChatIcon")}
>
<FormSwitchRow
label="/firstmessage"
subLabel="Get the first message in a channel"
leading={<FormRow.Icon source={getAssetIDByName("ChatIcon")} />}
value={storage.enabledCommands.firstmessage}
onValueChange={(v) => {
storage.enabledCommands.firstmessage = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Message Commands">
<TableSwitchRow
label="/firstmessage"
subLabel="Get the first message in a channel"
value={storage.enabledCommands.firstmessage}
onValueChange={(v) => {
storage.enabledCommands.firstmessage = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="System Commands"
icon={getAssetIDByName("SettingsIcon")}
>
<FormSwitchRow
label="/sysinfo"
subLabel="Display system information"
leading={<FormRow.Icon source={getAssetIDByName("SettingsIcon")} />}
value={storage.enabledCommands.sysinfo}
onValueChange={(v) => {
storage.enabledCommands.sysinfo = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<TableRowGroup title="System Commands">
<TableSwitchRow
label="/sysinfo"
subLabel="Display system information"
value={storage.enabledCommands.sysinfo}
onValueChange={(v) => {
storage.enabledCommands.sysinfo = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="Friend Invites"
icon={getAssetIDByName("UserAddIcon")}
>
<FormSwitchRow
label="/invite create"
subLabel="Generate a friend invite link"
value={storage.enabledCommands.friendInviteCreate}
onValueChange={(v) => {
storage.enabledCommands.friendInviteCreate = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/view invites"
subLabel="View your current friend invites"
value={storage.enabledCommands.friendInviteView}
onValueChange={(v) => {
storage.enabledCommands.friendInviteView = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/revoke invites"
subLabel="Revoke all your friend invites"
value={storage.enabledCommands.friendInviteRevoke}
onValueChange={(v) => {
storage.enabledCommands.friendInviteRevoke = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="Friend Invites">
<TableSwitchRow
label="/invite create"
subLabel="Generate a friend invite link"
value={storage.enabledCommands.friendInviteCreate}
onValueChange={(v) => {
storage.enabledCommands.friendInviteCreate = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/view invites"
subLabel="View your current friend invites"
value={storage.enabledCommands.friendInviteView}
onValueChange={(v) => {
storage.enabledCommands.friendInviteView = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/revoke invites"
subLabel="Revoke all your friend invites"
value={storage.enabledCommands.friendInviteRevoke}
onValueChange={(v) => {
storage.enabledCommands.friendInviteRevoke = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,104 +1,71 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative as RN, stylesheet } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import BetterTableRowGroup from "../components/BetterTableRowGroup";
const { FormSwitchRow, FormRow } = Forms;
import { React } from "@vendetta/metro/common";
import {
ScrollView,
Stack,
TableRowGroup,
TableRow,
TableSwitchRow,
} from "../components/TableComponents";
export default function SpotifySettingsPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const styles = stylesheet.createThemedStyleSheet({
infoText: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
textAlign: "center",
lineHeight: 20,
},
});
return (
<RN.ScrollView
style={{ flex: 1, backgroundColor: semanticColors.BACKGROUND_PRIMARY }}
contentContainerStyle={{
flexGrow: 1,
paddingVertical: 16,
maxWidth: "100%",
}}
>
<BetterTableRowGroup
title="Spotify Commands"
icon={getAssetIDByName("SpotifyNeutralIcon")}
>
<FormSwitchRow
label="/spotify track"
subLabel="Share your current Spotify track"
leading={
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
}
value={storage.enabledCommands.spotifyTrack}
onValueChange={(v) => {
storage.enabledCommands.spotifyTrack = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/spotify album"
subLabel="Share your current track's album"
leading={
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
}
value={storage.enabledCommands.spotifyAlbum}
onValueChange={(v) => {
storage.enabledCommands.spotifyAlbum = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/spotify artists"
subLabel="Share your current track's artists"
leading={
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
}
value={storage.enabledCommands.spotifyArtists}
onValueChange={(v) => {
storage.enabledCommands.spotifyArtists = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<FormSwitchRow
label="/spotify cover"
subLabel="Share your current track's cover"
leading={
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
}
value={storage.enabledCommands.spotifyCover}
onValueChange={(v) => {
storage.enabledCommands.spotifyCover = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</BetterTableRowGroup>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Stack spacing={12}>
<TableRowGroup title="Spotify Commands">
<TableSwitchRow
label="/spotify track"
subLabel="Share your current Spotify track"
value={storage.enabledCommands.spotifyTrack}
onValueChange={(v) => {
storage.enabledCommands.spotifyTrack = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/spotify album"
subLabel="Share your current track's album"
value={storage.enabledCommands.spotifyAlbum}
onValueChange={(v) => {
storage.enabledCommands.spotifyAlbum = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/spotify artists"
subLabel="Share your current track's artists"
value={storage.enabledCommands.spotifyArtists}
onValueChange={(v) => {
storage.enabledCommands.spotifyArtists = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
<TableSwitchRow
label="/spotify cover"
subLabel="Share your current track's cover"
value={storage.enabledCommands.spotifyCover}
onValueChange={(v) => {
storage.enabledCommands.spotifyCover = v;
storage.pendingRestart = true;
forceUpdate();
}}
/>
</TableRowGroup>
<BetterTableRowGroup
title="About Spotify Commands"
icon={getAssetIDByName("InfoIcon")}
padding={true}
>
<RN.Text style={styles.infoText}>
These commands allow you to share your current Spotify activity in
Discord. Make sure you have Spotify connected to Discord for these
commands to work properly.
</RN.Text>
</BetterTableRowGroup>
</RN.ScrollView>
<TableRowGroup title="About Spotify Commands">
<TableRow
label="Spotify Integration"
subLabel="These commands allow you to share your current Spotify activity in Discord. Make sure you have Spotify connected to Discord for these commands to work properly."
/>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,16 +1,16 @@
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { React, ReactNative, stylesheet } from "@vendetta/metro/common";
import { React } from "@vendetta/metro/common";
import { NavigationNative } from "@vendetta/metro/common";
import { semanticColors } from "@vendetta/ui";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { Forms } from "@vendetta/ui/components";
import { alerts } from "@vendetta/ui";
import { showToast } from "@vendetta/ui/toasts";
import {
ScrollView,
Stack,
TableRowGroup,
TableRow,
} from "./components/TableComponents";
import Header from "./components/Header";
import BetterTableRowGroup from "./components/BetterTableRowGroup";
// Import all pages directly
import FactsSettingsPage from "./pages/FactsSettingsPage";
import ListSettingsPage from "./pages/ListSettingsPage";
import ImageSettingsPage from "./pages/ImageSettingsPage";
@ -21,60 +21,68 @@ import OtherSettingsPage from "./pages/OtherSettingsPage";
import HiddenSettingsPage from "./pages/HiddenSettingsPage";
import CreditsPage from "./pages/CreditsPage";
const { FormRow } = Forms;
if (storage.factSettings == null) {
storage.factSettings = {
sendAsReply: true,
includeCitation: false,
};
}
// Initialize storage with default values
storage.factSettings ??= {
sendAsReply: true,
includeCitation: false,
};
if (storage.listSettings == null) {
storage.listSettings = {
pluginListAlwaysDetailed: false,
themeListAlwaysDetailed: false,
};
}
storage.listSettings ??= {
pluginListAlwaysDetailed: false,
themeListAlwaysDetailed: false,
};
if (storage.garySettings == null) {
storage.garySettings = {
imageSource: "gary",
};
}
storage.garySettings ??= {
imageSource: "gary",
};
if (storage.enabledCommands == null) {
storage.enabledCommands = {
catfact: true,
dogfact: true,
useless: true,
petpet: true,
pluginList: true,
themeList: true,
konoself: true,
konosend: true,
firstmessage: true,
sysinfo: true,
spotifyTrack: true,
spotifyAlbum: true,
spotifyArtists: true,
spotifyCover: true,
gary: true,
ip: true,
lovefemboys: false,
nekoslife: false,
friendInviteCreate: true,
friendInviteView: true,
friendInviteRevoke: true,
};
}
storage.enabledCommands ??= {
catfact: true,
dogfact: true,
useless: true,
petpet: true,
pluginList: true,
themeList: true,
konoself: true,
konosend: true,
firstmessage: true,
sysinfo: true,
spotifyTrack: true,
spotifyAlbum: true,
spotifyArtists: true,
spotifyCover: true,
gary: true,
ip: true,
lovefemboys: false,
nekoslife: false,
friendInviteCreate: true,
friendInviteView: true,
friendInviteRevoke: true,
};
if (storage.pendingRestart == null) {
storage.pendingRestart = false;
}
storage.pendingRestart ??= false;
if (storage.hiddenSettings == null) {
storage.hiddenSettings = {
enabled: false,
visible: false,
konochanBypassNsfw: false,
};
}
// Hidden settings storage with NSFW bypass option
storage.hiddenSettings ??= {
enabled: false,
visible: false,
konochanBypassNsfw: false,
};
if (storage.sidebarEnabled == null) {
storage.sidebarEnabled = true;
}
// Sidebar setting (enabled by default)
storage.sidebarEnabled ??= true;
// MAIN SETTINGS COMPONENT
export default function Settings() {
useProxy(storage);
const navigation = NavigationNative.useNavigation();
@ -95,30 +103,26 @@ export default function Settings() {
};
}, []);
return (
<ReactNative.View style={{ flex: 1 }}>
<ReactNative.ScrollView
style={{
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
}}
contentContainerStyle={{
flexGrow: 1,
maxWidth: "100%",
}}
showsVerticalScrollIndicator={false}
>
<Header />
const garySourceLabel =
storage.garySettings.imageSource === "gary"
? "Gary API"
: storage.garySettings.imageSource === "catapi"
? "Cat API"
: storage.garySettings.imageSource === "minker"
? "Minker API"
: storage.garySettings.imageSource === "goober"
? "Goober API"
: "Gary API";
<BetterTableRowGroup
title="Command Categories"
icon={getAssetIDByName("ChannelListIcon")}
>
<FormRow
return (
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Header />
<Stack spacing={12}>
<TableRowGroup title="Command Categories">
<TableRow
label="Facts Commands"
subLabel="Cat facts, dog facts, and useless facts"
leading={<FormRow.Icon source={getAssetIDByName("BookmarkIcon")} />}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Facts Commands",
@ -126,11 +130,10 @@ export default function Settings() {
})
}
/>
<FormRow
<TableRow
label="List Commands"
subLabel="Plugin lists and theme lists"
leading={<FormRow.Icon source={getAssetIDByName("ListViewIcon")} />}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "List Commands",
@ -138,11 +141,10 @@ export default function Settings() {
})
}
/>
<FormRow
<TableRow
label="Image Commands"
subLabel="PetPet, KonoChan, and image utilities"
leading={<FormRow.Icon source={getAssetIDByName("ImageIcon")} />}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Image Commands",
@ -150,21 +152,10 @@ export default function Settings() {
})
}
/>
<FormRow
<TableRow
label="Gary Commands"
subLabel={`Gary images - Current: ${
storage.garySettings.imageSource === "gary"
? "Gary API"
: storage.garySettings.imageSource === "catapi"
? "Cat API"
: storage.garySettings.imageSource === "minker"
? "Minker API"
: storage.garySettings.imageSource === "goober"
? "Goober API"
: "Gary API"
}`}
leading={<FormRow.Icon source={getAssetIDByName("CameraIcon")} />}
trailing={<FormRow.Arrow />}
subLabel={`Gary images - Current: ${garySourceLabel}`}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Gary Commands",
@ -172,13 +163,10 @@ export default function Settings() {
})
}
/>
<FormRow
<TableRow
label="Spotify Commands"
subLabel="Share your Spotify activity"
leading={
<FormRow.Icon source={getAssetIDByName("SpotifyNeutralIcon")} />
}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Spotify Commands",
@ -186,17 +174,10 @@ export default function Settings() {
})
}
/>
<FormRow
<TableRow
label="Aliucord Commands"
subLabel="Commands from Aliucord"
leading={
<FormRow.Icon
source={{
uri: "https://avatars.githubusercontent.com/u/78881422?s=200&v=4",
}}
/>
}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Aliucord",
@ -204,11 +185,10 @@ export default function Settings() {
})
}
/>
<FormRow
<TableRow
label="Other Commands"
subLabel="Utility and system commands"
leading={<FormRow.Icon source={getAssetIDByName("WrenchIcon")} />}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Other Commands",
@ -216,18 +196,14 @@ export default function Settings() {
})
}
/>
</BetterTableRowGroup>
</TableRowGroup>
{storage.hiddenSettings?.visible && (
<BetterTableRowGroup
title="Hidden Settings"
icon={getAssetIDByName("EyeIcon")}
>
<FormRow
<TableRowGroup title="Hidden Settings">
<TableRow
label="Hidden Commands"
subLabel="Access to experimental and NSFW commands"
leading={<FormRow.Icon source={getAssetIDByName("EyeIcon")} />}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Hidden Settings",
@ -235,18 +211,14 @@ export default function Settings() {
})
}
/>
</BetterTableRowGroup>
</TableRowGroup>
)}
<BetterTableRowGroup
title="Other"
icon={getAssetIDByName("CircleInformationIcon-primary")}
>
<FormRow
<TableRowGroup title="Other">
<TableRow
label="Credits"
subLabel="View original authors of the plugins"
leading={<FormRow.Icon source={getAssetIDByName("HeartIcon")} />}
trailing={<FormRow.Arrow />}
trailing={<TableRow.Arrow />}
onPress={() =>
navigation.push("VendettaCustomPage", {
title: "Credits",
@ -254,11 +226,8 @@ export default function Settings() {
})
}
/>
</BetterTableRowGroup>
{/* Add sufficient bottom padding for scrolling */}
<ReactNative.View style={{ height: 32 }} />
</ReactNative.ScrollView>
</ReactNative.View>
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,52 +0,0 @@
export interface FactSettings {
sendAsReply: boolean;
includeCitation: boolean;
}
export interface ListSettings {
pluginListAlwaysDetailed: boolean;
themeListAlwaysDetailed: boolean;
}
export interface GarySettings {
imageSource: "gary" | "catapi" | "minker" | "goober";
}
export interface EnabledCommands {
catfact: boolean;
dogfact: boolean;
useless: boolean;
petpet: boolean;
pluginList: boolean;
themeList: boolean;
konoself: boolean;
konosend: boolean;
firstmessage: boolean;
sysinfo: boolean;
spotifyTrack: boolean;
spotifyAlbum: boolean;
spotifyArtists: boolean;
spotifyCover: boolean;
gary: boolean;
ip: boolean;
lovefemboys: boolean;
nekoslife: boolean;
friendInviteCreate: boolean;
friendInviteView: boolean;
friendInviteRevoke: boolean;
}
export interface HiddenSettings {
enabled: boolean;
visible: boolean;
konochanBypassNsfw: boolean;
}
export interface StorageType {
factSettings: FactSettings;
listSettings: ListSettings;
garySettings: GarySettings;
enabledCommands: EnabledCommands;
pendingRestart: boolean;
hiddenSettings: HiddenSettings;
}

View file

@ -92,7 +92,7 @@ function patchPanelUI(tabs, patches) {
),
);
} catch (error) {
logger.info("Panel UI patch failed graciously 💔", error);
// we don't need to care about this error
}
}

View file

@ -6,7 +6,11 @@
"moduleResolution": "node",
"esModuleInterop": true,
"jsx": "react-native",
"allowJs": true
"allowJs": true,
"baseUrl": ".",
"paths": {
"@nlib/*": ["nexxstuff/*"]
}
},
"include": [
"plugins",