feat(AddonPage): Add sort options

This commit is contained in:
amsyarasyiq 2024-08-28 20:47:35 +08:00
parent f542e8ce47
commit 7bd53f7985
5 changed files with 70 additions and 25 deletions

View file

@ -4,51 +4,80 @@ import { findAssetId } from "@lib/api/assets";
import { settings } from "@lib/api/settings";
import { useProxy } from "@lib/api/storage";
import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { lazyDestructure } from "@lib/utils/lazy";
import { findByProps } from "@metro";
import { clipboard } from "@metro/common";
import { FlashList, FloatingActionButton, HelpMessage } from "@metro/common/components";
import { FlashList, FloatingActionButton, HelpMessage, IconButton } from "@metro/common/components";
import { showInputAlert } from "@ui/alerts";
import { ErrorBoundary, Search } from "@ui/components";
import fuzzysort from "fuzzysort";
import { ComponentType, ReactNode, useMemo } from "react";
import { View } from "react-native";
const { showSimpleActionSheet, hideActionSheet } = lazyDestructure(() => findByProps("showSimpleActionSheet"));
type SearchKeywords = Array<string | ((obj: any & {}) => string)>;
interface AddonPageProps<T extends object> {
interface AddonPageProps<T extends object, I = any> {
title: string;
items: I[];
sortOptions?: Record<string, (a: I, b: I) => number>;
fetchFunction?: (url: string) => Promise<void>;
items: any[];
resolveItem?: (value: any) => T | undefined;
safeModeMessage: string;
safeModeExtras?: ReactNode;
card: ComponentType<CardWrapper<T>>;
resolveItem?: (value: I) => T | undefined;
safeModeHint?: {
message?: string;
footer?: ReactNode;
}
searchKeywords: SearchKeywords;
onFabPress?: () => void;
CardComponent: ComponentType<CardWrapper<T>>;
ListHeaderComponent?: ComponentType<any>;
ListFooterComponent?: ComponentType<any>;
}
export default function AddonPage<T extends object>({ card: CardComponent, ...props }: AddonPageProps<T>) {
export default function AddonPage<T extends object>({ CardComponent, ...props }: AddonPageProps<T>) {
useProxy(settings);
const [search, setSearch] = React.useState("");
const [sortFn, setSortFn] = React.useState<((a: unknown, b: unknown) => number) | null>(() => null);
const results = useMemo(() => {
let values = props.items;
if (props.resolveItem) values = values.map(props.resolveItem);
const items = values.filter(i => i && typeof i === "object");
if (!search && sortFn) items.sort(sortFn);
return fuzzysort.go(search, items, { keys: props.searchKeywords, all: true });
}, [props.items, search]);
}, [props.items, sortFn, search]);
const headerElement = (
<View style={{ paddingBottom: 8 }}>
{props.ListHeaderComponent && <props.ListHeaderComponent />}
{settings.safeMode?.enabled && <View style={{ marginBottom: 10 }}>
<HelpMessage messageType={0}>{props.safeModeMessage}</HelpMessage>
{props.safeModeExtras}
<HelpMessage messageType={0}>
{props.safeModeHint?.message}
</HelpMessage>
{props.safeModeHint?.footer}
</View>}
<Search onChangeText={v => setSearch(v)} />
<View style={{ flexDirection: "row", gap: 8 }}>
<Search style={{ flexGrow: 1 }} isRound={!!props.sortOptions} onChangeText={v => setSearch(v)} />
{props.sortOptions && <IconButton
icon={findAssetId("ic_forum_channel_sort_order_24px")}
variant="secondary"
disabled={!!search}
onPress={() => showSimpleActionSheet({
key: "AddonListSortOptions",
header: {
title: "Sort Options",
onClose: () => hideActionSheet("AddonListSortOptions"),
},
options: Object.entries(props.sortOptions!).map(([name, fn]) => ({
label: name,
onPress: () => setSortFn(() => fn)
}))
})}
/>}
</View>
</View>
);

View file

@ -18,10 +18,14 @@ export default function Fonts() {
<AddonPage<FontDefinition>
title={Strings.FONTS}
searchKeywords={["name", "description"]}
sortOptions={{
"Name (A-Z)": (a, b) => a.name.localeCompare(b.name),
"Name (Z-A)": (a, b) => b.name.localeCompare(a.name)
}}
fetchFunction={installFont}
items={Object.values(fonts)}
safeModeMessage={Strings.SAFE_MODE_NOTICE_FONTS}
card={FontCard}
safeModeHint={{ message: Strings.SAFE_MODE_NOTICE_FONTS }}
CardComponent={FontCard}
onFabPress={() => {
navigation.push("BUNNY_CUSTOM_PAGE", {
title: "Import Font",

View file

@ -39,7 +39,7 @@ function PluginPage(props: PluginPageProps) {
const items = props.useItems();
return <AddonPage<UnifiedPluginModel>
card={PluginCard}
CardComponent={PluginCard}
title={Strings.PLUGINS}
searchKeywords={[
"name",
@ -48,7 +48,11 @@ function PluginPage(props: PluginPageProps) {
(a: Author | string) => typeof a === "string" ? a : a.name
).join()
]}
safeModeMessage={Strings.SAFE_MODE_NOTICE_PLUGINS}
sortOptions={{
"Name (A-Z)": (a, b) => a.name.localeCompare(b.name),
"Name (Z-A)": (a, b) => b.name.localeCompare(a.name)
}}
safeModeHint={{ message: Strings.SAFE_MODE_NOTICE_PLUGINS }}
items={items}
{...props}
/>;

View file

@ -19,16 +19,22 @@ export default function Themes() {
"data.description",
p => p.data.authors?.map((a: Author) => a.name).join(", ")
]}
sortOptions={{
"Name (A-Z)": (a, b) => a.name.localeCompare(b.name),
"Name (Z-A)": (a, b) => b.name.localeCompare(a.name)
}}
fetchFunction={installTheme}
items={Object.values(themes)}
safeModeMessage={formatString("SAFE_MODE_NOTICE_THEMES", { enabled: Boolean(settings.safeMode?.currentThemeId) })}
safeModeExtras={settings.safeMode?.currentThemeId ? <Button
text={Strings.DISABLE_THEME}
size="small"
onPress={() => delete settings.safeMode?.currentThemeId}
style={{ marginTop: 8 }}
/> : undefined}
card={ThemeCard}
safeModeHint={{
message: formatString("SAFE_MODE_NOTICE_THEMES", { enabled: Boolean(settings.safeMode?.currentThemeId) }),
footer: settings.safeMode?.currentThemeId && <Button
size="small"
text={Strings.DISABLE_THEME}
onPress={() => delete settings.safeMode?.currentThemeId}
style={{ marginTop: 8 }}
/>
}}
CardComponent={ThemeCard}
/>
);
}

View file

@ -8,13 +8,14 @@ export interface SearchProps {
onChangeText?: (v: string) => void;
placeholder?: string;
style?: ViewStyle;
isRound?: boolean;
}
function SearchIcon() {
return <Image style={{ width: 16, height: 16 }} source={findAssetId("search")} />;
}
export default ({ onChangeText, placeholder, style }: SearchProps) => {
export default ({ onChangeText, placeholder, style, isRound }: SearchProps) => {
const [query, setQuery] = React.useState("");
const onChange = (value: string) => {
@ -34,6 +35,7 @@ export default ({ onChangeText, placeholder, style }: SearchProps) => {
size="md"
autoCapitalize="none"
autoCorrect={false}
isRound={isRound}
value={query}
/>
</View>