263 lines
7.6 KiB
TypeScript
263 lines
7.6 KiB
TypeScript
import { storage } from "@vendetta/plugin";
|
|
import { React, ReactNative as RN, stylesheet } 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";
|
|
|
|
export default function Header() {
|
|
const [clickCounter, setClickCounter] = React.useState(0);
|
|
const [clickTimeout, setClickTimeout] = React.useState<NodeJS.Timeout | null>(
|
|
null,
|
|
);
|
|
const [avatarUrl, setAvatarUrl] = React.useState<string | null>(null);
|
|
const [isAnimatedAvatar, setIsAnimatedAvatar] = React.useState(false);
|
|
|
|
// Initialize sidebar enabled setting if not exists
|
|
React.useEffect(() => {
|
|
if (storage.sidebarEnabled === undefined) {
|
|
storage.sidebarEnabled = true;
|
|
}
|
|
}, []);
|
|
|
|
// Get current user from Discord's user store
|
|
const users = findByProps("getUser", "getCurrentUser");
|
|
const currentUser = users?.getCurrentUser?.();
|
|
|
|
// Get avatar URL utility
|
|
const { getUserAvatarURL } = findByProps("getUserAvatarURL") || {};
|
|
const { getDefaultAvatarURL } = findByProps("getDefaultAvatarURL") || {};
|
|
|
|
const styles = stylesheet.createThemedStyleSheet({
|
|
container: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
paddingVertical: 24,
|
|
paddingHorizontal: 16,
|
|
width: "100%",
|
|
justifyContent: "center",
|
|
},
|
|
leftSection: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
flex: 1,
|
|
gap: 20,
|
|
maxWidth: 500,
|
|
},
|
|
avatarContainer: {
|
|
position: "relative",
|
|
width: 96,
|
|
height: 96,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
},
|
|
avatarWrapper: {
|
|
width: 96,
|
|
height: 96,
|
|
borderRadius: 24,
|
|
backgroundColor: semanticColors.BACKGROUND_SECONDARY,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
overflow: "hidden",
|
|
borderWidth: 2,
|
|
borderColor: semanticColors.BACKGROUND_TERTIARY,
|
|
},
|
|
avatar: {
|
|
width: 92,
|
|
height: 92,
|
|
borderRadius: 22,
|
|
},
|
|
cogwheelBackground: {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
borderRadius: 24,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
opacity: 0,
|
|
},
|
|
cogwheelIcon: {
|
|
width: 32,
|
|
height: 32,
|
|
tintColor: semanticColors.INTERACTIVE_ACTIVE,
|
|
},
|
|
textContainer: {
|
|
flex: 1,
|
|
marginLeft: 4,
|
|
justifyContent: "center",
|
|
},
|
|
animatedIndicator: {
|
|
position: "absolute",
|
|
top: 8,
|
|
right: 8,
|
|
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
borderRadius: 10,
|
|
width: 20,
|
|
height: 20,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
zIndex: 1,
|
|
},
|
|
gifText: {
|
|
color: "#FFFFFF",
|
|
fontSize: 10,
|
|
fontWeight: "bold",
|
|
},
|
|
avatarPlaceholder: {
|
|
width: 48,
|
|
height: 48,
|
|
tintColor: semanticColors.INTERACTIVE_NORMAL,
|
|
},
|
|
});
|
|
|
|
// Fetch user's avatar on component mount
|
|
React.useEffect(() => {
|
|
if (currentUser) {
|
|
let url;
|
|
let animated = false;
|
|
|
|
// Check if avatar is animated (starts with "a_")
|
|
const hasAnimatedAvatar = currentUser.avatar?.startsWith("a_");
|
|
|
|
// 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;
|
|
}
|
|
// Fall back to default avatar
|
|
else if (getDefaultAvatarURL && typeof getDefaultAvatarURL === 'function') {
|
|
try {
|
|
url = getDefaultAvatarURL(currentUser);
|
|
} catch (error) {
|
|
console.error('Error getting default avatar URL:', error);
|
|
url = null;
|
|
}
|
|
}
|
|
// Last resort: Construct Discord CDN URL
|
|
else if (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 {
|
|
// Default Discord avatar based on discriminator or id
|
|
const defaultAvatarIndex = currentUser.discriminator
|
|
? parseInt(currentUser.discriminator) % 5
|
|
: (parseInt(currentUser.id) >> 22) % 6;
|
|
url = `https://cdn.discordapp.com/embed/avatars/${defaultAvatarIndex}.png`;
|
|
}
|
|
|
|
setAvatarUrl(url);
|
|
setIsAnimatedAvatar(animated);
|
|
}
|
|
}, [currentUser]);
|
|
|
|
const handleAvatarPress = () => {
|
|
if (storage.hiddenSettings?.enabled) {
|
|
storage.hiddenSettings.visible = !storage.hiddenSettings.visible;
|
|
showToast(
|
|
`Hidden settings ${storage.hiddenSettings.visible ? "visible" : "hidden"}`,
|
|
getAssetIDByName("SettingsIcon"),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (clickTimeout) {
|
|
clearTimeout(clickTimeout);
|
|
}
|
|
|
|
const newTimeout = setTimeout(() => {
|
|
setClickCounter(0);
|
|
}, 1000);
|
|
setClickTimeout(newTimeout);
|
|
|
|
const newCounter = clickCounter + 1;
|
|
setClickCounter(newCounter);
|
|
|
|
if (newCounter < 10) {
|
|
return;
|
|
}
|
|
|
|
showToast("Hidden settings unlocked!", getAssetIDByName("CheckmarkIcon"));
|
|
storage.hiddenSettings = {
|
|
...storage.hiddenSettings,
|
|
enabled: true,
|
|
visible: true,
|
|
};
|
|
setClickCounter(0);
|
|
};
|
|
|
|
const handleAvatarLongPress = () => {
|
|
const newState = !storage.sidebarEnabled;
|
|
storage.sidebarEnabled = newState;
|
|
storage.pendingRestart = true;
|
|
showToast(
|
|
`Sidebar ${newState ? "enabled" : "disabled"}. Restart Discord to apply.`,
|
|
getAssetIDByName(newState ? "CheckmarkIcon" : "XIcon"),
|
|
);
|
|
};
|
|
|
|
const [showCogwheel, setShowCogwheel] = React.useState(false);
|
|
|
|
return (
|
|
<RN.View style={styles.container}>
|
|
<RN.View style={styles.leftSection}>
|
|
<RN.Pressable
|
|
style={styles.avatarContainer}
|
|
onPress={handleAvatarPress}
|
|
onPressIn={() => setShowCogwheel(true)}
|
|
onPressOut={() => setShowCogwheel(false)}
|
|
delayLongPress={500}
|
|
onLongPress={handleAvatarLongPress}
|
|
>
|
|
<RN.View style={styles.avatarWrapper}>
|
|
{avatarUrl ? (
|
|
<>
|
|
<RN.Image
|
|
source={{ uri: avatarUrl }}
|
|
style={styles.avatar}
|
|
resizeMode="cover"
|
|
/>
|
|
{isAnimatedAvatar && (
|
|
<RN.View style={styles.animatedIndicator}>
|
|
</RN.View>
|
|
)}
|
|
</>
|
|
) : (
|
|
<RN.Image
|
|
source={getAssetIDByName("ic_account_circle_24px")}
|
|
style={styles.avatarPlaceholder}
|
|
/>
|
|
)}
|
|
</RN.View>
|
|
|
|
{/* Cogwheel background overlay (appears on press) */}
|
|
<RN.View
|
|
style={[
|
|
styles.cogwheelBackground,
|
|
{ opacity: showCogwheel ? 0.8 : 0 },
|
|
]}
|
|
>
|
|
<RN.Image
|
|
source={getAssetIDByName("SettingsIcon")}
|
|
style={styles.cogwheelIcon}
|
|
/>
|
|
</RN.View>
|
|
</RN.Pressable>
|
|
|
|
<RN.View style={styles.textContainer}>
|
|
<Text variant="display-md" color="TEXT_DEFAULT" align="left">
|
|
Commands
|
|
</Text>
|
|
<Text variant="text-md/bold" color="TEXT_MUTED" align="left">
|
|
A collection of useful commands for Vendetta like clients
|
|
</Text>
|
|
</RN.View>
|
|
</RN.View>
|
|
</RN.View>
|
|
);
|
|
}
|