From 9c682605921ec960099df603f1495e87e7c96e3c Mon Sep 17 00:00:00 2001 From: nguyd1 <91645046+nguyd1@users.noreply.github.com> Date: Fri, 4 Oct 2024 04:54:37 -0700 Subject: [PATCH 01/29] fix: Resolve 'Can't Parse Page' SQL Syntax Error (#1266) fix cant parse page sql syntax error --- src/database/queries/ChapterQueries.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/queries/ChapterQueries.ts b/src/database/queries/ChapterQueries.ts index 7c10a7b1e..f40cbe436 100644 --- a/src/database/queries/ChapterQueries.ts +++ b/src/database/queries/ChapterQueries.ts @@ -42,7 +42,7 @@ export const insertChapters = async ( ` UPDATE Chapter SET page = ?, position = ? - WHERE path = ? AND novelId = ? (AND page != ? OR position != ?) + WHERE path = ? AND novelId = ? AND (page != ? OR position != ?) `, [ chapter.page || '1', From 8245e776721536abf50932dcf48be589dff6576a Mon Sep 17 00:00:00 2001 From: Adam Kemish <49228220+Soopyboo32@users.noreply.github.com> Date: Sat, 12 Oct 2024 18:09:49 +0800 Subject: [PATCH 02/29] feat: Add Switch and TextInput Support to Plugin Filters (#1271) * Add FilterTypes.Switch support to plugin filters * Add FilterTypes.TextInput support to plugin filters --- .../components/FilterBottomSheet.tsx | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx b/src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx index 230ece16c..8aeb08699 100644 --- a/src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx +++ b/src/screens/BrowseSourceScreen/components/FilterBottomSheet.tsx @@ -29,6 +29,7 @@ import { getValueFor } from './filterUtils'; import { getString } from '@strings/translations'; import { ThemeColors } from '@theme/types'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import Switch from '@components/Switch/Switch'; const insertOrRemoveIntoArray = (array: string[], val: string): string[] => array.indexOf(val) > -1 ? array.filter(ele => ele !== val) : [...array, val]; @@ -56,6 +57,43 @@ const FilterItem: React.FC = ({ setFalse: closeCard, } = useBoolean(); const { width: screenWidth } = useWindowDimensions(); + if (filter.type === FilterTypes.TextInput) { + const value = getValueFor<(typeof filter)['type']>( + filter, + selectedFilters[filterKey], + ); + return ( + + + {` ${filter.label} `} + + } + defaultValue={value} + theme={{ colors: { background: 'transparent' } }} + outlineColor={theme.onSurface} + textColor={theme.onSurface} + onChangeText={text => + setSelectedFilters(prevState => ({ + ...prevState, + [filterKey]: { value: text, type: FilterTypes.TextInput }, + })) + } + /> + + ); + } if (filter.type === FilterTypes.Picker) { const value = getValueFor<(typeof filter)['type']>( filter, @@ -83,7 +121,7 @@ const FilterItem: React.FC = ({ styles.label, { color: isVisible ? theme.primary : theme.onSurface, - backgroundColor: theme.surface, + backgroundColor: overlay(2, theme.surface), }, ]} > @@ -165,6 +203,42 @@ const FilterItem: React.FC = ({ ); } + if (filter.type === FilterTypes.Switch) { + const value = getValueFor<(typeof filter)['type']>( + filter, + selectedFilters[filterKey], + ); + return ( + { + setSelectedFilters(prevState => ({ + ...prevState, + [filterKey]: { value: !value, type: FilterTypes.Switch }, + })); + }} + > + + + + {filter.label} + + + { + setSelectedFilters(prevState => ({ + ...prevState, + [filterKey]: { value: !value, type: FilterTypes.Switch }, + })); + }} + theme={theme} + /> + + + ); + } if (filter.type === FilterTypes.ExcludableCheckboxGroup) { const value = getValueFor<(typeof filter)['type']>( filter, @@ -344,6 +418,27 @@ const styles = StyleSheet.create({ paddingBottom: 8, paddingTop: 8, }, + switchLabelContainer: { + flex: 1, + justifyContent: 'center', + }, + switchLabel: { + fontSize: 16, + }, + switchContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginVertical: 8, + paddingHorizontal: 24, + }, + textContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginVertical: 8, + paddingHorizontal: 24, + }, pickerContainer: { flex: 1, flexDirection: 'row', From 0a170f14bca2f83eb2f54aa2d4586c5bec868f19 Mon Sep 17 00:00:00 2001 From: Adam Kemish <49228220+Soopyboo32@users.noreply.github.com> Date: Thu, 31 Oct 2024 00:02:21 +0800 Subject: [PATCH 03/29] fix: Adjust Cover Height for Screen Width Changes (#1295) --- src/components/NovelCover.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/NovelCover.tsx b/src/components/NovelCover.tsx index 27debc47c..2bbdf18e7 100644 --- a/src/components/NovelCover.tsx +++ b/src/components/NovelCover.tsx @@ -86,8 +86,7 @@ function NovelCover({ const coverHeight = useMemo( () => (window.width / numColumns) * (4 / 3), - // eslint-disable-next-line react-hooks/exhaustive-deps - [numColumns], + [window.width, numColumns], ); const selectNovel = () => onLongPress(item); From 0fad9b907f155205a46ec9d995b7360d324b7e45 Mon Sep 17 00:00:00 2001 From: Nil Silva Date: Wed, 30 Oct 2024 16:06:00 +0000 Subject: [PATCH 04/29] feat: Add User Input Support for Plugin Configurations (#1273) * Added plugin settings * Get plugin settings from plugin. --- src/hooks/persisted/usePlugins.ts | 1 + src/plugins/types/index.ts | 2 + src/screens/browse/components/BrowseTabs.tsx | 32 ++++ .../components/Modals/SourceSettings.tsx | 169 ++++++++++++++++++ strings/languages/en/strings.json | 6 +- 5 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 src/screens/browse/components/Modals/SourceSettings.tsx 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 } }} + /> + ))} + + +