diff --git a/src/hooks/persisted/usePlugins.ts b/src/hooks/persisted/usePlugins.ts index be5fbef05..c135ae044 100644 --- a/src/hooks/persisted/usePlugins.ts +++ b/src/hooks/persisted/usePlugins.ts @@ -108,6 +108,7 @@ export default function usePlugins() { const actualPlugin: PluginItem = { ...plugin, version: _plg.version, + hasSettings: !!_plg.pluginSettings, }; // safe if (!installedPlugins.some(plg => plg.id === plugin.id)) { diff --git a/src/plugins/types/index.ts b/src/plugins/types/index.ts index 69f500916..1f70cf613 100644 --- a/src/plugins/types/index.ts +++ b/src/plugins/types/index.ts @@ -56,6 +56,7 @@ export interface PluginItem { customJS?: string; customCSS?: string; hasUpdate?: boolean; + hasSettings?: boolean; } export interface ImageRequestInit { @@ -68,6 +69,7 @@ export interface ImageRequestInit { export interface Plugin extends PluginItem { imageRequestInit?: ImageRequestInit; filters?: Filters; + pluginSettings: any; popularNovels: ( pageNo: number, options?: PopularNovelsOptions, diff --git a/src/screens/browse/components/BrowseTabs.tsx b/src/screens/browse/components/BrowseTabs.tsx index d1fa74e16..25d990abb 100644 --- a/src/screens/browse/components/BrowseTabs.tsx +++ b/src/screens/browse/components/BrowseTabs.tsx @@ -28,6 +28,10 @@ import Animated, { useSharedValue, withTiming, } from 'react-native-reanimated'; +import { Portal } from 'react-native-paper'; +import SourceSettingsModal from './Modals/SourceSettings'; +import { useBoolean } from '@hooks'; +import { getPlugin } from '@plugins/pluginManager'; interface AvailableTabProps { searchText: string; @@ -50,6 +54,11 @@ export const InstalledTab = memo( updatePlugin, } = usePlugins(); const { showMyAnimeList, showAniList } = useBrowseSettings(); + const settingsModal = useBoolean(); + const [selectedPluginId, setSelectedPluginId] = useState(''); + + const pluginSettings = getPlugin(selectedPluginId)?.pluginSettings; + const navigateToSource = useCallback( (plugin: PluginItem, showLatestNovels?: boolean) => { navigation.navigate('SourceScreen', { @@ -159,6 +168,18 @@ export const InstalledTab = memo( + {item.hasSettings ? ( + { + setSelectedPluginId(item.id); + settingsModal.setTrue(); + }} + theme={theme} + /> + ) : null} {item.hasUpdate || __DEV__ ? ( {getString('browseScreen.installedPlugins')} + + + + } /> diff --git a/src/screens/browse/components/Modals/SourceSettings.tsx b/src/screens/browse/components/Modals/SourceSettings.tsx new file mode 100644 index 000000000..45efcbfc2 --- /dev/null +++ b/src/screens/browse/components/Modals/SourceSettings.tsx @@ -0,0 +1,169 @@ +import React, { useState, useEffect } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { Modal, overlay, TextInput } from 'react-native-paper'; +import { Button } from '@components/index'; +import { useTheme } from '@hooks/persisted'; +import { getString } from '@strings/translations'; +import { Storage } from '@plugins/helpers/storage'; + +interface PluginSetting { + value: string; + label: string; +} + +interface PluginSettings { + [key: string]: PluginSetting; +} + +interface SourceSettingsModal { + visible: boolean; + onDismiss: () => void; + title: string; + description?: string; + pluginId: string; + pluginSettings?: PluginSettings; +} + +const SourceSettingsModal: React.FC = ({ + onDismiss, + visible, + title, + description, + pluginId, + pluginSettings, +}) => { + const theme = useTheme(); + + const [formValues, setFormValues] = useState<{ [key: string]: string }>({}); + + useEffect(() => { + if (pluginSettings) { + const storage = new Storage(pluginId); + + const loadFormValues = async () => { + const loadedValues = await Promise.all( + Object.keys(pluginSettings).map(async key => { + const storedValue = await storage.get(key); + return { + key, + value: + storedValue !== null ? storedValue : pluginSettings[key].value, + }; + }), + ); + + const initialFormValues = Object.fromEntries( + loadedValues.map(({ key, value }) => [key, value]), + ); + + setFormValues(initialFormValues); + }; + + loadFormValues(); + } + }, [pluginSettings, pluginId]); + + const handleChange = (key: string, value: string) => { + setFormValues(prevValues => ({ + ...prevValues, + [key]: value, + })); + }; + + const handleSave = () => { + const storage = new Storage(pluginId); + Object.entries(formValues).forEach(([key, value]) => { + storage.set(key, value); + }); + onDismiss(); + }; + + if (!pluginSettings || Object.keys(pluginSettings).length === 0) { + return ( + + + {title} + + + {description || 'No settings available.'} + + + ); + } + + return ( + + + {title} + + {description} + + {Object.entries(pluginSettings).map(([key, setting]) => ( + handleChange(key, value)} + placeholder={`Enter ${setting.label}`} + placeholderTextColor={theme.onSurfaceDisabled} + underlineColor={theme.outline} + style={[{ color: theme.onSurface }, styles.textInput]} + theme={{ colors: { ...theme } }} + /> + ))} + + +