ServerDrawer: add DM tile to the drawer grid, with a settings screen

This commit is contained in:
Ally Founder 2026-08-11 11:49:20 -04:00
parent 68e478d7ca
commit 8bbfd6d7a5
5 changed files with 113 additions and 0 deletions

View file

@ -0,0 +1,45 @@
import React from "react";
import { View, Pressable, Image, StyleSheet } from "react-native";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { findByProps, findByName } from "@vendetta/metro";
const ICON = 48;
const ChatIcon = getAssetIDByName("ChatIcon");
const Haptic = findByProps("triggerHapticFeedback", "HapticFeedbackTypes");
const ChannelActions = findByProps("selectPrivateChannel");
const SelectedChannelStore = findByName("SelectedChannelStore");
const Flux = findByProps("useStateFromStores");
const NavContext = findByProps("getGuildId");
const colors = findByProps("colors", "unsafe_rawColors")?.colors;
function openDms() {
Haptic?.triggerHapticFeedback?.(Haptic.HapticFeedbackTypes.SOFT);
if (ChannelActions?.selectPrivateChannel) {
const lastChannelId = SelectedChannelStore?.getLastSelectedChannelId?.();
ChannelActions.selectPrivateChannel(lastChannelId);
}
}
// Was previously only reachable via the native left server rail's own DM shortcut icon - moved
// into the drawer's own grid instead, as the first tile, so DMs live in the same place as every
// other server rather than in a spot that only exists on the rail this plugin otherwise hides.
export default function DmTile() {
const selected = Flux?.useStateFromStores?.(
[NavContext],
() => NavContext?.getGuildId?.() == null,
) ?? false;
return (
<Pressable onPress={openDms} style={st.outer}>
<View style={[st.icon, { backgroundColor: selected ? (colors?.BG_ACCENT ?? "#5865f2") : "rgba(128,128,128,0.24)" }]}>
<Image source={ChatIcon} style={{ width: 24, height: 24, tintColor: "#fff" }} />
</View>
</Pressable>
);
}
const st = StyleSheet.create({
outer: { width: ICON, height: ICON },
icon: { width: ICON, height: ICON, borderRadius: 16, alignItems: "center", justifyContent: "center" },
});

View file

@ -1,9 +1,12 @@
import React from "react"; import React from "react";
import { View, Text, Pressable, Animated, Dimensions, StyleSheet, BackHandler } from "react-native"; import { View, Text, Pressable, Animated, Dimensions, StyleSheet, BackHandler } from "react-native";
import { find, findByProps, findByStoreName } from "@vendetta/metro"; import { find, findByProps, findByStoreName } from "@vendetta/metro";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { GuildNode } from "../utils/theme"; import { GuildNode } from "../utils/theme";
import GuildItem from "./GuildItem"; import GuildItem from "./GuildItem";
import FolderItem from "./FolderItem"; import FolderItem from "./FolderItem";
import DmTile from "./DmTile";
const Flux = findByProps("useStateFromStores"); const Flux = findByProps("useStateFromStores");
const SortedGuildStore = findByStoreName("SortedGuildStore"); const SortedGuildStore = findByStoreName("SortedGuildStore");
@ -54,6 +57,8 @@ function CreateJoinButton() {
} }
export default function ServerDrawerSheet({ gestureContext }: { gestureContext: any }) { export default function ServerDrawerSheet({ gestureContext }: { gestureContext: any }) {
useProxy(storage);
const pick = React.useCallback((id: string) => { const pick = React.useCallback((id: string) => {
Haptic?.triggerHapticFeedback(Haptic.HapticFeedbackTypes.SOFT); Haptic?.triggerHapticFeedback(Haptic.HapticFeedbackTypes.SOFT);
if (Routing?.transitionToGuild) { if (Routing?.transitionToGuild) {
@ -110,6 +115,7 @@ export default function ServerDrawerSheet({ gestureContext }: { gestureContext:
style={[st.grid, { paddingHorizontal: padX, gap: GAP }]} style={[st.grid, { paddingHorizontal: padX, gap: GAP }]}
onLayout={onLayout} onLayout={onLayout}
> >
{!storage.hideDmTile && <DmTile />}
{nodes.map((node) => {nodes.map((node) =>
node.type === "folder" node.type === "folder"
? <FolderItem key={node.id} node={node} onPick={pick} /> ? <FolderItem key={node.id} node={node} onPick={pick} />

View file

@ -5,12 +5,16 @@ import { patchGetQuestAsset } from "./patches/getQuestAsset";
import { patchExpanded, patchEmpty } from "./patches/contentPatch"; import { patchExpanded, patchEmpty } from "./patches/contentPatch";
import { patchHideGuildsBar } from "./patches/hideGuildsBar"; import { patchHideGuildsBar } from "./patches/hideGuildsBar";
import { patchCreateElement } from "./patches/createElementIntercept"; import { patchCreateElement } from "./patches/createElementIntercept";
import { storage } from "@vendetta/plugin";
import Settings from "./ui/Settings";
const TAG = "[ServerDrawer]"; const TAG = "[ServerDrawer]";
const cleanups: (() => void)[] = []; const cleanups: (() => void)[] = [];
export default { export default {
onLoad() { onLoad() {
storage.hideDmTile ??= false;
console.log(TAG, "onLoad"); console.log(TAG, "onLoad");
let patched = 0; let patched = 0;
@ -37,4 +41,5 @@ export default {
for (const fn of cleanups) fn(); for (const fn of cleanups) fn();
cleanups.length = 0; cleanups.length = 0;
}, },
settings: Settings,
}; };

View file

@ -0,0 +1,29 @@
import { React, ReactNative } from "@vendetta/metro/common";
import { findByProps } from "@vendetta/metro";
const { View, Text } = ReactNative;
const colors = findByProps("colors", "unsafe_rawColors")?.colors;
/** Small bordered callout for explanatory text in a settings screen. */
export default function NoteBox({ children, style }: { children: any; style?: any }) {
const textColor = colors?.TEXT_MUTED ?? colors?.TEXT_NORMAL ?? "#96989d";
return (
<View
style={[
{
marginHorizontal: 16,
marginVertical: 8,
padding: 10,
borderRadius: 8,
borderWidth: 1,
borderColor: "rgba(128,128,128,0.25)"
},
style
]}
>
<Text style={{ fontSize: 12.5, lineHeight: 18, opacity: 0.75, color: textColor }}>{children}</Text>
</View>
);
}

View file

@ -0,0 +1,28 @@
import { React } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
import { TableRowGroup, TableSwitchRow } from "@shared/ui/table";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import NoteBox from "./NoteBox";
export default function Settings() {
useProxy(storage);
return (
<SettingsScaffold>
<NoteBox>
Server Drawer takes over the Quest Dock (the strip above the tab bar) to show your
servers as a grid instead - tap to expand, long-press a server for its usual context
menu, and the + button creates or joins a server through Discord's own modal.
</NoteBox>
<TableRowGroup title="Layout">
<TableSwitchRow
label="Hide the DMs tile"
subLabel="Remove the DMs tile from the drawer entirely"
value={!!storage.hideDmTile}
onValueChange={(v: boolean) => { storage.hideDmTile = v; }}
/>
</TableRowGroup>
</SettingsScaffold>
);
}