feat+refactor: Add empty list component and readd unproxied plugin warning

This commit is contained in:
amsyarasyiq 2024-08-28 22:42:39 +08:00
parent 7bd53f7985
commit 8dc0b826ca
6 changed files with 93 additions and 69 deletions

View file

@ -26,6 +26,7 @@
"CONFIRMATION_LINK_IS_A_TYPE": "This link is a **{urlType, select, plugin {Plugin} theme {Theme} other {Add-on}}**, would you like to install it?",
"CONNECT_TO_DEBUG_WEBSOCKET": "Connect to debug websocket",
"CONNECT_TO_REACT_DEVTOOLS": "Connect to React DevTools",
"CONTINUE": "Continue",
"COPIED_TO_CLIPBOARD": "Copied to clipboard",
"COPY_URL": "Copy URL",
"DEBUG": "Debug",

View file

@ -7,12 +7,12 @@ 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, IconButton } from "@metro/common/components";
import { Button, FlashList, FloatingActionButton, HelpMessage, IconButton, Text } 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";
import { ComponentType, ReactNode, useCallback, useMemo } from "react";
import { Image, View } from "react-native";
const { showSimpleActionSheet, hideActionSheet } = lazyDestructure(() => findByProps("showSimpleActionSheet"));
@ -21,15 +21,19 @@ type SearchKeywords = Array<string | ((obj: any & {}) => string)>;
interface AddonPageProps<T extends object, I = any> {
title: string;
items: I[];
searchKeywords: SearchKeywords;
sortOptions?: Record<string, (a: I, b: I) => number>;
fetchFunction?: (url: string) => Promise<void>;
resolveItem?: (value: I) => T | undefined;
safeModeHint?: {
message?: string;
footer?: ReactNode;
}
searchKeywords: SearchKeywords;
onFabPress?: () => void;
installAction?: {
label?: string;
// Ignored when onPress is defined!
fetchFn?: (url: string) => Promise<void>;
onPress?: () => void;
}
CardComponent: ComponentType<CardWrapper<T>>;
ListHeaderComponent?: ComponentType<any>;
ListFooterComponent?: ComponentType<any>;
@ -50,6 +54,42 @@ export default function AddonPage<T extends object>({ CardComponent, ...props }:
return fuzzysort.go(search, items, { keys: props.searchKeywords, all: true });
}, [props.items, sortFn, search]);
const onInstallPress = useCallback(() => {
if (!props.installAction) return () => {};
const { onPress, fetchFn } = props.installAction;
if (fetchFn) {
clipboard.getString().then((content: string) =>
showInputAlert({
title: props.installAction?.label,
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
placeholder: Strings.URL_PLACEHOLDER,
onConfirm: (input: string) => fetchFn(input),
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,
})
);
} else {
onPress?.();
}
}, []);
if (results.length === 0 && !search) {
return <View style={{ gap: 32, flexGrow: 1, justifyContent: "center", alignItems: "center" }}>
<View style={{ gap: 8, alignItems: "center" }}>
<Image source={findAssetId("empty_quick_switcher")} />
<Text variant="text-lg/semibold" color="text-normal">
Oops! Nothing to see here… yet!
</Text>
</View>
<Button
size="lg"
icon={findAssetId("DownloadIcon")}
text={props.installAction?.label ?? "Install"}
onPress={onInstallPress}
/>
</View>;
}
const headerElement = (
<View style={{ paddingBottom: 8 }}>
{props.ListHeaderComponent && <props.ListHeaderComponent />}
@ -88,25 +128,20 @@ export default function AddonPage<T extends object>({ CardComponent, ...props }:
extraData={search}
estimatedItemSize={136}
ListHeaderComponent={headerElement}
ListEmptyComponent={() => <View style={{ gap: 12, padding: 12, alignItems: "center" }}>
<Image source={findAssetId("devices_not_found")} />
<Text variant="text-lg/semibold" color="text-normal">
Hmmm... could not find that!
</Text>
</View>}
contentContainerStyle={{ padding: 8, paddingHorizontal: 12 }}
ItemSeparatorComponent={() => <View style={{ height: 8 }} />}
ListFooterComponent={props.ListFooterComponent}
renderItem={({ item }: any) => <CardComponent item={item.obj} result={item} />}
/>
{(props.fetchFunction ?? props.onFabPress) && <FloatingActionButton
{props.installAction && <FloatingActionButton
icon={findAssetId("PlusLargeIcon")}
onPress={props.onFabPress ?? (() => {
// from ./InstallButton.tsx
clipboard.getString().then((content: string) =>
showInputAlert({
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
placeholder: Strings.URL_PLACEHOLDER,
onConfirm: (input: string) => props.fetchFunction!(input),
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,
})
);
})}
onPress={onInstallPress}
/>}
</ErrorBoundary>
);

View file

@ -1,41 +0,0 @@
import { Strings } from "@core/i18n";
import { findAssetId } from "@lib/api/assets";
import { HTTP_REGEX_MULTI } from "@lib/utils/constants";
import { clipboard } from "@metro/common";
import { showInputAlert } from "@ui/alerts";
import { semanticColors } from "@ui/color";
import { createStyles } from "@ui/styles";
import { Image, TouchableOpacity } from "react-native";
const useStyles = createStyles({
icon: {
marginRight: 10,
tintColor: semanticColors.HEADER_PRIMARY,
},
});
interface InstallButtonProps {
alertTitle: string;
installFunction: (id: string) => Promise<void>;
}
export default function InstallButton({ alertTitle, installFunction: fetchFunction }: InstallButtonProps) {
const styles = useStyles();
return (
<TouchableOpacity onPress={() =>
clipboard.getString().then((content: string) =>
showInputAlert({
title: alertTitle,
initialValue: content.match(HTTP_REGEX_MULTI)?.[0] ?? "",
placeholder: Strings.URL_PLACEHOLDER,
onConfirm: (input: string) => fetchFunction(input),
confirmText: Strings.INSTALL,
cancelText: Strings.CANCEL,
})
)
}>
<Image style={styles.icon} source={findAssetId("ic_add_24px")} />
</TouchableOpacity>
);
}

View file

@ -3,7 +3,7 @@ import AddonPage from "@core/ui/components/AddonPage";
import FontEditor from "@core/ui/settings/pages/Fonts/FontEditor";
import { settings } from "@lib/api/settings";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, installFont } from "@lib/fonts";
import { FontDefinition, fonts } from "@lib/fonts";
import { NavigationNative } from "@metro/common";
import FontCard from "./FontCard";
@ -22,15 +22,17 @@ export default function Fonts() {
"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)}
safeModeHint={{ message: Strings.SAFE_MODE_NOTICE_FONTS }}
CardComponent={FontCard}
onFabPress={() => {
navigation.push("BUNNY_CUSTOM_PAGE", {
title: "Import Font",
render: () => <FontEditor />
});
installAction={{
label: "Install a font",
onPress: () => {
navigation.push("BUNNY_CUSTOM_PAGE", {
title: "Import Font",
render: () => <FontEditor />
});
}
}}
/>
);

View file

@ -2,8 +2,12 @@ import { Strings } from "@core/i18n";
import AddonPage from "@core/ui/components/AddonPage";
import PluginCard from "@core/ui/settings/pages/Plugins/components/PluginCard";
import { VdPluginManager } from "@core/vendetta/plugins";
import { findAssetId } from "@lib/api/assets";
import { settings } from "@lib/api/settings";
import { useProxy } from "@lib/api/storage";
import { showConfirmationAlert } from "@lib/ui/alerts";
import { showToast } from "@lib/ui/toasts";
import { BUNNY_PROXY_PREFIX, VD_PROXY_PREFIX } from "@lib/utils/constants";
import { Author } from "@lib/utils/types";
import { ComponentProps } from "react";
@ -64,7 +68,27 @@ export default function Plugins() {
return <PluginPage
useItems={() => useProxy(VdPluginManager.plugins) && Object.values(VdPluginManager.plugins)}
resolveItem={unifyVdPlugin}
fetchFunction={(url: string) => VdPluginManager.installPlugin(url)}
installAction={{
label: "Install a plugin",
fetchFn: async (url: string) => {
const install = () =>
VdPluginManager.installPlugin(url)
.then(() => showToast(Strings.TOASTS_INSTALLED_PLUGIN, findAssetId("Check")))
.catch(x => showToast(x?.message ?? `${x}`, findAssetId("Small")));
if (!url.startsWith(VD_PROXY_PREFIX) && !url.startsWith(BUNNY_PROXY_PREFIX) && !settings.developerSettings)
setImmediate(() => showConfirmationAlert({
title: Strings.MODAL_UNPROXIED_PLUGIN_HEADER,
content: Strings.MODAL_UNPROXIED_PLUGIN_DESC,
confirmText: Strings.CONTINUE,
onConfirm: install,
cancelText: Strings.CANCEL
}));
else {
return await install();
}
}
}}
/>;
// const navigation = NavigationNative.useNavigation();

View file

@ -23,7 +23,10 @@ export default function Themes() {
"Name (A-Z)": (a, b) => a.name.localeCompare(b.name),
"Name (Z-A)": (a, b) => b.name.localeCompare(a.name)
}}
fetchFunction={installTheme}
installAction={{
label: "Install a theme",
fetchFn: installTheme
}}
items={Object.values(themes)}
safeModeHint={{
message: formatString("SAFE_MODE_NOTICE_THEMES", { enabled: Boolean(settings.safeMode?.currentThemeId) }),