Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
- Better format logs
- Remove form utility and use as many built-ins as possible
- Create generic settings page component
- Empty states for not found and empty addon pages
- Make plus button function in addon pages

# Stores
- Move settings sections to a store instead of using the Dispatcher

# Managers
Store install URL

# Built-Ins
- Show toasts for manager events

# API
- Switch patcher to https://github.com/marioparaschiv/possess
- Render custom badges for users without any badges.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion src/api/metro/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Moment = findByProps('isMoment') as MomentModule;

// Modules
export const Util = findByProps('inspect', { lazy: true });
export const Flux = findByProps('Store', 'connectStores', { lazy: true }) as FluxModule;
export const Flux = findByProps('Store', 'connectStores', { lazy: true, interop: false }) as FluxModule;
export const Assets = findByProps('registerAsset', { lazy: true }) as AssetsModule;
export const Clyde = findByProps('createBotMessage', { lazy: true }) as ClydeModule;
export const Dispatcher = findByProps('dispatch', 'subscribe', { lazy: true }) as DispatcherModule;
Expand Down
2 changes: 2 additions & 0 deletions src/api/metro/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { findByName, findByProps } from '@api/metro';


export const Discord = findByProps('createStyles', 'dismissAlerts', 'ContextMenu', { lazy: true }) as ComponentsModule;
export const Avatar = findByProps('default', 'AvatarSizes', { lazy: true });
export const BackdropFilters = findByProps('BackgroundBlurFill', { lazy: true });
export const Portal = findByProps('PortalHost', 'Portal', { lazy: true });
export const Media = findByProps('openMediaModal', { lazy: true });
export const FlashList = findByProps('FlashList', { lazy: true }) as FlashListModule;
export const Sheets = findByProps('hideActionSheet');
export const HelpMessage = findByName('HelpMessage');
export const Forms = findByProps('FormSliderRow');
7 changes: 4 additions & 3 deletions src/api/metro/stores.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Store } from '@typings/discord/flux';
import { findStore } from '@api/metro';


export const Theme = findStore('Theme', { lazy: true });
export const Users = findStore('User', { lazy: true });
export const Guilds = findStore('Guild', { lazy: true });
export const Theme = findStore('Theme', { lazy: true }) as Store;
export const Users = findStore('User', { lazy: true }) as Store;
export const Guilds = findStore('Guild', { lazy: true }) as Store;
27 changes: 27 additions & 0 deletions src/api/sheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Sheets } from '@api/metro/components';
import { uuid } from '@utilities';


interface SheetOptions<T extends React.ComponentType> {
component: T | Promise<{ default: T; }>;
key?: string;
props?: React.ComponentProps<T>;
}

export function showSheet<T extends React.ComponentType>(options: T | SheetOptions<T>) {
if (typeof options !== 'object') {
options = { component: options };
}

options.key ??= uuid();

if (!(options.component instanceof Promise)) {
options.component = Promise.resolve({ default: options.component });
}

Sheets.openLazy(options.component, options.key, options.props ?? {});
}

export function closeSheet(key: string) {
Sheets.hideActionSheet(key);
}
20 changes: 10 additions & 10 deletions src/built-ins/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { RegisterSettingsEntriesPayload, SettingsEntry } from '@typings/api/settings';
import { CLIENT_NAME, DispatchTypes, Screens } from '@constants';
import MarketplacePage from '@ui/settings/marketplace';
import type { BuiltInData } from '@typings/built-ins';
import MarketplacePage from '@ui/screens/marketplace';
import { findByName, findByProps } from '@api/metro';
import DeveloperPage from '@ui/settings/developer';
import DeveloperPage from '@ui/screens/developer';
import { createLogger } from '@structures/logger';
import { Discord } from '@api/metro/components';
import PluginsPage from '@ui/settings/plugins';
import GeneralPage from '@ui/settings/general';
import EventEmitter from '@structures/emitter';
import CustomScreen from '@ui/settings/custom';
import { Dispatcher } from '@api/metro/common';
import ToastsPage from '@ui/settings/toasts';
import DesignPage from '@ui/settings/design';
import AssetsPage from '@ui/settings/assets';
import PluginsPage from '@ui/screens/plugins';
import GeneralPage from '@ui/screens/general';
import CustomScreen from '@ui/screens/custom';
import { createPatcher } from '@api/patcher';
import ToastsPage from '@ui/screens/toasts';
import DesignPage from '@ui/screens/design';
import AssetsPage from '@ui/screens/assets';
import { useEffect, useState } from 'react';
import LogsPage from '@ui/settings/logs';
import LogsPage from '@ui/screens/logs';
import { Strings } from '@api/i18n';
import { Icons } from '@api/assets';

