diff --git a/src/core/i18n/default.json b/src/core/i18n/default.json index 156c86b..7c4b078 100644 --- a/src/core/i18n/default.json +++ b/src/core/i18n/default.json @@ -31,24 +31,31 @@ "DEBUG": "Debug", "DEBUGGER_URL": "Debugger URL", "DELETE": "Delete", + "DESC_EXTRACT_FONTS_FROM_THEME": "Looks out for \"fonts\" field in your currently applied theme and install it.", "DEVELOPER": "Developer", "DEVELOPER_SETTINGS": "Developer Settings", "DISABLE_THEME": "Disable Theme", "DISABLE_UPDATES": "Disable updates", "DISCORD_SERVER": "Discord Server", + "DONE": "Done", "ENABLE_EVAL_COMMAND": "Enable /eval command", "ENABLE_EVAL_COMMAND_DESC": "Evaluate JavaScript directly from command. Be cautious when using this command as it may pose a security risk. Make sure to know what you are doing.", "ENABLE_UPDATES": "Enable updates", "ERROR_BOUNDARY_TOOLS_LABEL": "ErrorBoundary Tools", + "EXTRACT": "Extract", + "FONT_NAME": "Font Name", + "FONTS": "Fonts", "GENERAL": "General", "GITHUB": "GitHub", "HOLD_UP": "Hold Up", "INFO": "Info", "INSTALL": "Install", - "INSTALL_ADDON": "Install Add-on", - "INSTALL_PLUGIN": "Install Plugin", + "INSTALL_ADDON": "Install an add-on", + "INSTALL_FONT": "Install a font", + "INSTALL_PLUGIN": "Install a plugin", "INSTALL_REACT_DEVTOOLS": "Install React DevTools", "INSTALL_THEME": "Install Theme", + "LABEL_EXTRACT_FONTS_FROM_THEME": "Extract font from theme", "LINKS": "Links", "LOAD_FROM_CUSTOM_URL": "Load from custom URL", "LOAD_FROM_CUSTOM_URL_DEC": "Load Bunny from a custom endpoint.", @@ -77,17 +84,21 @@ "RELOAD_IN_NORMAL_MODE_DESC": "This will reload Discord normally", "RELOAD_IN_SAFE_MODE": "Reload in Safe Mode", "RELOAD_IN_SAFE_MODE_DESC": "This will reload Discord without loading addons", - "RESTART_REQUIRED_TO_TAKE_EFFECT": "Restart required to take effect", + "REMOVE": "Remove", + "RESTART_REQUIRED_TO_TAKE_EFFECT": "Restart is required to take effect", "RETRY": "Retry", "RETRY_RENDER": "Retry Render", "SAFE_MODE": "Safe Mode", + "SAFE_MODE_NOTICE_FONTS": "You are in Safe Mode, meaning fonts have been temporarily disabled. {enabled, select, true {If a font appears to be causing the issue, you can press below to disable it persistently.} other {}}", "SAFE_MODE_NOTICE_PLUGINS": "You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page.", "SAFE_MODE_NOTICE_THEMES": "You are in Safe Mode, meaning themes have been temporarily disabled. {enabled, select, true {If a theme appears to be causing the issue, you can press below to disable it persistently.} other {}}", "SEARCH": "Search", + "SEPARATOR": ", ", "SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS": "Activate Discord Experiments", "SETTINGS_ACTIVATE_DISCORD_EXPERIMENTS_DESC": "Warning: Messing with this feature may lead to account termination. We are not responsible for what you do with this feature.", "STACK_TRACE": "Stack Trace", "SUCCESSFULLY_INSTALLED": "Successfully installed", + "THEME_EXTRACTOR_DESC": "This pack overrides the following: {fonts}", "THEME_REFETCH_FAILED": "Failed to refetch theme!", "THEME_REFETCH_SUCCESSFUL": "Successfully refetched theme.", "THEMES": "Themes", diff --git a/src/core/ui/components/AddonPage.tsx b/src/core/ui/components/AddonPage.tsx index cd95c6e..fa1ed89 100644 --- a/src/core/ui/components/AddonPage.tsx +++ b/src/core/ui/components/AddonPage.tsx @@ -10,24 +10,30 @@ import { clipboard } from "@metro/common"; import { showInputAlert } from "@ui/alerts"; import { ErrorBoundary, Search } from "@ui/components"; import fuzzysort from "fuzzysort"; +import { createContext } from "react"; import { FlatList, View } from "react-native"; +export const RemoveModeContext = createContext(false); + interface AddonPageProps { title: string; floatingButtonText: string; fetchFunction: (url: string) => Promise; - items: Record; + items: Record; safeModeMessage: string; safeModeExtras?: JSX.Element | JSX.Element[]; card: React.ComponentType>; + isRemoveMode?: boolean; + headerComponent?: JSX.Element; } -function getItemsByQuery(items: T[], query: string): T[] { +function getItemsByQuery["items"][string]>(items: T[], query: string): T[] { if (!query) return items; return fuzzysort.go(query, items, { keys: [ "id", + "name", "manifest.name", "manifest.description", "manifest.authors.0.name", @@ -39,9 +45,9 @@ function getItemsByQuery(items: T[], query: string): const reanimated = findByProps("useSharedValue"); const { FloatingActionButton } = findByProps("FloatingActionButton"); -export default function AddonPage({ floatingButtonText, fetchFunction, items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps) { - useProxy(settings); +export default function AddonPage({ floatingButtonText, fetchFunction, items, safeModeMessage, safeModeExtras, card: CardComponent, isRemoveMode, headerComponent }: AddonPageProps) { useProxy(items); + useProxy(settings); const collapseText = reanimated.useSharedValue(0); const yOffset = React.useRef(0); @@ -61,6 +67,7 @@ export default function AddonPage({ floatingButtonText, fetchFunction, items, onChangeText={(v: string) => setSearch(v.toLowerCase())} placeholder={Strings.SEARCH} /> + {headerComponent} } onScroll={e => { if (e.nativeEvent.contentOffset.y <= 0) return; @@ -69,8 +76,10 @@ export default function AddonPage({ floatingButtonText, fetchFunction, items, }} style={{ paddingHorizontal: 10, paddingTop: 10 }} contentContainerStyle={{ paddingBottom: 90, paddingHorizontal: 5 }} - data={getItemsByQuery(Object.values(items), search)} - renderItem={({ item, index }) => } + data={getItemsByQuery(Object.values(items).filter(i => typeof i === "object"), search)} + renderItem={({ item, index }) => + + } /> ) { + useProxy(fonts); + + const removeMode = useContext(RemoveModeContext); + const selected = fonts.__selected === font.name; + + return + + { + selectFont(selected ? null : font.name).then(() => { + showToast(Strings.RESTART_REQUIRED_TO_TAKE_EFFECT, getAssetIDByName("WarningIcon")); + }); + }}> + {!removeMode ? : removeFont(font.name)} + />} + + } + /> + + ; +} diff --git a/src/core/ui/settings/index.ts b/src/core/ui/settings/index.ts index 918d83b..5503a65 100644 --- a/src/core/ui/settings/index.ts +++ b/src/core/ui/settings/index.ts @@ -1,6 +1,6 @@ import { Strings } from "@core/i18n"; import { getAssetIDByName } from "@lib/api/assets"; -import { isThemeSupported } from "@lib/api/native/loader"; +import { isFontSupported, isThemeSupported } from "@lib/api/native/loader"; import { useProxy } from "@lib/api/storage"; import { settings } from "@lib/settings"; import { registerSection } from "@lib/ui/settings"; @@ -38,6 +38,13 @@ export default function initSettings() { render: () => import("@core/ui/settings/pages/Themes"), usePredicate: () => isThemeSupported() }, + { + key: "BUNNY_FONTS", + title: () => Strings.FONTS, + icon: getAssetIDByName("ic_add_text"), + render: () => import("@core/ui/settings/pages/Fonts"), + usePredicate: () => isFontSupported() + }, { key: "BUNNY_DEVELOPER", title: () => Strings.DEVELOPER, diff --git a/src/core/ui/settings/pages/Fonts.tsx b/src/core/ui/settings/pages/Fonts.tsx new file mode 100644 index 0000000..d9f2947 --- /dev/null +++ b/src/core/ui/settings/pages/Fonts.tsx @@ -0,0 +1,138 @@ +import { formatString, Strings } from "@core/i18n"; +import AddonPage from "@core/ui/components/AddonPage"; +import FontCard from "@core/ui/components/FontCard"; +import { getAssetIDByName } from "@lib/api/assets"; +import { useProxy } from "@lib/api/storage"; +import { FontDefinition, fonts, installFont, saveFont } from "@lib/managers/fonts"; +import { getCurrentTheme } from "@lib/managers/themes"; +import { findByProps } from "@lib/metro/filters"; +import { settings } from "@lib/settings"; +import { ErrorBoundary } from "@lib/ui/components"; +import { FormText } from "@lib/ui/components/discord/Forms"; +import { ActionSheet, BottomSheetTitleHeader, Button, RowButton, TableRow, Text, TextInput, useNavigation } from "@lib/ui/components/discord/Redesign"; +import { useEffect, useState } from "react"; +import { TouchableOpacity, View } from "react-native"; + +const actionSheet = findByProps("hideActionSheet"); + +function guessFontName(urls: string[]) { + const fileNames = urls.map(url => { + const { pathname } = new URL(url); + const fileName = pathname.replace(/\.[^/.]+$/, ""); + return fileName.split("/").pop(); + }).filter(Boolean) as string[]; + + const shortest = fileNames.reduce((shortest, name) => { + return name.length < shortest.length ? name : shortest; + }, fileNames[0] || ""); + + return shortest?.replace(/-[A-Za-z]*$/, "") || null; +} + +function ExtractFontsComponent({ fonts }: { fonts: Record; }) { + const [fontName, setFontName] = useState(guessFontName(Object.values(fonts))); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(undefined); + + return + + + {formatString("THEME_EXTRACTOR_DESC", { + fonts: Object.keys(fonts).join(Strings.SEPARATOR) + })} + +