Funny UI changes

This commit is contained in:
amsyarasyiq 2024-03-09 04:06:41 +08:00
parent cea9b1c7ba
commit 190940b417
13 changed files with 268 additions and 203 deletions

View file

@ -1,4 +1,5 @@
{
"ABOUT": "About",
"ACTIONS": "Actions",
"ARE_YOU_SURE_TO_CLEAR_DATA": "Are you sure you wish to clear the data of {name}?",
"ARE_YOU_SURE_TO_DELETE_PLUGIN": "Are you sure you wish to delete {name}? This will clear all of the plugin's data.",

View file

@ -39,7 +39,7 @@ function getItemsByQuery<T extends { id?: string; }>(items: T[], query: string):
const reanimated = findByProps("useSharedValue");
const { FloatingActionButton } = findByProps("FloatingActionButton");
export default function AddonPage<T>({ title, floatingButtonText, fetchFunction, items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps<T>) {
export default function AddonPage<T>({ floatingButtonText, fetchFunction, items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps<T>) {
useProxy(settings);
useProxy(items);
@ -67,9 +67,8 @@ export default function AddonPage<T>({ title, floatingButtonText, fetchFunction,
collapseText.value = Number(e.nativeEvent.contentOffset.y > yOffset.current);
yOffset.current = e.nativeEvent.contentOffset.y;
}}
onMomentumScrollEnd={() => collapseText.value = 0}
style={{ paddingHorizontal: 10, paddingTop: 10 }}
contentContainerStyle={{ paddingBottom: 70 }}
contentContainerStyle={{ paddingBottom: 90, paddingHorizontal: 5 }}
data={getItemsByQuery(Object.values(items), search)}
renderItem={({ item, index }) => <CardComponent item={item} index={index} />}
/>

View file

@ -1,41 +1,67 @@
import { getAssetIDByName } from "@lib/api/assets";
import { TableCheckboxRow, TableRow, TableRowGroup, TableRowIcon, TableSwitchRow } from "@lib/ui/components/discord/Redesign";
import { FormRow } from "@lib/ui/components/discord/Forms";
import { FormCheckbox, FormSwitch, IconButton } from "@lib/ui/components/discord/Redesign";
import { createStyles, TextStyleSheet } from "@lib/ui/styles";
import { ReactNative as RN } from "@metro/common";
import { findByProps } from "@metro/filters";
import { semanticColors } from "@ui/color";
import ContextMenu from "@ui/components/ContextMenu";
import { createStyles } from "@ui/styles";
import { Image, TouchableOpacity, View } from "react-native";
import { ImageBackground, View } from "react-native";
const { hideActionSheet } = findByProps("openLazy", "hideActionSheet");
const { showSimpleActionSheet } = findByProps("showSimpleActionSheet");
// TODO: These styles work weirdly. iOS has cramped text, Android with low DPI probably does too. Fix?
const useStyles = createStyles({
card: {
backgroundColor: semanticColors?.BACKGROUND_SECONDARY,
borderRadius: 15,
backgroundColor: semanticColors?.CARD_SECONDARY_BG,
borderRadius: 12,
overflow: "hidden"
},
header: {
padding: 0,
backgroundColor: semanticColors?.BACKGROUND_TERTIARY,
},
headerLeading: {
flexDirection: "column",
justifyContent: "center",
scale: 1.2
},
headerTrailing: {
display: "flex",
flexDirection: "row",
gap: 15,
alignItems: "center"
},
headerLabel: {
...TextStyleSheet["heading-md/semibold"],
color: semanticColors.TEXT_NORMAL,
},
headerSubtitle: {
...TextStyleSheet["text-md/semibold"],
color: semanticColors.TEXT_MUTED,
},
descriptionLabel: {
...TextStyleSheet["text-md/semibold"],
color: semanticColors.TEXT_NORMAL,
},
actions: {
flexDirection: "row-reverse",
alignItems: "center",
gap: 5
},
actionIcon: {
width: 22,
height: 22,
marginLeft: 5,
tintColor: semanticColors?.INTERACTIVE_NORMAL,
},
icon: {
width: 22,
height: 22,
marginLeft: 5,
tintColor: semanticColors?.INTERACTIVE_NORMAL,
iconStyle: {
tintColor: semanticColors.LOGO_PRIMARY,
opacity: 0.2,
height: 64,
width: 64,
left: void 0,
right: "30%",
top: "-10%"
}
});
interface Action {
icon: string;
disabled?: boolean;
onPress: () => void;
}
@ -51,69 +77,89 @@ export interface CardWrapper<T> {
interface CardProps {
index?: number;
headerLabel: string | React.ComponentType;
headerLabel: string;
headerSublabel?: string;
headerIcon?: string;
toggleType?: "switch" | "radio";
toggleValue?: boolean;
onToggleChange?: (v: boolean) => void;
descriptionLabel?: string | React.ComponentType;
/** @deprecated use overflowActions */
descriptionLabel?: string;
actions?: Action[];
overflowTitle?: string;
overflowActions?: OverflowAction[];
overflowIcon?: string;
}
export default function Card(props: CardProps) {
const styles = useStyles();
let pressableState = props.toggleValue ?? false;
const headerProps = {
style: styles.header,
label: props.headerLabel,
icon: props.headerIcon && <TableRowIcon source={getAssetIDByName(props.headerIcon)} />
};
return (
<View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}>
<TableRowGroup>
{props.toggleType && (props.toggleType === "switch" ?
<TableSwitchRow
{...headerProps}
value={props.toggleValue}
onValueChange={props.onToggleChange}
/> : props.toggleType === "radio" ?
<TableCheckboxRow
{...headerProps}
onPress={() => {
pressableState = !pressableState;
props.onToggleChange?.(pressableState);
}}
checked={props.toggleValue}
/> : undefined) || <TableRow {...headerProps} />}
<TableRow
label={props.descriptionLabel}
<RN.View style={[styles.card, { marginTop: props.index !== 0 ? 15 : 0 }]}>
<ImageBackground
source={props.headerIcon && getAssetIDByName(props.headerIcon) || {}}
resizeMode="cover"
imageStyle={styles.iconStyle}
>
<FormRow
style={styles.header}
label={
<View style={styles.headerLeading}>
<RN.Text style={styles.headerLabel}>{props.headerLabel}</RN.Text>
{props.headerSublabel && (
<RN.Text style={styles.headerSubtitle}>{props.headerSublabel}</RN.Text>
)}
</View>
}
trailing={
<View style={styles.actions}>
{props.overflowActions &&
<ContextMenu
triggerOnLongPress={false}
items={props.overflowActions.map(i => ({
label: i.label,
iconSource: getAssetIDByName(i.icon),
action: i.onPress
}))}
align="auto"
title={props.overflowTitle!!}
children={props => <TouchableOpacity {...props}>
<Image style={styles.icon} source={getAssetIDByName("ic_more_24px")} />
</TouchableOpacity>}
<View style={styles.headerTrailing}>
<View style={styles.actions}>
{props.overflowActions &&
<IconButton
onPress={() => showSimpleActionSheet({
key: "CardOverflow",
header: {
title: props.overflowTitle,
icon: props.headerIcon && <FormRow.Icon style={{ marginRight: 8 }} source={getAssetIDByName(props.headerIcon)} />,
onClose: () => hideActionSheet(),
},
options: props.overflowActions?.map(i => ({ ...i, icon: getAssetIDByName(i.icon) })),
})}
size="sm"
variant="secondary"
icon={getAssetIDByName("CircleInformationIcon")}
/>}
{props.actions?.map(({ icon, onPress, disabled }) => (
<IconButton
onPress={onPress}
disabled={disabled}
size="sm"
variant="secondary"
icon={getAssetIDByName(icon)}
/>
))}
</View>
{props.toggleType && (props.toggleType === "switch" ?
<FormSwitch
value={props.toggleValue}
onValueChange={props.onToggleChange}
/>
}
:
<RN.Pressable onPress={() => {
props.onToggleChange?.(!props.toggleValue);
}}>
<FormCheckbox checked={props.toggleValue} />
</RN.Pressable>
)}
</View>
}
/>
</TableRowGroup>
</View>
<FormRow
label={
<RN.View>
<RN.Text style={styles.descriptionLabel}>{props.descriptionLabel}</RN.Text>
</RN.View>
}
/>
</ImageBackground>
</RN.View>
);
}

View file

@ -29,8 +29,9 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
<Card
index={index}
// TODO: Actually make use of user IDs
headerLabel={`${plugin.manifest.name} by ${plugin.manifest.authors.map(i => i.name).join(", ")}`}
headerIcon={plugin.manifest.vendetta?.icon || "ic_application_command_24px"}
headerLabel={`${plugin.manifest.name}`}
headerSublabel={plugin.manifest.authors?.[0] && `by ${plugin.manifest.authors.map(i => i.name).join(", ")}`}
headerIcon={plugin.manifest.vendetta?.icon}
toggleType="switch"
toggleValue={plugin.enabled}
onToggleChange={(v: boolean) => {
@ -42,6 +43,14 @@ export default function PluginCard({ item: plugin, index }: CardWrapper<BunnyPlu
}}
descriptionLabel={plugin.manifest.description}
overflowTitle={plugin.manifest.name}
actions={[{
icon: "WrenchIcon",
disabled: !settings,
onPress: () => navigation.push("VendettaCustomPage", {
title: plugin.manifest.name,
render: settings,
})
}]}
overflowActions={[
...(settings ? [{
label: Strings.OVERFLOW_PLUGIN_SETTINGS,

View file

@ -1,33 +0,0 @@
import { Strings } from "@core/i18n";
import { getScreens } from "@core/ui/settings/data";
import { getAssetIDByName } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
import { settings } from "@lib/settings";
import { FormDivider, FormRow, FormSection } from "@lib/ui/components/discord/Forms";
import { NavigationNative } from "@metro/common";
import { ErrorBoundary } from "@ui/components";
export default function SettingsSection() {
const navigation = NavigationNative.useNavigation();
useProxy(settings);
const screens = getScreens();
return (
<ErrorBoundary>
<FormSection key="Vendetta" title={`${Strings.BUNNY}${settings.safeMode?.enabled ? ` (${Strings.SAFE_MODE})` : ""}`}>
{screens.filter(s => (s.shouldRender?.() ?? true)).map((s, i) => (
<>
<FormRow
label={s.title()}
leading={<FormRow.Icon source={getAssetIDByName(s.icon!)} />}
trailing={FormRow.Arrow}
onPress={() => navigation.push(s.key)}
/>
{i !== screens.length - 1 && <FormDivider />}
</>
))}
</FormSection>
</ErrorBoundary>
);
}

View file

@ -12,11 +12,8 @@ import { showToast } from "@ui/toasts";
async function selectAndApply(value: boolean, id: string) {
try {
await selectTheme(value ? id : "default");
value ? applyTheme(themes[id]) : applyTheme(null);
// TODO: Implement native side reload-less & check if it's applied by 100%
showToast(Strings.THEMES_RELOAD_FOR_CHANGES, getAssetIDByName("yellow-alert"));
await selectTheme(value ? id : null);
applyTheme(value ? themes[id] : null);
} catch (e: any) {
console.error("Error while selectAndApply,", e);
}
@ -25,6 +22,8 @@ async function selectAndApply(value: boolean, id: string) {
export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
useProxy(theme);
// little hack, our useProxy is kinda not reliable here
const [, forceUpdate] = React.useReducer(n => ~n, 0);
const [removed, setRemoved] = React.useState(false);
// This is needed because of React™
@ -35,12 +34,14 @@ export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
return (
<Card
index={index}
headerLabel={`${theme.data.name} ${authors ? `by ${authors.map(i => i.name).join(", ")}` : ""}`}
headerLabel={theme.data.name}
headerSublabel={authors ? `by ${authors.map(i => i.name).join(", ")}` : ""}
descriptionLabel={theme.data.description ?? "No description."}
toggleType={!settings.safeMode?.enabled ? "radio" : undefined}
toggleValue={theme.selected}
onToggleChange={(v: boolean) => {
selectAndApply(v, theme.id);
forceUpdate();
}}
overflowTitle={theme.data.name}
overflowActions={[

View file

@ -7,7 +7,9 @@ import { registerSection } from "@lib/ui/settings";
import { version } from "bunny-build";
// @ts-ignore
import PyoncordIcon from "../../../assets/icons/pyon64.png";
import _PyoncordIcon from "../../../assets/icons/pyon64.png";
export const PyoncordIcon = _PyoncordIcon as string;
export default function initSettings() {
registerSection({

View file

@ -0,0 +1,91 @@
import { Strings } from "@core/i18n";
import Version from "@core/ui/components/Version";
import { useProxy } from "@lib/api/storage";
import { getDebugInfo } from "@lib/debug";
import { settings } from "@lib/settings";
import { Stack, TableRowGroup } from "@lib/ui/components/discord/Redesign";
import { Platform, ScrollView } from "react-native";
export default function About() {
const debugInfo = getDebugInfo();
useProxy(settings);
const versions = [
{
label: Strings.BUNNY,
version: debugInfo.bunny.version,
icon: "ic_progress_wrench_24px",
},
{
label: "Discord",
version: `${debugInfo.discord.version} (${debugInfo.discord.build})`,
icon: "Discord",
},
{
label: "React",
version: debugInfo.react.version,
icon: "ic_category_16px",
},
{
label: "React Native",
version: debugInfo.react.nativeVersion,
icon: "mobile",
},
{
label: Strings.BYTECODE,
version: debugInfo.hermes.bytecodeVersion,
icon: "ic_server_security_24px",
},
];
const platformInfo = [
{
label: Strings.LOADER,
version: debugInfo.bunny.loader,
icon: "ic_download_24px",
},
{
label: Strings.OPERATING_SYSTEM,
version: `${debugInfo.os.name} ${debugInfo.os.version}`,
icon: "ic_cog_24px"
},
...(debugInfo.os.sdk ? [{
label: "SDK",
version: debugInfo.os.sdk,
icon: "pencil"
}] : []),
{
label: Strings.MANUFACTURER,
version: debugInfo.device.manufacturer,
icon: "ic_badge_staff"
},
{
label: Strings.BRAND,
version: debugInfo.device.brand,
icon: "ic_settings_boost_24px"
},
{
label: Strings.MODEL,
version: debugInfo.device.model,
icon: "ic_phonelink_24px"
},
{
label: Platform.select({ android: Strings.CODENAME, ios: Strings.MACHINE_ID })!,
version: debugInfo.device.codename,
icon: "ic_compose_24px"
}
];
return (
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={24}>
<TableRowGroup title={Strings.VERSIONS}>
{versions.map(v => <Version padding label={v.label} version={v.version} icon={v.icon} />)}
</TableRowGroup>
<TableRowGroup title={Strings.PLATFORM}>
{platformInfo.map(p => <Version padding label={p.label} version={p.version} icon={p.icon} />)}
</TableRowGroup>
</Stack>
</ScrollView>
);
}

View file

@ -1,99 +1,47 @@
import { Strings } from "@core/i18n";
import Version from "@core/ui/components/Version";
import { PyoncordIcon } from "@core/ui/settings";
import { getAssetIDByName } from "@lib/api/assets";
import { useProxy } from "@lib/api/storage";
import { _toggleSafeMode, getDebugInfo } from "@lib/debug";
import { settings } from "@lib/settings";
import { Stack, TableRow, TableRowGroup, TableSwitchRow } from "@lib/ui/components/discord/Redesign";
import { Stack, TableRow, TableRowGroup, TableSwitchRow, useNavigation } from "@lib/ui/components/discord/Redesign";
import { DISCORD_SERVER, GITHUB } from "@lib/utils/constants";
import { url } from "@metro/common";
import { semanticColors } from "@ui/color";
import { Summary } from "@ui/components";
import { createStyles } from "@ui/styles";
import { NativeModules, Platform, ScrollView } from "react-native";
import { NativeModules, ScrollView } from "react-native";
const debugInfo = getDebugInfo();
const useStyles = createStyles({
container: {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_MOBILE_SECONDARY,
}
});
import About from "./About";
export default function General() {
const styles = useStyles();
useProxy(settings);
const versions = [
{
label: Strings.BUNNY,
version: debugInfo.bunny.version,
icon: "ic_progress_wrench_24px",
},
{
label: "Discord",
version: `${debugInfo.discord.version} (${debugInfo.discord.build})`,
icon: "Discord",
},
{
label: "React",
version: debugInfo.react.version,
icon: "ic_category_16px",
},
{
label: "React Native",
version: debugInfo.react.nativeVersion,
icon: "mobile",
},
{
label: Strings.BYTECODE,
version: debugInfo.hermes.bytecodeVersion,
icon: "ic_server_security_24px",
},
];
const platformInfo = [
{
label: Strings.LOADER,
version: debugInfo.bunny.loader,
icon: "ic_download_24px",
},
{
label: Strings.OPERATING_SYSTEM,
version: `${debugInfo.os.name} ${debugInfo.os.version}`,
icon: "ic_cog_24px"
},
...(debugInfo.os.sdk ? [{
label: "SDK",
version: debugInfo.os.sdk,
icon: "pencil"
}] : []),
{
label: Strings.MANUFACTURER,
version: debugInfo.device.manufacturer,
icon: "ic_badge_staff"
},
{
label: Strings.BRAND,
version: debugInfo.device.brand,
icon: "ic_settings_boost_24px"
},
{
label: Strings.MODEL,
version: debugInfo.device.model,
icon: "ic_phonelink_24px"
},
{
label: Platform.select({ android: Strings.CODENAME, ios: Strings.MACHINE_ID })!,
version: debugInfo.device.codename,
icon: "ic_compose_24px"
}
];
const debugInfo = getDebugInfo();
const navigation = useNavigation();
return (
<ScrollView style={styles.container}>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={24}>
<TableRowGroup title={Strings.INFO}>
<TableRow
label={Strings.BUNNY}
icon={<TableRow.Icon source={{ uri: PyoncordIcon }} />}
trailing={<TableRow.TrailingText text={debugInfo.bunny.version} />}
/>
<TableRow
label={"Discord"}
icon={<TableRow.Icon source={getAssetIDByName("Discord")} />}
trailing={<TableRow.TrailingText text={`${debugInfo.discord.version} (${debugInfo.discord.build})`} />}
/>
<TableRow
arrow
label={Strings.ABOUT}
icon={<TableRow.Icon source={getAssetIDByName("CircleInformationIcon")} />}
trailing={TableRow.Arrow}
onPress={() => navigation.push("VendettaCustomPage", {
title: Strings.ABOUT,
render: () => <About />,
})}
/>
</TableRowGroup>
<TableRowGroup title={Strings.LINKS}>
<TableRow
label={Strings.DISCORD_SERVER}
@ -129,14 +77,6 @@ export default function General() {
}}
/>
</TableRowGroup>
<TableRowGroup title={Strings.INFO}>
<Summary noPadding label={Strings.VERSIONS} icon="ic_information_filled_24px">
{versions.map(v => <Version padding label={v.label} version={v.version} icon={v.icon} />)}
</Summary>
<Summary noPadding label={Strings.PLATFORM} icon="ic_mobile_device">
{platformInfo.map(p => <Version padding label={p.label} version={p.version} icon={p.icon} />)}
</Summary>
</TableRowGroup>
</Stack>
</ScrollView>
);

View file

@ -9,6 +9,7 @@ import { Button } from "@ui/components/discord";
export default function Themes() {
useProxy(settings);
useProxy(themes);
return (
<AddonPage<Theme>

View file

@ -128,7 +128,7 @@ export const createVendettaObject = (unloads: any[]): any => ({
themes: themes.themes,
fetchTheme: (id: string, selected?: boolean) => themes.fetchTheme(id, selected),
installTheme: (id: string) => themes.installTheme(id),
selectTheme: (id: string) => themes.selectTheme(id),
selectTheme: (id: string) => themes.selectTheme(id === "default" ? null : id),
removeTheme: (id: string) => themes.removeTheme(id),
getCurrentTheme: () => themes._getThemeFromLoader(),
updateThemes: () => themes.updateThemes()

View file

@ -177,11 +177,13 @@ export async function installTheme(id: string) {
await fetchTheme(id);
}
export async function selectTheme(id: string) {
export async function selectTheme(id: string | null) {
Object.values(themes).forEach(s => s.selected = s.id === id);
if (id === "default") {
if (id === null) {
return await writeTheme({});
} else {
themes[id].selected = true;
}
await writeTheme(themes[id]);
@ -189,7 +191,7 @@ export async function selectTheme(id: string) {
export async function removeTheme(id: string) {
const theme = themes[id];
if (theme.selected) await selectTheme("default");
if (theme.selected) await selectTheme(null);
delete themes[id];
return theme.selected;

View file

@ -1,6 +1,8 @@
import { findByProps } from "@lib/metro";
import { find, findByProps } from "@lib/metro";
import { RedesignModuleKeys } from "@lib/ui/types";
const findSingular = (prop: string) => find(m => m[prop] && Object.keys(m).length === 1)?.[prop];
export const Redesign = findByProps("TableRow") as Record<RedesignModuleKeys, any>;
export const CompatfulRedesign = findByProps("ActionSheetRow") as unknown as {
@ -8,6 +10,10 @@ export const CompatfulRedesign = findByProps("ActionSheetRow") as unknown as {
[key: string]: any;
};
export const FormSwitch = findSingular("FormSwitch");
export const FormRadio = findSingular("FormRadio");
export const FormCheckbox = findSingular("FormCheckbox");
export const {
AlertActionButton,
AlertModal,