Expand Down Expand Up @@ -87,7 +87,7 @@ export const data: BuiltInData & {
key: Screens.Marketplace,
parent: null,
section: CLIENT_NAME,
IconComponent: () => <Discord.TableRowIcon source={Icons['img_collectibles_shop']} />,
IconComponent: () => <Discord.TableRowIcon source={Icons['ShopSparkleIcon']} />,
screen: {
route: Screens.Marketplace,
getComponent: () => MarketplacePage
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export const ManagerEntity = {
[ManagerKind.ICONS]: 'icon-pack',
};

export const ManagerEntityNames = {
[ManagerKind.PLUGINS]: 'Plugin',
[ManagerKind.THEMES]: 'Theme',
[ManagerKind.ICONS]: 'Icon Pack',
};

export const DISCORD_INVITE = 'unboundapp' as const;

export const DefaultSources = [
Expand Down
15 changes: 13 additions & 2 deletions src/ui/addons/addon-card.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,26 @@ import { Discord } from '@api/metro/components';

export default Discord.createStyles({
container: {
margin: 20
// margin: 20
},
card: {
flexDirection: 'column'
flexDirection: 'column',
gap: 6,
},
header: {
flexDirection: 'row',
gap: 4,
justifyContent: 'space-between',
alignItems: 'center'
},
actions: {
flexDirection: 'row',
gap: 8,
alignItems: 'center'
},
authors: {
flexDirection: 'row',
gap: 4,
alignItems: 'center',
}
});
149 changes: 140 additions & 9 deletions src/ui/addons/addon-card.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import AddonAuthorsSheet, { type AddonAuthorsSheetAuthor } from '@ui/sheets/addon-authors-sheet';
import { ManagerEntityNames, ManagerNames, Screens } from '@constants';
import type { AddonCardProps } from '@typings/ui/addons/addon-card';
import { Avatar, Discord } from '@api/metro/components';
import { TouchableOpacity, View } from 'react-native';
import { useSettingsStore } from '@api/storage';
import { Discord } from '@api/metro/components';
import { ManagerNames } from '@constants';
import { Clipboard } from '@api/metro/common';
import useUsers from '@ui/hooks/use-users';
import { showDialog } from '@api/dialogs';
import { Switch } from '@ui/misc/forms';
import { showToast } from '@api/toasts';
import { showSheet } from '@api/sheet';
import * as Managers from '@managers';
import { View } from 'react-native';
import { Icons } from '@api/assets';
import { useMemo } from 'react';

import useStyles from './addon-card.style';
Expand All @@ -15,23 +22,147 @@ function AddonCard(props: AddonCardProps) {

const manager = useMemo<Values<typeof Managers>>(() => Managers[ManagerNames[kind]], [kind]);
const settings = useSettingsStore('unbound', ({ key }) => key === 'recovery');
const authors = useUsers(addon.data.authors.map((author) => author.id));
const navigation = Discord.useNavigation();
const styles = useStyles();

if (!manager) return null;

const addonAuthors = useMemo(() => {
return addon.data.authors.reduce<AddonAuthorsSheetAuthor[]>((prev, curr: AddonAuthorsSheetAuthor) => {
const user = authors.find((author) => author.id === curr.id);

if (user) {
curr.user = user;
} else {
curr.user = null;
}

return [...prev, curr];
}, []);
}, [addon, authors]);

return <View style={styles.container}>
<Discord.Card style={styles.card}>
<View style={styles.header}>
<Discord.Text color='text-normal' variant='text-lg/bold'>
{addon.data.name}
</Discord.Text>
<Switch.FormSwitch
disabled={addon.failed || settings.get('recovery', false)}
value={manager.isEnabled(addon.id)}
onValueChange={() => manager.toggle(addon.id)}
/>
<View style={styles.actions}>
<Discord.ContextMenu
title={`${ManagerEntityNames[kind]} Options`}
align='left'
items={[
{
label: 'Reload',
iconSource: Icons['RefreshIcon'],
action: () => {
// manager.(addon.id);
}
},
{
label: 'Re-fetch',
iconSource: Icons['DownloadIcon'],
action: () => {
// manager.(addon.id);
}
},
{
label: 'Copy URL',
iconSource: Icons['CopyIcon'],
action: () => {
Clipboard.setString(addon.data.url);
showToast({
title: 'URL Copied',
duration: 2500,
content: `The URL for ${addon.data.name} has been copied to your clipboard.`,
icon: Icons['CopyIcon']
});
}
},
// {
// label: 'View in Marketplace',
// iconSource: Icons['ShopSparkleIcon'],
// action: () => {
// navigation.push(Screens.Marketplace, {
// addonId: addon.id
// });
// }
// },
{
label: 'Uninstall',
variant: 'destructive',
iconSource: Icons['TrashIcon'],
action: () => {
showDialog({
title: `Uninstall ${ManagerEntityNames[kind]}`,
content: `Are you sure you want to uninstall ${addon.data.name}?`,
buttons: [
{
text: 'Uninstall',
variant: 'destructive',
onPress: () => manager.delete(addon.id)
}
]
});
}
},
]}
>
{(props) => <Discord.IconButton
{...props}
icon={Icons['MoreHorizontalIcon']}
variant='secondary'
size='sm'
/>}
</Discord.ContextMenu>
{typeof addon.instance.getSettingsPanel === 'function' && <Discord.IconButton
icon={Icons['SettingsIcon']}
variant='secondary'
size='sm'
onPress={() => {
navigation.push(Screens.Custom, {
title: `${addon.data.name} - Settings`,
render: addon.instance.getSettingsPanel
});
}}
/>}
<Switch.FormSwitch
disabled={addon.failed || settings.get('recovery', false)}
value={manager.isEnabled(addon.id)}
onValueChange={() => manager.toggle(addon.id)}
/>
</View>
</View>
<Discord.Text color='text-muted' variant='text-md/normal'>
<TouchableOpacity
style={styles.authors}
onPress={() => showSheet({
component: AddonAuthorsSheet,
props: {
authors: addonAuthors
}
})}
>
<Discord.AvatarPile
size='xsmall'
names={addonAuthors.slice(0, 3).map((author) => author.user?.globalName ?? author.user?.username ?? author.name ?? 'Unknown')}
totalCount={addonAuthors.length}
>
{addonAuthors.filter(author => author.user).map(({ user }) => <Avatar.default
key={user.id}
size={Avatar.AvatarSizes.XSMALL}
user={user}
/>)}
</Discord.AvatarPile>
<Discord.Text color='text-muted' variant='text-sm/semibold'>
{Discord.AvatarPile({
size: 'xsmall',
names: addonAuthors.slice(0, 3).map((author) => author.user?.globalName ?? author.user?.username ?? author.name ?? 'Unknown'),
totalCount: addonAuthors.length
}).props['aria-label']}
</Discord.Text>
</TouchableOpacity>
<Discord.Text color='text-muted-on-default' variant='text-md/normal' numberOfLines={2} ellipsizeMode='tail'>
{addon.data.description}
</Discord.Text>
</Discord.Card>
Expand Down
12 changes: 9 additions & 3 deletions src/ui/addons/addon-list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AddonListProps } from '@typings/ui/addons/addon-list';
import { FlashList } from '@api/metro/components';
import { Discord, FlashList } from '@api/metro/components';
import AddonCard from '@ui/addons/addon-card';
import Empty from '@ui/misc/empty-state';
import { View } from 'react-native';

import useStyles from './addon-list.style';
Expand All @@ -10,13 +11,18 @@ function AddonList(props: AddonListProps) {
const styles = useStyles();

return <View style={styles.container}>
<FlashList.FlashList
{props.addons.length !== 0 && <FlashList.FlashList
data={props.addons}
renderItem={({ item }) => <AddonCard
kind={props.kind}
addon={item}
/>}
/>
/>}
{props.addons.length === 0 && <Empty>
<Discord.Text>
Damn.
</Discord.Text>
</Empty>}
</View>;
}

Expand Down
4 changes: 2 additions & 2 deletions src/ui/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ const Outline = ({ state, error }: any) => {
}}>
<Discord.IconButton
icon={getIDByName('ic_message_copy')}
variant={'primary'}
size={'md'}
variant='secondary'
size='md'
loading={loading}
onPress={() => {
clearTimeout(loadingTimeout);
Expand Down
Loading